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.
7 * Class to communicate with the host daemon via Native Messaging.
12 /** @suppress {duplicate} */
13 var remoting
= remoting
|| {};
18 remoting
.HostDaemonFacade = function() {
19 /** @private {number} */
22 /** @private {Object<number, remoting.HostDaemonFacade.PendingReply>} */
23 this.pendingReplies_
= {};
25 /** @private {?Port} */
28 /** @private {string} */
31 /** @private {Array<remoting.HostController.Feature>} */
32 this.supportedFeatures_
= [];
34 /** @private {Array<function(boolean):void>} */
35 this.afterInitializationTasks_
= [];
38 * A promise that fulfills when the daemon finishes initializing.
39 * It will be set to null when the promise fulfills.
42 this.initializingPromise_
= null;
44 /** @private {!remoting.Error} */
45 this.error_
= remoting
.Error
.none();
48 this.onIncomingMessageCallback_
= this.onIncomingMessage_
.bind(this);
51 this.onDisconnectCallback_
= this.onDisconnect_
.bind(this);
57 * @return {Promise} A promise that fulfills when the daemon finishes
61 remoting
.HostDaemonFacade
.prototype.initialize_ = function() {
62 if (!this.initializingPromise_
) {
64 return Promise
.resolve();
67 /** @type {remoting.HostDaemonFacade} */
69 this.initializingPromise_
= this.connectNative_().then(function() {
70 that
.initializingPromise_
= null;
72 that
.initializingPromise_
= null;
73 throw new Error(that
.error_
);
76 return this.initializingPromise_
;
80 * Connects to the native messaging host and sends a hello message.
82 * @return {Promise} A promise that fulfills when the connection attempt
86 remoting
.HostDaemonFacade
.prototype.connectNative_ = function() {
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', []);
100 } catch (/** @type {*} */ err
) {
101 console
.log('Native Messaging initialization failed: ', err
);
102 throw remoting
.Error
.unexpected();
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
114 remoting
.HostDaemonFacade
.PendingReply = function(type
, deferred
) {
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
127 remoting
.HostDaemonFacade
.prototype.hasFeature = function(feature
) {
128 /** @type {remoting.HostDaemonFacade} */
130 return this.initialize_().then(function() {
131 return that
.supportedFeatures_
.indexOf(feature
) >= 0;
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>}
144 remoting
.HostDaemonFacade
.prototype.postMessage_
=
146 /** @type {remoting.HostDaemonFacade} */
148 return this.initialize_().then(function() {
149 return that
.postMessageInternal_(message
);
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>}
165 remoting
.HostDaemonFacade
.prototype.postMessageInternal_ = function(message
) {
166 var id
= this.nextId_
++;
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.
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');
189 var reply
= this.pendingReplies_
[id
];
191 console
.error('NativeMessaging: unexpected id: ', id
);
194 delete this.pendingReplies_
[id
];
197 var type
= base
.getStringAttr(message
, 'type');
198 if (type
!= reply
.type
) {
199 throw 'Expected reply type: ' + reply
.type
+ ', got: ' + type
;
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());
209 * @return {void} Nothing.
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_
);
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_
);
235 * Gets local hostname.
237 * @return {!Promise<string>}
239 remoting
.HostDaemonFacade
.prototype.getHostName = function() {
240 return this.postMessage_({type
: 'getHostName'}).then(function(reply
) {
241 return base
.getStringAttr(reply
, 'hostname');
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>}
253 remoting
.HostDaemonFacade
.prototype.getPinHash = function(hostId
, pin
) {
254 return this.postMessage_({
258 }).then(function(reply
) {
259 return base
.getStringAttr(reply
, 'hash');
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>}
270 remoting
.HostDaemonFacade
.prototype.generateKeyPair = function() {
271 return this.postMessage_({type
: 'generateKeyPair'}).then(function(reply
) {
273 privateKey
: base
.getStringAttr(reply
, 'privateKey'),
274 publicKey
: base
.getStringAttr(reply
, 'publicKey')
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
287 * TODO(jrw): Consider conversion exceptions to AsyncResult values.
289 * @param {Object} config The new config parameters.
290 * @return {!Promise<remoting.HostController.AsyncResult>}
292 remoting
.HostDaemonFacade
.prototype.updateDaemonConfig = function(config
) {
293 return this.postMessage_({
294 type
: 'updateDaemonConfig',
296 }).then(function(reply
) {
297 return remoting
.HostController
.AsyncResult
.fromString(
298 base
.getStringAttr(reply
, 'result'));
303 * Loads daemon config. The config is passed as a JSON formatted string to the
305 * @return {!Promise<Object>}
307 remoting
.HostDaemonFacade
.prototype.getDaemonConfig = function() {
308 return this.postMessage_({type
: 'getDaemonConfig'}).then(function(reply
) {
309 return base
.getObjectAttr(reply
, 'config');
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>}
319 remoting
.HostDaemonFacade
.prototype.getDaemonVersion = function() {
320 /** @type {remoting.HostDaemonFacade} */
322 return this.initialize_().then(function() {
323 return that
.version_
;
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>}
335 remoting
.HostDaemonFacade
.prototype.getUsageStatsConsent = function() {
336 return this.postMessage_({type
: 'getUsageStatsConsent'}).
337 then(function(reply
) {
339 supported
: base
.getBooleanAttr(reply
, 'supported'),
340 allowed
: base
.getBooleanAttr(reply
, 'allowed'),
341 setByPolicy
: base
.getBooleanAttr(reply
, 'setByPolicy')
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>}
355 remoting
.HostDaemonFacade
.prototype.startDaemon = function(config
, consent
) {
356 return this.postMessage_({
360 }).then(function(reply
) {
361 return remoting
.HostController
.AsyncResult
.fromString(
362 base
.getStringAttr(reply
, 'result'));
367 * Stops the daemon process.
369 * TODO(jrw): Consider conversion exceptions to AsyncResult values.
371 * @return {!Promise<remoting.HostController.AsyncResult>}
373 remoting
.HostDaemonFacade
.prototype.stopDaemon
=
375 return this.postMessage_({type
: 'stopDaemon'}).then(function(reply
) {
376 return remoting
.HostController
.AsyncResult
.fromString(
377 base
.getStringAttr(reply
, 'result'));
382 * Gets the installed/running state of the Host process.
384 * @return {!Promise<remoting.HostController.State>}
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'));
394 * Retrieves the list of paired clients.
396 * @return {!Promise<Array<remoting.PairedClient>>}
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
;
405 throw remoting
.Error
.unexpected('No paired clients!');
411 * Clears all paired clients from the registry.
413 * @return {!Promise<boolean>}
415 remoting
.HostDaemonFacade
.prototype.clearPairedClients = function() {
416 return this.postMessage_({type
: 'clearPairedClients'}).then(function(reply
) {
417 return base
.getBooleanAttr(reply
, 'result');
422 * Deletes a paired client referenced by client id.
424 * @param {string} client Client to delete.
425 * @return {!Promise<boolean>}
427 remoting
.HostDaemonFacade
.prototype.deletePairedClient = function(client
) {
428 return this.postMessage_({
429 type
: 'deletePairedClient',
431 }).then(function(reply
) {
432 return base
.getBooleanAttr(reply
, 'result');
437 * Gets the API keys to obtain/use service account credentials.
439 * @return {!Promise<string>}
441 remoting
.HostDaemonFacade
.prototype.getHostClientId = function() {
442 return this.postMessage_({type
: 'getHostClientId'}).then(function(reply
) {
443 return base
.getStringAttr(reply
, 'clientId');
448 * @param {string} authorizationCode OAuth authorization code.
449 * @return {!Promise<{remoting.XmppCredentials}>}
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
) {
461 userEmail
: userEmail
,
462 refreshToken
: refreshToken
465 throw remoting
.Error
.unexpected('Missing userEmail or refreshToken');
471 * @param {string} authorizationCode OAuth authorization code.
472 * @return {!Promise<string>}
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');
484 throw remoting
.Error
.unexpected('Missing refreshToken');