| // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| 'use strict'; |
| |
| /** @suppress {duplicate} */ |
| var remoting = remoting || {}; |
| |
| /** @constructor */ |
| remoting.HostController = function() { |
| /** @type {remoting.HostPlugin} @private */ |
| this.plugin_ = remoting.HostSession.createPlugin(); |
| /** @type {HTMLElement} @private */ |
| this.container_ = document.getElementById('daemon-plugin-container'); |
| this.container_.appendChild(this.plugin_); |
| /** @type {remoting.Host?} */ |
| this.localHost = null; |
| /** @param {string} version */ |
| var printVersion = function(version) { |
| if (version == '') { |
| console.log('Host not installed.'); |
| } else { |
| console.log('Host version: ' + version); |
| } |
| }; |
| try { |
| this.plugin_.getDaemonVersion(printVersion); |
| } catch (err) { |
| console.log('Host version not available.'); |
| } |
| }; |
| |
| // Note that the values in the enums below are copied from |
| // daemon_controller.h and must be kept in sync. |
| /** @enum {number} */ |
| remoting.HostController.State = { |
| NOT_IMPLEMENTED: -1, |
| NOT_INSTALLED: 0, |
| INSTALLING: 1, |
| STOPPED: 2, |
| STARTING: 3, |
| STARTED: 4, |
| STOPPING: 5, |
| UNKNOWN: 6 |
| }; |
| |
| /** @enum {number} */ |
| remoting.HostController.AsyncResult = { |
| OK: 0, |
| FAILED: 1, |
| CANCELLED: 2, |
| FAILED_DIRECTORY: 3 |
| }; |
| |
| /** @return {remoting.HostController.State} The current state of the daemon. */ |
| remoting.HostController.prototype.state = function() { |
| var result = this.plugin_.daemonState; |
| if (typeof(result) == 'undefined') { |
| // If the plug-in can't be instantiated, for example on ChromeOS, then |
| // return something sensible. |
| return remoting.HostController.State.NOT_IMPLEMENTED; |
| } else { |
| return result; |
| } |
| }; |
| |
| /** |
| * @param {function(boolean, boolean, boolean):void} callback Callback to be |
| * called when done. |
| */ |
| remoting.HostController.prototype.getConsent = function(callback) { |
| this.plugin_.getUsageStatsConsent(callback); |
| }; |
| |
| /** |
| * Show or hide daemon-specific parts of the UI. |
| * @return {void} Nothing. |
| */ |
| remoting.HostController.prototype.updateDom = function() { |
| var match = ''; |
| var state = this.state(); |
| var enabled = (state == remoting.HostController.State.STARTING) || |
| (state == remoting.HostController.State.STARTED); |
| var supported = (state != remoting.HostController.State.NOT_IMPLEMENTED); |
| remoting.updateModalUi(enabled ? 'enabled' : 'disabled', 'data-daemon-state'); |
| document.getElementById('daemon-control').hidden = !supported; |
| var element = document.getElementById('host-list-empty-hosting-supported'); |
| element.hidden = !supported; |
| element = document.getElementById('host-list-empty-hosting-unsupported'); |
| element.hidden = supported; |
| }; |
| |
| /** |
| * Set tool-tips for the 'connect' action. We can't just set this on the |
| * parent element because the button has no tool-tip, and therefore would |
| * inherit connectStr. |
| * |
| * return {void} Nothing. |
| */ |
| remoting.HostController.prototype.setTooltips = function() { |
| var connectStr = ''; |
| if (this.localHost) { |
| chrome.i18n.getMessage(/*i18n-content*/'TOOLTIP_CONNECT', |
| this.localHost.hostName); |
| } |
| document.getElementById('this-host-name').title = connectStr; |
| document.getElementById('this-host-icon').title = connectStr; |
| }; |
| |
| /** |
| * Registers and starts the host. |
| * @param {string} hostPin Host PIN. |
| * @param {boolean} consent The user's consent to crash dump reporting. |
| * @param {function(remoting.HostController.AsyncResult):void} callback |
| * callback Callback to be called when done. |
| * @return {void} Nothing. |
| */ |
| remoting.HostController.prototype.start = function(hostPin, consent, callback) { |
| /** @type {remoting.HostController} */ |
| var that = this; |
| var hostName = this.plugin_.getHostName(); |
| |
| /** @return {string} */ |
| function generateUuid() { |
| var random = new Uint16Array(8); |
| window.crypto.getRandomValues(random); |
| /** @type {Array.<string>} */ |
| var e = new Array(); |
| for (var i = 0; i < 8; i++) { |
| e[i] = (/** @type {number} */random[i] + 0x10000). |
| toString(16).substring(1); |
| } |
| return e[0] + e[1] + '-' + e[2] + "-" + e[3] + '-' + |
| e[4] + '-' + e[5] + e[6] + e[7]; |
| }; |
| |
| var newHostId = generateUuid(); |
| |
| /** @param {function(remoting.HostController.AsyncResult):void} callback |
| * @param {remoting.HostController.AsyncResult} result |
| * @param {string} hostName |
| * @param {string} publicKey */ |
| function onStarted(callback, result, hostName, publicKey) { |
| if (result == remoting.HostController.AsyncResult.OK) { |
| // Create a dummy remoting.Host instance to represent the local host. |
| // Refreshing the list is no good in general, because the directory |
| // information won't be in sync for several seconds. We don't know the |
| // host JID, but it can be missing from the cache with no ill effects. |
| // It will be refreshed if the user tries to connect to the local host, |
| // and we hope that the directory will have been updated by that point. |
| var localHost = new remoting.Host(); |
| localHost.hostName = hostName; |
| localHost.hostId = newHostId; |
| localHost.publicKey = publicKey; |
| localHost.status = 'ONLINE'; |
| that.setHost(localHost); |
| remoting.hostList.addHost(localHost); |
| } else { |
| // Unregister the host if we failed to start it. |
| remoting.HostList.unregisterHostById(newHostId); |
| } |
| callback(result); |
| }; |
| |
| /** @param {string} publicKey |
| * @param {string} privateKey |
| * @param {XMLHttpRequest} xhr */ |
| function onRegistered(publicKey, privateKey, xhr) { |
| var success = (xhr.status == 200); |
| |
| if (success) { |
| var hostSecretHash = |
| that.plugin_.getPinHash(newHostId, hostPin); |
| var hostConfig = JSON.stringify({ |
| xmpp_login: remoting.oauth2.getCachedEmail(), |
| oauth_refresh_token: remoting.oauth2.exportRefreshToken(), |
| oauth_use_official_client_id: |
| remoting.oauth2.USE_OFFICIAL_CLIENT_ID, |
| host_id: newHostId, |
| host_name: hostName, |
| host_secret_hash: hostSecretHash, |
| private_key: privateKey |
| }); |
| /** @param {remoting.HostController.AsyncResult} result */ |
| var onStartDaemon = function(result) { |
| onStarted(callback, result, hostName, publicKey); |
| }; |
| that.plugin_.startDaemon(hostConfig, consent, onStartDaemon); |
| } else { |
| console.log('Failed to register the host. Status: ' + xhr.status + |
| ' response: ' + xhr.responseText); |
| callback(remoting.HostController.AsyncResult.FAILED_DIRECTORY); |
| } |
| }; |
| |
| /** |
| * @param {string} privateKey |
| * @param {string} publicKey |
| * @param {string} oauthToken |
| */ |
| function doRegisterHost(privateKey, publicKey, oauthToken) { |
| var headers = { |
| 'Authorization': 'OAuth ' + oauthToken, |
| 'Content-type' : 'application/json; charset=UTF-8' |
| }; |
| |
| var newHostDetails = { data: { |
| hostId: newHostId, |
| hostName: hostName, |
| publicKey: publicKey |
| } }; |
| remoting.xhr.post( |
| 'https://www.googleapis.com/chromoting/v1/@me/hosts/', |
| /** @param {XMLHttpRequest} xhr */ |
| function (xhr) { onRegistered(publicKey, privateKey, xhr); }, |
| JSON.stringify(newHostDetails), |
| headers); |
| }; |
| |
| /** @param {string} privateKey |
| * @param {string} publicKey */ |
| function onKeyGenerated(privateKey, publicKey) { |
| remoting.oauth2.callWithToken( |
| /** @param {string} oauthToken */ |
| function(oauthToken) { |
| doRegisterHost(privateKey, publicKey, oauthToken); |
| }, |
| /** @param {remoting.Error} error */ |
| function(error) { |
| // TODO(jamiewalch): Have a more specific error code here? |
| callback(remoting.HostController.AsyncResult.FAILED); |
| }); |
| }; |
| |
| this.plugin_.generateKeyPair(onKeyGenerated); |
| }; |
| |
| /** |
| * Stop the daemon process. |
| * @param {function(remoting.HostController.AsyncResult):void} callback |
| * Callback to be called when finished. |
| * @return {void} Nothing. |
| */ |
| remoting.HostController.prototype.stop = function(callback) { |
| /** @type {remoting.HostController} */ |
| var that = this; |
| |
| /** @param {remoting.HostController.AsyncResult} result */ |
| function onStopped(result) { |
| if (result == remoting.HostController.AsyncResult.OK && |
| that.localHost && that.localHost.hostId) { |
| remoting.HostList.unregisterHostById(that.localHost.hostId); |
| } |
| callback(result); |
| }; |
| this.plugin_.stopDaemon(onStopped); |
| }; |
| |
| /** |
| * Parse a stringified host configuration and return it as a dictionary if it |
| * is well-formed and contains both host_id and xmpp_login keys. null is |
| * returned if either key is missing, or if the configuration is corrupt. |
| * @param {string} configStr The host configuration, JSON encoded to a string. |
| * @return {Object.<string,string>|null} The host configuration. |
| */ |
| function parseHostConfig_(configStr) { |
| var config = /** @type {Object.<string,string>} */ jsonParseSafe(configStr); |
| if (config && |
| typeof config['host_id'] == 'string' && |
| typeof config['xmpp_login'] == 'string') { |
| return config; |
| } else { |
| // {} means that host is not configured; '' means that the config file could |
| // not be read. |
| // TODO(jamiewalch): '' is expected if the host isn't installed, but should |
| // be reported as an error otherwise. Fix this once we have an event-based |
| // daemon state mechanism. |
| if (configStr != '{}' && configStr != '') { |
| console.error('Invalid getDaemonConfig response.'); |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * @param {string} newPin The new PIN to set |
| * @param {function(remoting.HostController.AsyncResult):void} callback |
| * Callback to be called when finished. |
| * @return {void} Nothing. |
| */ |
| remoting.HostController.prototype.updatePin = function(newPin, callback) { |
| /** @type {remoting.HostController} */ |
| var that = this; |
| |
| /** @param {string} configStr */ |
| function onConfig(configStr) { |
| var config = parseHostConfig_(configStr); |
| if (!config) { |
| callback(remoting.HostController.AsyncResult.FAILED); |
| return; |
| } |
| var hostId = config['host_id']; |
| var newConfig = JSON.stringify({ |
| host_secret_hash: that.plugin_.getPinHash(hostId, newPin) |
| }); |
| that.plugin_.updateDaemonConfig(newConfig, callback); |
| }; |
| |
| // TODO(sergeyu): When crbug.com/121518 is fixed: replace this call |
| // with an upriveleged version if that is necessary. |
| this.plugin_.getDaemonConfig(onConfig); |
| }; |
| |
| /** |
| * Set the remoting.Host instance (retrieved from the Chromoting service) |
| * that corresponds to the local computer, if any. |
| * |
| * @param {remoting.Host?} host The host, or null if not registered. |
| * @return {void} Nothing. |
| */ |
| remoting.HostController.prototype.setHost = function(host) { |
| this.localHost = host; |
| this.setTooltips(); |
| /** @type {remoting.HostController} */ |
| var that = this; |
| if (host) { |
| /** @param {remoting.HostTableEntry} host */ |
| var renameHost = function(host) { |
| remoting.hostList.renameHost(host); |
| that.setTooltips(); |
| }; |
| if (!this.hostTableEntry_) { |
| /** @type {remoting.HostTableEntry} @private */ |
| this.hostTableEntry_ = new remoting.HostTableEntry(); |
| this.hostTableEntry_.init(host, |
| document.getElementById('this-host-connect'), |
| document.getElementById('this-host-name'), |
| document.getElementById('this-host-rename'), |
| renameHost); |
| } else { |
| // TODO(jamiewalch): This is hack to prevent multiple click handlers being |
| // registered for the same DOM elements if this method is called more than |
| // once. A better solution would be to let HostTable create the daemon row |
| // like it creates the rows for non-local hosts. |
| this.hostTableEntry_.host = host; |
| } |
| } else { |
| this.hostTableEntry_ = null; |
| } |
| }; |
| |
| /** |
| * Update the internal state so that the local host can be correctly filtered |
| * out of the host list. |
| * |
| * @param {remoting.HostList} hostList The new host list, returned by the |
| * Chromoting service. |
| * @param {function():void} onDone Completion callback. |
| */ |
| remoting.HostController.prototype.onHostListRefresh = |
| function(hostList, onDone) { |
| /** @type {remoting.HostController} */ |
| var that = this; |
| /** @param {string} configStr */ |
| function onConfig(configStr) { |
| var config = parseHostConfig_(configStr); |
| if (config) { |
| var hostId = config['host_id']; |
| that.setHost(hostList.getHostForId(hostId)); |
| } else { |
| that.setHost(null); |
| } |
| onDone(); |
| }; |
| try { |
| this.plugin_.getDaemonConfig(onConfig); |
| } catch (err) { |
| this.setHost(null); |
| onDone(); |
| } |
| }; |
| |
| /** @type {remoting.HostController} */ |
| remoting.hostController = null; |