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 * This class provides an interface between the HostController and either the
8 * NativeMessaging Host or the Host NPAPI plugin, depending on whether or not
9 * NativeMessaging is supported. Since the test for NativeMessaging support is
10 * asynchronous, this class stores any requests on a queue, pending the result
12 * Once the test is complete, the pending requests are performed on either the
13 * NativeMessaging host, or the NPAPI plugin.
15 * If necessary, the HostController is instructed (via a callback) to
16 * instantiate the NPAPI plugin, and return a reference to it here.
21 /** @suppress {duplicate} */
22 var remoting = remoting || {};
26 * @param {function():remoting.HostPlugin} createPluginCallback Callback to
27 * instantiate the NPAPI plugin when NativeMessaging is determined to be
30 remoting.HostDispatcher = function(createPluginCallback) {
31 /** @type {remoting.HostNativeMessaging} @private */
32 this.nativeMessagingHost_ = new remoting.HostNativeMessaging();
34 /** @type {remoting.HostPlugin} @private */
35 this.npapiHost_ = null;
37 /** @type {remoting.HostDispatcher.State} @private */
38 this.state_ = remoting.HostDispatcher.State.UNKNOWN;
40 /** @type {Array.<function()>} @private */
41 this.pendingRequests_ = [];
43 /** @type {function():remoting.HostPlugin} @private */
44 this.createPluginCallback_ = createPluginCallback;
46 this.tryToInitialize_();
50 remoting.HostDispatcher.State = {
57 remoting.HostDispatcher.prototype.tryToInitialize_ = function() {
58 /** @type {remoting.HostDispatcher} */
61 if (this.state_ != remoting.HostDispatcher.State.UNKNOWN)
64 function sendPendingRequests() {
65 var pendingRequests = that.pendingRequests_;
66 that.pendingRequests_ = [];
67 for (var i = 0; i < pendingRequests.length; i++) {
72 function onNativeMessagingInit() {
73 that.state_ = remoting.HostDispatcher.State.NATIVE_MESSAGING;
74 sendPendingRequests();
77 function onNativeMessagingFailed(error) {
78 that.npapiHost_ = that.createPluginCallback_();
80 that.state_ = that.npapiHost_ ? remoting.HostDispatcher.State.NPAPI
81 : remoting.HostDispatcher.State.NOT_INSTALLED;
82 sendPendingRequests();
85 this.nativeMessagingHost_.initialize(onNativeMessagingInit,
86 onNativeMessagingFailed);
90 * @param {remoting.HostController.Feature} feature The feature to test for.
91 * @param {function(boolean):void} onDone
94 remoting.HostDispatcher.prototype.hasFeature = function(
96 switch (this.state_) {
97 case remoting.HostDispatcher.State.UNKNOWN:
98 this.pendingRequests_.push(
99 this.hasFeature.bind(this, feature, onDone));
101 case remoting.HostDispatcher.State.NATIVE_MESSAGING:
102 onDone(this.nativeMessagingHost_.hasFeature(feature));
104 case remoting.HostDispatcher.State.NPAPI:
105 // If this is an old NPAPI plugin that doesn't list supportedFeatures,
106 // assume it is an old plugin that doesn't support any new feature.
107 var supportedFeatures = [];
108 if (typeof(this.npapiHost_.supportedFeatures) == 'string') {
109 supportedFeatures = this.npapiHost_.supportedFeatures.split(' ');
111 onDone(supportedFeatures.indexOf(feature) >= 0);
113 case remoting.HostDispatcher.State.NOT_INSTALLED:
120 * @param {function(string):void} onDone
121 * @param {function(remoting.Error):void} onError
124 remoting.HostDispatcher.prototype.getHostName = function(onDone, onError) {
125 switch (this.state_) {
126 case remoting.HostDispatcher.State.UNKNOWN:
127 this.pendingRequests_.push(
128 this.getHostName.bind(this, onDone, onError));
130 case remoting.HostDispatcher.State.NATIVE_MESSAGING:
131 this.nativeMessagingHost_.getHostName(onDone, onError);
133 case remoting.HostDispatcher.State.NPAPI:
135 this.npapiHost_.getHostName(onDone);
137 onError(remoting.Error.MISSING_PLUGIN);
140 case remoting.HostDispatcher.State.NOT_INSTALLED:
141 onError(remoting.Error.MISSING_PLUGIN);
147 * @param {string} hostId
148 * @param {string} pin
149 * @param {function(string):void} onDone
150 * @param {function(remoting.Error):void} onError
153 remoting.HostDispatcher.prototype.getPinHash =
154 function(hostId, pin, onDone, onError) {
155 switch (this.state_) {
156 case remoting.HostDispatcher.State.UNKNOWN:
157 this.pendingRequests_.push(
158 this.getPinHash.bind(this, hostId, pin, onDone, onError));
160 case remoting.HostDispatcher.State.NATIVE_MESSAGING:
161 this.nativeMessagingHost_.getPinHash(hostId, pin, onDone, onError);
163 case remoting.HostDispatcher.State.NPAPI:
165 this.npapiHost_.getPinHash(hostId, pin, onDone);
167 onError(remoting.Error.MISSING_PLUGIN);
170 case remoting.HostDispatcher.State.NOT_INSTALLED:
171 onError(remoting.Error.MISSING_PLUGIN);
177 * @param {function(string, string):void} onDone
178 * @param {function(remoting.Error):void} onError
181 remoting.HostDispatcher.prototype.generateKeyPair = function(onDone, onError) {
182 switch (this.state_) {
183 case remoting.HostDispatcher.State.UNKNOWN:
184 this.pendingRequests_.push(
185 this.generateKeyPair.bind(this, onDone, onError));
187 case remoting.HostDispatcher.State.NATIVE_MESSAGING:
188 this.nativeMessagingHost_.generateKeyPair(onDone, onError);
190 case remoting.HostDispatcher.State.NPAPI:
192 this.npapiHost_.generateKeyPair(onDone);
194 onError(remoting.Error.MISSING_PLUGIN);
197 case remoting.HostDispatcher.State.NOT_INSTALLED:
198 onError(remoting.Error.MISSING_PLUGIN);
204 * @param {Object} config
205 * @param {function(remoting.HostController.AsyncResult):void} onDone
206 * @param {function(remoting.Error):void} onError
209 remoting.HostDispatcher.prototype.updateDaemonConfig =
210 function(config, onDone, onError) {
211 switch (this.state_) {
212 case remoting.HostDispatcher.State.UNKNOWN:
213 this.pendingRequests_.push(
214 this.updateDaemonConfig.bind(this, config, onDone, onError));
216 case remoting.HostDispatcher.State.NATIVE_MESSAGING:
217 this.nativeMessagingHost_.updateDaemonConfig(config, onDone, onError);
219 case remoting.HostDispatcher.State.NPAPI:
221 this.npapiHost_.updateDaemonConfig(JSON.stringify(config), onDone);
223 onError(remoting.Error.MISSING_PLUGIN);
226 case remoting.HostDispatcher.State.NOT_INSTALLED:
227 onError(remoting.Error.MISSING_PLUGIN);
233 * @param {function(Object):void} onDone
234 * @param {function(remoting.Error):void} onError
237 remoting.HostDispatcher.prototype.getDaemonConfig = function(onDone, onError) {
239 * Converts the config string from the NPAPI plugin to an Object, to pass to
241 * @param {string} configStr
244 function callbackForNpapi(configStr) {
245 var config = jsonParseSafe(configStr);
246 if (typeof(config) != 'object') {
247 onError(remoting.Error.UNEXPECTED);
249 onDone(/** @type {Object} */ (config));
253 switch (this.state_) {
254 case remoting.HostDispatcher.State.UNKNOWN:
255 this.pendingRequests_.push(
256 this.getDaemonConfig.bind(this, onDone, onError));
258 case remoting.HostDispatcher.State.NATIVE_MESSAGING:
259 this.nativeMessagingHost_.getDaemonConfig(onDone, onError);
261 case remoting.HostDispatcher.State.NPAPI:
263 this.npapiHost_.getDaemonConfig(callbackForNpapi);
265 onError(remoting.Error.MISSING_PLUGIN);
268 case remoting.HostDispatcher.State.NOT_INSTALLED:
275 * @param {function(string):void} onDone
276 * @param {function(remoting.Error):void} onError
279 remoting.HostDispatcher.prototype.getDaemonVersion = function(onDone, onError) {
280 switch (this.state_) {
281 case remoting.HostDispatcher.State.UNKNOWN:
282 this.pendingRequests_.push(
283 this.getDaemonVersion.bind(this, onDone, onError));
285 case remoting.HostDispatcher.State.NATIVE_MESSAGING:
286 onDone(this.nativeMessagingHost_.getDaemonVersion());
288 case remoting.HostDispatcher.State.NPAPI:
290 this.npapiHost_.getDaemonVersion(onDone);
292 onError(remoting.Error.MISSING_PLUGIN);
295 case remoting.HostDispatcher.State.NOT_INSTALLED:
296 onError(remoting.Error.MISSING_PLUGIN);
302 * @param {function(boolean, boolean, boolean):void} onDone
303 * @param {function(remoting.Error):void} onError
306 remoting.HostDispatcher.prototype.getUsageStatsConsent =
307 function(onDone, onError) {
308 switch (this.state_) {
309 case remoting.HostDispatcher.State.UNKNOWN:
310 this.pendingRequests_.push(
311 this.getUsageStatsConsent.bind(this, onDone, onError));
313 case remoting.HostDispatcher.State.NATIVE_MESSAGING:
314 this.nativeMessagingHost_.getUsageStatsConsent(onDone, onError);
316 case remoting.HostDispatcher.State.NPAPI:
318 this.npapiHost_.getUsageStatsConsent(onDone);
320 onError(remoting.Error.MISSING_PLUGIN);
323 case remoting.HostDispatcher.State.NOT_INSTALLED:
324 onError(remoting.Error.MISSING_PLUGIN);
330 * This function installs the chromoting host using the NPAPI plugin and should
331 * only be called on Windows.
333 * @param {function(remoting.HostController.AsyncResult):void} onDone
334 * @param {function(remoting.Error):void} onError
337 remoting.HostDispatcher.prototype.installHost = function(onDone, onError) {
338 switch (this.state_) {
339 case remoting.HostDispatcher.State.UNKNOWN:
340 this.pendingRequests_.push(this.installHost.bind(this, onDone, onError));
342 case remoting.HostDispatcher.State.NATIVE_MESSAGING:
343 // Host already installed, no action needed.
344 onDone(remoting.HostController.AsyncResult.OK);
346 case remoting.HostDispatcher.State.NPAPI:
348 this.npapiHost_.installHost(onDone);
350 onError(remoting.Error.MISSING_PLUGIN);
353 case remoting.HostDispatcher.State.NOT_INSTALLED:
354 onError(remoting.Error.MISSING_PLUGIN);
360 * @param {Object} config
361 * @param {boolean} consent
362 * @param {function(remoting.HostController.AsyncResult):void} onDone
363 * @param {function(remoting.Error):void} onError
366 remoting.HostDispatcher.prototype.startDaemon =
367 function(config, consent, onDone, onError) {
368 switch (this.state_) {
369 case remoting.HostDispatcher.State.UNKNOWN:
370 this.pendingRequests_.push(
371 this.startDaemon.bind(this, config, consent, onDone, onError));
373 case remoting.HostDispatcher.State.NATIVE_MESSAGING:
374 this.nativeMessagingHost_.startDaemon(config, consent, onDone, onError);
376 case remoting.HostDispatcher.State.NPAPI:
378 this.npapiHost_.startDaemon(JSON.stringify(config), consent, onDone);
380 onError(remoting.Error.MISSING_PLUGIN);
383 case remoting.HostDispatcher.State.NOT_INSTALLED:
384 onError(remoting.Error.MISSING_PLUGIN);
390 * @param {function(remoting.HostController.AsyncResult):void} onDone
391 * @param {function(remoting.Error):void} onError
394 remoting.HostDispatcher.prototype.stopDaemon = function(onDone, onError) {
395 switch (this.state_) {
396 case remoting.HostDispatcher.State.UNKNOWN:
397 this.pendingRequests_.push(this.stopDaemon.bind(this, onDone, onError));
399 case remoting.HostDispatcher.State.NATIVE_MESSAGING:
400 this.nativeMessagingHost_.stopDaemon(onDone, onError);
402 case remoting.HostDispatcher.State.NPAPI:
404 this.npapiHost_.stopDaemon(onDone);
406 onError(remoting.Error.MISSING_PLUGIN);
409 case remoting.HostDispatcher.State.NOT_INSTALLED:
410 onError(remoting.Error.MISSING_PLUGIN);
416 * @param {function(remoting.HostController.State):void} onDone
417 * @param {function(remoting.Error):void} onError
420 remoting.HostDispatcher.prototype.getDaemonState = function(onDone, onError) {
421 // If the host was in not-initialized state try initializing it again in case
423 if (this.state_ == remoting.HostDispatcher.State.NOT_INSTALLED) {
424 this.state_ = remoting.HostDispatcher.State.UNKNOWN;
425 this.tryToInitialize_();
428 this.getDaemonStateInternal_(onDone, onError);
432 * @param {function(remoting.HostController.State):void} onDone
433 * @param {function(remoting.Error):void} onError
436 remoting.HostDispatcher.prototype.getDaemonStateInternal_ =
437 function(onDone, onError) {
438 switch (this.state_) {
439 case remoting.HostDispatcher.State.UNKNOWN:
440 this.pendingRequests_.push(
441 this.getDaemonStateInternal_.bind(this, onDone, onError));
443 case remoting.HostDispatcher.State.NATIVE_MESSAGING:
444 this.nativeMessagingHost_.getDaemonState(onDone, onError);
446 case remoting.HostDispatcher.State.NPAPI:
447 // Call the callback directly, since NPAPI exposes the state directly as
448 // a field member, rather than an asynchronous method.
449 var state = this.npapiHost_.daemonState;
450 if (state === undefined) {
451 onError(remoting.Error.MISSING_PLUGIN);
456 case remoting.HostDispatcher.State.NOT_INSTALLED:
457 onDone(remoting.HostController.State.NOT_INSTALLED);
463 * @param {function(Array.<remoting.PairedClient>):void} onDone
464 * @param {function(remoting.Error):void} onError
467 remoting.HostDispatcher.prototype.getPairedClients = function(onDone, onError) {
469 * Converts the JSON string from the NPAPI plugin to Array.<PairedClient>, to
471 * @param {string} pairedClientsJson
474 function callbackForNpapi(pairedClientsJson) {
475 var pairedClients = remoting.PairedClient.convertToPairedClientArray(
476 jsonParseSafe(pairedClientsJson));
477 if (pairedClients != null) {
478 onDone(pairedClients);
480 onError(remoting.Error.UNEXPECTED);
484 switch (this.state_) {
485 case remoting.HostDispatcher.State.UNKNOWN:
486 this.pendingRequests_.push(
487 this.getPairedClients.bind(this, onDone, onError));
489 case remoting.HostDispatcher.State.NATIVE_MESSAGING:
490 this.nativeMessagingHost_.getPairedClients(onDone, onError);
492 case remoting.HostDispatcher.State.NPAPI:
494 this.npapiHost_.getPairedClients(callbackForNpapi);
496 onError(remoting.Error.MISSING_PLUGIN);
499 case remoting.HostDispatcher.State.NOT_INSTALLED:
500 onError(remoting.Error.MISSING_PLUGIN);
506 * The pairing API returns a boolean to indicate success or failure, but
507 * the JS API is defined in terms of onDone and onError callbacks. This
508 * function converts one to the other.
510 * @param {function():void} onDone Success callback.
511 * @param {function(remoting.Error):void} onError Error callback.
512 * @param {boolean} success True if the operation succeeded; false otherwise.
515 remoting.HostDispatcher.runCallback_ = function(onDone, onError, success) {
519 onError(remoting.Error.UNEXPECTED);
524 * @param {function():void} onDone
525 * @param {function(remoting.Error):void} onError
528 remoting.HostDispatcher.prototype.clearPairedClients =
529 function(onDone, onError) {
531 remoting.HostDispatcher.runCallback_.bind(null, onDone, onError);
532 switch (this.state_) {
533 case remoting.HostDispatcher.State.UNKNOWN:
534 this.pendingRequests_.push(
535 this.clearPairedClients.bind(this, onDone, onError));
537 case remoting.HostDispatcher.State.NATIVE_MESSAGING:
538 this.nativeMessagingHost_.clearPairedClients(callback, onError);
540 case remoting.HostDispatcher.State.NPAPI:
542 this.npapiHost_.clearPairedClients(callback);
544 onError(remoting.Error.MISSING_PLUGIN);
547 case remoting.HostDispatcher.State.NOT_INSTALLED:
548 onError(remoting.Error.MISSING_PLUGIN);
554 * @param {string} client
555 * @param {function():void} onDone
556 * @param {function(remoting.Error):void} onError
559 remoting.HostDispatcher.prototype.deletePairedClient =
560 function(client, onDone, onError) {
562 remoting.HostDispatcher.runCallback_.bind(null, onDone, onError);
563 switch (this.state_) {
564 case remoting.HostDispatcher.State.UNKNOWN:
565 this.pendingRequests_.push(
566 this.deletePairedClient.bind(this, client, onDone, onError));
568 case remoting.HostDispatcher.State.NATIVE_MESSAGING:
569 this.nativeMessagingHost_.deletePairedClient(client, callback, onError);
571 case remoting.HostDispatcher.State.NPAPI:
573 this.npapiHost_.deletePairedClient(client, callback);
575 onError(remoting.Error.MISSING_PLUGIN);
578 case remoting.HostDispatcher.State.NOT_INSTALLED:
579 onError(remoting.Error.MISSING_PLUGIN);
585 * @param {function(string):void} onDone
586 * @param {function(remoting.Error):void} onError
589 remoting.HostDispatcher.prototype.getHostClientId =
590 function(onDone, onError) {
591 switch (this.state_) {
592 case remoting.HostDispatcher.State.UNKNOWN:
593 this.pendingRequests_.push(
594 this.getHostClientId.bind(this, onDone, onError));
596 case remoting.HostDispatcher.State.NATIVE_MESSAGING:
597 this.nativeMessagingHost_.getHostClientId(onDone, onError);
599 case remoting.HostDispatcher.State.NPAPI:
600 // The NPAPI plugin is packaged with the webapp, not the host, so it
601 // doesn't have access to the API keys baked into the installed host.
602 onError(remoting.Error.UNEXPECTED);
604 case remoting.HostDispatcher.State.NOT_INSTALLED:
605 onError(remoting.Error.MISSING_PLUGIN);
611 * @param {string} authorizationCode
612 * @param {function(string, string):void} onDone
613 * @param {function(remoting.Error):void} onError
616 remoting.HostDispatcher.prototype.getCredentialsFromAuthCode =
617 function(authorizationCode, onDone, onError) {
618 switch (this.state_) {
619 case remoting.HostDispatcher.State.UNKNOWN:
620 this.pendingRequests_.push(
621 this.getCredentialsFromAuthCode.bind(
622 this, authorizationCode, onDone, onError));
624 case remoting.HostDispatcher.State.NATIVE_MESSAGING:
625 this.nativeMessagingHost_.getCredentialsFromAuthCode(
626 authorizationCode, onDone, onError);
628 case remoting.HostDispatcher.State.NPAPI:
629 // The NPAPI plugin is packaged with the webapp, not the host, so it
630 // doesn't have access to the API keys baked into the installed host.
631 onError(remoting.Error.UNEXPECTED);
633 case remoting.HostDispatcher.State.NOT_INSTALLED:
634 onError(remoting.Error.MISSING_PLUGIN);
640 * Returns true if the NPAPI plugin is being used.
643 remoting.HostDispatcher.prototype.usingNpapiPlugin = function() {
644 return this.state_ == remoting.HostDispatcher.State.NPAPI;