Convert cacheinvalidation_unittests to run exclusively on Swarming
[chromium-blink-merge.git] / remoting / webapp / crd / js / host_controller.js
blob03b49bc4c8c908936596ede421030a0df9c54c64
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.
5 'use strict';
7 /** @suppress {duplicate} */
8 var remoting = remoting || {};
10 /** @constructor */
11 remoting.HostController = function() {
12 /** @type {remoting.HostDaemonFacade} @private */
13 this.hostDaemonFacade_ = new remoting.HostDaemonFacade();
15 /** @param {string} version */
16 var printVersion = function(version) {
17 if (version == '') {
18 console.log('Host not installed.');
19 } else {
20 console.log('Host version: ' + version);
24 this.getLocalHostVersion()
25 .then(printVersion)
26 .catch(function() {
27 console.log('Host version not available.');
28 });
31 // The values in the enums below are duplicated in daemon_controller.h except
32 // for NOT_INSTALLED.
33 /** @enum {number} */
34 remoting.HostController.State = {
35 NOT_IMPLEMENTED: 0,
36 NOT_INSTALLED: 1,
37 STOPPED: 2,
38 STARTING: 3,
39 STARTED: 4,
40 STOPPING: 5,
41 UNKNOWN: 6
44 /**
45 * @param {string} state The host controller state name.
46 * @return {remoting.HostController.State} The state enum value.
48 remoting.HostController.State.fromString = function(state) {
49 if (!remoting.HostController.State.hasOwnProperty(state)) {
50 throw "Invalid HostController.State: " + state;
52 return remoting.HostController.State[state];
55 /** @enum {number} */
56 remoting.HostController.AsyncResult = {
57 OK: 0,
58 FAILED: 1,
59 CANCELLED: 2,
60 FAILED_DIRECTORY: 3
63 /**
64 * @param {string} result The async result name.
65 * @return {remoting.HostController.AsyncResult} The result enum value.
67 remoting.HostController.AsyncResult.fromString = function(result) {
68 if (!remoting.HostController.AsyncResult.hasOwnProperty(result)) {
69 throw "Invalid HostController.AsyncResult: " + result;
71 return remoting.HostController.AsyncResult[result];
74 /**
75 * Set of features for which hasFeature() can be used to test.
77 * @enum {string}
79 remoting.HostController.Feature = {
80 PAIRING_REGISTRY: 'pairingRegistry',
81 OAUTH_CLIENT: 'oauthClient'
84 /**
85 * Information relating to user consent to collect usage stats. The
86 * fields are:
88 * supported: True if crash dump reporting is supported by the host.
90 * allowed: True if crash dump reporting is allowed.
92 * setByPolicy: True if crash dump reporting is controlled by policy.
94 * @typedef {{
95 * supported:boolean,
96 * allowed:boolean,
97 * setByPolicy:boolean
98 * }}
100 remoting.UsageStatsConsent;
103 * @typedef {{
104 * userEmail:string,
105 * refreshToken:string
106 * }}
108 remoting.XmppCredentials;
111 * @typedef {{
112 * privateKey:string,
113 * publicKey:string
114 * }}
116 remoting.KeyPair;
119 * @param {remoting.HostController.Feature} feature The feature to test for.
120 * @return {!Promise<boolean>} A promise that always resolves.
122 remoting.HostController.prototype.hasFeature = function(feature) {
123 // TODO(rmsousa): This could synchronously return a boolean, provided it were
124 // only called after native messaging is completely initialized.
125 return this.hostDaemonFacade_.hasFeature(feature);
129 * @return {!Promise<remoting.UsageStatsConsent>}
131 remoting.HostController.prototype.getConsent = function() {
132 return this.hostDaemonFacade_.getUsageStatsConsent();
136 * Registers and starts the host.
138 * @param {string} hostPin Host PIN.
139 * @param {boolean} consent The user's consent to crash dump reporting.
140 * @return {!Promise<void>} A promise resolved once the host is started.
142 remoting.HostController.prototype.start = function(hostPin, consent) {
143 /** @type {remoting.HostController} */
144 var that = this;
146 // Start a bunch of requests with no side-effects.
147 var hostNamePromise = this.hostDaemonFacade_.getHostName();
148 var hasOauthPromise =
149 this.hasFeature(remoting.HostController.Feature.OAUTH_CLIENT);
150 var keyPairPromise = this.hostDaemonFacade_.generateKeyPair();
151 var hostClientIdPromise = hasOauthPromise.then(function(hasOauth) {
152 if (hasOauth) {
153 return that.hostDaemonFacade_.getHostClientId();
154 } else {
155 return null;
158 var hostOwnerPromise = this.getClientBaseJid_();
160 // Register the host and extract an auth code from the host response
161 // and, optionally an email address for the robot account.
162 /** @type {!Promise<remoting.HostListApi.RegisterResult>} */
163 var registerResultPromise = Promise.all([
164 hostClientIdPromise,
165 hostNamePromise,
166 keyPairPromise
167 ]).then(function(/** Array */ a) {
168 var hostClientId = /** @type {string} */ (a[0]);
169 var hostName = /** @type {string} */ (a[1]);
170 var keyPair = /** @type {remoting.KeyPair} */ (a[2]);
172 return remoting.HostListApi.getInstance().register(
173 hostName, keyPair.publicKey, hostClientId);
176 // For convenience, make the host ID available as a separate promise.
177 /** @type {!Promise<string>} */
178 var hostIdPromise = registerResultPromise.then(function(registerResult) {
179 return registerResult.hostId;
182 // Get the PIN hash based on the host ID.
183 /** @type {!Promise<string>} */
184 var pinHashPromise = hostIdPromise.then(function(hostId) {
185 return that.hostDaemonFacade_.getPinHash(hostId, hostPin);
188 // Get XMPP creditials.
189 var xmppCredsPromise = registerResultPromise.then(function(registerResult) {
190 console.assert(registerResult.authCode != '', '|authCode| is empty.');
191 if (registerResult.email) {
192 // Use auth code and email supplied by GCD.
193 return that.hostDaemonFacade_.getRefreshTokenFromAuthCode(
194 registerResult.authCode).then(function(token) {
195 return {
196 userEmail: registerResult.email,
197 refreshToken: token
200 } else {
201 // Use auth code supplied by Chromoting registry.
202 return that.hostDaemonFacade_.getCredentialsFromAuthCode(
203 registerResult.authCode);
207 // Build the host configuration.
208 /** @type {!Promise<!Object>} */
209 var hostConfigPromise = Promise.all([
210 hostNamePromise,
211 pinHashPromise,
212 xmppCredsPromise,
213 keyPairPromise,
214 hostOwnerPromise,
215 remoting.identity.getEmail(),
216 registerResultPromise
217 ]).then(function(/** Array */ a) {
218 var hostName = /** @type {string} */ (a[0]);
219 var hostSecretHash = /** @type {string} */ (a[1]);
220 var xmppCreds = /** @type {remoting.XmppCredentials} */ (a[2]);
221 var keyPair = /** @type {remoting.KeyPair} */ (a[3]);
222 var hostOwner = /** @type {string} */ (a[4]);
223 var hostOwnerEmail = /** @type {string} */ (a[5]);
224 var registerResult =
225 /** @type {remoting.HostListApi.RegisterResult} */ (a[6]);
226 var hostConfig = {
227 xmpp_login: xmppCreds.userEmail,
228 oauth_refresh_token: xmppCreds.refreshToken,
229 host_name: hostName,
230 host_secret_hash: hostSecretHash,
231 private_key: keyPair.privateKey,
232 host_owner: hostOwner
234 if (hostOwnerEmail != hostOwner) {
235 hostConfig['host_owner_email'] = hostOwnerEmail;
237 if (registerResult.isLegacy) {
238 hostConfig['host_id'] = registerResult.hostId;
240 else {
241 hostConfig['gcd_device_id'] = registerResult.hostId;
243 return hostConfig;
246 // Start the daemon.
247 /** @type {!Promise<remoting.HostController.AsyncResult>} */
248 var startDaemonResultPromise =
249 hostConfigPromise.then(function(hostConfig) {
250 return that.hostDaemonFacade_.startDaemon(hostConfig, consent);
253 // Update the UI or report an error.
254 return hostIdPromise.then(function(hostId) {
255 return startDaemonResultPromise.then(function(result) {
256 if (result == remoting.HostController.AsyncResult.OK) {
257 return hostNamePromise.then(function(hostName) {
258 return keyPairPromise.then(function(keyPair) {
259 remoting.hostList.onLocalHostStarted(
260 hostName, hostId, keyPair.publicKey);
263 } else if (result == remoting.HostController.AsyncResult.CANCELLED) {
264 throw new remoting.Error(remoting.Error.Tag.CANCELLED);
265 } else {
266 throw remoting.Error.unexpected();
268 }).catch(function(error) {
269 remoting.hostList.unregisterHostById(hostId);
270 throw error;
276 * Stop the daemon process.
277 * @param {function():void} onDone Callback to be called when done.
278 * @param {function(!remoting.Error):void} onError Callback to be called on
279 * error.
280 * @return {void} Nothing.
282 remoting.HostController.prototype.stop = function(onDone, onError) {
283 /** @type {remoting.HostController} */
284 var that = this;
286 /** @param {string?} hostId The host id of the local host. */
287 function unregisterHost(hostId) {
288 if (hostId) {
289 remoting.hostList.unregisterHostById(hostId, onDone);
290 return;
292 onDone();
295 /** @param {remoting.HostController.AsyncResult} result */
296 function onStopped(result) {
297 if (result == remoting.HostController.AsyncResult.OK) {
298 that.getLocalHostId(unregisterHost);
299 } else if (result == remoting.HostController.AsyncResult.CANCELLED) {
300 onError(new remoting.Error(remoting.Error.Tag.CANCELLED));
301 } else {
302 onError(remoting.Error.unexpected());
306 this.hostDaemonFacade_.stopDaemon().then(
307 onStopped, remoting.Error.handler(onError));
311 * Check the host configuration is valid (non-null, and contains both host_id
312 * and xmpp_login keys).
313 * @param {Object} config The host configuration.
314 * @return {boolean} True if it is valid.
316 function isHostConfigValid_(config) {
317 return !!config && typeof config['host_id'] == 'string' &&
318 typeof config['xmpp_login'] == 'string';
322 * @param {string} newPin The new PIN to set
323 * @param {function():void} onDone Callback to be called when done.
324 * @param {function(!remoting.Error):void} onError Callback to be called on
325 * error.
326 * @return {void} Nothing.
328 remoting.HostController.prototype.updatePin = function(newPin, onDone,
329 onError) {
330 /** @type {remoting.HostController} */
331 var that = this;
333 /** @param {remoting.HostController.AsyncResult} result */
334 function onConfigUpdated(result) {
335 if (result == remoting.HostController.AsyncResult.OK) {
336 that.clearPairedClients(onDone, onError);
337 } else if (result == remoting.HostController.AsyncResult.CANCELLED) {
338 onError(new remoting.Error(remoting.Error.Tag.CANCELLED));
339 } else {
340 onError(remoting.Error.unexpected());
344 /** @param {string} pinHash */
345 function updateDaemonConfigWithHash(pinHash) {
346 var newConfig = {
347 host_secret_hash: pinHash
349 that.hostDaemonFacade_.updateDaemonConfig(newConfig).then(
350 onConfigUpdated, remoting.Error.handler(onError));
353 /** @param {Object} config */
354 function onConfig(config) {
355 if (!isHostConfigValid_(config)) {
356 onError(remoting.Error.unexpected());
357 return;
359 /** @type {string} */
360 var hostId = config['host_id'];
361 that.hostDaemonFacade_.getPinHash(hostId, newPin).then(
362 updateDaemonConfigWithHash, remoting.Error.handler(onError));
365 // TODO(sergeyu): When crbug.com/121518 is fixed: replace this call
366 // with an unprivileged version if that is necessary.
367 this.hostDaemonFacade_.getDaemonConfig().then(
368 onConfig, remoting.Error.handler(onError));
372 * Get the state of the local host.
374 * @param {function(remoting.HostController.State):void} onDone Completion
375 * callback.
377 remoting.HostController.prototype.getLocalHostState = function(onDone) {
378 /** @param {!remoting.Error} error */
379 function onError(error) {
380 onDone((error.hasTag(remoting.Error.Tag.MISSING_PLUGIN)) ?
381 remoting.HostController.State.NOT_INSTALLED :
382 remoting.HostController.State.UNKNOWN);
384 this.hostDaemonFacade_.getDaemonState().then(
385 onDone, remoting.Error.handler(onError));
389 * Get the id of the local host, or null if it is not registered.
391 * @param {function(string?):void} onDone Completion callback.
393 remoting.HostController.prototype.getLocalHostId = function(onDone) {
394 /** @type {remoting.HostController} */
395 var that = this;
396 /** @param {Object} config */
397 function onConfig(config) {
398 var hostId = null;
399 if (isHostConfigValid_(config)) {
400 // Use the |gcd_device_id| field if it exists, or the |host_id|
401 // field otherwise.
402 hostId = base.getStringAttr(
403 config, 'gcd_device_id', base.getStringAttr(config, 'host_id'));
405 onDone(hostId);
408 this.hostDaemonFacade_.getDaemonConfig().then(onConfig, function(error) {
409 onDone(null);
414 * @return {Promise<string>} Promise that resolves with the host version, if
415 * installed, or rejects otherwise.
417 remoting.HostController.prototype.getLocalHostVersion = function() {
418 return this.hostDaemonFacade_.getDaemonVersion();
422 * Fetch the list of paired clients for this host.
424 * @param {function(Array<remoting.PairedClient>):void} onDone
425 * @param {function(!remoting.Error):void} onError
426 * @return {void}
428 remoting.HostController.prototype.getPairedClients = function(onDone,
429 onError) {
430 this.hostDaemonFacade_.getPairedClients().then(
431 onDone, remoting.Error.handler(onError));
435 * Delete a single paired client.
437 * @param {string} client The client id of the pairing to delete.
438 * @param {function():void} onDone Completion callback.
439 * @param {function(!remoting.Error):void} onError Error callback.
440 * @return {void}
442 remoting.HostController.prototype.deletePairedClient = function(
443 client, onDone, onError) {
444 this.hostDaemonFacade_.deletePairedClient(client).then(
445 onDone, remoting.Error.handler(onError));
449 * Delete all paired clients.
451 * @param {function():void} onDone Completion callback.
452 * @param {function(!remoting.Error):void} onError Error callback.
453 * @return {void}
455 remoting.HostController.prototype.clearPairedClients = function(
456 onDone, onError) {
457 this.hostDaemonFacade_.clearPairedClients().then(
458 onDone, remoting.Error.handler(onError));
462 * Gets the host owner's base JID, used by the host for client authorization.
463 * In most cases this is the same as the owner's email address, but for
464 * non-Gmail accounts, it may be different.
466 * @private
467 * @return {!Promise<string>}
469 remoting.HostController.prototype.getClientBaseJid_ = function() {
470 /** @type {remoting.SignalStrategy} */
471 var signalStrategy = null;
473 var result = new Promise(function(resolve, reject) {
474 /** @param {remoting.SignalStrategy.State} state */
475 var onState = function(state) {
476 switch (state) {
477 case remoting.SignalStrategy.State.CONNECTED:
478 var jid = signalStrategy.getJid().split('/')[0].toLowerCase();
479 base.dispose(signalStrategy);
480 signalStrategy = null;
481 resolve(jid);
482 break;
484 case remoting.SignalStrategy.State.FAILED:
485 var error = signalStrategy.getError();
486 base.dispose(signalStrategy);
487 signalStrategy = null;
488 reject(error);
489 break;
493 signalStrategy = remoting.SignalStrategy.create();
494 signalStrategy.setStateChangedCallback(onState);
497 var tokenPromise = remoting.identity.getToken();
498 var emailPromise = remoting.identity.getEmail();
499 tokenPromise.then(function(/** string */ token) {
500 emailPromise.then(function(/** string */ email) {
501 signalStrategy.connect(remoting.settings.XMPP_SERVER, email, token);
505 return result;
508 /** @type {remoting.HostController} */
509 remoting.hostController = null;