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.
7 /** @suppress {duplicate} */
8 var remoting = remoting || {};
11 remoting.HostController = function() {
12 this.hostDaemonFacade_ = this.createDaemonFacade_();
15 // The values in the enums below are duplicated in daemon_controller.h except
18 remoting.HostController.State = {
29 * @param {string} state The host controller state name.
30 * @return {remoting.HostController.State} The state enum value.
32 remoting.HostController.State.fromString = function(state) {
33 if (!remoting.HostController.State.hasOwnProperty(state)) {
34 throw "Invalid HostController.State: " + state;
36 return remoting.HostController.State[state];
40 remoting.HostController.AsyncResult = {
48 * @param {string} result The async result name.
49 * @return {remoting.HostController.AsyncResult} The result enum value.
51 remoting.HostController.AsyncResult.fromString = function(result) {
52 if (!remoting.HostController.AsyncResult.hasOwnProperty(result)) {
53 throw "Invalid HostController.AsyncResult: " + result;
55 return remoting.HostController.AsyncResult[result];
59 * @return {remoting.HostDaemonFacade}
62 remoting.HostController.prototype.createDaemonFacade_ = function() {
63 /** @type {remoting.HostDaemonFacade} @private */
64 var hostDaemonFacade = new remoting.HostDaemonFacade();
66 /** @param {string} version */
67 var printVersion = function(version) {
69 console.log('Host not installed.');
71 console.log('Host version: ' + version);
75 hostDaemonFacade.getDaemonVersion(printVersion, function() {
76 console.log('Host version not available.');
79 return hostDaemonFacade;
83 * Set of features for which hasFeature() can be used to test.
87 remoting.HostController.Feature = {
88 PAIRING_REGISTRY: 'pairingRegistry',
89 OAUTH_CLIENT: 'oauthClient'
93 * @param {remoting.HostController.Feature} feature The feature to test for.
94 * @param {function(boolean):void} callback
97 remoting.HostController.prototype.hasFeature = function(feature, callback) {
98 // TODO(rmsousa): This could synchronously return a boolean, provided it were
99 // only called after native messaging is completely initialized.
100 this.hostDaemonFacade_.hasFeature(feature, callback);
104 * @param {function(boolean, boolean, boolean):void} onDone Callback to be
106 * @param {function(remoting.Error):void} onError Callback to be called on
109 remoting.HostController.prototype.getConsent = function(onDone, onError) {
110 this.hostDaemonFacade_.getUsageStatsConsent(onDone, onError);
114 * Registers and starts the host.
116 * @param {string} hostPin Host PIN.
117 * @param {boolean} consent The user's consent to crash dump reporting.
118 * @param {function():void} onDone Callback to be called when done.
119 * @param {function(remoting.Error):void} onError Callback to be called on
121 * @return {void} Nothing.
123 remoting.HostController.prototype.start = function(hostPin, consent, onDone,
125 /** @type {remoting.HostController} */
128 /** @return {string} */
129 function generateUuid() {
130 var random = new Uint16Array(8);
131 window.crypto.getRandomValues(random);
132 /** @type {Array<string>} */
134 for (var i = 0; i < 8; i++) {
135 e[i] = (/** @type {number} */ (random[i]) + 0x10000).
136 toString(16).substring(1);
138 return e[0] + e[1] + '-' + e[2] + '-' + e[3] + '-' +
139 e[4] + '-' + e[5] + e[6] + e[7];
142 var newHostId = generateUuid();
144 /** @param {remoting.Error} error */
145 function onStartError(error) {
146 // Unregister the host if we failed to start it.
147 remoting.HostList.unregisterHostById(newHostId);
152 * @param {string} hostName
153 * @param {string} publicKey
154 * @param {remoting.HostController.AsyncResult} result
156 function onStarted(hostName, publicKey, result) {
157 if (result == remoting.HostController.AsyncResult.OK) {
158 remoting.hostList.onLocalHostStarted(hostName, newHostId, publicKey);
160 } else if (result == remoting.HostController.AsyncResult.CANCELLED) {
161 onStartError(remoting.Error.CANCELLED);
163 onStartError(remoting.Error.UNEXPECTED);
168 * @param {string} hostName
169 * @param {string} publicKey
170 * @param {string} privateKey
171 * @param {?string} xmppLogin
172 * @param {?string} refreshToken
173 * @param {?string} clientBaseJid
174 * @param {string} hostSecretHash
176 function startHostWithHash(hostName, publicKey, privateKey, xmppLogin,
177 refreshToken, clientBaseJid, hostSecretHash) {
179 xmpp_login: xmppLogin,
180 oauth_refresh_token: refreshToken,
183 host_secret_hash: hostSecretHash,
184 private_key: privateKey
186 var hostOwner = clientBaseJid;
187 var hostOwnerEmail = remoting.identity.getCachedEmail();
188 if (hostOwner != xmppLogin) {
189 hostConfig['host_owner'] = hostOwner;
190 if (hostOwnerEmail != hostOwner) {
191 hostConfig['host_owner_email'] = hostOwnerEmail;
194 that.hostDaemonFacade_.startDaemon(
195 hostConfig, consent, onStarted.bind(null, hostName, publicKey),
200 * @param {string} hostName
201 * @param {string} publicKey
202 * @param {string} privateKey
203 * @param {string} email
204 * @param {string} refreshToken
205 * @param {string} clientBaseJid
207 function onClientBaseJid(
208 hostName, publicKey, privateKey, email, refreshToken, clientBaseJid) {
209 that.hostDaemonFacade_.getPinHash(
211 startHostWithHash.bind(null, hostName, publicKey, privateKey,
212 email, refreshToken, clientBaseJid),
217 * @param {string} hostName
218 * @param {string} publicKey
219 * @param {string} privateKey
220 * @param {string} email
221 * @param {string} refreshToken
223 function onServiceAccountCredentials(
224 hostName, publicKey, privateKey, email, refreshToken) {
225 that.getClientBaseJid_(
226 onClientBaseJid.bind(
227 null, hostName, publicKey, privateKey, email, refreshToken),
232 * @param {string} hostName
233 * @param {string} publicKey
234 * @param {string} privateKey
235 * @param {XMLHttpRequest} xhr
237 function onRegistered(
238 hostName, publicKey, privateKey, xhr) {
239 var success = (xhr.status == 200);
242 var result = base.jsonParseSafe(xhr.responseText);
243 if ('data' in result && 'authorizationCode' in result['data']) {
244 that.hostDaemonFacade_.getCredentialsFromAuthCode(
245 result['data']['authorizationCode'],
246 onServiceAccountCredentials.bind(
247 null, hostName, publicKey, privateKey),
250 // No authorization code returned, use regular user credential flow.
251 that.hostDaemonFacade_.getPinHash(
252 newHostId, hostPin, startHostWithHash.bind(
253 null, hostName, publicKey, privateKey,
254 remoting.identity.getCachedEmail(),
255 remoting.oauth2.getRefreshToken(),
256 remoting.identity.getCachedEmail()),
260 console.log('Failed to register the host. Status: ' + xhr.status +
261 ' response: ' + xhr.responseText);
262 onError(remoting.Error.REGISTRATION_FAILED);
267 * @param {string} hostName
268 * @param {string} privateKey
269 * @param {string} publicKey
270 * @param {?string} hostClientId
271 * @param {string} oauthToken
273 function doRegisterHost(
274 hostName, privateKey, publicKey, hostClientId, oauthToken) {
275 var newHostDetails = { data: {
281 var registerHostUrl =
282 remoting.settings.DIRECTORY_API_BASE_URL + '/@me/hosts';
286 url: remoting.settings.DIRECTORY_API_BASE_URL + '/@me/hosts',
288 hostClientId: hostClientId
290 onDone: onRegistered.bind(null, hostName, publicKey, privateKey),
291 jsonContent: newHostDetails,
292 oauthToken: oauthToken
297 * @param {string} hostName
298 * @param {string} privateKey
299 * @param {string} publicKey
300 * @param {string} hostClientId
302 function onHostClientId(
303 hostName, privateKey, publicKey, hostClientId) {
304 remoting.identity.getToken().then(
306 null, hostName, privateKey, publicKey, hostClientId),
307 remoting.Error.handler(onError));
311 * @param {string} hostName
312 * @param {string} privateKey
313 * @param {string} publicKey
314 * @param {boolean} hasFeature
316 function onHasFeatureOAuthClient(
317 hostName, privateKey, publicKey, hasFeature) {
319 that.hostDaemonFacade_.getHostClientId(
320 onHostClientId.bind(null, hostName, privateKey, publicKey), onError);
322 remoting.identity.getToken().then(
324 null, hostName, privateKey, publicKey, null),
325 remoting.Error.handler(onError));
330 * @param {string} hostName
331 * @param {string} privateKey
332 * @param {string} publicKey
334 function onKeyGenerated(hostName, privateKey, publicKey) {
336 remoting.HostController.Feature.OAUTH_CLIENT,
337 onHasFeatureOAuthClient.bind(null, hostName, privateKey, publicKey));
341 * @param {string} hostName
342 * @return {void} Nothing.
344 function startWithHostname(hostName) {
345 that.hostDaemonFacade_.generateKeyPair(onKeyGenerated.bind(null, hostName),
349 this.hostDaemonFacade_.getHostName(startWithHostname, onError);
353 * Stop the daemon process.
354 * @param {function():void} onDone Callback to be called when done.
355 * @param {function(remoting.Error):void} onError Callback to be called on
357 * @return {void} Nothing.
359 remoting.HostController.prototype.stop = function(onDone, onError) {
360 /** @type {remoting.HostController} */
363 /** @param {string?} hostId The host id of the local host. */
364 function unregisterHost(hostId) {
366 remoting.HostList.unregisterHostById(hostId);
371 /** @param {remoting.HostController.AsyncResult} result */
372 function onStopped(result) {
373 if (result == remoting.HostController.AsyncResult.OK) {
374 that.getLocalHostId(unregisterHost);
375 } else if (result == remoting.HostController.AsyncResult.CANCELLED) {
376 onError(remoting.Error.CANCELLED);
378 onError(remoting.Error.UNEXPECTED);
382 this.hostDaemonFacade_.stopDaemon(onStopped, onError);
386 * Check the host configuration is valid (non-null, and contains both host_id
387 * and xmpp_login keys).
388 * @param {Object} config The host configuration.
389 * @return {boolean} True if it is valid.
391 function isHostConfigValid_(config) {
392 return !!config && typeof config['host_id'] == 'string' &&
393 typeof config['xmpp_login'] == 'string';
397 * @param {string} newPin The new PIN to set
398 * @param {function():void} onDone Callback to be called when done.
399 * @param {function(remoting.Error):void} onError Callback to be called on
401 * @return {void} Nothing.
403 remoting.HostController.prototype.updatePin = function(newPin, onDone,
405 /** @type {remoting.HostController} */
408 /** @param {remoting.HostController.AsyncResult} result */
409 function onConfigUpdated(result) {
410 if (result == remoting.HostController.AsyncResult.OK) {
412 } else if (result == remoting.HostController.AsyncResult.CANCELLED) {
413 onError(remoting.Error.CANCELLED);
415 onError(remoting.Error.UNEXPECTED);
419 /** @param {string} pinHash */
420 function updateDaemonConfigWithHash(pinHash) {
422 host_secret_hash: pinHash
424 that.hostDaemonFacade_.updateDaemonConfig(newConfig, onConfigUpdated,
428 /** @param {Object} config */
429 function onConfig(config) {
430 if (!isHostConfigValid_(config)) {
431 onError(remoting.Error.UNEXPECTED);
434 /** @type {string} */
435 var hostId = config['host_id'];
436 that.hostDaemonFacade_.getPinHash(
437 hostId, newPin, updateDaemonConfigWithHash, onError);
440 // TODO(sergeyu): When crbug.com/121518 is fixed: replace this call
441 // with an unprivileged version if that is necessary.
442 this.hostDaemonFacade_.getDaemonConfig(onConfig, onError);
446 * Get the state of the local host.
448 * @param {function(remoting.HostController.State):void} onDone Completion
451 remoting.HostController.prototype.getLocalHostState = function(onDone) {
452 /** @param {remoting.Error} error */
453 function onError(error) {
454 onDone((error == remoting.Error.MISSING_PLUGIN) ?
455 remoting.HostController.State.NOT_INSTALLED :
456 remoting.HostController.State.UNKNOWN);
458 this.hostDaemonFacade_.getDaemonState(onDone, onError);
462 * Get the id of the local host, or null if it is not registered.
464 * @param {function(string?):void} onDone Completion callback.
466 remoting.HostController.prototype.getLocalHostId = function(onDone) {
467 /** @type {remoting.HostController} */
469 /** @param {Object} config */
470 function onConfig(config) {
472 if (isHostConfigValid_(config)) {
473 hostId = /** @type {string} */ (config['host_id']);
478 this.hostDaemonFacade_.getDaemonConfig(onConfig, function(error) {
484 * Fetch the list of paired clients for this host.
486 * @param {function(Array<remoting.PairedClient>):void} onDone
487 * @param {function(remoting.Error):void} onError
490 remoting.HostController.prototype.getPairedClients = function(onDone,
492 this.hostDaemonFacade_.getPairedClients(onDone, onError);
496 * Delete a single paired client.
498 * @param {string} client The client id of the pairing to delete.
499 * @param {function():void} onDone Completion callback.
500 * @param {function(remoting.Error):void} onError Error callback.
503 remoting.HostController.prototype.deletePairedClient = function(
504 client, onDone, onError) {
505 this.hostDaemonFacade_.deletePairedClient(client, onDone, onError);
509 * Delete all paired clients.
511 * @param {function():void} onDone Completion callback.
512 * @param {function(remoting.Error):void} onError Error callback.
515 remoting.HostController.prototype.clearPairedClients = function(
517 this.hostDaemonFacade_.clearPairedClients(onDone, onError);
521 * Gets the host owner's base JID, used by the host for client authorization.
522 * In most cases this is the same as the owner's email address, but for
523 * non-Gmail accounts, it may be different.
526 * @param {function(string): void} onSuccess
527 * @param {function(remoting.Error): void} onError
529 remoting.HostController.prototype.getClientBaseJid_ = function(
530 onSuccess, onError) {
531 /** @type {remoting.SignalStrategy} */
532 var signalStrategy = null;
534 /** @param {remoting.SignalStrategy.State} state */
535 var onState = function(state) {
537 case remoting.SignalStrategy.State.CONNECTED:
538 var jid = signalStrategy.getJid().split('/')[0].toLowerCase();
539 base.dispose(signalStrategy);
540 signalStrategy = null;
544 case remoting.SignalStrategy.State.FAILED:
545 var error = signalStrategy.getError();
546 base.dispose(signalStrategy);
547 signalStrategy = null;
553 signalStrategy = remoting.SignalStrategy.create();
554 signalStrategy.setStateChangedCallback(onState);
556 /** @param {string} token */
557 function connectSignalingWithToken(token) {
558 remoting.identity.getEmail().then(
559 connectSignalingWithTokenAndEmail.bind(null, token),
560 remoting.Error.handler(onError));
564 * @param {string} token
565 * @param {string} email
567 function connectSignalingWithTokenAndEmail(token, email) {
568 signalStrategy.connect(
569 remoting.settings.XMPP_SERVER_FOR_CLIENT, email, token);
572 remoting.identity.getToken().then(
573 connectSignalingWithToken, remoting.Error.handler(onError));
576 /** @type {remoting.HostController} */
577 remoting.hostController = null;