Updating XTBs based on .GRDs from branch master
[chromium-blink-merge.git] / remoting / webapp / crd / js / host_screen.js
blob99df948db520504bd30a7e87fae2724093a5bfcc
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,
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);
56   };
58   var onFacadeInitialized = function () {
59     // Host already installed.
60     remoting.startHostUsingFacade_(hostFacade);
61   };
63   var onFacadeInitializationFailed = function() {
64     // If we failed to initialize the dispatcher then prompt the user to install
65     // the host manually.
66     var hasHostDialog = (hostInstallDialog !== null);  /** jscompile hack */
67     if (!hasHostDialog) {
68       hostInstallDialog = new remoting.HostInstallDialog();
69       hostInstallDialog.show(tryInitializeFacade, showShareError_);
70     } else {
71       hostInstallDialog.tryAgain();
72     }
73   };
75   tryInitializeFacade();
78 /**
79  * @param {remoting.It2MeHostFacade} hostFacade An initialized It2MeHostFacade.
80  */
81 remoting.startHostUsingFacade_ = function(hostFacade) {
82   console.log('Attempting to share...');
83   setHostVersion_()
84       .then(remoting.identity.getToken.bind(remoting.identity))
85       .then(remoting.tryShareWithToken_.bind(null, hostFacade),
86             remoting.Error.handler(showShareError_));
89 /**
90  * @param {remoting.It2MeHostFacade} hostFacade An initialized
91  *     It2MeHostFacade.
92  * @param {string} token The OAuth access token.
93  * @private
94  */
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_);
112       });
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.
119  */
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);
141     }
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);
149     } else {
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();
154     }
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);
175       } else {
176         remoting.setMode(remoting.AppMode.HOST_SHARE_FINISHED);
177       }
178     }
179     cleanUp();
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));
191   } else {
192     console.error('Unknown state -> ' + state);
193   }
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.
200  */
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.
210  */
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());
217   } else {
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,
224         error);
225   }
227   cleanUp();
231  * Show a sharing error with error code UNEXPECTED .
233  * @return {void} Nothing.
234  */
235 function it2meConnectFailed_() {
236   showShareError_(remoting.Error.unexpected());
239 function cleanUp() {
240   base.dispose(hostSession_);
241   hostSession_ = null;
245  * Cancel an active or pending it2me share operation.
247  * @return {void} Nothing.
248  */
249 remoting.cancelShare = function() {
250   document.getElementById('cancel-share-button').disabled = true;
251   console.log('Canceling share...');
252   remoting.lastShareWasCancelled = true;
253   try {
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());
266   }
267   disableTimeoutCountdown_();
271  * @type {boolean} Whether or not the access code timeout countdown is running.
272  */
273 var timerRunning_ = false;
276  * @type {number} The id of the access code expiry countdown timer.
277  */
278 var accessCodeTimerId_ = 0;
281  * @type {number} The number of seconds until the access code expires.
282  */
283 var accessCodeExpiresIn_ = 0;
286  * The timer callback function
287  * @return {void} Nothing.
288  */
289 function decrementAccessCodeTimeout_() {
290   --accessCodeExpiresIn_;
291   updateAccessCodeTimeoutElement_();
295  * Stop the access code timeout countdown if it is running.
296  * @return {void} Nothing.
297  */
298 function disableTimeoutCountdown_() {
299   if (timerRunning_) {
300     clearInterval(accessCodeTimerId_);
301     timerRunning_ = false;
302     updateTimeoutStyles_();
303   }
307  * Constants controlling the access code timer countdown display.
308  */
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
314  * and timer state.
316  * @return {boolean} True if the timeout is in progress, false if it has
317  * expired.
318  */
319 function updateTimeoutStyles_() {
320   if (timerRunning_) {
321     if (accessCodeExpiresIn_ <= 0) {
322       remoting.cancelShare();
323       return false;
324     }
325     var accessCode = document.getElementById('access-code-display');
326     if (accessCodeExpiresIn_ <= ACCESS_CODE_RED_THRESHOLD_) {
327       accessCode.classList.add('expiring');
328     } else {
329       accessCode.classList.remove('expiring');
330     }
331   }
332   document.getElementById('access-code-countdown').hidden =
333       (accessCodeExpiresIn_ > ACCESS_CODE_TIMER_DISPLAY_THRESHOLD_) ||
334       !timerRunning_;
335   return true;
339  * Update the text and appearance of the access code timeout element to
340  * reflect the time remaining.
341  * @return {void} Nothing.
342  */
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_();
349   }
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.
356  */
357 function onNatTraversalPolicyChanged_(enabled) {
358   var natBox = document.getElementById('nat-box');
359   if (enabled) {
360     natBox.classList.add('traversal-enabled');
361   } else {
362     natBox.classList.remove('traversal-enabled');
363   }
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.
371  */
372 function ensureIT2MeLogger_() {
373   if (it2meLogger) {
374     return Promise.resolve();
375   }
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);
383     });
384   });
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.
397  */
398 function setHostVersion_() {
399   return remoting.hostController.getLocalHostVersion().then(
400       function(/** string */ version) {
401         it2meLogger.setHostVersion(version);
402       }).catch(
403         base.doNothing
404       );
407 })();