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
|| {};
18 remoting
.HostNativeMessaging = function() {
26 * @type {Object.<number, remoting.HostNativeMessaging.PendingReply>}
29 this.pendingReplies_
= {};
31 /** @type {?chrome.extension.Port} @private */
34 /** @type {string} @private */
37 /** @type {Array.<remoting.HostController.Feature>} @private */
38 this.supportedFeatures_
= [];
42 * Type used for entries of |pendingReplies_| list.
44 * @param {string} type Type of the originating request.
45 * @param {?function(...):void} onDone The callback, if any, to be triggered
46 * on response. The actual parameters depend on the original request type.
47 * @param {function(remoting.Error):void} onError The callback to be triggered
51 remoting
.HostNativeMessaging
.PendingReply = function(type
, onDone
, onError
) {
54 this.onError
= onError
;
58 * Sets up connection to the Native Messaging host process and exchanges
59 * 'hello' messages. If Native Messaging is not available or the host
60 * process is not installed, this returns false to the callback.
62 * @param {function(): void} onDone Called after successful initialization.
63 * @param {function(remoting.Error): void} onError Called if initialization
65 * @return {void} Nothing.
67 remoting
.HostNativeMessaging
.prototype.initialize = function(onDone
, onError
) {
68 if (!chrome
.runtime
.connectNative
) {
69 console
.log('Native Messaging API not available');
70 onError(remoting
.Error
.UNEXPECTED
);
74 // NativeMessaging API exists on Chrome 26.xxx but fails to notify
75 // onDisconnect in the case where the Host components are not installed. Need
76 // to blacklist these versions of Chrome.
77 var majorVersion
= navigator
.appVersion
.match('Chrome/(\\d+)\.')[1];
78 if (!majorVersion
|| majorVersion
<= 26) {
79 console
.log('Native Messaging not supported on this version of Chrome');
80 onError(remoting
.Error
.UNEXPECTED
);
85 this.port_
= chrome
.runtime
.connectNative(
86 'com.google.chrome.remote_desktop');
87 this.port_
.onMessage
.addListener(this.onIncomingMessage_
.bind(this));
88 this.port_
.onDisconnect
.addListener(this.onDisconnect_
.bind(this));
89 this.postMessage_({type
: 'hello'}, onDone
,
90 onError
.bind(null, remoting
.Error
.UNEXPECTED
));
92 console
.log('Native Messaging initialization failed: ',
93 /** @type {*} */ (err
));
94 onError(remoting
.Error
.UNEXPECTED
);
100 * Verifies that |object| is of type |type|, logging an error if not.
102 * @param {string} name Name of the object, to be included in the error log.
103 * @param {*} object Object to test.
104 * @param {string} type Expected type of the object.
105 * @return {boolean} Result of test.
107 function checkType_(name
, object
, type
) {
108 if (typeof(object
) !== type
) {
109 console
.error('NativeMessaging: "' + name
+ '" expected to be of type "' +
110 type
+ '", got: ' + object
);
117 * Returns |result| as an AsyncResult. If |result| is not valid, returns null
121 * @return {remoting.HostController.AsyncResult?} Converted result.
123 function asAsyncResult_(result
) {
124 if (!checkType_('result', result
, 'string')) {
127 if (!remoting
.HostController
.AsyncResult
.hasOwnProperty(result
)) {
128 console
.error('NativeMessaging: unexpected result code: ', result
);
131 return remoting
.HostController
.AsyncResult
[result
];
135 * Returns |result| as a HostController.State. If |result| is not valid,
136 * returns null and logs an error.
139 * @return {remoting.HostController.State?} Converted result.
141 function asHostState_(result
) {
142 if (!checkType_('result', result
, 'string')) {
145 if (!remoting
.HostController
.State
.hasOwnProperty(result
)) {
146 console
.error('NativeMessaging: unexpected result code: ', result
);
149 return remoting
.HostController
.State
[result
];
153 * @param {remoting.HostController.Feature} feature The feature to test for.
154 * @return {boolean} True if the implementation supports the named feature.
156 remoting
.HostNativeMessaging
.prototype.hasFeature = function(feature
) {
157 return this.supportedFeatures_
.indexOf(feature
) >= 0;
161 * Attaches a new ID to the supplied message, and posts it to the Native
162 * Messaging port, adding |onDone| to the list of pending replies.
163 * |message| should have its 'type' field set, and any other fields set
164 * depending on the message type.
166 * @param {{type: string}} message The message to post.
167 * @param {?function(...):void} onDone The callback, if any, to be triggered
169 * @param {function(remoting.Error):void} onError The callback to be triggered
171 * @return {void} Nothing.
174 remoting
.HostNativeMessaging
.prototype.postMessage_
=
175 function(message
, onDone
, onError
) {
176 var id
= this.nextId_
++;
178 this.pendingReplies_
[id
] = new remoting
.HostNativeMessaging
.PendingReply(
179 message
.type
+ 'Response', onDone
, onError
);
180 this.port_
.postMessage(message
);
184 * Handler for incoming Native Messages.
186 * @param {Object} message The received message.
187 * @return {void} Nothing.
190 remoting
.HostNativeMessaging
.prototype.onIncomingMessage_ = function(message
) {
191 /** @type {number} */
192 var id
= message
['id'];
193 if (typeof(id
) != 'number') {
194 console
.error('NativeMessaging: missing or non-numeric id');
197 var reply
= this.pendingReplies_
[id
];
199 console
.error('NativeMessaging: unexpected id: ', id
);
202 delete this.pendingReplies_
[id
];
204 var onDone
= reply
.onDone
;
205 var onError
= reply
.onError
;
207 /** @type {string} */
208 var type
= message
['type'];
209 if (!checkType_('type', type
, 'string')) {
210 onError(remoting
.Error
.UNEXPECTED
);
213 if (type
!= reply
.type
) {
214 console
.error('NativeMessaging: expected reply type: ', reply
.type
,
216 onError(remoting
.Error
.UNEXPECTED
);
221 case 'helloResponse':
222 /** @type {string} */
223 var version
= message
['version'];
224 if (checkType_('version', version
, 'string')) {
225 this.version_
= version
;
226 if (message
['supportedFeatures'] instanceof Array
) {
227 this.supportedFeatures_
= message
['supportedFeatures'];
229 // Old versions of the native messaging host do not return this list.
230 // Those versions don't support any new feature.
231 this.supportedFeatures_
= [];
235 onError(remoting
.Error
.UNEXPECTED
);
239 case 'getHostNameResponse':
241 var hostname
= message
['hostname'];
242 if (checkType_('hostname', hostname
, 'string')) {
245 onError(remoting
.Error
.UNEXPECTED
);
249 case 'getPinHashResponse':
251 var hash
= message
['hash'];
252 if (checkType_('hash', hash
, 'string')) {
255 onError(remoting
.Error
.UNEXPECTED
);
259 case 'generateKeyPairResponse':
261 var privateKey
= message
['privateKey'];
263 var publicKey
= message
['publicKey'];
264 if (checkType_('privateKey', privateKey
, 'string') &&
265 checkType_('publicKey', publicKey
, 'string')) {
266 onDone(privateKey
, publicKey
);
268 onError(remoting
.Error
.UNEXPECTED
);
272 case 'updateDaemonConfigResponse':
273 var result
= asAsyncResult_(message
['result']);
274 if (result
!= null) {
277 onError(remoting
.Error
.UNEXPECTED
);
281 case 'getDaemonConfigResponse':
283 var config
= message
['config'];
284 if (checkType_('config', config
, 'object')) {
287 onError(remoting
.Error
.UNEXPECTED
);
291 case 'getUsageStatsConsentResponse':
293 var supported
= message
['supported'];
295 var allowed
= message
['allowed'];
297 var setByPolicy
= message
['setByPolicy'];
298 if (checkType_('supported', supported
, 'boolean') &&
299 checkType_('allowed', allowed
, 'boolean') &&
300 checkType_('setByPolicy', setByPolicy
, 'boolean')) {
301 onDone(supported
, allowed
, setByPolicy
);
303 onError(remoting
.Error
.UNEXPECTED
);
307 case 'startDaemonResponse':
308 case 'stopDaemonResponse':
309 var result
= asAsyncResult_(message
['result']);
310 if (result
!= null) {
313 onError(remoting
.Error
.UNEXPECTED
);
317 case 'getDaemonStateResponse':
318 var state
= asHostState_(message
['state']);
322 onError(remoting
.Error
.UNEXPECTED
);
326 case 'getPairedClientsResponse':
327 var pairedClients
= remoting
.PairedClient
.convertToPairedClientArray(
328 message
['pairedClients']);
329 if (pairedClients
!= null) {
330 onDone(pairedClients
);
332 onError(remoting
.Error
.UNEXPECTED
);
336 case 'clearPairedClientsResponse':
337 case 'deletePairedClientResponse':
338 /** @type {boolean} */
339 var success
= message
['result'];
340 if (checkType_('success', success
, 'boolean')) {
343 onError(remoting
.Error
.UNEXPECTED
);
347 case 'getHostClientIdResponse':
348 /** @type {string} */
349 var clientId
= message
['clientId'];
350 if (checkType_('clientId', clientId
, 'string')) {
353 onError(remoting
.Error
.UNEXPECTED
);
357 case 'getCredentialsFromAuthCodeResponse':
358 /** @type {string} */
359 var userEmail
= message
['userEmail'];
360 /** @type {string} */
361 var refreshToken
= message
['refreshToken'];
362 if (checkType_('userEmail', userEmail
, 'string') && userEmail
&&
363 checkType_('refreshToken', refreshToken
, 'string') && refreshToken
) {
364 onDone(userEmail
, refreshToken
);
366 onError(remoting
.Error
.UNEXPECTED
);
371 console
.error('Unexpected native message: ', message
);
372 onError(remoting
.Error
.UNEXPECTED
);
377 * @return {void} Nothing.
380 remoting
.HostNativeMessaging
.prototype.onDisconnect_ = function() {
381 console
.error('Native Message port disconnected');
383 // Notify the error-handlers of any requests that are still outstanding.
384 for (var id
in this.pendingReplies_
) {
385 this.pendingReplies_
[/** @type {number} */(id
)].onError(
386 remoting
.Error
.UNEXPECTED
);
388 this.pendingReplies_
= {};
392 * @param {function(string):void} onDone Callback to be called with the
394 * @param {function(remoting.Error):void} onError The callback to be triggered
396 * @return {void} Nothing.
398 remoting
.HostNativeMessaging
.prototype.getHostName
=
399 function(onDone
, onError
) {
400 this.postMessage_({type
: 'getHostName'}, onDone
, onError
);
404 * Calculates PIN hash value to be stored in the config, passing the resulting
405 * hash value base64-encoded to the callback.
407 * @param {string} hostId The host ID.
408 * @param {string} pin The PIN.
409 * @param {function(string):void} onDone Callback.
410 * @param {function(remoting.Error):void} onError The callback to be triggered
412 * @return {void} Nothing.
414 remoting
.HostNativeMessaging
.prototype.getPinHash
=
415 function(hostId
, pin
, onDone
, onError
) {
424 * Generates new key pair to use for the host. The specified callback is called
425 * when the key is generated. The key is returned in format understood by the
426 * host (PublicKeyInfo structure encoded with ASN.1 DER, and then BASE64).
428 * @param {function(string, string):void} onDone Callback.
429 * @param {function(remoting.Error):void} onError The callback to be triggered
431 * @return {void} Nothing.
433 remoting
.HostNativeMessaging
.prototype.generateKeyPair
=
434 function(onDone
, onError
) {
435 this.postMessage_({type
: 'generateKeyPair'}, onDone
, onError
);
439 * Updates host config with the values specified in |config|. All
440 * fields that are not specified in |config| remain
441 * unchanged. Following parameters cannot be changed using this
442 * function: host_id, xmpp_login. Error is returned if |config|
443 * includes these parameters. Changes take effect before the callback
446 * @param {Object} config The new config parameters.
447 * @param {function(remoting.HostController.AsyncResult):void} onDone
448 * Callback to be called when finished.
449 * @param {function(remoting.Error):void} onError The callback to be triggered
451 * @return {void} Nothing.
453 remoting
.HostNativeMessaging
.prototype.updateDaemonConfig
=
454 function(config
, onDone
, onError
) {
456 type
: 'updateDaemonConfig',
462 * Loads daemon config. The config is passed as a JSON formatted string to the
465 * @param {function(Object):void} onDone Callback.
466 * @param {function(remoting.Error):void} onError The callback to be triggered
468 * @return {void} Nothing.
470 remoting
.HostNativeMessaging
.prototype.getDaemonConfig
=
471 function(onDone
, onError
) {
472 this.postMessage_({type
: 'getDaemonConfig'}, onDone
, onError
);
476 * Retrieves daemon version. The version is returned as a dotted decimal string
477 * of the form major.minor.build.patch.
478 * @return {string} The daemon version, or the empty string if not available.
480 remoting
.HostNativeMessaging
.prototype.getDaemonVersion = function() {
481 // Return the cached version from the 'hello' exchange.
482 return this.version_
;
486 * Get the user's consent to crash reporting. The consent flags are passed to
487 * the callback as booleans: supported, allowed, set-by-policy.
489 * @param {function(boolean, boolean, boolean):void} onDone Callback.
490 * @param {function(remoting.Error):void} onError The callback to be triggered
492 * @return {void} Nothing.
494 remoting
.HostNativeMessaging
.prototype.getUsageStatsConsent
=
495 function(onDone
, onError
) {
496 this.postMessage_({type
: 'getUsageStatsConsent'}, onDone
, onError
);
500 * Starts the daemon process with the specified configuration.
502 * @param {Object} config Host configuration.
503 * @param {boolean} consent Consent to report crash dumps.
504 * @param {function(remoting.HostController.AsyncResult):void} onDone
506 * @param {function(remoting.Error):void} onError The callback to be triggered
508 * @return {void} Nothing.
510 remoting
.HostNativeMessaging
.prototype.startDaemon
=
511 function(config
, consent
, onDone
, onError
) {
520 * Stops the daemon process.
522 * @param {function(remoting.HostController.AsyncResult):void} onDone
524 * @param {function(remoting.Error):void} onError The callback to be triggered
526 * @return {void} Nothing.
528 remoting
.HostNativeMessaging
.prototype.stopDaemon
=
529 function(onDone
, onError
) {
530 this.postMessage_({type
: 'stopDaemon'}, onDone
, onError
);
534 * Gets the installed/running state of the Host process.
536 * @param {function(remoting.HostController.State):void} onDone Callback.
537 * @param {function(remoting.Error):void} onError The callback to be triggered
539 * @return {void} Nothing.
541 remoting
.HostNativeMessaging
.prototype.getDaemonState
=
542 function(onDone
, onError
) {
543 this.postMessage_({type
: 'getDaemonState'}, onDone
, onError
);
547 * Retrieves the list of paired clients.
549 * @param {function(Array.<remoting.PairedClient>):void} onDone Callback to be
550 * called with the result.
551 * @param {function(remoting.Error):void} onError Callback to be triggered
554 remoting
.HostNativeMessaging
.prototype.getPairedClients
=
555 function(onDone
, onError
) {
556 this.postMessage_({type
: 'getPairedClients'}, onDone
, onError
);
560 * Clears all paired clients from the registry.
562 * @param {function(boolean):void} onDone Callback to be called when finished.
563 * @param {function(remoting.Error):void} onError Callback to be triggered
566 remoting
.HostNativeMessaging
.prototype.clearPairedClients
=
567 function(onDone
, onError
) {
568 this.postMessage_({type
: 'clearPairedClients'}, onDone
, onError
);
572 * Deletes a paired client referenced by client id.
574 * @param {string} client Client to delete.
575 * @param {function(boolean):void} onDone Callback to be called when finished.
576 * @param {function(remoting.Error):void} onError Callback to be triggered
579 remoting
.HostNativeMessaging
.prototype.deletePairedClient
=
580 function(client
, onDone
, onError
) {
582 type
: 'deletePairedClient',
588 * Gets the API keys to obtain/use service account credentials.
590 * @param {function(string):void} onDone Callback.
591 * @param {function(remoting.Error):void} onError The callback to be triggered
593 * @return {void} Nothing.
595 remoting
.HostNativeMessaging
.prototype.getHostClientId
=
596 function(onDone
, onError
) {
597 this.postMessage_({type
: 'getHostClientId'}, onDone
, onError
);
602 * @param {string} authorizationCode OAuth authorization code.
603 * @param {function(string, string):void} onDone Callback.
604 * @param {function(remoting.Error):void} onError The callback to be triggered
606 * @return {void} Nothing.
608 remoting
.HostNativeMessaging
.prototype.getCredentialsFromAuthCode
=
609 function(authorizationCode
, onDone
, onError
) {
611 type
: 'getCredentialsFromAuthCode',
612 authorizationCode
: authorizationCode