Separate Simple Backend creation from initialization.
[chromium-blink-merge.git] / remoting / webapp / host_native_messaging.js
blobc0dff978c9d8ffea521451be90fd67efa10fc7d8
1 // Copyright 2013 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 components via Native Messaging.
8  */
10 'use strict';
12 /** @suppress {duplicate} */
13 var remoting = remoting || {};
15 /**
16  * @constructor
17  * @extends {remoting.HostPlugin}
18  */
19 remoting.HostNativeMessaging = function() {
20   /**
21    * @type {number}
22    * @private
23    */
24   this.nextId_ = 0;
26   /**
27    * @type {Object.<number, {callback:?function(...):void, type:string}>}
28    * @private
29    */
30   this.pendingReplies_ = {};
32   /** @type {?chrome.extension.Port} @private */
33   this.port_ = null;
35   /** @type {?function(boolean):void} @private */
36   this.onInitializedCallback_ = null;
38   /** @type {string} @private */
39   this.version_ = ''
42 /**
43  * Sets up connection to the Native Messaging host process and exchanges
44  * 'hello' messages. If Native Messaging is not available or the host
45  * process is not installed, this returns false to the callback.
46  *
47  * @param {function(boolean): void} onDone Called with the result of
48  *     initialization.
49  * @return {void} Nothing.
50  */
51 remoting.HostNativeMessaging.prototype.initialize = function(onDone) {
52   if (!chrome.runtime.connectNative) {
53     console.log('Native Messaging API not available');
54     onDone(false);
55     return;
56   }
58   // NativeMessaging API exists on Chrome 26.xxx but fails to notify
59   // onDisconnect in the case where the Host components are not installed. Need
60   // to blacklist these versions of Chrome.
61   var majorVersion = navigator.appVersion.match('Chrome/(\\d+)\.')[1];
62   if (!majorVersion || majorVersion <= 26) {
63     console.log('Native Messaging not supported on this version of Chrome');
64     onDone(false);
65     return;
66   }
68   try {
69     this.port_ = chrome.runtime.connectNative(
70         'com.google.chrome.remote_desktop');
71     this.port_.onMessage.addListener(this.onIncomingMessage_.bind(this));
72     this.port_.onDisconnect.addListener(this.onDisconnect_.bind(this));
73     this.postMessage_({type: 'hello'}, null);
74   } catch (err) {
75     console.log('Native Messaging initialization failed: ',
76                 /** @type {*} */ (err));
77     onDone(false);
78     return;
79   }
81   this.onInitializedCallback_ = onDone;
84 /**
85  * Verifies that |object| is of type |type|, logging an error if not.
86  *
87  * @param {string} name Name of the object, to be included in the error log.
88  * @param {*} object Object to test.
89  * @param {string} type Expected type of the object.
90  * @return {boolean} Result of test.
91  */
92 function checkType_(name, object, type) {
93   if (typeof(object) !== type) {
94     console.error('NativeMessaging: "', name, '" expected to be of type "',
95                   type, '", got: ', object);
96     return false;
97   }
98   return true;
102  * Returns |result| as an AsyncResult. If |result| is not valid, returns null
103  * and logs an error.
105  * @param {*} result
106  * @return {remoting.HostController.AsyncResult?} Converted result.
107  */
108 function asAsyncResult_(result) {
109   if (!checkType_('result', result, 'number')) {
110     return null;
111   }
112   for (var i in remoting.HostController.AsyncResult) {
113     if (remoting.HostController.AsyncResult[i] == result) {
114       return remoting.HostController.AsyncResult[i];
115     }
116   }
117   console.error('NativeMessaging: unexpected result code: ', result);
118   return null;
122  * Returns |result| as a HostController.State. If |result| is not valid,
123  * returns null and logs an error.
125  * @param {*} result
126  * @return {remoting.HostController.State?} Converted result.
127  */
128 function asHostState_(result) {
129   if (!checkType_('result', result, 'number')) {
130     return null;
131   }
132   for (var i in remoting.HostController.State) {
133     if (remoting.HostController.State[i] == result) {
134       return remoting.HostController.State[i];
135     }
136   }
137   console.error('NativeMessaging: unexpected result code: ', result);
138   return null;
142  * Attaches a new ID to the supplied message, and posts it to the Native
143  * Messaging port, adding |callback| to the list of pending replies.
144  * |message| should have its 'type' field set, and any other fields set
145  * depending on the message type.
147  * @param {{type: string}} message The message to post.
148  * @param {?function(...):void} callback The callback, if any, to be triggered
149  *     on response.
150  * @return {void} Nothing.
151  * @private
152  */
153 remoting.HostNativeMessaging.prototype.postMessage_ = function(message,
154                                                                callback) {
155   var id = this.nextId_++;
156   message['id'] = id;
157   this.pendingReplies_[id] = {
158     callback: callback,
159     type: message.type + 'Response'
160   }
161   this.port_.postMessage(message);
165  * Handler for incoming Native Messages.
167  * @param {Object} message The received message.
168  * @return {void} Nothing.
169  * @private
170  */
171 remoting.HostNativeMessaging.prototype.onIncomingMessage_ = function(message) {
172   /** @type {number} */
173   var id = message['id'];
174   if (typeof(id) != 'number') {
175     console.error('NativeMessaging: missing or non-numeric id');
176     return;
177   }
178   var reply = this.pendingReplies_[id];
179   if (!reply) {
180     console.error('NativeMessaging: unexpected id: ', id);
181     return;
182   }
183   delete this.pendingReplies_[id];
185   /** @type {string} */
186   var type = message['type'];
187   if (!checkType_('type', type, 'string')) {
188     return;
189   }
190   if (type != reply.type) {
191     console.error('NativeMessaging: expected reply type: ', reply.type,
192                   ', got: ', type);
193     return;
194   }
196   var callback = reply.callback;
198   // TODO(lambroslambrou): Errors here should be passed to an error-callback
199   // supplied by the caller of this interface.
200   switch (type) {
201     case 'helloResponse':
202       /** @type {string} */
203       var version = message['version'];
204       if (checkType_('version', version, 'string')) {
205         this.version_ = version;
206         if (this.onInitializedCallback_) {
207           this.onInitializedCallback_(true);
208           this.onInitializedCallback_ = null;
209         } else {
210           console.error('Unexpected helloResponse received.');
211         }
212       }
213       break;
215     case 'getHostNameResponse':
216       /** @type {*} */
217       var hostname = message['hostname'];
218       if (checkType_('hostname', hostname, 'string')) {
219         callback(hostname);
220       }
221       break;
223     case 'getPinHashResponse':
224       /** @type {*} */
225       var hash = message['hash'];
226       if (checkType_('hash', hash, 'string')) {
227         callback(hash);
228       }
229       break;
231     case 'generateKeyPairResponse':
232       /** @type {*} */
233       var private_key = message['private_key'];
234       /** @type {*} */
235       var public_key = message['public_key'];
236       if (checkType_('private_key', private_key, 'string') &&
237           checkType_('public_key', public_key, 'string')) {
238         callback(private_key, public_key);
239       }
240       break;
242     case 'updateDaemonConfigResponse':
243       var result = asAsyncResult_(message['result']);
244       if (result != null) {
245         callback(result);
246       }
247       break;
249     case 'getDaemonConfigResponse':
250       /** @type {*} */
251       var config = message['config'];
252       if (checkType_('config', config, 'string')) {
253         callback(config);
254       }
255       break;
257     case 'getUsageStatsConsentResponse':
258       /** @type {*} */
259       var supported = message['supported'];
260       /** @type {*} */
261       var allowed = message['allowed'];
262       /** @type {*} */
263       var set_by_policy = message['set_by_policy'];
264       if (checkType_('supported', supported, 'boolean') &&
265           checkType_('allowed', allowed, 'boolean') &&
266           checkType_('set_by_policy', set_by_policy, 'boolean')) {
267         callback(supported, allowed, set_by_policy);
268       }
269       break;
271     case 'startDaemonResponse':
272     case 'stopDaemonResponse':
273       var result = asAsyncResult_(message['result']);
274       if (result != null) {
275         callback(result);
276       }
277       break;
279     case 'getDaemonStateResponse':
280       var state = asHostState_(message['state']);
281       if (state != null) {
282         callback(state);
283       }
284       break;
286     default:
287       console.error('Unexpected native message: ', message);
288   }
292  * @return {void} Nothing.
293  * @private
294  */
295 remoting.HostNativeMessaging.prototype.onDisconnect_ = function() {
296   console.log('Native Message port disconnected');
297   if (this.onInitializedCallback_) {
298     this.onInitializedCallback_(false);
299     this.onInitializedCallback_ = null;
300   }
304  * @param {string} email The email address of the connector.
305  * @param {string} token The access token for the connector.
306  * @return {void} Nothing.
307  */
308 remoting.HostNativeMessaging.prototype.connect = function(email, token) {
309   console.error('NativeMessaging: connect() not implemented.');
312 /** @return {void} Nothing. */
313 remoting.HostNativeMessaging.prototype.disconnect = function() {
314   console.error('NativeMessaging: disconnect() not implemented.');
318  * @param {function(string):string} callback Pointer to chrome.i18n.getMessage.
319  * @return {void} Nothing.
320  */
321 remoting.HostNativeMessaging.prototype.localize = function(callback) {
322   console.error('NativeMessaging: localize() not implemented.');
326  * @param {function(string):void} callback Callback to be called with the
327  *     local hostname.
328  * @return {void} Nothing.
329  */
330 remoting.HostNativeMessaging.prototype.getHostName = function(callback) {
331   this.postMessage_({type: 'getHostName'}, callback);
335  * Calculates PIN hash value to be stored in the config, passing the resulting
336  * hash value base64-encoded to the callback.
338  * @param {string} hostId The host ID.
339  * @param {string} pin The PIN.
340  * @param {function(string):void} callback Callback.
341  * @return {void} Nothing.
342  */
343 remoting.HostNativeMessaging.prototype.getPinHash = function(hostId, pin,
344                                                              callback) {
345   this.postMessage_({
346       type: 'getPinHash',
347       hostId: hostId,
348       pin: pin
349   }, callback);
353  * Generates new key pair to use for the host. The specified callback is called
354  * when the key is generated. The key is returned in format understood by the
355  * host (PublicKeyInfo structure encoded with ASN.1 DER, and then BASE64).
357  * @param {function(string, string):void} callback Callback.
358  * @return {void} Nothing.
359  */
360 remoting.HostNativeMessaging.prototype.generateKeyPair = function(callback) {
361   this.postMessage_({type: 'generateKeyPair'}, callback);
365  * Updates host config with the values specified in |config|. All
366  * fields that are not specified in |config| remain
367  * unchanged. Following parameters cannot be changed using this
368  * function: host_id, xmpp_login. Error is returned if |config|
369  * includes these parameters. Changes take effect before the callback
370  * is called.
372  * @param {string} config The new config parameters, JSON encoded dictionary.
373  * @param {function(remoting.HostController.AsyncResult):void} callback
374  *     Callback to be called when finished.
375  * @return {void} Nothing.
376  */
377 remoting.HostNativeMessaging.prototype.updateDaemonConfig =
378     function(config, callback) {
379   this.postMessage_({
380       type: 'updateDaemonConfig',
381       config: config
382   }, callback);
386  * Loads daemon config. The config is passed as a JSON formatted string to the
387  * callback.
389  * @param {function(string):void} callback Callback.
390  * @return {void} Nothing.
391  */
392 remoting.HostNativeMessaging.prototype.getDaemonConfig = function(callback) {
393   this.postMessage_({type: 'getDaemonConfig'}, callback);
397  * Retrieves daemon version. The version is passed to the callback as a dotted
398  * decimal string of the form major.minor.build.patch.
400  * @param {function(string):void} callback Callback.
401  * @return {void} Nothing.
402  */
403 remoting.HostNativeMessaging.prototype.getDaemonVersion = function(callback) {
404   // Return the cached version from the 'hello' exchange. This interface needs
405   // to be asynchronous because the NPAPI version is, and this implements the
406   // same interface.
407   callback(this.version_);
411  * Get the user's consent to crash reporting. The consent flags are passed to
412  * the callback as booleans: supported, allowed, set-by-policy.
414  * @param {function(boolean, boolean, boolean):void} callback Callback.
415  * @return {void} Nothing.
416  */
417 remoting.HostNativeMessaging.prototype.getUsageStatsConsent =
418     function(callback) {
419   this.postMessage_({type: 'getUsageStatsConsent'}, callback);
423  * Starts the daemon process with the specified configuration.
425  * @param {string} config Host configuration.
426  * @param {boolean} consent Consent to report crash dumps.
427  * @param {function(remoting.HostController.AsyncResult):void} callback
428  *     Callback.
429  * @return {void} Nothing.
430  */
431 remoting.HostNativeMessaging.prototype.startDaemon = function(
432     config, consent, callback) {
433   this.postMessage_({
434       type: 'startDaemon',
435       config: config,
436       consent: consent
437   }, callback);
441  * Stops the daemon process.
443  * @param {function(remoting.HostController.AsyncResult):void} callback
444  *     Callback.
445  * @return {void} Nothing.
446  */
447 remoting.HostNativeMessaging.prototype.stopDaemon = function(callback) {
448   this.postMessage_({type: 'stopDaemon'}, callback);
452  * Gets the installed/running state of the Host process.
454  * @param {function(remoting.HostController.State):void} callback Callback.
455  * @return {void} Nothing.
456  */
457 remoting.HostNativeMessaging.prototype.getDaemonState = function(callback) {
458   this.postMessage_({type: 'getDaemonState'}, callback);