Upstream version 9.38.198.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} hostSecretHash
175    */
176   function startHostWithHash(hostName, publicKey, privateKey,
177                              xmppLogin, refreshToken, hostSecretHash) {
178     var hostConfig = {
179       xmpp_login: xmppLogin,
180       oauth_refresh_token: refreshToken,
181       host_id: newHostId,
182       host_name: hostName,
183       host_secret_hash: hostSecretHash,
184       private_key: privateKey
185     };
186     var hostOwner = remoting.identity.getCachedEmail();
187     if (hostOwner != xmppLogin) {
188       hostConfig['host_owner'] = hostOwner;
189     }
190     that.hostDaemonFacade_.startDaemon(
191         hostConfig, consent, onStarted.bind(null, hostName, publicKey),
192         onStartError);
193   }
194
195   /**
196    * @param {string} hostName
197    * @param {string} publicKey
198    * @param {string} privateKey
199    * @param {string} email
200    * @param {string} refreshToken
201    */
202   function onServiceAccountCredentials(
203       hostName, publicKey, privateKey, email, refreshToken) {
204     that.hostDaemonFacade_.getPinHash(
205         newHostId, hostPin,
206         startHostWithHash.bind(
207             null, hostName, publicKey, privateKey, email, refreshToken),
208         onError);
209   }
210
211   /**
212    * @param {string} hostName
213    * @param {string} publicKey
214    * @param {string} privateKey
215    * @param {XMLHttpRequest} xhr
216    */
217   function onRegistered(
218       hostName, publicKey, privateKey, xhr) {
219     var success = (xhr.status == 200);
220
221     if (success) {
222       var result = jsonParseSafe(xhr.responseText);
223       if ('data' in result && 'authorizationCode' in result['data']) {
224         that.hostDaemonFacade_.getCredentialsFromAuthCode(
225             result['data']['authorizationCode'],
226             onServiceAccountCredentials.bind(
227                 null, hostName, publicKey, privateKey),
228             onError);
229       } else {
230         // No authorization code returned, use regular user credential flow.
231         that.hostDaemonFacade_.getPinHash(
232             newHostId, hostPin, startHostWithHash.bind(
233                 null, hostName, publicKey, privateKey,
234                 remoting.identity.getCachedEmail(),
235                 remoting.oauth2.getRefreshToken()),
236           onError);
237       }
238     } else {
239       console.log('Failed to register the host. Status: ' + xhr.status +
240                   ' response: ' + xhr.responseText);
241       onError(remoting.Error.REGISTRATION_FAILED);
242     }
243   }
244
245   /**
246    * @param {string} hostName
247    * @param {string} privateKey
248    * @param {string} publicKey
249    * @param {string} hostClientId
250    * @param {string} oauthToken
251    */
252   function doRegisterHost(
253       hostName, privateKey, publicKey, hostClientId, oauthToken) {
254     var headers = {
255       'Authorization': 'OAuth ' + oauthToken,
256       'Content-type' : 'application/json; charset=UTF-8'
257     };
258
259     var newHostDetails = { data: {
260        hostId: newHostId,
261        hostName: hostName,
262        publicKey: publicKey
263     } };
264
265     var registerHostUrl =
266         remoting.settings.DIRECTORY_API_BASE_URL + '/@me/hosts';
267
268     if (hostClientId) {
269       registerHostUrl += '?' + remoting.xhr.urlencodeParamHash(
270           { hostClientId: hostClientId });
271     }
272
273     remoting.xhr.post(
274         registerHostUrl,
275         onRegistered.bind(null, hostName, publicKey, privateKey),
276         JSON.stringify(newHostDetails),
277         headers);
278   }
279
280   /**
281    * @param {string} hostName
282    * @param {string} privateKey
283    * @param {string} publicKey
284    * @param {string} hostClientId
285    */
286   function onHostClientId(
287       hostName, privateKey, publicKey, hostClientId) {
288     remoting.identity.callWithToken(
289         doRegisterHost.bind(
290             null, hostName, privateKey, publicKey, hostClientId), onError);
291   }
292
293   /**
294    * @param {string} hostName
295    * @param {string} privateKey
296    * @param {string} publicKey
297    * @param {boolean} hasFeature
298    */
299   function onHasFeatureOAuthClient(
300       hostName, privateKey, publicKey, hasFeature) {
301     if (hasFeature) {
302       that.hostDaemonFacade_.getHostClientId(
303           onHostClientId.bind(null, hostName, privateKey, publicKey), onError);
304     } else {
305       remoting.identity.callWithToken(
306           doRegisterHost.bind(
307               null, hostName, privateKey, publicKey, null), onError);
308     }
309   }
310
311   /**
312    * @param {string} hostName
313    * @param {string} privateKey
314    * @param {string} publicKey
315    */
316   function onKeyGenerated(hostName, privateKey, publicKey) {
317     that.hasFeature(
318         remoting.HostController.Feature.OAUTH_CLIENT,
319         onHasFeatureOAuthClient.bind(null, hostName, privateKey, publicKey));
320   }
321
322   /**
323    * @param {string} hostName
324    * @return {void} Nothing.
325    */
326   function startWithHostname(hostName) {
327     that.hostDaemonFacade_.generateKeyPair(onKeyGenerated.bind(null, hostName),
328                                          onError);
329   }
330
331   this.hostDaemonFacade_.getHostName(startWithHostname, onError);
332 };
333
334 /**
335  * Stop the daemon process.
336  * @param {function():void} onDone Callback to be called when done.
337  * @param {function(remoting.Error):void} onError Callback to be called on
338  *     error.
339  * @return {void} Nothing.
340  */
341 remoting.HostController.prototype.stop = function(onDone, onError) {
342   /** @type {remoting.HostController} */
343   var that = this;
344
345   /** @param {string?} hostId The host id of the local host. */
346   function unregisterHost(hostId) {
347     if (hostId) {
348       remoting.HostList.unregisterHostById(hostId);
349     }
350     onDone();
351   }
352
353   /** @param {remoting.HostController.AsyncResult} result */
354   function onStopped(result) {
355     if (result == remoting.HostController.AsyncResult.OK) {
356       that.getLocalHostId(unregisterHost);
357     } else if (result == remoting.HostController.AsyncResult.CANCELLED) {
358       onError(remoting.Error.CANCELLED);
359     } else {
360       onError(remoting.Error.UNEXPECTED);
361     }
362   }
363
364   this.hostDaemonFacade_.stopDaemon(onStopped, onError);
365 };
366
367 /**
368  * Check the host configuration is valid (non-null, and contains both host_id
369  * and xmpp_login keys).
370  * @param {Object} config The host configuration.
371  * @return {boolean} True if it is valid.
372  */
373 function isHostConfigValid_(config) {
374   return !!config && typeof config['host_id'] == 'string' &&
375       typeof config['xmpp_login'] == 'string';
376 }
377
378 /**
379  * @param {string} newPin The new PIN to set
380  * @param {function():void} onDone Callback to be called when done.
381  * @param {function(remoting.Error):void} onError Callback to be called on
382  *     error.
383  * @return {void} Nothing.
384  */
385 remoting.HostController.prototype.updatePin = function(newPin, onDone,
386                                                        onError) {
387   /** @type {remoting.HostController} */
388   var that = this;
389
390   /** @param {remoting.HostController.AsyncResult} result */
391   function onConfigUpdated(result) {
392     if (result == remoting.HostController.AsyncResult.OK) {
393       onDone();
394     } else if (result == remoting.HostController.AsyncResult.CANCELLED) {
395       onError(remoting.Error.CANCELLED);
396     } else {
397       onError(remoting.Error.UNEXPECTED);
398     }
399   }
400
401   /** @param {string} pinHash */
402   function updateDaemonConfigWithHash(pinHash) {
403     var newConfig = {
404       host_secret_hash: pinHash
405     };
406     that.hostDaemonFacade_.updateDaemonConfig(newConfig, onConfigUpdated,
407                                             onError);
408   }
409
410   /** @param {Object} config */
411   function onConfig(config) {
412     if (!isHostConfigValid_(config)) {
413       onError(remoting.Error.UNEXPECTED);
414       return;
415     }
416     /** @type {string} */
417     var hostId = config['host_id'];
418     that.hostDaemonFacade_.getPinHash(
419         hostId, newPin, updateDaemonConfigWithHash, onError);
420   }
421
422   // TODO(sergeyu): When crbug.com/121518 is fixed: replace this call
423   // with an unprivileged version if that is necessary.
424   this.hostDaemonFacade_.getDaemonConfig(onConfig, onError);
425 };
426
427 /**
428  * Get the state of the local host.
429  *
430  * @param {function(remoting.HostController.State):void} onDone Completion
431  *     callback.
432  */
433 remoting.HostController.prototype.getLocalHostState = function(onDone) {
434   /** @param {remoting.Error} error */
435   function onError(error) {
436     onDone((error == remoting.Error.MISSING_PLUGIN) ?
437                remoting.HostController.State.NOT_INSTALLED :
438                remoting.HostController.State.UNKNOWN);
439   }
440   this.hostDaemonFacade_.getDaemonState(onDone, onError);
441 };
442
443 /**
444  * Get the id of the local host, or null if it is not registered.
445  *
446  * @param {function(string?):void} onDone Completion callback.
447  */
448 remoting.HostController.prototype.getLocalHostId = function(onDone) {
449   /** @type {remoting.HostController} */
450   var that = this;
451   /** @param {Object} config */
452   function onConfig(config) {
453     var hostId = null;
454     if (isHostConfigValid_(config)) {
455       hostId = /** @type {string} */ config['host_id'];
456     }
457     onDone(hostId);
458   };
459
460   this.hostDaemonFacade_.getDaemonConfig(onConfig, function(error) {
461     onDone(null);
462   });
463 };
464
465 /**
466  * Fetch the list of paired clients for this host.
467  *
468  * @param {function(Array.<remoting.PairedClient>):void} onDone
469  * @param {function(remoting.Error):void} onError
470  * @return {void}
471  */
472 remoting.HostController.prototype.getPairedClients = function(onDone,
473                                                               onError) {
474   this.hostDaemonFacade_.getPairedClients(onDone, onError);
475 };
476
477 /**
478  * Delete a single paired client.
479  *
480  * @param {string} client The client id of the pairing to delete.
481  * @param {function():void} onDone Completion callback.
482  * @param {function(remoting.Error):void} onError Error callback.
483  * @return {void}
484  */
485 remoting.HostController.prototype.deletePairedClient = function(
486     client, onDone, onError) {
487   this.hostDaemonFacade_.deletePairedClient(client, onDone, onError);
488 };
489
490 /**
491  * Delete all paired clients.
492  *
493  * @param {function():void} onDone Completion callback.
494  * @param {function(remoting.Error):void} onError Error callback.
495  * @return {void}
496  */
497 remoting.HostController.prototype.clearPairedClients = function(
498     onDone, onError) {
499   this.hostDaemonFacade_.clearPairedClients(onDone, onError);
500 };
501
502 /** @type {remoting.HostController} */
503 remoting.hostController = null;