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');