Use device Robot Account to access Cloud Print.

- Search requests uses both: current cookies and OAuth2 token for device Robot Account.
- User cookies or OAuth2 used in search for operation with a printer (print, get capabilities etc.)
- Put printer accessible for Robot Account to local destination.

BUG=179229
NOTRY=True

Review URL: https://chromiumcodereview.appspot.com/14370003

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@195371 0039d316-1c4b-4281-b951-d872f2087c98
parent f77f856d
...@@ -10,10 +10,12 @@ cr.define('cloudprint', function() { ...@@ -10,10 +10,12 @@ cr.define('cloudprint', function() {
* @param {string} baseUrl Base part of the Google Cloud Print service URL * @param {string} baseUrl Base part of the Google Cloud Print service URL
* with no trailing slash. For example, * with no trailing slash. For example,
* 'https://www.google.com/cloudprint'. * 'https://www.google.com/cloudprint'.
* @param {!print_preview.NativeLayer} nativeLayer Native layer used to get
* Auth2 tokens.
* @constructor * @constructor
* @extends {cr.EventTarget} * @extends {cr.EventTarget}
*/ */
function CloudPrintInterface(baseUrl) { function CloudPrintInterface(baseUrl, nativeLayer) {
/** /**
* The base URL of the Google Cloud Print API. * The base URL of the Google Cloud Print API.
* @type {string} * @type {string}
...@@ -21,6 +23,13 @@ cr.define('cloudprint', function() { ...@@ -21,6 +23,13 @@ cr.define('cloudprint', function() {
*/ */
this.baseUrl_ = baseUrl; this.baseUrl_ = baseUrl;
/**
* Used to get Auth2 tokens.
* @type {!print_preview.NativeLayer}
* @private
*/
this.nativeLayer_ = nativeLayer;
/** /**
* Last received XSRF token. Sent as a parameter in every request. * Last received XSRF token. Sent as a parameter in every request.
* @type {string} * @type {string}
...@@ -28,12 +37,28 @@ cr.define('cloudprint', function() { ...@@ -28,12 +37,28 @@ cr.define('cloudprint', function() {
*/ */
this.xsrfToken_ = ''; this.xsrfToken_ = '';
/**
* Pending requests delayed until we get access token.
* @type {!Array.<!CloudPrintRequest>}
* @private
*/
this.requestQueue_ = [];
/** /**
* Number of outstanding cloud destination search requests. * Number of outstanding cloud destination search requests.
* @type {number} * @type {number}
* @private * @private
*/ */
this.outstandingCloudSearchRequestCount_ = 0; this.outstandingCloudSearchRequestCount_ = 0;
/**
* Event tracker used to keep track of native layer events.
* @type {!EventTracker}
* @private
*/
this.tracker_ = new EventTracker();
this.addEventListeners_();
}; };
/** /**
...@@ -97,6 +122,19 @@ cr.define('cloudprint', function() { ...@@ -97,6 +122,19 @@ cr.define('cloudprint', function() {
PRINTER: 'printer' PRINTER: 'printer'
}; };
/**
* Could Print origins used to search printers.
* @type {!Array.<!print_preview.Destination.Origin>}
* @const
* @private
*/
CloudPrintInterface.CLOUD_ORIGINS_ = [
print_preview.Destination.Origin.COOKIES,
print_preview.Destination.Origin.DEVICE
// TODO(vitalybuka): Enable when implemented.
// ready print_preview.Destination.Origin.PROFILE
];
CloudPrintInterface.prototype = { CloudPrintInterface.prototype = {
__proto__: cr.EventTarget.prototype, __proto__: cr.EventTarget.prototype,
...@@ -126,9 +164,13 @@ cr.define('cloudprint', function() { ...@@ -126,9 +164,13 @@ cr.define('cloudprint', function() {
if (isRecent) { if (isRecent) {
params.push(new HttpParam('q', '^recent')); params.push(new HttpParam('q', '^recent'));
} }
++this.outstandingCloudSearchRequestCount_; CloudPrintInterface.CLOUD_ORIGINS_.forEach(function(origin) {
this.sendRequest_('GET', 'search', params, ++this.outstandingCloudSearchRequestCount_;
this.onSearchDone_.bind(this, isRecent)); var cpRequest =
this.buildRequest_('GET', 'search', params, origin,
this.onSearchDone_.bind(this, isRecent));
this.sendOrQueueRequest_(cpRequest);
}, this);
}, },
/** /**
...@@ -157,21 +199,26 @@ cr.define('cloudprint', function() { ...@@ -157,21 +199,26 @@ cr.define('cloudprint', function() {
'__google__chrome_version=' + chromeVersion), '__google__chrome_version=' + chromeVersion),
new HttpParam('tag', '__google__os=' + navigator.platform) new HttpParam('tag', '__google__os=' + navigator.platform)
]; ];
this.sendRequest_('POST', 'submit', params, var cpRequest = this.buildRequest_('POST', 'submit', params,
this.onSubmitDone_.bind(this)); destination.origin,
this.onSubmitDone_.bind(this));
this.sendOrQueueRequest_(cpRequest);
}, },
/** /**
* Sends a Google Cloud Print printer API request. * Sends a Google Cloud Print printer API request.
* @param {string} printerId ID of the printer to lookup. * @param {string} printerId ID of the printer to lookup.
* @param {!print_preview.Destination.Origin} origin Origin of the printer.
*/ */
printer: function(printerId) { printer: function(printerId, origin) {
var params = [ var params = [
new HttpParam('printerid', printerId), new HttpParam('printerid', printerId),
new HttpParam('use_cdd', 'true') new HttpParam('use_cdd', 'true')
]; ];
this.sendRequest_('GET', 'printer', params, var cpRequest =
this.onPrinterDone_.bind(this, printerId)); this.buildRequest_('GET', 'printer', params, origin,
this.onPrinterDone_.bind(this, printerId));
this.sendOrQueueRequest_(cpRequest);
}, },
/** /**
...@@ -179,16 +226,30 @@ cr.define('cloudprint', function() { ...@@ -179,16 +226,30 @@ cr.define('cloudprint', function() {
* terms-of-service of the given printer. * terms-of-service of the given printer.
* @param {string} printerId ID of the printer to accept the * @param {string} printerId ID of the printer to accept the
* terms-of-service for. * terms-of-service for.
* @param {!print_preview.Destination.Origin} origin Origin of the printer.
* @param {boolean} isAccepted Whether the user accepted the * @param {boolean} isAccepted Whether the user accepted the
* terms-of-service. * terms-of-service.
*/ */
updatePrinterTosAcceptance: function(printerId, isAccepted) { updatePrinterTosAcceptance: function(printerId, origin, isAccepted) {
var params = [ var params = [
new HttpParam('printerid', printerId), new HttpParam('printerid', printerId),
new HttpParam('is_tos_accepted', isAccepted) new HttpParam('is_tos_accepted', isAccepted)
]; ];
this.sendRequest_('POST', 'update', params, var cpRequest =
this.onUpdatePrinterTosAcceptanceDone_.bind(this)); this.buildRequest_('POST', 'update', params, origin,
this.onUpdatePrinterTosAcceptanceDone_.bind(this));
this.sendOrQueueRequest_(cpRequest);
},
/**
* Adds event listeners to the relevant native layer events.
* @private
*/
addEventListeners_: function() {
this.tracker_.add(
this.nativeLayer_,
print_preview.NativeLayer.EventType.ACCESS_TOKEN_READY,
this.onAccessTokenReady_.bind(this));
}, },
/** /**
...@@ -246,22 +307,28 @@ cr.define('cloudprint', function() { ...@@ -246,22 +307,28 @@ cr.define('cloudprint', function() {
}, },
/** /**
* Sends a request to the Google Cloud Print API. * Builds request to the Google Cloud Print API.
* @param {string} method HTTP method of the request. * @param {string} method HTTP method of the request.
* @param {string} action Google Cloud Print action to perform. * @param {string} action Google Cloud Print action to perform.
* @param {Array.<!HttpParam>} params HTTP parameters to include in the * @param {Array.<!HttpParam>} params HTTP parameters to include in the
* request. * request.
* @param {function(number, Object)} callback Callback to invoke when * @param {!print_preview.Destination.Origin} origin Origin for destination.
* request completes. * @param {function(number, Object, !print_preview.Destination.Origin)}
* callback Callback to invoke when request completes.
* @return {!CloudPrintRequest} Partially prepared request.
* @private
*/ */
sendRequest_: function(method, action, params, callback) { buildRequest_: function(method, action, params, origin, callback) {
if (!this.xsrfToken_) { var url = this.baseUrl_ + '/' + action + '?xsrf=';
// TODO(rltoscano): Should throw an error if not a read-only action or if (origin == print_preview.Destination.Origin.COOKIES) {
// issue an xsrf token request. if (!this.xsrfToken_) {
// TODO(rltoscano): Should throw an error if not a read-only action or
// issue an xsrf token request.
} else {
url = url + this.xsrfToken_;
}
} }
var url = this.baseUrl_ + '/' + action + '?xsrf=' + this.xsrfToken_;
var body = null; var body = null;
if (params) { if (params) {
if (method == 'GET') { if (method == 'GET') {
url = params.reduce(function(partialUrl, param) { url = params.reduce(function(partialUrl, param) {
...@@ -286,50 +353,106 @@ cr.define('cloudprint', function() { ...@@ -286,50 +353,106 @@ cr.define('cloudprint', function() {
} }
var xhr = new XMLHttpRequest(); var xhr = new XMLHttpRequest();
xhr.onreadystatechange =
this.onReadyStateChange_.bind(this, xhr, callback);
xhr.open(method, url, true); xhr.open(method, url, true);
xhr.withCredentials = true; xhr.withCredentials =
(origin == print_preview.Destination.Origin.COOKIES);
for (var header in headers) { for (var header in headers) {
xhr.setRequestHeader(header, headers[header]); xhr.setRequestHeader(header, headers[header]);
} }
xhr.send(body);
return new CloudPrintRequest(xhr, body, origin, callback);
},
/**
* Sends a request to the Google Cloud Print API or queues if it needs to
* wait OAuth2 access token.
* @param {!CloudPrintRequest} request Request to send or queue.
* @private
*/
sendOrQueueRequest_: function(request) {
if (request.origin == print_preview.Destination.Origin.COOKIES) {
return this.sendRequest_(request);
} else {
this.requestQueue_.push(request);
this.nativeLayer_.startGetAccessToken(request.origin);
}
},
/**
* Sends a request to the Google Cloud Print API.
* @param {!CloudPrintRequest} request Request to send.
* @private
*/
sendRequest_: function(request) {
request.xhr.onreadystatechange =
this.onReadyStateChange_.bind(this, request);
request.xhr.send(request.body);
}, },
/** /**
* Creates a Google Cloud Print interface error that is ready to dispatch. * Creates a Google Cloud Print interface error that is ready to dispatch.
* @param {!CloudPrintInterface.EventType} type Type of the error. * @param {!CloudPrintInterface.EventType} type Type of the error.
* @param {number} status HTTP status code of the failed request. * @param {!CloudPrintRequest} request Request that has been completed.
* @param {Object} result JSON response of the request. {@code null} if
* status was not 200.
* @return {!cr.Event} Google Cloud Print interface error event. * @return {!cr.Event} Google Cloud Print interface error event.
* @private * @private
*/ */
createErrorEvent_: function(type, status, result) { createErrorEvent_: function(type, request) {
var errorEvent = new cr.Event(type); var errorEvent = new cr.Event(type);
errorEvent.status = status; errorEvent.status = request.xhr.status;
errorEvent.errorCode = status == 200 ? result['errorCode'] : 0; if (request.xhr.status == 200) {
errorEvent.message = status == 200 ? result['message'] : ''; errorEvent.errorCode = request.result['errorCode'];
errorEvent.message = request.result['message'];
} else {
errorEvent.errorCode = 0;
errorEvent.message = '';
}
errorEvent.origin = request.origin;
return errorEvent; return errorEvent;
}, },
/**
* Called when a native layer receives access token.
* @param {cr.Event} evt Contains the authetication type and access token.
* @private
*/
onAccessTokenReady_: function(event) {
// TODO(vitalybuka): remove when other Origins implemented.
assert(event.authType == print_preview.Destination.Origin.DEVICE);
this.requestQueue_ = this.requestQueue_.filter(function(request) {
assert(request.origin == print_preview.Destination.Origin.DEVICE);
if (request.origin != event.authType) {
return true;
}
if (event.accessToken) {
request.xhr.setRequestHeader('Authorization',
'Bearer ' + event.accessToken);
this.sendRequest_(request);
} else { // No valid token.
// Without abort status does not exists.
request.xhr.abort();
request.callback(request);
}
return false;
}, this);
},
/** /**
* Called when the ready-state of a XML http request changes. * Called when the ready-state of a XML http request changes.
* Calls the successCallback with the result or dispatches an ERROR event. * Calls the successCallback with the result or dispatches an ERROR event.
* @param {XMLHttpRequest} xhr XML http request that changed. * @param {!CloudPrintRequest} request Request that was changed.
* @param {function(number, Object)} callback Callback to invoke when
* request completes.
* @private * @private
*/ */
onReadyStateChange_: function(xhr, callback) { onReadyStateChange_: function(request) {
if (xhr.readyState == 4) { if (request.xhr.readyState == 4) {
if (xhr.status == 200) { if (request.xhr.status == 200) {
var result = JSON.parse(xhr.responseText); request.result = JSON.parse(request.xhr.responseText);
if (result['success']) { if (request.origin == print_preview.Destination.Origin.COOKIES &&
this.xsrfToken_ = result['xsrf_token']; request.result['success']) {
this.xsrfToken_ = request.result['xsrf_token'];
} }
} }
callback(xhr.status, result); request.status = request.xhr.status;
request.callback(request);
} }
}, },
...@@ -337,20 +460,19 @@ cr.define('cloudprint', function() { ...@@ -337,20 +460,19 @@ cr.define('cloudprint', function() {
* Called when the search request completes. * Called when the search request completes.
* @param {boolean} isRecent Whether the search request was for recent * @param {boolean} isRecent Whether the search request was for recent
* destinations. * destinations.
* @param {number} status Status of the HTTP request. * @param {!CloudPrintRequest} request Request that has been completed.
* @param {Object} result JSON response.
* @private * @private
*/ */
onSearchDone_: function(isRecent, status, result) { onSearchDone_: function(isRecent, request) {
--this.outstandingCloudSearchRequestCount_; --this.outstandingCloudSearchRequestCount_;
if (status == 200 && result['success']) { if (request.xhr.status == 200 && request.result['success']) {
var printerListJson = result['printers'] || []; var printerListJson = request.result['printers'] || [];
var printerList = []; var printerList = [];
printerListJson.forEach(function(printerJson) { printerListJson.forEach(function(printerJson) {
try { try {
printerList.push( printerList.push(
cloudprint.CloudDestinationParser.parse( cloudprint.CloudDestinationParser.parse(printerJson,
printerJson, print_preview.Destination.Origin.COOKIES)); request.origin));
} catch (err) { } catch (err) {
console.error('Unable to parse cloud print destination: ' + err); console.error('Unable to parse cloud print destination: ' + err);
} }
...@@ -358,31 +480,31 @@ cr.define('cloudprint', function() { ...@@ -358,31 +480,31 @@ cr.define('cloudprint', function() {
var searchDoneEvent = var searchDoneEvent =
new cr.Event(CloudPrintInterface.EventType.SEARCH_DONE); new cr.Event(CloudPrintInterface.EventType.SEARCH_DONE);
searchDoneEvent.printers = printerList; searchDoneEvent.printers = printerList;
searchDoneEvent.origin = request.origin;
searchDoneEvent.isRecent = isRecent; searchDoneEvent.isRecent = isRecent;
searchDoneEvent.email = result['request']['user']; searchDoneEvent.email = request.result['request']['user'];
this.dispatchEvent(searchDoneEvent); this.dispatchEvent(searchDoneEvent);
} else { } else {
var errorEvent = this.createErrorEvent_( var errorEvent = this.createErrorEvent_(
CloudPrintInterface.EventType.SEARCH_FAILED, status, result); CloudPrintInterface.EventType.SEARCH_FAILED, request);
this.dispatchEvent(errorEvent); this.dispatchEvent(errorEvent);
} }
}, },
/** /**
* Called when the submit request completes. * Called when the submit request completes.
* @param {number} status Status of the HTTP request. * @param {!CloudPrintRequest} request Request that has been completed.
* @param {Object} result JSON response.
* @private * @private
*/ */
onSubmitDone_: function(status, result) { onSubmitDone_: function(request) {
if (status == 200 && result['success']) { if (request.xhr.status == 200 && request.result['success']) {
var submitDoneEvent = new cr.Event( var submitDoneEvent = new cr.Event(
CloudPrintInterface.EventType.SUBMIT_DONE); CloudPrintInterface.EventType.SUBMIT_DONE);
submitDoneEvent.jobId = result['job']['id']; submitDoneEvent.jobId = result['job']['id'];
this.dispatchEvent(submitDoneEvent); this.dispatchEvent(submitDoneEvent);
} else { } else {
var errorEvent = this.createErrorEvent_( var errorEvent = this.createErrorEvent_(
CloudPrintInterface.EventType.SUBMIT_FAILED, status, result); CloudPrintInterface.EventType.SUBMIT_FAILED, request);
this.dispatchEvent(errorEvent); this.dispatchEvent(errorEvent);
} }
}, },
...@@ -390,17 +512,16 @@ cr.define('cloudprint', function() { ...@@ -390,17 +512,16 @@ cr.define('cloudprint', function() {
/** /**
* Called when the printer request completes. * Called when the printer request completes.
* @param {string} destinationId ID of the destination that was looked up. * @param {string} destinationId ID of the destination that was looked up.
* @param {number} status Status of the HTTP request. * @param {!CloudPrintRequest} request Request that has been completed.
* @param {Object} result JSON response.
* @private * @private
*/ */
onPrinterDone_: function(destinationId, status, result) { onPrinterDone_: function(destinationId, request) {
if (status == 200 && result['success']) { if (request.xhr.status == 200 && request.result['success']) {
var printerJson = result['printers'][0]; var printerJson = request.result['printers'][0];
var printer; var printer;
try { try {
printer = cloudprint.CloudDestinationParser.parse( printer = cloudprint.CloudDestinationParser.parse(printerJson,
printerJson, print_preview.Destination.Origin.COOKIES); request.origin);
} catch (err) { } catch (err) {
console.error('Failed to parse cloud print destination: ' + console.error('Failed to parse cloud print destination: ' +
JSON.stringify(printerJson)); JSON.stringify(printerJson));
...@@ -412,30 +533,70 @@ cr.define('cloudprint', function() { ...@@ -412,30 +533,70 @@ cr.define('cloudprint', function() {
this.dispatchEvent(printerDoneEvent); this.dispatchEvent(printerDoneEvent);
} else { } else {
var errorEvent = this.createErrorEvent_( var errorEvent = this.createErrorEvent_(
CloudPrintInterface.EventType.PRINTER_FAILED, status, result); CloudPrintInterface.EventType.PRINTER_FAILED, request);
errorEvent.destinationId = destinationId; errorEvent.destinationId = destinationId;
errorEvent.destinationOrigin = print_preview.Destination.Origin.COOKIES; errorEvent.destinationOrigin = request.origin;
this.dispatchEvent(errorEvent); this.dispatchEvent(errorEvent, request.origin);
} }
}, },
/** /**
* Called when the update printer TOS acceptance request completes. * Called when the update printer TOS acceptance request completes.
* @param {number} status Status of the HTTP request. * @param {!CloudPrintRequest} request Request that has been completed.
* @param {Object} result JSON response.
* @private * @private
*/ */
onUpdatePrinterTosAcceptanceDone_: function(status, result) { onUpdatePrinterTosAcceptanceDone_: function(request) {
if (status == 200 && result['success']) { if (request.xhr.status == 200 && request.result['success']) {
// Do nothing. // Do nothing.
} else { } else {
var errorEvent = this.createErrorEvent_( var errorEvent = this.createErrorEvent_(
CloudPrintInterface.EventType.SUBMIT_FAILED, status, result); CloudPrintInterface.EventType.SUBMIT_FAILED, request);
this.dispatchEvent(errorEvent); this.dispatchEvent(errorEvent);
} }
} }
}; };
/**
* Data structure that holds data for Cloud Print requests.
* @param {!XMLHttpRequest} xhr Partially prepared http request.
* @param {string} body Data to send with POST requests.
* @param {!print_preview.Destination.Origin} origin Origin for destination.
* @param {function(!CloudPrintRequest)} callback Callback to invoke when
* request completes.
* @constructor
*/
function CloudPrintRequest(xhr, body, origin, callback) {
/**
* Partially prepared http request.
* @type {!XMLHttpRequest}
*/
this.xhr = xhr;
/**
* Data to send with POST requests.
* @type {string}
*/
this.body = body;
/**
* Origin for destination.
* @type {!print_preview.Destination.Origin}
*/
this.origin = origin;
/**
* Callback to invoke when request completes.
* @type {function(!CloudPrintRequest)}
*/
this.callback = callback;
/**
* Result for requests.
* @type {Object} JSON response.
*/
this.result = null;
};
/** /**
* Data structure that represents an HTTP parameter. * Data structure that represents an HTTP parameter.
* @param {string} name Name of the parameter. * @param {string} name Name of the parameter.
......
...@@ -308,6 +308,7 @@ cr.define('print_preview', function() { ...@@ -308,6 +308,7 @@ cr.define('print_preview', function() {
'not enabled'); 'not enabled');
destination.isTosAccepted = true; destination.isTosAccepted = true;
this.cloudPrintInterface_.updatePrinterTosAcceptance(destination.id, this.cloudPrintInterface_.updatePrinterTosAcceptance(destination.id,
destination.origin,
true); true);
} }
this.appState_.persistSelectedDestination(this.selectedDestination_); this.appState_.persistSelectedDestination(this.selectedDestination_);
......
...@@ -72,8 +72,10 @@ cr.define('print_preview', function() { ...@@ -72,8 +72,10 @@ cr.define('print_preview', function() {
* @private * @private
*/ */
onCloudPrintSearchDone_: function(event) { onCloudPrintSearchDone_: function(event) {
this.userEmail_ = event.email; if (event.origin == print_preview.Destination.Origin.COOKIES) {
cr.dispatchSimpleEvent(this, UserInfo.EventType.EMAIL_CHANGE); this.userEmail_ = event.email;
cr.dispatchSimpleEvent(this, UserInfo.EventType.EMAIL_CHANGE);
}
} }
}; };
......
...@@ -496,7 +496,8 @@ cr.define('print_preview', function() { ...@@ -496,7 +496,8 @@ cr.define('print_preview', function() {
*/ */
onCloudPrintEnable_: function(event) { onCloudPrintEnable_: function(event) {
this.cloudPrintInterface_ = this.cloudPrintInterface_ =
new cloudprint.CloudPrintInterface(event.baseCloudPrintUrl); new cloudprint.CloudPrintInterface(event.baseCloudPrintUrl,
this.nativeLayer_);
this.tracker.add( this.tracker.add(
this.cloudPrintInterface_, this.cloudPrintInterface_,
cloudprint.CloudPrintInterface.EventType.SUBMIT_DONE, cloudprint.CloudPrintInterface.EventType.SUBMIT_DONE,
......
...@@ -284,7 +284,8 @@ cr.define('print_preview', function() { ...@@ -284,7 +284,8 @@ cr.define('print_preview', function() {
if (destination.isRecent) { if (destination.isRecent) {
recentDestinations.push(destination); recentDestinations.push(destination);
} }
if (destination.isLocal) { if (destination.isLocal ||
destination.origin == print_preview.Destination.Origin.DEVICE) {
localDestinations.push(destination); localDestinations.push(destination);
} else { } else {
cloudDestinations.push(destination); cloudDestinations.push(destination);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment