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.
7 * Class representing the host-list portion of the home screen UI.
12 /** @suppress {duplicate} */
13 var remoting = remoting || {};
16 * Create a host list consisting of the specified HTML elements, which should
17 * have a common parent that contains only host-list UI as it will be hidden
18 * if the host-list is empty.
21 * @param {Element} table The HTML <div> to contain host-list.
22 * @param {Element} noHosts The HTML <div> containing the "no hosts" message.
23 * @param {Element} errorMsg The HTML <div> to display error messages.
24 * @param {Element} errorButton The HTML <button> to display the error
26 * @param {HTMLElement} loadingIndicator The HTML <span> to update while the
27 * host list is being loaded. The first element of this span should be
29 * @param {function(!remoting.Error)} onError Function to call when an error
31 * @param {function(string)} handleConnect Function to call to connect to the
34 remoting.HostList = function(table, noHosts, errorMsg, errorButton,
35 loadingIndicator, onError, handleConnect) {
36 /** @private {Element} */
39 * TODO(jamiewalch): This should be doable using CSS's sibling selector,
40 * but it doesn't work right now (crbug.com/135050).
43 this.noHosts_ = noHosts;
44 /** @private {Element} */
45 this.errorMsg_ = errorMsg;
46 /** @private {Element} */
47 this.errorButton_ = errorButton;
48 /** @private {HTMLElement} */
49 this.loadingIndicator_ = loadingIndicator;
51 this.onError_ = remoting.Error.handler(onError);
53 this.handleConnect_ = handleConnect;
55 /** @private {Array<remoting.HostTableEntry>} */
56 this.hostTableEntries_ = [];
57 /** @private {Array<remoting.Host>} */
59 /** @private {!remoting.Error} */
60 this.lastError_ = remoting.Error.none();
61 /** @private {remoting.LocalHostSection} */
62 this.localHostSection_ = new remoting.LocalHostSection(
63 /** @type {HTMLElement} */ (document.querySelector('.daemon-control')),
64 new remoting.LocalHostSection.Controller(
65 this, new remoting.HostSetupDialog(remoting.hostController, onError),
68 /** @private {number} */
69 this.webappMajorVersion_ = parseInt(chrome.runtime.getManifest().version, 10);
71 this.errorButton_.addEventListener('click',
72 this.onErrorClick_.bind(this),
74 var reloadButton = this.loadingIndicator_.firstElementChild;
75 /** @type {remoting.HostList} */
77 /** @param {Event} event */
78 function refresh(event) {
79 event.preventDefault();
80 that.refresh(that.display.bind(that));
82 reloadButton.addEventListener('click', refresh, false);
86 * Load the host-list asynchronously from local storage.
88 * @param {function():void} onDone Completion callback.
90 remoting.HostList.prototype.load = function(onDone) {
91 // Load the cache of the last host-list, if present.
92 /** @type {remoting.HostList} */
94 /** @param {Object<string>} items */
95 var storeHostList = function(items) {
96 if (items[remoting.HostList.HOSTS_KEY]) {
97 var cached = base.jsonParseSafe(items[remoting.HostList.HOSTS_KEY]);
99 that.hosts_ = /** @type {Array<remoting.Host>} */ (cached);
101 console.error('Invalid value for ' + remoting.HostList.HOSTS_KEY);
106 chrome.storage.local.get(remoting.HostList.HOSTS_KEY, storeHostList);
110 * Search the host list for a host with the specified id.
112 * @param {string} hostId The unique id of the host.
113 * @return {remoting.Host?} The host, if any.
115 remoting.HostList.prototype.getHostForId = function(hostId) {
116 for (var i = 0; i < this.hosts_.length; ++i) {
117 if (this.hosts_[i].hostId == hostId) {
118 return this.hosts_[i];
125 * Get the host id corresponding to the specified host name.
127 * @param {string} hostName The name of the host.
128 * @return {string?} The host id, if a host with the given name exists.
130 remoting.HostList.prototype.getHostIdForName = function(hostName) {
131 for (var i = 0; i < this.hosts_.length; ++i) {
132 if (this.hosts_[i].hostName == hostName) {
133 return this.hosts_[i].hostId;
140 * Query the Remoting Directory for the user's list of hosts.
142 * @param {function(boolean):void} onDone Callback invoked with true on success
143 * or false on failure.
144 * @return {void} Nothing.
146 remoting.HostList.prototype.refresh = function(onDone) {
147 this.loadingIndicator_.classList.add('loading');
148 /** @type {remoting.HostList} */
150 /** @param {!remoting.Error} error */
151 var onError = function(error) {
152 that.lastError_ = error;
155 remoting.HostListApi.getInstance().get().then(function(hosts) {
156 onDone(that.onHostListResponse_(hosts));
158 remoting.Error.handler(onError)
163 * Handle the results of the host list request. A success response will
164 * include a JSON-encoded list of host descriptions, which we display if we're
165 * able to successfully parse it.
167 * @param {Array<remoting.Host>} hosts The list of hosts for the user.
171 remoting.HostList.prototype.onHostListResponse_ = function(hosts) {
172 this.lastError_ = remoting.Error.none();
176 this.loadingIndicator_.classList.remove('loading');
181 * Sort the internal list of hosts.
183 * @suppress {reportUnknownTypes}
184 * @return {void} Nothing.
186 remoting.HostList.prototype.sortHosts_ = function() {
188 * Sort hosts, first by ONLINE/OFFLINE status and then by host-name.
190 * @param {remoting.Host} a
191 * @param {remoting.Host} b
194 var cmp = function(a, b) {
195 if (a.status < b.status) {
197 } else if (b.status < a.status) {
199 } else if (a.hostName.toLocaleLowerCase() <
200 b.hostName.toLocaleLowerCase()) {
202 } else if (a.hostName.toLocaleLowerCase() >
203 b.hostName.toLocaleLowerCase()) {
209 this.hosts_ = this.hosts_.sort(cmp);
213 * Display the list of hosts or error condition.
215 * @return {void} Nothing.
217 remoting.HostList.prototype.display = function() {
218 this.table_.innerText = '';
219 this.errorMsg_.innerText = '';
220 this.hostTableEntries_ = [];
222 var noHostsRegistered = (this.hosts_.length == 0);
223 this.table_.hidden = noHostsRegistered;
224 this.noHosts_.hidden = !noHostsRegistered;
226 if (!this.lastError_.isNone()) {
227 l10n.localizeElementFromTag(this.errorMsg_, this.lastError_.getTag());
228 if (this.lastError_.hasTag(remoting.Error.Tag.AUTHENTICATION_FAILED)) {
229 l10n.localizeElementFromTag(this.errorButton_,
230 /*i18n-content*/'SIGN_IN_BUTTON');
232 l10n.localizeElementFromTag(this.errorButton_,
233 /*i18n-content*/'RETRY');
236 for (var i = 0; i < this.hosts_.length; ++i) {
237 /** @type {remoting.Host} */
238 var host = this.hosts_[i];
239 // Validate the entry to make sure it has all the fields we expect and is
240 // not the local host (which is displayed separately). NB: if the host has
241 // never sent a heartbeat, then there will be no jabberId.
242 if (host.hostName && host.hostId && host.status && host.publicKey &&
243 (host.hostId != this.localHostSection_.getHostId())) {
244 var hostTableEntry = new remoting.HostTableEntry(
245 this.webappMajorVersion_,
247 this.renameHost.bind(this),
248 this.deleteHost_.bind(this));
249 hostTableEntry.setHost(host);
250 this.hostTableEntries_[i] = hostTableEntry;
251 this.table_.appendChild(hostTableEntry.element());
256 this.errorMsg_.parentNode.hidden = this.lastError_.isNone();
257 if (noHostsRegistered) {
258 this.showHostListEmptyMessage_(this.localHostSection_.canChangeState());
263 * Displays a message to the user when the host list is empty.
265 * @param {boolean} hostingSupported
269 remoting.HostList.prototype.showHostListEmptyMessage_ = function(
272 remoting.AppsV2Migration.hasHostsInV1App().then(
274 * @param {remoting.MigrationSettings} previousIdentity
275 * @this {remoting.HostList}
277 function(previousIdentity) {
278 that.noHosts_.innerHTML = remoting.AppsV2Migration.buildMigrationTips(
279 previousIdentity.email, previousIdentity.fullName);
282 var buttonLabel = l10n.getTranslationOrError(
283 /*i18n-content*/'HOME_DAEMON_START_BUTTON');
284 if (hostingSupported) {
285 that.noHosts_.innerText = l10n.getTranslationOrError(
286 /*i18n-content*/'HOST_LIST_EMPTY_HOSTING_SUPPORTED',
289 that.noHosts_.innerText = l10n.getTranslationOrError(
290 /*i18n-content*/'HOST_LIST_EMPTY_HOSTING_UNSUPPORTED',
298 * Remove a host from the list, and deregister it.
299 * @param {remoting.HostTableEntry} hostTableEntry The host to be removed.
300 * @return {void} Nothing.
303 remoting.HostList.prototype.deleteHost_ = function(hostTableEntry) {
304 this.table_.removeChild(hostTableEntry.element());
305 var index = this.hostTableEntries_.indexOf(hostTableEntry);
307 this.hostTableEntries_.splice(index, 1);
309 remoting.HostListApi.getInstance().remove(hostTableEntry.host.hostId).
310 catch(this.onError_);
314 * Prepare a host for renaming by replacing its name with an edit box.
315 * @param {remoting.HostTableEntry} hostTableEntry The host to be renamed.
316 * @return {void} Nothing.
318 remoting.HostList.prototype.renameHost = function(hostTableEntry) {
319 for (var i = 0; i < this.hosts_.length; ++i) {
320 if (this.hosts_[i].hostId == hostTableEntry.host.hostId) {
321 this.hosts_[i].hostName = hostTableEntry.host.hostName;
327 var hostListApi = remoting.HostListApi.getInstance();
328 hostListApi.put(hostTableEntry.host.hostId,
329 hostTableEntry.host.hostName,
330 hostTableEntry.host.publicKey).
331 catch(this.onError_);
336 * @param {string} hostId The id of the host to be removed.
337 * @param {function(void)=} opt_onDone
338 * @return {void} Nothing.
340 remoting.HostList.prototype.unregisterHostById = function(hostId, opt_onDone) {
342 var onDone = opt_onDone || base.doNothing;
344 var host = this.getHostForId(hostId);
346 console.log('Skipping host un-registration as the host is not registered ' +
347 'under the current account');
352 remoting.HostListApi.getInstance().remove(hostId).
354 that.refresh(function() {
359 catch(this.onError_);
363 * Set the state of the local host and localHostId if any.
365 * @param {remoting.HostController.State} state State of the local host.
366 * @param {string?} hostId ID of the local host, or null.
367 * @return {void} Nothing.
369 remoting.HostList.prototype.setLocalHostStateAndId = function(state, hostId) {
370 var host = hostId ? this.getHostForId(hostId) : null;
371 this.localHostSection_.setModel(host, state, !this.lastError_.isNone());
375 * Called by the HostControlled after the local host has been started.
377 * @param {string} hostName Host name.
378 * @param {string} hostId ID of the local host.
379 * @param {string} publicKey Public key.
380 * @return {void} Nothing.
382 remoting.HostList.prototype.onLocalHostStarted = function(
383 hostName, hostId, publicKey) {
384 // Create a dummy remoting.Host instance to represent the local host.
385 // Refreshing the list is no good in general, because the directory
386 // information won't be in sync for several seconds. We don't know the
387 // host JID, but it can be missing from the cache with no ill effects.
388 // It will be refreshed if the user tries to connect to the local host,
389 // and we hope that the directory will have been updated by that point.
390 var localHost = new remoting.Host(hostId);
391 localHost.hostName = hostName;
392 // Provide a version number to avoid warning about this dummy host being
394 localHost.hostVersion = String(this.webappMajorVersion_) + ".x"
395 localHost.publicKey = publicKey;
396 localHost.status = 'ONLINE';
397 this.hosts_.push(localHost);
399 this.localHostSection_.setModel(localHost,
400 remoting.HostController.State.STARTED,
401 !this.lastError_.isNone());
405 * Called when the user clicks the button next to the error message. The action
406 * depends on the error.
410 remoting.HostList.prototype.onErrorClick_ = function() {
411 if (this.lastError_.hasTag(remoting.Error.Tag.AUTHENTICATION_FAILED)) {
412 remoting.handleAuthFailureAndRelaunch();
414 this.refresh(remoting.updateLocalHostState);
419 * Save the host list to local storage.
421 remoting.HostList.prototype.save_ = function() {
423 items[remoting.HostList.HOSTS_KEY] = JSON.stringify(this.hosts_);
424 chrome.storage.local.set(items);
425 if (this.hosts_.length !== 0) {
426 remoting.AppsV2Migration.saveUserInfo();
431 * Key name under which Me2Me hosts are cached.
433 remoting.HostList.HOSTS_KEY = 'me2me-cached-hosts';
435 /** @type {remoting.HostList} */
436 remoting.hostList = null;