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 * Start a host session. This is the main entry point for the host screen,
28 * called directly from the onclick action of a button on the home screen.
29 * It first verifies that the native host components are installed and asks
30 * to install them if necessary.
32 remoting.tryShare = function() {
33 /** @type {remoting.It2MeHostFacade} */
34 var hostFacade = new remoting.It2MeHostFacade();
36 /** @type {remoting.HostInstallDialog} */
37 var hostInstallDialog = null;
39 var tryInitializeFacade = function() {
40 hostFacade.initialize(onFacadeInitialized, onFacadeInitializationFailed);
43 var onFacadeInitialized = function () {
44 // Host already installed.
45 remoting.startHostUsingFacade_(hostFacade);
48 var onFacadeInitializationFailed = function() {
49 // If we failed to initialize the dispatcher then prompt the user to install
51 var hasHostDialog = (hostInstallDialog !== null); /** jscompile hack */
53 hostInstallDialog = new remoting.HostInstallDialog();
54 hostInstallDialog.show(tryInitializeFacade, onInstallError);
56 hostInstallDialog.tryAgain();
60 /** @param {remoting.Error} error */
61 var onInstallError = function(error) {
62 if (error == remoting.Error.CANCELLED) {
63 remoting.setMode(remoting.AppMode.HOME);
65 showShareError_(error);
69 tryInitializeFacade();
73 * @param {remoting.It2MeHostFacade} hostFacade An initialized It2MeHostFacade.
75 remoting.startHostUsingFacade_ = function(hostFacade) {
76 console.log('Attempting to share...');
77 remoting.identity.getToken().then(
78 remoting.tryShareWithToken_.bind(null, hostFacade),
79 remoting.Error.handler(remoting.showErrorMessage));
83 * @param {remoting.It2MeHostFacade} hostFacade An initialized
85 * @param {string} token The OAuth access token.
88 remoting.tryShareWithToken_ = function(hostFacade, token) {
89 lastShareWasCancelled_ = false;
90 onNatTraversalPolicyChanged_(true); // Hide warning by default.
91 remoting.setMode(remoting.AppMode.HOST_WAITING_FOR_CODE);
92 document.getElementById('cancel-share-button').disabled = false;
93 disableTimeoutCountdown_();
95 base.debug.assert(hostSession_ === null);
96 hostSession_ = new remoting.HostSession();
97 var email = /** @type {string} */ (remoting.identity.getCachedEmail());
99 hostFacade, email, token, onHostStateChanged_,
100 onNatTraversalPolicyChanged_, logDebugInfo_, it2meConnectFailed_);
104 * Callback for the host plugin to notify the web app of state changes.
105 * @param {remoting.HostSession.State} state The new state of the plugin.
106 * @return {void} Nothing.
108 function onHostStateChanged_(state) {
109 if (state == remoting.HostSession.State.STARTING) {
110 // Nothing to do here.
111 console.log('Host state: STARTING');
113 } else if (state == remoting.HostSession.State.REQUESTED_ACCESS_CODE) {
114 // Nothing to do here.
115 console.log('Host state: REQUESTED_ACCESS_CODE');
117 } else if (state == remoting.HostSession.State.RECEIVED_ACCESS_CODE) {
118 console.log('Host state: RECEIVED_ACCESS_CODE');
119 var accessCode = hostSession_.getAccessCode();
120 var accessCodeDisplay = document.getElementById('access-code-display');
121 accessCodeDisplay.innerText = '';
122 // Display the access code in groups of four digits for readability.
123 var kDigitsPerGroup = 4;
124 for (var i = 0; i < accessCode.length; i += kDigitsPerGroup) {
125 var nextFourDigits = document.createElement('span');
126 nextFourDigits.className = 'access-code-digit-group';
127 nextFourDigits.innerText = accessCode.substring(i, i + kDigitsPerGroup);
128 accessCodeDisplay.appendChild(nextFourDigits);
130 accessCodeExpiresIn_ = hostSession_.getAccessCodeLifetime();
131 if (accessCodeExpiresIn_ > 0) { // Check it hasn't expired.
132 accessCodeTimerId_ = setInterval(decrementAccessCodeTimeout_, 1000);
133 timerRunning_ = true;
134 updateAccessCodeTimeoutElement_();
135 updateTimeoutStyles_();
136 remoting.setMode(remoting.AppMode.HOST_WAITING_FOR_CONNECTION);
138 // This can only happen if the cloud tells us that the code lifetime is
139 // <= 0s, which shouldn't happen so we don't care how clean this UX is.
140 console.error('Access code already invalid on receipt!');
141 remoting.cancelShare();
144 } else if (state == remoting.HostSession.State.CONNECTED) {
145 console.log('Host state: CONNECTED');
146 var element = document.getElementById('host-shared-message');
147 var client = hostSession_.getClient();
148 l10n.localizeElement(element, client);
149 remoting.setMode(remoting.AppMode.HOST_SHARED);
150 disableTimeoutCountdown_();
152 } else if (state == remoting.HostSession.State.DISCONNECTING) {
153 console.log('Host state: DISCONNECTING');
155 } else if (state == remoting.HostSession.State.DISCONNECTED) {
156 console.log('Host state: DISCONNECTED');
157 if (remoting.currentMode != remoting.AppMode.HOST_SHARE_FAILED) {
158 // If an error is being displayed, then the plugin should not be able to
159 // hide it by setting the state. Errors must be dismissed by the user
160 // clicking OK, which puts the app into mode HOME.
161 if (lastShareWasCancelled_) {
162 remoting.setMode(remoting.AppMode.HOME);
164 remoting.setMode(remoting.AppMode.HOST_SHARE_FINISHED);
168 } else if (state == remoting.HostSession.State.ERROR) {
169 console.error('Host state: ERROR');
170 showShareError_(remoting.Error.UNEXPECTED);
171 } else if (state == remoting.HostSession.State.INVALID_DOMAIN_ERROR) {
172 console.error('Host state: INVALID_DOMAIN_ERROR');
173 showShareError_(remoting.Error.INVALID_HOST_DOMAIN);
175 console.error('Unknown state -> ' + state);
180 * This is the callback that the host plugin invokes to indicate that there
181 * is additional debug log info to display.
182 * @param {string} msg The message (which will not be localized) to be logged.
184 function logDebugInfo_(msg) {
185 console.log('plugin: ' + msg);
189 * Show a host-side error message.
191 * @param {string} errorTag The error message to be localized and displayed.
192 * @return {void} Nothing.
194 function showShareError_(errorTag) {
195 var errorDiv = document.getElementById('host-plugin-error');
196 l10n.localizeElementFromTag(errorDiv, errorTag);
197 console.error('Sharing error: ' + errorTag);
198 remoting.setMode(remoting.AppMode.HOST_SHARE_FAILED);
203 * Show a sharing error with error code UNEXPECTED .
205 * @return {void} Nothing.
207 function it2meConnectFailed_() {
208 // TODO (weitaosu): Instruct the user to install the native messaging host.
209 // We probably want to add a new error code (with the corresponding error
210 // message for sharing error.
211 console.error('Cannot share desktop.');
212 showShareError_(remoting.Error.UNEXPECTED);
216 base.dispose(hostSession_);
221 * Cancel an active or pending it2me share operation.
223 * @return {void} Nothing.
225 remoting.cancelShare = function() {
226 document.getElementById('cancel-share-button').disabled = true;
227 console.log('Canceling share...');
228 remoting.lastShareWasCancelled = true;
230 hostSession_.disconnect();
231 } catch (/** @type {*} */ error) {
232 console.error('Error disconnecting: ' + error +
233 '. The host probably crashed.');
234 // TODO(jamiewalch): Clean this up. We should have a class representing
235 // the host plugin, like we do for the client, which should handle crash
236 // reporting and it should use a more detailed error message than the
237 // default 'generic' one. See crbug.com/94624
238 showShareError_(remoting.Error.UNEXPECTED);
240 disableTimeoutCountdown_();
244 * @type {boolean} Whether or not the access code timeout countdown is running.
246 var timerRunning_ = false;
249 * @type {number} The id of the access code expiry countdown timer.
251 var accessCodeTimerId_ = 0;
254 * @type {number} The number of seconds until the access code expires.
256 var accessCodeExpiresIn_ = 0;
259 * The timer callback function
260 * @return {void} Nothing.
262 function decrementAccessCodeTimeout_() {
263 --accessCodeExpiresIn_;
264 updateAccessCodeTimeoutElement_();
268 * Stop the access code timeout countdown if it is running.
269 * @return {void} Nothing.
271 function disableTimeoutCountdown_() {
273 clearInterval(accessCodeTimerId_);
274 timerRunning_ = false;
275 updateTimeoutStyles_();
280 * 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
292 function updateTimeoutStyles_() {
294 if (accessCodeExpiresIn_ <= 0) {
295 remoting.cancelShare();
298 var accessCode = document.getElementById('access-code-display');
299 if (accessCodeExpiresIn_ <= ACCESS_CODE_RED_THRESHOLD_) {
300 accessCode.classList.add('expiring');
302 accessCode.classList.remove('expiring');
305 document.getElementById('access-code-countdown').hidden =
306 (accessCodeExpiresIn_ > ACCESS_CODE_TIMER_DISPLAY_THRESHOLD_) ||
312 * Update the text and appearance of the access code timeout element to
313 * reflect the time remaining.
314 * @return {void} Nothing.
316 function updateAccessCodeTimeoutElement_() {
317 var pad = (accessCodeExpiresIn_ < 10) ? '0:0' : '0:';
318 l10n.localizeElement(document.getElementById('seconds-remaining'),
319 pad + accessCodeExpiresIn_);
320 if (!updateTimeoutStyles_()) {
321 disableTimeoutCountdown_();
326 * Callback to show or hide the NAT traversal warning when the policy changes.
327 * @param {boolean} enabled True if NAT traversal is enabled.
328 * @return {void} Nothing.
330 function onNatTraversalPolicyChanged_(enabled) {
331 var natBox = document.getElementById('nat-box');
333 natBox.classList.add('traversal-enabled');
335 natBox.classList.remove('traversal-enabled');