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.HostIt2MeDispatcher} */
30 var hostDispatcher = new remoting.HostIt2MeDispatcher();
32 /** @type {remoting.HostInstallDialog} */
33 var hostInstallDialog = null;
35 var tryInitializeDispatcher = function() {
36 hostDispatcher.initialize(createPluginForIt2Me,
37 onDispatcherInitialized,
38 onDispatcherInitializationFailed);
41 /** @return {remoting.HostPlugin} */
42 var createPluginForIt2Me = function() {
43 return remoting.createNpapiPlugin(
44 document.getElementById('host-plugin-container'));
47 var onDispatcherInitialized = function () {
48 remoting.startHostUsingDispatcher_(hostDispatcher);
51 /** @param {remoting.Error} error */
52 var onDispatcherInitializationFailed = function(error) {
53 if (error != remoting.Error.MISSING_PLUGIN) {
54 showShareError_(error);
58 // If we failed to initialize dispatcher then prompt the user to install the
60 if (hostInstallDialog == null) {
61 hostInstallDialog = new remoting.HostInstallDialog();
63 (/** @type {remoting.HostInstallDialog} */ hostInstallDialog)
64 .show(tryInitializeDispatcher, onInstallPromptError);
66 (/** @type {remoting.HostInstallDialog} */ hostInstallDialog).tryAgain();
70 /** @param {remoting.Error} error */
71 var onInstallPromptError = function(error) {
72 if (error == remoting.Error.CANCELLED) {
73 remoting.setMode(remoting.AppMode.HOME);
75 showShareError_(error);
79 tryInitializeDispatcher();
83 * @param {remoting.HostIt2MeDispatcher} hostDispatcher An initialized
84 * HostIt2MeDispatcher.
86 remoting.startHostUsingDispatcher_ = function(hostDispatcher) {
87 console.log('Attempting to share...');
88 remoting.identity.callWithToken(
89 remoting.tryShareWithToken_.bind(null, hostDispatcher),
90 remoting.showErrorMessage);
94 * @param {remoting.HostIt2MeDispatcher} hostDispatcher An initialized
95 * HostIt2MeDispatcher.
96 * @param {string} token The OAuth access token.
99 remoting.tryShareWithToken_ = function(hostDispatcher, token) {
100 lastShareWasCancelled_ = false;
101 onNatTraversalPolicyChanged_(true); // Hide warning by default.
102 remoting.setMode(remoting.AppMode.HOST_WAITING_FOR_CODE);
103 document.getElementById('cancel-share-button').disabled = false;
104 disableTimeoutCountdown_();
106 remoting.hostSession = new remoting.HostSession();
107 var email = /** @type {string} */remoting.identity.getCachedEmail();
108 remoting.hostSession.connect(
109 hostDispatcher, email, token, onHostStateChanged_,
110 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.
119 function onHostStateChanged_(state) {
120 if (state == remoting.HostSession.State.STARTING) {
121 // Nothing to do here.
122 console.log('Host state: STARTING');
124 } else if (state == remoting.HostSession.State.REQUESTED_ACCESS_CODE) {
125 // Nothing to do here.
126 console.log('Host state: REQUESTED_ACCESS_CODE');
128 } else if (state == remoting.HostSession.State.RECEIVED_ACCESS_CODE) {
129 console.log('Host state: RECEIVED_ACCESS_CODE');
130 var accessCode = remoting.hostSession.getAccessCode();
131 var accessCodeDisplay = document.getElementById('access-code-display');
132 accessCodeDisplay.innerText = '';
133 // Display the access code in groups of four digits for readability.
134 var kDigitsPerGroup = 4;
135 for (var i = 0; i < accessCode.length; i += kDigitsPerGroup) {
136 var nextFourDigits = document.createElement('span');
137 nextFourDigits.className = 'access-code-digit-group';
138 nextFourDigits.innerText = accessCode.substring(i, i + kDigitsPerGroup);
139 accessCodeDisplay.appendChild(nextFourDigits);
141 accessCodeExpiresIn_ = remoting.hostSession.getAccessCodeLifetime();
142 if (accessCodeExpiresIn_ > 0) { // Check it hasn't expired.
143 accessCodeTimerId_ = setInterval(decrementAccessCodeTimeout_, 1000);
144 timerRunning_ = true;
145 updateAccessCodeTimeoutElement_();
146 updateTimeoutStyles_();
147 remoting.setMode(remoting.AppMode.HOST_WAITING_FOR_CONNECTION);
149 // This can only happen if the cloud tells us that the code lifetime is
150 // <= 0s, which shouldn't happen so we don't care how clean this UX is.
151 console.error('Access code already invalid on receipt!');
152 remoting.cancelShare();
155 } else if (state == remoting.HostSession.State.CONNECTED) {
156 console.log('Host state: CONNECTED');
157 var element = document.getElementById('host-shared-message');
158 var client = remoting.hostSession.getClient();
159 l10n.localizeElement(element, client);
160 remoting.setMode(remoting.AppMode.HOST_SHARED);
161 disableTimeoutCountdown_();
163 } else if (state == remoting.HostSession.State.DISCONNECTING) {
164 console.log('Host state: DISCONNECTING');
166 } else if (state == remoting.HostSession.State.DISCONNECTED) {
167 console.log('Host state: DISCONNECTED');
168 if (remoting.currentMode != remoting.AppMode.HOST_SHARE_FAILED) {
169 // If an error is being displayed, then the plugin should not be able to
170 // hide it by setting the state. Errors must be dismissed by the user
171 // clicking OK, which puts the app into mode HOME.
172 if (lastShareWasCancelled_) {
173 remoting.setMode(remoting.AppMode.HOME);
175 remoting.setMode(remoting.AppMode.HOST_SHARE_FINISHED);
178 remoting.hostSession.cleanup();
180 } else if (state == remoting.HostSession.State.ERROR) {
181 console.error('Host state: ERROR');
182 showShareError_(remoting.Error.UNEXPECTED);
183 } else if (state == remoting.HostSession.State.INVALID_DOMAIN_ERROR) {
184 console.error('Host state: INVALID_DOMAIN_ERROR');
185 showShareError_(remoting.Error.INVALID_HOST_DOMAIN);
187 console.error('Unknown state -> ' + state);
192 * This is the callback that the host plugin invokes to indicate that there
193 * is additional debug log info to display.
194 * @param {string} msg The message (which will not be localized) to be logged.
197 function logDebugInfo_(msg) {
198 console.log('plugin: ' + msg);
202 * Show a host-side error message.
204 * @param {string} errorTag The error message to be localized and displayed.
205 * @return {void} Nothing.
208 function showShareError_(errorTag) {
209 var errorDiv = document.getElementById('host-plugin-error');
210 l10n.localizeElementFromTag(errorDiv, errorTag);
211 console.error('Sharing error: ' + errorTag);
212 remoting.setMode(remoting.AppMode.HOST_SHARE_FAILED);
216 * Show a sharing error with error code UNEXPECTED .
218 * @return {void} Nothing.
221 function it2meConnectFailed_() {
222 // TODO (weitaosu): Instruct the user to install the native messaging host.
223 // We probably want to add a new error code (with the corresponding error
224 // message for sharing error.
225 console.error('Cannot share desktop.');
226 showShareError_(remoting.Error.UNEXPECTED);
230 * Cancel an active or pending it2me share operation.
232 * @return {void} Nothing.
234 remoting.cancelShare = function() {
235 document.getElementById('cancel-share-button').disabled = true;
236 console.log('Canceling share...');
237 remoting.lastShareWasCancelled = true;
239 remoting.hostSession.disconnect();
241 // Hack to force JSCompiler type-safety.
242 var errorTyped = /** @type {{description: string}} */ error;
243 console.error('Error disconnecting: ' + errorTyped.description +
244 '. The host probably crashed.');
245 // TODO(jamiewalch): Clean this up. We should have a class representing
246 // the host plugin, like we do for the client, which should handle crash
247 // reporting and it should use a more detailed error message than the
248 // default 'generic' one. See crbug.com/94624
249 showShareError_(remoting.Error.UNEXPECTED);
251 disableTimeoutCountdown_();
255 * @type {boolean} Whether or not the access code timeout countdown is running.
258 var timerRunning_ = false;
261 * @type {number} The id of the access code expiry countdown timer.
264 var accessCodeTimerId_ = 0;
267 * @type {number} The number of seconds until the access code expires.
270 var accessCodeExpiresIn_ = 0;
273 * The timer callback function
274 * @return {void} Nothing.
277 function decrementAccessCodeTimeout_() {
278 --accessCodeExpiresIn_;
279 updateAccessCodeTimeoutElement_();
283 * Stop the access code timeout countdown if it is running.
284 * @return {void} Nothing.
287 function disableTimeoutCountdown_() {
289 clearInterval(accessCodeTimerId_);
290 timerRunning_ = false;
291 updateTimeoutStyles_();
296 * Constants controlling the access code timer countdown display.
299 var ACCESS_CODE_TIMER_DISPLAY_THRESHOLD_ = 30;
300 var ACCESS_CODE_RED_THRESHOLD_ = 10;
303 * Show/hide or restyle various elements, depending on the remaining countdown
306 * @return {boolean} True if the timeout is in progress, false if it has
310 function updateTimeoutStyles_() {
312 if (accessCodeExpiresIn_ <= 0) {
313 remoting.cancelShare();
316 var accessCode = document.getElementById('access-code-display');
317 if (accessCodeExpiresIn_ <= ACCESS_CODE_RED_THRESHOLD_) {
318 accessCode.classList.add('expiring');
320 accessCode.classList.remove('expiring');
323 document.getElementById('access-code-countdown').hidden =
324 (accessCodeExpiresIn_ > ACCESS_CODE_TIMER_DISPLAY_THRESHOLD_) ||
330 * Update the text and appearance of the access code timeout element to
331 * reflect the time remaining.
332 * @return {void} Nothing.
335 function updateAccessCodeTimeoutElement_() {
336 var pad = (accessCodeExpiresIn_ < 10) ? '0:0' : '0:';
337 l10n.localizeElement(document.getElementById('seconds-remaining'),
338 pad + accessCodeExpiresIn_);
339 if (!updateTimeoutStyles_()) {
340 disableTimeoutCountdown_();
345 * Callback to show or hide the NAT traversal warning when the policy changes.
346 * @param {boolean} enabled True if NAT traversal is enabled.
347 * @return {void} Nothing.
350 function onNatTraversalPolicyChanged_(enabled) {
351 var natBox = document.getElementById('nat-box');
353 natBox.classList.add('traversal-enabled');
355 natBox.classList.remove('traversal-enabled');