OLD | NEW |
---|---|
(Empty) | |
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. | |
4 | |
5 /** | |
6 * @fileoverview | |
7 * Wrapper class for Chrome's identity API. | |
8 */ | |
9 | |
10 'use strict'; | |
11 | |
12 /** @suppress {duplicate} */ | |
13 var remoting = remoting || {}; | |
14 | |
15 /** | |
16 * TODO(jamiewalch): Remove remoting.OAuth2 from this type annotation when | |
17 * the Apps v2 work is complete. | |
18 * | |
19 * @type {remoting.Identity|remoting.OAuth2} | |
20 */ | |
21 remoting.identity = null; | |
22 | |
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 a "click" event handler. | |
Wez
2013/01/07 23:52:44
nit: Is that the actual requirement, or will any s
Jamie
2013/01/08 22:09:10
I've only tested it with a "click" handler, but I
| |
27 * @constructor | |
28 */ | |
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_ = []; | |
36 }; | |
37 | |
38 /** | |
39 * Call a function with an access token. | |
40 * | |
41 * @param {function(string):void} onOk Function to invoke with access token if | |
42 * an access token was successfully retrieved. | |
43 * @param {function(remoting.Error):void} onError Function to invoke with an | |
44 * error code on failure. | |
45 * @return {void} Nothing. | |
46 */ | |
47 remoting.Identity.prototype.callWithToken = function(onOk, onError) { | |
48 this.pendingCallbacks_.push(new remoting.Identity.Callbacks(onOk, onError)); | |
rmsousa
2013/01/05 05:21:11
Do we really want to do mint a new token on every
Jamie
2013/01/07 23:52:20
Thanks for checking on this. I think you're right
Wez
2013/01/07 23:52:44
Access tokens last about an hour, I think, so duri
Jamie
2013/01/08 22:09:10
I don't think it does--that's what Renato's commen
| |
49 if (this.pendingCallbacks_.length == 1) { | |
50 chrome.experimental.identity.getAuthToken( | |
51 { 'interactive': false }, | |
52 this.onAuthComplete_.bind(this, false)); | |
53 } | |
54 }; | |
55 | |
56 /** | |
57 * Get the user's email address. | |
58 * | |
59 * @param {function(string):void} onOk Callback invoked when the email | |
60 * address is available. | |
61 * @param {function(remoting.Error):void} onError Callback invoked if an | |
62 * error occurs. | |
63 * @return {void} Nothing. | |
64 */ | |
65 remoting.Identity.prototype.getEmail = function(onOk, onError) { | |
66 /** @type {remoting.Identity} */ | |
rmsousa
2013/01/05 05:21:11
Did you mean to remove these from oauth2.js? (If y
Jamie
2013/01/07 23:52:20
oauth2.js will be deleted completely once we're a
Wez
2013/01/07 23:54:20
How will we fetch a refresh token to configure the
| |
67 var that = this; | |
68 /** @param {XMLHttpRequest} xhr The XHR response. */ | |
69 var onResponse = function(xhr) { | |
70 var email = null; | |
71 if (xhr.status == 200) { | |
72 email = xhr.responseText.split('&')[0].split('=')[1]; | |
73 that.email_ = email; | |
74 onOk(email); | |
75 return; | |
76 } | |
77 console.error('Unable to get email address:', xhr.status, xhr); | |
78 if (xhr.status == 401) { | |
79 onError(remoting.Error.AUTHENTICATION_FAILED); | |
80 } else { | |
81 onError(that.interpretUnexpectedXhrStatus_(xhr.status)); | |
82 } | |
83 }; | |
84 | |
85 /** @param {string} token The access token. */ | |
86 var getEmailFromToken = function(token) { | |
87 var headers = { 'Authorization': 'OAuth ' + token }; | |
88 // TODO(ajwong): Update to new v2 API. | |
89 remoting.xhr.get('https://www.googleapis.com/userinfo/email', | |
90 onResponse, '', headers); | |
91 }; | |
92 | |
93 this.callWithToken(getEmailFromToken, onError); | |
94 }; | |
95 | |
96 /** | |
97 * Get the user's email address, or null if no successful call to getEmail | |
98 * has been made. | |
99 * | |
100 * @return {?string} The cached email address, if available. | |
101 */ | |
102 remoting.Identity.prototype.getCachedEmail = function() { | |
103 return this.email_; | |
104 }; | |
105 | |
106 /** | |
107 * Interprets unexpected HTTP response codes to authentication XMLHttpRequests. | |
108 * The caller should handle the usual expected responses (200, 400) separately. | |
109 * | |
110 * @param {number} xhrStatus Status (HTTP response code) of the XMLHttpRequest. | |
111 * @return {remoting.Error} An error code to be raised. | |
112 * @private | |
113 */ | |
114 remoting.Identity.prototype.interpretUnexpectedXhrStatus_ = function( | |
115 xhrStatus) { | |
116 // Return AUTHENTICATION_FAILED by default, so that the user can try to | |
117 // recover from an unexpected failure by signing in again. | |
118 /** @type {remoting.Error} */ | |
119 var error = remoting.Error.AUTHENTICATION_FAILED; | |
120 if (xhrStatus == 502 || xhrStatus == 503) { | |
121 error = remoting.Error.SERVICE_UNAVAILABLE; | |
122 } else if (xhrStatus == 0) { | |
123 error = remoting.Error.NETWORK_FAILURE; | |
124 } else { | |
125 console.warn('Unexpected authentication response code: ' + xhrStatus); | |
126 } | |
127 return error; | |
128 }; | |
129 | |
130 /** | |
131 * Callback for the getAuthToken API. | |
132 * | |
133 * @param {boolean} interactive The value of the "interactive" parameter to | |
134 * getAuthToken. | |
135 * @param {?string} token The auth token, or null if the request failed. | |
136 * @private | |
137 */ | |
138 remoting.Identity.prototype.onAuthComplete_ = function(interactive, token) { | |
139 // Pass the token to the callback(s) if it was retrieved successfully. | |
140 if (token) { | |
141 while (this.pendingCallbacks_.length > 0) { | |
142 var callback = /** @type {remoting.Identity.Callbacks} */ | |
143 this.pendingCallbacks_.shift(); | |
144 callback.onOk(token); | |
145 } | |
146 return; | |
147 } | |
148 | |
149 // If not, pass an error back to the callback(s) if we've already prompted the | |
150 // user for permission. | |
151 // TODO(jamiewalch): Figure out what to do with the error in this case. | |
152 if (interactive) { | |
153 console.error(chrome.runtime.lastError); | |
154 while (this.pendingCallbacks_.length > 0) { | |
155 var callback = /** @type {remoting.Identity.Callbacks} */ | |
156 this.pendingCallbacks_.shift(); | |
157 callback.onError(remoting.Error.UNEXPECTED); | |
158 } | |
159 return; | |
160 } | |
161 | |
162 // If there's no token, but we haven't yet prompted for permission, do so | |
163 // now. The consent callback is responsible for continuing the auth flow. | |
164 this.consentCallback_(this.onAuthContinue_.bind(this)); | |
165 }; | |
166 | |
167 /** | |
168 * Called in response to the user signing in to the web-app. | |
169 * | |
170 * @private | |
171 */ | |
172 remoting.Identity.prototype.onAuthContinue_ = function() { | |
173 chrome.experimental.identity.getAuthToken( | |
174 { 'interactive': true }, | |
175 this.onAuthComplete_.bind(this, true)); | |
176 }; | |
177 | |
178 /** | |
179 * Internal representation for pair of callWithToken callbacks. | |
180 * | |
181 * @param {function(string):void} onOk | |
182 * @param {function(remoting.Error):void} onError | |
183 * @constructor | |
184 * @private | |
185 */ | |
186 remoting.Identity.Callbacks = function(onOk, onError) { | |
187 /** @type {function(string):void} */ | |
188 this.onOk = onOk; | |
189 /** @type {function(remoting.Error):void} */ | |
190 this.onError = onError; | |
191 }; | |
OLD | NEW |