Don't show supervised user as "already on this device" while they're being imported.
[chromium-blink-merge.git] / remoting / webapp / crd / js / host_controller.js
bloba3ae6e6640545606d13f934a7a98c5f6fbd919a1
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.
31  */
32 remoting.HostController.State.fromString = function(state) {
33   if (!remoting.HostController.State.hasOwnProperty(state)) {
34     throw "Invalid HostController.State: " + state;
35   }
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.
50  */
51 remoting.HostController.AsyncResult.fromString = function(result) {
52   if (!remoting.HostController.AsyncResult.hasOwnProperty(result)) {
53     throw "Invalid HostController.AsyncResult: " + result;
54   }
55   return remoting.HostController.AsyncResult[result];
58 /**
59  * @return {remoting.HostDaemonFacade}
60  * @private
61  */
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);
72     }
73   };
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.
84  *
85  * @enum {string}
86  */
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:
95  *
96  *   supported: True if crash dump reporting is supported by the host.
97  *
98  *   allowed: True if crash dump reporting is allowed.
99  *
100  *   setByPolicy: True if crash dump reporting is controlled by policy.
102  * @typedef {{
103  *   supported:boolean,
104  *   allowed:boolean,
105  *   setByPolicy:boolean
106  * }}
107  */
108 remoting.UsageStatsConsent;
111  * @typedef {{
112  *   userEmail:string,
113  *   refreshToken:string
114  * }}
115  */
116 remoting.XmppCredentials;
119  * @typedef {{
120  *   privateKey:string,
121  *   publicKey:string
122  * }}
123  */
124 remoting.KeyPair;
127  * @param {remoting.HostController.Feature} feature The feature to test for.
128  * @return {!Promise<boolean>} A promise that always resolves.
129  */
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>}
138  */
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.
149  */
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;
164     }
165   });
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([
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.getInstance().register(
186         newHostId, hostName, keyPair.publicKey, hostClientId);
187   }).then(function(/** remoting.HostListApi.RegisterResult */ result) {
188     hostRegistered = true;
189     return result;
190   });
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) {
199             return {
200               userEmail: registerResult.email,
201               refreshToken: token
202             };
203           });
204     } else {
205       // Use auth code supplied by Chromoting registry.
206       return that.hostDaemonFacade_.getCredentialsFromAuthCode(
207           registerResult.authCode);
208     }
209   });
211   // Build the host configuration.
212   /** @type {!Promise<!Object>} */
213   var hostConfigPromise = Promise.all([
214     hostNamePromise,
215     pinHashPromise,
216     xmppCredsPromise,
217     keyPairPromise,
218     hostOwnerPromise,
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]);
228     var registerResult =
229         /** @type {remoting.HostListApi.RegisterResult} */ (a[6]);
230     var hostConfig = {
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,
237       host_owner: hostOwner
238     };
239     if (hostOwnerEmail != hostOwner) {
240       hostConfig['host_owner_email'] = hostOwnerEmail;
241     }
242     if (registerResult.gcdId) {
243       hostConfig['gcd_device_id'] = registerResult.gcdId;
244     }
245     return hostConfig;
246   });
248   // Start the daemon.
249   /** @type {!Promise<remoting.HostController.AsyncResult>} */
250   var startDaemonResultPromise =
251       hostConfigPromise.then(function(hostConfig) {
252         return that.hostDaemonFacade_.startDaemon(hostConfig, consent);
253       });
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);
262         });
263       });
264     } else if (result == remoting.HostController.AsyncResult.CANCELLED) {
265       throw new remoting.Error(remoting.Error.Tag.CANCELLED);
266     } else {
267       throw remoting.Error.unexpected();
268     }
269   }).catch(function(error) {
270     if (hostRegistered) {
271       remoting.hostList.unregisterHostById(newHostId);
272     }
273     throw error;
274   });
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
281  *     error.
282  * @return {void} Nothing.
283  */
284 remoting.HostController.prototype.stop = function(onDone, onError) {
285   /** @type {remoting.HostController} */
286   var that = this;
288   /** @param {string?} hostId The host id of the local host. */
289   function unregisterHost(hostId) {
290     if (hostId) {
291       remoting.hostList.unregisterHostById(hostId, onDone);
292       return;
293     }
294     onDone();
295   }
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));
303     } else {
304       onError(remoting.Error.unexpected());
305     }
306   }
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.
317  */
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
327  *     error.
328  * @return {void} Nothing.
329  */
330 remoting.HostController.prototype.updatePin = function(newPin, onDone,
331                                                        onError) {
332   /** @type {remoting.HostController} */
333   var that = this;
335   /** @param {remoting.HostController.AsyncResult} result */
336   function onConfigUpdated(result) {
337     if (result == remoting.HostController.AsyncResult.OK) {
338       onDone();
339     } else if (result == remoting.HostController.AsyncResult.CANCELLED) {
340       onError(new remoting.Error(remoting.Error.Tag.CANCELLED));
341     } else {
342       onError(remoting.Error.unexpected());
343     }
344   }
346   /** @param {string} pinHash */
347   function updateDaemonConfigWithHash(pinHash) {
348     var newConfig = {
349       host_secret_hash: pinHash
350     };
351     that.hostDaemonFacade_.updateDaemonConfig(newConfig).then(
352         onConfigUpdated, remoting.Error.handler(onError));
353   }
355   /** @param {Object} config */
356   function onConfig(config) {
357     if (!isHostConfigValid_(config)) {
358       onError(remoting.Error.unexpected());
359       return;
360     }
361     /** @type {string} */
362     var hostId = config['host_id'];
363     that.hostDaemonFacade_.getPinHash(hostId, newPin).then(
364         updateDaemonConfigWithHash, remoting.Error.handler(onError));
365   }
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
377  *     callback.
378  */
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);
385   }
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.
394  */
395 remoting.HostController.prototype.getLocalHostId = function(onDone) {
396   /** @type {remoting.HostController} */
397   var that = this;
398   /** @param {Object} config */
399   function onConfig(config) {
400     var hostId = null;
401     if (isHostConfigValid_(config)) {
402       hostId = /** @type {string} */ (config['host_id']);
403     }
404     onDone(hostId);
405   };
407   this.hostDaemonFacade_.getDaemonConfig().then(onConfig, function(error) {
408     onDone(null);
409   });
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
417  * @return {void}
418  */
419 remoting.HostController.prototype.getPairedClients = function(onDone,
420                                                               onError) {
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.
431  * @return {void}
432  */
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.
444  * @return {void}
445  */
446 remoting.HostController.prototype.clearPairedClients = function(
447     onDone, onError) {
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.
457  * @private
458  * @return {!Promise<string>}
459  */
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) {
467       switch (state) {
468         case remoting.SignalStrategy.State.CONNECTED:
469         var jid = signalStrategy.getJid().split('/')[0].toLowerCase();
470         base.dispose(signalStrategy);
471         signalStrategy = null;
472         resolve(jid);
473         break;
475         case remoting.SignalStrategy.State.FAILED:
476         var error = signalStrategy.getError();
477         base.dispose(signalStrategy);
478         signalStrategy = null;
479         reject(error);
480         break;
481       }
482     };
484     signalStrategy = remoting.SignalStrategy.create();
485     signalStrategy.setStateChangedCallback(onState);
486   });
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);
493     });
494   });
496   return result;
499 /** @type {remoting.HostController} */
500 remoting.hostController = null;