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.HostDispatcher} */
34 /** @type {remoting.HostNativeMessaging} @private */
35 this.nativeMessagingHost_ = new remoting.HostNativeMessaging();
37 /** @type {remoting.HostPlugin} @private */
38 this.npapiHost_ = null;
40 /** @type {remoting.HostDispatcher.State} */
41 this.state_ = remoting.HostDispatcher.State.UNKNOWN;
43 /** @type {Array.<function()>} */
44 this.pendingRequests_ = [];
46 function sendPendingRequests() {
47 for (var i = 0; i < that.pendingRequests_.length; i++) {
48 that.pendingRequests_[i]();
50 that.pendingRequests_ = null;
53 function onNativeMessagingInit() {
54 console.log('Native Messaging supported.');
55 that.state_ = remoting.HostDispatcher.State.NATIVE_MESSAGING;
56 sendPendingRequests();
59 function onNativeMessagingFailed(error) {
60 console.log('Native Messaging unsupported, falling back to NPAPI.');
61 that.npapiHost_ = createPluginCallback();
62 that.state_ = remoting.HostDispatcher.State.NPAPI;
63 sendPendingRequests();
66 this.nativeMessagingHost_.initialize(onNativeMessagingInit,
67 onNativeMessagingFailed);
71 remoting.HostDispatcher.State = {
78 * @param {remoting.HostController.Feature} feature The feature to test for.
79 * @param {function(boolean):void} onDone
82 remoting.HostDispatcher.prototype.hasFeature = function(
84 switch (this.state_) {
85 case remoting.HostDispatcher.State.UNKNOWN:
86 this.pendingRequests_.push(
87 this.hasFeature.bind(this, feature, onDone));
89 case remoting.HostDispatcher.State.NATIVE_MESSAGING:
90 onDone(this.nativeMessagingHost_.hasFeature(feature));
92 case remoting.HostDispatcher.State.NPAPI:
93 // If this is an old NPAPI plugin that doesn't list supportedFeatures,
94 // assume it is an old plugin that doesn't support any new feature.
95 var supportedFeatures = [];
96 if (typeof(this.npapiHost_.supportedFeatures) == 'string') {
97 supportedFeatures = this.npapiHost_.supportedFeatures.split(' ');
99 onDone(supportedFeatures.indexOf(feature) >= 0);
105 * @param {function(string):void} onDone
106 * @param {function(remoting.Error):void} onError
109 remoting.HostDispatcher.prototype.getHostName = function(onDone, onError) {
110 switch (this.state_) {
111 case remoting.HostDispatcher.State.UNKNOWN:
112 this.pendingRequests_.push(
113 this.getHostName.bind(this, onDone, onError));
115 case remoting.HostDispatcher.State.NATIVE_MESSAGING:
116 this.nativeMessagingHost_.getHostName(onDone, onError);
118 case remoting.HostDispatcher.State.NPAPI:
120 this.npapiHost_.getHostName(onDone);
122 onError(remoting.Error.MISSING_PLUGIN);
129 * @param {string} hostId
130 * @param {string} pin
131 * @param {function(string):void} onDone
132 * @param {function(remoting.Error):void} onError
135 remoting.HostDispatcher.prototype.getPinHash =
136 function(hostId, pin, onDone, onError) {
137 switch (this.state_) {
138 case remoting.HostDispatcher.State.UNKNOWN:
139 this.pendingRequests_.push(
140 this.getPinHash.bind(this, hostId, pin, onDone, onError));
142 case remoting.HostDispatcher.State.NATIVE_MESSAGING:
143 this.nativeMessagingHost_.getPinHash(hostId, pin, onDone, onError);
145 case remoting.HostDispatcher.State.NPAPI:
147 this.npapiHost_.getPinHash(hostId, pin, onDone);
149 onError(remoting.Error.MISSING_PLUGIN);
156 * @param {function(string, string):void} onDone
157 * @param {function(remoting.Error):void} onError
160 remoting.HostDispatcher.prototype.generateKeyPair = function(onDone, onError) {
161 switch (this.state_) {
162 case remoting.HostDispatcher.State.UNKNOWN:
163 this.pendingRequests_.push(
164 this.generateKeyPair.bind(this, onDone, onError));
166 case remoting.HostDispatcher.State.NATIVE_MESSAGING:
167 this.nativeMessagingHost_.generateKeyPair(onDone, onError);
169 case remoting.HostDispatcher.State.NPAPI:
171 this.npapiHost_.generateKeyPair(onDone);
173 onError(remoting.Error.MISSING_PLUGIN);
180 * @param {Object} config
181 * @param {function(remoting.HostController.AsyncResult):void} onDone
182 * @param {function(remoting.Error):void} onError
185 remoting.HostDispatcher.prototype.updateDaemonConfig =
186 function(config, onDone, onError) {
187 switch (this.state_) {
188 case remoting.HostDispatcher.State.UNKNOWN:
189 this.pendingRequests_.push(
190 this.updateDaemonConfig.bind(this, config, onDone, onError));
192 case remoting.HostDispatcher.State.NATIVE_MESSAGING:
193 this.nativeMessagingHost_.updateDaemonConfig(config, onDone, onError);
195 case remoting.HostDispatcher.State.NPAPI:
197 this.npapiHost_.updateDaemonConfig(JSON.stringify(config), onDone);
199 onError(remoting.Error.MISSING_PLUGIN);
206 * @param {function(Object):void} onDone
207 * @param {function(remoting.Error):void} onError
210 remoting.HostDispatcher.prototype.getDaemonConfig = function(onDone, onError) {
212 * Converts the config string from the NPAPI plugin to an Object, to pass to
214 * @param {string} configStr
217 function callbackForNpapi(configStr) {
218 var config = jsonParseSafe(configStr);
219 if (typeof(config) != 'object') {
220 onError(remoting.Error.UNEXPECTED);
222 onDone(/** @type {Object} */ (config));
226 switch (this.state_) {
227 case remoting.HostDispatcher.State.UNKNOWN:
228 this.pendingRequests_.push(
229 this.getDaemonConfig.bind(this, onDone, onError));
231 case remoting.HostDispatcher.State.NATIVE_MESSAGING:
232 this.nativeMessagingHost_.getDaemonConfig(onDone, onError);
234 case remoting.HostDispatcher.State.NPAPI:
236 this.npapiHost_.getDaemonConfig(callbackForNpapi);
238 onError(remoting.Error.MISSING_PLUGIN);
245 * @param {function(string):void} onDone
246 * @param {function(remoting.Error):void} onError
249 remoting.HostDispatcher.prototype.getDaemonVersion = function(onDone, onError) {
250 switch (this.state_) {
251 case remoting.HostDispatcher.State.UNKNOWN:
252 this.pendingRequests_.push(
253 this.getDaemonVersion.bind(this, onDone, onError));
255 case remoting.HostDispatcher.State.NATIVE_MESSAGING:
256 onDone(this.nativeMessagingHost_.getDaemonVersion());
258 case remoting.HostDispatcher.State.NPAPI:
260 this.npapiHost_.getDaemonVersion(onDone);
262 onError(remoting.Error.MISSING_PLUGIN);
269 * @param {function(boolean, boolean, boolean):void} onDone
270 * @param {function(remoting.Error):void} onError
273 remoting.HostDispatcher.prototype.getUsageStatsConsent =
274 function(onDone, onError) {
275 switch (this.state_) {
276 case remoting.HostDispatcher.State.UNKNOWN:
277 this.pendingRequests_.push(
278 this.getUsageStatsConsent.bind(this, onDone, onError));
280 case remoting.HostDispatcher.State.NATIVE_MESSAGING:
281 this.nativeMessagingHost_.getUsageStatsConsent(onDone, onError);
283 case remoting.HostDispatcher.State.NPAPI:
285 this.npapiHost_.getUsageStatsConsent(onDone);
287 onError(remoting.Error.MISSING_PLUGIN);
294 * @param {Object} config
295 * @param {boolean} consent
296 * @param {function(remoting.HostController.AsyncResult):void} onDone
297 * @param {function(remoting.Error):void} onError
300 remoting.HostDispatcher.prototype.startDaemon =
301 function(config, consent, onDone, onError) {
302 switch (this.state_) {
303 case remoting.HostDispatcher.State.UNKNOWN:
304 this.pendingRequests_.push(
305 this.startDaemon.bind(this, config, consent, onDone, onError));
307 case remoting.HostDispatcher.State.NATIVE_MESSAGING:
308 this.nativeMessagingHost_.startDaemon(config, consent, onDone, onError);
310 case remoting.HostDispatcher.State.NPAPI:
312 this.npapiHost_.startDaemon(JSON.stringify(config), consent, onDone);
314 onError(remoting.Error.MISSING_PLUGIN);
321 * @param {function(remoting.HostController.AsyncResult):void} onDone
322 * @param {function(remoting.Error):void} onError
325 remoting.HostDispatcher.prototype.stopDaemon = function(onDone, onError) {
326 switch (this.state_) {
327 case remoting.HostDispatcher.State.UNKNOWN:
328 this.pendingRequests_.push(this.stopDaemon.bind(this, onDone, onError));
330 case remoting.HostDispatcher.State.NATIVE_MESSAGING:
331 this.nativeMessagingHost_.stopDaemon(onDone, onError);
333 case remoting.HostDispatcher.State.NPAPI:
335 this.npapiHost_.stopDaemon(onDone);
337 onError(remoting.Error.MISSING_PLUGIN);
344 * @param {function(remoting.HostController.State):void} onDone
345 * @param {function(remoting.Error):void} onError
348 remoting.HostDispatcher.prototype.getDaemonState = function(onDone, onError) {
349 switch (this.state_) {
350 case remoting.HostDispatcher.State.UNKNOWN:
351 this.pendingRequests_.push(
352 this.getDaemonState.bind(this, onDone, onError));
354 case remoting.HostDispatcher.State.NATIVE_MESSAGING:
355 this.nativeMessagingHost_.getDaemonState(onDone, onError);
357 case remoting.HostDispatcher.State.NPAPI:
358 // Call the callback directly, since NPAPI exposes the state directly as
359 // a field member, rather than an asynchronous method.
360 var state = this.npapiHost_.daemonState;
361 if (state === undefined) {
362 onError(remoting.Error.MISSING_PLUGIN);
371 * @param {function(Array.<remoting.PairedClient>):void} onDone
372 * @param {function(remoting.Error):void} onError
375 remoting.HostDispatcher.prototype.getPairedClients = function(onDone, onError) {
377 * Converts the JSON string from the NPAPI plugin to Array.<PairedClient>, to
379 * @param {string} pairedClientsJson
382 function callbackForNpapi(pairedClientsJson) {
383 var pairedClients = remoting.PairedClient.convertToPairedClientArray(
384 jsonParseSafe(pairedClientsJson));
385 if (pairedClients != null) {
386 onDone(pairedClients);
388 onError(remoting.Error.UNEXPECTED);
392 switch (this.state_) {
393 case remoting.HostDispatcher.State.UNKNOWN:
394 this.pendingRequests_.push(
395 this.getPairedClients.bind(this, onDone, onError));
397 case remoting.HostDispatcher.State.NATIVE_MESSAGING:
398 this.nativeMessagingHost_.getPairedClients(onDone, onError);
400 case remoting.HostDispatcher.State.NPAPI:
402 this.npapiHost_.getPairedClients(callbackForNpapi);
404 onError(remoting.Error.MISSING_PLUGIN);
411 * The pairing API returns a boolean to indicate success or failure, but
412 * the JS API is defined in terms of onDone and onError callbacks. This
413 * function converts one to the other.
415 * @param {function():void} onDone Success callback.
416 * @param {function(remoting.Error):void} onError Error callback.
417 * @param {boolean} success True if the operation succeeded; false otherwise.
420 remoting.HostDispatcher.runCallback_ = function(onDone, onError, success) {
424 onError(remoting.Error.UNEXPECTED);
429 * @param {function():void} onDone
430 * @param {function(remoting.Error):void} onError
433 remoting.HostDispatcher.prototype.clearPairedClients =
434 function(onDone, onError) {
436 remoting.HostDispatcher.runCallback_.bind(null, onDone, onError);
437 switch (this.state_) {
438 case remoting.HostDispatcher.State.UNKNOWN:
439 this.pendingRequests_.push(
440 this.clearPairedClients.bind(this, onDone, onError));
442 case remoting.HostDispatcher.State.NATIVE_MESSAGING:
443 this.nativeMessagingHost_.clearPairedClients(callback, onError);
445 case remoting.HostDispatcher.State.NPAPI:
447 this.npapiHost_.clearPairedClients(callback);
449 onError(remoting.Error.MISSING_PLUGIN);
456 * @param {string} client
457 * @param {function():void} onDone
458 * @param {function(remoting.Error):void} onError
461 remoting.HostDispatcher.prototype.deletePairedClient =
462 function(client, onDone, onError) {
464 remoting.HostDispatcher.runCallback_.bind(null, onDone, onError);
465 switch (this.state_) {
466 case remoting.HostDispatcher.State.UNKNOWN:
467 this.pendingRequests_.push(
468 this.deletePairedClient.bind(this, client, onDone, onError));
470 case remoting.HostDispatcher.State.NATIVE_MESSAGING:
471 this.nativeMessagingHost_.deletePairedClient(client, callback, onError);
473 case remoting.HostDispatcher.State.NPAPI:
475 this.npapiHost_.deletePairedClient(client, callback);
477 onError(remoting.Error.MISSING_PLUGIN);
484 * @param {function(string):void} onDone
485 * @param {function(remoting.Error):void} onError
488 remoting.HostDispatcher.prototype.getHostClientId =
489 function(onDone, onError) {
490 switch (this.state_) {
491 case remoting.HostDispatcher.State.UNKNOWN:
492 this.pendingRequests_.push(
493 this.getHostClientId.bind(this, onDone, onError));
495 case remoting.HostDispatcher.State.NATIVE_MESSAGING:
496 this.nativeMessagingHost_.getHostClientId(onDone, onError);
498 case remoting.HostDispatcher.State.NPAPI:
499 // The NPAPI plugin is packaged with the webapp, not the host, so it
500 // doesn't have access to the API keys baked into the installed host.
501 onError(remoting.Error.UNEXPECTED);
507 * @param {string} authorizationCode
508 * @param {function(string, string):void} onDone
509 * @param {function(remoting.Error):void} onError
512 remoting.HostDispatcher.prototype.getCredentialsFromAuthCode =
513 function(authorizationCode, onDone, onError) {
514 switch (this.state_) {
515 case remoting.HostDispatcher.State.UNKNOWN:
516 this.pendingRequests_.push(
517 this.getCredentialsFromAuthCode.bind(
518 this, authorizationCode, onDone, onError));
520 case remoting.HostDispatcher.State.NATIVE_MESSAGING:
521 this.nativeMessagingHost_.getCredentialsFromAuthCode(
522 authorizationCode, onDone, onError);
524 case remoting.HostDispatcher.State.NPAPI:
525 // The NPAPI plugin is packaged with the webapp, not the host, so it
526 // doesn't have access to the API keys baked into the installed host.
527 onError(remoting.Error.UNEXPECTED);