Home | History | Annotate | Download | only in webapp
      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   /** @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} */
     41 remoting.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} */
     53 remoting.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  */
     65 remoting.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  */
     74 remoting.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  */
     86 remoting.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  */
    100 remoting.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  */
    240 remoting.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  */
    272 function 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  */
    284 remoting.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  */
    332 remoting.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  */
    343 remoting.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  */
    367 remoting.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  */
    380 remoting.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  */
    392 remoting.HostController.prototype.clearPairedClients = function(
    393     onDone, onError) {
    394   this.hostDispatcher_.clearPairedClients(onDone, onError);
    395 };
    396 
    397 /** @type {remoting.HostController} */
    398 remoting.hostController = null;
    399