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 /** @type {remoting.HostDaemonFacade} @private */
13 this.hostDaemonFacade_
= new remoting
.HostDaemonFacade();
15 /** @param {string} version */
16 var printVersion = function(version
) {
18 console
.log('Host not installed.');
20 console
.log('Host version: ' + version
);
24 this.getLocalHostVersion()
27 console
.log('Host version not available.');
31 // The values in the enums below are duplicated in daemon_controller.h except
34 remoting
.HostController
.State
= {
45 * @param {string} state The host controller state name.
46 * @return {remoting.HostController.State} The state enum value.
48 remoting
.HostController
.State
.fromString = function(state
) {
49 if (!remoting
.HostController
.State
.hasOwnProperty(state
)) {
50 throw "Invalid HostController.State: " + state
;
52 return remoting
.HostController
.State
[state
];
56 remoting
.HostController
.AsyncResult
= {
64 * @param {string} result The async result name.
65 * @return {remoting.HostController.AsyncResult} The result enum value.
67 remoting
.HostController
.AsyncResult
.fromString = function(result
) {
68 if (!remoting
.HostController
.AsyncResult
.hasOwnProperty(result
)) {
69 throw "Invalid HostController.AsyncResult: " + result
;
71 return remoting
.HostController
.AsyncResult
[result
];
75 * Set of features for which hasFeature() can be used to test.
79 remoting
.HostController
.Feature
= {
80 PAIRING_REGISTRY
: 'pairingRegistry',
81 OAUTH_CLIENT
: 'oauthClient'
85 * Information relating to user consent to collect usage stats. The
88 * supported: True if crash dump reporting is supported by the host.
90 * allowed: True if crash dump reporting is allowed.
92 * setByPolicy: True if crash dump reporting is controlled by policy.
100 remoting
.UsageStatsConsent
;
105 * refreshToken:string
108 remoting
.XmppCredentials
;
119 * @param {remoting.HostController.Feature} feature The feature to test for.
120 * @return {!Promise<boolean>} A promise that always resolves.
122 remoting
.HostController
.prototype.hasFeature = function(feature
) {
123 // TODO(rmsousa): This could synchronously return a boolean, provided it were
124 // only called after native messaging is completely initialized.
125 return this.hostDaemonFacade_
.hasFeature(feature
);
129 * @return {!Promise<remoting.UsageStatsConsent>}
131 remoting
.HostController
.prototype.getConsent = function() {
132 return this.hostDaemonFacade_
.getUsageStatsConsent();
136 * Registers and starts the host.
138 * @param {string} hostPin Host PIN.
139 * @param {boolean} consent The user's consent to crash dump reporting.
140 * @return {!Promise<void>} A promise resolved once the host is started.
142 remoting
.HostController
.prototype.start = function(hostPin
, consent
) {
143 /** @type {remoting.HostController} */
146 // Start a bunch of requests with no side-effects.
147 var hostNamePromise
= this.hostDaemonFacade_
.getHostName();
148 var hasOauthPromise
=
149 this.hasFeature(remoting
.HostController
.Feature
.OAUTH_CLIENT
);
150 var keyPairPromise
= this.hostDaemonFacade_
.generateKeyPair();
151 var hostClientIdPromise
= hasOauthPromise
.then(function(hasOauth
) {
153 return that
.hostDaemonFacade_
.getHostClientId();
158 var hostOwnerPromise
= this.getClientBaseJid_();
160 // Register the host and extract an auth code from the host response
161 // and, optionally an email address for the robot account.
162 /** @type {!Promise<remoting.HostListApi.RegisterResult>} */
163 var registerResultPromise
= Promise
.all([
167 ]).then(function(/** Array */ a
) {
168 var hostClientId
= /** @type {string} */ (a
[0]);
169 var hostName
= /** @type {string} */ (a
[1]);
170 var keyPair
= /** @type {remoting.KeyPair} */ (a
[2]);
172 return remoting
.HostListApi
.getInstance().register(
173 hostName
, keyPair
.publicKey
, hostClientId
);
176 // For convenience, make the host ID available as a separate promise.
177 /** @type {!Promise<string>} */
178 var hostIdPromise
= registerResultPromise
.then(function(registerResult
) {
179 return registerResult
.hostId
;
182 // Get the PIN hash based on the host ID.
183 /** @type {!Promise<string>} */
184 var pinHashPromise
= hostIdPromise
.then(function(hostId
) {
185 return that
.hostDaemonFacade_
.getPinHash(hostId
, hostPin
);
188 // Get XMPP creditials.
189 var xmppCredsPromise
= registerResultPromise
.then(function(registerResult
) {
190 console
.assert(registerResult
.authCode
!= '', '|authCode| is empty.');
191 if (registerResult
.email
) {
192 // Use auth code and email supplied by GCD.
193 return that
.hostDaemonFacade_
.getRefreshTokenFromAuthCode(
194 registerResult
.authCode
).then(function(token
) {
196 userEmail
: registerResult
.email
,
201 // Use auth code supplied by Chromoting registry.
202 return that
.hostDaemonFacade_
.getCredentialsFromAuthCode(
203 registerResult
.authCode
);
207 // Build the host configuration.
208 /** @type {!Promise<!Object>} */
209 var hostConfigPromise
= Promise
.all([
215 remoting
.identity
.getEmail(),
216 registerResultPromise
217 ]).then(function(/** Array */ a
) {
218 var hostName
= /** @type {string} */ (a
[0]);
219 var hostSecretHash
= /** @type {string} */ (a
[1]);
220 var xmppCreds
= /** @type {remoting.XmppCredentials} */ (a
[2]);
221 var keyPair
= /** @type {remoting.KeyPair} */ (a
[3]);
222 var hostOwner
= /** @type {string} */ (a
[4]);
223 var hostOwnerEmail
= /** @type {string} */ (a
[5]);
225 /** @type {remoting.HostListApi.RegisterResult} */ (a
[6]);
227 xmpp_login
: xmppCreds
.userEmail
,
228 oauth_refresh_token
: xmppCreds
.refreshToken
,
230 host_secret_hash
: hostSecretHash
,
231 private_key
: keyPair
.privateKey
,
232 host_owner
: hostOwner
234 if (hostOwnerEmail
!= hostOwner
) {
235 hostConfig
['host_owner_email'] = hostOwnerEmail
;
237 if (registerResult
.isLegacy
) {
238 hostConfig
['host_id'] = registerResult
.hostId
;
241 hostConfig
['gcd_device_id'] = registerResult
.hostId
;
247 /** @type {!Promise<remoting.HostController.AsyncResult>} */
248 var startDaemonResultPromise
=
249 hostConfigPromise
.then(function(hostConfig
) {
250 return that
.hostDaemonFacade_
.startDaemon(hostConfig
, consent
);
253 // Update the UI or report an error.
254 return hostIdPromise
.then(function(hostId
) {
255 return startDaemonResultPromise
.then(function(result
) {
256 if (result
== remoting
.HostController
.AsyncResult
.OK
) {
257 return hostNamePromise
.then(function(hostName
) {
258 return keyPairPromise
.then(function(keyPair
) {
259 remoting
.hostList
.onLocalHostStarted(
260 hostName
, hostId
, keyPair
.publicKey
);
263 } else if (result
== remoting
.HostController
.AsyncResult
.CANCELLED
) {
264 throw new remoting
.Error(remoting
.Error
.Tag
.CANCELLED
);
266 throw remoting
.Error
.unexpected();
268 }).catch(function(error
) {
269 remoting
.hostList
.unregisterHostById(hostId
);
276 * Stop the daemon process.
277 * @param {function():void} onDone Callback to be called when done.
278 * @param {function(!remoting.Error):void} onError Callback to be called on
280 * @return {void} Nothing.
282 remoting
.HostController
.prototype.stop = function(onDone
, onError
) {
283 /** @type {remoting.HostController} */
286 /** @param {string?} hostId The host id of the local host. */
287 function unregisterHost(hostId
) {
289 remoting
.hostList
.unregisterHostById(hostId
, onDone
);
295 /** @param {remoting.HostController.AsyncResult} result */
296 function onStopped(result
) {
297 if (result
== remoting
.HostController
.AsyncResult
.OK
) {
298 that
.getLocalHostId(unregisterHost
);
299 } else if (result
== remoting
.HostController
.AsyncResult
.CANCELLED
) {
300 onError(new remoting
.Error(remoting
.Error
.Tag
.CANCELLED
));
302 onError(remoting
.Error
.unexpected());
306 this.hostDaemonFacade_
.stopDaemon().then(
307 onStopped
, remoting
.Error
.handler(onError
));
311 * Check the host configuration is valid (non-null, and contains both host_id
312 * and xmpp_login keys).
313 * @param {Object} config The host configuration.
314 * @return {boolean} True if it is valid.
316 function isHostConfigValid_(config
) {
317 return !!config
&& typeof config
['host_id'] == 'string' &&
318 typeof config
['xmpp_login'] == 'string';
322 * @param {string} newPin The new PIN to set
323 * @param {function():void} onDone Callback to be called when done.
324 * @param {function(!remoting.Error):void} onError Callback to be called on
326 * @return {void} Nothing.
328 remoting
.HostController
.prototype.updatePin = function(newPin
, onDone
,
330 /** @type {remoting.HostController} */
333 /** @param {remoting.HostController.AsyncResult} result */
334 function onConfigUpdated(result
) {
335 if (result
== remoting
.HostController
.AsyncResult
.OK
) {
336 that
.clearPairedClients(onDone
, onError
);
337 } else if (result
== remoting
.HostController
.AsyncResult
.CANCELLED
) {
338 onError(new remoting
.Error(remoting
.Error
.Tag
.CANCELLED
));
340 onError(remoting
.Error
.unexpected());
344 /** @param {string} pinHash */
345 function updateDaemonConfigWithHash(pinHash
) {
347 host_secret_hash
: pinHash
349 that
.hostDaemonFacade_
.updateDaemonConfig(newConfig
).then(
350 onConfigUpdated
, remoting
.Error
.handler(onError
));
353 /** @param {Object} config */
354 function onConfig(config
) {
355 if (!isHostConfigValid_(config
)) {
356 onError(remoting
.Error
.unexpected());
359 /** @type {string} */
360 var hostId
= config
['host_id'];
361 that
.hostDaemonFacade_
.getPinHash(hostId
, newPin
).then(
362 updateDaemonConfigWithHash
, remoting
.Error
.handler(onError
));
365 // TODO(sergeyu): When crbug.com/121518 is fixed: replace this call
366 // with an unprivileged version if that is necessary.
367 this.hostDaemonFacade_
.getDaemonConfig().then(
368 onConfig
, remoting
.Error
.handler(onError
));
372 * Get the state of the local host.
374 * @param {function(remoting.HostController.State):void} onDone Completion
377 remoting
.HostController
.prototype.getLocalHostState = function(onDone
) {
378 /** @param {!remoting.Error} error */
379 function onError(error
) {
380 onDone((error
.hasTag(remoting
.Error
.Tag
.MISSING_PLUGIN
)) ?
381 remoting
.HostController
.State
.NOT_INSTALLED
:
382 remoting
.HostController
.State
.UNKNOWN
);
384 this.hostDaemonFacade_
.getDaemonState().then(
385 onDone
, remoting
.Error
.handler(onError
));
389 * Get the id of the local host, or null if it is not registered.
391 * @param {function(string?):void} onDone Completion callback.
393 remoting
.HostController
.prototype.getLocalHostId = function(onDone
) {
394 /** @type {remoting.HostController} */
396 /** @param {Object} config */
397 function onConfig(config
) {
399 if (isHostConfigValid_(config
)) {
400 // Use the |gcd_device_id| field if it exists, or the |host_id|
402 hostId
= base
.getStringAttr(
403 config
, 'gcd_device_id', base
.getStringAttr(config
, 'host_id'));
408 this.hostDaemonFacade_
.getDaemonConfig().then(onConfig
, function(error
) {
414 * @return {Promise<string>} Promise that resolves with the host version, if
415 * installed, or rejects otherwise.
417 remoting
.HostController
.prototype.getLocalHostVersion = function() {
418 return this.hostDaemonFacade_
.getDaemonVersion();
422 * Fetch the list of paired clients for this host.
424 * @param {function(Array<remoting.PairedClient>):void} onDone
425 * @param {function(!remoting.Error):void} onError
428 remoting
.HostController
.prototype.getPairedClients = function(onDone
,
430 this.hostDaemonFacade_
.getPairedClients().then(
431 onDone
, remoting
.Error
.handler(onError
));
435 * Delete a single paired client.
437 * @param {string} client The client id of the pairing to delete.
438 * @param {function():void} onDone Completion callback.
439 * @param {function(!remoting.Error):void} onError Error callback.
442 remoting
.HostController
.prototype.deletePairedClient = function(
443 client
, onDone
, onError
) {
444 this.hostDaemonFacade_
.deletePairedClient(client
).then(
445 onDone
, remoting
.Error
.handler(onError
));
449 * Delete all paired clients.
451 * @param {function():void} onDone Completion callback.
452 * @param {function(!remoting.Error):void} onError Error callback.
455 remoting
.HostController
.prototype.clearPairedClients = function(
457 this.hostDaemonFacade_
.clearPairedClients().then(
458 onDone
, remoting
.Error
.handler(onError
));
462 * Gets the host owner's base JID, used by the host for client authorization.
463 * In most cases this is the same as the owner's email address, but for
464 * non-Gmail accounts, it may be different.
467 * @return {!Promise<string>}
469 remoting
.HostController
.prototype.getClientBaseJid_ = function() {
470 /** @type {remoting.SignalStrategy} */
471 var signalStrategy
= null;
473 var result
= new Promise(function(resolve
, reject
) {
474 /** @param {remoting.SignalStrategy.State} state */
475 var onState = function(state
) {
477 case remoting
.SignalStrategy
.State
.CONNECTED
:
478 var jid
= signalStrategy
.getJid().split('/')[0].toLowerCase();
479 base
.dispose(signalStrategy
);
480 signalStrategy
= null;
484 case remoting
.SignalStrategy
.State
.FAILED
:
485 var error
= signalStrategy
.getError();
486 base
.dispose(signalStrategy
);
487 signalStrategy
= null;
493 signalStrategy
= remoting
.SignalStrategy
.create();
494 signalStrategy
.setStateChangedCallback(onState
);
497 var tokenPromise
= remoting
.identity
.getToken();
498 var emailPromise
= remoting
.identity
.getEmail();
499 tokenPromise
.then(function(/** string */ token
) {
500 emailPromise
.then(function(/** string */ email
) {
501 signalStrategy
.connect(remoting
.settings
.XMPP_SERVER
, email
, token
);
508 /** @type {remoting.HostController} */
509 remoting
.hostController
= null;