• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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  this.hostDispatcher_ = this.createDispatcher_();
13};
14
15/**
16 * @return {remoting.HostDispatcher}
17 */
18remoting.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} */
25remoting.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 */
40remoting.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} */
48remoting.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 */
59remoting.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 */
70remoting.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 */
95remoting.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 */
105remoting.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 */
117remoting.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 */
131remoting.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 */
348remoting.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 */
380function 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 */
392remoting.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 */
440remoting.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 */
451remoting.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 */
475remoting.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 */
488remoting.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 */
500remoting.HostController.prototype.clearPairedClients = function(
501    onDone, onError) {
502  this.hostDispatcher_.clearPairedClients(onDone, onError);
503};
504
505/** @type {remoting.HostController} */
506remoting.hostController = null;
507