Add myself as a password bubble owner.
[chromium-blink-merge.git] / remoting / webapp / host_screen.js
blob7aa917fdd20d86ef1455c5022fb39c63b40bf3d4
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 'use strict';
12 /** @suppress {duplicate} */
13 var remoting = remoting || {};
15 /**
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.
18  * @private
19  */
20 var lastShareWasCancelled_ = false;
22 /**
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.
27  */
28 remoting.tryShare = function() {
29   /** @type {remoting.It2MeHostFacade} */
30   var hostFacade = new remoting.It2MeHostFacade();
32   /** @type {remoting.HostInstallDialog} */
33   var hostInstallDialog = null;
35   var tryInitializeFacade = function() {
36     hostFacade.initialize(onFacadeInitialized, onFacadeInitializationFailed);
37   }
39   var onFacadeInitialized = function () {
40     // Host already installed.
41     remoting.startHostUsingFacade_(hostFacade);
42   };
44   var onFacadeInitializationFailed = function() {
45     // If we failed to initialize the dispatcher then prompt the user to install
46     // the host manually.
47     var hasHostDialog = (hostInstallDialog != null);  /** jscompile hack */
48     if (!hasHostDialog) {
49       hostInstallDialog = new remoting.HostInstallDialog();
50       hostInstallDialog.show(tryInitializeFacade, onInstallError);
51     } else {
52       hostInstallDialog.tryAgain();
53     }
54   };
56   /** @param {remoting.Error} error */
57   var onInstallError = function(error) {
58     if (error == remoting.Error.CANCELLED) {
59       remoting.setMode(remoting.AppMode.HOME);
60     } else {
61       showShareError_(error);
62     }
63   }
65   tryInitializeFacade();
68 /**
69  * @param {remoting.It2MeHostFacade} hostFacade An initialized It2MeHostFacade.
70  */
71 remoting.startHostUsingFacade_ = function(hostFacade) {
72   console.log('Attempting to share...');
73   remoting.identity.callWithToken(
74       remoting.tryShareWithToken_.bind(null, hostFacade),
75       remoting.showErrorMessage);
78 /**
79  * @param {remoting.It2MeHostFacade} hostFacade An initialized
80  *     It2MeHostFacade.
81  * @param {string} token The OAuth access token.
82  * @private
83  */
84 remoting.tryShareWithToken_ = function(hostFacade, token) {
85   lastShareWasCancelled_ = false;
86   onNatTraversalPolicyChanged_(true);  // Hide warning by default.
87   remoting.setMode(remoting.AppMode.HOST_WAITING_FOR_CODE);
88   document.getElementById('cancel-share-button').disabled = false;
89   disableTimeoutCountdown_();
91   remoting.hostSession = new remoting.HostSession();
92   var email = /** @type {string} */remoting.identity.getCachedEmail();
93   remoting.hostSession.connect(
94       hostFacade, email, token, onHostStateChanged_,
95       onNatTraversalPolicyChanged_, logDebugInfo_, it2meConnectFailed_);
98 /**
99  * Callback for the host plugin to notify the web app of state changes.
100  * @param {remoting.HostSession.State} state The new state of the plugin.
101  * @return {void} Nothing.
102  * @private
103  */
104 function onHostStateChanged_(state) {
105   if (state == remoting.HostSession.State.STARTING) {
106     // Nothing to do here.
107     console.log('Host state: STARTING');
109   } else if (state == remoting.HostSession.State.REQUESTED_ACCESS_CODE) {
110     // Nothing to do here.
111     console.log('Host state: REQUESTED_ACCESS_CODE');
113   } else if (state == remoting.HostSession.State.RECEIVED_ACCESS_CODE) {
114     console.log('Host state: RECEIVED_ACCESS_CODE');
115     var accessCode = remoting.hostSession.getAccessCode();
116     var accessCodeDisplay = document.getElementById('access-code-display');
117     accessCodeDisplay.innerText = '';
118     // Display the access code in groups of four digits for readability.
119     var kDigitsPerGroup = 4;
120     for (var i = 0; i < accessCode.length; i += kDigitsPerGroup) {
121       var nextFourDigits = document.createElement('span');
122       nextFourDigits.className = 'access-code-digit-group';
123       nextFourDigits.innerText = accessCode.substring(i, i + kDigitsPerGroup);
124       accessCodeDisplay.appendChild(nextFourDigits);
125     }
126     accessCodeExpiresIn_ = remoting.hostSession.getAccessCodeLifetime();
127     if (accessCodeExpiresIn_ > 0) {  // Check it hasn't expired.
128       accessCodeTimerId_ = setInterval(decrementAccessCodeTimeout_, 1000);
129       timerRunning_ = true;
130       updateAccessCodeTimeoutElement_();
131       updateTimeoutStyles_();
132       remoting.setMode(remoting.AppMode.HOST_WAITING_FOR_CONNECTION);
133     } else {
134       // This can only happen if the cloud tells us that the code lifetime is
135       // <= 0s, which shouldn't happen so we don't care how clean this UX is.
136       console.error('Access code already invalid on receipt!');
137       remoting.cancelShare();
138     }
140   } else if (state == remoting.HostSession.State.CONNECTED) {
141     console.log('Host state: CONNECTED');
142     var element = document.getElementById('host-shared-message');
143     var client = remoting.hostSession.getClient();
144     l10n.localizeElement(element, client);
145     remoting.setMode(remoting.AppMode.HOST_SHARED);
146     disableTimeoutCountdown_();
148   } else if (state == remoting.HostSession.State.DISCONNECTING) {
149     console.log('Host state: DISCONNECTING');
151   } else if (state == remoting.HostSession.State.DISCONNECTED) {
152     console.log('Host state: DISCONNECTED');
153     if (remoting.currentMode != remoting.AppMode.HOST_SHARE_FAILED) {
154       // If an error is being displayed, then the plugin should not be able to
155       // hide it by setting the state. Errors must be dismissed by the user
156       // clicking OK, which puts the app into mode HOME.
157       if (lastShareWasCancelled_) {
158         remoting.setMode(remoting.AppMode.HOME);
159       } else {
160         remoting.setMode(remoting.AppMode.HOST_SHARE_FINISHED);
161       }
162     }
163   } else if (state == remoting.HostSession.State.ERROR) {
164     console.error('Host state: ERROR');
165     showShareError_(remoting.Error.UNEXPECTED);
166   } else if (state == remoting.HostSession.State.INVALID_DOMAIN_ERROR) {
167     console.error('Host state: INVALID_DOMAIN_ERROR');
168     showShareError_(remoting.Error.INVALID_HOST_DOMAIN);
169   } else {
170     console.error('Unknown state -> ' + state);
171   }
175  * This is the callback that the host plugin invokes to indicate that there
176  * is additional debug log info to display.
177  * @param {string} msg The message (which will not be localized) to be logged.
178  * @private
179  */
180 function logDebugInfo_(msg) {
181   console.log('plugin: ' + msg);
185  * Show a host-side error message.
187  * @param {string} errorTag The error message to be localized and displayed.
188  * @return {void} Nothing.
189  * @private
190  */
191 function showShareError_(errorTag) {
192   var errorDiv = document.getElementById('host-plugin-error');
193   l10n.localizeElementFromTag(errorDiv, errorTag);
194   console.error('Sharing error: ' + errorTag);
195   remoting.setMode(remoting.AppMode.HOST_SHARE_FAILED);
199  * Show a sharing error with error code UNEXPECTED .
201  * @return {void} Nothing.
202  * @private
203  */
204 function it2meConnectFailed_() {
205   // TODO (weitaosu): Instruct the user to install the native messaging host.
206   // We probably want to add a new error code (with the corresponding error
207   // message for sharing error.
208   console.error('Cannot share desktop.');
209   showShareError_(remoting.Error.UNEXPECTED);
213  * Cancel an active or pending it2me share operation.
215  * @return {void} Nothing.
216  */
217 remoting.cancelShare = function() {
218   document.getElementById('cancel-share-button').disabled = true;
219   console.log('Canceling share...');
220   remoting.lastShareWasCancelled = true;
221   try {
222     remoting.hostSession.disconnect();
223   } catch (error) {
224     // Hack to force JSCompiler type-safety.
225     var errorTyped = /** @type {{description: string}} */ error;
226     console.error('Error disconnecting: ' + errorTyped.description +
227                   '. The host probably crashed.');
228     // TODO(jamiewalch): Clean this up. We should have a class representing
229     // the host plugin, like we do for the client, which should handle crash
230     // reporting and it should use a more detailed error message than the
231     // default 'generic' one. See crbug.com/94624
232     showShareError_(remoting.Error.UNEXPECTED);
233   }
234   disableTimeoutCountdown_();
238  * @type {boolean} Whether or not the access code timeout countdown is running.
239  * @private
240  */
241 var timerRunning_ = false;
244  * @type {number} The id of the access code expiry countdown timer.
245  * @private
246  */
247 var accessCodeTimerId_ = 0;
250  * @type {number} The number of seconds until the access code expires.
251  * @private
252  */
253 var accessCodeExpiresIn_ = 0;
256  * The timer callback function
257  * @return {void} Nothing.
258  * @private
259  */
260 function decrementAccessCodeTimeout_() {
261   --accessCodeExpiresIn_;
262   updateAccessCodeTimeoutElement_();
266  * Stop the access code timeout countdown if it is running.
267  * @return {void} Nothing.
268  * @private
269  */
270 function disableTimeoutCountdown_() {
271   if (timerRunning_) {
272     clearInterval(accessCodeTimerId_);
273     timerRunning_ = false;
274     updateTimeoutStyles_();
275   }
279  * Constants controlling the access code timer countdown display.
280  * @private
281  */
282 var ACCESS_CODE_TIMER_DISPLAY_THRESHOLD_ = 30;
283 var ACCESS_CODE_RED_THRESHOLD_ = 10;
286  * Show/hide or restyle various elements, depending on the remaining countdown
287  * and timer state.
289  * @return {boolean} True if the timeout is in progress, false if it has
290  * expired.
291  * @private
292  */
293 function updateTimeoutStyles_() {
294   if (timerRunning_) {
295     if (accessCodeExpiresIn_ <= 0) {
296       remoting.cancelShare();
297       return false;
298     }
299     var accessCode = document.getElementById('access-code-display');
300     if (accessCodeExpiresIn_ <= ACCESS_CODE_RED_THRESHOLD_) {
301       accessCode.classList.add('expiring');
302     } else {
303       accessCode.classList.remove('expiring');
304     }
305   }
306   document.getElementById('access-code-countdown').hidden =
307       (accessCodeExpiresIn_ > ACCESS_CODE_TIMER_DISPLAY_THRESHOLD_) ||
308       !timerRunning_;
309   return true;
313  * Update the text and appearance of the access code timeout element to
314  * reflect the time remaining.
315  * @return {void} Nothing.
316  * @private
317  */
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_();
324   }
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.
331  * @private
332  */
333 function onNatTraversalPolicyChanged_(enabled) {
334   var natBox = document.getElementById('nat-box');
335   if (enabled) {
336     natBox.classList.add('traversal-enabled');
337   } else {
338     natBox.classList.remove('traversal-enabled');
339   }