From 2589f69a7fb7be7586f075af0b9029517543ed31 Mon Sep 17 00:00:00 2001 From: kelvinp Date: Thu, 29 Jan 2015 14:24:28 -0800 Subject: [PATCH] Handle authentication failures in the v2 app by restarting the app This CL handles the case when a token is revoked, e.g. via https://security.google.com/settings/security/permissions. We need to instruct chrome.identity to remove the invalid cached token and fetch a new one. The app would then be reloaded app as some of the app components (wcs sandbox) cached the invalid token and cannot be cleanly reinitialized. Summary of changes: 1. Implement app reloading using chrome.app.AppWindow API's and base.Ipc so that it can be called from foreground pages 2. Implement handleAuthFailure in remoting.identity and remoting.oauth2. In remoting.identity, we would remove the cached token and request a new one by prompting the user for consent. 3. Modify the call sites to use the new method. BUG=339677 Review URL: https://codereview.chromium.org/868203002 Cr-Commit-Position: refs/heads/master@{#313807} --- remoting/remoting_webapp_files.gypi | 1 + remoting/webapp/base/js/auth_dialog.js | 86 +++++++++++++++++++++++--- remoting/webapp/base/js/auth_init.js | 60 ++++++++---------- remoting/webapp/crd/js/activation_handler.js | 72 +++++++++++++++++++++ remoting/webapp/crd/js/app_launcher.js | 61 ++++++++++++++---- remoting/webapp/crd/js/background.js | 55 +--------------- remoting/webapp/crd/js/crd_event_handlers.js | 25 +------- remoting/webapp/crd/js/desktop_remoting.js | 6 ++ remoting/webapp/crd/js/host_list.js | 4 +- remoting/webapp/crd/js/identity.js | 56 ++++++++++------- remoting/webapp/crd/js/it2me_helpee_channel.js | 2 +- remoting/webapp/crd/js/oauth2.js | 8 ++- remoting/webapp/crd/js/remoting.js | 19 +++--- remoting/webapp/js_proto/dom_proto.js | 10 +++ 14 files changed, 298 insertions(+), 167 deletions(-) create mode 100644 remoting/webapp/crd/js/activation_handler.js diff --git a/remoting/remoting_webapp_files.gypi b/remoting/remoting_webapp_files.gypi index a18ec50f36b3..79f10e2cc085 100644 --- a/remoting/remoting_webapp_files.gypi +++ b/remoting/remoting_webapp_files.gypi @@ -231,6 +231,7 @@ 'webapp/base/js/ipc.js', 'webapp/base/js/message_window_helper.js', 'webapp/base/js/message_window_manager.js', + 'webapp/crd/js/activation_handler.js', 'webapp/crd/js/app_launcher.js', 'webapp/crd/js/background.js', 'webapp/crd/js/client_session.js', diff --git a/remoting/webapp/base/js/auth_dialog.js b/remoting/webapp/base/js/auth_dialog.js index c9b884cebdfa..cdb62d056e3b 100644 --- a/remoting/webapp/base/js/auth_dialog.js +++ b/remoting/webapp/base/js/auth_dialog.js @@ -2,22 +2,49 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -'use strict'; - /** @suppress {duplicate} */ var remoting = remoting || {}; +(function() { + +'use strict'; + +var instance_ = null; + /** * @constructor * @implements {remoting.WindowShape.ClientUI} - * @param {HTMLElement} element The dialog DOM element. + * @implements {remoting.Identity.ConsentDialog} + * @param {HTMLElement} rootElement The dialog DOM element. + * @private */ -remoting.AuthDialog = function(element) { +remoting.AuthDialog = function(rootElement) { + /** + * @type {HTMLElement} + * @private + */ + this.rootElement_ = rootElement; + /** * @type {HTMLElement} * @private */ - this.element_ = element; + this.borderElement_ = rootElement.querySelector('#auth-dialog-border'); + + /** + * @type {HTMLElement} + * @private + */ + this.authButton_ = rootElement.querySelector('#auth-button'); + + /** + * @type {base.Deferred} + * @private + */ + this.onAuthButtonDeferred_ = null; + + this.authButton_.addEventListener('click', this.onClick_.bind(this), false); + remoting.windowShape.addCallback(this); }; /** @@ -25,12 +52,53 @@ remoting.AuthDialog = function(element) { * rects List of rectangles. */ remoting.AuthDialog.prototype.addToRegion = function(rects) { - var rect = /** @type {ClientRect} */(this.element_.getBoundingClientRect()); + var rect = + /** @type {ClientRect} */(this.borderElement_.getBoundingClientRect()); rects.push({left: rect.left, top: rect.top, width: rect.width, height: rect.height}); -} +}; + +/** @private */ +remoting.AuthDialog.prototype.onClick_ = function() { + this.rootElement_.hidden = true; + this.onAuthButtonDeferred_.resolve(); + this.onAuthButtonDeferred_ = null; + remoting.windowShape.updateClientWindowShape(); +}; + +/** + * @return {Promise} A Promise object that resolves when the user clicks on the + * auth button. + */ +remoting.AuthDialog.prototype.show = function() { + if (this.isVisible()) { + return Promise.reject('Auth dialog is already showing.'); + } + this.rootElement_.hidden = false; + base.debug.assert(this.onAuthButtonDeferred_ === null); + remoting.windowShape.updateClientWindowShape(); + this.onAuthButtonDeferred_ = new base.Deferred(); + return this.onAuthButtonDeferred_.promise(); +}; + +/** + * @return {boolean} whether the auth dialog is visible or not. + */ +remoting.AuthDialog.prototype.isVisible = function() { + return !this.rootElement_.hidden; +}; + +/** + * @return {remoting.AuthDialog} + */ +remoting.AuthDialog.getInstance = function() { + if (!instance_) { + var rootElement = document.getElementById('auth-dialog'); + instance_ = new remoting.AuthDialog(rootElement); + } + return instance_; +}; -/** @type {remoting.AuthDialog} */ -remoting.authDialog = null; +})(); diff --git a/remoting/webapp/base/js/auth_init.js b/remoting/webapp/base/js/auth_init.js index 2602efc81129..8312c31077fa 100644 --- a/remoting/webapp/base/js/auth_init.js +++ b/remoting/webapp/base/js/auth_init.js @@ -16,34 +16,6 @@ var remoting = remoting || {}; */ remoting.initIdentity = function(onUserInfoAvailable) { - /** - * Show the authorization consent UI and register a one-shot event handler to - * continue the authorization process. - * - * @param {function():void} authContinue Callback to invoke when the user - * clicks "Continue". - */ - function promptForConsent(authContinue) { - /** @type {HTMLElement} */ - var dialog = document.getElementById('auth-dialog'); - /** @type {HTMLElement} */ - var button = document.getElementById('auth-button'); - var consentGranted = function(event) { - dialog.hidden = true; - button.removeEventListener('click', consentGranted, false); - authContinue(); - remoting.windowShape.updateClientWindowShape(); - }; - dialog.hidden = false; - - /** @type {HTMLElement} */ - var dialog_border = document.getElementById('auth-dialog-border'); - remoting.authDialog = new remoting.AuthDialog(dialog_border); - remoting.windowShape.addCallback(remoting.authDialog); - - button.addEventListener('click', consentGranted, false); - } - /** @param {remoting.Error} error */ function onGetIdentityInfoError(error) { // No need to show the error message for NOT_AUTHENTICATED @@ -54,16 +26,38 @@ remoting.initIdentity = function(onUserInfoAvailable) { } if (base.isAppsV2()) { - remoting.identity = new remoting.Identity(promptForConsent); + remoting.identity = + new remoting.Identity(remoting.AuthDialog.getInstance()); } else { // TODO(garykac) Remove this and replace with identity. remoting.oauth2 = new remoting.OAuth2(); - if (!remoting.oauth2.isAuthenticated()) { - document.getElementById('auth-dialog').hidden = false; + var oauth2 = /** @type {*} */ (remoting.oauth2); + remoting.identity = /** @type {remoting.Identity} */ (oauth2); + if (!remoting.identity.isAuthenticated()) { + remoting.AuthDialog.getInstance().show().then(function() { + remoting.oauth2.doAuthRedirect(function(){ + window.location.reload(); + }); + }); } - remoting.identity = remoting.oauth2; } remoting.identity.getUserInfo(onUserInfoAvailable, onGetIdentityInfoError); -} +}; + +/** + * Removes the cached token and restarts the app. + * + * @return {void} Nothing. + */ +remoting.handleAuthFailureAndRelaunch = function() { + remoting.identity.removeCachedAuthToken(function(){ + if (base.isAppsV2()) { + base.Ipc.invoke('remoting.ActivationHandler.restart', + chrome.app.window.current().id); + } else { + window.location.reload(); + } + }); +}; \ No newline at end of file diff --git a/remoting/webapp/crd/js/activation_handler.js b/remoting/webapp/crd/js/activation_handler.js new file mode 100644 index 000000000000..4cdd83803837 --- /dev/null +++ b/remoting/webapp/crd/js/activation_handler.js @@ -0,0 +1,72 @@ +// Copyright 2014 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. + +/** @suppress {duplicate} */ +var remoting = remoting || {}; + +(function(){ + +'use strict'; + +/** @type {string} */ +var NEW_WINDOW_MENU_ID_ = 'new-window'; + +/** + * A class that handles application activation. + * + * @param {base.Ipc} ipc + * @param {remoting.V2AppLauncher} appLauncher + * @constructor + */ +remoting.ActivationHandler = function (ipc, appLauncher) { + /** + * @type {remoting.V2AppLauncher} + * @private + */ + this.appLauncher_ = appLauncher; + + chrome.contextMenus.create({ + id: NEW_WINDOW_MENU_ID_, + contexts: ['launcher'], + title: chrome.i18n.getMessage(/*i18n-content*/'NEW_WINDOW') + }); + + chrome.contextMenus.onClicked.addListener(this.onContextMenu_.bind(this)); + chrome.app.runtime.onLaunched.addListener(this.onLaunched_.bind(this)); + ipc.register(remoting.ActivationHandler.Ipc.RELAUNCH, + appLauncher.restart.bind(appLauncher)); +}; + +/** @enum {string} */ +remoting.ActivationHandler.Ipc = { + RELAUNCH: 'remoting.ActivationHandler.restart' +}; + +/** + * @param {OnClickData} info + * @private + */ +remoting.ActivationHandler.prototype.onContextMenu_ = function(info) { + if (info.menuItemId == NEW_WINDOW_MENU_ID_) { + this.appLauncher_.launch(); + } +}; + +/** + * Called when the App is activated (e.g. from the Chrome App Launcher). It + * creates a new window if there are no existing ones. Otherwise, it will put + * focus on the last window created. + * + * @private + */ +remoting.ActivationHandler.prototype.onLaunched_ = function() { + var windows = chrome.app.window.getAll(); + if (windows.length >= 1) { + windows[windows.length - 1].focus(); + } else { + this.appLauncher_.launch(); + } +}; + +})(); \ No newline at end of file diff --git a/remoting/webapp/crd/js/app_launcher.js b/remoting/webapp/crd/js/app_launcher.js index 9257d3dbfd47..4bf03c74f8e8 100644 --- a/remoting/webapp/crd/js/app_launcher.js +++ b/remoting/webapp/crd/js/app_launcher.js @@ -19,11 +19,13 @@ * appLauncher.close(appId); */ -'use strict'; - /** @suppress {duplicate} */ var remoting = remoting || {}; +(function() { + +'use strict'; + /** @interface */ remoting.AppLauncher = function() {}; @@ -91,14 +93,22 @@ remoting.V1AppLauncher.prototype.close = function(id) { */ remoting.V2AppLauncher = function() {}; +var APP_MAIN_URL = 'main.html'; + /** - * @type {number} - * @private + * @param {string} id */ -remoting.V2AppLauncher.nextWindowId_ = 0; +remoting.V2AppLauncher.prototype.restart = function(id) { + this.close(id).then(function() { + // Not using the launch() method because we want to launch a new window with + // the same id, such that the size and positioning of the original window + // can be preserved. + return chrome.app.window.create(APP_MAIN_URL, {'id' : id, 'frame': 'none'}); + }); +}; remoting.V2AppLauncher.prototype.launch = function(opt_launchArgs) { - var url = base.urlJoin('main.html', opt_launchArgs); + var url = base.urlJoin(APP_MAIN_URL, opt_launchArgs); /** * @param {function(*=):void} resolve @@ -114,7 +124,7 @@ remoting.V2AppLauncher.prototype.launch = function(opt_launchArgs) { 'width': 800, 'height': 600, 'frame': 'none', - 'id': String(remoting.V2AppLauncher.nextWindowId_++), + 'id': String(getNextWindowId()), 'state': state }, /** @param {AppWindow=} appWindow */ @@ -131,10 +141,35 @@ remoting.V2AppLauncher.prototype.launch = function(opt_launchArgs) { }; remoting.V2AppLauncher.prototype.close = function(id) { - var appWindow = chrome.app.window.get(id); - if (!appWindow) { - return Promise.reject(new Error(chrome.runtime.lastError.message)); - } - appWindow.close(); - return Promise.resolve(); + /** + * @param {function(*=):void} resolve + * @param {function(*=):void} reject + */ + return new Promise(function(resolve, reject) { + var appWindow = chrome.app.window.get(id); + if (!appWindow) { + return Promise.reject(new Error(chrome.runtime.lastError.message)); + } + appWindow.onClosed.addListener(resolve); + appWindow.close(); + }); }; + +/** + * @return {number} the next available window id. + */ +function getNextWindowId() { + var appWindows = chrome.app.window.getAll(); + var lastId = /** @type(number) */ (0); + appWindows.forEach(function(appWindow) { + base.debug.assert(Number.isInteger(appWindow.id), + "Window Id should be an integer"); + var id = parseInt(appWindow.id, 10); + if (lastId <= id) { + lastId = id; + } + }); + return ++lastId; +} + +})(); \ No newline at end of file diff --git a/remoting/webapp/crd/js/background.js b/remoting/webapp/crd/js/background.js index f3d5bea5c98d..fd2e41de6237 100644 --- a/remoting/webapp/crd/js/background.js +++ b/remoting/webapp/crd/js/background.js @@ -7,57 +7,7 @@ var remoting = remoting || {}; (function(){ -/** - * A class that handles application activation. - * - * @param {remoting.AppLauncher} appLauncher - * @constructor - */ -function ActivationHandler(appLauncher) { - /** - * @type {remoting.AppLauncher} - * @private - */ - this.appLauncher_ = appLauncher; - - chrome.contextMenus.create({ - id: ActivationHandler.NEW_WINDOW_MENU_ID_, - contexts: ['launcher'], - title: chrome.i18n.getMessage(/*i18n-content*/'NEW_WINDOW') - }); - - chrome.contextMenus.onClicked.addListener(this.onContextMenu_.bind(this)); - chrome.app.runtime.onLaunched.addListener(this.onLaunched_.bind(this)); -} - -/** @type {string} */ -ActivationHandler.NEW_WINDOW_MENU_ID_ = 'new-window'; - -/** - * @param {OnClickData} info - * @private - */ -ActivationHandler.prototype.onContextMenu_ = function(info) { - if (info.menuItemId == ActivationHandler.NEW_WINDOW_MENU_ID_) { - this.appLauncher_.launch(); - } -}; - -/** - * Called when the App is activated (e.g. from the Chrome App Launcher). It - * creates a new window if there are no existing ones. Otherwise, it will put - * focus on the last window created. - * - * @private - */ -ActivationHandler.prototype.onLaunched_ = function() { - var windows = chrome.app.window.getAll(); - if (windows.length >= 1) { - windows[windows.length - 1].focus(); - } else { - this.appLauncher_.launch(); - } -}; +'use strict'; /** * The background service is responsible for listening to incoming connection @@ -86,7 +36,8 @@ function initializeBackgroundService(appLauncher) { function main() { if (base.isAppsV2()) { - new ActivationHandler(new remoting.V2AppLauncher()); + new remoting.ActivationHandler(base.Ipc.getInstance(), + new remoting.V2AppLauncher()); } } diff --git a/remoting/webapp/crd/js/crd_event_handlers.js b/remoting/webapp/crd/js/crd_event_handlers.js index 2f754397b1cb..9196e6b3792f 100644 --- a/remoting/webapp/crd/js/crd_event_handlers.js +++ b/remoting/webapp/crd/js/crd_event_handlers.js @@ -38,27 +38,6 @@ remoting.initElementEventHandlers = function() { remoting.setMode(remoting.AppMode.CLIENT_CONNECTING); remoting.app.getSessionConnector().reconnect(); }; - var doAuthRedirect = function() { - if (!base.isAppsV2()) { - remoting.oauth2.doAuthRedirect(function() { - window.location.reload(); - }); - } - }; - var fixAuthError = function() { - if (base.isAppsV2()) { - var onRefresh = function() { - remoting.hostList.display(); - }; - var refreshHostList = function() { - goHome(); - remoting.hostList.refresh(onRefresh); - }; - remoting.identity.removeCachedAuthToken(refreshHostList); - } else { - doAuthRedirect(); - } - }; /** @param {Event} event The event. */ var stopDaemon = function(event) { remoting.hostSetupDialog.showForStop(); @@ -105,11 +84,11 @@ remoting.initElementEventHandlers = function() { ]; /** @type {Array.<{event: string, id: string, fn: function(Event):void}>} */ var auth_actions = [ - { event: 'click', id: 'auth-button', fn: doAuthRedirect }, { event: 'click', id: 'cancel-connect-button', fn: goHome }, { event: 'click', id: 'sign-out', fn:remoting.signOut }, { event: 'click', id: 'token-refresh-error-ok', fn: goHome }, - { event: 'click', id: 'token-refresh-error-sign-in', fn: fixAuthError } + { event: 'click', id: 'token-refresh-error-sign-in', + fn: remoting.handleAuthFailureAndRelaunch } ]; registerEventListeners(it2me_actions); registerEventListeners(me2me_actions); diff --git a/remoting/webapp/crd/js/desktop_remoting.js b/remoting/webapp/crd/js/desktop_remoting.js index cdf4607ee95c..9e2e10106200 100644 --- a/remoting/webapp/crd/js/desktop_remoting.js +++ b/remoting/webapp/crd/js/desktop_remoting.js @@ -306,6 +306,12 @@ remoting.DesktopRemoting.prototype.handleError = function(errorTag) { console.error('Connection failed: ' + errorTag); remoting.accessCode = ''; + if (errorTag === remoting.Error.AUTHENTICATION_FAILED) { + remoting.setMode(remoting.AppMode.HOME); + remoting.handleAuthFailureAndRelaunch(); + return; + } + // Reset the refresh flag so that the next connection will retry if needed. this.refreshHostJidIfOffline_ = true; diff --git a/remoting/webapp/crd/js/host_list.js b/remoting/webapp/crd/js/host_list.js index cdee3057d6ac..fd39e96fc716 100644 --- a/remoting/webapp/crd/js/host_list.js +++ b/remoting/webapp/crd/js/host_list.js @@ -482,9 +482,7 @@ remoting.HostList.prototype.onLocalHostStarted = function( */ remoting.HostList.prototype.onErrorClick_ = function() { if (this.lastError_ == remoting.Error.AUTHENTICATION_FAILED) { - remoting.oauth2.doAuthRedirect(function() { - window.location.reload(); - }); + remoting.handleAuthFailureAndRelaunch(); } else { this.refresh(remoting.updateLocalHostState); } diff --git a/remoting/webapp/crd/js/identity.js b/remoting/webapp/crd/js/identity.js index aef0f2c6f984..0421410448a5 100644 --- a/remoting/webapp/crd/js/identity.js +++ b/remoting/webapp/crd/js/identity.js @@ -16,19 +16,17 @@ var remoting = remoting || {}; * TODO(jamiewalch): Remove remoting.OAuth2 from this type annotation when * the Apps v2 work is complete. * - * @type {remoting.Identity|remoting.OAuth2} + * @type {remoting.Identity} */ 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"). + * @param {remoting.Identity.ConsentDialog} consentDialog * @constructor */ -remoting.Identity = function(consentCallback) { +remoting.Identity = function(consentDialog) { /** @private */ - this.consentCallback_ = consentCallback; + this.consentDialog_ = consentDialog; /** @type {string} @private */ this.email_ = ''; /** @type {string} @private */ @@ -38,6 +36,21 @@ remoting.Identity = function(consentCallback) { }; /** + * chrome.identity.getAuthToken must be initiated from user interactions if + * called with interactive equals true. This interface prompts a dialog for + * the user's consent. + * + * @interface + */ +remoting.Identity.ConsentDialog = function() {}; + +/** + * @return {Promise} A Promise that resolves when permission to start an + * interactive flow is granted. + */ +remoting.Identity.ConsentDialog.prototype.show = function() {}; + +/** * Call a function with an access token. * * @param {function(string):void} onOk Function to invoke with access token if @@ -75,7 +88,7 @@ remoting.Identity.prototype.callWithNewToken = function(onOk, onError) { chrome.identity.removeCachedAuthToken( {'token': token }, that.callWithToken.bind(that, onOk, onError)); - }; + } this.callWithToken(revokeToken, onError); }; @@ -83,19 +96,21 @@ remoting.Identity.prototype.callWithNewToken = function(onOk, onError) { /** * Remove the cached auth token, if any. * - * @param {function():void} onDone Completion callback. + * @param {function():void=} opt_onDone Completion callback. * @return {void} Nothing. */ -remoting.Identity.prototype.removeCachedAuthToken = function(onDone) { +remoting.Identity.prototype.removeCachedAuthToken = function(opt_onDone) { + var onDone = (opt_onDone) ? opt_onDone : base.doNothing; + /** @param {string} token */ var onToken = function(token) { if (token) { - chrome.identity.removeCachedAuthToken({ 'token': token }, onDone); + chrome.identity.removeCachedAuthToken({'token': token}, onDone); } else { onDone(); } }; - chrome.identity.getAuthToken({ 'interactive': false }, onToken); + chrome.identity.getAuthToken({'interactive': false}, onToken); }; /** @@ -206,19 +221,12 @@ remoting.Identity.prototype.onAuthComplete_ = function(interactive, token) { } // 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)); + // now. + var that = this; + this.consentDialog_.show().then(function() { + chrome.identity.getAuthToken({'interactive': true}, + that.onAuthComplete_.bind(that, true)); + }); }; /** diff --git a/remoting/webapp/crd/js/it2me_helpee_channel.js b/remoting/webapp/crd/js/it2me_helpee_channel.js index e4060acc92a8..af28dd4938d4 100644 --- a/remoting/webapp/crd/js/it2me_helpee_channel.js +++ b/remoting/webapp/crd/js/it2me_helpee_channel.js @@ -402,7 +402,7 @@ remoting.It2MeHelpeeChannel.prototype.fetchOAuthToken_ = function() { if (error != remoting.Error.NOT_AUTHENTICATED) { throw new Error('Unexpected error fetch auth token: ' + error); } - oauth2.doAuthRedirect(onAuthenticated); + oauth2.removeCachedAuthToken(); }; oauth2.callWithToken(resolve, onError); }); diff --git a/remoting/webapp/crd/js/oauth2.js b/remoting/webapp/crd/js/oauth2.js index 52e12f884148..7a18a9c99655 100644 --- a/remoting/webapp/crd/js/oauth2.js +++ b/remoting/webapp/crd/js/oauth2.js @@ -82,15 +82,19 @@ remoting.OAuth2.prototype.isAuthenticated = function() { }; /** - * Removes all storage, and effectively unauthenticates the user. + * Remove the cached auth token, if any. * + * @param {function():void=} opt_onDone Completion callback. * @return {void} Nothing. */ -remoting.OAuth2.prototype.clear = function() { +remoting.OAuth2.prototype.removeCachedAuthToken = function(opt_onDone) { window.localStorage.removeItem(this.KEY_EMAIL_); window.localStorage.removeItem(this.KEY_FULLNAME_); this.clearAccessToken_(); this.clearRefreshToken_(); + if (opt_onDone) { + opt_onDone(); + } }; /** diff --git a/remoting/webapp/crd/js/remoting.js b/remoting/webapp/crd/js/remoting.js index df203f1252e2..d86e6ec31f2f 100644 --- a/remoting/webapp/crd/js/remoting.js +++ b/remoting/webapp/crd/js/remoting.js @@ -109,10 +109,11 @@ remoting.promptClose = function() { * Also clear all local storage, to avoid leaking information. */ remoting.signOut = function() { - remoting.oauth2.clear(); - chrome.storage.local.clear(); - remoting.setMode(remoting.AppMode.HOME); - document.getElementById('auth-dialog').hidden = false; + remoting.oauth2.removeCachedAuthToken(function(){ + chrome.storage.local.clear(); + remoting.setMode(remoting.AppMode.HOME); + window.location.reload(); + }); }; /** @@ -193,9 +194,13 @@ remoting.showErrorMessage = function(error) { document.getElementById('token-refresh-error-message'), error); var auth_failed = (error == remoting.Error.AUTHENTICATION_FAILED); - document.getElementById('token-refresh-auth-failed').hidden = !auth_failed; - document.getElementById('token-refresh-other-error').hidden = auth_failed; - remoting.setMode(remoting.AppMode.TOKEN_REFRESH_FAILED); + if (base.isAppsV2()) { + remoting.handleAuthFailureAndRelaunch(); + } else { + document.getElementById('token-refresh-auth-failed').hidden = !auth_failed; + document.getElementById('token-refresh-other-error').hidden = auth_failed; + remoting.setMode(remoting.AppMode.TOKEN_REFRESH_FAILED); + } }; /** diff --git a/remoting/webapp/js_proto/dom_proto.js b/remoting/webapp/js_proto/dom_proto.js index 24f58d9c891b..518bebccf02c 100644 --- a/remoting/webapp/js_proto/dom_proto.js +++ b/remoting/webapp/js_proto/dom_proto.js @@ -52,6 +52,11 @@ Element.prototype.checked; /** @type {Window} */ HTMLIFrameElement.prototype.contentWindow; +/** + * @param {string} selector + * @return {?HTMLElement}. + */ +HTMLElement.prototype.querySelector = function(selector) {}; /** * @param {string} name @@ -114,6 +119,11 @@ Event.prototype.initMouseEvent = function( /** @type {Object} */ Event.prototype.data = {}; +/** + * @param {*} value + * @return {boolean} whether value is an integer or not. + */ +Number.isInteger = function(value) {}; // Chrome implements XMLHttpRequest.responseURL starting from Chrome 37. /** @type {string} */ -- 2.11.4.GIT