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;