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);
54 this.debugMessageHandler_
=
55 new remoting
.NativeMessageHostDebugMessageHandler();
61 * @return {Promise} A promise that fulfills when the daemon finishes
65 remoting
.HostDaemonFacade
.prototype.initialize_ = function() {
66 if (!this.initializingPromise_
) {
68 return Promise
.resolve();
71 /** @type {remoting.HostDaemonFacade} */
73 this.initializingPromise_
= this.connectNative_().then(function() {
74 that
.initializingPromise_
= null;
76 that
.initializingPromise_
= null;
77 throw new Error(that
.error_
);
80 return this.initializingPromise_
;
84 * Connects to the native messaging host and sends a hello message.
86 * @return {Promise} A promise that fulfills when the connection attempt
90 remoting
.HostDaemonFacade
.prototype.connectNative_ = function() {
93 this.port_
= chrome
.runtime
.connectNative(
94 'com.google.chrome.remote_desktop');
95 this.port_
.onMessage
.addListener(this.onIncomingMessageCallback_
);
96 this.port_
.onDisconnect
.addListener(this.onDisconnectCallback_
);
97 return this.postMessageInternal_({type
: 'hello'}).then(function(reply
) {
98 that
.version_
= base
.getStringAttr(reply
, 'version');
99 // Old versions of the native messaging host do not return this list.
100 // Those versions default to the empty list of supported features.
101 that
.supportedFeatures_
=
102 base
.getArrayAttr(reply
, 'supportedFeatures', []);
104 } catch (/** @type {*} */ err
) {
105 console
.log('Native Messaging initialization failed: ', err
);
106 throw remoting
.Error
.unexpected();
111 * Type used for entries of |pendingReplies_| list.
113 * @param {string} type Type of the originating request.
114 * @param {!base.Deferred} deferred Used to communicate returns back
118 remoting
.HostDaemonFacade
.PendingReply = function(type
, deferred
) {
123 this.deferred
= deferred
;
127 * @param {remoting.HostController.Feature} feature The feature to test for.
128 * @return {!Promise<boolean>} True if the implementation supports the
131 remoting
.HostDaemonFacade
.prototype.hasFeature = function(feature
) {
132 /** @type {remoting.HostDaemonFacade} */
134 return this.initialize_().then(function() {
135 return that
.supportedFeatures_
.indexOf(feature
) >= 0;
142 * Initializes that the Daemon if necessary and posts the supplied message.
144 * @param {{type: string}} message The message to post.
145 * @return {!Promise<!Object>}
148 remoting
.HostDaemonFacade
.prototype.postMessage_
=
150 /** @type {remoting.HostDaemonFacade} */
152 return this.initialize_().then(function() {
153 return that
.postMessageInternal_(message
);
160 * Attaches a new ID to the supplied message, and posts it to the
161 * Native Messaging port, adding a Deferred object to the list of
162 * pending replies. |message| should have its 'type' field set, and
163 * any other fields set depending on the message type.
165 * @param {{type: string}} message The message to post.
166 * @return {!Promise<!Object>}
169 remoting
.HostDaemonFacade
.prototype.postMessageInternal_ = function(message
) {
170 var id
= this.nextId_
++;
172 var deferred
= new base
.Deferred();
173 this.pendingReplies_
[id
] = new remoting
.HostDaemonFacade
.PendingReply(
174 message
.type
+ 'Response', deferred
);
175 this.port_
.postMessage(message
);
176 return deferred
.promise();
180 * Handler for incoming Native Messages.
182 * @param {Object} message The received message.
183 * @return {void} Nothing.
186 remoting
.HostDaemonFacade
.prototype.onIncomingMessage_ = function(message
) {
187 if (this.debugMessageHandler_
.handleMessage(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
];
205 var type
= base
.getStringAttr(message
, 'type');
206 if (type
!= reply
.type
) {
207 throw 'Expected reply type: ' + reply
.type
+ ', got: ' + type
;
209 reply
.deferred
.resolve(message
);
210 } catch (/** @type {*} */ e
) {
211 console
.error('Error while processing native message', e
);
212 reply
.deferred
.reject(remoting
.Error
.unexpected());
217 * @return {void} Nothing.
220 remoting
.HostDaemonFacade
.prototype.onDisconnect_ = function() {
221 console
.error('Native Message port disconnected');
223 this.port_
.onDisconnect
.removeListener(this.onDisconnectCallback_
);
224 this.port_
.onMessage
.removeListener(this.onIncomingMessageCallback_
);
227 // If initialization hasn't finished then assume that the port was
228 // disconnected because Native Messaging host is not installed.
229 this.error_
= this.initializingPromise_
?
230 new remoting
.Error(remoting
.Error
.Tag
.MISSING_PLUGIN
) :
231 remoting
.Error
.unexpected();
233 // Notify the error-handlers of any requests that are still outstanding.
234 var pendingReplies
= this.pendingReplies_
;
235 this.pendingReplies_
= {};
236 for (var id
in pendingReplies
) {
237 var num_id
= parseInt(id
, 10);
238 pendingReplies
[num_id
].deferred
.reject(this.error_
);
243 * Gets local hostname.
245 * @return {!Promise<string>}
247 remoting
.HostDaemonFacade
.prototype.getHostName = function() {
248 return this.postMessage_({type
: 'getHostName'}).then(function(reply
) {
249 return base
.getStringAttr(reply
, 'hostname');
254 * Calculates PIN hash value to be stored in the config, passing the resulting
255 * hash value base64-encoded to the callback.
257 * @param {string} hostId The host ID.
258 * @param {string} pin The PIN.
259 * @return {!Promise<string>}
261 remoting
.HostDaemonFacade
.prototype.getPinHash = function(hostId
, pin
) {
262 return this.postMessage_({
266 }).then(function(reply
) {
267 return base
.getStringAttr(reply
, 'hash');
272 * Generates new key pair to use for the host. The specified callback is called
273 * when the key is generated. The key is returned in format understood by the
274 * host (PublicKeyInfo structure encoded with ASN.1 DER, and then BASE64).
276 * @return {!Promise<remoting.KeyPair>}
278 remoting
.HostDaemonFacade
.prototype.generateKeyPair = function() {
279 return this.postMessage_({type
: 'generateKeyPair'}).then(function(reply
) {
281 privateKey
: base
.getStringAttr(reply
, 'privateKey'),
282 publicKey
: base
.getStringAttr(reply
, 'publicKey')
288 * Updates host config with the values specified in |config|. All
289 * fields that are not specified in |config| remain
290 * unchanged. Following parameters cannot be changed using this
291 * function: host_id, xmpp_login. Error is returned if |config|
292 * includes these parameters. Changes take effect before the callback
295 * TODO(jrw): Consider conversion exceptions to AsyncResult values.
297 * @param {Object} config The new config parameters.
298 * @return {!Promise<remoting.HostController.AsyncResult>}
300 remoting
.HostDaemonFacade
.prototype.updateDaemonConfig = function(config
) {
301 return this.postMessage_({
302 type
: 'updateDaemonConfig',
304 }).then(function(reply
) {
305 return remoting
.HostController
.AsyncResult
.fromString(
306 base
.getStringAttr(reply
, 'result'));
311 * Loads daemon config. The config is passed as a JSON formatted string to the
313 * @return {!Promise<Object>}
315 remoting
.HostDaemonFacade
.prototype.getDaemonConfig = function() {
316 return this.postMessage_({type
: 'getDaemonConfig'}).then(function(reply
) {
317 return base
.getObjectAttr(reply
, 'config');
322 * Retrieves daemon version. The version is returned as a dotted decimal
323 * string of the form major.minor.build.patch.
325 * @return {!Promise<string>}
327 remoting
.HostDaemonFacade
.prototype.getDaemonVersion = function() {
328 /** @type {remoting.HostDaemonFacade} */
330 return this.initialize_().then(function() {
331 return that
.version_
;
338 * Get the user's consent to crash reporting. The consent flags are passed to
339 * the callback as booleans: supported, allowed, set-by-policy.
341 * @return {!Promise<remoting.UsageStatsConsent>}
343 remoting
.HostDaemonFacade
.prototype.getUsageStatsConsent = function() {
344 return this.postMessage_({type
: 'getUsageStatsConsent'}).
345 then(function(reply
) {
347 supported
: base
.getBooleanAttr(reply
, 'supported'),
348 allowed
: base
.getBooleanAttr(reply
, 'allowed'),
349 setByPolicy
: base
.getBooleanAttr(reply
, 'setByPolicy')
355 * Starts the daemon process with the specified configuration.
357 * TODO(jrw): Consider conversion exceptions to AsyncResult values.
359 * @param {Object} config Host configuration.
360 * @param {boolean} consent Consent to report crash dumps.
361 * @return {!Promise<remoting.HostController.AsyncResult>}
363 remoting
.HostDaemonFacade
.prototype.startDaemon = function(config
, consent
) {
364 return this.postMessage_({
368 }).then(function(reply
) {
369 return remoting
.HostController
.AsyncResult
.fromString(
370 base
.getStringAttr(reply
, 'result'));
375 * Stops the daemon process.
377 * TODO(jrw): Consider conversion exceptions to AsyncResult values.
379 * @return {!Promise<remoting.HostController.AsyncResult>}
381 remoting
.HostDaemonFacade
.prototype.stopDaemon
=
383 return this.postMessage_({type
: 'stopDaemon'}).then(function(reply
) {
384 return remoting
.HostController
.AsyncResult
.fromString(
385 base
.getStringAttr(reply
, 'result'));
390 * Gets the installed/running state of the Host process.
392 * @return {!Promise<remoting.HostController.State>}
394 remoting
.HostDaemonFacade
.prototype.getDaemonState = function() {
395 return this.postMessage_({type
: 'getDaemonState'}).then(function(reply
) {
396 return remoting
.HostController
.State
.fromString(
397 base
.getStringAttr(reply
, 'state'));
402 * Retrieves the list of paired clients.
404 * @return {!Promise<Array<remoting.PairedClient>>}
406 remoting
.HostDaemonFacade
.prototype.getPairedClients = function() {
407 return this.postMessage_({type
: 'getPairedClients'}).then(function(reply
) {
408 var pairedClients
=remoting
.PairedClient
.convertToPairedClientArray(
409 reply
['pairedClients']);
410 if (pairedClients
!= null) {
411 return pairedClients
;
413 throw remoting
.Error
.unexpected('No paired clients!');
419 * Clears all paired clients from the registry.
421 * @return {!Promise<boolean>}
423 remoting
.HostDaemonFacade
.prototype.clearPairedClients = function() {
424 return this.postMessage_({type
: 'clearPairedClients'}).then(function(reply
) {
425 return base
.getBooleanAttr(reply
, 'result');
430 * Deletes a paired client referenced by client id.
432 * @param {string} client Client to delete.
433 * @return {!Promise<boolean>}
435 remoting
.HostDaemonFacade
.prototype.deletePairedClient = function(client
) {
436 return this.postMessage_({
437 type
: 'deletePairedClient',
439 }).then(function(reply
) {
440 return base
.getBooleanAttr(reply
, 'result');
445 * Gets the API keys to obtain/use service account credentials.
447 * @return {!Promise<string>}
449 remoting
.HostDaemonFacade
.prototype.getHostClientId = function() {
450 return this.postMessage_({type
: 'getHostClientId'}).then(function(reply
) {
451 return base
.getStringAttr(reply
, 'clientId');
456 * @param {string} authorizationCode OAuth authorization code.
457 * @return {!Promise<{remoting.XmppCredentials}>}
459 remoting
.HostDaemonFacade
.prototype.getCredentialsFromAuthCode
=
460 function(authorizationCode
) {
461 return this.postMessage_({
462 type
: 'getCredentialsFromAuthCode',
463 authorizationCode
: authorizationCode
464 }).then(function(reply
) {
465 var userEmail
= base
.getStringAttr(reply
, 'userEmail');
466 var refreshToken
= base
.getStringAttr(reply
, 'refreshToken');
467 if (userEmail
&& refreshToken
) {
469 userEmail
: userEmail
,
470 refreshToken
: refreshToken
473 throw remoting
.Error
.unexpected('Missing userEmail or refreshToken');
479 * @param {string} authorizationCode OAuth authorization code.
480 * @return {!Promise<string>}
482 remoting
.HostDaemonFacade
.prototype.getRefreshTokenFromAuthCode
=
483 function(authorizationCode
) {
484 return this.postMessage_({
485 type
: 'getRefreshTokenFromAuthCode',
486 authorizationCode
: authorizationCode
487 }).then(function(reply
) {
488 var refreshToken
= base
.getStringAttr(reply
, 'refreshToken');
492 throw remoting
.Error
.unexpected('Missing refreshToken');