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} */ 13var 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 */ 21remoting.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 an interactive event handler (e.g. "click"). 27 * @constructor 28 */ 29remoting.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 */ 47remoting.Identity.prototype.callWithToken = function(onOk, onError) { 48 this.pendingCallbacks_.push(new remoting.Identity.Callbacks(onOk, onError)); 49 if (this.pendingCallbacks_.length == 1) { 50 chrome.identity.getAuthToken( 51 { 'interactive': false }, 52 this.onAuthComplete_.bind(this, false)); 53 } 54}; 55 56/** 57 * Remove the cached auth token, if any. 58 * 59 * @param {function():void} onDone Completion callback. 60 * @return {void} Nothing. 61 */ 62remoting.Identity.prototype.removeCachedAuthToken = function(onDone) { 63 /** @param {string} token */ 64 var onToken = function(token) { 65 if (token) { 66 chrome.identity.removeCachedAuthToken({ 'token': token }, onDone); 67 } else { 68 onDone(); 69 } 70 }; 71 chrome.identity.getAuthToken({ 'interactive': false }, onToken); 72}; 73 74/** 75 * Get the user's email address. 76 * 77 * @param {function(string):void} onOk Callback invoked when the email 78 * address is available. 79 * @param {function(remoting.Error):void} onError Callback invoked if an 80 * error occurs. 81 * @return {void} Nothing. 82 */ 83remoting.Identity.prototype.getEmail = function(onOk, onError) { 84 /** @type {remoting.Identity} */ 85 var that = this; 86 /** @param {string} email */ 87 var onResponse = function(email) { 88 that.email_ = email; 89 onOk(email); 90 }; 91 92 this.callWithToken( 93 remoting.OAuth2Api.getEmail.bind(null, onResponse, onError), 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 */ 102remoting.Identity.prototype.getCachedEmail = function() { 103 return this.email_; 104}; 105 106/** 107 * Callback for the getAuthToken API. 108 * 109 * @param {boolean} interactive The value of the "interactive" parameter to 110 * getAuthToken. 111 * @param {?string} token The auth token, or null if the request failed. 112 * @private 113 */ 114remoting.Identity.prototype.onAuthComplete_ = function(interactive, token) { 115 // Pass the token to the callback(s) if it was retrieved successfully. 116 if (token) { 117 while (this.pendingCallbacks_.length > 0) { 118 var callback = /** @type {remoting.Identity.Callbacks} */ 119 this.pendingCallbacks_.shift(); 120 callback.onOk(token); 121 } 122 return; 123 } 124 125 // If not, pass an error back to the callback(s) if we've already prompted the 126 // user for permission. 127 // TODO(jamiewalch): Figure out what to do with the error in this case. 128 if (interactive) { 129 console.error(chrome.runtime.lastError); 130 while (this.pendingCallbacks_.length > 0) { 131 var callback = /** @type {remoting.Identity.Callbacks} */ 132 this.pendingCallbacks_.shift(); 133 callback.onError(remoting.Error.UNEXPECTED); 134 } 135 return; 136 } 137 138 // If there's no token, but we haven't yet prompted for permission, do so 139 // now. The consent callback is responsible for continuing the auth flow. 140 this.consentCallback_(this.onAuthContinue_.bind(this)); 141}; 142 143/** 144 * Called in response to the user signing in to the web-app. 145 * 146 * @private 147 */ 148remoting.Identity.prototype.onAuthContinue_ = function() { 149 chrome.identity.getAuthToken( 150 { 'interactive': true }, 151 this.onAuthComplete_.bind(this, true)); 152}; 153 154/** 155 * Internal representation for pair of callWithToken callbacks. 156 * 157 * @param {function(string):void} onOk 158 * @param {function(remoting.Error):void} onError 159 * @constructor 160 * @private 161 */ 162remoting.Identity.Callbacks = function(onOk, onError) { 163 /** @type {function(string):void} */ 164 this.onOk = onOk; 165 /** @type {function(remoting.Error):void} */ 166 this.onError = onError; 167}; 168 169/** 170 * Returns whether the web app has authenticated with the Google services. 171 * 172 * @return {boolean} 173 */ 174remoting.Identity.prototype.isAuthenticated = function() { 175 return remoting.identity.email_ != null; 176}; 177