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.
7 /** @suppress {duplicate} */
8 var remoting = remoting || {};
11 remoting.HostController = function() {
12 this.hostDaemonFacade_ = this.createDaemonFacade_();
15 // The values in the enums below are duplicated in daemon_controller.h except
18 remoting.HostController.State = {
29 * @param {string} state The host controller state name.
30 * @return {remoting.HostController.State} The state enum value.
32 remoting.HostController.State.fromString = function(state) {
33 if (!remoting.HostController.State.hasOwnProperty(state)) {
34 throw "Invalid HostController.State: " + state;
36 return remoting.HostController.State[state];
40 remoting.HostController.AsyncResult = {
48 * @param {string} result The async result name.
49 * @return {remoting.HostController.AsyncResult} The result enum value.
51 remoting.HostController.AsyncResult.fromString = function(result) {
52 if (!remoting.HostController.AsyncResult.hasOwnProperty(result)) {
53 throw "Invalid HostController.AsyncResult: " + result;
55 return remoting.HostController.AsyncResult[result];
59 * @return {remoting.HostDaemonFacade}
62 remoting.HostController.prototype.createDaemonFacade_ = function() {
63 /** @type {remoting.HostDaemonFacade} @private */
64 var hostDaemonFacade = new remoting.HostDaemonFacade();
66 /** @param {string} version */
67 var printVersion = function(version) {
69 console.log('Host not installed.');
71 console.log('Host version: ' + version);
75 hostDaemonFacade.getDaemonVersion().then(printVersion, function() {
76 console.log('Host version not available.');
79 return hostDaemonFacade;
83 * Set of features for which hasFeature() can be used to test.
87 remoting.HostController.Feature = {
88 PAIRING_REGISTRY: 'pairingRegistry',
89 OAUTH_CLIENT: 'oauthClient'
93 * Information relating to user consent to collect usage stats. The
96 * supported: True if crash dump reporting is supported by the host.
98 * allowed: True if crash dump reporting is allowed.
100 * setByPolicy: True if crash dump reporting is controlled by policy.
105 * setByPolicy:boolean
108 remoting.UsageStatsConsent;
113 * refreshToken:string
116 remoting.XmppCredentials;
127 * @param {remoting.HostController.Feature} feature The feature to test for.
128 * @return {!Promise<boolean>} A promise that always resolves.
130 remoting.HostController.prototype.hasFeature = function(feature) {
131 // TODO(rmsousa): This could synchronously return a boolean, provided it were
132 // only called after native messaging is completely initialized.
133 return this.hostDaemonFacade_.hasFeature(feature);
137 * @return {!Promise<remoting.UsageStatsConsent>}
139 remoting.HostController.prototype.getConsent = function() {
140 return this.hostDaemonFacade_.getUsageStatsConsent();
144 * Registers and starts the host.
146 * @param {string} hostPin Host PIN.
147 * @param {boolean} consent The user's consent to crash dump reporting.
148 * @return {!Promise<void>} A promise resolved once the host is started.
150 remoting.HostController.prototype.start = function(hostPin, consent) {
151 /** @type {remoting.HostController} */
154 // Start a bunch of requests with no side-effects.
155 var hostNamePromise = this.hostDaemonFacade_.getHostName();
156 var hasOauthPromise =
157 this.hasFeature(remoting.HostController.Feature.OAUTH_CLIENT);
158 var keyPairPromise = this.hostDaemonFacade_.generateKeyPair();
159 var hostClientIdPromise = hasOauthPromise.then(function(hasOauth) {
161 return that.hostDaemonFacade_.getHostClientId();
166 var newHostId = base.generateUuid();
167 var pinHashPromise = this.hostDaemonFacade_.getPinHash(newHostId, hostPin);
168 var hostOwnerPromise = this.getClientBaseJid_();
170 /** @type {boolean} */
171 var hostRegistered = false;
173 // Register the host and extract an auth code from the host response
174 // and, optionally an email address for the robot account.
175 /** @type {!Promise<remoting.HostListApi.RegisterResult>} */
176 var registerResultPromise = Promise.all([
180 ]).then(function(/** Array */ a) {
181 var hostClientId = /** @type {string} */ (a[0]);
182 var hostName = /** @type {string} */ (a[1]);
183 var keyPair = /** @type {remoting.KeyPair} */ (a[2]);
185 return remoting.HostListApi.getInstance().register(
186 newHostId, hostName, keyPair.publicKey, hostClientId);
187 }).then(function(/** remoting.HostListApi.RegisterResult */ result) {
188 hostRegistered = true;
192 // Get XMPP creditials.
193 var xmppCredsPromise = registerResultPromise.then(function(registerResult) {
194 base.debug.assert(registerResult.authCode != '');
195 if (registerResult.email) {
196 // Use auth code and email supplied by GCD.
197 return that.hostDaemonFacade_.getRefreshTokenFromAuthCode(
198 registerResult.authCode).then(function(token) {
200 userEmail: registerResult.email,
205 // Use auth code supplied by Chromoting registry.
206 return that.hostDaemonFacade_.getCredentialsFromAuthCode(
207 registerResult.authCode);
211 // Build the host configuration.
212 /** @type {!Promise<!Object>} */
213 var hostConfigPromise = Promise.all([
219 remoting.identity.getEmail(),
220 registerResultPromise
221 ]).then(function(/** Array */ a) {
222 var hostName = /** @type {string} */ (a[0]);
223 var hostSecretHash = /** @type {string} */ (a[1]);
224 var xmppCreds = /** @type {remoting.XmppCredentials} */ (a[2]);
225 var keyPair = /** @type {remoting.KeyPair} */ (a[3]);
226 var hostOwner = /** @type {string} */ (a[4]);
227 var hostOwnerEmail = /** @type {string} */ (a[5]);
229 /** @type {remoting.HostListApi.RegisterResult} */ (a[6]);
231 xmpp_login: xmppCreds.userEmail,
232 oauth_refresh_token: xmppCreds.refreshToken,
235 host_secret_hash: hostSecretHash,
236 private_key: keyPair.privateKey,
237 host_owner: hostOwner
239 if (hostOwnerEmail != hostOwner) {
240 hostConfig['host_owner_email'] = hostOwnerEmail;
242 if (registerResult.gcdId) {
243 hostConfig['gcd_device_id'] = registerResult.gcdId;
249 /** @type {!Promise<remoting.HostController.AsyncResult>} */
250 var startDaemonResultPromise =
251 hostConfigPromise.then(function(hostConfig) {
252 return that.hostDaemonFacade_.startDaemon(hostConfig, consent);
255 // Update the UI or report an error.
256 return startDaemonResultPromise.then(function(result) {
257 if (result == remoting.HostController.AsyncResult.OK) {
258 return hostNamePromise.then(function(hostName) {
259 return keyPairPromise.then(function(keyPair) {
260 remoting.hostList.onLocalHostStarted(
261 hostName, newHostId, keyPair.publicKey);
264 } else if (result == remoting.HostController.AsyncResult.CANCELLED) {
265 throw new remoting.Error(remoting.Error.Tag.CANCELLED);
267 throw remoting.Error.unexpected();
269 }).catch(function(error) {
270 if (hostRegistered) {
271 remoting.hostList.unregisterHostById(newHostId);
278 * Stop the daemon process.
279 * @param {function():void} onDone Callback to be called when done.
280 * @param {function(!remoting.Error):void} onError Callback to be called on
282 * @return {void} Nothing.
284 remoting.HostController.prototype.stop = function(onDone, onError) {
285 /** @type {remoting.HostController} */
288 /** @param {string?} hostId The host id of the local host. */
289 function unregisterHost(hostId) {
291 remoting.hostList.unregisterHostById(hostId, onDone);
297 /** @param {remoting.HostController.AsyncResult} result */
298 function onStopped(result) {
299 if (result == remoting.HostController.AsyncResult.OK) {
300 that.getLocalHostId(unregisterHost);
301 } else if (result == remoting.HostController.AsyncResult.CANCELLED) {
302 onError(new remoting.Error(remoting.Error.Tag.CANCELLED));
304 onError(remoting.Error.unexpected());
308 this.hostDaemonFacade_.stopDaemon().then(
309 onStopped, remoting.Error.handler(onError));
313 * Check the host configuration is valid (non-null, and contains both host_id
314 * and xmpp_login keys).
315 * @param {Object} config The host configuration.
316 * @return {boolean} True if it is valid.
318 function isHostConfigValid_(config) {
319 return !!config && typeof config['host_id'] == 'string' &&
320 typeof config['xmpp_login'] == 'string';
324 * @param {string} newPin The new PIN to set
325 * @param {function():void} onDone Callback to be called when done.
326 * @param {function(!remoting.Error):void} onError Callback to be called on
328 * @return {void} Nothing.
330 remoting.HostController.prototype.updatePin = function(newPin, onDone,
332 /** @type {remoting.HostController} */
335 /** @param {remoting.HostController.AsyncResult} result */
336 function onConfigUpdated(result) {
337 if (result == remoting.HostController.AsyncResult.OK) {
339 } else if (result == remoting.HostController.AsyncResult.CANCELLED) {
340 onError(new remoting.Error(remoting.Error.Tag.CANCELLED));
342 onError(remoting.Error.unexpected());
346 /** @param {string} pinHash */
347 function updateDaemonConfigWithHash(pinHash) {
349 host_secret_hash: pinHash
351 that.hostDaemonFacade_.updateDaemonConfig(newConfig).then(
352 onConfigUpdated, remoting.Error.handler(onError));
355 /** @param {Object} config */
356 function onConfig(config) {
357 if (!isHostConfigValid_(config)) {
358 onError(remoting.Error.unexpected());
361 /** @type {string} */
362 var hostId = config['host_id'];
363 that.hostDaemonFacade_.getPinHash(hostId, newPin).then(
364 updateDaemonConfigWithHash, remoting.Error.handler(onError));
367 // TODO(sergeyu): When crbug.com/121518 is fixed: replace this call
368 // with an unprivileged version if that is necessary.
369 this.hostDaemonFacade_.getDaemonConfig().then(
370 onConfig, remoting.Error.handler(onError));
374 * Get the state of the local host.
376 * @param {function(remoting.HostController.State):void} onDone Completion
379 remoting.HostController.prototype.getLocalHostState = function(onDone) {
380 /** @param {!remoting.Error} error */
381 function onError(error) {
382 onDone((error.hasTag(remoting.Error.Tag.MISSING_PLUGIN)) ?
383 remoting.HostController.State.NOT_INSTALLED :
384 remoting.HostController.State.UNKNOWN);
386 this.hostDaemonFacade_.getDaemonState().then(
387 onDone, remoting.Error.handler(onError));
391 * Get the id of the local host, or null if it is not registered.
393 * @param {function(string?):void} onDone Completion callback.
395 remoting.HostController.prototype.getLocalHostId = function(onDone) {
396 /** @type {remoting.HostController} */
398 /** @param {Object} config */
399 function onConfig(config) {
401 if (isHostConfigValid_(config)) {
402 hostId = /** @type {string} */ (config['host_id']);
407 this.hostDaemonFacade_.getDaemonConfig().then(onConfig, function(error) {
413 * Fetch the list of paired clients for this host.
415 * @param {function(Array<remoting.PairedClient>):void} onDone
416 * @param {function(!remoting.Error):void} onError
419 remoting.HostController.prototype.getPairedClients = function(onDone,
421 this.hostDaemonFacade_.getPairedClients().then(
422 onDone, remoting.Error.handler(onError));
426 * Delete a single paired client.
428 * @param {string} client The client id of the pairing to delete.
429 * @param {function():void} onDone Completion callback.
430 * @param {function(!remoting.Error):void} onError Error callback.
433 remoting.HostController.prototype.deletePairedClient = function(
434 client, onDone, onError) {
435 this.hostDaemonFacade_.deletePairedClient(client).then(
436 onDone, remoting.Error.handler(onError));
440 * Delete all paired clients.
442 * @param {function():void} onDone Completion callback.
443 * @param {function(!remoting.Error):void} onError Error callback.
446 remoting.HostController.prototype.clearPairedClients = function(
448 this.hostDaemonFacade_.clearPairedClients().then(
449 onDone, remoting.Error.handler(onError));
453 * Gets the host owner's base JID, used by the host for client authorization.
454 * In most cases this is the same as the owner's email address, but for
455 * non-Gmail accounts, it may be different.
458 * @return {!Promise<string>}
460 remoting.HostController.prototype.getClientBaseJid_ = function() {
461 /** @type {remoting.SignalStrategy} */
462 var signalStrategy = null;
464 var result = new Promise(function(resolve, reject) {
465 /** @param {remoting.SignalStrategy.State} state */
466 var onState = function(state) {
468 case remoting.SignalStrategy.State.CONNECTED:
469 var jid = signalStrategy.getJid().split('/')[0].toLowerCase();
470 base.dispose(signalStrategy);
471 signalStrategy = null;
475 case remoting.SignalStrategy.State.FAILED:
476 var error = signalStrategy.getError();
477 base.dispose(signalStrategy);
478 signalStrategy = null;
484 signalStrategy = remoting.SignalStrategy.create();
485 signalStrategy.setStateChangedCallback(onState);
488 var tokenPromise = remoting.identity.getToken();
489 var emailPromise = remoting.identity.getEmail();
490 tokenPromise.then(function(/** string */ token) {
491 emailPromise.then(function(/** string */ email) {
492 signalStrategy.connect(remoting.settings.XMPP_SERVER, email, token);
499 /** @type {remoting.HostController} */
500 remoting.hostController = null;