Upstream version 7.36.149.0
[platform/framework/web/crosswalk.git] / src / remoting / webapp / host_dispatcher.js
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.
4
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.
14  *
15  * If necessary, the HostController is instructed (via a callback) to
16  * instantiate the NPAPI plugin, and return a reference to it here.
17  */
18
19 'use strict';
20
21 /** @suppress {duplicate} */
22 var remoting = remoting || {};
23
24 /**
25  * @constructor
26  * @param {function():remoting.HostPlugin} createPluginCallback Callback to
27  *     instantiate the NPAPI plugin when NativeMessaging is determined to be
28  *     unsupported.
29  */
30 remoting.HostDispatcher = function(createPluginCallback) {
31   /** @type {remoting.HostNativeMessaging} @private */
32   this.nativeMessagingHost_ = new remoting.HostNativeMessaging();
33
34   /** @type {remoting.HostPlugin} @private */
35   this.npapiHost_ = null;
36
37   /** @type {remoting.HostDispatcher.State} @private */
38   this.state_ = remoting.HostDispatcher.State.UNKNOWN;
39
40   /** @type {Array.<function()>} @private */
41   this.pendingRequests_ = [];
42
43   /** @type {function():remoting.HostPlugin} @private */
44   this.createPluginCallback_ = createPluginCallback;
45
46   this.tryToInitialize_();
47 }
48
49 /** @enum {number} */
50 remoting.HostDispatcher.State = {
51   UNKNOWN: 0,
52   NATIVE_MESSAGING: 1,
53   NPAPI: 2,
54   NOT_INSTALLED: 3
55 };
56
57 remoting.HostDispatcher.prototype.tryToInitialize_ = function() {
58   /** @type {remoting.HostDispatcher} */
59   var that = this;
60
61   if (this.state_ != remoting.HostDispatcher.State.UNKNOWN)
62     return;
63
64   function sendPendingRequests() {
65     var pendingRequests = that.pendingRequests_;
66     that.pendingRequests_ = [];
67     for (var i = 0; i < pendingRequests.length; i++) {
68       pendingRequests[i]();
69     }
70   }
71
72   function onNativeMessagingInit() {
73     that.state_ = remoting.HostDispatcher.State.NATIVE_MESSAGING;
74     sendPendingRequests();
75   }
76
77   function onNativeMessagingFailed(error) {
78     that.npapiHost_ = that.createPluginCallback_();
79
80     that.state_ = that.npapiHost_ ? remoting.HostDispatcher.State.NPAPI
81                                   : remoting.HostDispatcher.State.NOT_INSTALLED;
82     sendPendingRequests();
83   }
84
85   this.nativeMessagingHost_.initialize(onNativeMessagingInit,
86                                        onNativeMessagingFailed);
87 };
88
89 /**
90  * @param {remoting.HostController.Feature} feature The feature to test for.
91  * @param {function(boolean):void} onDone
92  * @return {void}
93  */
94 remoting.HostDispatcher.prototype.hasFeature = function(
95     feature, onDone) {
96   switch (this.state_) {
97     case remoting.HostDispatcher.State.UNKNOWN:
98       this.pendingRequests_.push(
99           this.hasFeature.bind(this, feature, onDone));
100       break;
101     case remoting.HostDispatcher.State.NATIVE_MESSAGING:
102       onDone(this.nativeMessagingHost_.hasFeature(feature));
103       break;
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(' ');
110       }
111       onDone(supportedFeatures.indexOf(feature) >= 0);
112       break;
113     case remoting.HostDispatcher.State.NOT_INSTALLED:
114       onDone(false);
115       break;
116   }
117 };
118
119 /**
120  * @param {function(string):void} onDone
121  * @param {function(remoting.Error):void} onError
122  * @return {void}
123  */
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));
129       break;
130     case remoting.HostDispatcher.State.NATIVE_MESSAGING:
131       this.nativeMessagingHost_.getHostName(onDone, onError);
132       break;
133     case remoting.HostDispatcher.State.NPAPI:
134       try {
135         this.npapiHost_.getHostName(onDone);
136       } catch (err) {
137         onError(remoting.Error.MISSING_PLUGIN);
138       }
139       break;
140     case remoting.HostDispatcher.State.NOT_INSTALLED:
141       onError(remoting.Error.MISSING_PLUGIN);
142       break;
143   }
144 };
145
146 /**
147  * @param {string} hostId
148  * @param {string} pin
149  * @param {function(string):void} onDone
150  * @param {function(remoting.Error):void} onError
151  * @return {void}
152  */
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));
159       break;
160     case remoting.HostDispatcher.State.NATIVE_MESSAGING:
161       this.nativeMessagingHost_.getPinHash(hostId, pin, onDone, onError);
162       break;
163     case remoting.HostDispatcher.State.NPAPI:
164       try {
165         this.npapiHost_.getPinHash(hostId, pin, onDone);
166       } catch (err) {
167         onError(remoting.Error.MISSING_PLUGIN);
168       }
169       break;
170     case remoting.HostDispatcher.State.NOT_INSTALLED:
171       onError(remoting.Error.MISSING_PLUGIN);
172       break;
173   }
174 };
175
176 /**
177  * @param {function(string, string):void} onDone
178  * @param {function(remoting.Error):void} onError
179  * @return {void}
180  */
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));
186       break;
187     case remoting.HostDispatcher.State.NATIVE_MESSAGING:
188       this.nativeMessagingHost_.generateKeyPair(onDone, onError);
189       break;
190     case remoting.HostDispatcher.State.NPAPI:
191       try {
192         this.npapiHost_.generateKeyPair(onDone);
193       } catch (err) {
194         onError(remoting.Error.MISSING_PLUGIN);
195       }
196       break;
197     case remoting.HostDispatcher.State.NOT_INSTALLED:
198       onError(remoting.Error.MISSING_PLUGIN);
199       break;
200   }
201 };
202
203 /**
204  * @param {Object} config
205  * @param {function(remoting.HostController.AsyncResult):void} onDone
206  * @param {function(remoting.Error):void} onError
207  * @return {void}
208  */
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));
215       break;
216     case remoting.HostDispatcher.State.NATIVE_MESSAGING:
217       this.nativeMessagingHost_.updateDaemonConfig(config, onDone, onError);
218       break;
219     case remoting.HostDispatcher.State.NPAPI:
220       try {
221         this.npapiHost_.updateDaemonConfig(JSON.stringify(config), onDone);
222       } catch (err) {
223         onError(remoting.Error.MISSING_PLUGIN);
224       }
225       break;
226     case remoting.HostDispatcher.State.NOT_INSTALLED:
227       onError(remoting.Error.MISSING_PLUGIN);
228       break;
229   }
230 };
231
232 /**
233  * @param {function(Object):void} onDone
234  * @param {function(remoting.Error):void} onError
235  * @return {void}
236  */
237 remoting.HostDispatcher.prototype.getDaemonConfig = function(onDone, onError) {
238   /**
239    * Converts the config string from the NPAPI plugin to an Object, to pass to
240    * |onDone|.
241    * @param {string} configStr
242    * @return {void}
243    */
244   function callbackForNpapi(configStr) {
245     var config = jsonParseSafe(configStr);
246     if (typeof(config) != 'object') {
247       onError(remoting.Error.UNEXPECTED);
248     } else {
249       onDone(/** @type {Object} */ (config));
250     }
251   }
252
253   switch (this.state_) {
254     case remoting.HostDispatcher.State.UNKNOWN:
255       this.pendingRequests_.push(
256           this.getDaemonConfig.bind(this, onDone, onError));
257       break;
258     case remoting.HostDispatcher.State.NATIVE_MESSAGING:
259       this.nativeMessagingHost_.getDaemonConfig(onDone, onError);
260       break;
261     case remoting.HostDispatcher.State.NPAPI:
262       try {
263         this.npapiHost_.getDaemonConfig(callbackForNpapi);
264       } catch (err) {
265         onError(remoting.Error.MISSING_PLUGIN);
266       }
267       break;
268     case remoting.HostDispatcher.State.NOT_INSTALLED:
269       onDone({});
270       break;
271   }
272 };
273
274 /**
275  * @param {function(string):void} onDone
276  * @param {function(remoting.Error):void} onError
277  * @return {void}
278  */
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));
284       break;
285     case remoting.HostDispatcher.State.NATIVE_MESSAGING:
286       onDone(this.nativeMessagingHost_.getDaemonVersion());
287       break;
288     case remoting.HostDispatcher.State.NPAPI:
289       try {
290         this.npapiHost_.getDaemonVersion(onDone);
291       } catch (err) {
292         onError(remoting.Error.MISSING_PLUGIN);
293       }
294       break;
295     case remoting.HostDispatcher.State.NOT_INSTALLED:
296       onError(remoting.Error.MISSING_PLUGIN);
297       break;
298   }
299 };
300
301 /**
302  * @param {function(boolean, boolean, boolean):void} onDone
303  * @param {function(remoting.Error):void} onError
304  * @return {void}
305  */
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));
312       break;
313     case remoting.HostDispatcher.State.NATIVE_MESSAGING:
314       this.nativeMessagingHost_.getUsageStatsConsent(onDone, onError);
315       break;
316     case remoting.HostDispatcher.State.NPAPI:
317       try {
318         this.npapiHost_.getUsageStatsConsent(onDone);
319       } catch (err) {
320         onError(remoting.Error.MISSING_PLUGIN);
321       }
322       break;
323     case remoting.HostDispatcher.State.NOT_INSTALLED:
324       onError(remoting.Error.MISSING_PLUGIN);
325       break;
326   }
327 };
328
329 /**
330  * This function installs the chromoting host using the NPAPI plugin and should
331  * only be called on Windows.
332  *
333  * @param {function(remoting.HostController.AsyncResult):void} onDone
334  * @param {function(remoting.Error):void} onError
335  * @return {void}
336  */
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));
341       break;
342     case remoting.HostDispatcher.State.NATIVE_MESSAGING:
343       // Host already installed, no action needed.
344       onDone(remoting.HostController.AsyncResult.OK);
345       break;
346     case remoting.HostDispatcher.State.NPAPI:
347       try {
348         this.npapiHost_.installHost(onDone);
349       } catch (err) {
350         onError(remoting.Error.MISSING_PLUGIN);
351       }
352       break;
353     case remoting.HostDispatcher.State.NOT_INSTALLED:
354       onError(remoting.Error.MISSING_PLUGIN);
355       break;
356   }
357 };
358
359 /**
360  * @param {Object} config
361  * @param {boolean} consent
362  * @param {function(remoting.HostController.AsyncResult):void} onDone
363  * @param {function(remoting.Error):void} onError
364  * @return {void}
365  */
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));
372       break;
373     case remoting.HostDispatcher.State.NATIVE_MESSAGING:
374       this.nativeMessagingHost_.startDaemon(config, consent, onDone, onError);
375       break;
376     case remoting.HostDispatcher.State.NPAPI:
377       try {
378         this.npapiHost_.startDaemon(JSON.stringify(config), consent, onDone);
379       } catch (err) {
380         onError(remoting.Error.MISSING_PLUGIN);
381       }
382       break;
383     case remoting.HostDispatcher.State.NOT_INSTALLED:
384       onError(remoting.Error.MISSING_PLUGIN);
385       break;
386   }
387 };
388
389 /**
390  * @param {function(remoting.HostController.AsyncResult):void} onDone
391  * @param {function(remoting.Error):void} onError
392  * @return {void}
393  */
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));
398       break;
399     case remoting.HostDispatcher.State.NATIVE_MESSAGING:
400       this.nativeMessagingHost_.stopDaemon(onDone, onError);
401       break;
402     case remoting.HostDispatcher.State.NPAPI:
403       try {
404         this.npapiHost_.stopDaemon(onDone);
405       } catch (err) {
406         onError(remoting.Error.MISSING_PLUGIN);
407       }
408       break;
409     case remoting.HostDispatcher.State.NOT_INSTALLED:
410       onError(remoting.Error.MISSING_PLUGIN);
411       break;
412   }
413 };
414
415 /**
416  * @param {function(remoting.HostController.State):void} onDone
417  * @param {function(remoting.Error):void} onError
418  * @return {void}
419  */
420 remoting.HostDispatcher.prototype.getDaemonState = function(onDone, onError) {
421   // If the host was in not-initialized state try initializing it again in case
422   // it was installed.
423   if (this.state_ == remoting.HostDispatcher.State.NOT_INSTALLED) {
424     this.state_ = remoting.HostDispatcher.State.UNKNOWN;
425     this.tryToInitialize_();
426   }
427
428   this.getDaemonStateInternal_(onDone, onError);
429 }
430
431 /**
432  * @param {function(remoting.HostController.State):void} onDone
433  * @param {function(remoting.Error):void} onError
434  * @return {void}
435  */
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));
442       break;
443     case remoting.HostDispatcher.State.NATIVE_MESSAGING:
444       this.nativeMessagingHost_.getDaemonState(onDone, onError);
445       break;
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);
452       } else {
453         onDone(state);
454       }
455       break;
456     case remoting.HostDispatcher.State.NOT_INSTALLED:
457       onDone(remoting.HostController.State.NOT_INSTALLED);
458       break;
459   }
460 };
461
462 /**
463  * @param {function(Array.<remoting.PairedClient>):void} onDone
464  * @param {function(remoting.Error):void} onError
465  * @return {void}
466  */
467 remoting.HostDispatcher.prototype.getPairedClients = function(onDone, onError) {
468   /**
469    * Converts the JSON string from the NPAPI plugin to Array.<PairedClient>, to
470    * pass to |onDone|.
471    * @param {string} pairedClientsJson
472    * @return {void}
473    */
474   function callbackForNpapi(pairedClientsJson) {
475     var pairedClients = remoting.PairedClient.convertToPairedClientArray(
476         jsonParseSafe(pairedClientsJson));
477     if (pairedClients != null) {
478       onDone(pairedClients);
479     } else {
480       onError(remoting.Error.UNEXPECTED);
481     }
482   }
483
484   switch (this.state_) {
485     case remoting.HostDispatcher.State.UNKNOWN:
486       this.pendingRequests_.push(
487           this.getPairedClients.bind(this, onDone, onError));
488       break;
489     case remoting.HostDispatcher.State.NATIVE_MESSAGING:
490       this.nativeMessagingHost_.getPairedClients(onDone, onError);
491       break;
492     case remoting.HostDispatcher.State.NPAPI:
493       try {
494         this.npapiHost_.getPairedClients(callbackForNpapi);
495       } catch (err) {
496         onError(remoting.Error.MISSING_PLUGIN);
497       }
498       break;
499     case remoting.HostDispatcher.State.NOT_INSTALLED:
500       onError(remoting.Error.MISSING_PLUGIN);
501       break;
502   }
503 };
504
505 /**
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.
509  *
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.
513  * @private
514  */
515 remoting.HostDispatcher.runCallback_ = function(onDone, onError, success) {
516   if (success) {
517     onDone();
518   } else {
519     onError(remoting.Error.UNEXPECTED);
520   }
521 };
522
523 /**
524  * @param {function():void} onDone
525  * @param {function(remoting.Error):void} onError
526  * @return {void}
527  */
528 remoting.HostDispatcher.prototype.clearPairedClients =
529     function(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.clearPairedClients.bind(this, onDone, onError));
536       break;
537     case remoting.HostDispatcher.State.NATIVE_MESSAGING:
538       this.nativeMessagingHost_.clearPairedClients(callback, onError);
539       break;
540     case remoting.HostDispatcher.State.NPAPI:
541       try {
542         this.npapiHost_.clearPairedClients(callback);
543       } catch (err) {
544         onError(remoting.Error.MISSING_PLUGIN);
545       }
546       break;
547     case remoting.HostDispatcher.State.NOT_INSTALLED:
548       onError(remoting.Error.MISSING_PLUGIN);
549       break;
550   }
551 };
552
553 /**
554  * @param {string} client
555  * @param {function():void} onDone
556  * @param {function(remoting.Error):void} onError
557  * @return {void}
558  */
559 remoting.HostDispatcher.prototype.deletePairedClient =
560     function(client, onDone, onError) {
561   var callback =
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));
567       break;
568     case remoting.HostDispatcher.State.NATIVE_MESSAGING:
569       this.nativeMessagingHost_.deletePairedClient(client, callback, onError);
570       break;
571     case remoting.HostDispatcher.State.NPAPI:
572       try {
573         this.npapiHost_.deletePairedClient(client, callback);
574       } catch (err) {
575         onError(remoting.Error.MISSING_PLUGIN);
576       }
577       break;
578     case remoting.HostDispatcher.State.NOT_INSTALLED:
579       onError(remoting.Error.MISSING_PLUGIN);
580       break;
581   }
582 };
583
584 /**
585  * @param {function(string):void} onDone
586  * @param {function(remoting.Error):void} onError
587  * @return {void}
588  */
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));
595       break;
596     case remoting.HostDispatcher.State.NATIVE_MESSAGING:
597       this.nativeMessagingHost_.getHostClientId(onDone, onError);
598       break;
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);
603       break;
604     case remoting.HostDispatcher.State.NOT_INSTALLED:
605       onError(remoting.Error.MISSING_PLUGIN);
606       break;
607   }
608 };
609
610 /**
611  * @param {string} authorizationCode
612  * @param {function(string, string):void} onDone
613  * @param {function(remoting.Error):void} onError
614  * @return {void}
615  */
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));
623       break;
624     case remoting.HostDispatcher.State.NATIVE_MESSAGING:
625       this.nativeMessagingHost_.getCredentialsFromAuthCode(
626           authorizationCode, onDone, onError);
627       break;
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);
632       break;
633     case remoting.HostDispatcher.State.NOT_INSTALLED:
634       onError(remoting.Error.MISSING_PLUGIN);
635       break;
636   }
637 };
638
639 /**
640  * Returns true if the NPAPI plugin is being used.
641  * @return {boolean}
642  */
643 remoting.HostDispatcher.prototype.usingNpapiPlugin = function() {
644   return this.state_ == remoting.HostDispatcher.State.NPAPI;
645 }