Upstream version 11.39.266.0
[platform/framework/web/crosswalk.git] / src / remoting / webapp / host_controller.js
1 // Copyright (c) 2012 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 'use strict';
6
7 /** @suppress {duplicate} */
8 var remoting = remoting || {};
9
10 /** @constructor */
11 remoting.HostController = function() {
12   this.hostDaemonFacade_ = this.createDaemonFacade_();
13 };
14
15 // Note that the values in the enums below are copied from
16 // daemon_controller.h and must be kept in sync.
17 /** @enum {number} */
18 remoting.HostController.State = {
19   NOT_IMPLEMENTED: -1,
20   NOT_INSTALLED: 0,
21   INSTALLING: 1,
22   STOPPED: 2,
23   STARTING: 3,
24   STARTED: 4,
25   STOPPING: 5,
26   UNKNOWN: 6
27 };
28
29 /**
30  * @param {string} state The host controller state name.
31  * @return {remoting.HostController.State} The state enum value.
32  */
33 remoting.HostController.State.fromString = function(state) {
34   if (!remoting.HostController.State.hasOwnProperty(state)) {
35     throw "Invalid HostController.State: " + state;
36   }
37   return remoting.HostController.State[state];
38 }
39
40 /** @enum {number} */
41 remoting.HostController.AsyncResult = {
42   OK: 0,
43   FAILED: 1,
44   CANCELLED: 2,
45   FAILED_DIRECTORY: 3
46 };
47
48 /**
49  * @param {string} result The async result name.
50  * @return {remoting.HostController.AsyncResult} The result enum value.
51  */
52 remoting.HostController.AsyncResult.fromString = function(result) {
53   if (!remoting.HostController.AsyncResult.hasOwnProperty(result)) {
54     throw "Invalid HostController.AsyncResult: " + result;
55   }
56   return remoting.HostController.AsyncResult[result];
57 }
58
59 /**
60  * @return {remoting.HostDaemonFacade}
61  * @private
62  */
63 remoting.HostController.prototype.createDaemonFacade_ = function() {
64   /** @type {remoting.HostDaemonFacade} @private */
65   var hostDaemonFacade = new remoting.HostDaemonFacade();
66
67   /** @param {string} version */
68   var printVersion = function(version) {
69     if (version == '') {
70       console.log('Host not installed.');
71     } else {
72       console.log('Host version: ' + version);
73     }
74   };
75
76   hostDaemonFacade.getDaemonVersion(printVersion, function() {
77     console.log('Host version not available.');
78   });
79
80   return hostDaemonFacade;
81 };
82
83 /**
84  * Set of features for which hasFeature() can be used to test.
85  *
86  * @enum {string}
87  */
88 remoting.HostController.Feature = {
89   PAIRING_REGISTRY: 'pairingRegistry',
90   OAUTH_CLIENT: 'oauthClient'
91 };
92
93 /**
94  * @param {remoting.HostController.Feature} feature The feature to test for.
95  * @param {function(boolean):void} callback
96  * @return {void}
97  */
98 remoting.HostController.prototype.hasFeature = function(feature, callback) {
99   // TODO(rmsousa): This could synchronously return a boolean, provided it were
100   // only called after native messaging is completely initialized.
101   this.hostDaemonFacade_.hasFeature(feature, callback);
102 };
103
104 /**
105  * @param {function(boolean, boolean, boolean):void} onDone Callback to be
106  *     called when done.
107  * @param {function(remoting.Error):void} onError Callback to be called on
108  *     error.
109  */
110 remoting.HostController.prototype.getConsent = function(onDone, onError) {
111   this.hostDaemonFacade_.getUsageStatsConsent(onDone, onError);
112 };
113
114 /**
115  * Registers and starts the host.
116  *
117  * @param {string} hostPin Host PIN.
118  * @param {boolean} consent The user's consent to crash dump reporting.
119  * @param {function():void} onDone Callback to be called when done.
120  * @param {function(remoting.Error):void} onError Callback to be called on
121  *     error.
122  * @return {void} Nothing.
123  */
124 remoting.HostController.prototype.start = function(hostPin, consent, onDone,
125                                                    onError) {
126   /** @type {remoting.HostController} */
127   var that = this;
128
129   /** @return {string} */
130   function generateUuid() {
131     var random = new Uint16Array(8);
132     window.crypto.getRandomValues(random);
133     /** @type {Array.<string>} */
134     var e = new Array();
135     for (var i = 0; i < 8; i++) {
136       e[i] = (/** @type {number} */random[i] + 0x10000).
137           toString(16).substring(1);
138     }
139     return e[0] + e[1] + '-' + e[2] + '-' + e[3] + '-' +
140         e[4] + '-' + e[5] + e[6] + e[7];
141   };
142
143   var newHostId = generateUuid();
144
145   /** @param {remoting.Error} error */
146   function onStartError(error) {
147     // Unregister the host if we failed to start it.
148     remoting.HostList.unregisterHostById(newHostId);
149     onError(error);
150   }
151
152   /**
153    * @param {string} hostName
154    * @param {string} publicKey
155    * @param {remoting.HostController.AsyncResult} result
156    */
157   function onStarted(hostName, publicKey, result) {
158     if (result == remoting.HostController.AsyncResult.OK) {
159       remoting.hostList.onLocalHostStarted(hostName, newHostId, publicKey);
160       onDone();
161     } else if (result == remoting.HostController.AsyncResult.CANCELLED) {
162       onStartError(remoting.Error.CANCELLED);
163     } else {
164       onStartError(remoting.Error.UNEXPECTED);
165     }
166   }
167
168   /**
169    * @param {string} hostName
170    * @param {string} publicKey
171    * @param {string} privateKey
172    * @param {string} xmppLogin
173    * @param {string} refreshToken
174    * @param {string} clientBaseJid
175    * @param {string} hostSecretHash
176    */
177   function startHostWithHash(hostName, publicKey, privateKey, xmppLogin,
178                              refreshToken, clientBaseJid, hostSecretHash) {
179     var hostConfig = {
180       xmpp_login: xmppLogin,
181       oauth_refresh_token: refreshToken,
182       host_id: newHostId,
183       host_name: hostName,
184       host_secret_hash: hostSecretHash,
185       private_key: privateKey
186     };
187     var hostOwner = clientBaseJid;
188     var hostOwnerEmail = remoting.identity.getCachedEmail();
189     if (hostOwner != xmppLogin) {
190       hostConfig['host_owner'] = hostOwner;
191       if (hostOwnerEmail != hostOwner) {
192         hostConfig['host_owner_email'] = hostOwnerEmail;
193       }
194     }
195     that.hostDaemonFacade_.startDaemon(
196         hostConfig, consent, onStarted.bind(null, hostName, publicKey),
197         onStartError);
198   }
199
200   /**
201    * @param {string} hostName
202    * @param {string} publicKey
203    * @param {string} privateKey
204    * @param {string} email
205    * @param {string} refreshToken
206    * @param {string} clientBaseJid
207    */
208   function onClientBaseJid(
209       hostName, publicKey, privateKey, email, refreshToken, clientBaseJid) {
210     that.hostDaemonFacade_.getPinHash(
211         newHostId, hostPin,
212         startHostWithHash.bind(null, hostName, publicKey, privateKey,
213                                email, refreshToken, clientBaseJid),
214         onError);
215   }
216
217   /**
218    * @param {string} hostName
219    * @param {string} publicKey
220    * @param {string} privateKey
221    * @param {string} email
222    * @param {string} refreshToken
223    */
224   function onServiceAccountCredentials(
225       hostName, publicKey, privateKey, email, refreshToken) {
226     that.getClientBaseJid_(
227         onClientBaseJid.bind(
228             null, hostName, publicKey, privateKey, email, refreshToken),
229         onStartError);
230   }
231
232   /**
233    * @param {string} hostName
234    * @param {string} publicKey
235    * @param {string} privateKey
236    * @param {XMLHttpRequest} xhr
237    */
238   function onRegistered(
239       hostName, publicKey, privateKey, xhr) {
240     var success = (xhr.status == 200);
241
242     if (success) {
243       var result = jsonParseSafe(xhr.responseText);
244       if ('data' in result && 'authorizationCode' in result['data']) {
245         that.hostDaemonFacade_.getCredentialsFromAuthCode(
246             result['data']['authorizationCode'],
247             onServiceAccountCredentials.bind(
248                 null, hostName, publicKey, privateKey),
249             onError);
250       } else {
251         // No authorization code returned, use regular user credential flow.
252         that.hostDaemonFacade_.getPinHash(
253             newHostId, hostPin, startHostWithHash.bind(
254                 null, hostName, publicKey, privateKey,
255                 remoting.identity.getCachedEmail(),
256                 remoting.oauth2.getRefreshToken()),
257           onError);
258       }
259     } else {
260       console.log('Failed to register the host. Status: ' + xhr.status +
261                   ' response: ' + xhr.responseText);
262       onError(remoting.Error.REGISTRATION_FAILED);
263     }
264   }
265
266   /**
267    * @param {string} hostName
268    * @param {string} privateKey
269    * @param {string} publicKey
270    * @param {string} hostClientId
271    * @param {string} oauthToken
272    */
273   function doRegisterHost(
274       hostName, privateKey, publicKey, hostClientId, oauthToken) {
275     var headers = {
276       'Authorization': 'OAuth ' + oauthToken,
277       'Content-type' : 'application/json; charset=UTF-8'
278     };
279
280     var newHostDetails = { data: {
281        hostId: newHostId,
282        hostName: hostName,
283        publicKey: publicKey
284     } };
285
286     var registerHostUrl =
287         remoting.settings.DIRECTORY_API_BASE_URL + '/@me/hosts';
288
289     if (hostClientId) {
290       registerHostUrl += '?' + remoting.xhr.urlencodeParamHash(
291           { hostClientId: hostClientId });
292     }
293
294     remoting.xhr.post(
295         registerHostUrl,
296         onRegistered.bind(null, hostName, publicKey, privateKey),
297         JSON.stringify(newHostDetails),
298         headers);
299   }
300
301   /**
302    * @param {string} hostName
303    * @param {string} privateKey
304    * @param {string} publicKey
305    * @param {string} hostClientId
306    */
307   function onHostClientId(
308       hostName, privateKey, publicKey, hostClientId) {
309     remoting.identity.callWithToken(
310         doRegisterHost.bind(
311             null, hostName, privateKey, publicKey, hostClientId), onError);
312   }
313
314   /**
315    * @param {string} hostName
316    * @param {string} privateKey
317    * @param {string} publicKey
318    * @param {boolean} hasFeature
319    */
320   function onHasFeatureOAuthClient(
321       hostName, privateKey, publicKey, hasFeature) {
322     if (hasFeature) {
323       that.hostDaemonFacade_.getHostClientId(
324           onHostClientId.bind(null, hostName, privateKey, publicKey), onError);
325     } else {
326       remoting.identity.callWithToken(
327           doRegisterHost.bind(
328               null, hostName, privateKey, publicKey, null), onError);
329     }
330   }
331
332   /**
333    * @param {string} hostName
334    * @param {string} privateKey
335    * @param {string} publicKey
336    */
337   function onKeyGenerated(hostName, privateKey, publicKey) {
338     that.hasFeature(
339         remoting.HostController.Feature.OAUTH_CLIENT,
340         onHasFeatureOAuthClient.bind(null, hostName, privateKey, publicKey));
341   }
342
343   /**
344    * @param {string} hostName
345    * @return {void} Nothing.
346    */
347   function startWithHostname(hostName) {
348     that.hostDaemonFacade_.generateKeyPair(onKeyGenerated.bind(null, hostName),
349                                          onError);
350   }
351
352   this.hostDaemonFacade_.getHostName(startWithHostname, onError);
353 };
354
355 /**
356  * Stop the daemon process.
357  * @param {function():void} onDone Callback to be called when done.
358  * @param {function(remoting.Error):void} onError Callback to be called on
359  *     error.
360  * @return {void} Nothing.
361  */
362 remoting.HostController.prototype.stop = function(onDone, onError) {
363   /** @type {remoting.HostController} */
364   var that = this;
365
366   /** @param {string?} hostId The host id of the local host. */
367   function unregisterHost(hostId) {
368     if (hostId) {
369       remoting.HostList.unregisterHostById(hostId);
370     }
371     onDone();
372   }
373
374   /** @param {remoting.HostController.AsyncResult} result */
375   function onStopped(result) {
376     if (result == remoting.HostController.AsyncResult.OK) {
377       that.getLocalHostId(unregisterHost);
378     } else if (result == remoting.HostController.AsyncResult.CANCELLED) {
379       onError(remoting.Error.CANCELLED);
380     } else {
381       onError(remoting.Error.UNEXPECTED);
382     }
383   }
384
385   this.hostDaemonFacade_.stopDaemon(onStopped, onError);
386 };
387
388 /**
389  * Check the host configuration is valid (non-null, and contains both host_id
390  * and xmpp_login keys).
391  * @param {Object} config The host configuration.
392  * @return {boolean} True if it is valid.
393  */
394 function isHostConfigValid_(config) {
395   return !!config && typeof config['host_id'] == 'string' &&
396       typeof config['xmpp_login'] == 'string';
397 }
398
399 /**
400  * @param {string} newPin The new PIN to set
401  * @param {function():void} onDone Callback to be called when done.
402  * @param {function(remoting.Error):void} onError Callback to be called on
403  *     error.
404  * @return {void} Nothing.
405  */
406 remoting.HostController.prototype.updatePin = function(newPin, onDone,
407                                                        onError) {
408   /** @type {remoting.HostController} */
409   var that = this;
410
411   /** @param {remoting.HostController.AsyncResult} result */
412   function onConfigUpdated(result) {
413     if (result == remoting.HostController.AsyncResult.OK) {
414       onDone();
415     } else if (result == remoting.HostController.AsyncResult.CANCELLED) {
416       onError(remoting.Error.CANCELLED);
417     } else {
418       onError(remoting.Error.UNEXPECTED);
419     }
420   }
421
422   /** @param {string} pinHash */
423   function updateDaemonConfigWithHash(pinHash) {
424     var newConfig = {
425       host_secret_hash: pinHash
426     };
427     that.hostDaemonFacade_.updateDaemonConfig(newConfig, onConfigUpdated,
428                                             onError);
429   }
430
431   /** @param {Object} config */
432   function onConfig(config) {
433     if (!isHostConfigValid_(config)) {
434       onError(remoting.Error.UNEXPECTED);
435       return;
436     }
437     /** @type {string} */
438     var hostId = config['host_id'];
439     that.hostDaemonFacade_.getPinHash(
440         hostId, newPin, updateDaemonConfigWithHash, onError);
441   }
442
443   // TODO(sergeyu): When crbug.com/121518 is fixed: replace this call
444   // with an unprivileged version if that is necessary.
445   this.hostDaemonFacade_.getDaemonConfig(onConfig, onError);
446 };
447
448 /**
449  * Get the state of the local host.
450  *
451  * @param {function(remoting.HostController.State):void} onDone Completion
452  *     callback.
453  */
454 remoting.HostController.prototype.getLocalHostState = function(onDone) {
455   /** @param {remoting.Error} error */
456   function onError(error) {
457     onDone((error == remoting.Error.MISSING_PLUGIN) ?
458                remoting.HostController.State.NOT_INSTALLED :
459                remoting.HostController.State.UNKNOWN);
460   }
461   this.hostDaemonFacade_.getDaemonState(onDone, onError);
462 };
463
464 /**
465  * Get the id of the local host, or null if it is not registered.
466  *
467  * @param {function(string?):void} onDone Completion callback.
468  */
469 remoting.HostController.prototype.getLocalHostId = function(onDone) {
470   /** @type {remoting.HostController} */
471   var that = this;
472   /** @param {Object} config */
473   function onConfig(config) {
474     var hostId = null;
475     if (isHostConfigValid_(config)) {
476       hostId = /** @type {string} */ config['host_id'];
477     }
478     onDone(hostId);
479   };
480
481   this.hostDaemonFacade_.getDaemonConfig(onConfig, function(error) {
482     onDone(null);
483   });
484 };
485
486 /**
487  * Fetch the list of paired clients for this host.
488  *
489  * @param {function(Array.<remoting.PairedClient>):void} onDone
490  * @param {function(remoting.Error):void} onError
491  * @return {void}
492  */
493 remoting.HostController.prototype.getPairedClients = function(onDone,
494                                                               onError) {
495   this.hostDaemonFacade_.getPairedClients(onDone, onError);
496 };
497
498 /**
499  * Delete a single paired client.
500  *
501  * @param {string} client The client id of the pairing to delete.
502  * @param {function():void} onDone Completion callback.
503  * @param {function(remoting.Error):void} onError Error callback.
504  * @return {void}
505  */
506 remoting.HostController.prototype.deletePairedClient = function(
507     client, onDone, onError) {
508   this.hostDaemonFacade_.deletePairedClient(client, onDone, onError);
509 };
510
511 /**
512  * Delete all paired clients.
513  *
514  * @param {function():void} onDone Completion callback.
515  * @param {function(remoting.Error):void} onError Error callback.
516  * @return {void}
517  */
518 remoting.HostController.prototype.clearPairedClients = function(
519     onDone, onError) {
520   this.hostDaemonFacade_.clearPairedClients(onDone, onError);
521 };
522
523 /**
524  * Gets the host owner's base JID, used by the host for client authorization.
525  * In most cases this is the same as the owner's email address, but for
526  * non-Gmail accounts, it may be different.
527  *
528  * @private
529  * @param {function(string): void} onSuccess
530  * @param {function(remoting.Error): void} onError
531  */
532 remoting.HostController.prototype.getClientBaseJid_ = function(
533     onSuccess, onError) {
534   var signalStrategy = null;
535
536   var onState = function(state) {
537     switch (state) {
538       case remoting.SignalStrategy.State.CONNECTED:
539         var jid = signalStrategy.getJid().split('/')[0].toLowerCase();
540         base.dispose(signalStrategy);
541         signalStrategy = null;
542         onSuccess(jid);
543         break;
544
545       case remoting.SignalStrategy.State.FAILED:
546         var error = signalStrategy.getError();
547         base.dispose(signalStrategy);
548         signalStrategy = null;
549         onError(error);
550         break;
551     }
552   };
553
554   signalStrategy = remoting.SignalStrategy.create(onState);
555
556   /** @param {string} token */
557   function connectSignalingWithToken(token) {
558     remoting.identity.getEmail(
559         connectSignalingWithTokenAndEmail.bind(null, token), onError);
560   }
561
562   /**
563    * @param {string} token
564    * @param {string} email
565    */
566   function connectSignalingWithTokenAndEmail(token, email) {
567     signalStrategy.connect(
568         remoting.settings.XMPP_SERVER_ADDRESS, email, token);
569   }
570
571   remoting.identity.callWithToken(connectSignalingWithToken, onError);
572 };
573
574 /** @type {remoting.HostController} */
575 remoting.hostController = null;