Supervised user whitelists: Cleanup
[chromium-blink-merge.git] / remoting / webapp / crd / js / host_controller.js
blob8df1614adc03e56a39ab89fc31058fb5455a26b7
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.
5 'use strict';
7 /** @suppress {duplicate} */
8 var remoting = remoting || {};
10 /** @constructor */
11 remoting.HostController = function() {
12 this.hostDaemonFacade_ = this.createDaemonFacade_();
15 // The values in the enums below are duplicated in daemon_controller.h except
16 // for NOT_INSTALLED.
17 /** @enum {number} */
18 remoting.HostController.State = {
19 NOT_IMPLEMENTED: 0,
20 NOT_INSTALLED: 1,
21 STOPPED: 2,
22 STARTING: 3,
23 STARTED: 4,
24 STOPPING: 5,
25 UNKNOWN: 6
28 /**
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];
39 /** @enum {number} */
40 remoting.HostController.AsyncResult = {
41 OK: 0,
42 FAILED: 1,
43 CANCELLED: 2,
44 FAILED_DIRECTORY: 3
47 /**
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];
58 /**
59 * @return {remoting.HostDaemonFacade}
60 * @private
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) {
68 if (version == '') {
69 console.log('Host not installed.');
70 } else {
71 console.log('Host version: ' + version);
75 hostDaemonFacade.getDaemonVersion().then(printVersion, function() {
76 console.log('Host version not available.');
77 });
79 return hostDaemonFacade;
82 /**
83 * Set of features for which hasFeature() can be used to test.
85 * @enum {string}
87 remoting.HostController.Feature = {
88 PAIRING_REGISTRY: 'pairingRegistry',
89 OAUTH_CLIENT: 'oauthClient'
92 /**
93 * Information relating to user consent to collect usage stats. The
94 * fields are:
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.
102 * @typedef {{
103 * supported:boolean,
104 * allowed:boolean,
105 * setByPolicy:boolean
106 * }}
108 remoting.UsageStatsConsent;
111 * @typedef {{
112 * userEmail:string,
113 * refreshToken:string
114 * }}
116 remoting.XmppCredentials;
119 * @typedef {{
120 * privateKey:string,
121 * publicKey:string
122 * }}
124 remoting.KeyPair;
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} */
152 var that = this;
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) {
160 if (hasOauth) {
161 return that.hostDaemonFacade_.getHostClientId();
162 } else {
163 return null;
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
174 // string.
175 /** @type {!Promise<string>} */
176 var authCodePromise = Promise.all([
177 hostClientIdPromise,
178 hostNamePromise,
179 keyPairPromise
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;
189 return authCode;
192 // Get XMPP creditials.
193 var xmppCredsPromise = authCodePromise.then(function(authCode) {
194 if (authCode) {
195 // Use auth code supplied by registry.
196 return that.hostDaemonFacade_.getCredentialsFromAuthCode(authCode);
197 } else {
198 // No authorization code returned, use regular Chrome
199 // identitial credential flow.
200 return remoting.identity.getEmail().then(function(/** string */ email) {
201 return {
202 userEmail: email,
203 refreshToken: remoting.oauth2.getRefreshToken()
209 // Get as JID to use as the host owner.
210 var hostOwnerPromise = authCodePromise.then(function(authCode) {
211 if (authCode) {
212 return that.getClientBaseJid_();
213 } else {
214 return remoting.identity.getEmail();
218 // Build an initial host configuration.
219 /** @type {!Promise<!Object>} */
220 var hostConfigNoOwnerPromise = Promise.all([
221 hostNamePromise,
222 pinHashPromise,
223 xmppCredsPromise,
224 keyPairPromise
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]);
230 return {
231 xmpp_login: xmppCreds.userEmail,
232 oauth_refresh_token: xmppCreds.refreshToken,
233 host_id: newHostId,
234 host_name: hostName,
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,
247 hostOwnerPromise,
248 remoting.identity.getEmail(),
249 xmppCredsPromise
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;
261 return hostConfig;
264 // Start the daemon.
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);
282 } else {
283 throw remoting.Error.unexpected();
285 }).catch(function(error) {
286 if (hostRegistered) {
287 remoting.hostList.unregisterHostById(newHostId);
289 throw error;
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
297 * error.
298 * @return {void} Nothing.
300 remoting.HostController.prototype.stop = function(onDone, onError) {
301 /** @type {remoting.HostController} */
302 var that = this;
304 /** @param {string?} hostId The host id of the local host. */
305 function unregisterHost(hostId) {
306 if (hostId) {
307 remoting.hostList.unregisterHostById(hostId, onDone);
308 return;
310 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));
319 } else {
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
343 * error.
344 * @return {void} Nothing.
346 remoting.HostController.prototype.updatePin = function(newPin, onDone,
347 onError) {
348 /** @type {remoting.HostController} */
349 var that = this;
351 /** @param {remoting.HostController.AsyncResult} result */
352 function onConfigUpdated(result) {
353 if (result == remoting.HostController.AsyncResult.OK) {
354 onDone();
355 } else if (result == remoting.HostController.AsyncResult.CANCELLED) {
356 onError(new remoting.Error(remoting.Error.Tag.CANCELLED));
357 } else {
358 onError(remoting.Error.unexpected());
362 /** @param {string} pinHash */
363 function updateDaemonConfigWithHash(pinHash) {
364 var newConfig = {
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());
375 return;
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
393 * callback.
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} */
413 var that = this;
414 /** @param {Object} config */
415 function onConfig(config) {
416 var hostId = null;
417 if (isHostConfigValid_(config)) {
418 hostId = /** @type {string} */ (config['host_id']);
420 onDone(hostId);
423 this.hostDaemonFacade_.getDaemonConfig().then(onConfig, function(error) {
424 onDone(null);
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
433 * @return {void}
435 remoting.HostController.prototype.getPairedClients = function(onDone,
436 onError) {
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.
447 * @return {void}
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.
460 * @return {void}
462 remoting.HostController.prototype.clearPairedClients = function(
463 onDone, onError) {
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.
473 * @private
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) {
483 switch (state) {
484 case remoting.SignalStrategy.State.CONNECTED:
485 var jid = signalStrategy.getJid().split('/')[0].toLowerCase();
486 base.dispose(signalStrategy);
487 signalStrategy = null;
488 resolve(jid);
489 break;
491 case remoting.SignalStrategy.State.FAILED:
492 var error = signalStrategy.getError();
493 base.dispose(signalStrategy);
494 signalStrategy = null;
495 reject(error);
496 break;
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);
512 return result;
515 /** @type {remoting.HostController} */
516 remoting.hostController = null;