af05eab1cf7e5d57e3304d12b87bf4954fb057f9
[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   /** @return {remoting.HostPlugin} */
13   var createNpapiPlugin = function() {
14     var plugin = remoting.HostSession.createPlugin();
15     /** @type {HTMLElement} @private */
16     var container = document.getElementById('daemon-plugin-container');
17     container.appendChild(plugin);
18     return plugin;
19   };
20
21   /** @type {remoting.HostDispatcher} @private */
22   this.hostDispatcher_ = new remoting.HostDispatcher(createNpapiPlugin);
23
24   /** @param {string} version */
25   var printVersion = function(version) {
26     if (version == '') {
27       console.log('Host not installed.');
28     } else {
29       console.log('Host version: ' + version);
30     }
31   };
32
33   this.hostDispatcher_.getDaemonVersion(printVersion, function() {
34     console.log('Host version not available.');
35   });
36 };
37
38 // Note that the values in the enums below are copied from
39 // daemon_controller.h and must be kept in sync.
40 /** @enum {number} */
41 remoting.HostController.State = {
42   NOT_IMPLEMENTED: -1,
43   NOT_INSTALLED: 0,
44   INSTALLING: 1,
45   STOPPED: 2,
46   STARTING: 3,
47   STARTED: 4,
48   STOPPING: 5,
49   UNKNOWN: 6
50 };
51
52 /** @enum {number} */
53 remoting.HostController.AsyncResult = {
54   OK: 0,
55   FAILED: 1,
56   CANCELLED: 2,
57   FAILED_DIRECTORY: 3
58 };
59
60 /**
61  * Set of features for which hasFeature() can be used to test.
62  *
63  * @enum {string}
64  */
65 remoting.HostController.Feature = {
66   PAIRING_REGISTRY: 'pairingRegistry',
67   OAUTH_CLIENT: 'oauthClient'
68 };
69
70 /**
71  * @param {remoting.HostController.Feature} feature The feature to test for.
72  * @param {function(boolean):void} callback
73  * @return {void}
74  */
75 remoting.HostController.prototype.hasFeature = function(feature, callback) {
76   // TODO(rmsousa): This could synchronously return a boolean, provided it were
77   // only called after the dispatcher is completely initialized.
78   this.hostDispatcher_.hasFeature(feature, callback);
79 };
80
81 /**
82  * @param {function(boolean, boolean, boolean):void} onDone Callback to be
83  *     called when done.
84  * @param {function(remoting.Error):void} onError Callback to be called on
85  *     error.
86  */
87 remoting.HostController.prototype.getConsent = function(onDone, onError) {
88   this.hostDispatcher_.getUsageStatsConsent(onDone, onError);
89 };
90
91 /**
92  * Registers and starts the host.
93  *
94  * @param {string} hostPin Host PIN.
95  * @param {boolean} consent The user's consent to crash dump reporting.
96  * @param {function():void} onDone Callback to be called when done.
97  * @param {function(remoting.Error):void} onError Callback to be called on
98  *     error.
99  * @return {void} Nothing.
100  */
101 remoting.HostController.prototype.start = function(hostPin, consent, onDone,
102                                                    onError) {
103   /** @type {remoting.HostController} */
104   var that = this;
105
106   /** @return {string} */
107   function generateUuid() {
108     var random = new Uint16Array(8);
109     window.crypto.getRandomValues(random);
110     /** @type {Array.<string>} */
111     var e = new Array();
112     for (var i = 0; i < 8; i++) {
113       e[i] = (/** @type {number} */random[i] + 0x10000).
114           toString(16).substring(1);
115     }
116     return e[0] + e[1] + '-' + e[2] + '-' + e[3] + '-' +
117         e[4] + '-' + e[5] + e[6] + e[7];
118   };
119
120   var newHostId = generateUuid();
121
122   /** @param {remoting.Error} error */
123   function onStartError(error) {
124     // Unregister the host if we failed to start it.
125     remoting.HostList.unregisterHostById(newHostId);
126     onError(error);
127   }
128
129   /**
130    * @param {string} hostName
131    * @param {string} publicKey
132    * @param {remoting.HostController.AsyncResult} result
133    */
134   function onStarted(hostName, publicKey, result) {
135     if (result == remoting.HostController.AsyncResult.OK) {
136       remoting.hostList.onLocalHostStarted(hostName, newHostId, publicKey);
137       onDone();
138     } else if (result == remoting.HostController.AsyncResult.CANCELLED) {
139       onStartError(remoting.Error.CANCELLED);
140     } else {
141       onStartError(remoting.Error.UNEXPECTED);
142     }
143   }
144
145   /**
146    * @param {string} hostName
147    * @param {string} publicKey
148    * @param {string} privateKey
149    * @param {string} xmppLogin
150    * @param {string} refreshToken
151    * @param {string} hostSecretHash
152    */
153   function startHostWithHash(hostName, publicKey, privateKey,
154                              xmppLogin, refreshToken, hostSecretHash) {
155     var hostConfig = {
156       xmpp_login: xmppLogin,
157       oauth_refresh_token: refreshToken,
158       host_id: newHostId,
159       host_name: hostName,
160       host_secret_hash: hostSecretHash,
161       private_key: privateKey
162     };
163     var hostOwner = remoting.identity.getCachedEmail();
164     if (hostOwner != xmppLogin) {
165       hostConfig['host_owner'] = hostOwner;
166     }
167     that.hostDispatcher_.startDaemon(hostConfig, consent,
168                                      onStarted.bind(null, hostName, publicKey),
169                                      onStartError);
170   }
171
172   /**
173    * @param {string} hostName
174    * @param {string} publicKey
175    * @param {string} privateKey
176    * @param {string} email
177    * @param {string} refreshToken
178    */
179   function onServiceAccountCredentials(
180       hostName, publicKey, privateKey, email, refreshToken) {
181     that.hostDispatcher_.getPinHash(
182         newHostId, hostPin,
183         startHostWithHash.bind(
184             null, hostName, publicKey, privateKey, email, refreshToken),
185         onError);
186   }
187
188   /**
189    * @param {string} hostName
190    * @param {string} publicKey
191    * @param {string} privateKey
192    * @param {XMLHttpRequest} xhr
193    */
194   function onRegistered(
195       hostName, publicKey, privateKey, xhr) {
196     var success = (xhr.status == 200);
197
198     if (success) {
199       var result = jsonParseSafe(xhr.responseText);
200       if ('data' in result && 'authorizationCode' in result['data']) {
201         that.hostDispatcher_.getCredentialsFromAuthCode(
202             result['data']['authorizationCode'],
203             onServiceAccountCredentials.bind(
204                 null, hostName, publicKey, privateKey),
205             onError);
206       } else {
207         // No authorization code returned, use regular user credential flow.
208         that.hostDispatcher_.getPinHash(
209             newHostId, hostPin, startHostWithHash.bind(
210                 null, hostName, publicKey, privateKey,
211                 remoting.identity.getCachedEmail(),
212                 remoting.oauth2.exportRefreshToken()),
213           onError);
214       }
215     } else {
216       console.log('Failed to register the host. Status: ' + xhr.status +
217                   ' response: ' + xhr.responseText);
218       onError(remoting.Error.REGISTRATION_FAILED);
219     }
220   }
221
222   /**
223    * @param {string} hostName
224    * @param {string} privateKey
225    * @param {string} publicKey
226    * @param {string} hostClientId
227    * @param {string} oauthToken
228    */
229   function doRegisterHost(
230       hostName, privateKey, publicKey, hostClientId, oauthToken) {
231     var headers = {
232       'Authorization': 'OAuth ' + oauthToken,
233       'Content-type' : 'application/json; charset=UTF-8'
234     };
235
236     var newHostDetails = { data: {
237        hostId: newHostId,
238        hostName: hostName,
239        publicKey: publicKey
240     } };
241
242     var registerHostUrl =
243         remoting.settings.DIRECTORY_API_BASE_URL + '/@me/hosts';
244
245     if (hostClientId) {
246       registerHostUrl += '?' + remoting.xhr.urlencodeParamHash(
247           { hostClientId: hostClientId });
248     }
249
250     remoting.xhr.post(
251         registerHostUrl,
252         onRegistered.bind(null, hostName, publicKey, privateKey),
253         JSON.stringify(newHostDetails),
254         headers);
255   }
256
257   /**
258    * @param {string} hostName
259    * @param {string} privateKey
260    * @param {string} publicKey
261    * @param {string} hostClientId
262    */
263   function onHostClientId(
264       hostName, privateKey, publicKey, hostClientId) {
265     remoting.identity.callWithToken(
266         doRegisterHost.bind(
267             null, hostName, privateKey, publicKey, hostClientId), onError);
268   }
269
270   /**
271    * @param {string} hostName
272    * @param {string} privateKey
273    * @param {string} publicKey
274    * @param {boolean} hasFeature
275    */
276   function onHasFeatureOAuthClient(
277       hostName, privateKey, publicKey, hasFeature) {
278     if (hasFeature) {
279       that.hostDispatcher_.getHostClientId(
280           onHostClientId.bind(null, hostName, privateKey, publicKey), onError);
281     } else {
282       remoting.identity.callWithToken(
283           doRegisterHost.bind(
284               null, hostName, privateKey, publicKey, null), onError);
285     }
286   }
287
288   /**
289    * @param {string} hostName
290    * @param {string} privateKey
291    * @param {string} publicKey
292    */
293   function onKeyGenerated(hostName, privateKey, publicKey) {
294     that.hasFeature(
295         remoting.HostController.Feature.OAUTH_CLIENT,
296         onHasFeatureOAuthClient.bind(null, hostName, privateKey, publicKey));
297   }
298
299   /**
300    * @param {string} hostName
301    * @return {void} Nothing.
302    */
303   function startWithHostname(hostName) {
304     that.hostDispatcher_.generateKeyPair(onKeyGenerated.bind(null, hostName),
305                                          onError);
306   }
307
308   this.hostDispatcher_.getHostName(startWithHostname, onError);
309 };
310
311 /**
312  * Stop the daemon process.
313  * @param {function():void} onDone Callback to be called when done.
314  * @param {function(remoting.Error):void} onError Callback to be called on
315  *     error.
316  * @return {void} Nothing.
317  */
318 remoting.HostController.prototype.stop = function(onDone, onError) {
319   /** @type {remoting.HostController} */
320   var that = this;
321
322   /** @param {string?} hostId The host id of the local host. */
323   function unregisterHost(hostId) {
324     if (hostId) {
325       remoting.HostList.unregisterHostById(hostId);
326     }
327     onDone();
328   }
329
330   /** @param {remoting.HostController.AsyncResult} result */
331   function onStopped(result) {
332     if (result == remoting.HostController.AsyncResult.OK) {
333       that.getLocalHostId(unregisterHost);
334     } else if (result == remoting.HostController.AsyncResult.CANCELLED) {
335       onError(remoting.Error.CANCELLED);
336     } else {
337       onError(remoting.Error.UNEXPECTED);
338     }
339   }
340
341   this.hostDispatcher_.stopDaemon(onStopped, onError);
342 };
343
344 /**
345  * Check the host configuration is valid (non-null, and contains both host_id
346  * and xmpp_login keys).
347  * @param {Object} config The host configuration.
348  * @return {boolean} True if it is valid.
349  */
350 function isHostConfigValid_(config) {
351   return !!config && typeof config['host_id'] == 'string' &&
352       typeof config['xmpp_login'] == 'string';
353 }
354
355 /**
356  * @param {string} newPin The new PIN to set
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.updatePin = function(newPin, onDone,
363                                                        onError) {
364   /** @type {remoting.HostController} */
365   var that = this;
366
367   /** @param {remoting.HostController.AsyncResult} result */
368   function onConfigUpdated(result) {
369     if (result == remoting.HostController.AsyncResult.OK) {
370       onDone();
371     } else if (result == remoting.HostController.AsyncResult.CANCELLED) {
372       onError(remoting.Error.CANCELLED);
373     } else {
374       onError(remoting.Error.UNEXPECTED);
375     }
376   }
377
378   /** @param {string} pinHash */
379   function updateDaemonConfigWithHash(pinHash) {
380     var newConfig = {
381       host_secret_hash: pinHash
382     };
383     that.hostDispatcher_.updateDaemonConfig(newConfig, onConfigUpdated,
384                                             onError);
385   }
386
387   /** @param {Object} config */
388   function onConfig(config) {
389     if (!isHostConfigValid_(config)) {
390       onError(remoting.Error.UNEXPECTED);
391       return;
392     }
393     /** @type {string} */
394     var hostId = config['host_id'];
395     that.hostDispatcher_.getPinHash(hostId, newPin, updateDaemonConfigWithHash,
396                                     onError);
397   }
398
399   // TODO(sergeyu): When crbug.com/121518 is fixed: replace this call
400   // with an unprivileged version if that is necessary.
401   this.hostDispatcher_.getDaemonConfig(onConfig, onError);
402 };
403
404 /**
405  * Get the state of the local host.
406  *
407  * @param {function(remoting.HostController.State):void} onDone Completion
408  *     callback.
409  */
410 remoting.HostController.prototype.getLocalHostState = function(onDone) {
411   this.hostDispatcher_.getDaemonState(onDone, function() {
412     onDone(remoting.HostController.State.NOT_IMPLEMENTED);
413   });
414 };
415
416 /**
417  * Get the id of the local host, or null if it is not registered.
418  *
419  * @param {function(string?):void} onDone Completion callback.
420  */
421 remoting.HostController.prototype.getLocalHostId = function(onDone) {
422   /** @type {remoting.HostController} */
423   var that = this;
424   /** @param {Object} config */
425   function onConfig(config) {
426     var hostId = null;
427     if (isHostConfigValid_(config)) {
428       hostId = /** @type {string} */ config['host_id'];
429     }
430     onDone(hostId);
431   };
432
433   this.hostDispatcher_.getDaemonConfig(onConfig, function(error) {
434     onDone(null);
435   });
436 };
437
438 /**
439  * Fetch the list of paired clients for this host.
440  *
441  * @param {function(Array.<remoting.PairedClient>):void} onDone
442  * @param {function(remoting.Error):void} onError
443  * @return {void}
444  */
445 remoting.HostController.prototype.getPairedClients = function(onDone,
446                                                               onError) {
447   this.hostDispatcher_.getPairedClients(onDone, onError);
448 };
449
450 /**
451  * Delete a single paired client.
452  *
453  * @param {string} client The client id of the pairing to delete.
454  * @param {function():void} onDone Completion callback.
455  * @param {function(remoting.Error):void} onError Error callback.
456  * @return {void}
457  */
458 remoting.HostController.prototype.deletePairedClient = function(
459     client, onDone, onError) {
460   this.hostDispatcher_.deletePairedClient(client, onDone, onError);
461 };
462
463 /**
464  * Delete all paired clients.
465  *
466  * @param {function():void} onDone Completion callback.
467  * @param {function(remoting.Error):void} onError Error callback.
468  * @return {void}
469  */
470 remoting.HostController.prototype.clearPairedClients = function(
471     onDone, onError) {
472   this.hostDispatcher_.clearPairedClients(onDone, onError);
473 };
474
475 /** @type {remoting.HostController} */
476 remoting.hostController = null;