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 * @type {remoting.LogToServer} Logging instance for IT2Me host connection
30 var it2meLogger = null;
33 * Start a host session. This is the main entry point for the host screen,
34 * called directly from the onclick action of a button on the home screen.
35 * It first verifies that the native host components are installed and asks
36 * to install them if necessary.
38 remoting.tryShare = function() {
39 ensureIT2MeLogger_().then(tryShareWithLogger_);
42 function tryShareWithLogger_() {
43 it2meLogger.setSessionId();
44 it2meLogger.logClientSessionStateChange(
45 remoting.ClientSession.State.INITIALIZING,
46 remoting.Error.none());
48 /** @type {remoting.It2MeHostFacade} */
49 var hostFacade = new remoting.It2MeHostFacade();
51 /** @type {remoting.HostInstallDialog} */
52 var hostInstallDialog = null;
54 var tryInitializeFacade = function() {
55 hostFacade.initialize(onFacadeInitialized, onFacadeInitializationFailed);
58 var onFacadeInitialized = function () {
59 // Host already installed.
60 remoting.startHostUsingFacade_(hostFacade);
63 var onFacadeInitializationFailed = function() {
64 // If we failed to initialize the dispatcher then prompt the user to install
66 var hasHostDialog = (hostInstallDialog !== null); /** jscompile hack */
68 hostInstallDialog = new remoting.HostInstallDialog();
69 hostInstallDialog.show(tryInitializeFacade, showShareError_);
71 hostInstallDialog.tryAgain();
75 tryInitializeFacade();
79 * @param {remoting.It2MeHostFacade} hostFacade An initialized It2MeHostFacade.
81 remoting.startHostUsingFacade_ = function(hostFacade) {
82 console.log('Attempting to share...');
84 .then(remoting.identity.getToken.bind(remoting.identity))
85 .then(remoting.tryShareWithToken_.bind(null, hostFacade),
86 remoting.Error.handler(showShareError_));
90 * @param {remoting.It2MeHostFacade} hostFacade An initialized
92 * @param {string} token The OAuth access token.
95 remoting.tryShareWithToken_ = function(hostFacade, token) {
96 lastShareWasCancelled_ = false;
97 onNatTraversalPolicyChanged_(true); // Hide warning by default.
98 remoting.setMode(remoting.AppMode.HOST_WAITING_FOR_CODE);
99 it2meLogger.logClientSessionStateChange(
100 remoting.ClientSession.State.CONNECTING,
101 remoting.Error.none());
102 document.getElementById('cancel-share-button').disabled = false;
103 disableTimeoutCountdown_();
105 console.assert(hostSession_ === null, '|hostSession_| already exists.');
106 hostSession_ = new remoting.HostSession();
107 remoting.identity.getEmail().then(
108 function(/** string */ email) {
109 hostSession_.connect(
110 hostFacade, email, token, onHostStateChanged_,
111 onNatTraversalPolicyChanged_, logDebugInfo_, it2meConnectFailed_);
116 * Callback for the host plugin to notify the web app of state changes.
117 * @param {remoting.HostSession.State} state The new state of the plugin.
118 * @return {void} Nothing.
120 function onHostStateChanged_(state) {
121 if (state == remoting.HostSession.State.STARTING) {
122 // Nothing to do here.
123 console.log('Host state: STARTING');
125 } else if (state == remoting.HostSession.State.REQUESTED_ACCESS_CODE) {
126 // Nothing to do here.
127 console.log('Host state: REQUESTED_ACCESS_CODE');
129 } else if (state == remoting.HostSession.State.RECEIVED_ACCESS_CODE) {
130 console.log('Host state: RECEIVED_ACCESS_CODE');
131 var accessCode = hostSession_.getAccessCode();
132 var accessCodeDisplay = document.getElementById('access-code-display');
133 accessCodeDisplay.innerText = '';
134 // Display the access code in groups of four digits for readability.
135 var kDigitsPerGroup = 4;
136 for (var i = 0; i < accessCode.length; i += kDigitsPerGroup) {
137 var nextFourDigits = document.createElement('span');
138 nextFourDigits.className = 'access-code-digit-group';
139 nextFourDigits.innerText = accessCode.substring(i, i + kDigitsPerGroup);
140 accessCodeDisplay.appendChild(nextFourDigits);
142 accessCodeExpiresIn_ = hostSession_.getAccessCodeLifetime();
143 if (accessCodeExpiresIn_ > 0) { // Check it hasn't expired.
144 accessCodeTimerId_ = setInterval(decrementAccessCodeTimeout_, 1000);
145 timerRunning_ = true;
146 updateAccessCodeTimeoutElement_();
147 updateTimeoutStyles_();
148 remoting.setMode(remoting.AppMode.HOST_WAITING_FOR_CONNECTION);
150 // This can only happen if the cloud tells us that the code lifetime is
151 // <= 0s, which shouldn't happen so we don't care how clean this UX is.
152 console.error('Access code already invalid on receipt!');
153 remoting.cancelShare();
156 } else if (state == remoting.HostSession.State.CONNECTED) {
157 console.log('Host state: CONNECTED');
158 var element = document.getElementById('host-shared-message');
159 var client = hostSession_.getClient();
160 l10n.localizeElement(element, client);
161 remoting.setMode(remoting.AppMode.HOST_SHARED);
162 disableTimeoutCountdown_();
164 } else if (state == remoting.HostSession.State.DISCONNECTING) {
165 console.log('Host state: DISCONNECTING');
167 } else if (state == remoting.HostSession.State.DISCONNECTED) {
168 console.log('Host state: DISCONNECTED');
169 if (remoting.currentMode != remoting.AppMode.HOST_SHARE_FAILED) {
170 // If an error is being displayed, then the plugin should not be able to
171 // hide it by setting the state. Errors must be dismissed by the user
172 // clicking OK, which puts the app into mode HOME.
173 if (lastShareWasCancelled_) {
174 remoting.setMode(remoting.AppMode.HOME);
176 remoting.setMode(remoting.AppMode.HOST_SHARE_FINISHED);
180 } else if (state == remoting.HostSession.State.ERROR) {
181 // The processing of this message is identical to that of the "error"
182 // message (see it2me_host_facade.js); it is included only to support
183 // old native components that send errors as a host state message.
184 // TODO(jamiewalch): Remove this once there are sufficiently few old
185 // installations deployed.
186 console.error('Host state: ERROR');
187 showShareError_(remoting.Error.unexpected());
188 } else if (state == remoting.HostSession.State.INVALID_DOMAIN_ERROR) {
189 console.error('Host state: INVALID_DOMAIN_ERROR');
190 showShareError_(new remoting.Error(remoting.Error.Tag.INVALID_HOST_DOMAIN));
192 console.error('Unknown state -> ' + state);
197 * This is the callback that the host plugin invokes to indicate that there
198 * is additional debug log info to display.
199 * @param {string} msg The message (which will not be localized) to be logged.
201 function logDebugInfo_(msg) {
202 console.log('plugin: ' + msg);
206 * Show a host-side error message.
208 * @param {!remoting.Error} error The error to be localized and displayed.
209 * @return {void} Nothing.
211 function showShareError_(error) {
212 if (error.hasTag(remoting.Error.Tag.CANCELLED)) {
213 remoting.setMode(remoting.AppMode.HOME);
214 it2meLogger.logClientSessionStateChange(
215 remoting.ClientSession.State.CONNECTION_CANCELED,
216 remoting.Error.none());
218 var errorDiv = document.getElementById('host-plugin-error');
219 l10n.localizeElementFromTag(errorDiv, error.getTag());
220 console.error('Sharing error: ' + error.toString());
221 remoting.setMode(remoting.AppMode.HOST_SHARE_FAILED);
222 it2meLogger.logClientSessionStateChange(
223 remoting.ClientSession.State.FAILED,
231 * Show a sharing error with error code UNEXPECTED .
233 * @return {void} Nothing.
235 function it2meConnectFailed_() {
236 showShareError_(remoting.Error.unexpected());
240 base.dispose(hostSession_);
245 * Cancel an active or pending it2me share operation.
247 * @return {void} Nothing.
249 remoting.cancelShare = function() {
250 document.getElementById('cancel-share-button').disabled = true;
251 console.log('Canceling share...');
252 remoting.lastShareWasCancelled = true;
254 hostSession_.disconnect();
255 it2meLogger.logClientSessionStateChange(
256 remoting.ClientSession.State.CONNECTION_CANCELED,
257 remoting.Error.none());
258 } catch (/** @type {*} */ error) {
259 console.error('Error disconnecting: ' + error +
260 '. The host probably crashed.');
261 // TODO(jamiewalch): Clean this up. We should have a class representing
262 // the host plugin, like we do for the client, which should handle crash
263 // reporting and it should use a more detailed error message than the
264 // default 'generic' one. See crbug.com/94624
265 showShareError_(remoting.Error.unexpected());
267 disableTimeoutCountdown_();
271 * @type {boolean} Whether or not the access code timeout countdown is running.
273 var timerRunning_ = false;
276 * @type {number} The id of the access code expiry countdown timer.
278 var accessCodeTimerId_ = 0;
281 * @type {number} The number of seconds until the access code expires.
283 var accessCodeExpiresIn_ = 0;
286 * The timer callback function
287 * @return {void} Nothing.
289 function decrementAccessCodeTimeout_() {
290 --accessCodeExpiresIn_;
291 updateAccessCodeTimeoutElement_();
295 * Stop the access code timeout countdown if it is running.
296 * @return {void} Nothing.
298 function disableTimeoutCountdown_() {
300 clearInterval(accessCodeTimerId_);
301 timerRunning_ = false;
302 updateTimeoutStyles_();
307 * Constants controlling the access code timer countdown display.
309 var ACCESS_CODE_TIMER_DISPLAY_THRESHOLD_ = 30;
310 var ACCESS_CODE_RED_THRESHOLD_ = 10;
313 * Show/hide or restyle various elements, depending on the remaining countdown
316 * @return {boolean} True if the timeout is in progress, false if it has
319 function updateTimeoutStyles_() {
321 if (accessCodeExpiresIn_ <= 0) {
322 remoting.cancelShare();
325 var accessCode = document.getElementById('access-code-display');
326 if (accessCodeExpiresIn_ <= ACCESS_CODE_RED_THRESHOLD_) {
327 accessCode.classList.add('expiring');
329 accessCode.classList.remove('expiring');
332 document.getElementById('access-code-countdown').hidden =
333 (accessCodeExpiresIn_ > ACCESS_CODE_TIMER_DISPLAY_THRESHOLD_) ||
339 * Update the text and appearance of the access code timeout element to
340 * reflect the time remaining.
341 * @return {void} Nothing.
343 function updateAccessCodeTimeoutElement_() {
344 var pad = (accessCodeExpiresIn_ < 10) ? '0:0' : '0:';
345 l10n.localizeElement(document.getElementById('seconds-remaining'),
346 pad + accessCodeExpiresIn_);
347 if (!updateTimeoutStyles_()) {
348 disableTimeoutCountdown_();
353 * Callback to show or hide the NAT traversal warning when the policy changes.
354 * @param {boolean} enabled True if NAT traversal is enabled.
355 * @return {void} Nothing.
357 function onNatTraversalPolicyChanged_(enabled) {
358 var natBox = document.getElementById('nat-box');
360 natBox.classList.add('traversal-enabled');
362 natBox.classList.remove('traversal-enabled');
367 * Create an IT2Me LogToServer instance if one does not already exist.
369 * @return {Promise} Promise that resolves when the host version (if available),
370 * has been set on the logger instance.
372 function ensureIT2MeLogger_() {
374 return Promise.resolve();
377 var xmppConnection = new remoting.XmppConnection();
378 var tokenPromise = remoting.identity.getToken();
379 var emailPromise = remoting.identity.getEmail();
380 tokenPromise.then(function(/** string */ token) {
381 emailPromise.then(function(/** string */ email) {
382 xmppConnection.connect(remoting.settings.XMPP_SERVER, email, token);
386 var bufferedSignalStrategy =
387 new remoting.BufferedSignalStrategy(xmppConnection);
388 it2meLogger = new remoting.LogToServer(bufferedSignalStrategy, true);
389 it2meLogger.setLogEntryMode(remoting.ChromotingEvent.Mode.IT2ME);
391 return setHostVersion_();
395 * @return {Promise} Promise that resolves when the host version (if available),
396 * has been set on the logger instance.
398 function setHostVersion_() {
399 return remoting.hostController.getLocalHostVersion().then(
400 function(/** string */ version) {
401 it2meLogger.setHostVersion(version);