Rewrite AndroidSyncSettings to be significantly simpler.
[chromium-blink-merge.git] / remoting / webapp / crd / js / host_controller.js
blobdad18479b366293dc10e6afcafddd9f80023a315
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(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  * @param {remoting.HostController.Feature} feature The feature to test for.
94  * @param {function(boolean):void} callback
95  * @return {void}
96  */
97 remoting.HostController.prototype.hasFeature = function(feature, callback) {
98   // TODO(rmsousa): This could synchronously return a boolean, provided it were
99   // only called after native messaging is completely initialized.
100   this.hostDaemonFacade_.hasFeature(feature, callback);
104  * @param {function(boolean, boolean, boolean):void} onDone Callback to be
105  *     called when done.
106  * @param {function(remoting.Error):void} onError Callback to be called on
107  *     error.
108  */
109 remoting.HostController.prototype.getConsent = function(onDone, onError) {
110   this.hostDaemonFacade_.getUsageStatsConsent(onDone, onError);
114  * Registers and starts the host.
116  * @param {string} hostPin Host PIN.
117  * @param {boolean} consent The user's consent to crash dump reporting.
118  * @param {function():void} onDone Callback to be called when done.
119  * @param {function(remoting.Error):void} onError Callback to be called on
120  *     error.
121  * @return {void} Nothing.
122  */
123 remoting.HostController.prototype.start = function(hostPin, consent, onDone,
124                                                    onError) {
125   /** @type {remoting.HostController} */
126   var that = this;
128   /** @return {string} */
129   function generateUuid() {
130     var random = new Uint16Array(8);
131     window.crypto.getRandomValues(random);
132     /** @type {Array<string>} */
133     var e = new Array();
134     for (var i = 0; i < 8; i++) {
135       e[i] = (/** @type {number} */ (random[i]) + 0x10000).
136           toString(16).substring(1);
137     }
138     return e[0] + e[1] + '-' + e[2] + '-' + e[3] + '-' +
139         e[4] + '-' + e[5] + e[6] + e[7];
140   };
142   var newHostId = generateUuid();
144   /** @param {remoting.Error} error */
145   function onStartError(error) {
146     // Unregister the host if we failed to start it.
147     remoting.HostList.unregisterHostById(newHostId);
148     onError(error);
149   }
151   /**
152    * @param {string} hostName
153    * @param {string} publicKey
154    * @param {remoting.HostController.AsyncResult} result
155    */
156   function onStarted(hostName, publicKey, result) {
157     if (result == remoting.HostController.AsyncResult.OK) {
158       remoting.hostList.onLocalHostStarted(hostName, newHostId, publicKey);
159       onDone();
160     } else if (result == remoting.HostController.AsyncResult.CANCELLED) {
161       onStartError(remoting.Error.CANCELLED);
162     } else {
163       onStartError(remoting.Error.UNEXPECTED);
164     }
165   }
167   /**
168    * @param {string} hostName
169    * @param {string} publicKey
170    * @param {string} privateKey
171    * @param {?string} xmppLogin
172    * @param {?string} refreshToken
173    * @param {?string} clientBaseJid
174    * @param {string} hostSecretHash
175    */
176   function startHostWithHash(hostName, publicKey, privateKey, xmppLogin,
177                              refreshToken, clientBaseJid, hostSecretHash) {
178     var hostConfig = {
179       xmpp_login: xmppLogin,
180       oauth_refresh_token: refreshToken,
181       host_id: newHostId,
182       host_name: hostName,
183       host_secret_hash: hostSecretHash,
184       private_key: privateKey
185     };
186     var hostOwner = clientBaseJid;
187     var hostOwnerEmail = remoting.identity.getCachedEmail();
188     if (hostOwner != xmppLogin) {
189       hostConfig['host_owner'] = hostOwner;
190       if (hostOwnerEmail != hostOwner) {
191         hostConfig['host_owner_email'] = hostOwnerEmail;
192       }
193     }
194     that.hostDaemonFacade_.startDaemon(
195         hostConfig, consent, onStarted.bind(null, hostName, publicKey),
196         onStartError);
197   }
199   /**
200    * @param {string} hostName
201    * @param {string} publicKey
202    * @param {string} privateKey
203    * @param {string} email
204    * @param {string} refreshToken
205    * @param {string} clientBaseJid
206    */
207   function onClientBaseJid(
208       hostName, publicKey, privateKey, email, refreshToken, clientBaseJid) {
209     that.hostDaemonFacade_.getPinHash(
210         newHostId, hostPin,
211         startHostWithHash.bind(null, hostName, publicKey, privateKey,
212                                email, refreshToken, clientBaseJid),
213         onError);
214   }
216   /**
217    * @param {string} hostName
218    * @param {string} publicKey
219    * @param {string} privateKey
220    * @param {string} email
221    * @param {string} refreshToken
222    */
223   function onServiceAccountCredentials(
224       hostName, publicKey, privateKey, email, refreshToken) {
225     that.getClientBaseJid_(
226         onClientBaseJid.bind(
227             null, hostName, publicKey, privateKey, email, refreshToken),
228         onStartError);
229   }
231   /**
232    * @param {string} hostName
233    * @param {string} publicKey
234    * @param {string} privateKey
235    * @param {XMLHttpRequest} xhr
236    */
237   function onRegistered(
238       hostName, publicKey, privateKey, xhr) {
239     var success = (xhr.status == 200);
241     if (success) {
242       var result = base.jsonParseSafe(xhr.responseText);
243       if ('data' in result && 'authorizationCode' in result['data']) {
244         that.hostDaemonFacade_.getCredentialsFromAuthCode(
245             result['data']['authorizationCode'],
246             onServiceAccountCredentials.bind(
247                 null, hostName, publicKey, privateKey),
248             onError);
249       } else {
250         // No authorization code returned, use regular user credential flow.
251         that.hostDaemonFacade_.getPinHash(
252             newHostId, hostPin, startHostWithHash.bind(
253                 null, hostName, publicKey, privateKey,
254                 remoting.identity.getCachedEmail(),
255                 remoting.oauth2.getRefreshToken(),
256                 remoting.identity.getCachedEmail()),
257           onError);
258       }
259     } else {
260       console.log('Failed to register the host. Status: ' + xhr.status +
261                   ' response: ' + xhr.responseText);
262       onError(remoting.Error.REGISTRATION_FAILED);
263     }
264   }
266   /**
267    * @param {string} hostName
268    * @param {string} privateKey
269    * @param {string} publicKey
270    * @param {?string} hostClientId
271    * @param {string} oauthToken
272    */
273   function doRegisterHost(
274       hostName, privateKey, publicKey, hostClientId, oauthToken) {
275     var newHostDetails = { data: {
276        hostId: newHostId,
277        hostName: hostName,
278        publicKey: publicKey
279     } };
281     var registerHostUrl =
282         remoting.settings.DIRECTORY_API_BASE_URL + '/@me/hosts';
284     remoting.xhr.start({
285       method: 'POST',
286       url: remoting.settings.DIRECTORY_API_BASE_URL + '/@me/hosts',
287       urlParams: {
288         hostClientId: hostClientId
289       },
290       onDone: onRegistered.bind(null, hostName, publicKey, privateKey),
291       jsonContent: newHostDetails,
292       oauthToken: oauthToken
293     });
294   }
296   /**
297    * @param {string} hostName
298    * @param {string} privateKey
299    * @param {string} publicKey
300    * @param {string} hostClientId
301    */
302   function onHostClientId(
303       hostName, privateKey, publicKey, hostClientId) {
304     remoting.identity.getToken().then(
305         doRegisterHost.bind(
306             null, hostName, privateKey, publicKey, hostClientId),
307         remoting.Error.handler(onError));
308   }
310   /**
311    * @param {string} hostName
312    * @param {string} privateKey
313    * @param {string} publicKey
314    * @param {boolean} hasFeature
315    */
316   function onHasFeatureOAuthClient(
317       hostName, privateKey, publicKey, hasFeature) {
318     if (hasFeature) {
319       that.hostDaemonFacade_.getHostClientId(
320           onHostClientId.bind(null, hostName, privateKey, publicKey), onError);
321     } else {
322       remoting.identity.getToken().then(
323           doRegisterHost.bind(
324               null, hostName, privateKey, publicKey, null),
325           remoting.Error.handler(onError));
326     }
327   }
329   /**
330    * @param {string} hostName
331    * @param {string} privateKey
332    * @param {string} publicKey
333    */
334   function onKeyGenerated(hostName, privateKey, publicKey) {
335     that.hasFeature(
336         remoting.HostController.Feature.OAUTH_CLIENT,
337         onHasFeatureOAuthClient.bind(null, hostName, privateKey, publicKey));
338   }
340   /**
341    * @param {string} hostName
342    * @return {void} Nothing.
343    */
344   function startWithHostname(hostName) {
345     that.hostDaemonFacade_.generateKeyPair(onKeyGenerated.bind(null, hostName),
346                                          onError);
347   }
349   this.hostDaemonFacade_.getHostName(startWithHostname, onError);
353  * Stop the daemon process.
354  * @param {function():void} onDone Callback to be called when done.
355  * @param {function(remoting.Error):void} onError Callback to be called on
356  *     error.
357  * @return {void} Nothing.
358  */
359 remoting.HostController.prototype.stop = function(onDone, onError) {
360   /** @type {remoting.HostController} */
361   var that = this;
363   /** @param {string?} hostId The host id of the local host. */
364   function unregisterHost(hostId) {
365     if (hostId) {
366       remoting.HostList.unregisterHostById(hostId);
367     }
368     onDone();
369   }
371   /** @param {remoting.HostController.AsyncResult} result */
372   function onStopped(result) {
373     if (result == remoting.HostController.AsyncResult.OK) {
374       that.getLocalHostId(unregisterHost);
375     } else if (result == remoting.HostController.AsyncResult.CANCELLED) {
376       onError(remoting.Error.CANCELLED);
377     } else {
378       onError(remoting.Error.UNEXPECTED);
379     }
380   }
382   this.hostDaemonFacade_.stopDaemon(onStopped, onError);
386  * Check the host configuration is valid (non-null, and contains both host_id
387  * and xmpp_login keys).
388  * @param {Object} config The host configuration.
389  * @return {boolean} True if it is valid.
390  */
391 function isHostConfigValid_(config) {
392   return !!config && typeof config['host_id'] == 'string' &&
393       typeof config['xmpp_login'] == 'string';
397  * @param {string} newPin The new PIN to set
398  * @param {function():void} onDone Callback to be called when done.
399  * @param {function(remoting.Error):void} onError Callback to be called on
400  *     error.
401  * @return {void} Nothing.
402  */
403 remoting.HostController.prototype.updatePin = function(newPin, onDone,
404                                                        onError) {
405   /** @type {remoting.HostController} */
406   var that = this;
408   /** @param {remoting.HostController.AsyncResult} result */
409   function onConfigUpdated(result) {
410     if (result == remoting.HostController.AsyncResult.OK) {
411       onDone();
412     } else if (result == remoting.HostController.AsyncResult.CANCELLED) {
413       onError(remoting.Error.CANCELLED);
414     } else {
415       onError(remoting.Error.UNEXPECTED);
416     }
417   }
419   /** @param {string} pinHash */
420   function updateDaemonConfigWithHash(pinHash) {
421     var newConfig = {
422       host_secret_hash: pinHash
423     };
424     that.hostDaemonFacade_.updateDaemonConfig(newConfig, onConfigUpdated,
425                                             onError);
426   }
428   /** @param {Object} config */
429   function onConfig(config) {
430     if (!isHostConfigValid_(config)) {
431       onError(remoting.Error.UNEXPECTED);
432       return;
433     }
434     /** @type {string} */
435     var hostId = config['host_id'];
436     that.hostDaemonFacade_.getPinHash(
437         hostId, newPin, updateDaemonConfigWithHash, onError);
438   }
440   // TODO(sergeyu): When crbug.com/121518 is fixed: replace this call
441   // with an unprivileged version if that is necessary.
442   this.hostDaemonFacade_.getDaemonConfig(onConfig, onError);
446  * Get the state of the local host.
448  * @param {function(remoting.HostController.State):void} onDone Completion
449  *     callback.
450  */
451 remoting.HostController.prototype.getLocalHostState = function(onDone) {
452   /** @param {remoting.Error} error */
453   function onError(error) {
454     onDone((error == remoting.Error.MISSING_PLUGIN) ?
455                remoting.HostController.State.NOT_INSTALLED :
456                remoting.HostController.State.UNKNOWN);
457   }
458   this.hostDaemonFacade_.getDaemonState(onDone, onError);
462  * Get the id of the local host, or null if it is not registered.
464  * @param {function(string?):void} onDone Completion callback.
465  */
466 remoting.HostController.prototype.getLocalHostId = function(onDone) {
467   /** @type {remoting.HostController} */
468   var that = this;
469   /** @param {Object} config */
470   function onConfig(config) {
471     var hostId = null;
472     if (isHostConfigValid_(config)) {
473       hostId = /** @type {string} */ (config['host_id']);
474     }
475     onDone(hostId);
476   };
478   this.hostDaemonFacade_.getDaemonConfig(onConfig, function(error) {
479     onDone(null);
480   });
484  * Fetch the list of paired clients for this host.
486  * @param {function(Array<remoting.PairedClient>):void} onDone
487  * @param {function(remoting.Error):void} onError
488  * @return {void}
489  */
490 remoting.HostController.prototype.getPairedClients = function(onDone,
491                                                               onError) {
492   this.hostDaemonFacade_.getPairedClients(onDone, onError);
496  * Delete a single paired client.
498  * @param {string} client The client id of the pairing to delete.
499  * @param {function():void} onDone Completion callback.
500  * @param {function(remoting.Error):void} onError Error callback.
501  * @return {void}
502  */
503 remoting.HostController.prototype.deletePairedClient = function(
504     client, onDone, onError) {
505   this.hostDaemonFacade_.deletePairedClient(client, onDone, onError);
509  * Delete all paired clients.
511  * @param {function():void} onDone Completion callback.
512  * @param {function(remoting.Error):void} onError Error callback.
513  * @return {void}
514  */
515 remoting.HostController.prototype.clearPairedClients = function(
516     onDone, onError) {
517   this.hostDaemonFacade_.clearPairedClients(onDone, onError);
521  * Gets the host owner's base JID, used by the host for client authorization.
522  * In most cases this is the same as the owner's email address, but for
523  * non-Gmail accounts, it may be different.
525  * @private
526  * @param {function(string): void} onSuccess
527  * @param {function(remoting.Error): void} onError
528  */
529 remoting.HostController.prototype.getClientBaseJid_ = function(
530     onSuccess, onError) {
531   /** @type {remoting.SignalStrategy} */
532   var signalStrategy = null;
534   /** @param {remoting.SignalStrategy.State} state */
535   var onState = function(state) {
536     switch (state) {
537       case remoting.SignalStrategy.State.CONNECTED:
538         var jid = signalStrategy.getJid().split('/')[0].toLowerCase();
539         base.dispose(signalStrategy);
540         signalStrategy = null;
541         onSuccess(jid);
542         break;
544       case remoting.SignalStrategy.State.FAILED:
545         var error = signalStrategy.getError();
546         base.dispose(signalStrategy);
547         signalStrategy = null;
548         onError(error);
549         break;
550     }
551   };
553   signalStrategy = remoting.SignalStrategy.create();
554   signalStrategy.setStateChangedCallback(onState);
556   /** @param {string} token */
557   function connectSignalingWithToken(token) {
558     remoting.identity.getEmail().then(
559         connectSignalingWithTokenAndEmail.bind(null, token),
560         remoting.Error.handler(onError));
561   }
563   /**
564    * @param {string} token
565    * @param {string} email
566    */
567   function connectSignalingWithTokenAndEmail(token, email) {
568     signalStrategy.connect(
569         remoting.settings.XMPP_SERVER_FOR_CLIENT, email, token);
570   }
572   remoting.identity.getToken().then(
573       connectSignalingWithToken, remoting.Error.handler(onError));
576 /** @type {remoting.HostController} */
577 remoting.hostController = null;