| // 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() { |
| this.hostDispatcher_ = this.createDispatcher_(); |
| }; |
| |
| // 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 |
| }; |
| |
| /** |
| * @param {string} state The host controller state name. |
| * @return {remoting.HostController.State} The state enum value. |
| */ |
| remoting.HostController.State.fromString = function(state) { |
| if (!remoting.HostController.State.hasOwnProperty(state)) { |
| throw "Invalid HostController.State: " + state; |
| } |
| return remoting.HostController.State[state]; |
| } |
| |
| /** @enum {number} */ |
| remoting.HostController.AsyncResult = { |
| OK: 0, |
| FAILED: 1, |
| CANCELLED: 2, |
| FAILED_DIRECTORY: 3 |
| }; |
| |
| /** |
| * @param {string} result The async result name. |
| * @return {remoting.HostController.AsyncResult} The result enum value. |
| */ |
| remoting.HostController.AsyncResult.fromString = function(result) { |
| if (!remoting.HostController.AsyncResult.hasOwnProperty(result)) { |
| throw "Invalid HostController.AsyncResult: " + result; |
| } |
| return remoting.HostController.AsyncResult[result]; |
| } |
| |
| /** |
| * @return {remoting.HostDispatcher} |
| * @private |
| */ |
| remoting.HostController.prototype.createDispatcher_ = function() { |
| /** @return {remoting.HostPlugin} */ |
| var createPluginForMe2Me = function() { |
| /** @type {HTMLElement} @private */ |
| var container = document.getElementById('daemon-plugin-container'); |
| return remoting.createNpapiPlugin(container); |
| }; |
| |
| /** @type {remoting.HostDispatcher} @private */ |
| var hostDispatcher = new remoting.HostDispatcher(createPluginForMe2Me); |
| |
| /** @param {string} version */ |
| var printVersion = function(version) { |
| if (version == '') { |
| console.log('Host not installed.'); |
| } else { |
| console.log('Host version: ' + version); |
| } |
| }; |
| |
| hostDispatcher.getDaemonVersion(printVersion, function() { |
| console.log('Host version not available.'); |
| }); |
| |
| return hostDispatcher; |
| }; |
| |
| /** |
| * Set of features for which hasFeature() can be used to test. |
| * |
| * @enum {string} |
| */ |
| remoting.HostController.Feature = { |
| PAIRING_REGISTRY: 'pairingRegistry', |
| OAUTH_CLIENT: 'oauthClient' |
| }; |
| |
| /** |
| * @param {remoting.HostController.Feature} feature The feature to test for. |
| * @param {function(boolean):void} callback |
| * @return {void} |
| */ |
| remoting.HostController.prototype.hasFeature = function(feature, callback) { |
| // TODO(rmsousa): This could synchronously return a boolean, provided it were |
| // only called after the dispatcher is completely initialized. |
| this.hostDispatcher_.hasFeature(feature, callback); |
| }; |
| |
| /** |
| * @param {function(boolean, boolean, boolean):void} onDone Callback to be |
| * called when done. |
| * @param {function(remoting.Error):void} onError Callback to be called on |
| * error. |
| */ |
| remoting.HostController.prototype.getConsent = function(onDone, onError) { |
| this.hostDispatcher_.getUsageStatsConsent(onDone, onError); |
| }; |
| |
| /** |
| * @param {function(remoting.HostController.AsyncResult):void} onDone |
| * @param {function(remoting.Error):void} onError |
| * @return {void} |
| */ |
| remoting.HostController.prototype.installHost = function(onDone, onError) { |
| /** @type {remoting.HostController} */ |
| var that = this; |
| |
| /** @param {remoting.HostController.AsyncResult} asyncResult */ |
| var onHostInstalled = function(asyncResult) { |
| // Refresh the dispatcher after the host has been installed. |
| if (asyncResult == remoting.HostController.AsyncResult.OK) { |
| that.hostDispatcher_ = that.createDispatcher_(); |
| } |
| onDone(asyncResult); |
| }; |
| |
| this.hostDispatcher_.installHost(onHostInstalled, onError); |
| }; |
| |
| /** |
| * Registers and starts the host. |
| * |
| * @param {string} hostPin Host PIN. |
| * @param {boolean} consent The user's consent to crash dump reporting. |
| * @param {function():void} onDone Callback to be called when done. |
| * @param {function(remoting.Error):void} onError Callback to be called on |
| * error. |
| * @return {void} Nothing. |
| */ |
| remoting.HostController.prototype.start = function(hostPin, consent, onDone, |
| onError) { |
| /** @type {remoting.HostController} */ |
| var that = this; |
| |
| /** @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 {remoting.Error} error */ |
| function onStartError(error) { |
| // Unregister the host if we failed to start it. |
| remoting.HostList.unregisterHostById(newHostId); |
| onError(error); |
| } |
| |
| /** |
| * @param {string} hostName |
| * @param {string} publicKey |
| * @param {remoting.HostController.AsyncResult} result |
| */ |
| function onStarted(hostName, publicKey, result) { |
| if (result == remoting.HostController.AsyncResult.OK) { |
| remoting.hostList.onLocalHostStarted(hostName, newHostId, publicKey); |
| onDone(); |
| } else if (result == remoting.HostController.AsyncResult.CANCELLED) { |
| onStartError(remoting.Error.CANCELLED); |
| } else { |
| onStartError(remoting.Error.UNEXPECTED); |
| } |
| } |
| |
| /** |
| * @param {string} hostName |
| * @param {string} publicKey |
| * @param {string} privateKey |
| * @param {string} xmppLogin |
| * @param {string} refreshToken |
| * @param {string} hostSecretHash |
| */ |
| function startHostWithHash(hostName, publicKey, privateKey, |
| xmppLogin, refreshToken, hostSecretHash) { |
| var hostConfig = { |
| xmpp_login: xmppLogin, |
| oauth_refresh_token: refreshToken, |
| host_id: newHostId, |
| host_name: hostName, |
| host_secret_hash: hostSecretHash, |
| private_key: privateKey |
| }; |
| var hostOwner = remoting.identity.getCachedEmail(); |
| if (hostOwner != xmppLogin) { |
| hostConfig['host_owner'] = hostOwner; |
| } |
| that.hostDispatcher_.startDaemon(hostConfig, consent, |
| onStarted.bind(null, hostName, publicKey), |
| onStartError); |
| } |
| |
| /** |
| * @param {string} hostName |
| * @param {string} publicKey |
| * @param {string} privateKey |
| * @param {string} email |
| * @param {string} refreshToken |
| */ |
| function onServiceAccountCredentials( |
| hostName, publicKey, privateKey, email, refreshToken) { |
| that.hostDispatcher_.getPinHash( |
| newHostId, hostPin, |
| startHostWithHash.bind( |
| null, hostName, publicKey, privateKey, email, refreshToken), |
| onError); |
| } |
| |
| /** |
| * @param {string} hostName |
| * @param {string} publicKey |
| * @param {string} privateKey |
| * @param {XMLHttpRequest} xhr |
| */ |
| function onRegistered( |
| hostName, publicKey, privateKey, xhr) { |
| var success = (xhr.status == 200); |
| |
| if (success) { |
| var result = jsonParseSafe(xhr.responseText); |
| if ('data' in result && 'authorizationCode' in result['data']) { |
| that.hostDispatcher_.getCredentialsFromAuthCode( |
| result['data']['authorizationCode'], |
| onServiceAccountCredentials.bind( |
| null, hostName, publicKey, privateKey), |
| onError); |
| } else { |
| // No authorization code returned, use regular user credential flow. |
| that.hostDispatcher_.getPinHash( |
| newHostId, hostPin, startHostWithHash.bind( |
| null, hostName, publicKey, privateKey, |
| remoting.identity.getCachedEmail(), |
| remoting.oauth2.getRefreshToken()), |
| onError); |
| } |
| } else { |
| console.log('Failed to register the host. Status: ' + xhr.status + |
| ' response: ' + xhr.responseText); |
| onError(remoting.Error.REGISTRATION_FAILED); |
| } |
| } |
| |
| /** |
| * @param {string} hostName |
| * @param {string} privateKey |
| * @param {string} publicKey |
| * @param {string} hostClientId |
| * @param {string} oauthToken |
| */ |
| function doRegisterHost( |
| hostName, privateKey, publicKey, hostClientId, oauthToken) { |
| var headers = { |
| 'Authorization': 'OAuth ' + oauthToken, |
| 'Content-type' : 'application/json; charset=UTF-8' |
| }; |
| |
| var newHostDetails = { data: { |
| hostId: newHostId, |
| hostName: hostName, |
| publicKey: publicKey |
| } }; |
| |
| var registerHostUrl = |
| remoting.settings.DIRECTORY_API_BASE_URL + '/@me/hosts'; |
| |
| if (hostClientId) { |
| registerHostUrl += '?' + remoting.xhr.urlencodeParamHash( |
| { hostClientId: hostClientId }); |
| } |
| |
| remoting.xhr.post( |
| registerHostUrl, |
| onRegistered.bind(null, hostName, publicKey, privateKey), |
| JSON.stringify(newHostDetails), |
| headers); |
| } |
| |
| /** |
| * @param {string} hostName |
| * @param {string} privateKey |
| * @param {string} publicKey |
| * @param {string} hostClientId |
| */ |
| function onHostClientId( |
| hostName, privateKey, publicKey, hostClientId) { |
| remoting.identity.callWithToken( |
| doRegisterHost.bind( |
| null, hostName, privateKey, publicKey, hostClientId), onError); |
| } |
| |
| /** |
| * @param {string} hostName |
| * @param {string} privateKey |
| * @param {string} publicKey |
| * @param {boolean} hasFeature |
| */ |
| function onHasFeatureOAuthClient( |
| hostName, privateKey, publicKey, hasFeature) { |
| if (hasFeature) { |
| that.hostDispatcher_.getHostClientId( |
| onHostClientId.bind(null, hostName, privateKey, publicKey), onError); |
| } else { |
| remoting.identity.callWithToken( |
| doRegisterHost.bind( |
| null, hostName, privateKey, publicKey, null), onError); |
| } |
| } |
| |
| /** |
| * @param {string} hostName |
| * @param {string} privateKey |
| * @param {string} publicKey |
| */ |
| function onKeyGenerated(hostName, privateKey, publicKey) { |
| that.hasFeature( |
| remoting.HostController.Feature.OAUTH_CLIENT, |
| onHasFeatureOAuthClient.bind(null, hostName, privateKey, publicKey)); |
| } |
| |
| /** |
| * @param {string} hostName |
| * @return {void} Nothing. |
| */ |
| function startWithHostname(hostName) { |
| that.hostDispatcher_.generateKeyPair(onKeyGenerated.bind(null, hostName), |
| onError); |
| } |
| |
| this.hostDispatcher_.getHostName(startWithHostname, onError); |
| }; |
| |
| /** |
| * Stop the daemon process. |
| * @param {function():void} onDone Callback to be called when done. |
| * @param {function(remoting.Error):void} onError Callback to be called on |
| * error. |
| * @return {void} Nothing. |
| */ |
| remoting.HostController.prototype.stop = function(onDone, onError) { |
| /** @type {remoting.HostController} */ |
| var that = this; |
| |
| /** @param {string?} hostId The host id of the local host. */ |
| function unregisterHost(hostId) { |
| if (hostId) { |
| remoting.HostList.unregisterHostById(hostId); |
| } |
| onDone(); |
| } |
| |
| /** @param {remoting.HostController.AsyncResult} result */ |
| function onStopped(result) { |
| if (result == remoting.HostController.AsyncResult.OK) { |
| that.getLocalHostId(unregisterHost); |
| } else if (result == remoting.HostController.AsyncResult.CANCELLED) { |
| onError(remoting.Error.CANCELLED); |
| } else { |
| onError(remoting.Error.UNEXPECTED); |
| } |
| } |
| |
| this.hostDispatcher_.stopDaemon(onStopped, onError); |
| }; |
| |
| /** |
| * Check the host configuration is valid (non-null, and contains both host_id |
| * and xmpp_login keys). |
| * @param {Object} config The host configuration. |
| * @return {boolean} True if it is valid. |
| */ |
| function isHostConfigValid_(config) { |
| return !!config && typeof config['host_id'] == 'string' && |
| typeof config['xmpp_login'] == 'string'; |
| } |
| |
| /** |
| * @param {string} newPin The new PIN to set |
| * @param {function():void} onDone Callback to be called when done. |
| * @param {function(remoting.Error):void} onError Callback to be called on |
| * error. |
| * @return {void} Nothing. |
| */ |
| remoting.HostController.prototype.updatePin = function(newPin, onDone, |
| onError) { |
| /** @type {remoting.HostController} */ |
| var that = this; |
| |
| /** @param {remoting.HostController.AsyncResult} result */ |
| function onConfigUpdated(result) { |
| if (result == remoting.HostController.AsyncResult.OK) { |
| onDone(); |
| } else if (result == remoting.HostController.AsyncResult.CANCELLED) { |
| onError(remoting.Error.CANCELLED); |
| } else { |
| onError(remoting.Error.UNEXPECTED); |
| } |
| } |
| |
| /** @param {string} pinHash */ |
| function updateDaemonConfigWithHash(pinHash) { |
| var newConfig = { |
| host_secret_hash: pinHash |
| }; |
| that.hostDispatcher_.updateDaemonConfig(newConfig, onConfigUpdated, |
| onError); |
| } |
| |
| /** @param {Object} config */ |
| function onConfig(config) { |
| if (!isHostConfigValid_(config)) { |
| onError(remoting.Error.UNEXPECTED); |
| return; |
| } |
| /** @type {string} */ |
| var hostId = config['host_id']; |
| that.hostDispatcher_.getPinHash(hostId, newPin, updateDaemonConfigWithHash, |
| onError); |
| } |
| |
| // TODO(sergeyu): When crbug.com/121518 is fixed: replace this call |
| // with an unprivileged version if that is necessary. |
| this.hostDispatcher_.getDaemonConfig(onConfig, onError); |
| }; |
| |
| /** |
| * Get the state of the local host. |
| * |
| * @param {function(remoting.HostController.State):void} onDone Completion |
| * callback. |
| */ |
| remoting.HostController.prototype.getLocalHostState = function(onDone) { |
| this.hostDispatcher_.getDaemonState(onDone, function(error) { |
| onDone(remoting.HostController.State.UNKNOWN); |
| }); |
| }; |
| |
| /** |
| * Get the id of the local host, or null if it is not registered. |
| * |
| * @param {function(string?):void} onDone Completion callback. |
| */ |
| remoting.HostController.prototype.getLocalHostId = function(onDone) { |
| /** @type {remoting.HostController} */ |
| var that = this; |
| /** @param {Object} config */ |
| function onConfig(config) { |
| var hostId = null; |
| if (isHostConfigValid_(config)) { |
| hostId = /** @type {string} */ config['host_id']; |
| } |
| onDone(hostId); |
| }; |
| |
| this.hostDispatcher_.getDaemonConfig(onConfig, function(error) { |
| onDone(null); |
| }); |
| }; |
| |
| /** |
| * Fetch the list of paired clients for this host. |
| * |
| * @param {function(Array.<remoting.PairedClient>):void} onDone |
| * @param {function(remoting.Error):void} onError |
| * @return {void} |
| */ |
| remoting.HostController.prototype.getPairedClients = function(onDone, |
| onError) { |
| this.hostDispatcher_.getPairedClients(onDone, onError); |
| }; |
| |
| /** |
| * Delete a single paired client. |
| * |
| * @param {string} client The client id of the pairing to delete. |
| * @param {function():void} onDone Completion callback. |
| * @param {function(remoting.Error):void} onError Error callback. |
| * @return {void} |
| */ |
| remoting.HostController.prototype.deletePairedClient = function( |
| client, onDone, onError) { |
| this.hostDispatcher_.deletePairedClient(client, onDone, onError); |
| }; |
| |
| /** |
| * Delete all paired clients. |
| * |
| * @param {function():void} onDone Completion callback. |
| * @param {function(remoting.Error):void} onError Error callback. |
| * @return {void} |
| */ |
| remoting.HostController.prototype.clearPairedClients = function( |
| onDone, onError) { |
| this.hostDispatcher_.clearPairedClients(onDone, onError); |
| }; |
| |
| /** |
| * Returns true if the NPAPI plugin is being used. |
| * @return {boolean} |
| */ |
| remoting.HostController.prototype.usingNpapiPlugin = function() { |
| return this.hostDispatcher_.usingNpapiPlugin(); |
| } |
| |
| /** @type {remoting.HostController} */ |
| remoting.hostController = null; |