Pipeline: Handle the case where Seek() is called after error happened.
[chromium-blink-merge.git] / remoting / webapp / host_daemon_facade.js
blob5b1567aaece2a4deb8b625df829a537c156b981b
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.extension.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   /** @private */
44   this.initializationFinished_ = false;
46   /** @type {remoting.Error} @private */
47   this.error_ = remoting.Error.NONE;
49   try {
50     this.port_ = chrome.runtime.connectNative(
51         'com.google.chrome.remote_desktop');
52     this.port_.onMessage.addListener(this.onIncomingMessage_.bind(this));
53     this.port_.onDisconnect.addListener(this.onDisconnect_.bind(this));
54     this.postMessage_({type: 'hello'},
55                       this.onInitialized_.bind(this, true),
56                       this.onInitialized_.bind(this, false));
57   } catch (err) {
58     console.log('Native Messaging initialization failed: ',
59                 /** @type {*} */ (err));
60     this.onInitialized_(false);
61   }
64 /**
65  * Type used for entries of |pendingReplies_| list.
66  *
67  * @param {string} type Type of the originating request.
68  * @param {function(...):void} onDone Response callback. Parameters depend on
69  *     the request type.
70  * @param {function(remoting.Error):void} onError Callback to call on error.
71  * @constructor
72  */
73 remoting.HostDaemonFacade.PendingReply = function(type, onDone, onError) {
74   this.type = type;
75   this.onDone = onDone;
76   this.onError = onError;
79 /**
80  * @param {boolean} success
81  * @return {void} Nothing.
82  * @private
83  */
84 remoting.HostDaemonFacade.prototype.onInitialized_ = function(success) {
85   this.initializationFinished_ = true;
86   var afterInitializationTasks = this.afterInitializationTasks_;
87   this.afterInitializationTasks_ = [];
88   for (var id in afterInitializationTasks) {
89     afterInitializationTasks[/** @type {number} */(id)](success);
90   }
93 /**
94  * @param {remoting.HostController.Feature} feature The feature to test for.
95  * @param {function(boolean):void} onDone Callback to return result.
96  * @return {boolean} True if the implementation supports the named feature.
97  */
98 remoting.HostDaemonFacade.prototype.hasFeature = function(feature, onDone) {
99   if (!this.port_) {
100     onDone(false);
101   } else if (this.initializationFinished_) {
102     onDone(this.supportedFeatures_.indexOf(feature) >= 0);
103   } else {
104     /** @type remoting.HostDaemonFacade */
105     var that = this;
106     this.afterInitializationTasks_.push(
107         /** @param {boolean} success */
108         function(success) {
109           onDone(that.supportedFeatures_.indexOf(feature) >= 0);
110         });
111   }
115  * Attaches a new ID to the supplied message, and posts it to the Native
116  * Messaging port, adding |onDone| to the list of pending replies.
117  * |message| should have its 'type' field set, and any other fields set
118  * depending on the message type.
120  * @param {{type: string}} message The message to post.
121  * @param {function(...):void} onDone The callback, if any, to be triggered
122  *     on response.
123  * @param {function(remoting.Error):void} onError Callback to call on error.
124  * @return {void} Nothing.
125  * @private
126  */
127 remoting.HostDaemonFacade.prototype.postMessage_ =
128     function(message, onDone, onError) {
129   if (!this.port_) {
130     onError(this.error_);
131     return;
132   }
133   var id = this.nextId_++;
134   message['id'] = id;
135   this.pendingReplies_[id] = new remoting.HostDaemonFacade.PendingReply(
136     message.type + 'Response', onDone, onError);
137   this.port_.postMessage(message);
141  * Handler for incoming Native Messages.
143  * @param {Object} message The received message.
144  * @return {void} Nothing.
145  * @private
146  */
147 remoting.HostDaemonFacade.prototype.onIncomingMessage_ = function(message) {
148   /** @type {number} */
149   var id = message['id'];
150   if (typeof(id) != 'number') {
151     console.error('NativeMessaging: missing or non-numeric id');
152     return;
153   }
154   var reply = this.pendingReplies_[id];
155   if (!reply) {
156     console.error('NativeMessaging: unexpected id: ', id);
157     return;
158   }
159   delete this.pendingReplies_[id];
161   try {
162     var type = getStringAttr(message, 'type');
163     if (type != reply.type) {
164       throw 'Expected reply type: ' + reply.type + ', got: ' + type;
165     }
167     this.handleIncomingMessage_(message, reply.onDone);
168   } catch (e) {
169     console.error('Error while processing native message' +
170                   /** @type {*} */ (e));
171     reply.onError(remoting.Error.UNEXPECTED);
172   }
176  * Handler for incoming Native Messages.
178  * @param {Object} message The received message.
179  * @param {function(...):void} onDone Function to call when we're done
180  *     processing the message.
181  * @return {void} Nothing.
182  * @private
183  */
184 remoting.HostDaemonFacade.prototype.handleIncomingMessage_ =
185     function(message, onDone) {
186   var type = getStringAttr(message, 'type');
188   switch (type) {
189     case 'helloResponse':
190       this.version_ = getStringAttr(message, 'version');
191       // Old versions of the native messaging host do not return this list.
192       // Those versions default to the empty list of supported features.
193       this.supportedFeatures_ = getArrayAttr(message, 'supportedFeatures', []);
194       onDone();
195       break;
197     case 'getHostNameResponse':
198       onDone(getStringAttr(message, 'hostname'));
199       break;
201     case 'getPinHashResponse':
202       onDone(getStringAttr(message, 'hash'));
203       break;
205     case 'generateKeyPairResponse':
206       var privateKey = getStringAttr(message, 'privateKey');
207       var publicKey = getStringAttr(message, 'publicKey');
208       onDone(privateKey, publicKey);
209       break;
211     case 'updateDaemonConfigResponse':
212       var result = remoting.HostController.AsyncResult.fromString(
213           getStringAttr(message, 'result'));
214       onDone(result);
215       break;
217     case 'getDaemonConfigResponse':
218       onDone(getObjectAttr(message, 'config'));
219       break;
221     case 'getUsageStatsConsentResponse':
222       var supported = getBooleanAttr(message, 'supported');
223       var allowed = getBooleanAttr(message, 'allowed');
224       var setByPolicy = getBooleanAttr(message, 'setByPolicy');
225       onDone(supported, allowed, setByPolicy);
226       break;
228     case 'startDaemonResponse':
229     case 'stopDaemonResponse':
230       var result = remoting.HostController.AsyncResult.fromString(
231           getStringAttr(message, 'result'));
232       onDone(result);
233       break;
235     case 'getDaemonStateResponse':
236       var state = remoting.HostController.State.fromString(
237         getStringAttr(message, 'state'));
238       onDone(state);
239       break;
241     case 'getPairedClientsResponse':
242       var pairedClients = remoting.PairedClient.convertToPairedClientArray(
243           message['pairedClients']);
244       if (pairedClients != null) {
245         onDone(pairedClients);
246       } else {
247         throw 'No paired clients!';
248       }
249       break;
251     case 'clearPairedClientsResponse':
252     case 'deletePairedClientResponse':
253       onDone(getBooleanAttr(message, 'result'));
254       break;
256     case 'getHostClientIdResponse':
257       onDone(getStringAttr(message, 'clientId'));
258       break;
260     case 'getCredentialsFromAuthCodeResponse':
261       var userEmail = getStringAttr(message, 'userEmail');
262       var refreshToken = getStringAttr(message, 'refreshToken');
263       if (userEmail && refreshToken) {
264         onDone(userEmail, refreshToken);
265       } else {
266         throw 'Missing userEmail or refreshToken';
267       }
268       break;
270     default:
271       throw 'Unexpected native message: ' + message;
272   }
276  * @return {void} Nothing.
277  * @private
278  */
279 remoting.HostDaemonFacade.prototype.onDisconnect_ = function() {
280   console.error('Native Message port disconnected');
282   this.port_ = null;
284   // If initialization hasn't finished then assume that the port was
285   // disconnected because Native Messaging host is not installed.
286   this.error_ = this.initializationFinished_ ? remoting.Error.UNEXPECTED :
287                                                remoting.Error.MISSING_PLUGIN;
289   // Notify the error-handlers of any requests that are still outstanding.
290   var pendingReplies = this.pendingReplies_;
291   this.pendingReplies_ = {};
292   for (var id in pendingReplies) {
293     pendingReplies[/** @type {number} */(id)].onError(this.error_);
294   }
298  * Gets local hostname.
300  * @param {function(string):void} onDone Callback to return result.
301  * @param {function(remoting.Error):void} onError Callback to call on error.
302  * @return {void} Nothing.
303  */
304 remoting.HostDaemonFacade.prototype.getHostName =
305     function(onDone, onError) {
306   this.postMessage_({type: 'getHostName'}, onDone, onError);
310  * Calculates PIN hash value to be stored in the config, passing the resulting
311  * hash value base64-encoded to the callback.
313  * @param {string} hostId The host ID.
314  * @param {string} pin The PIN.
315  * @param {function(string):void} onDone Callback to return result.
316  * @param {function(remoting.Error):void} onError Callback to call on error.
317  * @return {void} Nothing.
318  */
319 remoting.HostDaemonFacade.prototype.getPinHash =
320     function(hostId, pin, onDone, onError) {
321   this.postMessage_({
322       type: 'getPinHash',
323       hostId: hostId,
324       pin: pin
325   }, onDone, onError);
329  * Generates new key pair to use for the host. The specified callback is called
330  * when the key is generated. The key is returned in format understood by the
331  * host (PublicKeyInfo structure encoded with ASN.1 DER, and then BASE64).
333  * @param {function(string, string):void} onDone Callback to return result.
334  * @param {function(remoting.Error):void} onError Callback to call on error.
335  * @return {void} Nothing.
336  */
337 remoting.HostDaemonFacade.prototype.generateKeyPair =
338     function(onDone, onError) {
339   this.postMessage_({type: 'generateKeyPair'}, onDone, onError);
343  * Updates host config with the values specified in |config|. All
344  * fields that are not specified in |config| remain
345  * unchanged. Following parameters cannot be changed using this
346  * function: host_id, xmpp_login. Error is returned if |config|
347  * includes these parameters. Changes take effect before the callback
348  * is called.
350  * @param {Object} config The new config parameters.
351  * @param {function(remoting.HostController.AsyncResult):void} onDone
352  *     Callback to be called when finished.
353  * @param {function(remoting.Error):void} onError Callback to call on error.
354  * @return {void} Nothing.
355  */
356 remoting.HostDaemonFacade.prototype.updateDaemonConfig =
357     function(config, onDone, onError) {
358   this.postMessage_({
359       type: 'updateDaemonConfig',
360       config: config
361   }, onDone, onError);
365  * Loads daemon config. The config is passed as a JSON formatted string to the
366  * callback.
368  * @param {function(Object):void} onDone Callback to return result.
369  * @param {function(remoting.Error):void} onError Callback to call on error.
370  * @return {void} Nothing.
371  */
372 remoting.HostDaemonFacade.prototype.getDaemonConfig =
373     function(onDone, onError) {
374   this.postMessage_({type: 'getDaemonConfig'}, onDone, onError);
378  * Retrieves daemon version. The version is passed to onDone as a dotted decimal
379  * string of the form major.minor.build.patch.
380  * @param {function(string):void} onDone Callback to be called to return result.
381  * @param {function(remoting.Error):void} onError Callback to call on error.
382  * @return {void}
383  */
384 remoting.HostDaemonFacade.prototype.getDaemonVersion =
385     function(onDone, onError) {
386   if (!this.port_) {
387     onError(remoting.Error.UNEXPECTED);
388   } else if (this.initializationFinished_) {
389     onDone(this.version_);
390   } else {
391     /** @type remoting.HostDaemonFacade */
392     var that = this;
393     this.afterInitializationTasks_.push(
394         /** @param {boolean} success */
395         function(success) {
396           if (success) {
397             onDone(that.version_);
398           } else {
399             onError(that.error_);
400           }
401         });
402   }
406  * Get the user's consent to crash reporting. The consent flags are passed to
407  * the callback as booleans: supported, allowed, set-by-policy.
409  * @param {function(boolean, boolean, boolean):void} onDone Callback to return
410  *     result.
411  * @param {function(remoting.Error):void} onError Callback to call on error.
412  * @return {void} Nothing.
413  */
414 remoting.HostDaemonFacade.prototype.getUsageStatsConsent =
415     function(onDone, onError) {
416   this.postMessage_({type: 'getUsageStatsConsent'}, onDone, onError);
420  * Starts the daemon process with the specified configuration.
422  * @param {Object} config Host configuration.
423  * @param {boolean} consent Consent to report crash dumps.
424  * @param {function(remoting.HostController.AsyncResult):void} onDone
425  *     Callback to return result.
426  * @param {function(remoting.Error):void} onError Callback to call on error.
427  * @return {void} Nothing.
428  */
429 remoting.HostDaemonFacade.prototype.startDaemon =
430     function(config, consent, onDone, onError) {
431   this.postMessage_({
432       type: 'startDaemon',
433       config: config,
434       consent: consent
435   }, onDone, onError);
439  * Stops the daemon process.
441  * @param {function(remoting.HostController.AsyncResult):void} onDone
442  *     Callback to return result.
443  * @param {function(remoting.Error):void} onError Callback to call on error.
444  * @return {void} Nothing.
445  */
446 remoting.HostDaemonFacade.prototype.stopDaemon =
447     function(onDone, onError) {
448   this.postMessage_({type: 'stopDaemon'}, onDone, onError);
452  * Gets the installed/running state of the Host process.
454  * @param {function(remoting.HostController.State):void} onDone Callback to
455 *      return result.
456  * @param {function(remoting.Error):void} onError Callback to call on error.
457  * @return {void} Nothing.
458  */
459 remoting.HostDaemonFacade.prototype.getDaemonState =
460     function(onDone, onError) {
461   this.postMessage_({type: 'getDaemonState'}, onDone, onError);
465  * Retrieves the list of paired clients.
467  * @param {function(Array.<remoting.PairedClient>):void} onDone Callback to
468  *     return result.
469  * @param {function(remoting.Error):void} onError Callback to call on error.
470  */
471 remoting.HostDaemonFacade.prototype.getPairedClients =
472     function(onDone, onError) {
473   this.postMessage_({type: 'getPairedClients'}, onDone, onError);
477  * Clears all paired clients from the registry.
479  * @param {function(boolean):void} onDone Callback to be called when finished.
480  * @param {function(remoting.Error):void} onError Callback to call on error.
481  */
482 remoting.HostDaemonFacade.prototype.clearPairedClients =
483     function(onDone, onError) {
484   this.postMessage_({type: 'clearPairedClients'}, onDone, onError);
488  * Deletes a paired client referenced by client id.
490  * @param {string} client Client to delete.
491  * @param {function(boolean):void} onDone Callback to be called when finished.
492  * @param {function(remoting.Error):void} onError Callback to call on error.
493  */
494 remoting.HostDaemonFacade.prototype.deletePairedClient =
495     function(client, onDone, onError) {
496   this.postMessage_({
497     type: 'deletePairedClient',
498     clientId: client
499   }, onDone, onError);
503  * Gets the API keys to obtain/use service account credentials.
505  * @param {function(string):void} onDone Callback to return result.
506  * @param {function(remoting.Error):void} onError Callback to call on error.
507  * @return {void} Nothing.
508  */
509 remoting.HostDaemonFacade.prototype.getHostClientId =
510     function(onDone, onError) {
511   this.postMessage_({type: 'getHostClientId'}, onDone, onError);
516  * @param {string} authorizationCode OAuth authorization code.
517  * @param {function(string, string):void} onDone Callback to return result.
518  * @param {function(remoting.Error):void} onError Callback to call on error.
519  * @return {void} Nothing.
520  */
521 remoting.HostDaemonFacade.prototype.getCredentialsFromAuthCode =
522     function(authorizationCode, onDone, onError) {
523   this.postMessage_({
524     type: 'getCredentialsFromAuthCode',
525     authorizationCode: authorizationCode
526   }, onDone, onError);