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; |
+}; |