Updating trunk VERSION from 2139.0 to 2140.0
[chromium-blink-merge.git] / remoting / webapp / host_setup_dialog.js
blob4697c9008c9ac3e443999477137f332d0150e631
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 'use strict';
7 /** @suppress {duplicate} */
8 var remoting = remoting || {};
10 /**
11  * @param {Array.<remoting.HostSetupFlow.State>} sequence Sequence of
12  *     steps for the flow.
13  * @constructor
14  */
15 remoting.HostSetupFlow = function(sequence) {
16   this.sequence_ = sequence;
17   this.currentStep_ = 0;
18   this.state_ = sequence[0];
19   this.pin = '';
20   this.consent = false;
23 /** @enum {number} */
24 remoting.HostSetupFlow.State = {
25   NONE: 0,
27   // Dialog states.
28   ASK_PIN: 1,
30   // Prompts the user to install the host package.
31   INSTALL_HOST: 2,
33   // Processing states.
34   STARTING_HOST: 3,
35   UPDATING_PIN: 4,
36   STOPPING_HOST: 5,
38   // Done states.
39   HOST_STARTED: 6,
40   UPDATED_PIN: 7,
41   HOST_STOPPED: 8,
43   // Failure states.
44   REGISTRATION_FAILED: 9,
45   START_HOST_FAILED: 10,
46   UPDATE_PIN_FAILED: 11,
47   STOP_HOST_FAILED: 12
50 /** @return {remoting.HostSetupFlow.State} Current state of the flow. */
51 remoting.HostSetupFlow.prototype.getState = function() {
52   return this.state_;
55 remoting.HostSetupFlow.prototype.switchToNextStep = function() {
56   if (this.state_ == remoting.HostSetupFlow.State.NONE) {
57     return;
58   }
60   if (this.currentStep_ < this.sequence_.length - 1) {
61     this.currentStep_ += 1;
62     this.state_ = this.sequence_[this.currentStep_];
63   } else {
64     this.state_ = remoting.HostSetupFlow.State.NONE;
65   }
68 /**
69  * @param {remoting.Error} error
70  */
71 remoting.HostSetupFlow.prototype.switchToErrorState = function(error) {
72   if (error == remoting.Error.CANCELLED) {
73     // Stop the setup flow if user rejected one of the actions.
74     this.state_ = remoting.HostSetupFlow.State.NONE;
75   } else {
76     // Current step failed, so switch to corresponding error state.
77     if (this.state_ == remoting.HostSetupFlow.State.STARTING_HOST) {
78       if (error == remoting.Error.REGISTRATION_FAILED) {
79         this.state_ = remoting.HostSetupFlow.State.REGISTRATION_FAILED;
80       } else {
81         this.state_ = remoting.HostSetupFlow.State.START_HOST_FAILED;
82       }
83     } else if (this.state_ == remoting.HostSetupFlow.State.UPDATING_PIN) {
84       this.state_ = remoting.HostSetupFlow.State.UPDATE_PIN_FAILED;
85     } else if (this.state_ == remoting.HostSetupFlow.State.STOPPING_HOST) {
86       this.state_ = remoting.HostSetupFlow.State.STOP_HOST_FAILED;
87     } else {
88       // TODO(sergeyu): Add other error states and use them here.
89       this.state_ = remoting.HostSetupFlow.State.START_HOST_FAILED;
90     }
91   }
94 /**
95  * @param {remoting.HostController} hostController The HostController
96  * responsible for the host daemon.
97  * @constructor
98  */
99 remoting.HostSetupDialog = function(hostController) {
100   this.hostController_ = hostController;
101   this.pinEntry_ = document.getElementById('daemon-pin-entry');
102   this.pinConfirm_ = document.getElementById('daemon-pin-confirm');
103   this.pinErrorDiv_ = document.getElementById('daemon-pin-error-div');
104   this.pinErrorMessage_ = document.getElementById('daemon-pin-error-message');
106   /** @type {remoting.HostSetupFlow} */
107   this.flow_ = new remoting.HostSetupFlow([remoting.HostSetupFlow.State.NONE]);
109   /** @type {remoting.HostSetupDialog} */
110   var that = this;
111   /** @param {Event} event The event. */
112   var onPinSubmit = function(event) {
113     event.preventDefault();
114     that.onPinSubmit_();
115   };
116   var onPinConfirmFocus = function() {
117     that.validatePin_();
118   };
120   var form = document.getElementById('ask-pin-form');
121   form.addEventListener('submit', onPinSubmit, false);
122   /** @param {Event} event The event. */
123   var onDaemonPinEntryKeyPress = function(event) {
124     if (event.which == 13) {
125       document.getElementById('daemon-pin-confirm').focus();
126       event.preventDefault();
127     }
128   };
129   /** @param {Event} event A keypress event. */
130   var noDigitsInPin = function(event) {
131     if (event.which == 13) {
132       return;  // Otherwise the "submit" action can't be triggered by Enter.
133     }
134     if ((event.which >= 48) && (event.which <= 57)) {
135       return;
136     }
137     event.preventDefault();
138   };
139   this.pinEntry_.addEventListener('keypress', onDaemonPinEntryKeyPress, false);
140   this.pinEntry_.addEventListener('keypress', noDigitsInPin, false);
141   this.pinConfirm_.addEventListener('focus', onPinConfirmFocus, false);
142   this.pinConfirm_.addEventListener('keypress', noDigitsInPin, false);
144   this.usageStats_ = document.getElementById('usagestats-consent');
145   this.usageStatsCheckbox_ = /** @type {HTMLInputElement} */
146       document.getElementById('usagestats-consent-checkbox');
150  * Show the dialog in order to get a PIN prior to starting the daemon. When the
151  * user clicks OK, the dialog shows a spinner until the daemon has started.
153  * @return {void} Nothing.
154  */
155 remoting.HostSetupDialog.prototype.showForStart = function() {
156   /** @type {remoting.HostSetupDialog} */
157   var that = this;
159   /**
160    * @param {remoting.HostController.State} state
161    */
162   var onState = function(state) {
163     // Although we don't need an access token in order to start the host,
164     // using callWithToken here ensures consistent error handling in the
165     // case where the refresh token is invalid.
166     remoting.identity.callWithToken(
167         that.showForStartWithToken_.bind(that, state),
168         remoting.showErrorMessage);
169   };
171   this.hostController_.getLocalHostState(onState);
175  * @param {remoting.HostController.State} state The current state of the local
176  *     host.
177  * @param {string} token The OAuth2 token.
178  * @private
179  */
180 remoting.HostSetupDialog.prototype.showForStartWithToken_ =
181     function(state, token) {
182   /** @type {remoting.HostSetupDialog} */
183   var that = this;
185   /**
186    * @param {boolean} supported True if crash dump reporting is supported by
187    *     the host.
188    * @param {boolean} allowed True if crash dump reporting is allowed.
189    * @param {boolean} set_by_policy True if crash dump reporting is controlled
190    *     by policy.
191    */
192   function onGetConsent(supported, allowed, set_by_policy) {
193     that.usageStats_.hidden = !supported;
194     that.usageStatsCheckbox_.checked = allowed;
195     that.usageStatsCheckbox_.disabled = set_by_policy;
196   }
198   /** @param {remoting.Error} error */
199   function onError(error) {
200     console.error('Error getting consent status: ' + error);
201   }
203   this.usageStats_.hidden = false;
204   this.usageStatsCheckbox_.checked = false;
206   // Prevent user from ticking the box until the current consent status is
207   // known.
208   this.usageStatsCheckbox_.disabled = true;
210   this.hostController_.getConsent(onGetConsent, onError);
212   var flow = [
213       remoting.HostSetupFlow.State.INSTALL_HOST,
214       remoting.HostSetupFlow.State.ASK_PIN,
215       remoting.HostSetupFlow.State.STARTING_HOST,
216       remoting.HostSetupFlow.State.HOST_STARTED];
218   var installed =
219       state != remoting.HostController.State.NOT_INSTALLED &&
220       state != remoting.HostController.State.INSTALLING;
222   // Skip the installation step when the host is already installed.
223   if (installed) {
224     flow.shift();
225   }
227   this.startNewFlow_(flow);
231  * Show the dialog in order to change the PIN associated with a running daemon.
233  * @return {void} Nothing.
234  */
235 remoting.HostSetupDialog.prototype.showForPin = function() {
236   this.usageStats_.hidden = true;
237   this.startNewFlow_(
238       [remoting.HostSetupFlow.State.ASK_PIN,
239        remoting.HostSetupFlow.State.UPDATING_PIN,
240        remoting.HostSetupFlow.State.UPDATED_PIN]);
244  * Show the dialog in order to stop the daemon.
246  * @return {void} Nothing.
247  */
248 remoting.HostSetupDialog.prototype.showForStop = function() {
249   // TODO(sergeyu): Add another step to unregister the host, crubg.com/121146 .
250   this.startNewFlow_(
251       [remoting.HostSetupFlow.State.STOPPING_HOST,
252        remoting.HostSetupFlow.State.HOST_STOPPED]);
256  * @return {void} Nothing.
257  */
258 remoting.HostSetupDialog.prototype.hide = function() {
259   remoting.setMode(remoting.AppMode.HOME);
263  * Starts new flow with the specified sequence of steps.
264  * @param {Array.<remoting.HostSetupFlow.State>} sequence Sequence of steps.
265  * @private
266  */
267 remoting.HostSetupDialog.prototype.startNewFlow_ = function(sequence) {
268   this.flow_ = new remoting.HostSetupFlow(sequence);
269   this.pinEntry_.value = '';
270   this.pinConfirm_.value = '';
271   this.pinErrorDiv_.hidden = true;
272   this.updateState_();
276  * Updates current UI mode according to the current state of the setup
277  * flow and start the action corresponding to the current step (if
278  * any).
279  * @private
280  */
281 remoting.HostSetupDialog.prototype.updateState_ = function() {
282   remoting.updateLocalHostState();
284   /** @param {string} tag1
285    *  @param {string=} opt_tag2 */
286   function showDoneMessage(tag1, opt_tag2) {
287     var messageDiv = document.getElementById('host-setup-done-message');
288     l10n.localizeElementFromTag(messageDiv, tag1);
289     messageDiv = document.getElementById('host-setup-done-message-2');
290     if (opt_tag2) {
291       l10n.localizeElementFromTag(messageDiv, opt_tag2);
292     } else {
293       messageDiv.innerText = '';
294     }
295     remoting.setMode(remoting.AppMode.HOST_SETUP_DONE);
296   }
297   /** @param {string} tag */
298   function showErrorMessage(tag) {
299     var errorDiv = document.getElementById('host-setup-error-message');
300     l10n.localizeElementFromTag(errorDiv, tag);
301     remoting.setMode(remoting.AppMode.HOST_SETUP_ERROR);
302   }
304   var state = this.flow_.getState();
305   if (state == remoting.HostSetupFlow.State.NONE) {
306     this.hide();
307   } else if (state == remoting.HostSetupFlow.State.ASK_PIN) {
308     remoting.setMode(remoting.AppMode.HOST_SETUP_ASK_PIN);
309   } else if (state == remoting.HostSetupFlow.State.INSTALL_HOST) {
310     this.installHost_();
311   } else if (state == remoting.HostSetupFlow.State.STARTING_HOST) {
312     remoting.showSetupProcessingMessage(/*i18n-content*/'HOST_SETUP_STARTING');
313     this.startHost_();
314   } else if (state == remoting.HostSetupFlow.State.UPDATING_PIN) {
315     remoting.showSetupProcessingMessage(
316         /*i18n-content*/'HOST_SETUP_UPDATING_PIN');
317     this.updatePin_();
318   } else if (state == remoting.HostSetupFlow.State.STOPPING_HOST) {
319     remoting.showSetupProcessingMessage(/*i18n-content*/'HOST_SETUP_STOPPING');
320     this.stopHost_();
321   } else if (state == remoting.HostSetupFlow.State.HOST_STARTED) {
322     // TODO(jamiewalch): Only display the second string if the computer's power
323     // management settings indicate that it's necessary.
324     showDoneMessage(/*i18n-content*/'HOST_SETUP_STARTED',
325                     /*i18n-content*/'HOST_SETUP_STARTED_DISABLE_SLEEP');
326   } else if (state == remoting.HostSetupFlow.State.UPDATED_PIN) {
327     showDoneMessage(/*i18n-content*/'HOST_SETUP_UPDATED_PIN');
328   } else if (state == remoting.HostSetupFlow.State.HOST_STOPPED) {
329     showDoneMessage(/*i18n-content*/'HOST_SETUP_STOPPED');
330   } else if (state == remoting.HostSetupFlow.State.REGISTRATION_FAILED) {
331     showErrorMessage(/*i18n-content*/'ERROR_HOST_REGISTRATION_FAILED');
332   } else if (state == remoting.HostSetupFlow.State.START_HOST_FAILED) {
333     showErrorMessage(/*i18n-content*/'HOST_SETUP_HOST_FAILED');
334   } else if (state == remoting.HostSetupFlow.State.UPDATE_PIN_FAILED) {
335     showErrorMessage(/*i18n-content*/'HOST_SETUP_UPDATE_PIN_FAILED');
336   } else if (state == remoting.HostSetupFlow.State.STOP_HOST_FAILED) {
337     showErrorMessage(/*i18n-content*/'HOST_SETUP_STOP_FAILED');
338   }
342  * Shows the prompt that asks the user to install the host.
343  */
344 remoting.HostSetupDialog.prototype.installHost_ = function() {
345   /** @type {remoting.HostSetupDialog} */
346   var that = this;
347   /** @type {remoting.HostSetupFlow} */
348   var flow = this.flow_;
350   /** @param {remoting.Error} error */
351   var onError = function(error) {
352     flow.switchToErrorState(error);
353     that.updateState_();
354   };
356   var onDone = function() {
357     that.hostController_.getLocalHostState(onHostState);
358   };
360   /** @param {remoting.HostController.State} state */
361   var onHostState = function(state) {
362     var installed =
363         state != remoting.HostController.State.NOT_INSTALLED &&
364         state != remoting.HostController.State.INSTALLING;
366     if (installed) {
367       that.flow_.switchToNextStep();
368       that.updateState_();
369     } else {
370       // Prompt the user again if the host is not installed.
371       hostInstallDialog.tryAgain();
372     }
373   };
375   /** @type {remoting.HostInstallDialog} */
376   var hostInstallDialog = new remoting.HostInstallDialog();
377   hostInstallDialog.show(onDone, onError);
381  * Registers and starts the host.
382  */
383 remoting.HostSetupDialog.prototype.startHost_ = function() {
384   /** @type {remoting.HostSetupDialog} */
385   var that = this;
386   /** @type {remoting.HostSetupFlow} */
387   var flow = this.flow_;
389   /** @return {boolean} */
390   function isFlowActive() {
391     if (flow !== that.flow_ ||
392         flow.getState() != remoting.HostSetupFlow.State.STARTING_HOST) {
393       console.error('Host setup was interrupted when starting the host');
394       return false;
395     }
396     return true;
397   }
399   function onHostStarted() {
400     if (isFlowActive()) {
401       flow.switchToNextStep();
402       that.updateState_();
403     }
404   }
406   /** @param {remoting.Error} error */
407   function onError(error) {
408     if (isFlowActive()) {
409       flow.switchToErrorState(error);
410       that.updateState_();
411     }
412   }
414   this.hostController_.start(this.flow_.pin, this.flow_.consent, onHostStarted,
415                              onError);
418 remoting.HostSetupDialog.prototype.updatePin_ = function() {
419   /** @type {remoting.HostSetupDialog} */
420   var that = this;
421   /** @type {remoting.HostSetupFlow} */
422   var flow = this.flow_;
424   /** @return {boolean} */
425   function isFlowActive() {
426     if (flow !== that.flow_ ||
427         flow.getState() != remoting.HostSetupFlow.State.UPDATING_PIN) {
428       console.error('Host setup was interrupted when updating PIN');
429       return false;
430     }
431     return true;
432   }
434   function onPinUpdated() {
435     if (isFlowActive()) {
436       flow.switchToNextStep();
437       that.updateState_();
438     }
439   }
441   /** @param {remoting.Error} error */
442   function onError(error) {
443     if (isFlowActive()) {
444       flow.switchToErrorState(error);
445       that.updateState_();
446     }
447   }
449   this.hostController_.updatePin(flow.pin, onPinUpdated, onError);
453  * Stops the host.
454  */
455 remoting.HostSetupDialog.prototype.stopHost_ = function() {
456   /** @type {remoting.HostSetupDialog} */
457   var that = this;
458   /** @type {remoting.HostSetupFlow} */
459   var flow = this.flow_;
461   /** @return {boolean} */
462   function isFlowActive() {
463     if (flow !== that.flow_ ||
464         flow.getState() != remoting.HostSetupFlow.State.STOPPING_HOST) {
465       console.error('Host setup was interrupted when stopping the host');
466       return false;
467     }
468     return true;
469   }
471   function onHostStopped() {
472     if (isFlowActive()) {
473       flow.switchToNextStep();
474       that.updateState_();
475     }
476   }
478   /** @param {remoting.Error} error */
479   function onError(error) {
480     if (isFlowActive()) {
481       flow.switchToErrorState(error);
482       that.updateState_();
483     }
484   }
486   this.hostController_.stop(onHostStopped, onError);
490  * Validates the PIN and shows an error message if it's invalid.
491  * @return {boolean} true if the PIN is valid, false otherwise.
492  * @private
493  */
494 remoting.HostSetupDialog.prototype.validatePin_ = function() {
495   var pin = this.pinEntry_.value;
496   var pinIsValid = remoting.HostSetupDialog.validPin_(pin);
497   if (!pinIsValid) {
498     l10n.localizeElementFromTag(
499         this.pinErrorMessage_, /*i18n-content*/'INVALID_PIN');
500   }
501   this.pinErrorDiv_.hidden = pinIsValid;
502   return pinIsValid;
505 /** @private */
506 remoting.HostSetupDialog.prototype.onPinSubmit_ = function() {
507   if (this.flow_.getState() != remoting.HostSetupFlow.State.ASK_PIN) {
508     console.error('PIN submitted in an invalid state', this.flow_.getState());
509     return;
510   }
511   var pin1 = this.pinEntry_.value;
512   var pin2 = this.pinConfirm_.value;
513   if (pin1 != pin2) {
514     l10n.localizeElementFromTag(
515         this.pinErrorMessage_, /*i18n-content*/'PINS_NOT_EQUAL');
516     this.pinErrorDiv_.hidden = false;
517     this.prepareForPinEntry_();
518     return;
519   }
520   if (!this.validatePin_()) {
521     this.prepareForPinEntry_();
522     return;
523   }
524   this.flow_.pin = pin1;
525   this.flow_.consent = !this.usageStats_.hidden &&
526       this.usageStatsCheckbox_.checked;
527   this.flow_.switchToNextStep();
528   this.updateState_();
531 /** @private */
532 remoting.HostSetupDialog.prototype.prepareForPinEntry_ = function() {
533   this.pinEntry_.value = '';
534   this.pinConfirm_.value = '';
535   this.pinEntry_.focus();
539  * Returns whether a PIN is valid.
541  * @private
542  * @param {string} pin A PIN.
543  * @return {boolean} Whether the PIN is valid.
544  */
545 remoting.HostSetupDialog.validPin_ = function(pin) {
546   if (pin.length < 6) {
547     return false;
548   }
549   for (var i = 0; i < pin.length; i++) {
550     var c = pin.charAt(i);
551     if ((c < '0') || (c > '9')) {
552       return false;
553     }
554   }
555   return true;
558 /** @type {remoting.HostSetupDialog} */
559 remoting.hostSetupDialog = null;