rAc - revert invalid suggestions to edit mode
[chromium-blink-merge.git] / remoting / webapp / host_dispatcher.js
blob5dd3bc9a1d183940c61921e17e93306f18fd561d
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.
5 /**
6 * @fileoverview
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
11 * of the test.
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.
19 'use strict';
21 /** @suppress {duplicate} */
22 var remoting = remoting || {};
24 /**
25 * @constructor
26 * @param {function():remoting.HostPlugin} createPluginCallback Callback to
27 * instantiate the NPAPI plugin when NativeMessaging is determined to be
28 * unsupported.
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_();
48 /** @enum {number} */
49 remoting.HostDispatcher.State = {
50 UNKNOWN: 0,
51 NATIVE_MESSAGING: 1,
52 NPAPI: 2,
53 NOT_INSTALLED: 3
56 remoting.HostDispatcher.prototype.tryToInitialize_ = function() {
57 /** @type {remoting.HostDispatcher} */
58 var that = this;
60 if (this.state_ != remoting.HostDispatcher.State.UNKNOWN)
61 return;
63 function sendPendingRequests() {
64 var pendingRequests = that.pendingRequests_;
65 that.pendingRequests_ = [];
66 for (var i = 0; i < pendingRequests.length; i++) {
67 pendingRequests[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);
88 /**
89 * @param {remoting.HostController.Feature} feature The feature to test for.
90 * @param {function(boolean):void} onDone
91 * @return {void}
93 remoting.HostDispatcher.prototype.hasFeature = function(
94 feature, onDone) {
95 switch (this.state_) {
96 case remoting.HostDispatcher.State.UNKNOWN:
97 this.pendingRequests_.push(
98 this.hasFeature.bind(this, feature, onDone));
99 break;
100 case remoting.HostDispatcher.State.NATIVE_MESSAGING:
101 onDone(this.nativeMessagingHost_.hasFeature(feature));
102 break;
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);
111 break;
112 case remoting.HostDispatcher.State.NOT_INSTALLED:
113 onDone(false);
114 break;
119 * @param {function(string):void} onDone
120 * @param {function(remoting.Error):void} onError
121 * @return {void}
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));
128 break;
129 case remoting.HostDispatcher.State.NATIVE_MESSAGING:
130 this.nativeMessagingHost_.getHostName(onDone, onError);
131 break;
132 case remoting.HostDispatcher.State.NPAPI:
133 try {
134 this.npapiHost_.getHostName(onDone);
135 } catch (err) {
136 onError(remoting.Error.MISSING_PLUGIN);
138 break;
139 case remoting.HostDispatcher.State.NOT_INSTALLED:
140 onError(remoting.Error.MISSING_PLUGIN);
141 break;
146 * @param {string} hostId
147 * @param {string} pin
148 * @param {function(string):void} onDone
149 * @param {function(remoting.Error):void} onError
150 * @return {void}
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));
158 break;
159 case remoting.HostDispatcher.State.NATIVE_MESSAGING:
160 this.nativeMessagingHost_.getPinHash(hostId, pin, onDone, onError);
161 break;
162 case remoting.HostDispatcher.State.NPAPI:
163 try {
164 this.npapiHost_.getPinHash(hostId, pin, onDone);
165 } catch (err) {
166 onError(remoting.Error.MISSING_PLUGIN);
168 break;
169 case remoting.HostDispatcher.State.NOT_INSTALLED:
170 onError(remoting.Error.MISSING_PLUGIN);
171 break;
176 * @param {function(string, string):void} onDone
177 * @param {function(remoting.Error):void} onError
178 * @return {void}
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));
185 break;
186 case remoting.HostDispatcher.State.NATIVE_MESSAGING:
187 this.nativeMessagingHost_.generateKeyPair(onDone, onError);
188 break;
189 case remoting.HostDispatcher.State.NPAPI:
190 try {
191 this.npapiHost_.generateKeyPair(onDone);
192 } catch (err) {
193 onError(remoting.Error.MISSING_PLUGIN);
195 break;
196 case remoting.HostDispatcher.State.NOT_INSTALLED:
197 onError(remoting.Error.MISSING_PLUGIN);
198 break;
203 * @param {Object} config
204 * @param {function(remoting.HostController.AsyncResult):void} onDone
205 * @param {function(remoting.Error):void} onError
206 * @return {void}
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));
214 break;
215 case remoting.HostDispatcher.State.NATIVE_MESSAGING:
216 this.nativeMessagingHost_.updateDaemonConfig(config, onDone, onError);
217 break;
218 case remoting.HostDispatcher.State.NPAPI:
219 try {
220 this.npapiHost_.updateDaemonConfig(JSON.stringify(config), onDone);
221 } catch (err) {
222 onError(remoting.Error.MISSING_PLUGIN);
224 break;
225 case remoting.HostDispatcher.State.NOT_INSTALLED:
226 onError(remoting.Error.MISSING_PLUGIN);
227 break;
232 * @param {function(Object):void} onDone
233 * @param {function(remoting.Error):void} onError
234 * @return {void}
236 remoting.HostDispatcher.prototype.getDaemonConfig = function(onDone, onError) {
238 * Converts the config string from the NPAPI plugin to an Object, to pass to
239 * |onDone|.
240 * @param {string} configStr
241 * @return {void}
243 function callbackForNpapi(configStr) {
244 var config = jsonParseSafe(configStr);
245 if (typeof(config) != 'object') {
246 onError(remoting.Error.UNEXPECTED);
247 } else {
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));
256 break;
257 case remoting.HostDispatcher.State.NATIVE_MESSAGING:
258 this.nativeMessagingHost_.getDaemonConfig(onDone, onError);
259 break;
260 case remoting.HostDispatcher.State.NPAPI:
261 try {
262 this.npapiHost_.getDaemonConfig(callbackForNpapi);
263 } catch (err) {
264 onError(remoting.Error.MISSING_PLUGIN);
266 break;
267 case remoting.HostDispatcher.State.NOT_INSTALLED:
268 onDone({});
269 break;
274 * @param {function(string):void} onDone
275 * @param {function(remoting.Error):void} onError
276 * @return {void}
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));
283 break;
284 case remoting.HostDispatcher.State.NATIVE_MESSAGING:
285 onDone(this.nativeMessagingHost_.getDaemonVersion());
286 break;
287 case remoting.HostDispatcher.State.NPAPI:
288 try {
289 this.npapiHost_.getDaemonVersion(onDone);
290 } catch (err) {
291 onError(remoting.Error.MISSING_PLUGIN);
293 break;
294 case remoting.HostDispatcher.State.NOT_INSTALLED:
295 onError(remoting.Error.MISSING_PLUGIN);
296 break;
301 * @param {function(boolean, boolean, boolean):void} onDone
302 * @param {function(remoting.Error):void} onError
303 * @return {void}
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));
311 break;
312 case remoting.HostDispatcher.State.NATIVE_MESSAGING:
313 this.nativeMessagingHost_.getUsageStatsConsent(onDone, onError);
314 break;
315 case remoting.HostDispatcher.State.NPAPI:
316 try {
317 this.npapiHost_.getUsageStatsConsent(onDone);
318 } catch (err) {
319 onError(remoting.Error.MISSING_PLUGIN);
321 break;
322 case remoting.HostDispatcher.State.NOT_INSTALLED:
323 onError(remoting.Error.MISSING_PLUGIN);
324 break;
329 * @param {Object} config
330 * @param {boolean} consent
331 * @param {function(remoting.HostController.AsyncResult):void} onDone
332 * @param {function(remoting.Error):void} onError
333 * @return {void}
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));
341 break;
342 case remoting.HostDispatcher.State.NATIVE_MESSAGING:
343 this.nativeMessagingHost_.startDaemon(config, consent, onDone, onError);
344 break;
345 case remoting.HostDispatcher.State.NPAPI:
346 try {
347 this.npapiHost_.startDaemon(JSON.stringify(config), consent, onDone);
348 } catch (err) {
349 onError(remoting.Error.MISSING_PLUGIN);
351 break;
352 case remoting.HostDispatcher.State.NOT_INSTALLED:
353 onError(remoting.Error.MISSING_PLUGIN);
354 break;
359 * @param {function(remoting.HostController.AsyncResult):void} onDone
360 * @param {function(remoting.Error):void} onError
361 * @return {void}
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));
367 break;
368 case remoting.HostDispatcher.State.NATIVE_MESSAGING:
369 this.nativeMessagingHost_.stopDaemon(onDone, onError);
370 break;
371 case remoting.HostDispatcher.State.NPAPI:
372 try {
373 this.npapiHost_.stopDaemon(onDone);
374 } catch (err) {
375 onError(remoting.Error.MISSING_PLUGIN);
377 break;
378 case remoting.HostDispatcher.State.NOT_INSTALLED:
379 onError(remoting.Error.MISSING_PLUGIN);
380 break;
385 * @param {function(remoting.HostController.State):void} onDone
386 * @param {function(remoting.Error):void} onError
387 * @return {void}
389 remoting.HostDispatcher.prototype.getDaemonState = function(onDone, onError) {
390 // If the host was in not-initialized state try initializing it again in case
391 // it was installed.
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
403 * @return {void}
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));
411 break;
412 case remoting.HostDispatcher.State.NATIVE_MESSAGING:
413 this.nativeMessagingHost_.getDaemonState(onDone, onError);
414 break;
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);
421 } else {
422 onDone(state);
424 break;
425 case remoting.HostDispatcher.State.NOT_INSTALLED:
426 onDone(remoting.HostController.State.NOT_INSTALLED);
427 break;
432 * @param {function(Array.<remoting.PairedClient>):void} onDone
433 * @param {function(remoting.Error):void} onError
434 * @return {void}
436 remoting.HostDispatcher.prototype.getPairedClients = function(onDone, onError) {
438 * Converts the JSON string from the NPAPI plugin to Array.<PairedClient>, to
439 * pass to |onDone|.
440 * @param {string} pairedClientsJson
441 * @return {void}
443 function callbackForNpapi(pairedClientsJson) {
444 var pairedClients = remoting.PairedClient.convertToPairedClientArray(
445 jsonParseSafe(pairedClientsJson));
446 if (pairedClients != null) {
447 onDone(pairedClients);
448 } else {
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));
457 break;
458 case remoting.HostDispatcher.State.NATIVE_MESSAGING:
459 this.nativeMessagingHost_.getPairedClients(onDone, onError);
460 break;
461 case remoting.HostDispatcher.State.NPAPI:
462 try {
463 this.npapiHost_.getPairedClients(callbackForNpapi);
464 } catch (err) {
465 onError(remoting.Error.MISSING_PLUGIN);
467 break;
468 case remoting.HostDispatcher.State.NOT_INSTALLED:
469 onError(remoting.Error.MISSING_PLUGIN);
470 break;
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.
482 * @private
484 remoting.HostDispatcher.runCallback_ = function(onDone, onError, success) {
485 if (success) {
486 onDone();
487 } else {
488 onError(remoting.Error.UNEXPECTED);
493 * @param {function():void} onDone
494 * @param {function(remoting.Error):void} onError
495 * @return {void}
497 remoting.HostDispatcher.prototype.clearPairedClients =
498 function(onDone, onError) {
499 var callback =
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));
505 break;
506 case remoting.HostDispatcher.State.NATIVE_MESSAGING:
507 this.nativeMessagingHost_.clearPairedClients(callback, onError);
508 break;
509 case remoting.HostDispatcher.State.NPAPI:
510 try {
511 this.npapiHost_.clearPairedClients(callback);
512 } catch (err) {
513 onError(remoting.Error.MISSING_PLUGIN);
515 break;
516 case remoting.HostDispatcher.State.NOT_INSTALLED:
517 onError(remoting.Error.MISSING_PLUGIN);
518 break;
523 * @param {string} client
524 * @param {function():void} onDone
525 * @param {function(remoting.Error):void} onError
526 * @return {void}
528 remoting.HostDispatcher.prototype.deletePairedClient =
529 function(client, onDone, onError) {
530 var callback =
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));
536 break;
537 case remoting.HostDispatcher.State.NATIVE_MESSAGING:
538 this.nativeMessagingHost_.deletePairedClient(client, callback, onError);
539 break;
540 case remoting.HostDispatcher.State.NPAPI:
541 try {
542 this.npapiHost_.deletePairedClient(client, callback);
543 } catch (err) {
544 onError(remoting.Error.MISSING_PLUGIN);
546 break;
547 case remoting.HostDispatcher.State.NOT_INSTALLED:
548 onError(remoting.Error.MISSING_PLUGIN);
549 break;
554 * @param {function(string):void} onDone
555 * @param {function(remoting.Error):void} onError
556 * @return {void}
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));
564 break;
565 case remoting.HostDispatcher.State.NATIVE_MESSAGING:
566 this.nativeMessagingHost_.getHostClientId(onDone, onError);
567 break;
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);
572 break;
573 case remoting.HostDispatcher.State.NOT_INSTALLED:
574 onError(remoting.Error.MISSING_PLUGIN);
575 break;
580 * @param {string} authorizationCode
581 * @param {function(string, string):void} onDone
582 * @param {function(remoting.Error):void} onError
583 * @return {void}
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));
592 break;
593 case remoting.HostDispatcher.State.NATIVE_MESSAGING:
594 this.nativeMessagingHost_.getCredentialsFromAuthCode(
595 authorizationCode, onDone, onError);
596 break;
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);
601 break;
602 case remoting.HostDispatcher.State.NOT_INSTALLED:
603 onError(remoting.Error.MISSING_PLUGIN);
604 break;
609 * Returns true if the NPAPI plugin is being used.
610 * @return {boolean}
612 remoting.HostDispatcher.prototype.usingNpapiPlugin = function() {
613 return this.state_ == remoting.HostDispatcher.State.NPAPI;