OLD | NEW |
---|---|
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 /** | 5 /** |
6 * @fileoverview | 6 * @fileoverview |
7 * Wrapper class for Chrome's identity API. | 7 * Wrapper class for Chrome's identity API. |
8 */ | 8 */ |
9 | 9 |
10 'use strict'; | 10 'use strict'; |
(...skipping 13 matching lines...) Expand all Loading... | |
24 * @param {remoting.Identity.ConsentDialog=} opt_consentDialog | 24 * @param {remoting.Identity.ConsentDialog=} opt_consentDialog |
25 * @constructor | 25 * @constructor |
26 */ | 26 */ |
27 remoting.Identity = function(opt_consentDialog) { | 27 remoting.Identity = function(opt_consentDialog) { |
28 /** @private */ | 28 /** @private */ |
29 this.consentDialog_ = opt_consentDialog; | 29 this.consentDialog_ = opt_consentDialog; |
30 /** @type {string} @private */ | 30 /** @type {string} @private */ |
31 this.email_ = ''; | 31 this.email_ = ''; |
32 /** @type {string} @private */ | 32 /** @type {string} @private */ |
33 this.fullName_ = ''; | 33 this.fullName_ = ''; |
34 /** @type {Array.<remoting.Identity.Callbacks>} */ | 34 /** @type {Promise<string>} */ |
35 this.pendingCallbacks_ = []; | 35 this.pendingPromise_ = null; |
36 }; | 36 }; |
37 | 37 |
38 /** | 38 /** |
39 * chrome.identity.getAuthToken should be initiated from user interactions if | 39 * chrome.identity.getAuthToken should be initiated from user interactions if |
40 * called with interactive equals true. This interface prompts a dialog for | 40 * called with interactive equals true. This interface prompts a dialog for |
41 * the user's consent. | 41 * the user's consent. |
42 * | 42 * |
43 * @interface | 43 * @interface |
44 */ | 44 */ |
45 remoting.Identity.ConsentDialog = function() {}; | 45 remoting.Identity.ConsentDialog = function() {}; |
46 | 46 |
47 /** | 47 /** |
48 * @return {Promise} A Promise that resolves when permission to start an | 48 * @return {Promise} A Promise that resolves when permission to start an |
49 * interactive flow is granted. | 49 * interactive flow is granted. |
50 */ | 50 */ |
51 remoting.Identity.ConsentDialog.prototype.show = function() {}; | 51 remoting.Identity.ConsentDialog.prototype.show = function() {}; |
52 | 52 |
53 /** | 53 /** |
54 * Call a function with an access token. | 54 * Get an access token. |
55 * | 55 * |
56 * @param {function(string):void} onOk Function to invoke with access token if | 56 * @return {!Promise<string>} A promise resolved the an access token or |
57 * an access token was successfully retrieved. | 57 * rejected with a remoting.Error. |
58 * @param {function(remoting.Error):void} onError Function to invoke with an | |
59 * error code on failure. | |
60 * @return {void} Nothing. | |
61 */ | 58 */ |
62 remoting.Identity.prototype.callWithToken = function(onOk, onError) { | 59 remoting.Identity.prototype.getToken = function() { |
63 this.pendingCallbacks_.push(new remoting.Identity.Callbacks(onOk, onError)); | 60 /** @const */ |
64 if (this.pendingCallbacks_.length == 1) { | 61 var that = this; |
65 chrome.identity.getAuthToken( | 62 |
66 { 'interactive': false }, | 63 if (this.pendingPromise_ == null) { |
67 this.onAuthComplete_.bind(this, false)); | 64 this.pendingPromise_ = new Promise(function( |
kelvinp
2015/02/18 19:45:24
I think base.Deferred will work better as you want
John Williams
2015/02/18 20:28:22
Done.
| |
65 /** function(string) */ resolve, | |
kelvinp
2015/02/18 19:45:24
Is this a new syntax of annotating parameters? I l
John Williams
2015/02/18 20:28:22
It's a recent addition to the syntax. I like it f
| |
66 /** function(remoting.Error) */ reject) { | |
67 chrome.identity.getAuthToken( | |
68 { 'interactive': false }, | |
69 that.onAuthComplete_.bind(that, resolve, reject, false)); | |
70 }); | |
68 } | 71 } |
72 return this.pendingPromise_; | |
69 }; | 73 }; |
70 | 74 |
71 /** | 75 /** |
72 * Call a function with a fresh access token. | 76 * Get a fresh access token. |
kelvinp
2015/02/18 19:45:24
Nit: Gets
John Williams
2015/02/18 20:28:22
Done.
| |
73 * | 77 * |
74 * @param {function(string):void} onOk Function to invoke with access token if | 78 * @return {!Promise<string>} A promise resolved the an access token or |
kelvinp
2015/02/18 19:45:24
s/resolved the an/resolved with an/
| |
75 * an access token was successfully retrieved. | 79 * rejected with a remoting.Error. |
76 * @param {function(remoting.Error):void} onError Function to invoke with an | |
77 * error code on failure. | |
78 * @return {void} Nothing. | |
79 */ | 80 */ |
80 remoting.Identity.prototype.callWithNewToken = function(onOk, onError) { | 81 remoting.Identity.prototype.getNewToken = function() { |
81 /** @type {remoting.Identity} */ | 82 /** @type {remoting.Identity} */ |
82 var that = this; | 83 var that = this; |
83 | 84 |
84 /** | 85 return this.getToken().then(function(/** string */ token) { |
85 * @param {string} token | 86 return new Promise(function(resolve, reject) { |
86 */ | 87 chrome.identity.removeCachedAuthToken({'token': token }, function() { |
87 function revokeToken(token) { | 88 resolve(that.getToken()); |
88 chrome.identity.removeCachedAuthToken( | 89 }); |
89 {'token': token }, | 90 }); |
90 that.callWithToken.bind(that, onOk, onError)); | 91 }); |
91 } | |
92 | |
93 this.callWithToken(revokeToken, onError); | |
94 }; | 92 }; |
95 | 93 |
96 /** | 94 /** |
97 * Remove the cached auth token, if any. | 95 * Remove the cached auth token, if any. |
98 * | 96 * |
99 * @param {function():void=} opt_onDone Completion callback. | 97 * @return {!Promise<null>} A promise resolved with the operation completes. |
100 * @return {void} Nothing. | |
101 */ | 98 */ |
102 remoting.Identity.prototype.removeCachedAuthToken = function(opt_onDone) { | 99 remoting.Identity.prototype.removeCachedAuthToken = function() { |
103 var onDone = (opt_onDone) ? opt_onDone : base.doNothing; | 100 return new Promise(function(resolve, reject) { |
104 | 101 /** @param {string} token */ |
105 /** @param {string} token */ | 102 var onToken = function(token) { |
106 var onToken = function(token) { | 103 if (token) { |
107 if (token) { | 104 chrome.identity.removeCachedAuthToken( |
108 chrome.identity.removeCachedAuthToken({'token': token}, onDone); | 105 {'token': token}, resolve.bind(null, null)); |
109 } else { | 106 } else { |
110 onDone(); | 107 resolve(null); |
111 } | 108 } |
112 }; | 109 }; |
113 chrome.identity.getAuthToken({'interactive': false}, onToken); | 110 chrome.identity.getAuthToken({'interactive': false}, onToken); |
111 }); | |
114 }; | 112 }; |
115 | 113 |
116 /** | 114 /** |
117 * Get the user's email address and full name. | 115 * Get the user's email address and full name. The full name will be |
118 * The full name will be null unless the webapp has requested and been | 116 * null unless the webapp has requested and been granted the |
119 * granted the userinfo.profile permission. | 117 * userinfo.profile permission. |
120 * | 118 * |
121 * @param {function(string,string):void} onOk Callback invoked when the user's | 119 * TODO(jrw): Type declarations say the name can't be null. Are the |
122 * email address and full name are available. | 120 * types wrong, or is the documentation wrong? |
123 * @param {function(remoting.Error):void} onError Callback invoked if an | 121 * |
124 * error occurs. | 122 * @return {!Promise<{email: string, name: string}>} Promise |
kelvinp
2015/02/18 19:45:24
no space between : and type name
John Williams
2015/02/18 20:28:22
Done.
| |
125 * @return {void} Nothing. | 123 * resolved with the user's email address and full name, or rejected |
124 * with a remoting.Error. | |
126 */ | 125 */ |
127 remoting.Identity.prototype.getUserInfo = function(onOk, onError) { | 126 remoting.Identity.prototype.getUserInfo = function() { |
128 if (this.isAuthenticated()) { | 127 if (this.isAuthenticated()) { |
129 onOk(this.email_, this.fullName_); | 128 /** |
130 return; | 129 * The temp variable is needed to work around a compiler bug. |
130 * @type {{email: string, name: string}} | |
131 */ | |
132 var result = {email: this.email_, name: this.fullName_}; | |
133 return Promise.resolve(result); | |
131 } | 134 } |
132 | 135 |
133 /** @type {remoting.Identity} */ | 136 /** @type {remoting.Identity} */ |
134 var that = this; | 137 var that = this; |
135 | 138 |
136 /** | 139 return this.getToken().then(function(token) { |
137 * @param {string} email | 140 return new Promise(function(resolve, reject) { |
138 * @param {string} name | 141 /** |
139 */ | 142 * @param {string} email |
140 var onResponse = function(email, name) { | 143 * @param {string} name |
141 that.email_ = email; | 144 */ |
142 that.fullName_ = name; | 145 var onResponse = function(email, name) { |
143 onOk(email, name); | 146 that.email_ = email; |
144 }; | 147 that.fullName_ = name; |
148 resolve({email: email, name: name}); | |
149 }; | |
145 | 150 |
146 this.callWithToken( | 151 remoting.oauth2Api.getUserInfo(onResponse, reject, token); |
147 remoting.oauth2Api.getUserInfo.bind( | 152 }); |
148 remoting.oauth2Api, onResponse, onError), | 153 }); |
149 onError); | |
150 }; | 154 }; |
151 | 155 |
152 /** | 156 /** |
153 * Get the user's email address. | 157 * Get the user's email address. |
154 * | 158 * |
155 * @param {function(string):void} onOk Callback invoked when the email | 159 * @return {!Promise<string>} Promise resolved with the user's email |
156 * address is available. | 160 * address or rejected with a remoting.Error. |
157 * @param {function(remoting.Error):void} onError Callback invoked if an | |
158 * error occurs. | |
159 * @return {void} Nothing. | |
160 */ | 161 */ |
161 remoting.Identity.prototype.getEmail = function(onOk, onError) { | 162 remoting.Identity.prototype.getEmail = function() { |
162 this.getUserInfo(function(email, name) { | 163 this.getUserInfo().then(function(userInfo) { |
163 onOk(email); | 164 return userInfo.email; |
164 }, onError); | 165 }); |
165 }; | 166 }; |
166 | 167 |
167 /** | 168 /** |
168 * Get the user's email address, or null if no successful call to getUserInfo | 169 * Get the user's email address, or null if no successful call to getUserInfo |
169 * has been made. | 170 * has been made. |
170 * | 171 * |
171 * @return {?string} The cached email address, if available. | 172 * @return {?string} The cached email address, if available. |
172 */ | 173 */ |
173 remoting.Identity.prototype.getCachedEmail = function() { | 174 remoting.Identity.prototype.getCachedEmail = function() { |
174 return this.email_; | 175 return this.email_; |
175 }; | 176 }; |
176 | 177 |
177 /** | 178 /** |
178 * Get the user's full name. | 179 * Get the user's full name. |
179 * This will return null if either: | 180 * This will return null if either: |
180 * No successful call to getUserInfo has been made, or | 181 * No successful call to getUserInfo has been made, or |
181 * The webapp doesn't have permission to access this value. | 182 * The webapp doesn't have permission to access this value. |
182 * | 183 * |
183 * @return {?string} The cached user's full name, if available. | 184 * @return {?string} The cached user's full name, if available. |
184 */ | 185 */ |
185 remoting.Identity.prototype.getCachedUserFullName = function() { | 186 remoting.Identity.prototype.getCachedUserFullName = function() { |
186 return this.fullName_; | 187 return this.fullName_; |
187 }; | 188 }; |
188 | 189 |
189 /** | 190 /** |
190 * Callback for the getAuthToken API. | 191 * Callback for the getAuthToken API. |
191 * | 192 * |
193 * @param {function(string):void} resolve | |
194 * @param {function(remoting.Error):void} reject | |
192 * @param {boolean} interactive The value of the "interactive" parameter to | 195 * @param {boolean} interactive The value of the "interactive" parameter to |
193 * getAuthToken. | 196 * getAuthToken. |
194 * @param {?string} token The auth token, or null if the request failed. | 197 * @param {?string} token The auth token, or null if the request failed. |
195 * @private | 198 * @private |
196 */ | 199 */ |
197 remoting.Identity.prototype.onAuthComplete_ = function(interactive, token) { | 200 remoting.Identity.prototype.onAuthComplete_ = function( |
201 resolve, reject, interactive, token) { | |
kelvinp
2015/02/18 19:45:24
As commented earlier, use base.Deferred instead
John Williams
2015/02/18 20:28:22
Done.
| |
198 // Pass the token to the callback(s) if it was retrieved successfully. | 202 // Pass the token to the callback(s) if it was retrieved successfully. |
199 if (token) { | 203 if (token) { |
200 while (this.pendingCallbacks_.length > 0) { | 204 resolve(token); |
201 var callback = /** @type {remoting.Identity.Callbacks} */ | 205 this.pendingPromise_ = null; |
202 (this.pendingCallbacks_.shift()); | |
203 callback.onOk(token); | |
204 } | |
205 return; | 206 return; |
206 } | 207 } |
207 | 208 |
208 // If not, pass an error back to the callback(s) if we've already prompted the | 209 // If not, pass an error back to the callback(s) if we've already prompted the |
209 // user for permission. | 210 // user for permission. |
210 if (interactive) { | 211 if (interactive) { |
211 var error_message = | 212 var error_message = |
212 chrome.runtime.lastError ? chrome.runtime.lastError.message | 213 chrome.runtime.lastError ? chrome.runtime.lastError.message |
213 : 'Unknown error.'; | 214 : 'Unknown error.'; |
214 console.error(error_message); | 215 console.error(error_message); |
215 while (this.pendingCallbacks_.length > 0) { | 216 reject(remoting.Error.NOT_AUTHENTICATED); |
216 var callback = /** @type {remoting.Identity.Callbacks} */ | |
217 (this.pendingCallbacks_.shift()); | |
218 callback.onError(remoting.Error.NOT_AUTHENTICATED); | |
219 } | |
220 return; | 217 return; |
221 } | 218 } |
222 | 219 |
223 // If there's no token, but we haven't yet prompted for permission, do so | 220 // If there's no token, but we haven't yet prompted for permission, do so |
224 // now. | 221 // now. |
225 var that = this; | 222 var that = this; |
226 var showConsentDialog = | 223 var showConsentDialog = |
227 (this.consentDialog_) ? this.consentDialog_.show() : Promise.resolve(); | 224 (this.consentDialog_) ? this.consentDialog_.show() : Promise.resolve(); |
228 showConsentDialog.then(function() { | 225 showConsentDialog.then(function() { |
229 chrome.identity.getAuthToken({'interactive': true}, | 226 chrome.identity.getAuthToken({'interactive': true}, |
230 that.onAuthComplete_.bind(that, true)); | 227 that.onAuthComplete_.bind( |
228 that, resolve, reject, true)); | |
kelvinp
2015/02/18 19:45:24
indentation
John Williams
2015/02/18 20:28:22
Done.
| |
231 }); | 229 }); |
232 }; | 230 }; |
233 | 231 |
234 /** | 232 /** |
235 * Internal representation for pair of callWithToken callbacks. | |
236 * | |
237 * @param {function(string):void} onOk | |
238 * @param {function(remoting.Error):void} onError | |
239 * @constructor | |
240 * @private | |
241 */ | |
242 remoting.Identity.Callbacks = function(onOk, onError) { | |
243 /** @type {function(string):void} */ | |
244 this.onOk = onOk; | |
245 /** @type {function(remoting.Error):void} */ | |
246 this.onError = onError; | |
247 }; | |
248 | |
249 /** | |
250 * Returns whether the web app has authenticated with the Google services. | 233 * Returns whether the web app has authenticated with the Google services. |
251 * | 234 * |
252 * @return {boolean} | 235 * @return {boolean} |
253 */ | 236 */ |
254 remoting.Identity.prototype.isAuthenticated = function() { | 237 remoting.Identity.prototype.isAuthenticated = function() { |
255 return remoting.identity.email_ !== ''; | 238 return remoting.identity.email_ !== ''; |
256 }; | 239 }; |
OLD | NEW |