Separate Simple Backend creation from initialization.
[chromium-blink-merge.git] / remoting / webapp / identity.js
blobd4e2b6acc8f51ecbc55e580c13e31c5161f31a83
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.
5 /**
6 * @fileoverview
7 * Wrapper class for Chrome's identity API.
8 */
10 'use strict';
12 /** @suppress {duplicate} */
13 var remoting = remoting || {};
15 /**
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;
23 /**
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").
27 * @constructor
29 remoting.Identity = function(consentCallback) {
30 /** @private */
31 this.consentCallback_ = consentCallback;
32 /** @type {?string} @private */
33 this.email_ = null;
34 /** @type {Array.<remoting.Identity.Callbacks>} */
35 this.pendingCallbacks_ = [];
38 /**
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));
60 /**
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
66 * error occurs.
67 * @return {void} Nothing.
69 remoting.Identity.prototype.getEmail = function(onOk, onError) {
70 /** @type {remoting.Identity} */
71 var that = this;
72 /** @param {XMLHttpRequest} xhr The XHR response. */
73 var onResponse = function(xhr) {
74 var email = null;
75 if (xhr.status == 200) {
76 email = xhr.responseText.split('&')[0].split('=')[1];
77 that.email_ = email;
78 onOk(email);
79 return;
81 console.error('Unable to get email address:', xhr.status, xhr);
82 if (xhr.status == 401) {
83 onError(remoting.Error.AUTHENTICATION_FAILED);
84 } else {
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
102 * has been made.
104 * @return {?string} The cached email address, if available.
106 remoting.Identity.prototype.getCachedEmail = function() {
107 return this.email_;
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.
116 * @private
118 remoting.Identity.prototype.interpretUnexpectedXhrStatus_ = function(
119 xhrStatus) {
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;
128 } else {
129 console.warn('Unexpected authentication response code: ' + xhrStatus);
131 return error;
135 * Callback for the getAuthToken API.
137 * @param {boolean} interactive The value of the "interactive" parameter to
138 * getAuthToken.
139 * @param {?string} token The auth token, or null if the request failed.
140 * @private
142 remoting.Identity.prototype.onAuthComplete_ = function(interactive, token) {
143 // Pass the token to the callback(s) if it was retrieved successfully.
144 if (token) {
145 while (this.pendingCallbacks_.length > 0) {
146 var callback = /** @type {remoting.Identity.Callbacks} */
147 this.pendingCallbacks_.shift();
148 callback.onOk(token);
150 return;
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.
156 if (interactive) {
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);
163 return;
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.
174 * @private
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
187 * @constructor
188 * @private
190 remoting.Identity.Callbacks = function(onOk, onError) {
191 /** @type {function(string):void} */
192 this.onOk = onOk;
193 /** @type {function(remoting.Error):void} */
194 this.onError = onError;