Updating XTBs based on .GRDs from branch master
[chromium-blink-merge.git] / remoting / webapp / crd / js / host_daemon_facade.js
blob544abd39683d617c787e344caa5695f963b32a6a
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   this.initialize_();
56 /**
57  * @return {Promise} A promise that fulfills when the daemon finishes
58  *     initializing
59  * @private
60  */
61 remoting.HostDaemonFacade.prototype.initialize_ = function() {
62   if (!this.initializingPromise_) {
63     if (this.port_) {
64       return Promise.resolve();
65     }
67     /** @type {remoting.HostDaemonFacade} */
68     var that = this;
69     this.initializingPromise_ = this.connectNative_().then(function() {
70       that.initializingPromise_ = null;
71     }, function() {
72       that.initializingPromise_ = null;
73       throw new Error(that.error_);
74     });
75   }
76   return this.initializingPromise_;
79 /**
80  * Connects to the native messaging host and sends a hello message.
81  *
82  * @return {Promise} A promise that fulfills when the connection attempt
83  *     succeeds or fails.
84  * @private
85  */
86 remoting.HostDaemonFacade.prototype.connectNative_ = function() {
87   var that = this;
88   try {
89     this.port_ = chrome.runtime.connectNative(
90         'com.google.chrome.remote_desktop');
91     this.port_.onMessage.addListener(this.onIncomingMessageCallback_);
92     this.port_.onDisconnect.addListener(this.onDisconnectCallback_);
93     return this.postMessageInternal_({type: 'hello'}).then(function(reply) {
94       that.version_ = base.getStringAttr(reply, 'version');
95       // Old versions of the native messaging host do not return this list.
96       // Those versions default to the empty list of supported features.
97       that.supportedFeatures_ =
98           base.getArrayAttr(reply, 'supportedFeatures', []);
99     });
100   } catch (/** @type {*} */ err) {
101     console.log('Native Messaging initialization failed: ', err);
102     throw remoting.Error.unexpected();
103   }
107  * Type used for entries of |pendingReplies_| list.
109  * @param {string} type Type of the originating request.
110  * @param {!base.Deferred} deferred Used to communicate returns back
111  *     to the caller.
112  * @constructor
113  */
114 remoting.HostDaemonFacade.PendingReply = function(type, deferred) {
115   /** @const */
116   this.type = type;
118   /** @const */
119   this.deferred = deferred;
123  * @param {remoting.HostController.Feature} feature The feature to test for.
124  * @return {!Promise<boolean>} True if the implementation supports the
125  *     named feature.
126  */
127 remoting.HostDaemonFacade.prototype.hasFeature = function(feature) {
128   /** @type {remoting.HostDaemonFacade} */
129   var that = this;
130   return this.initialize_().then(function() {
131     return that.supportedFeatures_.indexOf(feature) >= 0;
132   }, function () {
133     return false;
134   });
138  * Initializes that the Daemon if necessary and posts the supplied message.
140  * @param {{type: string}} message The message to post.
141  * @return {!Promise<!Object>}
142  * @private
143  */
144 remoting.HostDaemonFacade.prototype.postMessage_ =
145     function(message) {
146   /** @type {remoting.HostDaemonFacade} */
147   var that = this;
148   return this.initialize_().then(function() {
149     return that.postMessageInternal_(message);
150   }, function() {
151     throw that.error_;
152   });
156  * Attaches a new ID to the supplied message, and posts it to the
157  * Native Messaging port, adding a Deferred object to the list of
158  * pending replies.  |message| should have its 'type' field set, and
159  * any other fields set depending on the message type.
161  * @param {{type: string}} message The message to post.
162  * @return {!Promise<!Object>}
163  * @private
164  */
165 remoting.HostDaemonFacade.prototype.postMessageInternal_ = function(message) {
166   var id = this.nextId_++;
167   message['id'] = id;
168   var deferred = new base.Deferred();
169   this.pendingReplies_[id] = new remoting.HostDaemonFacade.PendingReply(
170     message.type + 'Response', deferred);
171   this.port_.postMessage(message);
172   return deferred.promise();
176  * Handler for incoming Native Messages.
178  * @param {Object} message The received message.
179  * @return {void} Nothing.
180  * @private
181  */
182 remoting.HostDaemonFacade.prototype.onIncomingMessage_ = function(message) {
183   /** @type {number} */
184   var id = message['id'];
185   if (typeof(id) != 'number') {
186     console.error('NativeMessaging: missing or non-numeric id');
187     return;
188   }
189   var reply = this.pendingReplies_[id];
190   if (!reply) {
191     console.error('NativeMessaging: unexpected id: ', id);
192     return;
193   }
194   delete this.pendingReplies_[id];
196   try {
197     var type = base.getStringAttr(message, 'type');
198     if (type != reply.type) {
199       throw 'Expected reply type: ' + reply.type + ', got: ' + type;
200     }
201     reply.deferred.resolve(message);
202   } catch (/** @type {*} */ e) {
203     console.error('Error while processing native message', e);
204     reply.deferred.reject(remoting.Error.unexpected());
205   }
209  * @return {void} Nothing.
210  * @private
211  */
212 remoting.HostDaemonFacade.prototype.onDisconnect_ = function() {
213   console.error('Native Message port disconnected');
215   this.port_.onDisconnect.removeListener(this.onDisconnectCallback_);
216   this.port_.onMessage.removeListener(this.onIncomingMessageCallback_);
217   this.port_ = null;
219   // If initialization hasn't finished then assume that the port was
220   // disconnected because Native Messaging host is not installed.
221   this.error_ = this.initializingPromise_ ?
222       new remoting.Error(remoting.Error.Tag.MISSING_PLUGIN) :
223       remoting.Error.unexpected();
225   // Notify the error-handlers of any requests that are still outstanding.
226   var pendingReplies = this.pendingReplies_;
227   this.pendingReplies_ = {};
228   for (var id in pendingReplies) {
229     var num_id = parseInt(id, 10);
230     pendingReplies[num_id].deferred.reject(this.error_);
231   }
235  * Gets local hostname.
237  * @return {!Promise<string>}
238  */
239 remoting.HostDaemonFacade.prototype.getHostName = function() {
240   return this.postMessage_({type: 'getHostName'}).then(function(reply) {
241     return base.getStringAttr(reply, 'hostname');
242   });
246  * Calculates PIN hash value to be stored in the config, passing the resulting
247  * hash value base64-encoded to the callback.
249  * @param {string} hostId The host ID.
250  * @param {string} pin The PIN.
251  * @return {!Promise<string>}
252  */
253 remoting.HostDaemonFacade.prototype.getPinHash = function(hostId, pin) {
254   return this.postMessage_({
255       type: 'getPinHash',
256       hostId: hostId,
257       pin: pin
258   }).then(function(reply) {
259     return base.getStringAttr(reply, 'hash');
260   });
264  * Generates new key pair to use for the host. The specified callback is called
265  * when the key is generated. The key is returned in format understood by the
266  * host (PublicKeyInfo structure encoded with ASN.1 DER, and then BASE64).
268  * @return {!Promise<remoting.KeyPair>}
269  */
270 remoting.HostDaemonFacade.prototype.generateKeyPair = function() {
271   return this.postMessage_({type: 'generateKeyPair'}).then(function(reply) {
272     return {
273       privateKey: base.getStringAttr(reply, 'privateKey'),
274       publicKey: base.getStringAttr(reply, 'publicKey')
275     };
276   });
280  * Updates host config with the values specified in |config|. All
281  * fields that are not specified in |config| remain
282  * unchanged. Following parameters cannot be changed using this
283  * function: host_id, xmpp_login. Error is returned if |config|
284  * includes these parameters. Changes take effect before the callback
285  * is called.
287  * TODO(jrw): Consider conversion exceptions to AsyncResult values.
289  * @param {Object} config The new config parameters.
290  * @return {!Promise<remoting.HostController.AsyncResult>}
291  */
292 remoting.HostDaemonFacade.prototype.updateDaemonConfig = function(config) {
293   return this.postMessage_({
294       type: 'updateDaemonConfig',
295       config: config
296   }).then(function(reply) {
297     return remoting.HostController.AsyncResult.fromString(
298         base.getStringAttr(reply, 'result'));
299   });
303  * Loads daemon config. The config is passed as a JSON formatted string to the
304  * callback.
305  * @return {!Promise<Object>}
306  */
307 remoting.HostDaemonFacade.prototype.getDaemonConfig = function() {
308   return this.postMessage_({type: 'getDaemonConfig'}).then(function(reply) {
309     return base.getObjectAttr(reply, 'config');
310   });
314  * Retrieves daemon version. The version is returned as a dotted decimal
315  * string of the form major.minor.build.patch.
317  * @return {!Promise<string>}
318  */
319 remoting.HostDaemonFacade.prototype.getDaemonVersion = function() {
320   /** @type {remoting.HostDaemonFacade} */
321   var that = this;
322   return this.initialize_().then(function() {
323     return that.version_;
324   }, function() {
325     throw that.error_;
326   });
330  * Get the user's consent to crash reporting. The consent flags are passed to
331  * the callback as booleans: supported, allowed, set-by-policy.
333  * @return {!Promise<remoting.UsageStatsConsent>}
334  */
335 remoting.HostDaemonFacade.prototype.getUsageStatsConsent = function() {
336   return this.postMessage_({type: 'getUsageStatsConsent'}).
337       then(function(reply) {
338         return {
339           supported: base.getBooleanAttr(reply, 'supported'),
340           allowed: base.getBooleanAttr(reply, 'allowed'),
341           setByPolicy: base.getBooleanAttr(reply, 'setByPolicy')
342         };
343       });
347  * Starts the daemon process with the specified configuration.
349  * TODO(jrw): Consider conversion exceptions to AsyncResult values.
351  * @param {Object} config Host configuration.
352  * @param {boolean} consent Consent to report crash dumps.
353  * @return {!Promise<remoting.HostController.AsyncResult>}
354  */
355 remoting.HostDaemonFacade.prototype.startDaemon = function(config, consent) {
356   return this.postMessage_({
357       type: 'startDaemon',
358       config: config,
359       consent: consent
360   }).then(function(reply) {
361     return remoting.HostController.AsyncResult.fromString(
362         base.getStringAttr(reply, 'result'));
363   });
367  * Stops the daemon process.
369  * TODO(jrw): Consider conversion exceptions to AsyncResult values.
371  * @return {!Promise<remoting.HostController.AsyncResult>}
372  */
373 remoting.HostDaemonFacade.prototype.stopDaemon =
374     function() {
375   return this.postMessage_({type: 'stopDaemon'}).then(function(reply) {
376     return remoting.HostController.AsyncResult.fromString(
377         base.getStringAttr(reply, 'result'));
378   });
382  * Gets the installed/running state of the Host process.
384  * @return {!Promise<remoting.HostController.State>}
385  */
386 remoting.HostDaemonFacade.prototype.getDaemonState = function() {
387   return this.postMessage_({type: 'getDaemonState'}).then(function(reply) {
388     return remoting.HostController.State.fromString(
389         base.getStringAttr(reply, 'state'));
390  });
394  * Retrieves the list of paired clients.
396  * @return {!Promise<Array<remoting.PairedClient>>}
397  */
398 remoting.HostDaemonFacade.prototype.getPairedClients = function() {
399   return this.postMessage_({type: 'getPairedClients'}).then(function(reply) {
400     var pairedClients =remoting.PairedClient.convertToPairedClientArray(
401         reply['pairedClients']);
402     if (pairedClients != null) {
403       return pairedClients;
404     } else {
405       throw remoting.Error.unexpected('No paired clients!');
406     }
407   });
411  * Clears all paired clients from the registry.
413  * @return {!Promise<boolean>}
414  */
415 remoting.HostDaemonFacade.prototype.clearPairedClients = function() {
416   return this.postMessage_({type: 'clearPairedClients'}).then(function(reply) {
417     return base.getBooleanAttr(reply, 'result');
418   });
422  * Deletes a paired client referenced by client id.
424  * @param {string} client Client to delete.
425  * @return {!Promise<boolean>}
426  */
427 remoting.HostDaemonFacade.prototype.deletePairedClient = function(client) {
428   return this.postMessage_({
429     type: 'deletePairedClient',
430     clientId: client
431   }).then(function(reply) {
432     return base.getBooleanAttr(reply, 'result');
433   });
437  * Gets the API keys to obtain/use service account credentials.
439  * @return {!Promise<string>}
440  */
441 remoting.HostDaemonFacade.prototype.getHostClientId = function() {
442   return this.postMessage_({type: 'getHostClientId'}).then(function(reply) {
443     return base.getStringAttr(reply, 'clientId');
444   });
448  * @param {string} authorizationCode OAuth authorization code.
449  * @return {!Promise<{remoting.XmppCredentials}>}
450  */
451 remoting.HostDaemonFacade.prototype.getCredentialsFromAuthCode =
452     function(authorizationCode) {
453   return this.postMessage_({
454     type: 'getCredentialsFromAuthCode',
455     authorizationCode: authorizationCode
456   }).then(function(reply) {
457     var userEmail = base.getStringAttr(reply, 'userEmail');
458     var refreshToken = base.getStringAttr(reply, 'refreshToken');
459     if (userEmail && refreshToken) {
460       return {
461         userEmail: userEmail,
462         refreshToken: refreshToken
463       };
464     } else {
465       throw remoting.Error.unexpected('Missing userEmail or refreshToken');
466     }
467   });
471  * @param {string} authorizationCode OAuth authorization code.
472  * @return {!Promise<string>}
473  */
474 remoting.HostDaemonFacade.prototype.getRefreshTokenFromAuthCode =
475     function(authorizationCode) {
476   return this.postMessage_({
477     type: 'getRefreshTokenFromAuthCode',
478     authorizationCode: authorizationCode
479   }).then(function(reply) {
480     var refreshToken = base.getStringAttr(reply, 'refreshToken');
481     if (refreshToken) {
482       return refreshToken
483     } else {
484       throw remoting.Error.unexpected('Missing refreshToken');
485     }
486   });