// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. /** * @fileoverview * Wrapper class for Chrome's identity API. */ 'use strict'; /** @suppress {duplicate} */ var remoting = remoting || {}; /** * TODO(jamiewalch): Remove remoting.OAuth2 from this type annotation when * the Apps v2 work is complete. * * @type {remoting.Identity|remoting.OAuth2} */ remoting.identity = null; /** * @param {function(function():void):void} consentCallback Callback invoked if * user consent is required. The callback is passed a continuation function * which must be called from an interactive event handler (e.g. "click"). * @constructor */ remoting.Identity = function(consentCallback) { /** @private */ this.consentCallback_ = consentCallback; /** @type {?string} @private */ this.email_ = null; /** @type {Array.<remoting.Identity.Callbacks>} */ this.pendingCallbacks_ = []; }; /** * Call a function with an access token. * * TODO(jamiewalch): Currently, this results in a new GAIA token being minted * each time the function is called. Implement caching functionality unless * getAuthToken starts doing so itself. * * @param {function(string):void} onOk Function to invoke with access token if * an access token was successfully retrieved. * @param {function(remoting.Error):void} onError Function to invoke with an * error code on failure. * @return {void} Nothing. */ remoting.Identity.prototype.callWithToken = function(onOk, onError) { this.pendingCallbacks_.push(new remoting.Identity.Callbacks(onOk, onError)); if (this.pendingCallbacks_.length == 1) { chrome.identity.getAuthToken( { 'interactive': false }, this.onAuthComplete_.bind(this, false)); } }; /** * Get the user's email address. * * @param {function(string):void} onOk Callback invoked when the email * address is available. * @param {function(remoting.Error):void} onError Callback invoked if an * error occurs. * @return {void} Nothing. */ remoting.Identity.prototype.getEmail = function(onOk, onError) { /** @type {remoting.Identity} */ var that = this; /** @param {string} email */ var onResponse = function(email) { that.email_ = email; onOk(email); }; this.callWithToken( remoting.OAuth2Api.getEmail.bind(null, onResponse, onError), onError); }; /** * Get the user's email address, or null if no successful call to getEmail * has been made. * * @return {?string} The cached email address, if available. */ remoting.Identity.prototype.getCachedEmail = function() { return this.email_; }; /** * Callback for the getAuthToken API. * * @param {boolean} interactive The value of the "interactive" parameter to * getAuthToken. * @param {?string} token The auth token, or null if the request failed. * @private */ remoting.Identity.prototype.onAuthComplete_ = function(interactive, token) { // Pass the token to the callback(s) if it was retrieved successfully. if (token) { while (this.pendingCallbacks_.length > 0) { var callback = /** @type {remoting.Identity.Callbacks} */ this.pendingCallbacks_.shift(); callback.onOk(token); } return; } // If not, pass an error back to the callback(s) if we've already prompted the // user for permission. // TODO(jamiewalch): Figure out what to do with the error in this case. if (interactive) { console.error(chrome.runtime.lastError); while (this.pendingCallbacks_.length > 0) { var callback = /** @type {remoting.Identity.Callbacks} */ this.pendingCallbacks_.shift(); callback.onError(remoting.Error.UNEXPECTED); } return; } // If there's no token, but we haven't yet prompted for permission, do so // now. The consent callback is responsible for continuing the auth flow. this.consentCallback_(this.onAuthContinue_.bind(this)); }; /** * Called in response to the user signing in to the web-app. * * @private */ remoting.Identity.prototype.onAuthContinue_ = function() { chrome.identity.getAuthToken( { 'interactive': true }, this.onAuthComplete_.bind(this, true)); }; /** * Internal representation for pair of callWithToken callbacks. * * @param {function(string):void} onOk * @param {function(remoting.Error):void} onError * @constructor * @private */ remoting.Identity.Callbacks = function(onOk, onError) { /** @type {function(string):void} */ this.onOk = onOk; /** @type {function(remoting.Error):void} */ this.onError = onError; }; /** * Returns whether the web app has authenticated with the Google services. * * @return {boolean} */ remoting.Identity.prototype.isAuthenticated = function() { return remoting.identity.email_ != null; };