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
= base
.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(),
257 remoting
.identity
.getCachedEmail()),
261 console
.log('Failed to register the host. Status: ' + xhr
.status
+
262 ' response: ' + xhr
.responseText
);
263 onError(remoting
.Error
.REGISTRATION_FAILED
);
268 * @param {string} hostName
269 * @param {string} privateKey
270 * @param {string} publicKey
271 * @param {string} hostClientId
272 * @param {string} oauthToken
274 function doRegisterHost(
275 hostName
, privateKey
, publicKey
, hostClientId
, oauthToken
) {
277 'Authorization': 'OAuth ' + oauthToken
,
278 'Content-type' : 'application/json; charset=UTF-8'
281 var newHostDetails
= { data
: {
287 var registerHostUrl
=
288 remoting
.settings
.DIRECTORY_API_BASE_URL
+ '/@me/hosts';
291 registerHostUrl
+= '?' + remoting
.xhr
.urlencodeParamHash(
292 { hostClientId
: hostClientId
});
297 onRegistered
.bind(null, hostName
, publicKey
, privateKey
),
298 JSON
.stringify(newHostDetails
),
303 * @param {string} hostName
304 * @param {string} privateKey
305 * @param {string} publicKey
306 * @param {string} hostClientId
308 function onHostClientId(
309 hostName
, privateKey
, publicKey
, hostClientId
) {
310 remoting
.identity
.callWithToken(
312 null, hostName
, privateKey
, publicKey
, hostClientId
), onError
);
316 * @param {string} hostName
317 * @param {string} privateKey
318 * @param {string} publicKey
319 * @param {boolean} hasFeature
321 function onHasFeatureOAuthClient(
322 hostName
, privateKey
, publicKey
, hasFeature
) {
324 that
.hostDaemonFacade_
.getHostClientId(
325 onHostClientId
.bind(null, hostName
, privateKey
, publicKey
), onError
);
327 remoting
.identity
.callWithToken(
329 null, hostName
, privateKey
, publicKey
, null), onError
);
334 * @param {string} hostName
335 * @param {string} privateKey
336 * @param {string} publicKey
338 function onKeyGenerated(hostName
, privateKey
, publicKey
) {
340 remoting
.HostController
.Feature
.OAUTH_CLIENT
,
341 onHasFeatureOAuthClient
.bind(null, hostName
, privateKey
, publicKey
));
345 * @param {string} hostName
346 * @return {void} Nothing.
348 function startWithHostname(hostName
) {
349 that
.hostDaemonFacade_
.generateKeyPair(onKeyGenerated
.bind(null, hostName
),
353 this.hostDaemonFacade_
.getHostName(startWithHostname
, onError
);
357 * Stop the daemon process.
358 * @param {function():void} onDone Callback to be called when done.
359 * @param {function(remoting.Error):void} onError Callback to be called on
361 * @return {void} Nothing.
363 remoting
.HostController
.prototype.stop = function(onDone
, onError
) {
364 /** @type {remoting.HostController} */
367 /** @param {string?} hostId The host id of the local host. */
368 function unregisterHost(hostId
) {
370 remoting
.HostList
.unregisterHostById(hostId
);
375 /** @param {remoting.HostController.AsyncResult} result */
376 function onStopped(result
) {
377 if (result
== remoting
.HostController
.AsyncResult
.OK
) {
378 that
.getLocalHostId(unregisterHost
);
379 } else if (result
== remoting
.HostController
.AsyncResult
.CANCELLED
) {
380 onError(remoting
.Error
.CANCELLED
);
382 onError(remoting
.Error
.UNEXPECTED
);
386 this.hostDaemonFacade_
.stopDaemon(onStopped
, onError
);
390 * Check the host configuration is valid (non-null, and contains both host_id
391 * and xmpp_login keys).
392 * @param {Object} config The host configuration.
393 * @return {boolean} True if it is valid.
395 function isHostConfigValid_(config
) {
396 return !!config
&& typeof config
['host_id'] == 'string' &&
397 typeof config
['xmpp_login'] == 'string';
401 * @param {string} newPin The new PIN to set
402 * @param {function():void} onDone Callback to be called when done.
403 * @param {function(remoting.Error):void} onError Callback to be called on
405 * @return {void} Nothing.
407 remoting
.HostController
.prototype.updatePin = function(newPin
, onDone
,
409 /** @type {remoting.HostController} */
412 /** @param {remoting.HostController.AsyncResult} result */
413 function onConfigUpdated(result
) {
414 if (result
== remoting
.HostController
.AsyncResult
.OK
) {
416 } else if (result
== remoting
.HostController
.AsyncResult
.CANCELLED
) {
417 onError(remoting
.Error
.CANCELLED
);
419 onError(remoting
.Error
.UNEXPECTED
);
423 /** @param {string} pinHash */
424 function updateDaemonConfigWithHash(pinHash
) {
426 host_secret_hash
: pinHash
428 that
.hostDaemonFacade_
.updateDaemonConfig(newConfig
, onConfigUpdated
,
432 /** @param {Object} config */
433 function onConfig(config
) {
434 if (!isHostConfigValid_(config
)) {
435 onError(remoting
.Error
.UNEXPECTED
);
438 /** @type {string} */
439 var hostId
= config
['host_id'];
440 that
.hostDaemonFacade_
.getPinHash(
441 hostId
, newPin
, updateDaemonConfigWithHash
, onError
);
444 // TODO(sergeyu): When crbug.com/121518 is fixed: replace this call
445 // with an unprivileged version if that is necessary.
446 this.hostDaemonFacade_
.getDaemonConfig(onConfig
, onError
);
450 * Get the state of the local host.
452 * @param {function(remoting.HostController.State):void} onDone Completion
455 remoting
.HostController
.prototype.getLocalHostState = function(onDone
) {
456 /** @param {remoting.Error} error */
457 function onError(error
) {
458 onDone((error
== remoting
.Error
.MISSING_PLUGIN
) ?
459 remoting
.HostController
.State
.NOT_INSTALLED
:
460 remoting
.HostController
.State
.UNKNOWN
);
462 this.hostDaemonFacade_
.getDaemonState(onDone
, onError
);
466 * Get the id of the local host, or null if it is not registered.
468 * @param {function(string?):void} onDone Completion callback.
470 remoting
.HostController
.prototype.getLocalHostId = function(onDone
) {
471 /** @type {remoting.HostController} */
473 /** @param {Object} config */
474 function onConfig(config
) {
476 if (isHostConfigValid_(config
)) {
477 hostId
= /** @type {string} */ config
['host_id'];
482 this.hostDaemonFacade_
.getDaemonConfig(onConfig
, function(error
) {
488 * Fetch the list of paired clients for this host.
490 * @param {function(Array.<remoting.PairedClient>):void} onDone
491 * @param {function(remoting.Error):void} onError
494 remoting
.HostController
.prototype.getPairedClients = function(onDone
,
496 this.hostDaemonFacade_
.getPairedClients(onDone
, onError
);
500 * Delete a single paired client.
502 * @param {string} client The client id of the pairing to delete.
503 * @param {function():void} onDone Completion callback.
504 * @param {function(remoting.Error):void} onError Error callback.
507 remoting
.HostController
.prototype.deletePairedClient = function(
508 client
, onDone
, onError
) {
509 this.hostDaemonFacade_
.deletePairedClient(client
, onDone
, onError
);
513 * Delete all paired clients.
515 * @param {function():void} onDone Completion callback.
516 * @param {function(remoting.Error):void} onError Error callback.
519 remoting
.HostController
.prototype.clearPairedClients = function(
521 this.hostDaemonFacade_
.clearPairedClients(onDone
, onError
);
525 * Gets the host owner's base JID, used by the host for client authorization.
526 * In most cases this is the same as the owner's email address, but for
527 * non-Gmail accounts, it may be different.
530 * @param {function(string): void} onSuccess
531 * @param {function(remoting.Error): void} onError
533 remoting
.HostController
.prototype.getClientBaseJid_ = function(
534 onSuccess
, onError
) {
535 /** @type {remoting.SignalStrategy} */
536 var signalStrategy
= null;
538 /** @param {remoting.SignalStrategy.State} state */
539 var onState = function(state
) {
541 case remoting
.SignalStrategy
.State
.CONNECTED
:
542 var jid
= signalStrategy
.getJid().split('/')[0].toLowerCase();
543 base
.dispose(signalStrategy
);
544 signalStrategy
= null;
548 case remoting
.SignalStrategy
.State
.FAILED
:
549 var error
= signalStrategy
.getError();
550 base
.dispose(signalStrategy
);
551 signalStrategy
= null;
557 signalStrategy
= remoting
.SignalStrategy
.create(onState
);
559 /** @param {string} token */
560 function connectSignalingWithToken(token
) {
561 remoting
.identity
.getEmail(
562 connectSignalingWithTokenAndEmail
.bind(null, token
), onError
);
566 * @param {string} token
567 * @param {string} email
569 function connectSignalingWithTokenAndEmail(token
, email
) {
570 signalStrategy
.connect(
571 remoting
.settings
.XMPP_SERVER_ADDRESS
, email
, token
);
574 remoting
.identity
.callWithToken(connectSignalingWithToken
, onError
);
577 /** @type {remoting.HostController} */
578 remoting
.hostController
= null;