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