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.
7 * Class to communicate with the Host components via Native Messaging.
12 /** @suppress {duplicate} */
13 var remoting = remoting || {};
17 * @extends {remoting.HostPlugin}
19 remoting.HostNativeMessaging = function() {
27 * @type {Object.<number, {callback:?function(...):void, type:string}>}
30 this.pendingReplies_ = {};
32 /** @type {?chrome.extension.Port} @private */
35 /** @type {?function(boolean):void} @private */
36 this.onInitializedCallback_ = null;
38 /** @type {string} @private */
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.
47 * @param {function(boolean): void} onDone Called with the result of
49 * @return {void} Nothing.
51 remoting.HostNativeMessaging.prototype.initialize = function(onDone) {
52 if (!chrome.runtime.connectNative) {
53 console.log('Native Messaging API not available');
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');
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);
75 console.log('Native Messaging initialization failed: ',
76 /** @type {*} */ (err));
81 this.onInitializedCallback_ = onDone;
85 * Verifies that |object| is of type |type|, logging an error if not.
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.
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);
102 * Returns |result| as an AsyncResult. If |result| is not valid, returns null
106 * @return {remoting.HostController.AsyncResult?} Converted result.
108 function asAsyncResult_(result) {
109 if (!checkType_('result', result, 'number')) {
112 for (var i in remoting.HostController.AsyncResult) {
113 if (remoting.HostController.AsyncResult[i] == result) {
114 return remoting.HostController.AsyncResult[i];
117 console.error('NativeMessaging: unexpected result code: ', result);
122 * Returns |result| as a HostController.State. If |result| is not valid,
123 * returns null and logs an error.
126 * @return {remoting.HostController.State?} Converted result.
128 function asHostState_(result) {
129 if (!checkType_('result', result, 'number')) {
132 for (var i in remoting.HostController.State) {
133 if (remoting.HostController.State[i] == result) {
134 return remoting.HostController.State[i];
137 console.error('NativeMessaging: unexpected result code: ', result);
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
150 * @return {void} Nothing.
153 remoting.HostNativeMessaging.prototype.postMessage_ = function(message,
155 var id = this.nextId_++;
157 this.pendingReplies_[id] = {
159 type: message.type + 'Response'
161 this.port_.postMessage(message);
165 * Handler for incoming Native Messages.
167 * @param {Object} message The received message.
168 * @return {void} Nothing.
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');
178 var reply = this.pendingReplies_[id];
180 console.error('NativeMessaging: unexpected id: ', id);
183 delete this.pendingReplies_[id];
185 /** @type {string} */
186 var type = message['type'];
187 if (!checkType_('type', type, 'string')) {
190 if (type != reply.type) {
191 console.error('NativeMessaging: expected reply type: ', reply.type,
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.
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;
210 console.error('Unexpected helloResponse received.');
215 case 'getHostNameResponse':
217 var hostname = message['hostname'];
218 if (checkType_('hostname', hostname, 'string')) {
223 case 'getPinHashResponse':
225 var hash = message['hash'];
226 if (checkType_('hash', hash, 'string')) {
231 case 'generateKeyPairResponse':
233 var private_key = message['private_key'];
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);
242 case 'updateDaemonConfigResponse':
243 var result = asAsyncResult_(message['result']);
244 if (result != null) {
249 case 'getDaemonConfigResponse':
251 var config = message['config'];
252 if (checkType_('config', config, 'string')) {
257 case 'getUsageStatsConsentResponse':
259 var supported = message['supported'];
261 var allowed = message['allowed'];
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);
271 case 'startDaemonResponse':
272 case 'stopDaemonResponse':
273 var result = asAsyncResult_(message['result']);
274 if (result != null) {
279 case 'getDaemonStateResponse':
280 var state = asHostState_(message['state']);
287 console.error('Unexpected native message: ', message);
292 * @return {void} Nothing.
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;
304 * @param {string} email The email address of the connector.
305 * @param {string} token The access token for the connector.
306 * @return {void} Nothing.
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.
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
328 * @return {void} Nothing.
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.
343 remoting.HostNativeMessaging.prototype.getPinHash = function(hostId, pin,
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.
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
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.
377 remoting.HostNativeMessaging.prototype.updateDaemonConfig =
378 function(config, callback) {
380 type: 'updateDaemonConfig',
386 * Loads daemon config. The config is passed as a JSON formatted string to the
389 * @param {function(string):void} callback Callback.
390 * @return {void} Nothing.
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.
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
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.
417 remoting.HostNativeMessaging.prototype.getUsageStatsConsent =
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
429 * @return {void} Nothing.
431 remoting.HostNativeMessaging.prototype.startDaemon = function(
432 config, consent, callback) {
441 * Stops the daemon process.
443 * @param {function(remoting.HostController.AsyncResult):void} callback
445 * @return {void} Nothing.
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.
457 remoting.HostNativeMessaging.prototype.getDaemonState = function(callback) {
458 this.postMessage_({type: 'getDaemonState'}, callback);