Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / remoting / webapp / crd / js / host_screen.js
blob656a7038b43c31ab7d32cad046bf3d7ec13a2eaf
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.
5 /**
6  * @fileoverview
7  * Functions related to the 'host screen' for Chromoting.
8  */
10 /** @suppress {duplicate} */
11 var remoting = remoting || {};
13 (function(){
15 'use strict';
17 /** @type {remoting.HostSession} */
18 var hostSession_ = null;
20 /**
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.
23  */
24 var lastShareWasCancelled_ = false;
26 /**
27  * @type {remoting.LogToServer} Logging instance for IT2Me host connection
28  *     status.
29  */
30 var it2meLogger = null;
32 /**
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.
37  */
38 remoting.tryShare = function() {
39   ensureIT2MeLogger_().then(tryShareWithLogger_);
42 function tryShareWithLogger_() {
43   it2meLogger.setSessionId();
44   it2meLogger.logClientSessionStateChange(
45       remoting.ClientSession.State.INITIALIZING, remoting.Error.none(), null);
47   /** @type {remoting.It2MeHostFacade} */
48   var hostFacade = new remoting.It2MeHostFacade();
50   /** @type {remoting.HostInstallDialog} */
51   var hostInstallDialog = null;
53   var tryInitializeFacade = function() {
54     hostFacade.initialize(onFacadeInitialized, onFacadeInitializationFailed);
55   };
57   var onFacadeInitialized = function () {
58     // Host already installed.
59     remoting.startHostUsingFacade_(hostFacade);
60   };
62   var onFacadeInitializationFailed = function() {
63     // If we failed to initialize the dispatcher then prompt the user to install
64     // the host manually.
65     var hasHostDialog = (hostInstallDialog !== null);  /** jscompile hack */
66     if (!hasHostDialog) {
67       hostInstallDialog = new remoting.HostInstallDialog();
68       hostInstallDialog.show(tryInitializeFacade, showShareError_);
69     } else {
70       hostInstallDialog.tryAgain();
71     }
72   };
74   tryInitializeFacade();
77 /**
78  * @param {remoting.It2MeHostFacade} hostFacade An initialized It2MeHostFacade.
79  */
80 remoting.startHostUsingFacade_ = function(hostFacade) {
81   console.log('Attempting to share...');
82   setHostVersion_()
83       .then(remoting.identity.getToken.bind(remoting.identity))
84       .then(remoting.tryShareWithToken_.bind(null, hostFacade),
85             remoting.Error.handler(showShareError_));
88 /**
89  * @param {remoting.It2MeHostFacade} hostFacade An initialized
90  *     It2MeHostFacade.
91  * @param {string} token The OAuth access token.
92  * @private
93  */
94 remoting.tryShareWithToken_ = function(hostFacade, token) {
95   lastShareWasCancelled_ = false;
96   onNatTraversalPolicyChanged_(true);  // Hide warning by default.
97   remoting.setMode(remoting.AppMode.HOST_WAITING_FOR_CODE);
98   it2meLogger.logClientSessionStateChange(
99       remoting.ClientSession.State.CONNECTING, remoting.Error.none(), null);
100   document.getElementById('cancel-share-button').disabled = false;
101   disableTimeoutCountdown_();
103   console.assert(hostSession_ === null, '|hostSession_| already exists.');
104   hostSession_ = new remoting.HostSession();
105   remoting.identity.getEmail().then(
106       function(/** string */ email) {
107         hostSession_.connect(
108             hostFacade, email, token, onHostStateChanged_,
109             onNatTraversalPolicyChanged_, logDebugInfo_, it2meConnectFailed_);
110       });
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.
117  */
118 function onHostStateChanged_(state) {
119   if (state == remoting.HostSession.State.STARTING) {
120     // Nothing to do here.
121     console.log('Host state: STARTING');
123   } else if (state == remoting.HostSession.State.REQUESTED_ACCESS_CODE) {
124     // Nothing to do here.
125     console.log('Host state: REQUESTED_ACCESS_CODE');
127   } else if (state == remoting.HostSession.State.RECEIVED_ACCESS_CODE) {
128     console.log('Host state: RECEIVED_ACCESS_CODE');
129     var accessCode = hostSession_.getAccessCode();
130     var accessCodeDisplay = document.getElementById('access-code-display');
131     accessCodeDisplay.innerText = '';
132     // Display the access code in groups of four digits for readability.
133     var kDigitsPerGroup = 4;
134     for (var i = 0; i < accessCode.length; i += kDigitsPerGroup) {
135       var nextFourDigits = document.createElement('span');
136       nextFourDigits.className = 'access-code-digit-group';
137       nextFourDigits.innerText = accessCode.substring(i, i + kDigitsPerGroup);
138       accessCodeDisplay.appendChild(nextFourDigits);
139     }
140     accessCodeExpiresIn_ = hostSession_.getAccessCodeLifetime();
141     if (accessCodeExpiresIn_ > 0) {  // Check it hasn't expired.
142       accessCodeTimerId_ = setInterval(decrementAccessCodeTimeout_, 1000);
143       timerRunning_ = true;
144       updateAccessCodeTimeoutElement_();
145       updateTimeoutStyles_();
146       remoting.setMode(remoting.AppMode.HOST_WAITING_FOR_CONNECTION);
147     } else {
148       // This can only happen if the cloud tells us that the code lifetime is
149       // <= 0s, which shouldn't happen so we don't care how clean this UX is.
150       console.error('Access code already invalid on receipt!');
151       remoting.cancelShare();
152     }
154   } else if (state == remoting.HostSession.State.CONNECTED) {
155     console.log('Host state: CONNECTED');
156     var element = document.getElementById('host-shared-message');
157     var client = hostSession_.getClient();
158     l10n.localizeElement(element, client);
159     remoting.setMode(remoting.AppMode.HOST_SHARED);
160     disableTimeoutCountdown_();
162   } else if (state == remoting.HostSession.State.DISCONNECTING) {
163     console.log('Host state: DISCONNECTING');
165   } else if (state == remoting.HostSession.State.DISCONNECTED) {
166     console.log('Host state: DISCONNECTED');
167     if (remoting.currentMode != remoting.AppMode.HOST_SHARE_FAILED) {
168       // If an error is being displayed, then the plugin should not be able to
169       // hide it by setting the state. Errors must be dismissed by the user
170       // clicking OK, which puts the app into mode HOME.
171       if (lastShareWasCancelled_) {
172         remoting.setMode(remoting.AppMode.HOME);
173       } else {
174         remoting.setMode(remoting.AppMode.HOST_SHARE_FINISHED);
175       }
176     }
177     cleanUp();
178   } else if (state == remoting.HostSession.State.ERROR) {
179     // The processing of this message is identical to that of the "error"
180     // message (see it2me_host_facade.js); it is included only to support
181     // old native components that send errors as a host state message.
182     // TODO(jamiewalch): Remove this once there are sufficiently few old
183     //     installations deployed.
184     console.error('Host state: ERROR');
185     showShareError_(remoting.Error.unexpected());
186   } else if (state == remoting.HostSession.State.INVALID_DOMAIN_ERROR) {
187     console.error('Host state: INVALID_DOMAIN_ERROR');
188     showShareError_(new remoting.Error(remoting.Error.Tag.INVALID_HOST_DOMAIN));
189   } else {
190     console.error('Unknown state -> ' + state);
191   }
195  * This is the callback that the host plugin invokes to indicate that there
196  * is additional debug log info to display.
197  * @param {string} msg The message (which will not be localized) to be logged.
198  */
199 function logDebugInfo_(msg) {
200   console.log('plugin: ' + msg);
204  * Show a host-side error message.
206  * @param {!remoting.Error} error The error to be localized and displayed.
207  * @return {void} Nothing.
208  */
209 function showShareError_(error) {
210   if (error.hasTag(remoting.Error.Tag.CANCELLED)) {
211     remoting.setMode(remoting.AppMode.HOME);
212     it2meLogger.logClientSessionStateChange(
213         remoting.ClientSession.State.CONNECTION_CANCELED,
214         remoting.Error.none(),
215         null);
216   } else {
217     var errorDiv = document.getElementById('host-plugin-error');
218     l10n.localizeElementFromTag(errorDiv, error.getTag());
219     console.error('Sharing error: ' + error.toString());
220     remoting.setMode(remoting.AppMode.HOST_SHARE_FAILED);
221     it2meLogger.logClientSessionStateChange(
222         remoting.ClientSession.State.FAILED, error, null);
223   }
225   cleanUp();
229  * Show a sharing error with error code UNEXPECTED .
231  * @return {void} Nothing.
232  */
233 function it2meConnectFailed_() {
234   showShareError_(remoting.Error.unexpected());
237 function cleanUp() {
238   base.dispose(hostSession_);
239   hostSession_ = null;
243  * Cancel an active or pending it2me share operation.
245  * @return {void} Nothing.
246  */
247 remoting.cancelShare = function() {
248   document.getElementById('cancel-share-button').disabled = true;
249   console.log('Canceling share...');
250   remoting.lastShareWasCancelled = true;
251   try {
252     hostSession_.disconnect();
253     it2meLogger.logClientSessionStateChange(
254         remoting.ClientSession.State.CONNECTION_CANCELED,
255         remoting.Error.none(),
256         null);
257   } catch (/** @type {*} */ error) {
258     console.error('Error disconnecting: ' + error +
259                   '. The host probably crashed.');
260     // TODO(jamiewalch): Clean this up. We should have a class representing
261     // the host plugin, like we do for the client, which should handle crash
262     // reporting and it should use a more detailed error message than the
263     // default 'generic' one. See crbug.com/94624
264     showShareError_(remoting.Error.unexpected());
265   }
266   disableTimeoutCountdown_();
270  * @type {boolean} Whether or not the access code timeout countdown is running.
271  */
272 var timerRunning_ = false;
275  * @type {number} The id of the access code expiry countdown timer.
276  */
277 var accessCodeTimerId_ = 0;
280  * @type {number} The number of seconds until the access code expires.
281  */
282 var accessCodeExpiresIn_ = 0;
285  * The timer callback function
286  * @return {void} Nothing.
287  */
288 function decrementAccessCodeTimeout_() {
289   --accessCodeExpiresIn_;
290   updateAccessCodeTimeoutElement_();
294  * Stop the access code timeout countdown if it is running.
295  * @return {void} Nothing.
296  */
297 function disableTimeoutCountdown_() {
298   if (timerRunning_) {
299     clearInterval(accessCodeTimerId_);
300     timerRunning_ = false;
301     updateTimeoutStyles_();
302   }
306  * Constants controlling the access code timer countdown display.
307  */
308 var ACCESS_CODE_TIMER_DISPLAY_THRESHOLD_ = 30;
309 var ACCESS_CODE_RED_THRESHOLD_ = 10;
312  * Show/hide or restyle various elements, depending on the remaining countdown
313  * and timer state.
315  * @return {boolean} True if the timeout is in progress, false if it has
316  * expired.
317  */
318 function updateTimeoutStyles_() {
319   if (timerRunning_) {
320     if (accessCodeExpiresIn_ <= 0) {
321       remoting.cancelShare();
322       return false;
323     }
324     var accessCode = document.getElementById('access-code-display');
325     if (accessCodeExpiresIn_ <= ACCESS_CODE_RED_THRESHOLD_) {
326       accessCode.classList.add('expiring');
327     } else {
328       accessCode.classList.remove('expiring');
329     }
330   }
331   document.getElementById('access-code-countdown').hidden =
332       (accessCodeExpiresIn_ > ACCESS_CODE_TIMER_DISPLAY_THRESHOLD_) ||
333       !timerRunning_;
334   return true;
338  * Update the text and appearance of the access code timeout element to
339  * reflect the time remaining.
340  * @return {void} Nothing.
341  */
342 function updateAccessCodeTimeoutElement_() {
343   var pad = (accessCodeExpiresIn_ < 10) ? '0:0' : '0:';
344   l10n.localizeElement(document.getElementById('seconds-remaining'),
345                        pad + accessCodeExpiresIn_);
346   if (!updateTimeoutStyles_()) {
347     disableTimeoutCountdown_();
348   }
352  * Callback to show or hide the NAT traversal warning when the policy changes.
353  * @param {boolean} enabled True if NAT traversal is enabled.
354  * @return {void} Nothing.
355  */
356 function onNatTraversalPolicyChanged_(enabled) {
357   var natBox = document.getElementById('nat-box');
358   if (enabled) {
359     natBox.classList.add('traversal-enabled');
360   } else {
361     natBox.classList.remove('traversal-enabled');
362   }
366  * Create an IT2Me LogToServer instance if one does not already exist.
368  * @return {Promise} Promise that resolves when the host version (if available),
369  *     has been set on the logger instance.
370  */
371 function ensureIT2MeLogger_() {
372   if (it2meLogger) {
373     return Promise.resolve();
374   }
376   var xmppConnection = new remoting.XmppConnection();
377   var tokenPromise = remoting.identity.getToken();
378   var emailPromise = remoting.identity.getEmail();
379   tokenPromise.then(function(/** string */ token) {
380     emailPromise.then(function(/** string */ email) {
381       xmppConnection.connect(remoting.settings.XMPP_SERVER, email, token);
382     });
383   });
385   var bufferedSignalStrategy =
386       new remoting.BufferedSignalStrategy(xmppConnection);
387   it2meLogger = new remoting.LogToServer(bufferedSignalStrategy, true);
388   it2meLogger.setLogEntryMode(remoting.ChromotingEvent.Mode.IT2ME);
390   return setHostVersion_();
394  * @return {Promise} Promise that resolves when the host version (if available),
395  *     has been set on the logger instance.
396  */
397 function setHostVersion_() {
398   return remoting.hostController.getLocalHostVersion().then(
399       function(/** string */ version) {
400         it2meLogger.setHostVersion(version);
401       }).catch(
402         base.doNothing
403       );
406 })();