Chromium Code Reviews| Index: remoting/webapp/identity.js |
| diff --git a/remoting/webapp/identity.js b/remoting/webapp/identity.js |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..3734bc6530eab4e0688257a026ea9b4ed8ef2de9 |
| --- /dev/null |
| +++ b/remoting/webapp/identity.js |
| @@ -0,0 +1,193 @@ |
| +// Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| +// Use of this source code is governed by a BSD-style license that can be |
| +// found in the LICENSE file. |
| + |
| +/** |
| + * @fileoverview |
| + * Wrapper class for Chrome's identity API. |
| + */ |
| + |
| +'use strict'; |
| + |
| +/** @suppress {duplicate} */ |
| +var remoting = remoting || {}; |
| + |
| +/** |
| + * TODO(jamiewalch): Remove the remoting.OAuth2 possibility when the Apps v2 |
|
Wez
2013/01/05 00:04:24
nit: "the" and "possibility" are redundant here.
Jamie
2013/01/05 01:32:54
Done.
|
| + * work is complete. |
| + * |
| + * @type {remoting.Identity|remoting.OAuth2} |
| + */ |
| +remoting.identity = null; |
| + |
| + |
| +/** |
| + * @param {HTMLElement} authDialog The authentication dialog, to be displayed |
| + * if user permission is required to obtain an access token. |
| + * @param {HTMLElement} authButton The "Continue" button, which must be a |
| + * visible child of the dialog. |
| + * @constructor |
| + */ |
| +remoting.Identity = function(authDialog, authButton) { |
|
Wez
2013/01/05 00:04:24
This arrangement is a little strange; this class s
Jamie
2013/01/05 01:32:54
Done.
|
| + /** @private */ |
| + this.dialog_ = authDialog; |
| + /** @type {?string} @private */ |
| + this.email_ = null; |
| + /** @type {Array.<remoting.Identity.Callbacks>} */ |
| + this.pendingCallbacks_ = []; |
| + authButton.addEventListener('click', this.onAuthContinue_.bind(this), false); |
| +}; |
| + |
| +/** |
| + * Call a function with an access token. |
| + * |
| + * @param {function(string):void} onOk Function to invoke with access token if |
| + * an access token was successfully retrieved. |
| + * @param {function(remoting.Error):void} onError Function to invoke with an |
| + * error code on failure. |
| + * @return {void} Nothing. |
| + */ |
| +remoting.Identity.prototype.callWithToken = function(onOk, onError) { |
| + this.pendingCallbacks_.push(new remoting.Identity.Callbacks(onOk, onError)); |
| + if (this.pendingCallbacks_.length == 1) { |
| + chrome.experimental.identity.getAuthToken( |
| + { 'interactive': false }, |
| + this.onAuthComplete_.bind(this, false)); |
| + } |
| +}; |
| + |
| +/** |
| + * Get the user's email address. |
| + * |
| + * @param {function(string):void} onOk Callback invoked when the email |
| + * address is available. |
| + * @param {function(remoting.Error):void} onError Callback invoked if an |
| + * error occurs. |
| + * @return {void} Nothing. |
| + */ |
| +remoting.Identity.prototype.getEmail = function(onOk, onError) { |
| + /** @type {remoting.Identity} */ |
| + var that = this; |
| + /** @param {XMLHttpRequest} xhr The XHR response. */ |
| + var onResponse = function(xhr) { |
| + var email = null; |
| + if (xhr.status == 200) { |
| + // TODO(ajwong): See if we can't find a JSON endpoint. |
|
Wez
2013/01/05 00:04:24
What does this TODO mean?
Jamie
2013/01/05 01:32:54
It's essentially the same as the next TODO. I've r
|
| + email = xhr.responseText.split('&')[0].split('=')[1]; |
| + that.email_ = email; |
| + onOk(email); |
| + return; |
| + } |
| + console.error('Unable to get email address:', xhr.status, xhr); |
| + if (xhr.status == 401) { |
| + onError(remoting.Error.AUTHENTICATION_FAILED); |
| + } else { |
| + onError(that.interpretUnexpectedXhrStatus_(xhr.status)); |
| + } |
| + }; |
| + |
| + /** @param {string} token The access token. */ |
| + var getEmailFromToken = function(token) { |
| + var headers = { 'Authorization': 'OAuth ' + token }; |
| + // TODO(ajwong): Update to new v2 API. |
|
Wez
2013/01/05 00:04:24
Does this mean use a new v2 Google API, or use an
Jamie
2013/01/05 01:32:54
There's an endpoint, https://www.googleapis.com/oa
Wez
2013/01/07 23:52:44
OK, great - can you create a bug for migrating to
Jamie
2013/01/08 22:09:10
I think Renato is in the process of fixing it as w
|
| + remoting.xhr.get('https://www.googleapis.com/userinfo/email', |
| + onResponse, '', headers); |
| + }; |
| + |
| + this.callWithToken(getEmailFromToken, onError); |
| +}; |
| + |
| +/** |
| + * Get the user's email address, or null if no successful call to getEmail |
| + * has been made. |
|
Wez
2013/01/05 00:04:24
nit: Worth noting that this may return a valid ema
Jamie
2013/01/05 01:32:54
I don't think that would add much.
|
| + * |
| + * @return {?string} The cached email address, if available. |
| + */ |
| +remoting.Identity.prototype.getCachedEmail = function() { |
| + return this.email_; |
| +}; |
| + |
| +/** |
| + * Interprets unexpected HTTP response codes to authentication XMLHttpRequests. |
| + * The caller should handle the usual expected responses (200, 400) separately. |
| + * |
| + * @param {number} xhrStatus Status (HTTP response code) of the XMLHttpRequest. |
| + * @return {remoting.Error} An error code to be raised. |
| + * @private |
| + */ |
| +remoting.Identity.prototype.interpretUnexpectedXhrStatus_ = function( |
| + xhrStatus) { |
| + // Return AUTHENTICATION_FAILED by default, so that the user can try to |
| + // recover from an unexpected failure by signing in again. |
| + /** @type {remoting.Error} */ |
| + var error = remoting.Error.AUTHENTICATION_FAILED; |
| + if (xhrStatus == 502 || xhrStatus == 503) { |
| + error = remoting.Error.SERVICE_UNAVAILABLE; |
| + } else if (xhrStatus == 0) { |
| + error = remoting.Error.NETWORK_FAILURE; |
| + } else { |
| + console.warn('Unexpected authentication response code: ' + xhrStatus); |
| + } |
| + return error; |
| +}; |
| + |
| +/** |
| + * Callback for the getAuthToken API. |
| + * |
| + * @param {boolean} interactive |
| + * @param {?string} token |
| + * @private |
| + */ |
| +remoting.Identity.prototype.onAuthComplete_ = function(interactive, token) { |
|
Wez
2013/01/05 00:04:24
nit: interactive -> was_interactive?
Jamie
2013/01/05 01:32:54
I've left this as it was, but added some descripti
|
| + // Pass the token to the callback(s) if it was retrieved successfully. |
| + if (token) { |
| + for (var i = 0; i < this.pendingCallbacks_.length; ++i) { |
| + this.pendingCallbacks_[i].onOk(token); |
|
Wez
2013/01/05 00:04:24
nit: Can you re-write this as a while(!empty) loop
Jamie
2013/01/05 01:32:54
Done, and also below.
|
| + } |
| + this.pendingCallbacks_ = []; |
| + return; |
| + } |
| + |
| + // If not, pass an error back to the callback(s) if we've already prompted the |
| + // user for permission. |
| + // TODO(jamiewalch): Figure out what to do with the error in this case. |
| + if (interactive) { |
| + console.error(chrome.runtime.lastError); |
| + for (var i = 0; i < this.pendingCallbacks_.length; ++i) { |
| + this.pendingCallbacks_[i].onError(remoting.Error.UNEXPECTED); |
| + } |
| + this.pendingCallbacks_ = []; |
| + return; |
| + } |
| + |
| + // If there's no token, but we haven't yet prompted for permission, do so |
| + // now. The flow will continue via the Continue button event handler. |
| + this.dialog_.hidden = false; |
| +}; |
| + |
| +/** |
| + * Called in response to the user signing in to the web-app. |
| + * |
| + * @private |
| + */ |
| +remoting.Identity.prototype.onAuthContinue_ = function() { |
| + this.dialog_.hidden = true; |
| + chrome.experimental.identity.getAuthToken( |
| + { 'interactive': true }, |
| + this.onAuthComplete_.bind(this, true)); |
| +}; |
| + |
| +/** |
| + * Internal representation for pair of callWithToken callbacks. |
| + * |
| + * @param {function(string):void} onOk |
| + * @param {function(remoting.Error):void} onError |
| + * @constructor |
| + * @private |
| + */ |
| +remoting.Identity.Callbacks = function(onOk, onError) { |
| + /** @type {function(string):void} */ |
| + this.onOk = onOk; |
| + /** @type {function(remoting.Error):void} */ |
| + this.onError = onError; |
| +}; |