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
32 remoting.HostList = function(table, noHosts, errorMsg, errorButton,
33 loadingIndicator, onError) {
34 /** @private {Element} */
37 * TODO(jamiewalch): This should be doable using CSS's sibling selector,
38 * but it doesn't work right now (crbug.com/135050).
41 this.noHosts_ = noHosts;
42 /** @private {Element} */
43 this.errorMsg_ = errorMsg;
44 /** @private {Element} */
45 this.errorButton_ = errorButton;
46 /** @private {HTMLElement} */
47 this.loadingIndicator_ = loadingIndicator;
48 this.onError_ = onError;
50 /** @private {Array<remoting.HostTableEntry>} */
51 this.hostTableEntries_ = [];
52 /** @private {Array<remoting.Host>} */
54 /** @private {!remoting.Error} */
55 this.lastError_ = remoting.Error.none();
56 /** @private {remoting.LocalHostSection} */
57 this.localHostSection_ = new remoting.LocalHostSection(
58 /** @type {HTMLElement} */ (document.querySelector('.daemon-control')),
59 new remoting.LocalHostSection.Controller(
61 new remoting.HostSetupDialog(remoting.hostController, onError)));
63 /** @private {number} */
64 this.webappMajorVersion_ = parseInt(chrome.runtime.getManifest().version, 10);
66 this.errorButton_.addEventListener('click',
67 this.onErrorClick_.bind(this),
69 var reloadButton = this.loadingIndicator_.firstElementChild;
70 /** @type {remoting.HostList} */
72 /** @param {Event} event */
73 function refresh(event) {
74 event.preventDefault();
75 that.refresh(that.display.bind(that));
77 reloadButton.addEventListener('click', refresh, false);
81 * Load the host-list asynchronously from local storage.
83 * @param {function():void} onDone Completion callback.
85 remoting.HostList.prototype.load = function(onDone) {
86 // Load the cache of the last host-list, if present.
87 /** @type {remoting.HostList} */
89 /** @param {Object<string>} items */
90 var storeHostList = function(items) {
91 if (items[remoting.HostList.HOSTS_KEY]) {
92 var cached = base.jsonParseSafe(items[remoting.HostList.HOSTS_KEY]);
94 that.hosts_ = /** @type {Array<remoting.Host>} */ (cached);
96 console.error('Invalid value for ' + remoting.HostList.HOSTS_KEY);
101 chrome.storage.local.get(remoting.HostList.HOSTS_KEY, storeHostList);
105 * Search the host list for a host with the specified id.
107 * @param {string} hostId The unique id of the host.
108 * @return {remoting.Host?} The host, if any.
110 remoting.HostList.prototype.getHostForId = function(hostId) {
111 for (var i = 0; i < this.hosts_.length; ++i) {
112 if (this.hosts_[i].hostId == hostId) {
113 return this.hosts_[i];
120 * Get the host id corresponding to the specified host name.
122 * @param {string} hostName The name of the host.
123 * @return {string?} The host id, if a host with the given name exists.
125 remoting.HostList.prototype.getHostIdForName = function(hostName) {
126 for (var i = 0; i < this.hosts_.length; ++i) {
127 if (this.hosts_[i].hostName == hostName) {
128 return this.hosts_[i].hostId;
135 * Query the Remoting Directory for the user's list of hosts.
137 * @param {function(boolean):void} onDone Callback invoked with true on success
138 * or false on failure.
139 * @return {void} Nothing.
141 remoting.HostList.prototype.refresh = function(onDone) {
142 this.loadingIndicator_.classList.add('loading');
143 /** @type {remoting.HostList} */
145 /** @param {!remoting.Error} error */
146 var onError = function(error) {
147 that.lastError_ = error;
150 remoting.hostListApi.get(this.onHostListResponse_.bind(this, onDone),
155 * Handle the results of the host list request. A success response will
156 * include a JSON-encoded list of host descriptions, which we display if we're
157 * able to successfully parse it.
159 * @param {function(boolean):void} onDone The callback passed to |refresh|.
160 * @param {Array<remoting.Host>} hosts The list of hosts for the user.
161 * @return {void} Nothing.
164 remoting.HostList.prototype.onHostListResponse_ = function(onDone, hosts) {
165 this.lastError_ = remoting.Error.none();
169 this.loadingIndicator_.classList.remove('loading');
174 * Sort the internal list of hosts.
176 * @suppress {reportUnknownTypes}
177 * @return {void} Nothing.
179 remoting.HostList.prototype.sortHosts_ = function() {
181 * Sort hosts, first by ONLINE/OFFLINE status and then by host-name.
183 * @param {remoting.Host} a
184 * @param {remoting.Host} b
187 var cmp = function(a, b) {
188 if (a.status < b.status) {
190 } else if (b.status < a.status) {
192 } else if (a.hostName.toLocaleLowerCase() <
193 b.hostName.toLocaleLowerCase()) {
195 } else if (a.hostName.toLocaleLowerCase() >
196 b.hostName.toLocaleLowerCase()) {
202 this.hosts_ = this.hosts_.sort(cmp);
206 * Display the list of hosts or error condition.
208 * @return {void} Nothing.
210 remoting.HostList.prototype.display = function() {
211 this.table_.innerText = '';
212 this.errorMsg_.innerText = '';
213 this.hostTableEntries_ = [];
215 var noHostsRegistered = (this.hosts_.length == 0);
216 this.table_.hidden = noHostsRegistered;
217 this.noHosts_.hidden = !noHostsRegistered;
219 if (!this.lastError_.isNone()) {
220 l10n.localizeElementFromTag(this.errorMsg_, this.lastError_.getTag());
221 if (this.lastError_.hasTag(remoting.Error.Tag.AUTHENTICATION_FAILED)) {
222 l10n.localizeElementFromTag(this.errorButton_,
223 /*i18n-content*/'SIGN_IN_BUTTON');
225 l10n.localizeElementFromTag(this.errorButton_,
226 /*i18n-content*/'RETRY');
229 for (var i = 0; i < this.hosts_.length; ++i) {
230 /** @type {remoting.Host} */
231 var host = this.hosts_[i];
232 // Validate the entry to make sure it has all the fields we expect and is
233 // not the local host (which is displayed separately). NB: if the host has
234 // never sent a heartbeat, then there will be no jabberId.
235 if (host.hostName && host.hostId && host.status && host.publicKey &&
236 (host.hostId != this.localHostSection_.getHostId())) {
237 var hostTableEntry = new remoting.HostTableEntry(
238 this.webappMajorVersion_,
239 remoting.connectMe2Me,
240 this.renameHost.bind(this),
241 this.deleteHost_.bind(this));
242 hostTableEntry.setHost(host);
243 this.hostTableEntries_[i] = hostTableEntry;
244 this.table_.appendChild(hostTableEntry.element());
249 this.errorMsg_.parentNode.hidden = this.lastError_.isNone();
250 if (noHostsRegistered) {
251 this.showHostListEmptyMessage_(this.localHostSection_.canChangeState());
256 * Displays a message to the user when the host list is empty.
258 * @param {boolean} hostingSupported
262 remoting.HostList.prototype.showHostListEmptyMessage_ = function(
265 remoting.AppsV2Migration.hasHostsInV1App().then(
267 * @param {remoting.MigrationSettings} previousIdentity
268 * @this {remoting.HostList}
270 function(previousIdentity) {
271 that.noHosts_.innerHTML = remoting.AppsV2Migration.buildMigrationTips(
272 previousIdentity.email, previousIdentity.fullName);
275 var buttonLabel = l10n.getTranslationOrError(
276 /*i18n-content*/'HOME_DAEMON_START_BUTTON');
277 if (hostingSupported) {
278 that.noHosts_.innerText = l10n.getTranslationOrError(
279 /*i18n-content*/'HOST_LIST_EMPTY_HOSTING_SUPPORTED',
282 that.noHosts_.innerText = l10n.getTranslationOrError(
283 /*i18n-content*/'HOST_LIST_EMPTY_HOSTING_UNSUPPORTED',
291 * Remove a host from the list, and deregister it.
292 * @param {remoting.HostTableEntry} hostTableEntry The host to be removed.
293 * @return {void} Nothing.
296 remoting.HostList.prototype.deleteHost_ = function(hostTableEntry) {
297 this.table_.removeChild(hostTableEntry.element());
298 var index = this.hostTableEntries_.indexOf(hostTableEntry);
300 this.hostTableEntries_.splice(index, 1);
302 remoting.hostListApi.remove(hostTableEntry.host.hostId, base.doNothing,
307 * Prepare a host for renaming by replacing its name with an edit box.
308 * @param {remoting.HostTableEntry} hostTableEntry The host to be renamed.
309 * @return {void} Nothing.
311 remoting.HostList.prototype.renameHost = function(hostTableEntry) {
312 for (var i = 0; i < this.hosts_.length; ++i) {
313 if (this.hosts_[i].hostId == hostTableEntry.host.hostId) {
314 this.hosts_[i].hostName = hostTableEntry.host.hostName;
320 remoting.hostListApi.put(hostTableEntry.host.hostId,
321 hostTableEntry.host.hostName,
322 hostTableEntry.host.publicKey,
329 * @param {string} hostId The id of the host to be removed.
330 * @param {function(void)=} opt_onDone
331 * @return {void} Nothing.
333 remoting.HostList.prototype.unregisterHostById = function(hostId, opt_onDone) {
335 var onDone = opt_onDone || base.doNothing;
337 var host = this.getHostForId(hostId);
339 console.log('Skipping host un-registration as the host is not registered ' +
340 'under the current account');
345 var onRemoved = function() {
346 that.refresh(function() {
351 remoting.hostListApi.remove(hostId, onRemoved, this.onError_);
355 * Set the state of the local host and localHostId if any.
357 * @param {remoting.HostController.State} state State of the local host.
358 * @param {string?} hostId ID of the local host, or null.
359 * @return {void} Nothing.
361 remoting.HostList.prototype.setLocalHostStateAndId = function(state, hostId) {
362 var host = hostId ? this.getHostForId(hostId) : null;
363 this.localHostSection_.setModel(host, state, !this.lastError_.isNone());
367 * Called by the HostControlled after the local host has been started.
369 * @param {string} hostName Host name.
370 * @param {string} hostId ID of the local host.
371 * @param {string} publicKey Public key.
372 * @return {void} Nothing.
374 remoting.HostList.prototype.onLocalHostStarted = function(
375 hostName, hostId, publicKey) {
376 // Create a dummy remoting.Host instance to represent the local host.
377 // Refreshing the list is no good in general, because the directory
378 // information won't be in sync for several seconds. We don't know the
379 // host JID, but it can be missing from the cache with no ill effects.
380 // It will be refreshed if the user tries to connect to the local host,
381 // and we hope that the directory will have been updated by that point.
382 var localHost = new remoting.Host();
383 localHost.hostName = hostName;
384 // Provide a version number to avoid warning about this dummy host being
386 localHost.hostVersion = String(this.webappMajorVersion_) + ".x"
387 localHost.hostId = hostId;
388 localHost.publicKey = publicKey;
389 localHost.status = 'ONLINE';
390 this.hosts_.push(localHost);
392 this.localHostSection_.setModel(localHost,
393 remoting.HostController.State.STARTED,
394 !this.lastError_.isNone());
398 * Called when the user clicks the button next to the error message. The action
399 * depends on the error.
403 remoting.HostList.prototype.onErrorClick_ = function() {
404 if (this.lastError_.hasTag(remoting.Error.Tag.AUTHENTICATION_FAILED)) {
405 remoting.handleAuthFailureAndRelaunch();
407 this.refresh(remoting.updateLocalHostState);
412 * Save the host list to local storage.
414 remoting.HostList.prototype.save_ = function() {
416 items[remoting.HostList.HOSTS_KEY] = JSON.stringify(this.hosts_);
417 chrome.storage.local.set(items);
418 if (this.hosts_.length !== 0) {
419 remoting.AppsV2Migration.saveUserInfo();
424 * Key name under which Me2Me hosts are cached.
426 remoting.HostList.HOSTS_KEY = 'me2me-cached-hosts';
428 /** @type {remoting.HostList} */
429 remoting.hostList = null;