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
);
169 /** @type {boolean} */
170 var hostRegistered
= false;
172 // Register the host and extract an optional auth code from the host
173 // response. The absence of an auth code is represented by an empty
175 /** @type {!Promise<string>} */
176 var authCodePromise
= 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
.register(
186 newHostId
, hostName
, keyPair
.publicKey
, hostClientId
);
187 }).then(function(/** string */ authCode
) {
188 hostRegistered
= true;
192 // Get XMPP creditials.
193 var xmppCredsPromise
= authCodePromise
.then(function(authCode
) {
195 // Use auth code supplied by registry.
196 return that
.hostDaemonFacade_
.getCredentialsFromAuthCode(authCode
);
198 // No authorization code returned, use regular Chrome
199 // identitial credential flow.
200 return remoting
.identity
.getEmail().then(function(/** string */ email
) {
203 refreshToken
: remoting
.oauth2
.getRefreshToken()
209 // Get as JID to use as the host owner.
210 var hostOwnerPromise
= authCodePromise
.then(function(authCode
) {
212 return that
.getClientBaseJid_();
214 return remoting
.identity
.getEmail();
218 // Build an initial host configuration.
219 /** @type {!Promise<!Object>} */
220 var hostConfigNoOwnerPromise
= Promise
.all([
225 ]).then(function(/** Array */ a
) {
226 var hostName
= /** @type {string} */ (a
[0]);
227 var hostSecretHash
= /** @type {string} */ (a
[1]);
228 var xmppCreds
= /** @type {remoting.XmppCredentials} */ (a
[2]);
229 var keyPair
= /** @type {remoting.KeyPair} */ (a
[3]);
231 xmpp_login
: xmppCreds
.userEmail
,
232 oauth_refresh_token
: xmppCreds
.refreshToken
,
235 host_secret_hash
: hostSecretHash
,
236 private_key
: keyPair
.privateKey
240 // Add host_owner and host_owner_email fields to the host config if
241 // necessary. This promise resolves to the same value as
242 // hostConfigNoOwnerPromise, with not until the extra fields are
243 // either added or determined to be redundant.
244 /** @type {!Promise<!Object>} */
245 var hostConfigWithOwnerPromise
= Promise
.all([
246 hostConfigNoOwnerPromise
,
248 remoting
.identity
.getEmail(),
250 ]).then(function(/** Array */ a
) {
251 var hostConfig
= /** @type {!Object} */ (a
[0]);
252 var hostOwner
= /** @type {string} */ (a
[1]);
253 var hostOwnerEmail
= /** @type {string} */ (a
[2]);
254 var xmppCreds
= /** @type {remoting.XmppCredentials} */ (a
[3]);
255 if (hostOwner
!= xmppCreds
.userEmail
) {
256 hostConfig
['host_owner'] = hostOwner
;
257 if (hostOwnerEmail
!= hostOwner
) {
258 hostConfig
['host_owner_email'] = hostOwnerEmail
;
265 /** @type {!Promise<remoting.HostController.AsyncResult>} */
266 var startDaemonResultPromise
=
267 hostConfigWithOwnerPromise
.then(function(hostConfig
) {
268 return that
.hostDaemonFacade_
.startDaemon(hostConfig
, consent
);
271 // Update the UI or report an error.
272 return startDaemonResultPromise
.then(function(result
) {
273 if (result
== remoting
.HostController
.AsyncResult
.OK
) {
274 return hostNamePromise
.then(function(hostName
) {
275 return keyPairPromise
.then(function(keyPair
) {
276 remoting
.hostList
.onLocalHostStarted(
277 hostName
, newHostId
, keyPair
.publicKey
);
280 } else if (result
== remoting
.HostController
.AsyncResult
.CANCELLED
) {
281 throw new remoting
.Error(remoting
.Error
.Tag
.CANCELLED
);
283 throw remoting
.Error
.unexpected();
285 }).catch(function(error
) {
286 if (hostRegistered
) {
287 remoting
.hostList
.unregisterHostById(newHostId
);
294 * Stop the daemon process.
295 * @param {function():void} onDone Callback to be called when done.
296 * @param {function(!remoting.Error):void} onError Callback to be called on
298 * @return {void} Nothing.
300 remoting
.HostController
.prototype.stop = function(onDone
, onError
) {
301 /** @type {remoting.HostController} */
304 /** @param {string?} hostId The host id of the local host. */
305 function unregisterHost(hostId
) {
307 remoting
.hostList
.unregisterHostById(hostId
, onDone
);
313 /** @param {remoting.HostController.AsyncResult} result */
314 function onStopped(result
) {
315 if (result
== remoting
.HostController
.AsyncResult
.OK
) {
316 that
.getLocalHostId(unregisterHost
);
317 } else if (result
== remoting
.HostController
.AsyncResult
.CANCELLED
) {
318 onError(new remoting
.Error(remoting
.Error
.Tag
.CANCELLED
));
320 onError(remoting
.Error
.unexpected());
324 this.hostDaemonFacade_
.stopDaemon().then(
325 onStopped
, remoting
.Error
.handler(onError
));
329 * Check the host configuration is valid (non-null, and contains both host_id
330 * and xmpp_login keys).
331 * @param {Object} config The host configuration.
332 * @return {boolean} True if it is valid.
334 function isHostConfigValid_(config
) {
335 return !!config
&& typeof config
['host_id'] == 'string' &&
336 typeof config
['xmpp_login'] == 'string';
340 * @param {string} newPin The new PIN to set
341 * @param {function():void} onDone Callback to be called when done.
342 * @param {function(!remoting.Error):void} onError Callback to be called on
344 * @return {void} Nothing.
346 remoting
.HostController
.prototype.updatePin = function(newPin
, onDone
,
348 /** @type {remoting.HostController} */
351 /** @param {remoting.HostController.AsyncResult} result */
352 function onConfigUpdated(result
) {
353 if (result
== remoting
.HostController
.AsyncResult
.OK
) {
355 } else if (result
== remoting
.HostController
.AsyncResult
.CANCELLED
) {
356 onError(new remoting
.Error(remoting
.Error
.Tag
.CANCELLED
));
358 onError(remoting
.Error
.unexpected());
362 /** @param {string} pinHash */
363 function updateDaemonConfigWithHash(pinHash
) {
365 host_secret_hash
: pinHash
367 that
.hostDaemonFacade_
.updateDaemonConfig(newConfig
).then(
368 onConfigUpdated
, remoting
.Error
.handler(onError
));
371 /** @param {Object} config */
372 function onConfig(config
) {
373 if (!isHostConfigValid_(config
)) {
374 onError(remoting
.Error
.unexpected());
377 /** @type {string} */
378 var hostId
= config
['host_id'];
379 that
.hostDaemonFacade_
.getPinHash(hostId
, newPin
).then(
380 updateDaemonConfigWithHash
, remoting
.Error
.handler(onError
));
383 // TODO(sergeyu): When crbug.com/121518 is fixed: replace this call
384 // with an unprivileged version if that is necessary.
385 this.hostDaemonFacade_
.getDaemonConfig().then(
386 onConfig
, remoting
.Error
.handler(onError
));
390 * Get the state of the local host.
392 * @param {function(remoting.HostController.State):void} onDone Completion
395 remoting
.HostController
.prototype.getLocalHostState = function(onDone
) {
396 /** @param {!remoting.Error} error */
397 function onError(error
) {
398 onDone((error
.hasTag(remoting
.Error
.Tag
.MISSING_PLUGIN
)) ?
399 remoting
.HostController
.State
.NOT_INSTALLED
:
400 remoting
.HostController
.State
.UNKNOWN
);
402 this.hostDaemonFacade_
.getDaemonState().then(
403 onDone
, remoting
.Error
.handler(onError
));
407 * Get the id of the local host, or null if it is not registered.
409 * @param {function(string?):void} onDone Completion callback.
411 remoting
.HostController
.prototype.getLocalHostId = function(onDone
) {
412 /** @type {remoting.HostController} */
414 /** @param {Object} config */
415 function onConfig(config
) {
417 if (isHostConfigValid_(config
)) {
418 hostId
= /** @type {string} */ (config
['host_id']);
423 this.hostDaemonFacade_
.getDaemonConfig().then(onConfig
, function(error
) {
429 * Fetch the list of paired clients for this host.
431 * @param {function(Array<remoting.PairedClient>):void} onDone
432 * @param {function(!remoting.Error):void} onError
435 remoting
.HostController
.prototype.getPairedClients = function(onDone
,
437 this.hostDaemonFacade_
.getPairedClients().then(
438 onDone
, remoting
.Error
.handler(onError
));
442 * Delete a single paired client.
444 * @param {string} client The client id of the pairing to delete.
445 * @param {function():void} onDone Completion callback.
446 * @param {function(!remoting.Error):void} onError Error callback.
449 remoting
.HostController
.prototype.deletePairedClient = function(
450 client
, onDone
, onError
) {
451 this.hostDaemonFacade_
.deletePairedClient(client
).then(
452 onDone
, remoting
.Error
.handler(onError
));
456 * Delete all paired clients.
458 * @param {function():void} onDone Completion callback.
459 * @param {function(!remoting.Error):void} onError Error callback.
462 remoting
.HostController
.prototype.clearPairedClients = function(
464 this.hostDaemonFacade_
.clearPairedClients().then(
465 onDone
, remoting
.Error
.handler(onError
));
469 * Gets the host owner's base JID, used by the host for client authorization.
470 * In most cases this is the same as the owner's email address, but for
471 * non-Gmail accounts, it may be different.
474 * @return {!Promise<string>}
476 remoting
.HostController
.prototype.getClientBaseJid_ = function() {
477 /** @type {remoting.SignalStrategy} */
478 var signalStrategy
= null;
480 var result
= new Promise(function(resolve
, reject
) {
481 /** @param {remoting.SignalStrategy.State} state */
482 var onState = function(state
) {
484 case remoting
.SignalStrategy
.State
.CONNECTED
:
485 var jid
= signalStrategy
.getJid().split('/')[0].toLowerCase();
486 base
.dispose(signalStrategy
);
487 signalStrategy
= null;
491 case remoting
.SignalStrategy
.State
.FAILED
:
492 var error
= signalStrategy
.getError();
493 base
.dispose(signalStrategy
);
494 signalStrategy
= null;
500 signalStrategy
= remoting
.SignalStrategy
.create();
501 signalStrategy
.setStateChangedCallback(onState
);
504 var tokenPromise
= remoting
.identity
.getToken();
505 var emailPromise
= remoting
.identity
.getEmail();
506 tokenPromise
.then(function(/** string */ token
) {
507 emailPromise
.then(function(/** string */ email
) {
508 signalStrategy
.connect(remoting
.settings
.XMPP_SERVER
, email
, token
);
515 /** @type {remoting.HostController} */
516 remoting
.hostController
= null;