1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
7 * Wrapper class for Chrome's identity API.
12 /** @suppress {duplicate} */
13 var remoting
= remoting
|| {};
16 * TODO(jamiewalch): Remove remoting.OAuth2 from this type annotation when
17 * the Apps v2 work is complete.
19 * @type {remoting.Identity|remoting.OAuth2}
21 remoting
.identity
= null;
24 * @param {function(function():void):void} consentCallback Callback invoked if
25 * user consent is required. The callback is passed a continuation function
26 * which must be called from an interactive event handler (e.g. "click").
29 remoting
.Identity = function(consentCallback
) {
31 this.consentCallback_
= consentCallback
;
32 /** @type {?string} @private */
34 /** @type {Array.<remoting.Identity.Callbacks>} */
35 this.pendingCallbacks_
= [];
39 * Call a function with an access token.
41 * TODO(jamiewalch): Currently, this results in a new GAIA token being minted
42 * each time the function is called. Implement caching functionality unless
43 * getAuthToken starts doing so itself.
45 * @param {function(string):void} onOk Function to invoke with access token if
46 * an access token was successfully retrieved.
47 * @param {function(remoting.Error):void} onError Function to invoke with an
48 * error code on failure.
49 * @return {void} Nothing.
51 remoting
.Identity
.prototype.callWithToken = function(onOk
, onError
) {
52 this.pendingCallbacks_
.push(new remoting
.Identity
.Callbacks(onOk
, onError
));
53 if (this.pendingCallbacks_
.length
== 1) {
54 chrome
.experimental
.identity
.getAuthToken(
55 { 'interactive': false },
56 this.onAuthComplete_
.bind(this, false));
61 * Get the user's email address.
63 * @param {function(string):void} onOk Callback invoked when the email
64 * address is available.
65 * @param {function(remoting.Error):void} onError Callback invoked if an
67 * @return {void} Nothing.
69 remoting
.Identity
.prototype.getEmail = function(onOk
, onError
) {
70 /** @type {remoting.Identity} */
72 /** @param {XMLHttpRequest} xhr The XHR response. */
73 var onResponse = function(xhr
) {
75 if (xhr
.status
== 200) {
76 email
= xhr
.responseText
.split('&')[0].split('=')[1];
81 console
.error('Unable to get email address:', xhr
.status
, xhr
);
82 if (xhr
.status
== 401) {
83 onError(remoting
.Error
.AUTHENTICATION_FAILED
);
85 onError(that
.interpretUnexpectedXhrStatus_(xhr
.status
));
89 /** @param {string} token The access token. */
90 var getEmailFromToken = function(token
) {
91 var headers
= { 'Authorization': 'OAuth ' + token
};
92 // TODO(ajwong): Update to new v2 API.
93 remoting
.xhr
.get('https://www.googleapis.com/userinfo/email',
94 onResponse
, '', headers
);
97 this.callWithToken(getEmailFromToken
, onError
);
101 * Get the user's email address, or null if no successful call to getEmail
104 * @return {?string} The cached email address, if available.
106 remoting
.Identity
.prototype.getCachedEmail = function() {
111 * Interprets unexpected HTTP response codes to authentication XMLHttpRequests.
112 * The caller should handle the usual expected responses (200, 400) separately.
114 * @param {number} xhrStatus Status (HTTP response code) of the XMLHttpRequest.
115 * @return {remoting.Error} An error code to be raised.
118 remoting
.Identity
.prototype.interpretUnexpectedXhrStatus_ = function(
120 // Return AUTHENTICATION_FAILED by default, so that the user can try to
121 // recover from an unexpected failure by signing in again.
122 /** @type {remoting.Error} */
123 var error
= remoting
.Error
.AUTHENTICATION_FAILED
;
124 if (xhrStatus
== 502 || xhrStatus
== 503) {
125 error
= remoting
.Error
.SERVICE_UNAVAILABLE
;
126 } else if (xhrStatus
== 0) {
127 error
= remoting
.Error
.NETWORK_FAILURE
;
129 console
.warn('Unexpected authentication response code: ' + xhrStatus
);
135 * Callback for the getAuthToken API.
137 * @param {boolean} interactive The value of the "interactive" parameter to
139 * @param {?string} token The auth token, or null if the request failed.
142 remoting
.Identity
.prototype.onAuthComplete_ = function(interactive
, token
) {
143 // Pass the token to the callback(s) if it was retrieved successfully.
145 while (this.pendingCallbacks_
.length
> 0) {
146 var callback
= /** @type {remoting.Identity.Callbacks} */
147 this.pendingCallbacks_
.shift();
148 callback
.onOk(token
);
153 // If not, pass an error back to the callback(s) if we've already prompted the
154 // user for permission.
155 // TODO(jamiewalch): Figure out what to do with the error in this case.
157 console
.error(chrome
.runtime
.lastError
);
158 while (this.pendingCallbacks_
.length
> 0) {
159 var callback
= /** @type {remoting.Identity.Callbacks} */
160 this.pendingCallbacks_
.shift();
161 callback
.onError(remoting
.Error
.UNEXPECTED
);
166 // If there's no token, but we haven't yet prompted for permission, do so
167 // now. The consent callback is responsible for continuing the auth flow.
168 this.consentCallback_(this.onAuthContinue_
.bind(this));
172 * Called in response to the user signing in to the web-app.
176 remoting
.Identity
.prototype.onAuthContinue_ = function() {
177 chrome
.experimental
.identity
.getAuthToken(
178 { 'interactive': true },
179 this.onAuthComplete_
.bind(this, true));
183 * Internal representation for pair of callWithToken callbacks.
185 * @param {function(string):void} onOk
186 * @param {function(remoting.Error):void} onError
190 remoting
.Identity
.Callbacks = function(onOk
, onError
) {
191 /** @type {function(string):void} */
193 /** @type {function(remoting.Error):void} */
194 this.onError
= onError
;