Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / remoting / webapp / crd / js / host_list.js
blob1262cd82c540296c96aba56b8cd9e560a04f3b11
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.
5 /**
6  * @fileoverview
7  * Class representing the host-list portion of the home screen UI.
8  */
10 'use strict';
12 /** @suppress {duplicate} */
13 var remoting = remoting || {};
15 /**
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.
19  *
20  * @constructor
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
25  *     resolution action.
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
28  *     the reload button.
29  * @param {function(!remoting.Error)} onError Function to call when an error
30  *     occurs.
31  * @param {function(string)} handleConnect Function to call to connect to the
32  *     host with |hostId|.
33  */
34 remoting.HostList = function(table, noHosts, errorMsg, errorButton,
35                              loadingIndicator, onError, handleConnect) {
36   /** @private {Element} */
37   this.table_ = table;
38   /**
39    * TODO(jamiewalch): This should be doable using CSS's sibling selector,
40    * but it doesn't work right now (crbug.com/135050).
41    * @private {Element}
42    */
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;
50   /** @private */
51   this.onError_ = remoting.Error.handler(onError);
52   /** @private */
53   this.handleConnect_ = handleConnect;
55   /** @private {Array<remoting.HostTableEntry>} */
56   this.hostTableEntries_ = [];
57   /** @private {Array<remoting.Host>} */
58   this.hosts_ = [];
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),
66           handleConnect));
68   /** @private {number} */
69   this.webappMajorVersion_ = parseInt(chrome.runtime.getManifest().version, 10);
71   this.errorButton_.addEventListener('click',
72                                      this.onErrorClick_.bind(this),
73                                      false);
74   var reloadButton = this.loadingIndicator_.firstElementChild;
75   /** @type {remoting.HostList} */
76   var that = this;
77   /** @param {Event} event */
78   function refresh(event) {
79     event.preventDefault();
80     that.refresh(that.display.bind(that));
81   }
82   reloadButton.addEventListener('click', refresh, false);
85 /**
86  * Load the host-list asynchronously from local storage.
87  *
88  * @param {function():void} onDone Completion callback.
89  */
90 remoting.HostList.prototype.load = function(onDone) {
91   // Load the cache of the last host-list, if present.
92   /** @type {remoting.HostList} */
93   var that = this;
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]);
98       if (cached) {
99         that.hosts_ = /** @type {Array<remoting.Host>} */ (cached);
100       } else {
101         console.error('Invalid value for ' + remoting.HostList.HOSTS_KEY);
102       }
103     }
104     onDone();
105   };
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.
114  */
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];
119     }
120   }
121   return null;
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.
129  */
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;
134     }
135   }
136   return null;
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.
145  */
146 remoting.HostList.prototype.refresh = function(onDone) {
147   this.loadingIndicator_.classList.add('loading');
148   /** @type {remoting.HostList} */
149   var that = this;
150   /** @param {!remoting.Error} error */
151   var onError = function(error) {
152     that.lastError_ = error;
153     onDone(false);
154   };
155   remoting.HostListApi.getInstance().get().then(function(hosts) {
156     onDone(that.onHostListResponse_(hosts));
157   }).catch(
158     remoting.Error.handler(onError)
159   );
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.
168  * @return {boolean}
169  * @private
170  */
171 remoting.HostList.prototype.onHostListResponse_ = function(hosts) {
172   this.lastError_ = remoting.Error.none();
173   this.hosts_ = hosts;
174   this.sortHosts_();
175   this.save_();
176   this.loadingIndicator_.classList.remove('loading');
177   return true;
181  * Sort the internal list of hosts.
183  * @suppress {reportUnknownTypes}
184  * @return {void} Nothing.
185  */
186 remoting.HostList.prototype.sortHosts_ = function() {
187   /**
188    * Sort hosts, first by ONLINE/OFFLINE status and then by host-name.
189    *
190    * @param {remoting.Host} a
191    * @param {remoting.Host} b
192    * @return {number}
193    */
194   var cmp = function(a, b) {
195     if (a.status < b.status) {
196       return 1;
197     } else if (b.status < a.status) {
198       return -1;
199     } else if (a.hostName.toLocaleLowerCase() <
200                b.hostName.toLocaleLowerCase()) {
201       return -1;
202     } else if (a.hostName.toLocaleLowerCase() >
203                b.hostName.toLocaleLowerCase()) {
204       return 1;
205     }
206     return 0;
207   };
209   this.hosts_ = this.hosts_.sort(cmp);
213  * Display the list of hosts or error condition.
215  * @return {void} Nothing.
216  */
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');
231     } else {
232       l10n.localizeElementFromTag(this.errorButton_,
233                                   /*i18n-content*/'RETRY');
234     }
235   } else {
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_,
246             this.handleConnect_,
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());
252       }
253     }
254   }
256   this.errorMsg_.parentNode.hidden = this.lastError_.isNone();
257   if (noHostsRegistered) {
258     this.showHostListEmptyMessage_(this.localHostSection_.canChangeState());
259   }
263  * Displays a message to the user when the host list is empty.
265  * @param {boolean} hostingSupported
266  * @return {void}
267  * @private
268  */
269 remoting.HostList.prototype.showHostListEmptyMessage_ = function(
270     hostingSupported) {
271   var that = this;
272   remoting.AppsV2Migration.hasHostsInV1App().then(
273     /**
274      * @param {remoting.MigrationSettings} previousIdentity
275      * @this {remoting.HostList}
276      */
277     function(previousIdentity) {
278       that.noHosts_.innerHTML = remoting.AppsV2Migration.buildMigrationTips(
279           previousIdentity.email, previousIdentity.fullName);
280     },
281     function() {
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',
287             [buttonLabel]);
288       } else {
289         that.noHosts_.innerText = l10n.getTranslationOrError(
290             /*i18n-content*/'HOST_LIST_EMPTY_HOSTING_UNSUPPORTED',
291             [buttonLabel]);
292       }
293     }
294   );
298  * Remove a host from the list, and deregister it.
299  * @param {remoting.HostTableEntry} hostTableEntry The host to be removed.
300  * @return {void} Nothing.
301  * @private
302  */
303 remoting.HostList.prototype.deleteHost_ = function(hostTableEntry) {
304   this.table_.removeChild(hostTableEntry.element());
305   var index = this.hostTableEntries_.indexOf(hostTableEntry);
306   if (index != -1) {
307     this.hostTableEntries_.splice(index, 1);
308   }
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.
317  */
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;
322       break;
323     }
324   }
325   this.save_();
327   var hostListApi = remoting.HostListApi.getInstance();
328   hostListApi.put(hostTableEntry.host.hostId,
329                   hostTableEntry.host.hostName,
330                   hostTableEntry.host.publicKey).
331       catch(this.onError_);
335  * Unregister a host.
336  * @param {string} hostId The id of the host to be removed.
337  * @param {function(void)=} opt_onDone
338  * @return {void} Nothing.
339  */
340 remoting.HostList.prototype.unregisterHostById = function(hostId, opt_onDone) {
341   var that = this;
342   var onDone = opt_onDone || base.doNothing;
344   var host = this.getHostForId(hostId);
345   if (!host) {
346     console.log('Skipping host un-registration as the host is not registered ' +
347                 'under the current account');
348     onDone();
349     return;
350   }
352   remoting.HostListApi.getInstance().remove(hostId).
353       then(function() {
354         that.refresh(function() {
355           that.display();
356           onDone();
357         });
358       }).
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.
368  */
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.
381  */
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
393   // out-of-date.
394   localHost.hostVersion = String(this.webappMajorVersion_) + ".x"
395   localHost.publicKey = publicKey;
396   localHost.status = 'ONLINE';
397   this.hosts_.push(localHost);
398   this.save_();
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.
408  * @private
409  */
410 remoting.HostList.prototype.onErrorClick_ = function() {
411   if (this.lastError_.hasTag(remoting.Error.Tag.AUTHENTICATION_FAILED)) {
412     remoting.handleAuthFailureAndRelaunch();
413   } else {
414     this.refresh(remoting.updateLocalHostState);
415   }
419  * Save the host list to local storage.
420  */
421 remoting.HostList.prototype.save_ = function() {
422   var items = {};
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();
427   }
431  * Key name under which Me2Me hosts are cached.
432  */
433 remoting.HostList.HOSTS_KEY = 'me2me-cached-hosts';
435 /** @type {remoting.HostList} */
436 remoting.hostList = null;