Rewrite AndroidSyncSettings to be significantly simpler.
[chromium-blink-merge.git] / remoting / webapp / crd / js / host_daemon_facade.js
blob193a7562a609a18097f2e2a5e0d8d962592627dd
1 // Copyright 2014 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 /**
6  * @fileoverview
7  * Class to communicate with the host daemon via Native Messaging.
8  */
10 'use strict';
12 /** @suppress {duplicate} */
13 var remoting = remoting || {};
15 /**
16  * @constructor
17  */
18 remoting.HostDaemonFacade = function() {
19   /**
20    * @type {number}
21    * @private
22    */
23   this.nextId_ = 0;
25   /**
26    * @type {Object<number, remoting.HostDaemonFacade.PendingReply>}
27    * @private
28    */
29   this.pendingReplies_ = {};
31   /** @type {?chrome.runtime.Port} @private */
32   this.port_ = null;
34   /** @type {string} @private */
35   this.version_ = '';
37   /** @type {Array<remoting.HostController.Feature>} @private */
38   this.supportedFeatures_ = [];
40   /** @type {Array<function(boolean):void>} @private */
41   this.afterInitializationTasks_ = [];
43   /**
44    * A promise that fulfills when the daemon finishes initializing.
45    * It will be set to null when the promise fulfills.
46    * @type {Promise}
47    * @private
48    */
49   this.initializingPromise_ = null;
51   /** @type {remoting.Error} @private */
52   this.error_ = remoting.Error.NONE;
54   /** @private */
55   this.onIncomingMessageCallback_ = this.onIncomingMessage_.bind(this);
57   /** @private */
58   this.onDisconnectCallback_ = this.onDisconnect_.bind(this);
60   this.initialize_();
63 /**
64  * @return {Promise} A promise that fulfills when the daemon finishes
65  *     initializing
66  * @private
67  */
68 remoting.HostDaemonFacade.prototype.initialize_ = function() {
69   if (!this.initializingPromise_) {
70     if (this.port_) {
71       return Promise.resolve();
72     }
74     /** @type {remoting.HostDaemonFacade} */
75     var that = this;
76     this.initializingPromise_ = this.connectNative_().then(function() {
77       that.initializingPromise_ = null;
78     }, function() {
79       that.initializingPromise_ = null;
80       throw new Error(that.error_);
81     });
82   }
83   return this.initializingPromise_;
86 /**
87  * Connects to the native messaging host and sends a hello message.
88  *
89  * @return {Promise} A promise that fulfills when the connection attempt
90  *     succeeds or fails.
91  * @private
92  */
93 remoting.HostDaemonFacade.prototype.connectNative_ = function() {
94   /**
95    * @this {remoting.HostDaemonFacade}
96    * @param {function(?):void} resolve
97    * @param {function(*):void} reject
98    */
99   var connect = function(resolve, reject) {
100     try {
101       this.port_ = chrome.runtime.connectNative(
102           'com.google.chrome.remote_desktop');
103       this.port_.onMessage.addListener(this.onIncomingMessageCallback_);
104       this.port_.onDisconnect.addListener(this.onDisconnectCallback_);
105       this.postMessageInternal_({type: 'hello'}, resolve, reject);
106     } catch (/** @type {*} */ err) {
107       console.log('Native Messaging initialization failed: ', err);
108       reject(false);
109     }
110   };
112   return new Promise(connect.bind(this));
116  * Type used for entries of |pendingReplies_| list.
118  * @param {string} type Type of the originating request.
119  * @param {function(...):void} onDone Response callback. Parameters depend on
120  *     the request type.
121  * @param {function(remoting.Error):void} onError Callback to call on error.
122  * @constructor
123  */
124 remoting.HostDaemonFacade.PendingReply = function(type, onDone, onError) {
125   this.type = type;
126   this.onDone = onDone;
127   this.onError = onError;
131  * @param {remoting.HostController.Feature} feature The feature to test for.
132  * @param {function(boolean):void} onDone Callback to return result.
133  * @return {boolean} True if the implementation supports the named feature.
134  */
135 remoting.HostDaemonFacade.prototype.hasFeature = function(feature, onDone) {
136   /** @type {remoting.HostDaemonFacade} */
137   var that = this;
138   this.initialize_().then(function() {
139     onDone(that.supportedFeatures_.indexOf(feature) >= 0);
140   }, function (){
141     onDone(false);
142   });
146  * Initializes that the Daemon if necessary and posts the supplied message.
148  * @param {{type: string}} message The message to post.
149  * @param {function(...):void} onDone The callback, if any, to be triggered
150  *     on response.
151  * @param {function(remoting.Error):void} onError Callback to call on error.
152  * @private
153  */
154 remoting.HostDaemonFacade.prototype.postMessage_ =
155     function(message, onDone, onError) {
156   /** @type {remoting.HostDaemonFacade} */
157   var that = this;
158   this.initialize_().then(function() {
159     that.postMessageInternal_(message, onDone, onError);
160   }, function() {
161     onError(that.error_);
162   });
166  * Attaches a new ID to the supplied message, and posts it to the Native
167  * Messaging port, adding |onDone| to the list of pending replies.
168  * |message| should have its 'type' field set, and any other fields set
169  * depending on the message type.
171  * @param {{type: string}} message The message to post.
172  * @param {function(...):void} onDone The callback, if any, to be triggered
173  *     on response.
174  * @param {function(remoting.Error):void} onError Callback to call on error.
175  * @return {void} Nothing.
176  * @private
177  */
178 remoting.HostDaemonFacade.prototype.postMessageInternal_ =
179     function(message, onDone, onError) {
180   var id = this.nextId_++;
181   message['id'] = id;
182   this.pendingReplies_[id] = new remoting.HostDaemonFacade.PendingReply(
183     message.type + 'Response', onDone, onError);
184   this.port_.postMessage(message);
188  * Handler for incoming Native Messages.
190  * @param {Object} message The received message.
191  * @return {void} Nothing.
192  * @private
193  */
194 remoting.HostDaemonFacade.prototype.onIncomingMessage_ = function(message) {
195   /** @type {number} */
196   var id = message['id'];
197   if (typeof(id) != 'number') {
198     console.error('NativeMessaging: missing or non-numeric id');
199     return;
200   }
201   var reply = this.pendingReplies_[id];
202   if (!reply) {
203     console.error('NativeMessaging: unexpected id: ', id);
204     return;
205   }
206   delete this.pendingReplies_[id];
208   try {
209     var type = getStringAttr(message, 'type');
210     if (type != reply.type) {
211       throw 'Expected reply type: ' + reply.type + ', got: ' + type;
212     }
214     this.handleIncomingMessage_(message, reply.onDone);
215   } catch (/** @type {*} */ e) {
216     console.error('Error while processing native message' + e);
217     reply.onError(remoting.Error.UNEXPECTED);
218   }
222  * Handler for incoming Native Messages.
224  * @param {Object} message The received message.
225  * @param {function(...):void} onDone Function to call when we're done
226  *     processing the message.
227  * @return {void} Nothing.
228  * @private
229  */
230 remoting.HostDaemonFacade.prototype.handleIncomingMessage_ =
231     function(message, onDone) {
232   var type = getStringAttr(message, 'type');
234   switch (type) {
235     case 'helloResponse':
236       this.version_ = getStringAttr(message, 'version');
237       // Old versions of the native messaging host do not return this list.
238       // Those versions default to the empty list of supported features.
239       this.supportedFeatures_ = getArrayAttr(message, 'supportedFeatures', []);
240       onDone();
241       break;
243     case 'getHostNameResponse':
244       onDone(getStringAttr(message, 'hostname'));
245       break;
247     case 'getPinHashResponse':
248       onDone(getStringAttr(message, 'hash'));
249       break;
251     case 'generateKeyPairResponse':
252       var privateKey = getStringAttr(message, 'privateKey');
253       var publicKey = getStringAttr(message, 'publicKey');
254       onDone(privateKey, publicKey);
255       break;
257     case 'updateDaemonConfigResponse':
258       var result = remoting.HostController.AsyncResult.fromString(
259           getStringAttr(message, 'result'));
260       onDone(result);
261       break;
263     case 'getDaemonConfigResponse':
264       onDone(getObjectAttr(message, 'config'));
265       break;
267     case 'getUsageStatsConsentResponse':
268       var supported = getBooleanAttr(message, 'supported');
269       var allowed = getBooleanAttr(message, 'allowed');
270       var setByPolicy = getBooleanAttr(message, 'setByPolicy');
271       onDone(supported, allowed, setByPolicy);
272       break;
274     case 'startDaemonResponse':
275     case 'stopDaemonResponse':
276       var result = remoting.HostController.AsyncResult.fromString(
277           getStringAttr(message, 'result'));
278       onDone(result);
279       break;
281     case 'getDaemonStateResponse':
282       var state = remoting.HostController.State.fromString(
283         getStringAttr(message, 'state'));
284       onDone(state);
285       break;
287     case 'getPairedClientsResponse':
288       var pairedClients = remoting.PairedClient.convertToPairedClientArray(
289           message['pairedClients']);
290       if (pairedClients != null) {
291         onDone(pairedClients);
292       } else {
293         throw 'No paired clients!';
294       }
295       break;
297     case 'clearPairedClientsResponse':
298     case 'deletePairedClientResponse':
299       onDone(getBooleanAttr(message, 'result'));
300       break;
302     case 'getHostClientIdResponse':
303       onDone(getStringAttr(message, 'clientId'));
304       break;
306     case 'getCredentialsFromAuthCodeResponse':
307       var userEmail = getStringAttr(message, 'userEmail');
308       var refreshToken = getStringAttr(message, 'refreshToken');
309       if (userEmail && refreshToken) {
310         onDone(userEmail, refreshToken);
311       } else {
312         throw 'Missing userEmail or refreshToken';
313       }
314       break;
316     default:
317       throw 'Unexpected native message: ' + message;
318   }
322  * @return {void} Nothing.
323  * @private
324  */
325 remoting.HostDaemonFacade.prototype.onDisconnect_ = function() {
326   console.error('Native Message port disconnected');
328   this.port_.onDisconnect.removeListener(this.onDisconnectCallback_);
329   this.port_.onMessage.removeListener(this.onIncomingMessageCallback_);
330   this.port_ = null;
332   // If initialization hasn't finished then assume that the port was
333   // disconnected because Native Messaging host is not installed.
334   this.error_ = this.initializingPromise_ ? remoting.Error.MISSING_PLUGIN :
335                                             remoting.Error.UNEXPECTED;
337   // Notify the error-handlers of any requests that are still outstanding.
338   var pendingReplies = this.pendingReplies_;
339   this.pendingReplies_ = {};
340   for (var id in pendingReplies) {
341     var num_id = parseInt(id, 10);
342     pendingReplies[num_id].onError(this.error_);
343   }
347  * Gets local hostname.
349  * @param {function(string):void} onDone Callback to return result.
350  * @param {function(remoting.Error):void} onError Callback to call on error.
351  * @return {void} Nothing.
352  */
353 remoting.HostDaemonFacade.prototype.getHostName =
354     function(onDone, onError) {
355   this.postMessage_({type: 'getHostName'}, onDone, onError);
359  * Calculates PIN hash value to be stored in the config, passing the resulting
360  * hash value base64-encoded to the callback.
362  * @param {string} hostId The host ID.
363  * @param {string} pin The PIN.
364  * @param {function(string):void} onDone Callback to return result.
365  * @param {function(remoting.Error):void} onError Callback to call on error.
366  * @return {void} Nothing.
367  */
368 remoting.HostDaemonFacade.prototype.getPinHash =
369     function(hostId, pin, onDone, onError) {
370   this.postMessage_({
371       type: 'getPinHash',
372       hostId: hostId,
373       pin: pin
374   }, onDone, onError);
378  * Generates new key pair to use for the host. The specified callback is called
379  * when the key is generated. The key is returned in format understood by the
380  * host (PublicKeyInfo structure encoded with ASN.1 DER, and then BASE64).
382  * @param {function(string, string):void} onDone Callback to return result.
383  * @param {function(remoting.Error):void} onError Callback to call on error.
384  * @return {void} Nothing.
385  */
386 remoting.HostDaemonFacade.prototype.generateKeyPair =
387     function(onDone, onError) {
388   this.postMessage_({type: 'generateKeyPair'}, onDone, onError);
392  * Updates host config with the values specified in |config|. All
393  * fields that are not specified in |config| remain
394  * unchanged. Following parameters cannot be changed using this
395  * function: host_id, xmpp_login. Error is returned if |config|
396  * includes these parameters. Changes take effect before the callback
397  * is called.
399  * @param {Object} config The new config parameters.
400  * @param {function(remoting.HostController.AsyncResult):void} onDone
401  *     Callback to be called when finished.
402  * @param {function(remoting.Error):void} onError Callback to call on error.
403  * @return {void} Nothing.
404  */
405 remoting.HostDaemonFacade.prototype.updateDaemonConfig =
406     function(config, onDone, onError) {
407   this.postMessage_({
408       type: 'updateDaemonConfig',
409       config: config
410   }, onDone, onError);
414  * Loads daemon config. The config is passed as a JSON formatted string to the
415  * callback.
417  * @param {function(Object):void} onDone Callback to return result.
418  * @param {function(remoting.Error):void} onError Callback to call on error.
419  * @return {void} Nothing.
420  */
421 remoting.HostDaemonFacade.prototype.getDaemonConfig =
422     function(onDone, onError) {
423   this.postMessage_({type: 'getDaemonConfig'}, onDone, onError);
427  * Retrieves daemon version. The version is passed to onDone as a dotted decimal
428  * string of the form major.minor.build.patch.
430  * @param {function(string):void} onDone Callback to be called to return result.
431  * @param {function(remoting.Error):void} onError Callback to call on error.
432  * @return {void}
433  */
434 remoting.HostDaemonFacade.prototype.getDaemonVersion =
435     function(onDone, onError) {
436   /** @type {remoting.HostDaemonFacade} */
437   var that = this;
438   this.initialize_().then(function() {
439     onDone(that.version_);
440   }, function() {
441     onError(that.error_);
442   });
446  * Get the user's consent to crash reporting. The consent flags are passed to
447  * the callback as booleans: supported, allowed, set-by-policy.
449  * @param {function(boolean, boolean, boolean):void} onDone Callback to return
450  *     result.
451  * @param {function(remoting.Error):void} onError Callback to call on error.
452  * @return {void} Nothing.
453  */
454 remoting.HostDaemonFacade.prototype.getUsageStatsConsent =
455     function(onDone, onError) {
456   this.postMessage_({type: 'getUsageStatsConsent'}, onDone, onError);
460  * Starts the daemon process with the specified configuration.
462  * @param {Object} config Host configuration.
463  * @param {boolean} consent Consent to report crash dumps.
464  * @param {function(remoting.HostController.AsyncResult):void} onDone
465  *     Callback to return result.
466  * @param {function(remoting.Error):void} onError Callback to call on error.
467  * @return {void} Nothing.
468  */
469 remoting.HostDaemonFacade.prototype.startDaemon =
470     function(config, consent, onDone, onError) {
471   this.postMessage_({
472       type: 'startDaemon',
473       config: config,
474       consent: consent
475   }, onDone, onError);
479  * Stops the daemon process.
481  * @param {function(remoting.HostController.AsyncResult):void} onDone
482  *     Callback to return result.
483  * @param {function(remoting.Error):void} onError Callback to call on error.
484  * @return {void} Nothing.
485  */
486 remoting.HostDaemonFacade.prototype.stopDaemon =
487     function(onDone, onError) {
488   this.postMessage_({type: 'stopDaemon'}, onDone, onError);
492  * Gets the installed/running state of the Host process.
494  * @param {function(remoting.HostController.State):void} onDone Callback to
495 *      return result.
496  * @param {function(remoting.Error):void} onError Callback to call on error.
497  * @return {void} Nothing.
498  */
499 remoting.HostDaemonFacade.prototype.getDaemonState =
500     function(onDone, onError) {
501   this.postMessage_({type: 'getDaemonState'}, onDone, onError);
505  * Retrieves the list of paired clients.
507  * @param {function(Array<remoting.PairedClient>):void} onDone Callback to
508  *     return result.
509  * @param {function(remoting.Error):void} onError Callback to call on error.
510  */
511 remoting.HostDaemonFacade.prototype.getPairedClients =
512     function(onDone, onError) {
513   this.postMessage_({type: 'getPairedClients'}, onDone, onError);
517  * Clears all paired clients from the registry.
519  * @param {function(boolean):void} onDone Callback to be called when finished.
520  * @param {function(remoting.Error):void} onError Callback to call on error.
521  */
522 remoting.HostDaemonFacade.prototype.clearPairedClients =
523     function(onDone, onError) {
524   this.postMessage_({type: 'clearPairedClients'}, onDone, onError);
528  * Deletes a paired client referenced by client id.
530  * @param {string} client Client to delete.
531  * @param {function(boolean):void} onDone Callback to be called when finished.
532  * @param {function(remoting.Error):void} onError Callback to call on error.
533  */
534 remoting.HostDaemonFacade.prototype.deletePairedClient =
535     function(client, onDone, onError) {
536   this.postMessage_({
537     type: 'deletePairedClient',
538     clientId: client
539   }, onDone, onError);
543  * Gets the API keys to obtain/use service account credentials.
545  * @param {function(string):void} onDone Callback to return result.
546  * @param {function(remoting.Error):void} onError Callback to call on error.
547  * @return {void} Nothing.
548  */
549 remoting.HostDaemonFacade.prototype.getHostClientId =
550     function(onDone, onError) {
551   this.postMessage_({type: 'getHostClientId'}, onDone, onError);
556  * @param {string} authorizationCode OAuth authorization code.
557  * @param {function(string, string):void} onDone Callback to return result.
558  * @param {function(remoting.Error):void} onError Callback to call on error.
559  * @return {void} Nothing.
560  */
561 remoting.HostDaemonFacade.prototype.getCredentialsFromAuthCode =
562     function(authorizationCode, onDone, onError) {
563   this.postMessage_({
564     type: 'getCredentialsFromAuthCode',
565     authorizationCode: authorizationCode
566   }, onDone, onError);