Commit 12d28546 authored by jamiewalch@google.com's avatar jamiewalch@google.com

Refactored HostList to better support bookmarking.

BUG=106218,104368
TEST=Bookmark a host, then restart it. The bookmark should still work.

Review URL: http://codereview.chromium.org/8782001

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@114116 0039d316-1c4b-4281-b951-d872f2087c98
parent 83199c91
......@@ -103,6 +103,10 @@
"message": "The server failed to respond to the network request.",
"description": "Error displayed by the client if the server does not respond to a network request."
},
"ERROR_UNEXPECTED": {
"message": "An unexpected error occurred. Please report this problem to the developers.",
"description": "Error displayed in situations where things go wrong in ways not anticipated by the developers. There is typically no user-workaround to suggest, other than reporting the error so that we can investigate further."
},
"FOOTER_WAITING": {
"message": "waiting for connection\u2026",
"description": "Footer text displayed at the host after an access code has been generated, but before a client connects."
......
......@@ -55,7 +55,7 @@ found in the LICENSE file.
hidden>
<span id="email-status">
<span id="current-email"></span>
<span data-ui-mode="home client.unconnected">
<span data-ui-mode="home client.unconnected client.connect-failed">
(<a href="#" onclick="remoting.clearOAuth2();"
i18n-content="SIGN_OUT_BUTTON"></a>)
</span>
......
......@@ -35,6 +35,12 @@ remoting.accessCode = '';
*/
remoting.hostJid = '';
/**
* @type {string} For Me2Me connections, the id of the current host, used when
* (re-)connecting, as the JID may have changed.
*/
remoting.hostId = '';
/**
* @type {string} The host's public key, returned by the server.
*/
......@@ -248,7 +254,7 @@ function onClientStateChange_(oldState, newState) {
remoting.clientSession.error);
if (remoting.clientSession.error ==
remoting.ClientSession.ConnectionError.HOST_IS_OFFLINE) {
showConnectError_(remoting.Error.HOST_IS_OFFLINE);
retryConnectOrReportOffline_();
} else if (remoting.clientSession.error ==
remoting.ClientSession.ConnectionError.SESSION_REJECTED) {
showConnectError_(remoting.Error.INVALID_ACCESS_CODE);
......@@ -270,6 +276,30 @@ function onClientStateChange_(oldState, newState) {
}
}
/**
* If we have a hostId to retry, try refreshing it and connecting again. If not,
* then show the 'host offline' error message.
*
* @return {void} Nothing.
*/
function retryConnectOrReportOffline_() {
if (remoting.hostId) {
console.log('Connection failed. Retrying.');
/** @param {boolean} success True if the refresh was successful. */
var onDone = function(success) {
if (success) {
remoting.connectHost(remoting.hostId, false);
} else {
showConnectError_(remoting.Error.HOST_IS_OFFLINE);
}
};
remoting.hostList.refresh(onDone);
} else {
console.log('Connection failed. Not retrying.');
showConnectError_(remoting.Error.HOST_IS_OFFLINE);
}
}
/**
* Create the client session object and initiate the connection.
*
......@@ -396,25 +426,32 @@ function updateStatistics_() {
/**
* Start a connection to the specified host, using the stored details.
* Start a connection to the specified host, using the cached details.
*
* @param {string} hostJid The jabber Id of the host.
* @param {string} hostPublicKey The public key of the host.
* @param {string} hostName The name of the host.
* @param {string} hostId The unique id of the host.
* @param {boolean} retryIfOffline If true and the host can't be contacted,
* refresh the host list and try again. This allows bookmarked hosts to
* work even if they reregister with Talk and get a different Jid.
* @return {void} Nothing.
*/
remoting.connectHost = function(hostJid, hostPublicKey, hostName) {
// TODO(jamiewalch): Instead of passing the jid in the URL, cache it in local
// storage so that host bookmarks can be implemented efficiently.
remoting.hostJid = hostJid;
remoting.hostPublicKey = hostPublicKey;
document.getElementById('connected-to').innerText = hostName;
document.title = document.title + ': ' + hostName;
remoting.connectHost = function(hostId, retryIfOffline) {
remoting.debug.log('Connecting to host...');
remoting.currentConnectionType = remoting.ConnectionType.Me2Me;
// Storing the hostId indicates that it should be retried on failure.
remoting.hostId = retryIfOffline ? hostId : '';
remoting.setMode(remoting.AppMode.CLIENT_CONNECTING);
var host = remoting.hostList.getHostForId(hostId);
if (!host) {
retryConnectOrReportOffline_();
return;
}
remoting.hostJid = host.jabberId;
remoting.hostPublicKey = host.publicKey;
document.getElementById('connected-to').innerText = host.hostName;
document.title = document.title + ': ' + host.hostName;
if (!remoting.wcsLoader) {
remoting.wcsLoader = new remoting.WcsLoader();
}
......@@ -432,10 +469,10 @@ remoting.connectHost = function(hostJid, hostPublicKey, hostName) {
*/
remoting.connectHostWithWcs = function() {
remoting.clientSession =
new remoting.ClientSession(
remoting.hostJid, remoting.hostPublicKey,
'', /** @type {string} */ (remoting.oauth2.getCachedEmail()),
onClientStateChange_);
new remoting.ClientSession(
remoting.hostJid, remoting.hostPublicKey,
'', /** @type {string} */ (remoting.oauth2.getCachedEmail()),
onClientStateChange_);
/** @param {string} token The auth token. */
var createPluginAndConnect = function(token) {
remoting.clientSession.createPluginAndConnect(
......
......@@ -16,6 +16,7 @@ var remoting = remoting || {};
* Create a host list consisting of the specified HTML elements, which should
* have a common parent that contains only host-list UI as it will be hidden
* if the host-list is empty.
*
* @constructor
* @param {Element} table The HTML <table> to contain host-list.
* @param {Element} errorDiv The HTML <div> to display error messages.
......@@ -35,18 +36,40 @@ remoting.HostList = function(table, errorDiv) {
* @type {Array.<remoting.HostTableEntry>}
* @private
*/
this.hostTableEntries_ = null;
this.hostTableEntries_ = [];
/**
* @type {Array.<remoting.Host>}
* @private
*/
this.hosts_ = [];
/**
* @type {string}
* @private
*/
this.lastError_ = '';
// Load the cache of the last host-list, if present.
var cached = /** @type {string} */
(window.localStorage.getItem(remoting.HostList.HOSTS_KEY));
if (cached) {
try {
this.hosts_ = /** @type {Array} */ JSON.parse(cached);
} catch (err) {
console.error('Invalid host list cache:', /** @type {*} */(err));
}
}
};
/**
* Search the host list for a host with the specified id.
*
* @param {string} hostId The unique id of the host.
* @return {remoting.HostTableEntry?} The host table entry, if any.
* @return {remoting.Host?} The host, if any.
*/
remoting.HostList.prototype.getHostForId = function(hostId) {
for (var i = 0; i < this.hostTableEntries_.length; ++i) {
if (this.hostTableEntries_[i].host.hostId == hostId) {
return this.hostTableEntries_[i];
for (var i = 0; i < this.hosts_.length; ++i) {
if (this.hosts_[i].hostId == hostId) {
return this.hosts_[i];
}
}
return null;
......@@ -55,16 +78,18 @@ remoting.HostList.prototype.getHostForId = function(hostId) {
/**
* Query the Remoting Directory for the user's list of hosts.
*
* @param {function(boolean):void} onDone Callback invoked with true on success
* or false on failure.
* @return {void} Nothing.
*/
remoting.HostList.prototype.refresh = function() {
remoting.HostList.prototype.refresh = function(onDone) {
/** @type {remoting.HostList} */
var that = this;
/** @param {XMLHttpRequest} xhr */
/** @param {XMLHttpRequest} xhr The response from the server. */
var parseHostListResponse = function(xhr) {
that.parseHostListResponse_(xhr);
that.parseHostListResponse_(xhr, onDone);
}
/** @param {string} token */
/** @param {string} token The OAuth2 token. */
var getHosts = function(token) {
var headers = { 'Authorization': 'OAuth ' + token };
remoting.xhr.get(
......@@ -72,7 +97,7 @@ remoting.HostList.prototype.refresh = function() {
parseHostListResponse, '', headers);
};
remoting.oauth2.callWithToken(getHosts);
}
};
/**
* Handle the results of the host list request. A success response will
......@@ -80,41 +105,50 @@ remoting.HostList.prototype.refresh = function() {
* able to successfully parse it.
*
* @param {XMLHttpRequest} xhr The XHR object for the host list request.
* @param {function(boolean):void} onDone The callback passed to |refresh|.
* @return {void} Nothing.
* @private
*/
remoting.HostList.prototype.parseHostListResponse_ = function(xhr) {
remoting.HostList.prototype.parseHostListResponse_ = function(xhr, onDone) {
this.hosts_ = [];
this.lastError_ = '';
try {
if (xhr.status == 200) {
var parsed_response =
/** @type {{data: {items: Array}}} */ JSON.parse(xhr.responseText);
if (parsed_response.data && parsed_response.data.items) {
this.setHosts_(parsed_response.data.items);
this.hosts_ = parsed_response.data.items;
}
} else {
// Some other error.
console.error('Bad status on host list query: ', xhr);
// For most errors in the 4xx range, tell the user to re-authorize us.
if (xhr.status == 403) {
// The user's account is not enabled for Me2Me, so fail silently.
} else if (xhr.status >= 400 && xhr.status <= 499) {
this.showError_(remoting.Error.GENERIC);
} else if (xhr.status >= 400 && xhr.status < 500) {
// For other errors, tell the user to re-authorize us.
this.lastError_ = remoting.Error.GENERIC;
} else {
this.lastError_ = remoting.Error.UNEXPECTED;
}
}
} catch (er) {
var typed_er = /** @type {Object} */ (er);
console.error('Error processing response: ', xhr, typed_er);
this.lastError_ = remoting.Error.UNEXPECTED;
}
}
window.localStorage.setItem(remoting.HostList.HOSTS_KEY,
JSON.stringify(this.hosts_));
onDone(this.lastError_ == '');
};
/**
* Refresh the host list with up-to-date details.
* @param {Array.<remoting.Host>} hosts The new host list.
* Display the list of hosts or error condition.
*
* @return {void} Nothing.
* @private
*/
remoting.HostList.prototype.setHosts_ = function(hosts) {
remoting.HostList.prototype.display = function() {
this.table_.innerHTML = '';
this.showError_(null);
this.errorDiv_.innerText = '';
this.hostTableEntries_ = [];
/**
......@@ -130,9 +164,9 @@ remoting.HostList.prototype.setHosts_ = function(hosts) {
*/
var onDelete = function(hostTableEntry) { that.deleteHost_(hostTableEntry); }
for (var i = 0; i < hosts.length; ++i) {
for (var i = 0; i < this.hosts_.length; ++i) {
/** @type {remoting.Host} */
var host = hosts[i];
var host = this.hosts_[i];
// Validate the entry to make sure it has all the fields we expect.
if (host.hostName && host.hostId && host.status && host.jabberId &&
host.publicKey) {
......@@ -143,28 +177,16 @@ remoting.HostList.prototype.setHosts_ = function(hosts) {
}
}
this.showOrHide_(this.hostTableEntries_.length != 0);
};
/**
* Display a localized error message.
* @param {remoting.Error?} errorTag The error to display, or NULL to clear any
* previous error.
* @return {void} Nothing.
*/
remoting.HostList.prototype.showError_ = function(errorTag) {
this.table_.innerHTML = '';
if (errorTag) {
l10n.localizeElementFromTag(this.errorDiv_,
/** @type {string} */ (errorTag));
this.showOrHide_(true);
} else {
this.errorDiv_.innerText = '';
if (this.lastError_ != '') {
l10n.localizeElementFromTag(this.errorDiv_, this.lastError_);
}
this.showOrHide_(this.hosts_.length != 0 || this.lastError_ != '');
};
/**
* Show or hide the host-list UI.
*
* @param {boolean} show True to show the UI, or false to hide it.
* @return {void} Nothing.
* @private
......@@ -193,7 +215,7 @@ remoting.HostList.prototype.deleteHost_ = function(hostTableEntry) {
this.hostTableEntries_.splice(index, 1);
}
/** @param {string} token */
/** @param {string} token The OAuth2 token. */
var deleteHost = function(token) {
var headers = { 'Authorization': 'OAuth ' + token };
remoting.xhr.remove(
......@@ -240,5 +262,10 @@ remoting.HostList.prototype.renameHost_ = function(hostTableEntry) {
*/
remoting.HostList.COLLAPSED_ = 'collapsed';
/**
* Key name under which Me2Me hosts are cached.
*/
remoting.HostList.HOSTS_KEY = 'me2me-cached-hosts';
/** @type {remoting.HostList} */
remoting.hostList = null;
......@@ -85,10 +85,7 @@ remoting.HostTableEntry.prototype.init = function(host, onRename, onDelete) {
var hostStatus = document.createElement('td');
if (host.status == 'ONLINE') {
var hostUrl = chrome.extension.getURL('choice.html') +
'?mode=me2me' +
'&hostJid=' + encodeURIComponent(host.jabberId) +
'&hostPublicKey=' + encodeURIComponent(host.publicKey) +
'&hostName=' + encodeURIComponent(host.hostName);
'?mode=me2me&hostId=' + encodeURIComponent(host.hostId);
var connectButton = document.createElement('button');
connectButton.setAttribute('class', 'mode-select-button');
connectButton.setAttribute('type', 'button');
......
......@@ -20,7 +20,8 @@ remoting.Error = {
HOST_IS_OFFLINE: /*i18n-content*/'ERROR_HOST_IS_OFFLINE',
INCOMPATIBLE_PROTOCOL: /*i18n-content*/'ERROR_INCOMPATIBLE_PROTOCOL',
BAD_PLUGIN_VERSION: /*i18n-content*/'ERROR_BAD_PLUGIN_VERSION',
GENERIC: /*i18n-content*/'ERROR_GENERIC'
GENERIC: /*i18n-content*/'ERROR_GENERIC',
UNEXPECTED: /*i18n-content*/'ERROR_UNEXPECTED'
};
(function() {
......@@ -55,10 +56,8 @@ remoting.init = function() {
var urlParams = getUrlParameters();
if ('mode' in urlParams) {
if (urlParams['mode'] == 'me2me') {
var hostJid = urlParams['hostJid'];
var hostPublicKey = urlParams['hostPublicKey'];
var hostName = urlParams['hostName'];
remoting.connectHost(hostJid, hostPublicKey, hostName);
var hostId = urlParams['hostId'];
remoting.connectHost(hostId, true);
return;
}
}
......
......@@ -70,7 +70,8 @@ remoting.setMode = function(mode) {
document.addEventListener('keydown', remoting.DebugLog.onKeydown, false);
}
if (mode == remoting.AppMode.HOME) {
remoting.hostList.refresh();
var display = function() { remoting.hostList.display(); };
remoting.hostList.refresh(display);
}
};
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment