containing the "no hosts" message.
* @param {Element} errorMsg The HTML
to display error messages.
* @param {Element} errorButton The HTML to display the error
* resolution action.
*/
remoting.HostList = function(table, noHosts, errorMsg, errorButton) {
/**
* @type {Element}
* @private
*/
this.table_ = table;
/**
* @type {Element}
* @private
* TODO(jamiewalch): This should be doable using CSS's sibling selector,
* but it doesn't work right now (crbug.com/135050).
*/
this.noHosts_ = noHosts;
/**
* @type {Element}
* @private
*/
this.errorMsg_ = errorMsg;
/**
* @type {Element}
* @private
*/
this.errorButton_ = errorButton;
/**
* @type {Array.}
* @private
*/
this.hostTableEntries_ = [];
/**
* @type {Array.}
* @private
*/
this.hosts_ = [];
/**
* @type {string}
* @private
*/
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));
if (cachedStr) {
var cached = jsonParseSafe(cachedStr);
if (cached) {
this.hosts_ = /** @type {Array} */ cached;
} else {
console.error('Invalid value for ' + remoting.HostList.HOSTS_KEY);
}
}
};
/**
* Search the host list for a host with the specified id.
*
* @param {string} hostId The unique id of the host.
* @return {remoting.Host?} The host, if any.
*/
remoting.HostList.prototype.getHostForId = function(hostId) {
for (var i = 0; i < this.hosts_.length; ++i) {
if (this.hosts_[i].hostId == hostId) {
return this.hosts_[i];
}
}
return null;
};
/**
* 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(onDone) {
/** @param {XMLHttpRequest} xhr The response from the server. */
var parseHostListResponse = this.parseHostListResponse_.bind(this, onDone);
/** @type {remoting.HostList} */
var that = this;
/** @param {string} token The OAuth2 token. */
var getHosts = function(token) {
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.hosts_ = [];
that.lastError_ = error;
onDone(false);
};
remoting.oauth2.callWithToken(getHosts, onError);
};
/**
* Handle the results of the host list request. A success response will
* include a JSON-encoded list of host descriptions, which we display if we're
* able to successfully parse it.
*
* @param {function(boolean):void} onDone The callback passed to |refresh|.
* @param {XMLHttpRequest} xhr The XHR object for the host list request.
* @return {void} Nothing.
* @private
*/
remoting.HostList.prototype.parseHostListResponse_ = function(onDone, xhr) {
this.hosts_ = [];
this.lastError_ = '';
try {
if (xhr.status == 200) {
var response =
/** @type {{data: {items: Array}}} */ jsonParseSafe(xhr.responseText);
if (response && response.data) {
if (response.data.items) {
this.hosts_ = response.data.items;
/**
* @param {remoting.Host} a
* @param {remoting.Host} b
*/
var cmp = function(a, b) {
if (a.status < b.status) {
return 1;
} else if (b.status < a.status) {
return -1;
}
return 0;
};
this.hosts_ = /** @type {Array} */ this.hosts_.sort(cmp);
}
} else {
this.lastError_ = remoting.Error.UNEXPECTED;
console.error('Invalid "hosts" response from server.');
}
} else {
// Some other error.
console.error('Bad status on host list query: ', xhr);
if (xhr.status == 401) {
this.lastError_ = remoting.Error.AUTHENTICATION_FAILED;
} else if (xhr.status == 503) {
this.lastError_ = remoting.Error.SERVICE_UNAVAILABLE;
} 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_ == '');
};
/**
* Display the list of hosts or error condition.
*
* @param {string?} thisHostId The id of this host, or null if not registered.
* @return {void} Nothing.
*/
remoting.HostList.prototype.display = function(thisHostId) {
this.table_.innerText = '';
this.errorMsg_.innerText = '';
this.hostTableEntries_ = [];
var noHostsRegistered = (this.hosts_.length == 0);
this.table_.hidden = noHostsRegistered;
this.noHosts_.hidden = !noHostsRegistered;
for (var i = 0; i < this.hosts_.length; ++i) {
/** @type {remoting.Host} */
var host = this.hosts_[i];
// Validate the entry to make sure it has all the fields we expect and is
// not the local host (which is displayed separately). NB: if the host has
// never sent a heartbeat, then there will be no jabberId.
if (host.hostName && host.hostId && host.status && host.publicKey &&
host.hostId != thisHostId) {
var hostTableEntry = new remoting.HostTableEntry();
hostTableEntry.create(host,
this.renameHost.bind(this),
this.deleteHost_.bind(this));
this.hostTableEntries_[i] = hostTableEntry;
this.table_.appendChild(hostTableEntry.tableRow);
}
}
if (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.errorMsg_.parentNode.hidden = (this.lastError_ == '');
};
/**
* Remove a host from the list, and deregister it.
* @param {remoting.HostTableEntry} hostTableEntry The host to be removed.
* @return {void} Nothing.
* @private
*/
remoting.HostList.prototype.deleteHost_ = function(hostTableEntry) {
this.table_.removeChild(hostTableEntry.tableRow);
var index = this.hostTableEntries_.indexOf(hostTableEntry);
if (index != -1) {
this.hostTableEntries_.splice(index, 1);
}
remoting.HostList.unregisterHostById(hostTableEntry.host.hostId);
};
/**
* Unregister a host.
* @param {string} hostId The id of the host to be removed.
* @return {void} Nothing.
*/
remoting.HostList.unregisterHostById = function(hostId) {
/** @param {string} token The OAuth2 token. */
var deleteHost = function(token) {
var headers = { 'Authorization': 'OAuth ' + token };
remoting.xhr.remove(
'https://www.googleapis.com/chromoting/v1/@me/hosts/' + hostId,
function() {}, '', headers);
}
remoting.oauth2.callWithToken(deleteHost, remoting.showErrorMessage);
};
/**
* Prepare a host for renaming by replacing its name with an edit box.
* @param {remoting.HostTableEntry} hostTableEntry The host to be renamed.
* @return {void} Nothing.
*/
remoting.HostList.prototype.renameHost = function(hostTableEntry) {
for (var i = 0; i < this.hosts_.length; ++i) {
if (this.hosts_[i].hostId == hostTableEntry.host.hostId) {
this.hosts_[i].hostName = hostTableEntry.host.hostName;
break;
}
}
window.localStorage.setItem(remoting.HostList.HOSTS_KEY,
JSON.stringify(this.hosts_));
/** @param {string?} token */
var renameHost = function(token) {
if (token) {
var headers = {
'Authorization': 'OAuth ' + token,
'Content-type' : 'application/json; charset=UTF-8'
};
var newHostDetails = { data: {
hostId: hostTableEntry.host.hostId,
hostName: hostTableEntry.host.hostName,
publicKey: hostTableEntry.host.publicKey
} };
remoting.xhr.put(
'https://www.googleapis.com/chromoting/v1/@me/hosts/' +
hostTableEntry.host.hostId,
function(xhr) {},
JSON.stringify(newHostDetails),
headers);
} else {
console.error('Could not rename host. Authentication failure.');
}
}
remoting.oauth2.callWithToken(renameHost, remoting.showErrorMessage);
};
/**
* Add a host to the list. This is called when the local host is started to
* avoid having to refresh the host list and deal with replication delays.
*
* @param {remoting.Host} localHost The local Me2Me host.
* @return {void} Nothing.
*/
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);
}
}
/**
* Key name under which Me2Me hosts are cached.
*/
remoting.HostList.HOSTS_KEY = 'me2me-cached-hosts';
/** @type {remoting.HostList} */
remoting.hostList = null;