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.
9 /** @suppress {duplicate} */
10 var remoting
= remoting
|| {};
17 * @type {remoting.Identity}
19 remoting
.identity
= null;
21 var USER_CANCELLED
= 'The user did not approve access.';
24 * @param {remoting.Identity.ConsentDialog=} opt_consentDialog
27 remoting
.Identity = function(opt_consentDialog
) {
29 this.consentDialog_
= opt_consentDialog
;
30 /** @private {string} */
32 /** @private {string} */
34 /** @private {Object<base.Deferred<string>>} */
35 this.authTokensDeferred_
= {};
36 /** @private {boolean} */
37 this.interactive_
= false;
41 * chrome.identity.getAuthToken should be initiated from user interactions if
42 * called with interactive equals true. This interface prompts a dialog for
47 remoting
.Identity
.ConsentDialog = function() {};
50 * @return {Promise} A Promise that resolves when permission to start an
51 * interactive flow is granted.
53 remoting
.Identity
.ConsentDialog
.prototype.show = function() {};
56 * Gets an access token.
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.
66 * @return {!Promise<string>} A promise resolved with an access token
67 * or rejected with a remoting.Error.
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();
74 'interactive': this.interactive_
,
77 chrome
.identity
.getAuthToken(options
,
78 this.onAuthComplete_
.bind(this, opt_scopes
));
80 return this.authTokensDeferred_
[key
].promise();
84 * Gets a fresh access token.
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.
91 remoting
.Identity
.prototype.getNewToken = function(opt_scopes
) {
92 /** @type {remoting.Identity} */
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(opt_scopes
));
105 * Removes the cached auth token, if any.
107 * @return {!Promise<null>} A promise resolved with the operation completes.
109 remoting
.Identity
.prototype.removeCachedAuthToken = function() {
110 return new Promise(function(resolve
, reject
) {
111 /** @param {string=} token */
112 var onToken = function(token
) {
114 chrome
.identity
.removeCachedAuthToken(
115 {'token': token
}, resolve
.bind(null, null));
120 chrome
.identity
.getAuthToken({'interactive': false}, onToken
);
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.
129 * TODO(jrw): Type declarations say the name can't be null. Are the
130 * types wrong, or is the documentation wrong?
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.
136 remoting
.Identity
.prototype.getUserInfo = function() {
137 if (this.isAuthenticated()) {
139 * The temp variable is needed to work around a compiler bug.
140 * @type {{email: string, name: string}}
142 var result
= {email
: this.email_
, name
: this.fullName_
};
143 return Promise
.resolve(result
);
146 /** @type {remoting.Identity} */
149 return this.getToken().then(function(token
) {
150 return new Promise(function(resolve
, reject
) {
152 * @param {string} email
153 * @param {string} name
155 var onResponse = function(email
, name
) {
157 that
.fullName_
= name
;
158 resolve({email
: email
, name
: name
});
161 remoting
.oauth2Api
.getUserInfo(onResponse
, reject
, token
);
167 * Gets the user's email address.
169 * @return {!Promise<string>} Promise resolved with the user's email
170 * address or rejected with a remoting.Error.
172 remoting
.Identity
.prototype.getEmail = function() {
173 return this.getUserInfo().then(function(userInfo
) {
174 return userInfo
.email
;
179 * Callback for the getAuthToken API.
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.
186 remoting
.Identity
.prototype.onAuthComplete_ = function(scopes
, token
) {
187 var key
= getScopesKey(scopes
);
189 // Pass the token to the callback(s) if it was retrieved successfully.
191 var promise
= this.authTokensDeferred_
[key
];
192 delete this.authTokensDeferred_
[key
];
193 promise
.resolve(token
);
197 // If not, pass an error back to the callback(s) if we've already prompted the
198 // user for permission.
199 if (this.interactive_
) {
201 chrome
.runtime
.lastError
? chrome
.runtime
.lastError
.message
203 console
.error(error_message
);
204 var error
= (error_message
== USER_CANCELLED
) ?
205 new remoting
.Error(remoting
.Error
.Tag
.CANCELLED
) :
206 new remoting
.Error(remoting
.Error
.Tag
.NOT_AUTHENTICATED
);
207 this.authTokensDeferred_
[key
].reject(error
);
208 delete this.authTokensDeferred_
[key
];
212 // If there's no token, but we haven't yet prompted for permission, do so
215 var showConsentDialog
=
216 (this.consentDialog_
) ? this.consentDialog_
.show() : Promise
.resolve();
217 showConsentDialog
.then(function() {
218 that
.interactive_
= true;
220 'interactive': that
.interactive_
,
223 chrome
.identity
.getAuthToken(options
,
224 that
.onAuthComplete_
.bind(that
, scopes
));
229 * Returns whether the web app has authenticated with the Google services.
233 remoting
.Identity
.prototype.isAuthenticated = function() {
234 return remoting
.identity
.email_
!== '';
239 * @param {Array<string>=} opt_scopes
242 function getScopesKey(opt_scopes
) {
243 return opt_scopes
? JSON
.stringify(opt_scopes
) : '';