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 * Functions related to the 'host screen' for Chromoting.
10 /** @suppress {duplicate} */
11 var remoting
= remoting
|| {};
17 /** @type {remoting.HostSession} */
18 var hostSession_
= null;
21 * @type {boolean} Whether or not the last share was cancelled by the user.
22 * This controls what screen is shown when the host signals completion.
24 var lastShareWasCancelled_
= false;
27 * @type {remoting.LogToServer} Logging instance for IT2Me host connection
30 var it2meLogger
= null;
33 * Start a host session. This is the main entry point for the host screen,
34 * called directly from the onclick action of a button on the home screen.
35 * It first verifies that the native host components are installed and asks
36 * to install them if necessary.
38 remoting
.tryShare = function() {
39 ensureIT2MeLogger_().then(tryShareWithLogger_
);
42 function tryShareWithLogger_() {
43 it2meLogger
.setSessionId();
44 it2meLogger
.logClientSessionStateChange(
45 remoting
.ClientSession
.State
.INITIALIZING
, remoting
.Error
.none(), null);
47 /** @type {remoting.It2MeHostFacade} */
48 var hostFacade
= new remoting
.It2MeHostFacade();
50 /** @type {remoting.HostInstallDialog} */
51 var hostInstallDialog
= null;
53 var tryInitializeFacade = function() {
54 hostFacade
.initialize(onFacadeInitialized
, onFacadeInitializationFailed
);
57 var onFacadeInitialized = function () {
58 // Host already installed.
59 remoting
.startHostUsingFacade_(hostFacade
);
62 var onFacadeInitializationFailed = function() {
63 // If we failed to initialize the dispatcher then prompt the user to install
65 var hasHostDialog
= (hostInstallDialog
!== null); /** jscompile hack */
67 hostInstallDialog
= new remoting
.HostInstallDialog();
68 hostInstallDialog
.show(tryInitializeFacade
, showShareError_
);
70 hostInstallDialog
.tryAgain();
74 tryInitializeFacade();
78 * @param {remoting.It2MeHostFacade} hostFacade An initialized It2MeHostFacade.
80 remoting
.startHostUsingFacade_ = function(hostFacade
) {
81 console
.log('Attempting to share...');
83 .then(remoting
.identity
.getToken
.bind(remoting
.identity
))
84 .then(remoting
.tryShareWithToken_
.bind(null, hostFacade
),
85 remoting
.Error
.handler(showShareError_
));
89 * @param {remoting.It2MeHostFacade} hostFacade An initialized
91 * @param {string} token The OAuth access token.
94 remoting
.tryShareWithToken_ = function(hostFacade
, token
) {
95 lastShareWasCancelled_
= false;
96 onNatTraversalPolicyChanged_(true); // Hide warning by default.
97 remoting
.setMode(remoting
.AppMode
.HOST_WAITING_FOR_CODE
);
98 it2meLogger
.logClientSessionStateChange(
99 remoting
.ClientSession
.State
.CONNECTING
, remoting
.Error
.none(), null);
100 document
.getElementById('cancel-share-button').disabled
= false;
101 disableTimeoutCountdown_();
103 console
.assert(hostSession_
=== null, '|hostSession_| already exists.');
104 hostSession_
= new remoting
.HostSession();
105 remoting
.identity
.getEmail().then(
106 function(/** string */ email
) {
107 hostSession_
.connect(
108 hostFacade
, email
, token
, onHostStateChanged_
,
109 onNatTraversalPolicyChanged_
, logDebugInfo_
, it2meConnectFailed_
);
114 * Callback for the host plugin to notify the web app of state changes.
115 * @param {remoting.HostSession.State} state The new state of the plugin.
116 * @return {void} Nothing.
118 function onHostStateChanged_(state
) {
119 if (state
== remoting
.HostSession
.State
.STARTING
) {
120 // Nothing to do here.
121 console
.log('Host state: STARTING');
123 } else if (state
== remoting
.HostSession
.State
.REQUESTED_ACCESS_CODE
) {
124 // Nothing to do here.
125 console
.log('Host state: REQUESTED_ACCESS_CODE');
127 } else if (state
== remoting
.HostSession
.State
.RECEIVED_ACCESS_CODE
) {
128 console
.log('Host state: RECEIVED_ACCESS_CODE');
129 var accessCode
= hostSession_
.getAccessCode();
130 var accessCodeDisplay
= document
.getElementById('access-code-display');
131 accessCodeDisplay
.innerText
= '';
132 // Display the access code in groups of four digits for readability.
133 var kDigitsPerGroup
= 4;
134 for (var i
= 0; i
< accessCode
.length
; i
+= kDigitsPerGroup
) {
135 var nextFourDigits
= document
.createElement('span');
136 nextFourDigits
.className
= 'access-code-digit-group';
137 nextFourDigits
.innerText
= accessCode
.substring(i
, i
+ kDigitsPerGroup
);
138 accessCodeDisplay
.appendChild(nextFourDigits
);
140 accessCodeExpiresIn_
= hostSession_
.getAccessCodeLifetime();
141 if (accessCodeExpiresIn_
> 0) { // Check it hasn't expired.
142 accessCodeTimerId_
= setInterval(decrementAccessCodeTimeout_
, 1000);
143 timerRunning_
= true;
144 updateAccessCodeTimeoutElement_();
145 updateTimeoutStyles_();
146 remoting
.setMode(remoting
.AppMode
.HOST_WAITING_FOR_CONNECTION
);
148 // This can only happen if the cloud tells us that the code lifetime is
149 // <= 0s, which shouldn't happen so we don't care how clean this UX is.
150 console
.error('Access code already invalid on receipt!');
151 remoting
.cancelShare();
154 } else if (state
== remoting
.HostSession
.State
.CONNECTED
) {
155 console
.log('Host state: CONNECTED');
156 var element
= document
.getElementById('host-shared-message');
157 var client
= hostSession_
.getClient();
158 l10n
.localizeElement(element
, client
);
159 remoting
.setMode(remoting
.AppMode
.HOST_SHARED
);
160 disableTimeoutCountdown_();
162 } else if (state
== remoting
.HostSession
.State
.DISCONNECTING
) {
163 console
.log('Host state: DISCONNECTING');
165 } else if (state
== remoting
.HostSession
.State
.DISCONNECTED
) {
166 console
.log('Host state: DISCONNECTED');
167 if (remoting
.currentMode
!= remoting
.AppMode
.HOST_SHARE_FAILED
) {
168 // If an error is being displayed, then the plugin should not be able to
169 // hide it by setting the state. Errors must be dismissed by the user
170 // clicking OK, which puts the app into mode HOME.
171 if (lastShareWasCancelled_
) {
172 remoting
.setMode(remoting
.AppMode
.HOME
);
174 remoting
.setMode(remoting
.AppMode
.HOST_SHARE_FINISHED
);
178 } else if (state
== remoting
.HostSession
.State
.ERROR
) {
179 // The processing of this message is identical to that of the "error"
180 // message (see it2me_host_facade.js); it is included only to support
181 // old native components that send errors as a host state message.
182 // TODO(jamiewalch): Remove this once there are sufficiently few old
183 // installations deployed.
184 console
.error('Host state: ERROR');
185 showShareError_(remoting
.Error
.unexpected());
186 } else if (state
== remoting
.HostSession
.State
.INVALID_DOMAIN_ERROR
) {
187 console
.error('Host state: INVALID_DOMAIN_ERROR');
188 showShareError_(new remoting
.Error(remoting
.Error
.Tag
.INVALID_HOST_DOMAIN
));
190 console
.error('Unknown state -> ' + state
);
195 * This is the callback that the host plugin invokes to indicate that there
196 * is additional debug log info to display.
197 * @param {string} msg The message (which will not be localized) to be logged.
199 function logDebugInfo_(msg
) {
200 console
.log('plugin: ' + msg
);
204 * Show a host-side error message.
206 * @param {!remoting.Error} error The error to be localized and displayed.
207 * @return {void} Nothing.
209 function showShareError_(error
) {
210 if (error
.hasTag(remoting
.Error
.Tag
.CANCELLED
)) {
211 remoting
.setMode(remoting
.AppMode
.HOME
);
212 it2meLogger
.logClientSessionStateChange(
213 remoting
.ClientSession
.State
.CONNECTION_CANCELED
,
214 remoting
.Error
.none(),
217 var errorDiv
= document
.getElementById('host-plugin-error');
218 l10n
.localizeElementFromTag(errorDiv
, error
.getTag());
219 console
.error('Sharing error: ' + error
.toString());
220 remoting
.setMode(remoting
.AppMode
.HOST_SHARE_FAILED
);
221 it2meLogger
.logClientSessionStateChange(
222 remoting
.ClientSession
.State
.FAILED
, error
, null);
229 * Show a sharing error with error code UNEXPECTED .
231 * @return {void} Nothing.
233 function it2meConnectFailed_() {
234 showShareError_(remoting
.Error
.unexpected());
238 base
.dispose(hostSession_
);
243 * Cancel an active or pending it2me share operation.
245 * @return {void} Nothing.
247 remoting
.cancelShare = function() {
248 document
.getElementById('cancel-share-button').disabled
= true;
249 console
.log('Canceling share...');
250 remoting
.lastShareWasCancelled
= true;
252 hostSession_
.disconnect();
253 it2meLogger
.logClientSessionStateChange(
254 remoting
.ClientSession
.State
.CONNECTION_CANCELED
,
255 remoting
.Error
.none(),
257 } catch (/** @type {*} */ error
) {
258 console
.error('Error disconnecting: ' + error
+
259 '. The host probably crashed.');
260 // TODO(jamiewalch): Clean this up. We should have a class representing
261 // the host plugin, like we do for the client, which should handle crash
262 // reporting and it should use a more detailed error message than the
263 // default 'generic' one. See crbug.com/94624
264 showShareError_(remoting
.Error
.unexpected());
266 disableTimeoutCountdown_();
270 * @type {boolean} Whether or not the access code timeout countdown is running.
272 var timerRunning_
= false;
275 * @type {number} The id of the access code expiry countdown timer.
277 var accessCodeTimerId_
= 0;
280 * @type {number} The number of seconds until the access code expires.
282 var accessCodeExpiresIn_
= 0;
285 * The timer callback function
286 * @return {void} Nothing.
288 function decrementAccessCodeTimeout_() {
289 --accessCodeExpiresIn_
;
290 updateAccessCodeTimeoutElement_();
294 * Stop the access code timeout countdown if it is running.
295 * @return {void} Nothing.
297 function disableTimeoutCountdown_() {
299 clearInterval(accessCodeTimerId_
);
300 timerRunning_
= false;
301 updateTimeoutStyles_();
306 * Constants controlling the access code timer countdown display.
308 var ACCESS_CODE_TIMER_DISPLAY_THRESHOLD_
= 30;
309 var ACCESS_CODE_RED_THRESHOLD_
= 10;
312 * Show/hide or restyle various elements, depending on the remaining countdown
315 * @return {boolean} True if the timeout is in progress, false if it has
318 function updateTimeoutStyles_() {
320 if (accessCodeExpiresIn_
<= 0) {
321 remoting
.cancelShare();
324 var accessCode
= document
.getElementById('access-code-display');
325 if (accessCodeExpiresIn_
<= ACCESS_CODE_RED_THRESHOLD_
) {
326 accessCode
.classList
.add('expiring');
328 accessCode
.classList
.remove('expiring');
331 document
.getElementById('access-code-countdown').hidden
=
332 (accessCodeExpiresIn_
> ACCESS_CODE_TIMER_DISPLAY_THRESHOLD_
) ||
338 * Update the text and appearance of the access code timeout element to
339 * reflect the time remaining.
340 * @return {void} Nothing.
342 function updateAccessCodeTimeoutElement_() {
343 var pad
= (accessCodeExpiresIn_
< 10) ? '0:0' : '0:';
344 l10n
.localizeElement(document
.getElementById('seconds-remaining'),
345 pad
+ accessCodeExpiresIn_
);
346 if (!updateTimeoutStyles_()) {
347 disableTimeoutCountdown_();
352 * Callback to show or hide the NAT traversal warning when the policy changes.
353 * @param {boolean} enabled True if NAT traversal is enabled.
354 * @return {void} Nothing.
356 function onNatTraversalPolicyChanged_(enabled
) {
357 var natBox
= document
.getElementById('nat-box');
359 natBox
.classList
.add('traversal-enabled');
361 natBox
.classList
.remove('traversal-enabled');
366 * Create an IT2Me LogToServer instance if one does not already exist.
368 * @return {Promise} Promise that resolves when the host version (if available),
369 * has been set on the logger instance.
371 function ensureIT2MeLogger_() {
373 return Promise
.resolve();
376 var xmppConnection
= new remoting
.XmppConnection();
377 var tokenPromise
= remoting
.identity
.getToken();
378 var emailPromise
= remoting
.identity
.getEmail();
379 tokenPromise
.then(function(/** string */ token
) {
380 emailPromise
.then(function(/** string */ email
) {
381 xmppConnection
.connect(remoting
.settings
.XMPP_SERVER
, email
, token
);
385 var bufferedSignalStrategy
=
386 new remoting
.BufferedSignalStrategy(xmppConnection
);
387 it2meLogger
= new remoting
.LogToServer(bufferedSignalStrategy
, true);
388 it2meLogger
.setLogEntryMode(remoting
.ChromotingEvent
.Mode
.IT2ME
);
390 return setHostVersion_();
394 * @return {Promise} Promise that resolves when the host version (if available),
395 * has been set on the logger instance.
397 function setHostVersion_() {
398 return remoting
.hostController
.getLocalHostVersion().then(
399 function(/** string */ version
) {
400 it2meLogger
.setHostVersion(version
);