| 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 /** @suppress {duplicate} */ | |
| 10 var remoting = remoting || {}; | |
| 11 | |
| 12 (function(){ | |
| 13 | |
| 14 'use strict'; | |
| 15 | |
| 16 /** | |
| 17 * @type {remoting.Identity} | |
| 18 */ | |
| 19 remoting.identity = null; | |
| 20 | |
| 21 var USER_CANCELLED = 'The user did not approve access.'; | |
| 22 | |
| 23 /** | |
| 24 * @param {remoting.Identity.ConsentDialog=} opt_consentDialog | |
| 25 * @constructor | |
| 26 */ | |
| 27 remoting.Identity = function(opt_consentDialog) { | |
| 28 /** @private */ | |
| 29 this.consentDialog_ = opt_consentDialog; | |
| 30 /** @private {string} */ | |
| 31 this.email_ = ''; | |
| 32 /** @private {string} */ | |
| 33 this.fullName_ = ''; | |
| 34 /** @private {Object<base.Deferred<string>>} */ | |
| 35 this.authTokensDeferred_ = {}; | |
| 36 /** @private {boolean} */ | |
| 37 this.interactive_ = false; | |
| 38 }; | |
| 39 | |
| 40 /** | |
| 41 * chrome.identity.getAuthToken should be initiated from user interactions if | |
| 42 * called with interactive equals true. This interface prompts a dialog for | |
| 43 * the user's consent. | |
| 44 * | |
| 45 * @interface | |
| 46 */ | |
| 47 remoting.Identity.ConsentDialog = function() {}; | |
| 48 | |
| 49 /** | |
| 50 * @return {Promise} A Promise that resolves when permission to start an | |
| 51 * interactive flow is granted. | |
| 52 */ | |
| 53 remoting.Identity.ConsentDialog.prototype.show = function() {}; | |
| 54 | |
| 55 /** | |
| 56 * Gets an access token. | |
| 57 * | |
| 58 * @param {Array<string>=} opt_scopes Optional OAuth2 scopes to request. If not | |
| 59 * specified, the scopes specified in the manifest will be used. No consent | |
| 60 * prompt will be needed as long as the requested scopes are a subset of | |
| 61 * those already granted (in most cases, the remoting.Application framework | |
| 62 * ensures that the scopes specified in the manifest are already authorized | |
| 63 * before any application code is executed). Callers can request scopes not | |
| 64 * specified in the manifest, but a consent prompt will be shown. | |
| 65 * | |
| 66 * @return {!Promise<string>} A promise resolved with an access token | |
| 67 * or rejected with a remoting.Error. | |
| 68 */ | |
| 69 remoting.Identity.prototype.getToken = function(opt_scopes) { | |
| 70 var key = getScopesKey(opt_scopes); | |
| 71 if (!this.authTokensDeferred_[key]) { | |
| 72 this.authTokensDeferred_[key] = new base.Deferred(); | |
| 73 var options = { | |
| 74 'interactive': this.interactive_, | |
| 75 'scopes': opt_scopes | |
| 76 }; | |
| 77 chrome.identity.getAuthToken(options, | |
| 78 this.onAuthComplete_.bind(this, opt_scopes)); | |
| 79 } | |
| 80 return this.authTokensDeferred_[key].promise(); | |
| 81 }; | |
| 82 | |
| 83 /** | |
| 84 * Gets a fresh access token. | |
| 85 * | |
| 86 * @param {Array<string>=} opt_scopes Optional OAuth2 scopes to request, as | |
| 87 * documented in getToken(). | |
| 88 * @return {!Promise<string>} A promise resolved with an access token | |
| 89 * or rejected with a remoting.Error. | |
| 90 */ | |
| 91 remoting.Identity.prototype.getNewToken = function(opt_scopes) { | |
| 92 /** @type {remoting.Identity} */ | |
| 93 var that = this; | |
| 94 | |
| 95 return this.getToken(opt_scopes).then(function(/** string */ token) { | |
| 96 return new Promise(function(resolve, reject) { | |
| 97 chrome.identity.removeCachedAuthToken({'token': token }, function() { | |
| 98 resolve(that.getToken()); | |
| 99 }); | |
| 100 }); | |
| 101 }); | |
| 102 }; | |
| 103 | |
| 104 /** | |
| 105 * Removes the cached auth token, if any. | |
| 106 * | |
| 107 * @return {!Promise<null>} A promise resolved with the operation completes. | |
| 108 */ | |
| 109 remoting.Identity.prototype.removeCachedAuthToken = function() { | |
| 110 return new Promise(function(resolve, reject) { | |
| 111 /** @param {string} token */ | |
| 112 var onToken = function(token) { | |
| 113 if (token) { | |
| 114 chrome.identity.removeCachedAuthToken( | |
| 115 {'token': token}, resolve.bind(null, null)); | |
| 116 } else { | |
| 117 resolve(null); | |
| 118 } | |
| 119 }; | |
| 120 chrome.identity.getAuthToken({'interactive': false}, onToken); | |
| 121 }); | |
| 122 }; | |
| 123 | |
| 124 /** | |
| 125 * Gets the user's email address and full name. The full name will be | |
| 126 * null unless the webapp has requested and been granted the | |
| 127 * userinfo.profile permission. | |
| 128 * | |
| 129 * TODO(jrw): Type declarations say the name can't be null. Are the | |
| 130 * types wrong, or is the documentation wrong? | |
| 131 * | |
| 132 * @return {!Promise<{email:string, name:string}>} Promise | |
| 133 * resolved with the user's email address and full name, or rejected | |
| 134 * with a remoting.Error. | |
| 135 */ | |
| 136 remoting.Identity.prototype.getUserInfo = function() { | |
| 137 if (this.isAuthenticated()) { | |
| 138 /** | |
| 139 * The temp variable is needed to work around a compiler bug. | |
| 140 * @type {{email: string, name: string}} | |
| 141 */ | |
| 142 var result = {email: this.email_, name: this.fullName_}; | |
| 143 return Promise.resolve(result); | |
| 144 } | |
| 145 | |
| 146 /** @type {remoting.Identity} */ | |
| 147 var that = this; | |
| 148 | |
| 149 return this.getToken().then(function(token) { | |
| 150 return new Promise(function(resolve, reject) { | |
| 151 /** | |
| 152 * @param {string} email | |
| 153 * @param {string} name | |
| 154 */ | |
| 155 var onResponse = function(email, name) { | |
| 156 that.email_ = email; | |
| 157 that.fullName_ = name; | |
| 158 resolve({email: email, name: name}); | |
| 159 }; | |
| 160 | |
| 161 remoting.oauth2Api.getUserInfo(onResponse, reject, token); | |
| 162 }); | |
| 163 }); | |
| 164 }; | |
| 165 | |
| 166 /** | |
| 167 * Gets the user's email address. | |
| 168 * | |
| 169 * @return {!Promise<string>} Promise resolved with the user's email | |
| 170 * address or rejected with a remoting.Error. | |
| 171 */ | |
| 172 remoting.Identity.prototype.getEmail = function() { | |
| 173 return this.getUserInfo().then(function(userInfo) { | |
| 174 return userInfo.email; | |
| 175 }); | |
| 176 }; | |
| 177 | |
| 178 /** | |
| 179 * Callback for the getAuthToken API. | |
| 180 * | |
| 181 * @param {Array<string>|undefined} scopes The explicit scopes passed to | |
| 182 * getToken, or undefined if no scopes were specified. | |
| 183 * @param {?string} token The auth token, or null if the request failed. | |
| 184 * @private | |
| 185 */ | |
| 186 remoting.Identity.prototype.onAuthComplete_ = function(scopes, token) { | |
| 187 var key = getScopesKey(scopes); | |
| 188 var authTokenDeferred = this.authTokensDeferred_[key]; | |
| 189 | |
| 190 // Pass the token to the callback(s) if it was retrieved successfully. | |
| 191 if (token) { | |
| 192 var promise = this.authTokensDeferred_[key]; | |
| 193 delete this.authTokensDeferred_[key]; | |
| 194 promise.resolve(token); | |
| 195 return; | |
| 196 } | |
| 197 | |
| 198 // If not, pass an error back to the callback(s) if we've already prompted the | |
| 199 // user for permission. | |
| 200 if (this.interactive_) { | |
| 201 var error_message = | |
| 202 chrome.runtime.lastError ? chrome.runtime.lastError.message | |
| 203 : 'Unknown error.'; | |
| 204 console.error(error_message); | |
| 205 var error = (error_message == USER_CANCELLED) ? | |
| 206 new remoting.Error(remoting.Error.Tag.CANCELLED) : | |
| 207 new remoting.Error(remoting.Error.Tag.NOT_AUTHENTICATED); | |
| 208 this.authTokensDeferred_[key].reject(error); | |
| 209 delete this.authTokensDeferred_[key]; | |
| 210 return; | |
| 211 } | |
| 212 | |
| 213 // If there's no token, but we haven't yet prompted for permission, do so | |
| 214 // now. | |
| 215 var that = this; | |
| 216 var showConsentDialog = | |
| 217 (this.consentDialog_) ? this.consentDialog_.show() : Promise.resolve(); | |
| 218 showConsentDialog.then(function() { | |
| 219 that.interactive_ = true; | |
| 220 var options = { | |
| 221 'interactive': that.interactive_, | |
| 222 'scopes': scopes | |
| 223 }; | |
| 224 chrome.identity.getAuthToken(options, | |
| 225 that.onAuthComplete_.bind(that, scopes)); | |
| 226 }); | |
| 227 }; | |
| 228 | |
| 229 /** | |
| 230 * Returns whether the web app has authenticated with the Google services. | |
| 231 * | |
| 232 * @return {boolean} | |
| 233 */ | |
| 234 remoting.Identity.prototype.isAuthenticated = function() { | |
| 235 return remoting.identity.email_ !== ''; | |
| 236 }; | |
| 237 | |
| 238 | |
| 239 /** | |
| 240 * @param {Array<string>=} opt_scopes | |
| 241 * @return {string} | |
| 242 */ | |
| 243 function getScopesKey(opt_scopes) { | |
| 244 return opt_scopes ? JSON.stringify(opt_scopes) : ''; | |
| 245 } | |
| 246 | |
| 247 })(); | |
| OLD | NEW |