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} */
38 this.state_
= remoting
.HostDispatcher
.State
.UNKNOWN
;
40 /** @type {Array.<function()>} */
41 this.pendingRequests_
= [];
43 this.createPluginCallback_
= createPluginCallback
;
45 this.tryToInitialize_();
49 remoting
.HostDispatcher
.State
= {
56 remoting
.HostDispatcher
.prototype.tryToInitialize_ = function() {
57 /** @type {remoting.HostDispatcher} */
60 if (this.state_
!= remoting
.HostDispatcher
.State
.UNKNOWN
)
63 function sendPendingRequests() {
64 var pendingRequests
= that
.pendingRequests_
;
65 that
.pendingRequests_
= [];
66 for (var i
= 0; i
< pendingRequests
.length
; i
++) {
71 function onNativeMessagingInit() {
72 that
.state_
= remoting
.HostDispatcher
.State
.NATIVE_MESSAGING
;
73 sendPendingRequests();
76 function onNativeMessagingFailed(error
) {
77 that
.npapiHost_
= that
.createPluginCallback_();
79 that
.state_
= that
.npapiHost_
? remoting
.HostDispatcher
.State
.NPAPI
80 : remoting
.HostDispatcher
.State
.NOT_INSTALLED
;
81 sendPendingRequests();
84 this.nativeMessagingHost_
.initialize(onNativeMessagingInit
,
85 onNativeMessagingFailed
);
89 * @param {remoting.HostController.Feature} feature The feature to test for.
90 * @param {function(boolean):void} onDone
93 remoting
.HostDispatcher
.prototype.hasFeature = function(
95 switch (this.state_
) {
96 case remoting
.HostDispatcher
.State
.UNKNOWN
:
97 this.pendingRequests_
.push(
98 this.hasFeature
.bind(this, feature
, onDone
));
100 case remoting
.HostDispatcher
.State
.NATIVE_MESSAGING
:
101 onDone(this.nativeMessagingHost_
.hasFeature(feature
));
103 case remoting
.HostDispatcher
.State
.NPAPI
:
104 // If this is an old NPAPI plugin that doesn't list supportedFeatures,
105 // assume it is an old plugin that doesn't support any new feature.
106 var supportedFeatures
= [];
107 if (typeof(this.npapiHost_
.supportedFeatures
) == 'string') {
108 supportedFeatures
= this.npapiHost_
.supportedFeatures
.split(' ');
110 onDone(supportedFeatures
.indexOf(feature
) >= 0);
112 case remoting
.HostDispatcher
.State
.NOT_INSTALLED
:
119 * @param {function(string):void} onDone
120 * @param {function(remoting.Error):void} onError
123 remoting
.HostDispatcher
.prototype.getHostName = function(onDone
, onError
) {
124 switch (this.state_
) {
125 case remoting
.HostDispatcher
.State
.UNKNOWN
:
126 this.pendingRequests_
.push(
127 this.getHostName
.bind(this, onDone
, onError
));
129 case remoting
.HostDispatcher
.State
.NATIVE_MESSAGING
:
130 this.nativeMessagingHost_
.getHostName(onDone
, onError
);
132 case remoting
.HostDispatcher
.State
.NPAPI
:
134 this.npapiHost_
.getHostName(onDone
);
136 onError(remoting
.Error
.MISSING_PLUGIN
);
139 case remoting
.HostDispatcher
.State
.NOT_INSTALLED
:
140 onError(remoting
.Error
.MISSING_PLUGIN
);
146 * @param {string} hostId
147 * @param {string} pin
148 * @param {function(string):void} onDone
149 * @param {function(remoting.Error):void} onError
152 remoting
.HostDispatcher
.prototype.getPinHash
=
153 function(hostId
, pin
, onDone
, onError
) {
154 switch (this.state_
) {
155 case remoting
.HostDispatcher
.State
.UNKNOWN
:
156 this.pendingRequests_
.push(
157 this.getPinHash
.bind(this, hostId
, pin
, onDone
, onError
));
159 case remoting
.HostDispatcher
.State
.NATIVE_MESSAGING
:
160 this.nativeMessagingHost_
.getPinHash(hostId
, pin
, onDone
, onError
);
162 case remoting
.HostDispatcher
.State
.NPAPI
:
164 this.npapiHost_
.getPinHash(hostId
, pin
, onDone
);
166 onError(remoting
.Error
.MISSING_PLUGIN
);
169 case remoting
.HostDispatcher
.State
.NOT_INSTALLED
:
170 onError(remoting
.Error
.MISSING_PLUGIN
);
176 * @param {function(string, string):void} onDone
177 * @param {function(remoting.Error):void} onError
180 remoting
.HostDispatcher
.prototype.generateKeyPair = function(onDone
, onError
) {
181 switch (this.state_
) {
182 case remoting
.HostDispatcher
.State
.UNKNOWN
:
183 this.pendingRequests_
.push(
184 this.generateKeyPair
.bind(this, onDone
, onError
));
186 case remoting
.HostDispatcher
.State
.NATIVE_MESSAGING
:
187 this.nativeMessagingHost_
.generateKeyPair(onDone
, onError
);
189 case remoting
.HostDispatcher
.State
.NPAPI
:
191 this.npapiHost_
.generateKeyPair(onDone
);
193 onError(remoting
.Error
.MISSING_PLUGIN
);
196 case remoting
.HostDispatcher
.State
.NOT_INSTALLED
:
197 onError(remoting
.Error
.MISSING_PLUGIN
);
203 * @param {Object} config
204 * @param {function(remoting.HostController.AsyncResult):void} onDone
205 * @param {function(remoting.Error):void} onError
208 remoting
.HostDispatcher
.prototype.updateDaemonConfig
=
209 function(config
, onDone
, onError
) {
210 switch (this.state_
) {
211 case remoting
.HostDispatcher
.State
.UNKNOWN
:
212 this.pendingRequests_
.push(
213 this.updateDaemonConfig
.bind(this, config
, onDone
, onError
));
215 case remoting
.HostDispatcher
.State
.NATIVE_MESSAGING
:
216 this.nativeMessagingHost_
.updateDaemonConfig(config
, onDone
, onError
);
218 case remoting
.HostDispatcher
.State
.NPAPI
:
220 this.npapiHost_
.updateDaemonConfig(JSON
.stringify(config
), onDone
);
222 onError(remoting
.Error
.MISSING_PLUGIN
);
225 case remoting
.HostDispatcher
.State
.NOT_INSTALLED
:
226 onError(remoting
.Error
.MISSING_PLUGIN
);
232 * @param {function(Object):void} onDone
233 * @param {function(remoting.Error):void} onError
236 remoting
.HostDispatcher
.prototype.getDaemonConfig = function(onDone
, onError
) {
238 * Converts the config string from the NPAPI plugin to an Object, to pass to
240 * @param {string} configStr
243 function callbackForNpapi(configStr
) {
244 var config
= jsonParseSafe(configStr
);
245 if (typeof(config
) != 'object') {
246 onError(remoting
.Error
.UNEXPECTED
);
248 onDone(/** @type {Object} */ (config
));
252 switch (this.state_
) {
253 case remoting
.HostDispatcher
.State
.UNKNOWN
:
254 this.pendingRequests_
.push(
255 this.getDaemonConfig
.bind(this, onDone
, onError
));
257 case remoting
.HostDispatcher
.State
.NATIVE_MESSAGING
:
258 this.nativeMessagingHost_
.getDaemonConfig(onDone
, onError
);
260 case remoting
.HostDispatcher
.State
.NPAPI
:
262 this.npapiHost_
.getDaemonConfig(callbackForNpapi
);
264 onError(remoting
.Error
.MISSING_PLUGIN
);
267 case remoting
.HostDispatcher
.State
.NOT_INSTALLED
:
274 * @param {function(string):void} onDone
275 * @param {function(remoting.Error):void} onError
278 remoting
.HostDispatcher
.prototype.getDaemonVersion = function(onDone
, onError
) {
279 switch (this.state_
) {
280 case remoting
.HostDispatcher
.State
.UNKNOWN
:
281 this.pendingRequests_
.push(
282 this.getDaemonVersion
.bind(this, onDone
, onError
));
284 case remoting
.HostDispatcher
.State
.NATIVE_MESSAGING
:
285 onDone(this.nativeMessagingHost_
.getDaemonVersion());
287 case remoting
.HostDispatcher
.State
.NPAPI
:
289 this.npapiHost_
.getDaemonVersion(onDone
);
291 onError(remoting
.Error
.MISSING_PLUGIN
);
294 case remoting
.HostDispatcher
.State
.NOT_INSTALLED
:
295 onError(remoting
.Error
.MISSING_PLUGIN
);
301 * @param {function(boolean, boolean, boolean):void} onDone
302 * @param {function(remoting.Error):void} onError
305 remoting
.HostDispatcher
.prototype.getUsageStatsConsent
=
306 function(onDone
, onError
) {
307 switch (this.state_
) {
308 case remoting
.HostDispatcher
.State
.UNKNOWN
:
309 this.pendingRequests_
.push(
310 this.getUsageStatsConsent
.bind(this, onDone
, onError
));
312 case remoting
.HostDispatcher
.State
.NATIVE_MESSAGING
:
313 this.nativeMessagingHost_
.getUsageStatsConsent(onDone
, onError
);
315 case remoting
.HostDispatcher
.State
.NPAPI
:
317 this.npapiHost_
.getUsageStatsConsent(onDone
);
319 onError(remoting
.Error
.MISSING_PLUGIN
);
322 case remoting
.HostDispatcher
.State
.NOT_INSTALLED
:
323 onError(remoting
.Error
.MISSING_PLUGIN
);
329 * @param {Object} config
330 * @param {boolean} consent
331 * @param {function(remoting.HostController.AsyncResult):void} onDone
332 * @param {function(remoting.Error):void} onError
335 remoting
.HostDispatcher
.prototype.startDaemon
=
336 function(config
, consent
, onDone
, onError
) {
337 switch (this.state_
) {
338 case remoting
.HostDispatcher
.State
.UNKNOWN
:
339 this.pendingRequests_
.push(
340 this.startDaemon
.bind(this, config
, consent
, onDone
, onError
));
342 case remoting
.HostDispatcher
.State
.NATIVE_MESSAGING
:
343 this.nativeMessagingHost_
.startDaemon(config
, consent
, onDone
, onError
);
345 case remoting
.HostDispatcher
.State
.NPAPI
:
347 this.npapiHost_
.startDaemon(JSON
.stringify(config
), consent
, onDone
);
349 onError(remoting
.Error
.MISSING_PLUGIN
);
352 case remoting
.HostDispatcher
.State
.NOT_INSTALLED
:
353 onError(remoting
.Error
.MISSING_PLUGIN
);
359 * @param {function(remoting.HostController.AsyncResult):void} onDone
360 * @param {function(remoting.Error):void} onError
363 remoting
.HostDispatcher
.prototype.stopDaemon = function(onDone
, onError
) {
364 switch (this.state_
) {
365 case remoting
.HostDispatcher
.State
.UNKNOWN
:
366 this.pendingRequests_
.push(this.stopDaemon
.bind(this, onDone
, onError
));
368 case remoting
.HostDispatcher
.State
.NATIVE_MESSAGING
:
369 this.nativeMessagingHost_
.stopDaemon(onDone
, onError
);
371 case remoting
.HostDispatcher
.State
.NPAPI
:
373 this.npapiHost_
.stopDaemon(onDone
);
375 onError(remoting
.Error
.MISSING_PLUGIN
);
378 case remoting
.HostDispatcher
.State
.NOT_INSTALLED
:
379 onError(remoting
.Error
.MISSING_PLUGIN
);
385 * @param {function(remoting.HostController.State):void} onDone
386 * @param {function(remoting.Error):void} onError
389 remoting
.HostDispatcher
.prototype.getDaemonState = function(onDone
, onError
) {
390 // If the host was in not-initialized state try initializing it again in case
392 if (this.state_
== remoting
.HostDispatcher
.State
.NOT_INSTALLED
) {
393 this.state_
= remoting
.HostDispatcher
.State
.UNKNOWN
;
394 this.tryToInitialize_();
397 this.getDaemonStateInternal_(onDone
, onError
);
401 * @param {function(remoting.HostController.State):void} onDone
402 * @param {function(remoting.Error):void} onError
405 remoting
.HostDispatcher
.prototype.getDaemonStateInternal_
=
406 function(onDone
, onError
) {
407 switch (this.state_
) {
408 case remoting
.HostDispatcher
.State
.UNKNOWN
:
409 this.pendingRequests_
.push(
410 this.getDaemonStateInternal_
.bind(this, onDone
, onError
));
412 case remoting
.HostDispatcher
.State
.NATIVE_MESSAGING
:
413 this.nativeMessagingHost_
.getDaemonState(onDone
, onError
);
415 case remoting
.HostDispatcher
.State
.NPAPI
:
416 // Call the callback directly, since NPAPI exposes the state directly as
417 // a field member, rather than an asynchronous method.
418 var state
= this.npapiHost_
.daemonState
;
419 if (state
=== undefined) {
420 onError(remoting
.Error
.MISSING_PLUGIN
);
425 case remoting
.HostDispatcher
.State
.NOT_INSTALLED
:
426 onDone(remoting
.HostController
.State
.NOT_INSTALLED
);
432 * @param {function(Array.<remoting.PairedClient>):void} onDone
433 * @param {function(remoting.Error):void} onError
436 remoting
.HostDispatcher
.prototype.getPairedClients = function(onDone
, onError
) {
438 * Converts the JSON string from the NPAPI plugin to Array.<PairedClient>, to
440 * @param {string} pairedClientsJson
443 function callbackForNpapi(pairedClientsJson
) {
444 var pairedClients
= remoting
.PairedClient
.convertToPairedClientArray(
445 jsonParseSafe(pairedClientsJson
));
446 if (pairedClients
!= null) {
447 onDone(pairedClients
);
449 onError(remoting
.Error
.UNEXPECTED
);
453 switch (this.state_
) {
454 case remoting
.HostDispatcher
.State
.UNKNOWN
:
455 this.pendingRequests_
.push(
456 this.getPairedClients
.bind(this, onDone
, onError
));
458 case remoting
.HostDispatcher
.State
.NATIVE_MESSAGING
:
459 this.nativeMessagingHost_
.getPairedClients(onDone
, onError
);
461 case remoting
.HostDispatcher
.State
.NPAPI
:
463 this.npapiHost_
.getPairedClients(callbackForNpapi
);
465 onError(remoting
.Error
.MISSING_PLUGIN
);
468 case remoting
.HostDispatcher
.State
.NOT_INSTALLED
:
469 onError(remoting
.Error
.MISSING_PLUGIN
);
475 * The pairing API returns a boolean to indicate success or failure, but
476 * the JS API is defined in terms of onDone and onError callbacks. This
477 * function converts one to the other.
479 * @param {function():void} onDone Success callback.
480 * @param {function(remoting.Error):void} onError Error callback.
481 * @param {boolean} success True if the operation succeeded; false otherwise.
484 remoting
.HostDispatcher
.runCallback_ = function(onDone
, onError
, success
) {
488 onError(remoting
.Error
.UNEXPECTED
);
493 * @param {function():void} onDone
494 * @param {function(remoting.Error):void} onError
497 remoting
.HostDispatcher
.prototype.clearPairedClients
=
498 function(onDone
, onError
) {
500 remoting
.HostDispatcher
.runCallback_
.bind(null, onDone
, onError
);
501 switch (this.state_
) {
502 case remoting
.HostDispatcher
.State
.UNKNOWN
:
503 this.pendingRequests_
.push(
504 this.clearPairedClients
.bind(this, onDone
, onError
));
506 case remoting
.HostDispatcher
.State
.NATIVE_MESSAGING
:
507 this.nativeMessagingHost_
.clearPairedClients(callback
, onError
);
509 case remoting
.HostDispatcher
.State
.NPAPI
:
511 this.npapiHost_
.clearPairedClients(callback
);
513 onError(remoting
.Error
.MISSING_PLUGIN
);
516 case remoting
.HostDispatcher
.State
.NOT_INSTALLED
:
517 onError(remoting
.Error
.MISSING_PLUGIN
);
523 * @param {string} client
524 * @param {function():void} onDone
525 * @param {function(remoting.Error):void} onError
528 remoting
.HostDispatcher
.prototype.deletePairedClient
=
529 function(client
, 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.deletePairedClient
.bind(this, client
, onDone
, onError
));
537 case remoting
.HostDispatcher
.State
.NATIVE_MESSAGING
:
538 this.nativeMessagingHost_
.deletePairedClient(client
, callback
, onError
);
540 case remoting
.HostDispatcher
.State
.NPAPI
:
542 this.npapiHost_
.deletePairedClient(client
, callback
);
544 onError(remoting
.Error
.MISSING_PLUGIN
);
547 case remoting
.HostDispatcher
.State
.NOT_INSTALLED
:
548 onError(remoting
.Error
.MISSING_PLUGIN
);
554 * @param {function(string):void} onDone
555 * @param {function(remoting.Error):void} onError
558 remoting
.HostDispatcher
.prototype.getHostClientId
=
559 function(onDone
, onError
) {
560 switch (this.state_
) {
561 case remoting
.HostDispatcher
.State
.UNKNOWN
:
562 this.pendingRequests_
.push(
563 this.getHostClientId
.bind(this, onDone
, onError
));
565 case remoting
.HostDispatcher
.State
.NATIVE_MESSAGING
:
566 this.nativeMessagingHost_
.getHostClientId(onDone
, onError
);
568 case remoting
.HostDispatcher
.State
.NPAPI
:
569 // The NPAPI plugin is packaged with the webapp, not the host, so it
570 // doesn't have access to the API keys baked into the installed host.
571 onError(remoting
.Error
.UNEXPECTED
);
573 case remoting
.HostDispatcher
.State
.NOT_INSTALLED
:
574 onError(remoting
.Error
.MISSING_PLUGIN
);
580 * @param {string} authorizationCode
581 * @param {function(string, string):void} onDone
582 * @param {function(remoting.Error):void} onError
585 remoting
.HostDispatcher
.prototype.getCredentialsFromAuthCode
=
586 function(authorizationCode
, onDone
, onError
) {
587 switch (this.state_
) {
588 case remoting
.HostDispatcher
.State
.UNKNOWN
:
589 this.pendingRequests_
.push(
590 this.getCredentialsFromAuthCode
.bind(
591 this, authorizationCode
, onDone
, onError
));
593 case remoting
.HostDispatcher
.State
.NATIVE_MESSAGING
:
594 this.nativeMessagingHost_
.getCredentialsFromAuthCode(
595 authorizationCode
, onDone
, onError
);
597 case remoting
.HostDispatcher
.State
.NPAPI
:
598 // The NPAPI plugin is packaged with the webapp, not the host, so it
599 // doesn't have access to the API keys baked into the installed host.
600 onError(remoting
.Error
.UNEXPECTED
);
602 case remoting
.HostDispatcher
.State
.NOT_INSTALLED
:
603 onError(remoting
.Error
.MISSING_PLUGIN
);
609 * Returns true if the NPAPI plugin is being used.
612 remoting
.HostDispatcher
.prototype.usingNpapiPlugin = function() {
613 return this.state_
== remoting
.HostDispatcher
.State
.NPAPI
;