Javascript  |  477行  |  13.97 KB

// 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() {
  /** @return {remoting.HostPlugin} */
  var createNpapiPlugin = function() {
    var plugin = remoting.HostSession.createPlugin();
    /** @type {HTMLElement} @private */
    var container = document.getElementById('daemon-plugin-container');
    container.appendChild(plugin);
    return plugin;
  };

  /** @type {remoting.HostDispatcher} @private */
  this.hostDispatcher_ = new remoting.HostDispatcher(createNpapiPlugin);

  /** @param {string} version */
  var printVersion = function(version) {
    if (version == '') {
      console.log('Host not installed.');
    } else {
      console.log('Host version: ' + version);
    }
  };

  this.hostDispatcher_.getDaemonVersion(printVersion, function() {
    console.log('Host version not available.');
  });
};

// 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
};

/** @enum {number} */
remoting.HostController.AsyncResult = {
  OK: 0,
  FAILED: 1,
  CANCELLED: 2,
  FAILED_DIRECTORY: 3
};

/**
 * 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);
};

/**
 * 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.exportRefreshToken()),
          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() {
    onDone(remoting.HostController.State.NOT_IMPLEMENTED);
  });
};

/**
 * 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);
};

/** @type {remoting.HostController} */
remoting.hostController = null;