summaryrefslogtreecommitdiffstats
path: root/remoting
diff options
context:
space:
mode:
Diffstat (limited to 'remoting')
-rw-r--r--remoting/webapp/_locales/en/messages.json10
-rw-r--r--remoting/webapp/client_screen.js20
-rw-r--r--remoting/webapp/event_handlers.js15
-rw-r--r--remoting/webapp/host_controller.js14
-rw-r--r--remoting/webapp/host_list.js94
-rw-r--r--remoting/webapp/host_screen.js28
-rw-r--r--remoting/webapp/host_setup_dialog.js18
-rw-r--r--remoting/webapp/main.html30
-rw-r--r--remoting/webapp/oauth2.js81
-rw-r--r--remoting/webapp/remoting.js22
-rw-r--r--remoting/webapp/ui_mode.js1
-rw-r--r--remoting/webapp/wcs.js13
-rw-r--r--remoting/webapp/wcs_loader.js41
13 files changed, 256 insertions, 131 deletions
diff --git a/remoting/webapp/_locales/en/messages.json b/remoting/webapp/_locales/en/messages.json
index 4ca21a7..fe9a84f 100644
--- a/remoting/webapp/_locales/en/messages.json
+++ b/remoting/webapp/_locales/en/messages.json
@@ -127,17 +127,13 @@
"description": "Column header in the connection history table showing the length of time for which a connection was active, if available."
},
"ERROR_AUTHENTICATION_FAILED": {
- "message": "Authentication failed. Please sign out of Chromoting and try again.",
+ "message": "Authentication failed. Please sign in to Chromoting again.",
"description": "Error displayed if authentication fails. This can be caused by stale credentials, in which logging out of the web-app and retrying can fix the problem."
},
"ERROR_BAD_PLUGIN_VERSION": {
"message": "Some components required for Chromoting are missing. Please make sure you have installed the latest version and try again.",
"description": "Error displayed if the host or client plugin are missing or if they could not be loaded."
},
- "ERROR_GENERIC": {
- "message": "An unknown error occurred. Please sign out of Chromoting and try again.",
- "description": "Generic error message, displayed if something went wrong, but we aren't able to determine what, or the cause is too technical to be of use to a typical user."
- },
"ERROR_HOST_OVERLOAD": {
"message": "Connections to the remote computer are temporarily blocked because somebody was trying to connect to it with invalid PIN. Please try again later.",
"description": "Error that is shown on the client side when the host is blocking all connections due to failed authentication attempts."
@@ -428,6 +424,10 @@
"message": "Shrink to fit",
"description": "Menu option for enabling shrink-to-fit. When clicked, the remote desktop will be scaled down so that it fits in the browser window."
},
+ "SIGN_IN_BUTTON": {
+ "message": "Sign in",
+ "description": "Sign in button, visible if the user's authentication token is corrupt, in which case the user has to sign in (authenticate) to the app again."
+ },
"SIGN_OUT_BUTTON": {
"message": "Sign out",
"description": "Sign out button, visible if the user has authenticated. Clicking this clears authentication credentials and returns the web-app to the initial 'unauthenticated' state."
diff --git a/remoting/webapp/client_screen.js b/remoting/webapp/client_screen.js
index f57c937..808c825 100644
--- a/remoting/webapp/client_screen.js
+++ b/remoting/webapp/client_screen.js
@@ -70,7 +70,8 @@ remoting.currentConnectionType = null;
*/
remoting.connectIt2Me = function() {
remoting.currentConnectionType = remoting.ConnectionType.It2Me;
- remoting.WcsLoader.load(connectIt2MeWithAccessToken_);
+ remoting.WcsLoader.load(connectIt2MeWithAccessToken_,
+ remoting.defaultOAuthErrorHandler);
};
/**
@@ -176,7 +177,7 @@ function connectIt2MeWithAccessToken_(token) {
} else {
var supportId = remoting.accessCode.substring(0, kSupportIdLen);
remoting.setMode(remoting.AppMode.CLIENT_CONNECTING);
- resolveSupportId(supportId);
+ resolveSupportId(supportId, token);
}
} else {
showConnectError_(remoting.Error.AUTHENTICATION_FAILED);
@@ -264,7 +265,7 @@ function onClientStateChange_(oldState, newState) {
remoting.ClientSession.ConnectionError.HOST_OVERLOAD) {
showConnectError_(remoting.Error.HOST_OVERLOAD);
} else {
- showConnectError_(remoting.Error.GENERIC);
+ showConnectError_(remoting.Error.UNEXPECTED);
}
if (clearPin) {
@@ -334,7 +335,8 @@ function startSession_() {
showConnectError_(remoting.Error.AUTHENTICATION_FAILED);
}
};
- remoting.oauth2.callWithToken(createPluginAndConnect);
+ remoting.oauth2.callWithToken(createPluginAndConnect,
+ remoting.defaultOAuthErrorHandler);
}
/**
@@ -397,7 +399,7 @@ function parseServerResponse_(xhr) {
console.error('Invalid "support-hosts" response from server.');
}
}
- var errorMsg = remoting.Error.GENERIC;
+ var errorMsg = remoting.Error.UNEXPECTED;
if (xhr.status == 404) {
errorMsg = remoting.Error.INVALID_ACCESS_CODE;
} else if (xhr.status == 0) {
@@ -426,10 +428,11 @@ function normalizeAccessCode_(accessCode) {
* Initiate a request to the server to resolve a support ID.
*
* @param {string} supportId The canonicalized support ID.
+ * @param {string} token The OAuth access token.
*/
-function resolveSupportId(supportId) {
+function resolveSupportId(supportId, token) {
var headers = {
- 'Authorization': 'OAuth ' + remoting.oauth2.getAccessToken()
+ 'Authorization': 'OAuth ' + token
};
remoting.supportHostsXhr_ = remoting.xhr.get(
@@ -506,7 +509,8 @@ remoting.connectMe2MeWithPin = function() {
document.title = chrome.i18n.getMessage('PRODUCT_NAME') + ': ' +
host.hostName;
- remoting.WcsLoader.load(connectMe2MeWithAccessToken_);
+ remoting.WcsLoader.load(connectMe2MeWithAccessToken_,
+ remoting.defaultOAuthErrorHandler);
};
/**
diff --git a/remoting/webapp/event_handlers.js b/remoting/webapp/event_handlers.js
index 778c160..338bd1e 100644
--- a/remoting/webapp/event_handlers.js
+++ b/remoting/webapp/event_handlers.js
@@ -12,7 +12,15 @@ function onLoad() {
window.location.replace(chrome.extension.getURL('main.html'));
};
var goEnterAccessCode = function() {
- remoting.setMode(remoting.AppMode.CLIENT_UNCONNECTED);
+ // We don't need a token until we authenticate, but asking for one here
+ // handles the token-expired case earlier, avoiding asking the user for
+ // the access code both before and after re-authentication.
+ remoting.oauth2.callWithToken(
+ /** @param {string} token */
+ function(token) {
+ remoting.setMode(remoting.AppMode.CLIENT_UNCONNECTED);
+ },
+ remoting.defaultOAuthErrorHandler);
};
var goFinishedIt2Me = function() {
if (remoting.currentMode == remoting.AppMode.CLIENT_CONNECT_FAILED_IT2ME) {
@@ -87,7 +95,10 @@ function onLoad() {
{ event: 'click', id: 'host-config-install-dismiss',
fn: function() { remoting.hostSetupDialog.hide(); } },
{ event: 'click', id: 'host-config-install-retry', fn: function() {
- remoting.hostSetupDialog.onInstallDialogRetry(); } }
+ remoting.hostSetupDialog.onInstallDialogRetry(); } },
+ { event: 'click', id: 'token-refresh-error-ok',
+ fn: function() { remoting.setMode(remoting.AppMode.HOME); } },
+ { event: 'click', id: 'token-refresh-error-sign-in', fn: doAuthRedirect }
];
for (var i = 0; i < actions.length; ++i) {
diff --git a/remoting/webapp/host_controller.js b/remoting/webapp/host_controller.js
index 63ef1f1..845e65d 100644
--- a/remoting/webapp/host_controller.js
+++ b/remoting/webapp/host_controller.js
@@ -225,14 +225,14 @@ remoting.HostController.prototype.start = function(hostPin, consent, callback) {
* @param {string} publicKey */
function onKeyGenerated(privateKey, publicKey) {
remoting.oauth2.callWithToken(
- /** @param {string?} oauthToken */
+ /** @param {string} oauthToken */
function(oauthToken) {
- if (oauthToken) {
- doRegisterHost(privateKey, publicKey, oauthToken);
- } else {
- // TODO(jamiewalch): Have a more specific error code here?
- callback(remoting.HostController.AsyncResult.FAILED);
- }
+ doRegisterHost(privateKey, publicKey, oauthToken);
+ },
+ /** @param {remoting.Error} error */
+ function(error) {
+ // TODO(jamiewalch): Have a more specific error code here?
+ callback(remoting.HostController.AsyncResult.FAILED);
});
};
diff --git a/remoting/webapp/host_list.js b/remoting/webapp/host_list.js
index dd5a546..540067e 100644
--- a/remoting/webapp/host_list.js
+++ b/remoting/webapp/host_list.js
@@ -19,9 +19,11 @@ var remoting = remoting || {};
*
* @constructor
* @param {Element} table The HTML <table> to contain host-list.
- * @param {Element} errorDiv The HTML <div> to display error messages.
+ * @param {Element} errorMsg The HTML <div> to display error messages.
+ * @param {Element} errorButton The HTML <button> to display the error
+ * resolution action.
*/
-remoting.HostList = function(table, errorDiv) {
+remoting.HostList = function(table, errorMsg, errorButton) {
/**
* @type {Element}
* @private
@@ -31,7 +33,12 @@ remoting.HostList = function(table, errorDiv) {
* @type {Element}
* @private
*/
- this.errorDiv_ = errorDiv;
+ this.errorMsg_ = errorMsg;
+ /**
+ * @type {Element}
+ * @private
+ */
+ this.errorButton_ = errorButton;
/**
* @type {Array.<remoting.HostTableEntry>}
* @private
@@ -48,6 +55,10 @@ remoting.HostList = function(table, errorDiv) {
*/
this.lastError_ = '';
+ this.errorButton_.addEventListener('click',
+ this.onErrorClick_.bind(this),
+ false);
+
// Load the cache of the last host-list, if present.
var cachedStr = /** @type {string} */
(window.localStorage.getItem(remoting.HostList.HOSTS_KEY));
@@ -88,21 +99,21 @@ remoting.HostList.prototype.refresh = function(onDone) {
var parseHostListResponse = this.parseHostListResponse_.bind(this, onDone);
/** @type {remoting.HostList} */
var that = this;
- /** @param {string?} token The OAuth2 token. */
+ /** @param {string} token The OAuth2 token. */
var getHosts = function(token) {
- if (token) {
- var headers = { 'Authorization': 'OAuth ' + token };
- remoting.xhr.get(
- 'https://www.googleapis.com/chromoting/v1/@me/hosts',
- parseHostListResponse, '', headers);
- } else {
- that.lastError_ = remoting.Error.AUTHENTICATION_FAILED;
- onDone(false);
- }
+ var headers = { 'Authorization': 'OAuth ' + token };
+ remoting.xhr.get(
+ 'https://www.googleapis.com/chromoting/v1/@me/hosts',
+ parseHostListResponse, '', headers);
+ };
+ /** @param {remoting.Error} error */
+ var onError = function(error) {
+ that.lastError_ = error;
+ onDone(false);
};
this.hosts_ = [];
this.lastError_ = '';
- remoting.oauth2.callWithToken(getHosts);
+ remoting.oauth2.callWithToken(getHosts, onError);
};
/**
@@ -141,11 +152,8 @@ remoting.HostList.prototype.parseHostListResponse_ = function(onDone, xhr) {
} else {
// Some other error.
console.error('Bad status on host list query: ', xhr);
- if (xhr.status == 403) {
- // The user's account is not enabled for Me2Me, so fail silently.
- } else if (xhr.status >= 400 && xhr.status < 500) {
- // For other errors, tell the user to re-authorize us.
- this.lastError_ = remoting.Error.GENERIC;
+ if (xhr.status == 401) {
+ this.lastError_ = remoting.Error.AUTHENTICATION_FAILED;
} else if (xhr.status == 503) {
this.lastError_ = remoting.Error.SERVICE_UNAVAILABLE;
} else {
@@ -170,7 +178,7 @@ remoting.HostList.prototype.parseHostListResponse_ = function(onDone, xhr) {
*/
remoting.HostList.prototype.display = function(thisHostId) {
this.table_.innerText = '';
- this.errorDiv_.innerText = '';
+ this.errorMsg_.innerText = '';
this.hostTableEntries_ = [];
this.table_.hidden = (this.hosts_.length == 0);
@@ -193,9 +201,16 @@ remoting.HostList.prototype.display = function(thisHostId) {
}
if (this.lastError_ != '') {
- l10n.localizeElementFromTag(this.errorDiv_, this.lastError_);
+ l10n.localizeElementFromTag(this.errorMsg_, this.lastError_);
+ if (this.lastError_ == remoting.Error.AUTHENTICATION_FAILED) {
+ l10n.localizeElementFromTag(this.errorButton_,
+ /*i18n-content*/'SIGN_IN_BUTTON');
+ } else {
+ l10n.localizeElementFromTag(this.errorButton_,
+ /*i18n-content*/'RETRY');
+ }
}
- this.errorDiv_.hidden = (this.lastError_ == '');
+ this.errorMsg_.parentNode.hidden = (this.lastError_ == '');
};
/**
@@ -219,19 +234,14 @@ remoting.HostList.prototype.deleteHost_ = function(hostTableEntry) {
* @return {void} Nothing.
*/
remoting.HostList.unregisterHostById = function(hostId) {
- /** @param {string?} token The OAuth2 token. */
+ /** @param {string} token The OAuth2 token. */
var deleteHost = function(token) {
- if (token) {
- var headers = { 'Authorization': 'OAuth ' + token };
- remoting.xhr.remove(
- 'https://www.googleapis.com/chromoting/v1/@me/hosts/' + hostId,
- function() {}, '', headers);
- } else {
- console.error('Could not unregister host. Authentication failure.');
- // TODO(jamiewalch): Add a callback to signify success/failure?
- }
+ var headers = { 'Authorization': 'OAuth ' + token };
+ remoting.xhr.remove(
+ 'https://www.googleapis.com/chromoting/v1/@me/hosts/' + hostId,
+ function() {}, '', headers);
}
- remoting.oauth2.callWithToken(deleteHost);
+ remoting.oauth2.callWithToken(deleteHost, remoting.defaultOAuthErrorHandler);
};
/**
@@ -271,7 +281,7 @@ remoting.HostList.prototype.renameHost = function(hostTableEntry) {
console.error('Could not rename host. Authentication failure.');
}
}
- remoting.oauth2.callWithToken(renameHost);
+ remoting.oauth2.callWithToken(renameHost, remoting.defaultOAuthErrorHandler);
};
/**
@@ -285,6 +295,22 @@ remoting.HostList.prototype.addHost = function(localHost) {
this.hosts_.push(localHost);
window.localStorage.setItem(remoting.HostList.HOSTS_KEY,
JSON.stringify(this.hosts_));
+};
+
+/**
+ * Called when the user clicks the button next to the error message. The action
+ * depends on the error.
+ *
+ * @private
+ */
+remoting.HostList.prototype.onErrorClick_ = function() {
+ if (this.lastError_ == remoting.Error.AUTHENTICATION_FAILED) {
+ remoting.oauth2.doAuthRedirect();
+ } else {
+ this.lastError_ = '';
+ this.display(null);
+ this.refresh(remoting.extractThisHostAndDisplay);
+ }
}
/**
diff --git a/remoting/webapp/host_screen.js b/remoting/webapp/host_screen.js
index f904c96..7edb2f0 100644
--- a/remoting/webapp/host_screen.js
+++ b/remoting/webapp/host_screen.js
@@ -26,20 +26,16 @@ var lastShareWasCancelled_ = false;
*/
remoting.tryShare = function() {
console.log('Attempting to share...');
- lastShareWasCancelled_ = false;
- if (remoting.oauth2.needsNewAccessToken()) {
- console.log('Refreshing token...');
- remoting.oauth2.refreshAccessToken(function() {
- if (remoting.oauth2.needsNewAccessToken()) {
- // If we still need it, we're going to infinite loop.
- showShareError_(remoting.Error.AUTHENTICATION_FAILED);
- throw 'Unable to get access token';
- }
- remoting.tryShare();
- });
- return;
- }
+ remoting.oauth2.callWithToken(remoting.tryShareWithToken_,
+ remoting.defaultOAuthErrorHandler);
+};
+/**
+ * @param {string} token The OAuth access token.
+ * @private
+ */
+remoting.tryShareWithToken_ = function(token) {
+ lastShareWasCancelled_ = false;
onNatTraversalPolicyChanged_(true); // Hide warning by default.
remoting.setMode(remoting.AppMode.HOST_WAITING_FOR_CODE);
document.getElementById('cancel-share-button').disabled = false;
@@ -50,7 +46,7 @@ remoting.tryShare = function() {
remoting.hostSession.createPluginAndConnect(
document.getElementById('host-plugin-container'),
/** @type {string} */(remoting.oauth2.getCachedEmail()),
- remoting.oauth2.getAccessToken(),
+ token,
onNatTraversalPolicyChanged_,
onHostStateChanged_,
logDebugInfo_);
@@ -124,7 +120,7 @@ function onHostStateChanged_(state) {
} else if (state == remoting.HostSession.State.ERROR) {
console.error('Host plugin state: ERROR');
- showShareError_(remoting.Error.GENERIC);
+ showShareError_(remoting.Error.UNEXPECTED);
} else {
console.error('Unknown state -> ' + state);
}
@@ -172,7 +168,7 @@ remoting.cancelShare = function() {
// the host plugin, like we do for the client, which should handle crash
// reporting and it should use a more detailed error message than the
// default 'generic' one. See crbug.com/94624
- showShareError_(remoting.Error.GENERIC);
+ showShareError_(remoting.Error.UNEXPECTED);
}
disableTimeoutCountdown_();
};
diff --git a/remoting/webapp/host_setup_dialog.js b/remoting/webapp/host_setup_dialog.js
index fe60ecb..c0e5ae2 100644
--- a/remoting/webapp/host_setup_dialog.js
+++ b/remoting/webapp/host_setup_dialog.js
@@ -150,6 +150,18 @@ remoting.HostSetupDialog = function(hostController) {
* @return {void} Nothing.
*/
remoting.HostSetupDialog.prototype.showForStart = function() {
+ // Although we don't need an access token in order to start the host,
+ // using callWithToken here ensures consistent error handling in the
+ // case where the refresh token is invalid.
+ remoting.oauth2.callWithToken(this.showForStartWithToken_.bind(this),
+ remoting.defaultOAuthErrorHandler);
+};
+
+/**
+ * @param {string} token The OAuth2 token.
+ * @private
+ */
+remoting.HostSetupDialog.prototype.showForStartWithToken_ = function(token) {
/** @type {remoting.HostSetupDialog} */
var that = this;
@@ -423,7 +435,7 @@ remoting.HostSetupDialog.validPin_ = function(pin) {
}
}
return true;
-}
+};
/**
* @return {void} Nothing.
@@ -436,14 +448,14 @@ remoting.HostSetupDialog.prototype.onInstallDialogOk = function() {
} else {
remoting.setMode(remoting.AppMode.HOST_SETUP_INSTALL_PENDING);
}
-}
+};
/**
* @return {void} Nothing.
*/
remoting.HostSetupDialog.prototype.onInstallDialogRetry = function() {
remoting.setMode(remoting.AppMode.HOST_SETUP_INSTALL);
-}
+};
/** @type {remoting.HostSetupDialog} */
remoting.hostSetupDialog = null;
diff --git a/remoting/webapp/main.html b/remoting/webapp/main.html
index 17a338c..a47b80e 100644
--- a/remoting/webapp/main.html
+++ b/remoting/webapp/main.html
@@ -140,7 +140,12 @@ found in the LICENSE file.
</div>
<div id="me2me-content">
<div id="host-list" hidden></div>
- <div id="host-list-error" class="error-state" hidden></div>
+ <div id="host-list-error" class="box" hidden>
+ <div id="host-list-error-message" class="error-state"></div>
+ <div class="box-spacer"></div>
+ <button type="button"
+ id="host-list-refresh-failed-button"></button>
+ </div>
<div id="daemon-control" data-daemon-state="enabled disabled" hidden>
<div class="section-row no-non-local-hosts"
data-daemon-state="disabled">
@@ -182,15 +187,34 @@ found in the LICENSE file.
</div> <!-- home -->
<div id="dialog-screen"
- data-ui-mode="home.host home.client home.auth home.history home.confirm-host-delete home.host-setup"
+ data-ui-mode="home.host home.client home.auth home.history home.confirm-host-delete home.host-setup home.token-refresh-failed"
hidden></div>
<div id="dialog-container"
- data-ui-mode="home.host home.client home.auth home.history home.confirm-host-delete home.host-setup"
+ data-ui-mode="home.host home.client home.auth home.history home.confirm-host-delete home.host-setup home.token-refresh-failed"
hidden>
<div class="box-spacer"></div>
+ <!-- TODO(jamiewalch): Refactor the various error-state divs -->
+ <div class="kd-modaldialog" data-ui-mode="home.token-refresh-failed">
+ <div class="message">
+ <span id="token-refresh-error-message" class="error-state"></span>
+ </div>
+ <div id="token-refresh-auth-failed" class="box">
+ <div class="box-spacer"></div>
+ <button id="token-refresh-error-sign-in"
+ type="button"
+ i18n-content="SIGN_IN_BUTTON"></button>
+ </div>
+ <div id="token-refresh-other-error" class="box">
+ <div class="box-spacer"></div>
+ <button id="token-refresh-error-ok"
+ type="button"
+ i18n-content="OK"></button>
+ </div>
+ </div> <!-- home.token-refresh-failed -->
+
<div id="host-setup-dialog"
class="kd-modaldialog"
data-ui-mode="home.host-setup">
diff --git a/remoting/webapp/oauth2.js b/remoting/webapp/oauth2.js
index 6024afc..e030822 100644
--- a/remoting/webapp/oauth2.js
+++ b/remoting/webapp/oauth2.js
@@ -184,8 +184,9 @@ remoting.OAuth2.prototype.needsNewAccessToken = function() {
* Will throw if !isAuthenticated() or needsNewAccessToken().
*
* @return {string} The access token.
+ * @private
*/
-remoting.OAuth2.prototype.getAccessToken = function() {
+remoting.OAuth2.prototype.getAccessToken_ = function() {
if (this.needsNewAccessToken()) {
throw 'Access Token expired.';
}
@@ -245,8 +246,9 @@ remoting.OAuth2.prototype.processTokenResponse_ = function(onDone, xhr) {
* @param {function(XMLHttpRequest): void} onDone Callback to invoke on
* completion.
* @return {void} Nothing.
+ * @private
*/
-remoting.OAuth2.prototype.refreshAccessToken = function(onDone) {
+remoting.OAuth2.prototype.refreshAccessToken_ = function(onDone) {
if (!this.isAuthenticated()) {
throw 'Not Authenticated.';
}
@@ -335,24 +337,51 @@ remoting.OAuth2.prototype.revokeToken_ = function(token) {
*
* The access token will remain valid for at least 2 minutes.
*
- * @param {function(string?):void} myfunc Function to invoke with access token.
+ * @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.OAuth2.prototype.callWithToken = function(myfunc) {
- /** @type {remoting.OAuth2} */
- var that = this;
+remoting.OAuth2.prototype.callWithToken = function(onOk, onError) {
if (this.needsNewAccessToken()) {
- var onRefresh = function() {
- if (that.needsNewAccessToken()) {
- myfunc(null);
- } else {
- myfunc(that.getAccessToken());
- }
- };
- this.refreshAccessToken(onRefresh);
+ this.refreshAccessToken_(this.onRefreshToken_.bind(this, onOk, onError));
} else {
- myfunc(this.getAccessToken());
+ onOk(this.getAccessToken_());
+ }
+};
+
+/**
+ * Process token refresh results and notify caller.
+ *
+ * @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.
+ * @param {XMLHttpRequest} xhr The result of the refresh operation.
+ * @private
+ */
+remoting.OAuth2.prototype.onRefreshToken_ = function(onOk, onError, xhr) {
+ var error = remoting.Error.UNEXPECTED;
+ if (xhr.status == 200) {
+ onOk(this.getAccessToken_());
+ return;
+ } else if (xhr.status == 400) {
+ var result =
+ /** @type {{error: string}} */ (jsonParseSafe(xhr.responseText));
+ if (result && result.error == 'invalid_grant') {
+ error = remoting.Error.AUTHENTICATION_FAILED;
+ }
+ } else if (xhr.status == 401) {
+ // According to the OAuth2 draft RFC, the server shouldn't return 401,
+ // but AUTHENTICATION_FAILED is the obvious interpretation if it does.
+ console.warn('Unexpected 401 in response to refresh.');
+ error = remoting.Error.AUTHENTICATION_FAILED;
+ } else if (xhr.status == 503) {
+ error = remoting.Error.SERVICE_UNAVAILABLE;
}
+ // TODO(jamiewalch): Add timeout support.
+ onError(error);
};
/**
@@ -378,20 +407,20 @@ remoting.OAuth2.prototype.getEmail = function(setEmail) {
setEmail(that.email);
};
- /** @param {string?} token The access token. */
+ /** @param {string} token The access token. */
var getEmailFromToken = function(token) {
- if (token) {
- var headers = { 'Authorization': 'OAuth ' + token };
- // TODO(ajwong): Update to new v2 API.
- remoting.xhr.get('https://www.googleapis.com/userinfo/email',
- onResponse, '', headers);
- } else {
- console.error('Unable to get email address: no access token');
- setEmail(null);
- }
+ var headers = { 'Authorization': 'OAuth ' + token };
+ // TODO(ajwong): Update to new v2 API.
+ remoting.xhr.get('https://www.googleapis.com/userinfo/email',
+ onResponse, '', headers);
+ };
+ /** @param {remoting.Error} error */
+ var onError = function(error) {
+ console.error('Unable to get email address: ' + error);
+ setEmail(null);
};
- this.callWithToken(getEmailFromToken);
+ this.callWithToken(getEmailFromToken, onError);
};
/**
diff --git a/remoting/webapp/remoting.js b/remoting/webapp/remoting.js
index 1737436..5fa18f4 100644
--- a/remoting/webapp/remoting.js
+++ b/remoting/webapp/remoting.js
@@ -22,7 +22,6 @@ remoting.Error = {
BAD_PLUGIN_VERSION: /*i18n-content*/'ERROR_BAD_PLUGIN_VERSION',
NETWORK_FAILURE: /*i18n-content*/'ERROR_NETWORK_FAILURE',
HOST_OVERLOAD: /*i18n-content*/'ERROR_HOST_OVERLOAD',
- GENERIC: /*i18n-content*/'ERROR_GENERIC',
UNEXPECTED: /*i18n-content*/'ERROR_UNEXPECTED',
SERVICE_UNAVAILABLE: /*i18n-content*/'ERROR_SERVICE_UNAVAILABLE'
};
@@ -40,7 +39,8 @@ remoting.init = function() {
remoting.formatIq = new remoting.FormatIq();
remoting.hostList = new remoting.HostList(
document.getElementById('host-list'),
- document.getElementById('host-list-error'));
+ document.getElementById('host-list-error-message'),
+ document.getElementById('host-list-refresh-failed-button'));
remoting.toolbar = new remoting.Toolbar(
document.getElementById('session-toolbar'));
remoting.clipboard = new remoting.Clipboard();
@@ -322,3 +322,21 @@ remoting.timestamp = function() {
pad(now.getSeconds(), 2) + '.' + pad(now.getMilliseconds(), 3);
return '[' + timestamp + ']';
};
+
+/**
+ * Default handler for OAuth token refresh failures. This switches the app mode
+ * to display an error message, optionally including a short-cut for signing in
+ * to Chromoting again.
+ *
+ * @param {remoting.Error} error
+ * @return {void} Nothing.
+ */
+remoting.defaultOAuthErrorHandler = function(error) {
+ l10n.localizeElementFromTag(
+ 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);
+};
diff --git a/remoting/webapp/ui_mode.js b/remoting/webapp/ui_mode.js
index ea1862d..e800e4a 100644
--- a/remoting/webapp/ui_mode.js
+++ b/remoting/webapp/ui_mode.js
@@ -22,6 +22,7 @@ var remoting = remoting || {};
remoting.AppMode = {
HOME: 'home',
UNAUTHENTICATED: 'home.auth',
+ TOKEN_REFRESH_FAILED: 'home.token-refresh-failed',
HOST: 'home.host',
HOST_WAITING_FOR_CODE: 'home.host.waiting-for-code',
HOST_WAITING_FOR_CONNECTION: 'home.host.waiting-for-connection',
diff --git a/remoting/webapp/wcs.js b/remoting/webapp/wcs.js
index 4906c82..d5c4257 100644
--- a/remoting/webapp/wcs.js
+++ b/remoting/webapp/wcs.js
@@ -53,6 +53,10 @@ remoting.Wcs = function(wcsIqClient, token, onReady) {
this.clientFullJid_ = '';
var updateAccessToken = this.updateAccessToken_.bind(this);
+ /** @param {remoting.Error} error */
+ var onError = function(error) {
+ console.error('updateAccessToken: Authentication failed: ' + error);
+ };
/**
* A timer that polls for an updated access token.
@@ -60,7 +64,9 @@ remoting.Wcs = function(wcsIqClient, token, onReady) {
* @private
*/
this.pollForUpdatedToken_ = setInterval(
- function() { remoting.oauth2.callWithToken(updateAccessToken); },
+ function() {
+ remoting.oauth2.callWithToken(updateAccessToken, onError);
+ },
60 * 1000);
/**
@@ -85,10 +91,7 @@ remoting.Wcs = function(wcsIqClient, token, onReady) {
* @private
*/
remoting.Wcs.prototype.updateAccessToken_ = function(tokenNew) {
- if (!tokenNew) {
- console.error('updateAccessToken_: Authentication failed.');
- // No need to update the token. The hanging GET will fail anyway.
- } else if (tokenNew != this.token_) {
+ if (tokenNew != this.token_) {
this.token_ = tokenNew;
this.wcsIqClient_.updateAccessToken(this.token_);
}
diff --git a/remoting/webapp/wcs_loader.js b/remoting/webapp/wcs_loader.js
index fdd3736..000369d 100644
--- a/remoting/webapp/wcs_loader.js
+++ b/remoting/webapp/wcs_loader.js
@@ -33,24 +33,21 @@ remoting.WcsLoader = function() {
/**
* Load WCS if necessary, then invoke the callback with an access token.
*
- * @param {function(string?): void} onReady The callback function, called with
- * an OAuth2 access token when WCS has been loaded, or with null on error.
+ * @param {function(string): void} onReady The callback function, called with
+ * an OAuth2 access token when WCS has been loaded.
+ * @param {function(remoting.Error):void} onError Function to invoke with an
+ * error code on failure.
* @return {void} Nothing.
*/
-remoting.WcsLoader.load = function(onReady) {
+remoting.WcsLoader.load = function(onReady, onError) {
if (!remoting.wcsLoader) {
remoting.wcsLoader = new remoting.WcsLoader();
}
- /** @param {string?} token The OAuth2 access token. */
+ /** @param {string} token The OAuth2 access token. */
var start = function(token) {
- if (token) {
- remoting.wcsLoader.start_(token, onReady);
- } else {
- console.error('WcsLoader: Authentication failed.');
- onReady(null);
- }
+ remoting.wcsLoader.start_(token, onReady, onError);
};
- remoting.oauth2.callWithToken(start);
+ remoting.oauth2.callWithToken(start, onError);
};
/**
@@ -79,15 +76,18 @@ remoting.WcsLoader.prototype.SCRIPT_NODE_LOADED_FLAG_ = 'wcs-script-loaded';
* Starts loading the WCS IQ client.
*
* When it's loaded, construct remoting.wcs as a wrapper for it.
- * When the WCS connection is ready, or on error, call |onReady|.
+ * When the WCS connection is ready, or on error, call |onReady| or |onError|,
+ * respectively.
*
* @param {string} token An OAuth2 access token.
- * @param {function(string?): void} onReady The callback function, called with
- * an OAuth2 access token when WCS has been loaded, or with null on error.
+ * @param {function(string): void} onReady The callback function, called with
+ * an OAuth2 access token when WCS has been loaded.
+ * @param {function(remoting.Error):void} onError Function to invoke with an
+ * error code on failure.
* @return {void} Nothing.
* @private
*/
-remoting.WcsLoader.prototype.start_ = function(token, onReady) {
+remoting.WcsLoader.prototype.start_ = function(token, onReady, onError) {
var node = document.getElementById(this.SCRIPT_NODE_ID_);
if (!node) {
// The first time, there will be no script node, so create one.
@@ -111,21 +111,22 @@ remoting.WcsLoader.prototype.start_ = function(token, onReady) {
typedNode.setAttribute(that.SCRIPT_NODE_LOADED_FLAG_, true);
that.constructWcs_(token, onReady);
};
- var onError = function() {
+ var removeNodeAndNotify = function() {
var typedNode = /** @type {Element} */ (node);
typedNode.parentNode.removeChild(node);
- onReady(null);
+ // TODO(jamiewalch): See if we can do better by looking at the event.
+ onError(remoting.Error.NETWORK_FAILURE);
};
node.addEventListener('load', onLoad, false);
- node.addEventListener('error', onError, false);
+ node.addEventListener('error', removeNodeAndNotify, false);
};
/**
* Constructs the remoting.wcs object.
*
* @param {string} token An OAuth2 access token.
- * @param {function(string?): void} onReady The callback function, called with
- * an OAuth2 access token when WCS has been loaded, or with null on error.
+ * @param {function(string): void} onReady The callback function, called with
+ * an OAuth2 access token when WCS has been loaded.
* @return {void} Nothing.
* @private
*/