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 // Note that the values in the enums below are copied from
16 // daemon_controller.h and must be kept in sync.
18 remoting.HostController.State = {
30 * @param {string} state The host controller state name.
31 * @return {remoting.HostController.State} The state enum value.
33 remoting.HostController.State.fromString = function(state) {
34 if (!remoting.HostController.State.hasOwnProperty(state)) {
35 throw "Invalid HostController.State: " + state;
37 return remoting.HostController.State[state];
41 remoting.HostController.AsyncResult = {
49 * @param {string} result The async result name.
50 * @return {remoting.HostController.AsyncResult} The result enum value.
52 remoting.HostController.AsyncResult.fromString = function(result) {
53 if (!remoting.HostController.AsyncResult.hasOwnProperty(result)) {
54 throw "Invalid HostController.AsyncResult: " + result;
56 return remoting.HostController.AsyncResult[result];
60 * @return {remoting.HostDaemonFacade}
63 remoting.HostController.prototype.createDaemonFacade_ = function() {
64 /** @type {remoting.HostDaemonFacade} @private */
65 var hostDaemonFacade = new remoting.HostDaemonFacade();
67 /** @param {string} version */
68 var printVersion = function(version) {
70 console.log('Host not installed.');
72 console.log('Host version: ' + version);
76 hostDaemonFacade.getDaemonVersion(printVersion, function() {
77 console.log('Host version not available.');
80 return hostDaemonFacade;
84 * Set of features for which hasFeature() can be used to test.
88 remoting.HostController.Feature = {
89 PAIRING_REGISTRY: 'pairingRegistry',
90 OAUTH_CLIENT: 'oauthClient'
94 * @param {remoting.HostController.Feature} feature The feature to test for.
95 * @param {function(boolean):void} callback
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);
105 * @param {function(boolean, boolean, boolean):void} onDone Callback to be
107 * @param {function(remoting.Error):void} onError Callback to be called on
110 remoting.HostController.prototype.getConsent = function(onDone, onError) {
111 this.hostDaemonFacade_.getUsageStatsConsent(onDone, onError);
115 * Registers and starts the host.
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
122 * @return {void} Nothing.
124 remoting.HostController.prototype.start = function(hostPin, consent, onDone,
126 /** @type {remoting.HostController} */
129 /** @return {string} */
130 function generateUuid() {
131 var random = new Uint16Array(8);
132 window.crypto.getRandomValues(random);
133 /** @type {Array.<string>} */
135 for (var i = 0; i < 8; i++) {
136 e[i] = (/** @type {number} */random[i] + 0x10000).
137 toString(16).substring(1);
139 return e[0] + e[1] + '-' + e[2] + '-' + e[3] + '-' +
140 e[4] + '-' + e[5] + e[6] + e[7];
143 var newHostId = generateUuid();
145 /** @param {remoting.Error} error */
146 function onStartError(error) {
147 // Unregister the host if we failed to start it.
148 remoting.HostList.unregisterHostById(newHostId);
153 * @param {string} hostName
154 * @param {string} publicKey
155 * @param {remoting.HostController.AsyncResult} result
157 function onStarted(hostName, publicKey, result) {
158 if (result == remoting.HostController.AsyncResult.OK) {
159 remoting.hostList.onLocalHostStarted(hostName, newHostId, publicKey);
161 } else if (result == remoting.HostController.AsyncResult.CANCELLED) {
162 onStartError(remoting.Error.CANCELLED);
164 onStartError(remoting.Error.UNEXPECTED);
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
177 function startHostWithHash(hostName, publicKey, privateKey, xmppLogin,
178 refreshToken, clientBaseJid, hostSecretHash) {
180 xmpp_login: xmppLogin,
181 oauth_refresh_token: refreshToken,
184 host_secret_hash: hostSecretHash,
185 private_key: privateKey
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;
195 that.hostDaemonFacade_.startDaemon(
196 hostConfig, consent, onStarted.bind(null, hostName, publicKey),
201 * @param {string} hostName
202 * @param {string} publicKey
203 * @param {string} privateKey
204 * @param {string} email
205 * @param {string} refreshToken
206 * @param {string} clientBaseJid
208 function onClientBaseJid(
209 hostName, publicKey, privateKey, email, refreshToken, clientBaseJid) {
210 that.hostDaemonFacade_.getPinHash(
212 startHostWithHash.bind(null, hostName, publicKey, privateKey,
213 email, refreshToken, clientBaseJid),
218 * @param {string} hostName
219 * @param {string} publicKey
220 * @param {string} privateKey
221 * @param {string} email
222 * @param {string} refreshToken
224 function onServiceAccountCredentials(
225 hostName, publicKey, privateKey, email, refreshToken) {
226 that.getClientBaseJid_(
227 onClientBaseJid.bind(
228 null, hostName, publicKey, privateKey, email, refreshToken),
233 * @param {string} hostName
234 * @param {string} publicKey
235 * @param {string} privateKey
236 * @param {XMLHttpRequest} xhr
238 function onRegistered(
239 hostName, publicKey, privateKey, xhr) {
240 var success = (xhr.status == 200);
243 var result = 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),
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()),
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) {
276 'Authorization': 'OAuth ' + oauthToken,
277 'Content-type' : 'application/json; charset=UTF-8'
280 var newHostDetails = { data: {
286 var registerHostUrl =
287 remoting.settings.DIRECTORY_API_BASE_URL + '/@me/hosts';
290 registerHostUrl += '?' + remoting.xhr.urlencodeParamHash(
291 { hostClientId: hostClientId });
296 onRegistered.bind(null, hostName, publicKey, privateKey),
297 JSON.stringify(newHostDetails),
302 * @param {string} hostName
303 * @param {string} privateKey
304 * @param {string} publicKey
305 * @param {string} hostClientId
307 function onHostClientId(
308 hostName, privateKey, publicKey, hostClientId) {
309 remoting.identity.callWithToken(
311 null, hostName, privateKey, publicKey, hostClientId), onError);
315 * @param {string} hostName
316 * @param {string} privateKey
317 * @param {string} publicKey
318 * @param {boolean} hasFeature
320 function onHasFeatureOAuthClient(
321 hostName, privateKey, publicKey, hasFeature) {
323 that.hostDaemonFacade_.getHostClientId(
324 onHostClientId.bind(null, hostName, privateKey, publicKey), onError);
326 remoting.identity.callWithToken(
328 null, hostName, privateKey, publicKey, null), onError);
333 * @param {string} hostName
334 * @param {string} privateKey
335 * @param {string} publicKey
337 function onKeyGenerated(hostName, privateKey, publicKey) {
339 remoting.HostController.Feature.OAUTH_CLIENT,
340 onHasFeatureOAuthClient.bind(null, hostName, privateKey, publicKey));
344 * @param {string} hostName
345 * @return {void} Nothing.
347 function startWithHostname(hostName) {
348 that.hostDaemonFacade_.generateKeyPair(onKeyGenerated.bind(null, hostName),
352 this.hostDaemonFacade_.getHostName(startWithHostname, onError);
356 * Stop the daemon process.
357 * @param {function():void} onDone Callback to be called when done.
358 * @param {function(remoting.Error):void} onError Callback to be called on
360 * @return {void} Nothing.
362 remoting.HostController.prototype.stop = function(onDone, onError) {
363 /** @type {remoting.HostController} */
366 /** @param {string?} hostId The host id of the local host. */
367 function unregisterHost(hostId) {
369 remoting.HostList.unregisterHostById(hostId);
374 /** @param {remoting.HostController.AsyncResult} result */
375 function onStopped(result) {
376 if (result == remoting.HostController.AsyncResult.OK) {
377 that.getLocalHostId(unregisterHost);
378 } else if (result == remoting.HostController.AsyncResult.CANCELLED) {
379 onError(remoting.Error.CANCELLED);
381 onError(remoting.Error.UNEXPECTED);
385 this.hostDaemonFacade_.stopDaemon(onStopped, onError);
389 * Check the host configuration is valid (non-null, and contains both host_id
390 * and xmpp_login keys).
391 * @param {Object} config The host configuration.
392 * @return {boolean} True if it is valid.
394 function isHostConfigValid_(config) {
395 return !!config && typeof config['host_id'] == 'string' &&
396 typeof config['xmpp_login'] == 'string';
400 * @param {string} newPin The new PIN to set
401 * @param {function():void} onDone Callback to be called when done.
402 * @param {function(remoting.Error):void} onError Callback to be called on
404 * @return {void} Nothing.
406 remoting.HostController.prototype.updatePin = function(newPin, onDone,
408 /** @type {remoting.HostController} */
411 /** @param {remoting.HostController.AsyncResult} result */
412 function onConfigUpdated(result) {
413 if (result == remoting.HostController.AsyncResult.OK) {
415 } else if (result == remoting.HostController.AsyncResult.CANCELLED) {
416 onError(remoting.Error.CANCELLED);
418 onError(remoting.Error.UNEXPECTED);
422 /** @param {string} pinHash */
423 function updateDaemonConfigWithHash(pinHash) {
425 host_secret_hash: pinHash
427 that.hostDaemonFacade_.updateDaemonConfig(newConfig, onConfigUpdated,
431 /** @param {Object} config */
432 function onConfig(config) {
433 if (!isHostConfigValid_(config)) {
434 onError(remoting.Error.UNEXPECTED);
437 /** @type {string} */
438 var hostId = config['host_id'];
439 that.hostDaemonFacade_.getPinHash(
440 hostId, newPin, updateDaemonConfigWithHash, onError);
443 // TODO(sergeyu): When crbug.com/121518 is fixed: replace this call
444 // with an unprivileged version if that is necessary.
445 this.hostDaemonFacade_.getDaemonConfig(onConfig, onError);
449 * Get the state of the local host.
451 * @param {function(remoting.HostController.State):void} onDone Completion
454 remoting.HostController.prototype.getLocalHostState = function(onDone) {
455 /** @param {remoting.Error} error */
456 function onError(error) {
457 onDone((error == remoting.Error.MISSING_PLUGIN) ?
458 remoting.HostController.State.NOT_INSTALLED :
459 remoting.HostController.State.UNKNOWN);
461 this.hostDaemonFacade_.getDaemonState(onDone, onError);
465 * Get the id of the local host, or null if it is not registered.
467 * @param {function(string?):void} onDone Completion callback.
469 remoting.HostController.prototype.getLocalHostId = function(onDone) {
470 /** @type {remoting.HostController} */
472 /** @param {Object} config */
473 function onConfig(config) {
475 if (isHostConfigValid_(config)) {
476 hostId = /** @type {string} */ config['host_id'];
481 this.hostDaemonFacade_.getDaemonConfig(onConfig, function(error) {
487 * Fetch the list of paired clients for this host.
489 * @param {function(Array.<remoting.PairedClient>):void} onDone
490 * @param {function(remoting.Error):void} onError
493 remoting.HostController.prototype.getPairedClients = function(onDone,
495 this.hostDaemonFacade_.getPairedClients(onDone, onError);
499 * Delete a single paired client.
501 * @param {string} client The client id of the pairing to delete.
502 * @param {function():void} onDone Completion callback.
503 * @param {function(remoting.Error):void} onError Error callback.
506 remoting.HostController.prototype.deletePairedClient = function(
507 client, onDone, onError) {
508 this.hostDaemonFacade_.deletePairedClient(client, onDone, onError);
512 * Delete all paired clients.
514 * @param {function():void} onDone Completion callback.
515 * @param {function(remoting.Error):void} onError Error callback.
518 remoting.HostController.prototype.clearPairedClients = function(
520 this.hostDaemonFacade_.clearPairedClients(onDone, onError);
524 * Gets the host owner's base JID, used by the host for client authorization.
525 * In most cases this is the same as the owner's email address, but for
526 * non-Gmail accounts, it may be different.
529 * @param {function(string): void} onSuccess
530 * @param {function(remoting.Error): void} onError
532 remoting.HostController.prototype.getClientBaseJid_ = function(
533 onSuccess, onError) {
534 var signalStrategy = null;
536 var onState = function(state) {
538 case remoting.SignalStrategy.State.CONNECTED:
539 var jid = signalStrategy.getJid().split('/')[0].toLowerCase();
540 base.dispose(signalStrategy);
541 signalStrategy = null;
545 case remoting.SignalStrategy.State.FAILED:
546 var error = signalStrategy.getError();
547 base.dispose(signalStrategy);
548 signalStrategy = null;
554 signalStrategy = remoting.SignalStrategy.create(onState);
556 /** @param {string} token */
557 function connectSignalingWithToken(token) {
558 remoting.identity.getEmail(
559 connectSignalingWithTokenAndEmail.bind(null, token), onError);
563 * @param {string} token
564 * @param {string} email
566 function connectSignalingWithTokenAndEmail(token, email) {
567 signalStrategy.connect(
568 remoting.settings.XMPP_SERVER_ADDRESS, email, token);
571 remoting.identity.callWithToken(connectSignalingWithToken, onError);
574 /** @type {remoting.HostController} */
575 remoting.hostController = null;