Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / remoting / webapp / crd / js / host_daemon_facade.js
blob62ae4ee2f79eb66d30c8345393acbe01c5213648
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   /** @private {number} */
20   this.nextId_ = 0;
22   /** @private {Object<number, remoting.HostDaemonFacade.PendingReply>} */
23   this.pendingReplies_ = {};
25   /** @private {?Port} */
26   this.port_ = null;
28   /** @private {string} */
29   this.version_ = '';
31   /** @private {Array<remoting.HostController.Feature>} */
32   this.supportedFeatures_ = [];
34   /** @private {Array<function(boolean):void>} */
35   this.afterInitializationTasks_ = [];
37   /**
38    * A promise that fulfills when the daemon finishes initializing.
39    * It will be set to null when the promise fulfills.
40    * @private {Promise}
41    */
42   this.initializingPromise_ = null;
44   /** @private {!remoting.Error} */
45   this.error_ = remoting.Error.none();
47   /** @private */
48   this.onIncomingMessageCallback_ = this.onIncomingMessage_.bind(this);
50   /** @private */
51   this.onDisconnectCallback_ = this.onDisconnect_.bind(this);
53   /** @private */
54   this.debugMessageHandler_ =
55       new remoting.NativeMessageHostDebugMessageHandler();
57   this.initialize_();
60 /**
61  * @return {Promise} A promise that fulfills when the daemon finishes
62  *     initializing
63  * @private
64  */
65 remoting.HostDaemonFacade.prototype.initialize_ = function() {
66   if (!this.initializingPromise_) {
67     if (this.port_) {
68       return Promise.resolve();
69     }
71     /** @type {remoting.HostDaemonFacade} */
72     var that = this;
73     this.initializingPromise_ = this.connectNative_().then(function() {
74       that.initializingPromise_ = null;
75     }, function() {
76       that.initializingPromise_ = null;
77       throw new Error(that.error_);
78     });
79   }
80   return this.initializingPromise_;
83 /**
84  * Connects to the native messaging host and sends a hello message.
85  *
86  * @return {Promise} A promise that fulfills when the connection attempt
87  *     succeeds or fails.
88  * @private
89  */
90 remoting.HostDaemonFacade.prototype.connectNative_ = function() {
91   var that = this;
92   try {
93     this.port_ = chrome.runtime.connectNative(
94         'com.google.chrome.remote_desktop');
95     this.port_.onMessage.addListener(this.onIncomingMessageCallback_);
96     this.port_.onDisconnect.addListener(this.onDisconnectCallback_);
97     return this.postMessageInternal_({type: 'hello'}).then(function(reply) {
98       that.version_ = base.getStringAttr(reply, 'version');
99       // Old versions of the native messaging host do not return this list.
100       // Those versions default to the empty list of supported features.
101       that.supportedFeatures_ =
102           base.getArrayAttr(reply, 'supportedFeatures', []);
103     });
104   } catch (/** @type {*} */ err) {
105     console.log('Native Messaging initialization failed: ', err);
106     throw remoting.Error.unexpected();
107   }
111  * Type used for entries of |pendingReplies_| list.
113  * @param {string} type Type of the originating request.
114  * @param {!base.Deferred} deferred Used to communicate returns back
115  *     to the caller.
116  * @constructor
117  */
118 remoting.HostDaemonFacade.PendingReply = function(type, deferred) {
119   /** @const */
120   this.type = type;
122   /** @const */
123   this.deferred = deferred;
127  * @param {remoting.HostController.Feature} feature The feature to test for.
128  * @return {!Promise<boolean>} True if the implementation supports the
129  *     named feature.
130  */
131 remoting.HostDaemonFacade.prototype.hasFeature = function(feature) {
132   /** @type {remoting.HostDaemonFacade} */
133   var that = this;
134   return this.initialize_().then(function() {
135     return that.supportedFeatures_.indexOf(feature) >= 0;
136   }, function () {
137     return false;
138   });
142  * Initializes that the Daemon if necessary and posts the supplied message.
144  * @param {{type: string}} message The message to post.
145  * @return {!Promise<!Object>}
146  * @private
147  */
148 remoting.HostDaemonFacade.prototype.postMessage_ =
149     function(message) {
150   /** @type {remoting.HostDaemonFacade} */
151   var that = this;
152   return this.initialize_().then(function() {
153     return that.postMessageInternal_(message);
154   }, function() {
155     throw that.error_;
156   });
160  * Attaches a new ID to the supplied message, and posts it to the
161  * Native Messaging port, adding a Deferred object to the list of
162  * pending replies.  |message| should have its 'type' field set, and
163  * any other fields set depending on the message type.
165  * @param {{type: string}} message The message to post.
166  * @return {!Promise<!Object>}
167  * @private
168  */
169 remoting.HostDaemonFacade.prototype.postMessageInternal_ = function(message) {
170   var id = this.nextId_++;
171   message['id'] = id;
172   var deferred = new base.Deferred();
173   this.pendingReplies_[id] = new remoting.HostDaemonFacade.PendingReply(
174     message.type + 'Response', deferred);
175   this.port_.postMessage(message);
176   return deferred.promise();
180  * Handler for incoming Native Messages.
182  * @param {Object} message The received message.
183  * @return {void} Nothing.
184  * @private
185  */
186 remoting.HostDaemonFacade.prototype.onIncomingMessage_ = function(message) {
187   if (this.debugMessageHandler_.handleMessage(message)) {
188     return;
189   }
191   /** @type {number} */
192   var id = message['id'];
193   if (typeof(id) != 'number') {
194     console.error('NativeMessaging: missing or non-numeric id');
195     return;
196   }
197   var reply = this.pendingReplies_[id];
198   if (!reply) {
199     console.error('NativeMessaging: unexpected id: ', id);
200     return;
201   }
202   delete this.pendingReplies_[id];
204   try {
205     var type = base.getStringAttr(message, 'type');
206     if (type != reply.type) {
207       throw 'Expected reply type: ' + reply.type + ', got: ' + type;
208     }
209     reply.deferred.resolve(message);
210   } catch (/** @type {*} */ e) {
211     console.error('Error while processing native message', e);
212     reply.deferred.reject(remoting.Error.unexpected());
213   }
217  * @return {void} Nothing.
218  * @private
219  */
220 remoting.HostDaemonFacade.prototype.onDisconnect_ = function() {
221   console.error('Native Message port disconnected');
223   this.port_.onDisconnect.removeListener(this.onDisconnectCallback_);
224   this.port_.onMessage.removeListener(this.onIncomingMessageCallback_);
225   this.port_ = null;
227   // If initialization hasn't finished then assume that the port was
228   // disconnected because Native Messaging host is not installed.
229   this.error_ = this.initializingPromise_ ?
230       new remoting.Error(remoting.Error.Tag.MISSING_PLUGIN) :
231       remoting.Error.unexpected();
233   // Notify the error-handlers of any requests that are still outstanding.
234   var pendingReplies = this.pendingReplies_;
235   this.pendingReplies_ = {};
236   for (var id in pendingReplies) {
237     var num_id = parseInt(id, 10);
238     pendingReplies[num_id].deferred.reject(this.error_);
239   }
243  * Gets local hostname.
245  * @return {!Promise<string>}
246  */
247 remoting.HostDaemonFacade.prototype.getHostName = function() {
248   return this.postMessage_({type: 'getHostName'}).then(function(reply) {
249     return base.getStringAttr(reply, 'hostname');
250   });
254  * Calculates PIN hash value to be stored in the config, passing the resulting
255  * hash value base64-encoded to the callback.
257  * @param {string} hostId The host ID.
258  * @param {string} pin The PIN.
259  * @return {!Promise<string>}
260  */
261 remoting.HostDaemonFacade.prototype.getPinHash = function(hostId, pin) {
262   return this.postMessage_({
263       type: 'getPinHash',
264       hostId: hostId,
265       pin: pin
266   }).then(function(reply) {
267     return base.getStringAttr(reply, 'hash');
268   });
272  * Generates new key pair to use for the host. The specified callback is called
273  * when the key is generated. The key is returned in format understood by the
274  * host (PublicKeyInfo structure encoded with ASN.1 DER, and then BASE64).
276  * @return {!Promise<remoting.KeyPair>}
277  */
278 remoting.HostDaemonFacade.prototype.generateKeyPair = function() {
279   return this.postMessage_({type: 'generateKeyPair'}).then(function(reply) {
280     return {
281       privateKey: base.getStringAttr(reply, 'privateKey'),
282       publicKey: base.getStringAttr(reply, 'publicKey')
283     };
284   });
288  * Updates host config with the values specified in |config|. All
289  * fields that are not specified in |config| remain
290  * unchanged. Following parameters cannot be changed using this
291  * function: host_id, xmpp_login. Error is returned if |config|
292  * includes these parameters. Changes take effect before the callback
293  * is called.
295  * TODO(jrw): Consider conversion exceptions to AsyncResult values.
297  * @param {Object} config The new config parameters.
298  * @return {!Promise<remoting.HostController.AsyncResult>}
299  */
300 remoting.HostDaemonFacade.prototype.updateDaemonConfig = function(config) {
301   return this.postMessage_({
302       type: 'updateDaemonConfig',
303       config: config
304   }).then(function(reply) {
305     return remoting.HostController.AsyncResult.fromString(
306         base.getStringAttr(reply, 'result'));
307   });
311  * Loads daemon config. The config is passed as a JSON formatted string to the
312  * callback.
313  * @return {!Promise<Object>}
314  */
315 remoting.HostDaemonFacade.prototype.getDaemonConfig = function() {
316   return this.postMessage_({type: 'getDaemonConfig'}).then(function(reply) {
317     return base.getObjectAttr(reply, 'config');
318   });
322  * Retrieves daemon version. The version is returned as a dotted decimal
323  * string of the form major.minor.build.patch.
325  * @return {!Promise<string>}
326  */
327 remoting.HostDaemonFacade.prototype.getDaemonVersion = function() {
328   /** @type {remoting.HostDaemonFacade} */
329   var that = this;
330   return this.initialize_().then(function() {
331     return that.version_;
332   }, function() {
333     throw that.error_;
334   });
338  * Get the user's consent to crash reporting. The consent flags are passed to
339  * the callback as booleans: supported, allowed, set-by-policy.
341  * @return {!Promise<remoting.UsageStatsConsent>}
342  */
343 remoting.HostDaemonFacade.prototype.getUsageStatsConsent = function() {
344   return this.postMessage_({type: 'getUsageStatsConsent'}).
345       then(function(reply) {
346         return {
347           supported: base.getBooleanAttr(reply, 'supported'),
348           allowed: base.getBooleanAttr(reply, 'allowed'),
349           setByPolicy: base.getBooleanAttr(reply, 'setByPolicy')
350         };
351       });
355  * Starts the daemon process with the specified configuration.
357  * TODO(jrw): Consider conversion exceptions to AsyncResult values.
359  * @param {Object} config Host configuration.
360  * @param {boolean} consent Consent to report crash dumps.
361  * @return {!Promise<remoting.HostController.AsyncResult>}
362  */
363 remoting.HostDaemonFacade.prototype.startDaemon = function(config, consent) {
364   return this.postMessage_({
365       type: 'startDaemon',
366       config: config,
367       consent: consent
368   }).then(function(reply) {
369     return remoting.HostController.AsyncResult.fromString(
370         base.getStringAttr(reply, 'result'));
371   });
375  * Stops the daemon process.
377  * TODO(jrw): Consider conversion exceptions to AsyncResult values.
379  * @return {!Promise<remoting.HostController.AsyncResult>}
380  */
381 remoting.HostDaemonFacade.prototype.stopDaemon =
382     function() {
383   return this.postMessage_({type: 'stopDaemon'}).then(function(reply) {
384     return remoting.HostController.AsyncResult.fromString(
385         base.getStringAttr(reply, 'result'));
386   });
390  * Gets the installed/running state of the Host process.
392  * @return {!Promise<remoting.HostController.State>}
393  */
394 remoting.HostDaemonFacade.prototype.getDaemonState = function() {
395   return this.postMessage_({type: 'getDaemonState'}).then(function(reply) {
396     return remoting.HostController.State.fromString(
397         base.getStringAttr(reply, 'state'));
398  });
402  * Retrieves the list of paired clients.
404  * @return {!Promise<Array<remoting.PairedClient>>}
405  */
406 remoting.HostDaemonFacade.prototype.getPairedClients = function() {
407   return this.postMessage_({type: 'getPairedClients'}).then(function(reply) {
408     var pairedClients =remoting.PairedClient.convertToPairedClientArray(
409         reply['pairedClients']);
410     if (pairedClients != null) {
411       return pairedClients;
412     } else {
413       throw remoting.Error.unexpected('No paired clients!');
414     }
415   });
419  * Clears all paired clients from the registry.
421  * @return {!Promise<boolean>}
422  */
423 remoting.HostDaemonFacade.prototype.clearPairedClients = function() {
424   return this.postMessage_({type: 'clearPairedClients'}).then(function(reply) {
425     return base.getBooleanAttr(reply, 'result');
426   });
430  * Deletes a paired client referenced by client id.
432  * @param {string} client Client to delete.
433  * @return {!Promise<boolean>}
434  */
435 remoting.HostDaemonFacade.prototype.deletePairedClient = function(client) {
436   return this.postMessage_({
437     type: 'deletePairedClient',
438     clientId: client
439   }).then(function(reply) {
440     return base.getBooleanAttr(reply, 'result');
441   });
445  * Gets the API keys to obtain/use service account credentials.
447  * @return {!Promise<string>}
448  */
449 remoting.HostDaemonFacade.prototype.getHostClientId = function() {
450   return this.postMessage_({type: 'getHostClientId'}).then(function(reply) {
451     return base.getStringAttr(reply, 'clientId');
452   });
456  * @param {string} authorizationCode OAuth authorization code.
457  * @return {!Promise<{remoting.XmppCredentials}>}
458  */
459 remoting.HostDaemonFacade.prototype.getCredentialsFromAuthCode =
460     function(authorizationCode) {
461   return this.postMessage_({
462     type: 'getCredentialsFromAuthCode',
463     authorizationCode: authorizationCode
464   }).then(function(reply) {
465     var userEmail = base.getStringAttr(reply, 'userEmail');
466     var refreshToken = base.getStringAttr(reply, 'refreshToken');
467     if (userEmail && refreshToken) {
468       return {
469         userEmail: userEmail,
470         refreshToken: refreshToken
471       };
472     } else {
473       throw remoting.Error.unexpected('Missing userEmail or refreshToken');
474     }
475   });
479  * @param {string} authorizationCode OAuth authorization code.
480  * @return {!Promise<string>}
481  */
482 remoting.HostDaemonFacade.prototype.getRefreshTokenFromAuthCode =
483     function(authorizationCode) {
484   return this.postMessage_({
485     type: 'getRefreshTokenFromAuthCode',
486     authorizationCode: authorizationCode
487   }).then(function(reply) {
488     var refreshToken = base.getStringAttr(reply, 'refreshToken');
489     if (refreshToken) {
490       return refreshToken
491     } else {
492       throw remoting.Error.unexpected('Missing refreshToken');
493     }
494   });