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. 4 5'use strict'; 6 7/** @suppress {duplicate} */ 8var remoting = remoting || {}; 9 10/** @constructor */ 11remoting.HostController = function() { 12 /** @return {remoting.HostPlugin} */ 13 var createNpapiPlugin = function() { 14 var plugin = remoting.HostSession.createPlugin(); 15 /** @type {HTMLElement} @private */ 16 var container = document.getElementById('daemon-plugin-container'); 17 container.appendChild(plugin); 18 return plugin; 19 } 20 21 /** @type {remoting.HostDispatcher} @private */ 22 this.hostDispatcher_ = new remoting.HostDispatcher(createNpapiPlugin); 23 24 /** @param {string} version */ 25 var printVersion = function(version) { 26 if (version == '') { 27 console.log('Host not installed.'); 28 } else { 29 console.log('Host version: ' + version); 30 } 31 }; 32 33 this.hostDispatcher_.getDaemonVersion(printVersion, function() { 34 console.log('Host version not available.'); 35 }); 36} 37 38// Note that the values in the enums below are copied from 39// daemon_controller.h and must be kept in sync. 40/** @enum {number} */ 41remoting.HostController.State = { 42 NOT_IMPLEMENTED: -1, 43 NOT_INSTALLED: 0, 44 INSTALLING: 1, 45 STOPPED: 2, 46 STARTING: 3, 47 STARTED: 4, 48 STOPPING: 5, 49 UNKNOWN: 6 50}; 51 52/** @enum {number} */ 53remoting.HostController.AsyncResult = { 54 OK: 0, 55 FAILED: 1, 56 CANCELLED: 2, 57 FAILED_DIRECTORY: 3 58}; 59 60/** 61 * Set of features for which hasFeature() can be used to test. 62 * 63 * @enum {string} 64 */ 65remoting.HostController.Feature = { 66 PAIRING_REGISTRY: 'pairingRegistry' 67}; 68 69/** 70 * @param {remoting.HostController.Feature} feature The feature to test for. 71 * @param {function(boolean):void} callback 72 * @return {void} 73 */ 74remoting.HostController.prototype.hasFeature = function(feature, callback) { 75 // TODO(rmsousa): This could synchronously return a boolean, provided it were 76 // only called after the dispatcher is completely initialized. 77 this.hostDispatcher_.hasFeature(feature, callback); 78}; 79 80/** 81 * @param {function(boolean, boolean, boolean):void} onDone Callback to be 82 * called when done. 83 * @param {function(remoting.Error):void} onError Callback to be called on 84 * error. 85 */ 86remoting.HostController.prototype.getConsent = function(onDone, onError) { 87 this.hostDispatcher_.getUsageStatsConsent(onDone, onError); 88}; 89 90/** 91 * Registers and starts the host. 92 * 93 * @param {string} hostPin Host PIN. 94 * @param {boolean} consent The user's consent to crash dump reporting. 95 * @param {function():void} onDone Callback to be called when done. 96 * @param {function(remoting.Error):void} onError Callback to be called on 97 * error. 98 * @return {void} Nothing. 99 */ 100remoting.HostController.prototype.start = function(hostPin, consent, onDone, 101 onError) { 102 /** @type {remoting.HostController} */ 103 var that = this; 104 105 /** @return {string} */ 106 function generateUuid() { 107 var random = new Uint16Array(8); 108 window.crypto.getRandomValues(random); 109 /** @type {Array.<string>} */ 110 var e = new Array(); 111 for (var i = 0; i < 8; i++) { 112 e[i] = (/** @type {number} */random[i] + 0x10000). 113 toString(16).substring(1); 114 } 115 return e[0] + e[1] + '-' + e[2] + '-' + e[3] + '-' + 116 e[4] + '-' + e[5] + e[6] + e[7]; 117 }; 118 119 var newHostId = generateUuid(); 120 121 /** @param {remoting.Error} error */ 122 function onStartError(error) { 123 // Unregister the host if we failed to start it. 124 remoting.HostList.unregisterHostById(newHostId); 125 onError(error); 126 } 127 128 /** 129 * @param {string} hostName 130 * @param {string} publicKey 131 * @param {remoting.HostController.AsyncResult} result 132 */ 133 function onStarted(hostName, publicKey, result) { 134 if (result == remoting.HostController.AsyncResult.OK) { 135 remoting.hostList.onLocalHostStarted(hostName, newHostId, publicKey); 136 onDone(); 137 } else if (result == remoting.HostController.AsyncResult.CANCELLED) { 138 onStartError(remoting.Error.CANCELLED); 139 } else { 140 onStartError(remoting.Error.UNEXPECTED); 141 } 142 } 143 144 /** 145 * @param {string} hostName 146 * @param {string} publicKey 147 * @param {string} privateKey 148 * @param {XMLHttpRequest} xhr 149 * @param {string} hostSecretHash 150 */ 151 function startHostWithHash(hostName, publicKey, privateKey, xhr, 152 hostSecretHash) { 153 var hostConfig = { 154 xmpp_login: remoting.identity.getCachedEmail(), 155 oauth_refresh_token: remoting.oauth2.exportRefreshToken(), 156 host_id: newHostId, 157 host_name: hostName, 158 host_secret_hash: hostSecretHash, 159 private_key: privateKey 160 }; 161 that.hostDispatcher_.startDaemon(hostConfig, consent, 162 onStarted.bind(null, hostName, publicKey), 163 onStartError); 164 } 165 166 /** 167 * @param {string} hostName 168 * @param {string} publicKey 169 * @param {string} privateKey 170 * @param {XMLHttpRequest} xhr 171 */ 172 function onRegistered(hostName, publicKey, privateKey, xhr) { 173 var success = (xhr.status == 200); 174 175 if (success) { 176 that.hostDispatcher_.getPinHash(newHostId, hostPin, 177 startHostWithHash.bind(null, hostName, publicKey, privateKey, xhr), 178 onError); 179 } else { 180 console.log('Failed to register the host. Status: ' + xhr.status + 181 ' response: ' + xhr.responseText); 182 onError(remoting.Error.REGISTRATION_FAILED); 183 } 184 }; 185 186 /** 187 * @param {string} hostName 188 * @param {string} privateKey 189 * @param {string} publicKey 190 * @param {string} oauthToken 191 */ 192 function doRegisterHost(hostName, privateKey, publicKey, oauthToken) { 193 var headers = { 194 'Authorization': 'OAuth ' + oauthToken, 195 'Content-type' : 'application/json; charset=UTF-8' 196 }; 197 198 var newHostDetails = { data: { 199 hostId: newHostId, 200 hostName: hostName, 201 publicKey: publicKey 202 } }; 203 remoting.xhr.post( 204 remoting.settings.DIRECTORY_API_BASE_URL + '/@me/hosts/', 205 onRegistered.bind(null, hostName, publicKey, privateKey), 206 JSON.stringify(newHostDetails), 207 headers); 208 }; 209 210 /** 211 * @param {string} hostName 212 * @param {string} privateKey 213 * @param {string} publicKey 214 */ 215 function onKeyGenerated(hostName, privateKey, publicKey) { 216 remoting.identity.callWithToken( 217 doRegisterHost.bind(null, hostName, privateKey, publicKey), 218 onError); 219 }; 220 221 /** 222 * @param {string} hostName 223 * @return {void} Nothing. 224 */ 225 function startWithHostname(hostName) { 226 that.hostDispatcher_.generateKeyPair(onKeyGenerated.bind(null, hostName), 227 onError); 228 } 229 230 this.hostDispatcher_.getHostName(startWithHostname, onError); 231}; 232 233/** 234 * Stop the daemon process. 235 * @param {function():void} onDone Callback to be called when done. 236 * @param {function(remoting.Error):void} onError Callback to be called on 237 * error. 238 * @return {void} Nothing. 239 */ 240remoting.HostController.prototype.stop = function(onDone, onError) { 241 /** @type {remoting.HostController} */ 242 var that = this; 243 244 /** @param {string?} hostId The host id of the local host. */ 245 function unregisterHost(hostId) { 246 if (hostId) { 247 remoting.HostList.unregisterHostById(hostId); 248 } 249 onDone(); 250 }; 251 252 /** @param {remoting.HostController.AsyncResult} result */ 253 function onStopped(result) { 254 if (result == remoting.HostController.AsyncResult.OK) { 255 that.getLocalHostId(unregisterHost); 256 } else if (result == remoting.HostController.AsyncResult.CANCELLED) { 257 onError(remoting.Error.CANCELLED); 258 } else { 259 onError(remoting.Error.UNEXPECTED); 260 } 261 } 262 263 this.hostDispatcher_.stopDaemon(onStopped, onError); 264}; 265 266/** 267 * Check the host configuration is valid (non-null, and contains both host_id 268 * and xmpp_login keys). 269 * @param {Object} config The host configuration. 270 * @return {boolean} True if it is valid. 271 */ 272function isHostConfigValid_(config) { 273 return !!config && typeof config['host_id'] == 'string' && 274 typeof config['xmpp_login'] == 'string'; 275} 276 277/** 278 * @param {string} newPin The new PIN to set 279 * @param {function():void} onDone Callback to be called when done. 280 * @param {function(remoting.Error):void} onError Callback to be called on 281 * error. 282 * @return {void} Nothing. 283 */ 284remoting.HostController.prototype.updatePin = function(newPin, onDone, 285 onError) { 286 /** @type {remoting.HostController} */ 287 var that = this; 288 289 /** @param {remoting.HostController.AsyncResult} result */ 290 function onConfigUpdated(result) { 291 if (result == remoting.HostController.AsyncResult.OK) { 292 onDone(); 293 } else if (result == remoting.HostController.AsyncResult.CANCELLED) { 294 onError(remoting.Error.CANCELLED); 295 } else { 296 onError(remoting.Error.UNEXPECTED); 297 } 298 } 299 300 /** @param {string} pinHash */ 301 function updateDaemonConfigWithHash(pinHash) { 302 var newConfig = { 303 host_secret_hash: pinHash 304 }; 305 that.hostDispatcher_.updateDaemonConfig(newConfig, onConfigUpdated, 306 onError); 307 } 308 309 /** @param {Object} config */ 310 function onConfig(config) { 311 if (!isHostConfigValid_(config)) { 312 onError(remoting.Error.UNEXPECTED); 313 return; 314 } 315 /** @type {string} */ 316 var hostId = config['host_id']; 317 that.hostDispatcher_.getPinHash(hostId, newPin, updateDaemonConfigWithHash, 318 onError); 319 } 320 321 // TODO(sergeyu): When crbug.com/121518 is fixed: replace this call 322 // with an unprivileged version if that is necessary. 323 this.hostDispatcher_.getDaemonConfig(onConfig, onError); 324}; 325 326/** 327 * Get the state of the local host. 328 * 329 * @param {function(remoting.HostController.State):void} onDone Completion 330 * callback. 331 */ 332remoting.HostController.prototype.getLocalHostState = function(onDone) { 333 this.hostDispatcher_.getDaemonState(onDone, function() { 334 onDone(remoting.HostController.State.NOT_IMPLEMENTED); 335 }); 336}; 337 338/** 339 * Get the id of the local host, or null if it is not registered. 340 * 341 * @param {function(string?):void} onDone Completion callback. 342 */ 343remoting.HostController.prototype.getLocalHostId = function(onDone) { 344 /** @type {remoting.HostController} */ 345 var that = this; 346 /** @param {Object} config */ 347 function onConfig(config) { 348 var hostId = null; 349 if (isHostConfigValid_(config)) { 350 hostId = /** @type {string} */ config['host_id']; 351 } 352 onDone(hostId); 353 }; 354 355 this.hostDispatcher_.getDaemonConfig(onConfig, function(error) { 356 onDone(null); 357 }); 358}; 359 360/** 361 * Fetch the list of paired clients for this host. 362 * 363 * @param {function(Array.<remoting.PairedClient>):void} onDone 364 * @param {function(remoting.Error):void} onError 365 * @return {void} 366 */ 367remoting.HostController.prototype.getPairedClients = function(onDone, 368 onError) { 369 this.hostDispatcher_.getPairedClients(onDone, onError); 370}; 371 372/** 373 * Delete a single paired client. 374 * 375 * @param {string} client The client id of the pairing to delete. 376 * @param {function():void} onDone Completion callback. 377 * @param {function(remoting.Error):void} onError Error callback. 378 * @return {void} 379 */ 380remoting.HostController.prototype.deletePairedClient = function( 381 client, onDone, onError) { 382 this.hostDispatcher_.deletePairedClient(client, onDone, onError); 383}; 384 385/** 386 * Delete all paired clients. 387 * 388 * @param {function():void} onDone Completion callback. 389 * @param {function(remoting.Error):void} onError Error callback. 390 * @return {void} 391 */ 392remoting.HostController.prototype.clearPairedClients = function( 393 onDone, onError) { 394 this.hostDispatcher_.clearPairedClients(onDone, onError); 395}; 396 397/** @type {remoting.HostController} */ 398remoting.hostController = null; 399