- add sources.
[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.HostDispatcher} */
32   var that = this;
33
34   /** @type {remoting.HostNativeMessaging} @private */
35   this.nativeMessagingHost_ = new remoting.HostNativeMessaging();
36
37   /** @type {remoting.HostPlugin} @private */
38   this.npapiHost_ = null;
39
40   /** @type {remoting.HostDispatcher.State} */
41   this.state_ = remoting.HostDispatcher.State.UNKNOWN;
42
43   /** @type {Array.<function()>} */
44   this.pendingRequests_ = [];
45
46   function sendPendingRequests() {
47     for (var i = 0; i < that.pendingRequests_.length; i++) {
48       that.pendingRequests_[i]();
49     }
50     that.pendingRequests_ = null;
51   }
52
53   function onNativeMessagingInit() {
54     console.log('Native Messaging supported.');
55     that.state_ = remoting.HostDispatcher.State.NATIVE_MESSAGING;
56     sendPendingRequests();
57   }
58
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();
64   }
65
66   this.nativeMessagingHost_.initialize(onNativeMessagingInit,
67                                        onNativeMessagingFailed);
68 };
69
70 /** @enum {number} */
71 remoting.HostDispatcher.State = {
72   UNKNOWN: 0,
73   NATIVE_MESSAGING: 1,
74   NPAPI: 2
75 };
76
77 /**
78  * @param {remoting.HostController.Feature} feature The feature to test for.
79  * @param {function(boolean):void} onDone
80  * @return {void}
81  */
82 remoting.HostDispatcher.prototype.hasFeature = function(
83     feature, onDone) {
84   switch (this.state_) {
85     case remoting.HostDispatcher.State.UNKNOWN:
86       this.pendingRequests_.push(
87           this.hasFeature.bind(this, feature, onDone));
88       break;
89     case remoting.HostDispatcher.State.NATIVE_MESSAGING:
90       onDone(this.nativeMessagingHost_.hasFeature(feature));
91       break;
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(' ');
98       }
99       onDone(supportedFeatures.indexOf(feature) >= 0);
100       break;
101   }
102 };
103
104 /**
105  * @param {function(string):void} onDone
106  * @param {function(remoting.Error):void} onError
107  * @return {void}
108  */
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));
114       break;
115     case remoting.HostDispatcher.State.NATIVE_MESSAGING:
116       this.nativeMessagingHost_.getHostName(onDone, onError);
117       break;
118     case remoting.HostDispatcher.State.NPAPI:
119       try {
120         this.npapiHost_.getHostName(onDone);
121       } catch (err) {
122         onError(remoting.Error.MISSING_PLUGIN);
123       }
124       break;
125   }
126 };
127
128 /**
129  * @param {string} hostId
130  * @param {string} pin
131  * @param {function(string):void} onDone
132  * @param {function(remoting.Error):void} onError
133  * @return {void}
134  */
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));
141       break;
142     case remoting.HostDispatcher.State.NATIVE_MESSAGING:
143       this.nativeMessagingHost_.getPinHash(hostId, pin, onDone, onError);
144       break;
145     case remoting.HostDispatcher.State.NPAPI:
146       try {
147         this.npapiHost_.getPinHash(hostId, pin, onDone);
148       } catch (err) {
149         onError(remoting.Error.MISSING_PLUGIN);
150       }
151       break;
152   }
153 };
154
155 /**
156  * @param {function(string, string):void} onDone
157  * @param {function(remoting.Error):void} onError
158  * @return {void}
159  */
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));
165       break;
166     case remoting.HostDispatcher.State.NATIVE_MESSAGING:
167       this.nativeMessagingHost_.generateKeyPair(onDone, onError);
168       break;
169     case remoting.HostDispatcher.State.NPAPI:
170       try {
171         this.npapiHost_.generateKeyPair(onDone);
172       } catch (err) {
173         onError(remoting.Error.MISSING_PLUGIN);
174       }
175       break;
176   }
177 };
178
179 /**
180  * @param {Object} config
181  * @param {function(remoting.HostController.AsyncResult):void} onDone
182  * @param {function(remoting.Error):void} onError
183  * @return {void}
184  */
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));
191       break;
192     case remoting.HostDispatcher.State.NATIVE_MESSAGING:
193       this.nativeMessagingHost_.updateDaemonConfig(config, onDone, onError);
194       break;
195     case remoting.HostDispatcher.State.NPAPI:
196       try {
197         this.npapiHost_.updateDaemonConfig(JSON.stringify(config), onDone);
198       } catch (err) {
199         onError(remoting.Error.MISSING_PLUGIN);
200       }
201       break;
202   }
203 };
204
205 /**
206  * @param {function(Object):void} onDone
207  * @param {function(remoting.Error):void} onError
208  * @return {void}
209  */
210 remoting.HostDispatcher.prototype.getDaemonConfig = function(onDone, onError) {
211   /**
212    * Converts the config string from the NPAPI plugin to an Object, to pass to
213    * |onDone|.
214    * @param {string} configStr
215    * @return {void}
216    */
217   function callbackForNpapi(configStr) {
218     var config = jsonParseSafe(configStr);
219     if (typeof(config) != 'object') {
220       onError(remoting.Error.UNEXPECTED);
221     } else {
222       onDone(/** @type {Object} */ (config));
223     }
224   }
225
226   switch (this.state_) {
227     case remoting.HostDispatcher.State.UNKNOWN:
228       this.pendingRequests_.push(
229           this.getDaemonConfig.bind(this, onDone, onError));
230       break;
231     case remoting.HostDispatcher.State.NATIVE_MESSAGING:
232       this.nativeMessagingHost_.getDaemonConfig(onDone, onError);
233       break;
234     case remoting.HostDispatcher.State.NPAPI:
235       try {
236         this.npapiHost_.getDaemonConfig(callbackForNpapi);
237       } catch (err) {
238         onError(remoting.Error.MISSING_PLUGIN);
239       }
240       break;
241   }
242 };
243
244 /**
245  * @param {function(string):void} onDone
246  * @param {function(remoting.Error):void} onError
247  * @return {void}
248  */
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));
254       break;
255     case remoting.HostDispatcher.State.NATIVE_MESSAGING:
256       onDone(this.nativeMessagingHost_.getDaemonVersion());
257       break;
258     case remoting.HostDispatcher.State.NPAPI:
259       try {
260         this.npapiHost_.getDaemonVersion(onDone);
261       } catch (err) {
262         onError(remoting.Error.MISSING_PLUGIN);
263       }
264       break;
265   }
266 };
267
268 /**
269  * @param {function(boolean, boolean, boolean):void} onDone
270  * @param {function(remoting.Error):void} onError
271  * @return {void}
272  */
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));
279       break;
280     case remoting.HostDispatcher.State.NATIVE_MESSAGING:
281       this.nativeMessagingHost_.getUsageStatsConsent(onDone, onError);
282       break;
283     case remoting.HostDispatcher.State.NPAPI:
284       try {
285         this.npapiHost_.getUsageStatsConsent(onDone);
286       } catch (err) {
287         onError(remoting.Error.MISSING_PLUGIN);
288       }
289       break;
290   }
291 };
292
293 /**
294  * @param {Object} config
295  * @param {boolean} consent
296  * @param {function(remoting.HostController.AsyncResult):void} onDone
297  * @param {function(remoting.Error):void} onError
298  * @return {void}
299  */
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));
306       break;
307     case remoting.HostDispatcher.State.NATIVE_MESSAGING:
308       this.nativeMessagingHost_.startDaemon(config, consent, onDone, onError);
309       break;
310     case remoting.HostDispatcher.State.NPAPI:
311       try {
312         this.npapiHost_.startDaemon(JSON.stringify(config), consent, onDone);
313       } catch (err) {
314         onError(remoting.Error.MISSING_PLUGIN);
315       }
316       break;
317   }
318 };
319
320 /**
321  * @param {function(remoting.HostController.AsyncResult):void} onDone
322  * @param {function(remoting.Error):void} onError
323  * @return {void}
324  */
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));
329       break;
330     case remoting.HostDispatcher.State.NATIVE_MESSAGING:
331       this.nativeMessagingHost_.stopDaemon(onDone, onError);
332       break;
333     case remoting.HostDispatcher.State.NPAPI:
334       try {
335         this.npapiHost_.stopDaemon(onDone);
336       } catch (err) {
337         onError(remoting.Error.MISSING_PLUGIN);
338       }
339       break;
340   }
341 };
342
343 /**
344  * @param {function(remoting.HostController.State):void} onDone
345  * @param {function(remoting.Error):void} onError
346  * @return {void}
347  */
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));
353       break;
354     case remoting.HostDispatcher.State.NATIVE_MESSAGING:
355       this.nativeMessagingHost_.getDaemonState(onDone, onError);
356       break;
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);
363       } else {
364         onDone(state);
365       }
366       break;
367   }
368 };
369
370 /**
371  * @param {function(Array.<remoting.PairedClient>):void} onDone
372  * @param {function(remoting.Error):void} onError
373  * @return {void}
374  */
375 remoting.HostDispatcher.prototype.getPairedClients = function(onDone, onError) {
376   /**
377    * Converts the JSON string from the NPAPI plugin to Array.<PairedClient>, to
378    * pass to |onDone|.
379    * @param {string} pairedClientsJson
380    * @return {void}
381    */
382   function callbackForNpapi(pairedClientsJson) {
383     var pairedClients = remoting.PairedClient.convertToPairedClientArray(
384         jsonParseSafe(pairedClientsJson));
385     if (pairedClients != null) {
386       onDone(pairedClients);
387     } else {
388       onError(remoting.Error.UNEXPECTED);
389     }
390   }
391
392   switch (this.state_) {
393     case remoting.HostDispatcher.State.UNKNOWN:
394       this.pendingRequests_.push(
395           this.getPairedClients.bind(this, onDone, onError));
396       break;
397     case remoting.HostDispatcher.State.NATIVE_MESSAGING:
398       this.nativeMessagingHost_.getPairedClients(onDone, onError);
399       break;
400     case remoting.HostDispatcher.State.NPAPI:
401       try {
402         this.npapiHost_.getPairedClients(callbackForNpapi);
403       } catch (err) {
404         onError(remoting.Error.MISSING_PLUGIN);
405       }
406       break;
407   }
408 };
409
410 /**
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.
414  *
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.
418  * @private
419  */
420 remoting.HostDispatcher.runCallback_ = function(onDone, onError, success) {
421   if (success) {
422     onDone();
423   } else {
424     onError(remoting.Error.UNEXPECTED);
425   }
426 };
427
428 /**
429  * @param {function():void} onDone
430  * @param {function(remoting.Error):void} onError
431  * @return {void}
432  */
433 remoting.HostDispatcher.prototype.clearPairedClients =
434     function(onDone, onError) {
435   var callback =
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));
441       break;
442     case remoting.HostDispatcher.State.NATIVE_MESSAGING:
443       this.nativeMessagingHost_.clearPairedClients(callback, onError);
444       break;
445     case remoting.HostDispatcher.State.NPAPI:
446       try {
447         this.npapiHost_.clearPairedClients(callback);
448       } catch (err) {
449         onError(remoting.Error.MISSING_PLUGIN);
450       }
451       break;
452   }
453 };
454
455 /**
456  * @param {string} client
457  * @param {function():void} onDone
458  * @param {function(remoting.Error):void} onError
459  * @return {void}
460  */
461 remoting.HostDispatcher.prototype.deletePairedClient =
462     function(client, onDone, onError) {
463   var callback =
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));
469       break;
470     case remoting.HostDispatcher.State.NATIVE_MESSAGING:
471       this.nativeMessagingHost_.deletePairedClient(client, callback, onError);
472       break;
473     case remoting.HostDispatcher.State.NPAPI:
474       try {
475         this.npapiHost_.deletePairedClient(client, callback);
476       } catch (err) {
477         onError(remoting.Error.MISSING_PLUGIN);
478       }
479       break;
480   }
481 };
482
483 /**
484  * @param {function(string):void} onDone
485  * @param {function(remoting.Error):void} onError
486  * @return {void}
487  */
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));
494       break;
495     case remoting.HostDispatcher.State.NATIVE_MESSAGING:
496       this.nativeMessagingHost_.getHostClientId(onDone, onError);
497       break;
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);
502       break;
503   }
504 };
505
506 /**
507  * @param {string} authorizationCode
508  * @param {function(string, string):void} onDone
509  * @param {function(remoting.Error):void} onError
510  * @return {void}
511  */
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));
519       break;
520     case remoting.HostDispatcher.State.NATIVE_MESSAGING:
521       this.nativeMessagingHost_.getCredentialsFromAuthCode(
522           authorizationCode, onDone, onError);
523       break;
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);
528       break;
529   }
530 };