Upstream version 11.40.271.0
[platform/framework/web/crosswalk.git] / src / remoting / webapp / crd / js / 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 = base.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                 remoting.identity.getCachedEmail()),
258           onError);
259       }
260     } else {
261       console.log('Failed to register the host. Status: ' + xhr.status +
262                   ' response: ' + xhr.responseText);
263       onError(remoting.Error.REGISTRATION_FAILED);
264     }
265   }
266
267   /**
268    * @param {string} hostName
269    * @param {string} privateKey
270    * @param {string} publicKey
271    * @param {string} hostClientId
272    * @param {string} oauthToken
273    */
274   function doRegisterHost(
275       hostName, privateKey, publicKey, hostClientId, oauthToken) {
276     var headers = {
277       'Authorization': 'OAuth ' + oauthToken,
278       'Content-type' : 'application/json; charset=UTF-8'
279     };
280
281     var newHostDetails = { data: {
282        hostId: newHostId,
283        hostName: hostName,
284        publicKey: publicKey
285     } };
286
287     var registerHostUrl =
288         remoting.settings.DIRECTORY_API_BASE_URL + '/@me/hosts';
289
290     if (hostClientId) {
291       registerHostUrl += '?' + remoting.xhr.urlencodeParamHash(
292           { hostClientId: hostClientId });
293     }
294
295     remoting.xhr.post(
296         registerHostUrl,
297         onRegistered.bind(null, hostName, publicKey, privateKey),
298         JSON.stringify(newHostDetails),
299         headers);
300   }
301
302   /**
303    * @param {string} hostName
304    * @param {string} privateKey
305    * @param {string} publicKey
306    * @param {string} hostClientId
307    */
308   function onHostClientId(
309       hostName, privateKey, publicKey, hostClientId) {
310     remoting.identity.callWithToken(
311         doRegisterHost.bind(
312             null, hostName, privateKey, publicKey, hostClientId), onError);
313   }
314
315   /**
316    * @param {string} hostName
317    * @param {string} privateKey
318    * @param {string} publicKey
319    * @param {boolean} hasFeature
320    */
321   function onHasFeatureOAuthClient(
322       hostName, privateKey, publicKey, hasFeature) {
323     if (hasFeature) {
324       that.hostDaemonFacade_.getHostClientId(
325           onHostClientId.bind(null, hostName, privateKey, publicKey), onError);
326     } else {
327       remoting.identity.callWithToken(
328           doRegisterHost.bind(
329               null, hostName, privateKey, publicKey, null), onError);
330     }
331   }
332
333   /**
334    * @param {string} hostName
335    * @param {string} privateKey
336    * @param {string} publicKey
337    */
338   function onKeyGenerated(hostName, privateKey, publicKey) {
339     that.hasFeature(
340         remoting.HostController.Feature.OAUTH_CLIENT,
341         onHasFeatureOAuthClient.bind(null, hostName, privateKey, publicKey));
342   }
343
344   /**
345    * @param {string} hostName
346    * @return {void} Nothing.
347    */
348   function startWithHostname(hostName) {
349     that.hostDaemonFacade_.generateKeyPair(onKeyGenerated.bind(null, hostName),
350                                          onError);
351   }
352
353   this.hostDaemonFacade_.getHostName(startWithHostname, onError);
354 };
355
356 /**
357  * Stop the daemon process.
358  * @param {function():void} onDone Callback to be called when done.
359  * @param {function(remoting.Error):void} onError Callback to be called on
360  *     error.
361  * @return {void} Nothing.
362  */
363 remoting.HostController.prototype.stop = function(onDone, onError) {
364   /** @type {remoting.HostController} */
365   var that = this;
366
367   /** @param {string?} hostId The host id of the local host. */
368   function unregisterHost(hostId) {
369     if (hostId) {
370       remoting.HostList.unregisterHostById(hostId);
371     }
372     onDone();
373   }
374
375   /** @param {remoting.HostController.AsyncResult} result */
376   function onStopped(result) {
377     if (result == remoting.HostController.AsyncResult.OK) {
378       that.getLocalHostId(unregisterHost);
379     } else if (result == remoting.HostController.AsyncResult.CANCELLED) {
380       onError(remoting.Error.CANCELLED);
381     } else {
382       onError(remoting.Error.UNEXPECTED);
383     }
384   }
385
386   this.hostDaemonFacade_.stopDaemon(onStopped, onError);
387 };
388
389 /**
390  * Check the host configuration is valid (non-null, and contains both host_id
391  * and xmpp_login keys).
392  * @param {Object} config The host configuration.
393  * @return {boolean} True if it is valid.
394  */
395 function isHostConfigValid_(config) {
396   return !!config && typeof config['host_id'] == 'string' &&
397       typeof config['xmpp_login'] == 'string';
398 }
399
400 /**
401  * @param {string} newPin The new PIN to set
402  * @param {function():void} onDone Callback to be called when done.
403  * @param {function(remoting.Error):void} onError Callback to be called on
404  *     error.
405  * @return {void} Nothing.
406  */
407 remoting.HostController.prototype.updatePin = function(newPin, onDone,
408                                                        onError) {
409   /** @type {remoting.HostController} */
410   var that = this;
411
412   /** @param {remoting.HostController.AsyncResult} result */
413   function onConfigUpdated(result) {
414     if (result == remoting.HostController.AsyncResult.OK) {
415       onDone();
416     } else if (result == remoting.HostController.AsyncResult.CANCELLED) {
417       onError(remoting.Error.CANCELLED);
418     } else {
419       onError(remoting.Error.UNEXPECTED);
420     }
421   }
422
423   /** @param {string} pinHash */
424   function updateDaemonConfigWithHash(pinHash) {
425     var newConfig = {
426       host_secret_hash: pinHash
427     };
428     that.hostDaemonFacade_.updateDaemonConfig(newConfig, onConfigUpdated,
429                                             onError);
430   }
431
432   /** @param {Object} config */
433   function onConfig(config) {
434     if (!isHostConfigValid_(config)) {
435       onError(remoting.Error.UNEXPECTED);
436       return;
437     }
438     /** @type {string} */
439     var hostId = config['host_id'];
440     that.hostDaemonFacade_.getPinHash(
441         hostId, newPin, updateDaemonConfigWithHash, onError);
442   }
443
444   // TODO(sergeyu): When crbug.com/121518 is fixed: replace this call
445   // with an unprivileged version if that is necessary.
446   this.hostDaemonFacade_.getDaemonConfig(onConfig, onError);
447 };
448
449 /**
450  * Get the state of the local host.
451  *
452  * @param {function(remoting.HostController.State):void} onDone Completion
453  *     callback.
454  */
455 remoting.HostController.prototype.getLocalHostState = function(onDone) {
456   /** @param {remoting.Error} error */
457   function onError(error) {
458     onDone((error == remoting.Error.MISSING_PLUGIN) ?
459                remoting.HostController.State.NOT_INSTALLED :
460                remoting.HostController.State.UNKNOWN);
461   }
462   this.hostDaemonFacade_.getDaemonState(onDone, onError);
463 };
464
465 /**
466  * Get the id of the local host, or null if it is not registered.
467  *
468  * @param {function(string?):void} onDone Completion callback.
469  */
470 remoting.HostController.prototype.getLocalHostId = function(onDone) {
471   /** @type {remoting.HostController} */
472   var that = this;
473   /** @param {Object} config */
474   function onConfig(config) {
475     var hostId = null;
476     if (isHostConfigValid_(config)) {
477       hostId = /** @type {string} */ config['host_id'];
478     }
479     onDone(hostId);
480   };
481
482   this.hostDaemonFacade_.getDaemonConfig(onConfig, function(error) {
483     onDone(null);
484   });
485 };
486
487 /**
488  * Fetch the list of paired clients for this host.
489  *
490  * @param {function(Array.<remoting.PairedClient>):void} onDone
491  * @param {function(remoting.Error):void} onError
492  * @return {void}
493  */
494 remoting.HostController.prototype.getPairedClients = function(onDone,
495                                                               onError) {
496   this.hostDaemonFacade_.getPairedClients(onDone, onError);
497 };
498
499 /**
500  * Delete a single paired client.
501  *
502  * @param {string} client The client id of the pairing to delete.
503  * @param {function():void} onDone Completion callback.
504  * @param {function(remoting.Error):void} onError Error callback.
505  * @return {void}
506  */
507 remoting.HostController.prototype.deletePairedClient = function(
508     client, onDone, onError) {
509   this.hostDaemonFacade_.deletePairedClient(client, onDone, onError);
510 };
511
512 /**
513  * Delete all paired clients.
514  *
515  * @param {function():void} onDone Completion callback.
516  * @param {function(remoting.Error):void} onError Error callback.
517  * @return {void}
518  */
519 remoting.HostController.prototype.clearPairedClients = function(
520     onDone, onError) {
521   this.hostDaemonFacade_.clearPairedClients(onDone, onError);
522 };
523
524 /**
525  * Gets the host owner's base JID, used by the host for client authorization.
526  * In most cases this is the same as the owner's email address, but for
527  * non-Gmail accounts, it may be different.
528  *
529  * @private
530  * @param {function(string): void} onSuccess
531  * @param {function(remoting.Error): void} onError
532  */
533 remoting.HostController.prototype.getClientBaseJid_ = function(
534     onSuccess, onError) {
535   /** @type {remoting.SignalStrategy} */
536   var signalStrategy = null;
537
538   /** @param {remoting.SignalStrategy.State} state */
539   var onState = function(state) {
540     switch (state) {
541       case remoting.SignalStrategy.State.CONNECTED:
542         var jid = signalStrategy.getJid().split('/')[0].toLowerCase();
543         base.dispose(signalStrategy);
544         signalStrategy = null;
545         onSuccess(jid);
546         break;
547
548       case remoting.SignalStrategy.State.FAILED:
549         var error = signalStrategy.getError();
550         base.dispose(signalStrategy);
551         signalStrategy = null;
552         onError(error);
553         break;
554     }
555   };
556
557   signalStrategy = remoting.SignalStrategy.create(onState);
558
559   /** @param {string} token */
560   function connectSignalingWithToken(token) {
561     remoting.identity.getEmail(
562         connectSignalingWithTokenAndEmail.bind(null, token), onError);
563   }
564
565   /**
566    * @param {string} token
567    * @param {string} email
568    */
569   function connectSignalingWithTokenAndEmail(token, email) {
570     signalStrategy.connect(
571         remoting.settings.XMPP_SERVER_ADDRESS, email, token);
572   }
573
574   remoting.identity.callWithToken(connectSignalingWithToken, onError);
575 };
576
577 /** @type {remoting.HostController} */
578 remoting.hostController = null;