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.hasTag(remoting.Error.Tag.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 remoting.identity.getEmail().then(
98 function(/** string */ email) {
100 hostFacade, email, token, onHostStateChanged_,
101 onNatTraversalPolicyChanged_, logDebugInfo_, it2meConnectFailed_);
106 * Callback for the host plugin to notify the web app of state changes.
107 * @param {remoting.HostSession.State} state The new state of the plugin.
108 * @return {void} Nothing.
110 function onHostStateChanged_(state) {
111 if (state == remoting.HostSession.State.STARTING) {
112 // Nothing to do here.
113 console.log('Host state: STARTING');
115 } else if (state == remoting.HostSession.State.REQUESTED_ACCESS_CODE) {
116 // Nothing to do here.
117 console.log('Host state: REQUESTED_ACCESS_CODE');
119 } else if (state == remoting.HostSession.State.RECEIVED_ACCESS_CODE) {
120 console.log('Host state: RECEIVED_ACCESS_CODE');
121 var accessCode = hostSession_.getAccessCode();
122 var accessCodeDisplay = document.getElementById('access-code-display');
123 accessCodeDisplay.innerText = '';
124 // Display the access code in groups of four digits for readability.
125 var kDigitsPerGroup = 4;
126 for (var i = 0; i < accessCode.length; i += kDigitsPerGroup) {
127 var nextFourDigits = document.createElement('span');
128 nextFourDigits.className = 'access-code-digit-group';
129 nextFourDigits.innerText = accessCode.substring(i, i + kDigitsPerGroup);
130 accessCodeDisplay.appendChild(nextFourDigits);
132 accessCodeExpiresIn_ = hostSession_.getAccessCodeLifetime();
133 if (accessCodeExpiresIn_ > 0) { // Check it hasn't expired.
134 accessCodeTimerId_ = setInterval(decrementAccessCodeTimeout_, 1000);
135 timerRunning_ = true;
136 updateAccessCodeTimeoutElement_();
137 updateTimeoutStyles_();
138 remoting.setMode(remoting.AppMode.HOST_WAITING_FOR_CONNECTION);
140 // This can only happen if the cloud tells us that the code lifetime is
141 // <= 0s, which shouldn't happen so we don't care how clean this UX is.
142 console.error('Access code already invalid on receipt!');
143 remoting.cancelShare();
146 } else if (state == remoting.HostSession.State.CONNECTED) {
147 console.log('Host state: CONNECTED');
148 var element = document.getElementById('host-shared-message');
149 var client = hostSession_.getClient();
150 l10n.localizeElement(element, client);
151 remoting.setMode(remoting.AppMode.HOST_SHARED);
152 disableTimeoutCountdown_();
154 } else if (state == remoting.HostSession.State.DISCONNECTING) {
155 console.log('Host state: DISCONNECTING');
157 } else if (state == remoting.HostSession.State.DISCONNECTED) {
158 console.log('Host state: DISCONNECTED');
159 if (remoting.currentMode != remoting.AppMode.HOST_SHARE_FAILED) {
160 // If an error is being displayed, then the plugin should not be able to
161 // hide it by setting the state. Errors must be dismissed by the user
162 // clicking OK, which puts the app into mode HOME.
163 if (lastShareWasCancelled_) {
164 remoting.setMode(remoting.AppMode.HOME);
166 remoting.setMode(remoting.AppMode.HOST_SHARE_FINISHED);
170 } else if (state == remoting.HostSession.State.ERROR) {
171 console.error('Host state: ERROR');
172 showShareError_(remoting.Error.unexpected());
173 } else if (state == remoting.HostSession.State.INVALID_DOMAIN_ERROR) {
174 console.error('Host state: INVALID_DOMAIN_ERROR');
175 showShareError_(new remoting.Error(remoting.Error.Tag.INVALID_HOST_DOMAIN));
177 console.error('Unknown state -> ' + state);
182 * This is the callback that the host plugin invokes to indicate that there
183 * is additional debug log info to display.
184 * @param {string} msg The message (which will not be localized) to be logged.
186 function logDebugInfo_(msg) {
187 console.log('plugin: ' + msg);
191 * Show a host-side error message.
193 * @param {!remoting.Error} error The error to be localized and displayed.
194 * @return {void} Nothing.
196 function showShareError_(error) {
197 var errorDiv = document.getElementById('host-plugin-error');
198 l10n.localizeElementFromTag(errorDiv, error.getTag());
199 console.error('Sharing error: ' + error.toString());
200 remoting.setMode(remoting.AppMode.HOST_SHARE_FAILED);
205 * Show a sharing error with error code UNEXPECTED .
207 * @return {void} Nothing.
209 function it2meConnectFailed_() {
210 // TODO (weitaosu): Instruct the user to install the native messaging host.
211 // We probably want to add a new error code (with the corresponding error
212 // message for sharing error.
213 console.error('Cannot share desktop.');
214 showShareError_(remoting.Error.unexpected());
218 base.dispose(hostSession_);
223 * Cancel an active or pending it2me share operation.
225 * @return {void} Nothing.
227 remoting.cancelShare = function() {
228 document.getElementById('cancel-share-button').disabled = true;
229 console.log('Canceling share...');
230 remoting.lastShareWasCancelled = true;
232 hostSession_.disconnect();
233 } catch (/** @type {*} */ error) {
234 console.error('Error disconnecting: ' + error +
235 '. The host probably crashed.');
236 // TODO(jamiewalch): Clean this up. We should have a class representing
237 // the host plugin, like we do for the client, which should handle crash
238 // reporting and it should use a more detailed error message than the
239 // default 'generic' one. See crbug.com/94624
240 showShareError_(remoting.Error.unexpected());
242 disableTimeoutCountdown_();
246 * @type {boolean} Whether or not the access code timeout countdown is running.
248 var timerRunning_ = false;
251 * @type {number} The id of the access code expiry countdown timer.
253 var accessCodeTimerId_ = 0;
256 * @type {number} The number of seconds until the access code expires.
258 var accessCodeExpiresIn_ = 0;
261 * The timer callback function
262 * @return {void} Nothing.
264 function decrementAccessCodeTimeout_() {
265 --accessCodeExpiresIn_;
266 updateAccessCodeTimeoutElement_();
270 * Stop the access code timeout countdown if it is running.
271 * @return {void} Nothing.
273 function disableTimeoutCountdown_() {
275 clearInterval(accessCodeTimerId_);
276 timerRunning_ = false;
277 updateTimeoutStyles_();
282 * Constants controlling the access code timer countdown display.
284 var ACCESS_CODE_TIMER_DISPLAY_THRESHOLD_ = 30;
285 var ACCESS_CODE_RED_THRESHOLD_ = 10;
288 * Show/hide or restyle various elements, depending on the remaining countdown
291 * @return {boolean} True if the timeout is in progress, false if it has
294 function updateTimeoutStyles_() {
296 if (accessCodeExpiresIn_ <= 0) {
297 remoting.cancelShare();
300 var accessCode = document.getElementById('access-code-display');
301 if (accessCodeExpiresIn_ <= ACCESS_CODE_RED_THRESHOLD_) {
302 accessCode.classList.add('expiring');
304 accessCode.classList.remove('expiring');
307 document.getElementById('access-code-countdown').hidden =
308 (accessCodeExpiresIn_ > ACCESS_CODE_TIMER_DISPLAY_THRESHOLD_) ||
314 * Update the text and appearance of the access code timeout element to
315 * reflect the time remaining.
316 * @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.
332 function onNatTraversalPolicyChanged_(enabled) {
333 var natBox = document.getElementById('nat-box');
335 natBox.classList.add('traversal-enabled');
337 natBox.classList.remove('traversal-enabled');