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() {
26 * @type {Object<number, remoting.HostDaemonFacade.PendingReply>}
29 this.pendingReplies_ = {};
31 /** @type {?chrome.runtime.Port} @private */
34 /** @type {string} @private */
37 /** @type {Array<remoting.HostController.Feature>} @private */
38 this.supportedFeatures_ = [];
40 /** @type {Array<function(boolean):void>} @private */
41 this.afterInitializationTasks_ = [];
44 * A promise that fulfills when the daemon finishes initializing.
45 * It will be set to null when the promise fulfills.
49 this.initializingPromise_ = null;
51 /** @type {remoting.Error} @private */
52 this.error_ = remoting.Error.NONE;
55 this.onIncomingMessageCallback_ = this.onIncomingMessage_.bind(this);
58 this.onDisconnectCallback_ = this.onDisconnect_.bind(this);
64 * @return {Promise} A promise that fulfills when the daemon finishes
68 remoting.HostDaemonFacade.prototype.initialize_ = function() {
69 if (!this.initializingPromise_) {
71 return Promise.resolve();
74 /** @type {remoting.HostDaemonFacade} */
76 this.initializingPromise_ = this.connectNative_().then(function() {
77 that.initializingPromise_ = null;
79 that.initializingPromise_ = null;
80 throw new Error(that.error_);
83 return this.initializingPromise_;
87 * Connects to the native messaging host and sends a hello message.
89 * @return {Promise} A promise that fulfills when the connection attempt
93 remoting.HostDaemonFacade.prototype.connectNative_ = function() {
95 * @this {remoting.HostDaemonFacade}
96 * @param {function(?):void} resolve
97 * @param {function(*):void} reject
99 var connect = function(resolve, reject) {
101 this.port_ = chrome.runtime.connectNative(
102 'com.google.chrome.remote_desktop');
103 this.port_.onMessage.addListener(this.onIncomingMessageCallback_);
104 this.port_.onDisconnect.addListener(this.onDisconnectCallback_);
105 this.postMessageInternal_({type: 'hello'}, resolve, reject);
106 } catch (/** @type {*} */ err) {
107 console.log('Native Messaging initialization failed: ', err);
112 return new Promise(connect.bind(this));
116 * Type used for entries of |pendingReplies_| list.
118 * @param {string} type Type of the originating request.
119 * @param {function(...):void} onDone Response callback. Parameters depend on
121 * @param {function(remoting.Error):void} onError Callback to call on error.
124 remoting.HostDaemonFacade.PendingReply = function(type, onDone, onError) {
126 this.onDone = onDone;
127 this.onError = onError;
131 * @param {remoting.HostController.Feature} feature The feature to test for.
132 * @param {function(boolean):void} onDone Callback to return result.
133 * @return {boolean} True if the implementation supports the named feature.
135 remoting.HostDaemonFacade.prototype.hasFeature = function(feature, onDone) {
136 /** @type {remoting.HostDaemonFacade} */
138 this.initialize_().then(function() {
139 onDone(that.supportedFeatures_.indexOf(feature) >= 0);
146 * Initializes that the Daemon if necessary and posts the supplied message.
148 * @param {{type: string}} message The message to post.
149 * @param {function(...):void} onDone The callback, if any, to be triggered
151 * @param {function(remoting.Error):void} onError Callback to call on error.
154 remoting.HostDaemonFacade.prototype.postMessage_ =
155 function(message, onDone, onError) {
156 /** @type {remoting.HostDaemonFacade} */
158 this.initialize_().then(function() {
159 that.postMessageInternal_(message, onDone, onError);
161 onError(that.error_);
166 * Attaches a new ID to the supplied message, and posts it to the Native
167 * Messaging port, adding |onDone| to the list of pending replies.
168 * |message| should have its 'type' field set, and any other fields set
169 * depending on the message type.
171 * @param {{type: string}} message The message to post.
172 * @param {function(...):void} onDone The callback, if any, to be triggered
174 * @param {function(remoting.Error):void} onError Callback to call on error.
175 * @return {void} Nothing.
178 remoting.HostDaemonFacade.prototype.postMessageInternal_ =
179 function(message, onDone, onError) {
180 var id = this.nextId_++;
182 this.pendingReplies_[id] = new remoting.HostDaemonFacade.PendingReply(
183 message.type + 'Response', onDone, onError);
184 this.port_.postMessage(message);
188 * Handler for incoming Native Messages.
190 * @param {Object} message The received message.
191 * @return {void} Nothing.
194 remoting.HostDaemonFacade.prototype.onIncomingMessage_ = function(message) {
195 /** @type {number} */
196 var id = message['id'];
197 if (typeof(id) != 'number') {
198 console.error('NativeMessaging: missing or non-numeric id');
201 var reply = this.pendingReplies_[id];
203 console.error('NativeMessaging: unexpected id: ', id);
206 delete this.pendingReplies_[id];
209 var type = getStringAttr(message, 'type');
210 if (type != reply.type) {
211 throw 'Expected reply type: ' + reply.type + ', got: ' + type;
214 this.handleIncomingMessage_(message, reply.onDone);
215 } catch (/** @type {*} */ e) {
216 console.error('Error while processing native message' + e);
217 reply.onError(remoting.Error.UNEXPECTED);
222 * Handler for incoming Native Messages.
224 * @param {Object} message The received message.
225 * @param {function(...):void} onDone Function to call when we're done
226 * processing the message.
227 * @return {void} Nothing.
230 remoting.HostDaemonFacade.prototype.handleIncomingMessage_ =
231 function(message, onDone) {
232 var type = getStringAttr(message, 'type');
235 case 'helloResponse':
236 this.version_ = getStringAttr(message, 'version');
237 // Old versions of the native messaging host do not return this list.
238 // Those versions default to the empty list of supported features.
239 this.supportedFeatures_ = getArrayAttr(message, 'supportedFeatures', []);
243 case 'getHostNameResponse':
244 onDone(getStringAttr(message, 'hostname'));
247 case 'getPinHashResponse':
248 onDone(getStringAttr(message, 'hash'));
251 case 'generateKeyPairResponse':
252 var privateKey = getStringAttr(message, 'privateKey');
253 var publicKey = getStringAttr(message, 'publicKey');
254 onDone(privateKey, publicKey);
257 case 'updateDaemonConfigResponse':
258 var result = remoting.HostController.AsyncResult.fromString(
259 getStringAttr(message, 'result'));
263 case 'getDaemonConfigResponse':
264 onDone(getObjectAttr(message, 'config'));
267 case 'getUsageStatsConsentResponse':
268 var supported = getBooleanAttr(message, 'supported');
269 var allowed = getBooleanAttr(message, 'allowed');
270 var setByPolicy = getBooleanAttr(message, 'setByPolicy');
271 onDone(supported, allowed, setByPolicy);
274 case 'startDaemonResponse':
275 case 'stopDaemonResponse':
276 var result = remoting.HostController.AsyncResult.fromString(
277 getStringAttr(message, 'result'));
281 case 'getDaemonStateResponse':
282 var state = remoting.HostController.State.fromString(
283 getStringAttr(message, 'state'));
287 case 'getPairedClientsResponse':
288 var pairedClients = remoting.PairedClient.convertToPairedClientArray(
289 message['pairedClients']);
290 if (pairedClients != null) {
291 onDone(pairedClients);
293 throw 'No paired clients!';
297 case 'clearPairedClientsResponse':
298 case 'deletePairedClientResponse':
299 onDone(getBooleanAttr(message, 'result'));
302 case 'getHostClientIdResponse':
303 onDone(getStringAttr(message, 'clientId'));
306 case 'getCredentialsFromAuthCodeResponse':
307 var userEmail = getStringAttr(message, 'userEmail');
308 var refreshToken = getStringAttr(message, 'refreshToken');
309 if (userEmail && refreshToken) {
310 onDone(userEmail, refreshToken);
312 throw 'Missing userEmail or refreshToken';
317 throw 'Unexpected native message: ' + message;
322 * @return {void} Nothing.
325 remoting.HostDaemonFacade.prototype.onDisconnect_ = function() {
326 console.error('Native Message port disconnected');
328 this.port_.onDisconnect.removeListener(this.onDisconnectCallback_);
329 this.port_.onMessage.removeListener(this.onIncomingMessageCallback_);
332 // If initialization hasn't finished then assume that the port was
333 // disconnected because Native Messaging host is not installed.
334 this.error_ = this.initializingPromise_ ? remoting.Error.MISSING_PLUGIN :
335 remoting.Error.UNEXPECTED;
337 // Notify the error-handlers of any requests that are still outstanding.
338 var pendingReplies = this.pendingReplies_;
339 this.pendingReplies_ = {};
340 for (var id in pendingReplies) {
341 var num_id = parseInt(id, 10);
342 pendingReplies[num_id].onError(this.error_);
347 * Gets local hostname.
349 * @param {function(string):void} onDone Callback to return result.
350 * @param {function(remoting.Error):void} onError Callback to call on error.
351 * @return {void} Nothing.
353 remoting.HostDaemonFacade.prototype.getHostName =
354 function(onDone, onError) {
355 this.postMessage_({type: 'getHostName'}, onDone, onError);
359 * Calculates PIN hash value to be stored in the config, passing the resulting
360 * hash value base64-encoded to the callback.
362 * @param {string} hostId The host ID.
363 * @param {string} pin The PIN.
364 * @param {function(string):void} onDone Callback to return result.
365 * @param {function(remoting.Error):void} onError Callback to call on error.
366 * @return {void} Nothing.
368 remoting.HostDaemonFacade.prototype.getPinHash =
369 function(hostId, pin, onDone, onError) {
378 * Generates new key pair to use for the host. The specified callback is called
379 * when the key is generated. The key is returned in format understood by the
380 * host (PublicKeyInfo structure encoded with ASN.1 DER, and then BASE64).
382 * @param {function(string, string):void} onDone Callback to return result.
383 * @param {function(remoting.Error):void} onError Callback to call on error.
384 * @return {void} Nothing.
386 remoting.HostDaemonFacade.prototype.generateKeyPair =
387 function(onDone, onError) {
388 this.postMessage_({type: 'generateKeyPair'}, onDone, onError);
392 * Updates host config with the values specified in |config|. All
393 * fields that are not specified in |config| remain
394 * unchanged. Following parameters cannot be changed using this
395 * function: host_id, xmpp_login. Error is returned if |config|
396 * includes these parameters. Changes take effect before the callback
399 * @param {Object} config The new config parameters.
400 * @param {function(remoting.HostController.AsyncResult):void} onDone
401 * Callback to be called when finished.
402 * @param {function(remoting.Error):void} onError Callback to call on error.
403 * @return {void} Nothing.
405 remoting.HostDaemonFacade.prototype.updateDaemonConfig =
406 function(config, onDone, onError) {
408 type: 'updateDaemonConfig',
414 * Loads daemon config. The config is passed as a JSON formatted string to the
417 * @param {function(Object):void} onDone Callback to return result.
418 * @param {function(remoting.Error):void} onError Callback to call on error.
419 * @return {void} Nothing.
421 remoting.HostDaemonFacade.prototype.getDaemonConfig =
422 function(onDone, onError) {
423 this.postMessage_({type: 'getDaemonConfig'}, onDone, onError);
427 * Retrieves daemon version. The version is passed to onDone as a dotted decimal
428 * string of the form major.minor.build.patch.
430 * @param {function(string):void} onDone Callback to be called to return result.
431 * @param {function(remoting.Error):void} onError Callback to call on error.
434 remoting.HostDaemonFacade.prototype.getDaemonVersion =
435 function(onDone, onError) {
436 /** @type {remoting.HostDaemonFacade} */
438 this.initialize_().then(function() {
439 onDone(that.version_);
441 onError(that.error_);
446 * Get the user's consent to crash reporting. The consent flags are passed to
447 * the callback as booleans: supported, allowed, set-by-policy.
449 * @param {function(boolean, boolean, boolean):void} onDone Callback to return
451 * @param {function(remoting.Error):void} onError Callback to call on error.
452 * @return {void} Nothing.
454 remoting.HostDaemonFacade.prototype.getUsageStatsConsent =
455 function(onDone, onError) {
456 this.postMessage_({type: 'getUsageStatsConsent'}, onDone, onError);
460 * Starts the daemon process with the specified configuration.
462 * @param {Object} config Host configuration.
463 * @param {boolean} consent Consent to report crash dumps.
464 * @param {function(remoting.HostController.AsyncResult):void} onDone
465 * Callback to return result.
466 * @param {function(remoting.Error):void} onError Callback to call on error.
467 * @return {void} Nothing.
469 remoting.HostDaemonFacade.prototype.startDaemon =
470 function(config, consent, onDone, onError) {
479 * Stops the daemon process.
481 * @param {function(remoting.HostController.AsyncResult):void} onDone
482 * Callback to return result.
483 * @param {function(remoting.Error):void} onError Callback to call on error.
484 * @return {void} Nothing.
486 remoting.HostDaemonFacade.prototype.stopDaemon =
487 function(onDone, onError) {
488 this.postMessage_({type: 'stopDaemon'}, onDone, onError);
492 * Gets the installed/running state of the Host process.
494 * @param {function(remoting.HostController.State):void} onDone Callback to
496 * @param {function(remoting.Error):void} onError Callback to call on error.
497 * @return {void} Nothing.
499 remoting.HostDaemonFacade.prototype.getDaemonState =
500 function(onDone, onError) {
501 this.postMessage_({type: 'getDaemonState'}, onDone, onError);
505 * Retrieves the list of paired clients.
507 * @param {function(Array<remoting.PairedClient>):void} onDone Callback to
509 * @param {function(remoting.Error):void} onError Callback to call on error.
511 remoting.HostDaemonFacade.prototype.getPairedClients =
512 function(onDone, onError) {
513 this.postMessage_({type: 'getPairedClients'}, onDone, onError);
517 * Clears all paired clients from the registry.
519 * @param {function(boolean):void} onDone Callback to be called when finished.
520 * @param {function(remoting.Error):void} onError Callback to call on error.
522 remoting.HostDaemonFacade.prototype.clearPairedClients =
523 function(onDone, onError) {
524 this.postMessage_({type: 'clearPairedClients'}, onDone, onError);
528 * Deletes a paired client referenced by client id.
530 * @param {string} client Client to delete.
531 * @param {function(boolean):void} onDone Callback to be called when finished.
532 * @param {function(remoting.Error):void} onError Callback to call on error.
534 remoting.HostDaemonFacade.prototype.deletePairedClient =
535 function(client, onDone, onError) {
537 type: 'deletePairedClient',
543 * Gets the API keys to obtain/use service account credentials.
545 * @param {function(string):void} onDone Callback to return result.
546 * @param {function(remoting.Error):void} onError Callback to call on error.
547 * @return {void} Nothing.
549 remoting.HostDaemonFacade.prototype.getHostClientId =
550 function(onDone, onError) {
551 this.postMessage_({type: 'getHostClientId'}, onDone, onError);
556 * @param {string} authorizationCode OAuth authorization code.
557 * @param {function(string, string):void} onDone Callback to return result.
558 * @param {function(remoting.Error):void} onError Callback to call on error.
559 * @return {void} Nothing.
561 remoting.HostDaemonFacade.prototype.getCredentialsFromAuthCode =
562 function(authorizationCode, onDone, onError) {
564 type: 'getCredentialsFromAuthCode',
565 authorizationCode: authorizationCode