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.
12 /** @suppress {duplicate} */
13 var remoting
= remoting
|| {};
16 * @type {boolean} Whether or not the last share was cancelled by the user.
17 * This controls what screen is shown when the host signals completion.
20 var lastShareWasCancelled_
= false;
23 * Start a host session. This is the main entry point for the host screen,
24 * called directly from the onclick action of a button on the home screen.
25 * It first verifies that the native host components are installed and asks
26 * to install them if necessary.
28 remoting
.tryShare = function() {
29 /** @type {remoting.It2MeHostFacade} */
30 var hostFacade
= new remoting
.It2MeHostFacade();
32 /** @type {remoting.HostInstallDialog} */
33 var hostInstallDialog
= null;
35 var tryInitializeFacade = function() {
36 hostFacade
.initialize(onFacadeInitialized
, onFacadeInitializationFailed
);
39 var onFacadeInitialized = function () {
40 // Host already installed.
41 remoting
.startHostUsingFacade_(hostFacade
);
44 var onFacadeInitializationFailed = function() {
45 // If we failed to initialize the dispatcher then prompt the user to install
47 var hasHostDialog
= (hostInstallDialog
!= null); /** jscompile hack */
49 hostInstallDialog
= new remoting
.HostInstallDialog();
50 hostInstallDialog
.show(tryInitializeFacade
, onInstallError
);
52 hostInstallDialog
.tryAgain();
56 /** @param {remoting.Error} error */
57 var onInstallError = function(error
) {
58 if (error
== remoting
.Error
.CANCELLED
) {
59 remoting
.setMode(remoting
.AppMode
.HOME
);
61 showShareError_(error
);
65 tryInitializeFacade();
69 * @param {remoting.It2MeHostFacade} hostFacade An initialized It2MeHostFacade.
71 remoting
.startHostUsingFacade_ = function(hostFacade
) {
72 console
.log('Attempting to share...');
73 remoting
.identity
.callWithToken(
74 remoting
.tryShareWithToken_
.bind(null, hostFacade
),
75 remoting
.showErrorMessage
);
79 * @param {remoting.It2MeHostFacade} hostFacade An initialized
81 * @param {string} token The OAuth access token.
84 remoting
.tryShareWithToken_ = function(hostFacade
, token
) {
85 lastShareWasCancelled_
= false;
86 onNatTraversalPolicyChanged_(true); // Hide warning by default.
87 remoting
.setMode(remoting
.AppMode
.HOST_WAITING_FOR_CODE
);
88 document
.getElementById('cancel-share-button').disabled
= false;
89 disableTimeoutCountdown_();
91 remoting
.hostSession
= new remoting
.HostSession();
92 var email
= /** @type {string} */remoting
.identity
.getCachedEmail();
93 remoting
.hostSession
.connect(
94 hostFacade
, email
, token
, onHostStateChanged_
,
95 onNatTraversalPolicyChanged_
, logDebugInfo_
, it2meConnectFailed_
);
99 * Callback for the host plugin to notify the web app of state changes.
100 * @param {remoting.HostSession.State} state The new state of the plugin.
101 * @return {void} Nothing.
104 function onHostStateChanged_(state
) {
105 if (state
== remoting
.HostSession
.State
.STARTING
) {
106 // Nothing to do here.
107 console
.log('Host state: STARTING');
109 } else if (state
== remoting
.HostSession
.State
.REQUESTED_ACCESS_CODE
) {
110 // Nothing to do here.
111 console
.log('Host state: REQUESTED_ACCESS_CODE');
113 } else if (state
== remoting
.HostSession
.State
.RECEIVED_ACCESS_CODE
) {
114 console
.log('Host state: RECEIVED_ACCESS_CODE');
115 var accessCode
= remoting
.hostSession
.getAccessCode();
116 var accessCodeDisplay
= document
.getElementById('access-code-display');
117 accessCodeDisplay
.innerText
= '';
118 // Display the access code in groups of four digits for readability.
119 var kDigitsPerGroup
= 4;
120 for (var i
= 0; i
< accessCode
.length
; i
+= kDigitsPerGroup
) {
121 var nextFourDigits
= document
.createElement('span');
122 nextFourDigits
.className
= 'access-code-digit-group';
123 nextFourDigits
.innerText
= accessCode
.substring(i
, i
+ kDigitsPerGroup
);
124 accessCodeDisplay
.appendChild(nextFourDigits
);
126 accessCodeExpiresIn_
= remoting
.hostSession
.getAccessCodeLifetime();
127 if (accessCodeExpiresIn_
> 0) { // Check it hasn't expired.
128 accessCodeTimerId_
= setInterval(decrementAccessCodeTimeout_
, 1000);
129 timerRunning_
= true;
130 updateAccessCodeTimeoutElement_();
131 updateTimeoutStyles_();
132 remoting
.setMode(remoting
.AppMode
.HOST_WAITING_FOR_CONNECTION
);
134 // This can only happen if the cloud tells us that the code lifetime is
135 // <= 0s, which shouldn't happen so we don't care how clean this UX is.
136 console
.error('Access code already invalid on receipt!');
137 remoting
.cancelShare();
140 } else if (state
== remoting
.HostSession
.State
.CONNECTED
) {
141 console
.log('Host state: CONNECTED');
142 var element
= document
.getElementById('host-shared-message');
143 var client
= remoting
.hostSession
.getClient();
144 l10n
.localizeElement(element
, client
);
145 remoting
.setMode(remoting
.AppMode
.HOST_SHARED
);
146 disableTimeoutCountdown_();
148 } else if (state
== remoting
.HostSession
.State
.DISCONNECTING
) {
149 console
.log('Host state: DISCONNECTING');
151 } else if (state
== remoting
.HostSession
.State
.DISCONNECTED
) {
152 console
.log('Host state: DISCONNECTED');
153 if (remoting
.currentMode
!= remoting
.AppMode
.HOST_SHARE_FAILED
) {
154 // If an error is being displayed, then the plugin should not be able to
155 // hide it by setting the state. Errors must be dismissed by the user
156 // clicking OK, which puts the app into mode HOME.
157 if (lastShareWasCancelled_
) {
158 remoting
.setMode(remoting
.AppMode
.HOME
);
160 remoting
.setMode(remoting
.AppMode
.HOST_SHARE_FINISHED
);
163 } else if (state
== remoting
.HostSession
.State
.ERROR
) {
164 console
.error('Host state: ERROR');
165 showShareError_(remoting
.Error
.UNEXPECTED
);
166 } else if (state
== remoting
.HostSession
.State
.INVALID_DOMAIN_ERROR
) {
167 console
.error('Host state: INVALID_DOMAIN_ERROR');
168 showShareError_(remoting
.Error
.INVALID_HOST_DOMAIN
);
170 console
.error('Unknown state -> ' + state
);
175 * This is the callback that the host plugin invokes to indicate that there
176 * is additional debug log info to display.
177 * @param {string} msg The message (which will not be localized) to be logged.
180 function logDebugInfo_(msg
) {
181 console
.log('plugin: ' + msg
);
185 * Show a host-side error message.
187 * @param {string} errorTag The error message to be localized and displayed.
188 * @return {void} Nothing.
191 function showShareError_(errorTag
) {
192 var errorDiv
= document
.getElementById('host-plugin-error');
193 l10n
.localizeElementFromTag(errorDiv
, errorTag
);
194 console
.error('Sharing error: ' + errorTag
);
195 remoting
.setMode(remoting
.AppMode
.HOST_SHARE_FAILED
);
199 * Show a sharing error with error code UNEXPECTED .
201 * @return {void} Nothing.
204 function it2meConnectFailed_() {
205 // TODO (weitaosu): Instruct the user to install the native messaging host.
206 // We probably want to add a new error code (with the corresponding error
207 // message for sharing error.
208 console
.error('Cannot share desktop.');
209 showShareError_(remoting
.Error
.UNEXPECTED
);
213 * Cancel an active or pending it2me share operation.
215 * @return {void} Nothing.
217 remoting
.cancelShare = function() {
218 document
.getElementById('cancel-share-button').disabled
= true;
219 console
.log('Canceling share...');
220 remoting
.lastShareWasCancelled
= true;
222 remoting
.hostSession
.disconnect();
224 // Hack to force JSCompiler type-safety.
225 var errorTyped
= /** @type {{description: string}} */ error
;
226 console
.error('Error disconnecting: ' + errorTyped
.description
+
227 '. The host probably crashed.');
228 // TODO(jamiewalch): Clean this up. We should have a class representing
229 // the host plugin, like we do for the client, which should handle crash
230 // reporting and it should use a more detailed error message than the
231 // default 'generic' one. See crbug.com/94624
232 showShareError_(remoting
.Error
.UNEXPECTED
);
234 disableTimeoutCountdown_();
238 * @type {boolean} Whether or not the access code timeout countdown is running.
241 var timerRunning_
= false;
244 * @type {number} The id of the access code expiry countdown timer.
247 var accessCodeTimerId_
= 0;
250 * @type {number} The number of seconds until the access code expires.
253 var accessCodeExpiresIn_
= 0;
256 * The timer callback function
257 * @return {void} Nothing.
260 function decrementAccessCodeTimeout_() {
261 --accessCodeExpiresIn_
;
262 updateAccessCodeTimeoutElement_();
266 * Stop the access code timeout countdown if it is running.
267 * @return {void} Nothing.
270 function disableTimeoutCountdown_() {
272 clearInterval(accessCodeTimerId_
);
273 timerRunning_
= false;
274 updateTimeoutStyles_();
279 * Constants controlling the access code timer countdown display.
282 var ACCESS_CODE_TIMER_DISPLAY_THRESHOLD_
= 30;
283 var ACCESS_CODE_RED_THRESHOLD_
= 10;
286 * Show/hide or restyle various elements, depending on the remaining countdown
289 * @return {boolean} True if the timeout is in progress, false if it has
293 function updateTimeoutStyles_() {
295 if (accessCodeExpiresIn_
<= 0) {
296 remoting
.cancelShare();
299 var accessCode
= document
.getElementById('access-code-display');
300 if (accessCodeExpiresIn_
<= ACCESS_CODE_RED_THRESHOLD_
) {
301 accessCode
.classList
.add('expiring');
303 accessCode
.classList
.remove('expiring');
306 document
.getElementById('access-code-countdown').hidden
=
307 (accessCodeExpiresIn_
> ACCESS_CODE_TIMER_DISPLAY_THRESHOLD_
) ||
313 * Update the text and appearance of the access code timeout element to
314 * reflect the time remaining.
315 * @return {void} Nothing.
318 function updateAccessCodeTimeoutElement_() {
319 var pad
= (accessCodeExpiresIn_
< 10) ? '0:0' : '0:';
320 l10n
.localizeElement(document
.getElementById('seconds-remaining'),
321 pad
+ accessCodeExpiresIn_
);
322 if (!updateTimeoutStyles_()) {
323 disableTimeoutCountdown_();
328 * Callback to show or hide the NAT traversal warning when the policy changes.
329 * @param {boolean} enabled True if NAT traversal is enabled.
330 * @return {void} Nothing.
333 function onNatTraversalPolicyChanged_(enabled
) {
334 var natBox
= document
.getElementById('nat-box');
336 natBox
.classList
.add('traversal-enabled');
338 natBox
.classList
.remove('traversal-enabled');