[Password Generation] Don't generate passwords for custom passphrase users.
[chromium-blink-merge.git] / remoting / webapp / host_setup_dialog.js
blob800770b5af62839f838b78a3e1a83fcb123c4b52
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 or when using
223   // NPAPI plugin on Windows (because on Windows the plugin takes care of
224   // installation).
225   if (installed || (navigator.platform == 'Win32' &&
226                     this.hostController_.usingNpapiPlugin())) {
227     flow.shift();
228   }
230   this.startNewFlow_(flow);
234  * Show the dialog in order to change the PIN associated with a running daemon.
236  * @return {void} Nothing.
237  */
238 remoting.HostSetupDialog.prototype.showForPin = function() {
239   this.usageStats_.hidden = true;
240   this.startNewFlow_(
241       [remoting.HostSetupFlow.State.ASK_PIN,
242        remoting.HostSetupFlow.State.UPDATING_PIN,
243        remoting.HostSetupFlow.State.UPDATED_PIN]);
247  * Show the dialog in order to stop the daemon.
249  * @return {void} Nothing.
250  */
251 remoting.HostSetupDialog.prototype.showForStop = function() {
252   // TODO(sergeyu): Add another step to unregister the host, crubg.com/121146 .
253   this.startNewFlow_(
254       [remoting.HostSetupFlow.State.STOPPING_HOST,
255        remoting.HostSetupFlow.State.HOST_STOPPED]);
259  * @return {void} Nothing.
260  */
261 remoting.HostSetupDialog.prototype.hide = function() {
262   remoting.setMode(remoting.AppMode.HOME);
266  * Starts new flow with the specified sequence of steps.
267  * @param {Array.<remoting.HostSetupFlow.State>} sequence Sequence of steps.
268  * @private
269  */
270 remoting.HostSetupDialog.prototype.startNewFlow_ = function(sequence) {
271   this.flow_ = new remoting.HostSetupFlow(sequence);
272   this.pinEntry_.value = '';
273   this.pinConfirm_.value = '';
274   this.pinErrorDiv_.hidden = true;
275   this.updateState_();
279  * Updates current UI mode according to the current state of the setup
280  * flow and start the action corresponding to the current step (if
281  * any).
282  * @private
283  */
284 remoting.HostSetupDialog.prototype.updateState_ = function() {
285   remoting.updateLocalHostState();
287   /** @param {string} tag */
288   function showProcessingMessage(tag) {
289     var messageDiv = document.getElementById('host-setup-processing-message');
290     l10n.localizeElementFromTag(messageDiv, tag);
291     remoting.setMode(remoting.AppMode.HOST_SETUP_PROCESSING);
292   }
293   /** @param {string} tag1
294    *  @param {string=} opt_tag2 */
295   function showDoneMessage(tag1, opt_tag2) {
296     var messageDiv = document.getElementById('host-setup-done-message');
297     l10n.localizeElementFromTag(messageDiv, tag1);
298     messageDiv = document.getElementById('host-setup-done-message-2');
299     if (opt_tag2) {
300       l10n.localizeElementFromTag(messageDiv, opt_tag2);
301     } else {
302       messageDiv.innerText = '';
303     }
304     remoting.setMode(remoting.AppMode.HOST_SETUP_DONE);
305   }
306   /** @param {string} tag */
307   function showErrorMessage(tag) {
308     var errorDiv = document.getElementById('host-setup-error-message');
309     l10n.localizeElementFromTag(errorDiv, tag);
310     remoting.setMode(remoting.AppMode.HOST_SETUP_ERROR);
311   }
313   var state = this.flow_.getState();
314   if (state == remoting.HostSetupFlow.State.NONE) {
315     this.hide();
316   } else if (state == remoting.HostSetupFlow.State.ASK_PIN) {
317     remoting.setMode(remoting.AppMode.HOST_SETUP_ASK_PIN);
318   } else if (state == remoting.HostSetupFlow.State.INSTALL_HOST) {
319     this.installHost_();
320   } else if (state == remoting.HostSetupFlow.State.STARTING_HOST) {
321     showProcessingMessage(/*i18n-content*/'HOST_SETUP_STARTING');
322     this.startHost_();
323   } else if (state == remoting.HostSetupFlow.State.UPDATING_PIN) {
324     showProcessingMessage(/*i18n-content*/'HOST_SETUP_UPDATING_PIN');
325     this.updatePin_();
326   } else if (state == remoting.HostSetupFlow.State.STOPPING_HOST) {
327     showProcessingMessage(/*i18n-content*/'HOST_SETUP_STOPPING');
328     this.stopHost_();
329   } else if (state == remoting.HostSetupFlow.State.HOST_STARTED) {
330     // TODO(jamiewalch): Only display the second string if the computer's power
331     // management settings indicate that it's necessary.
332     showDoneMessage(/*i18n-content*/'HOST_SETUP_STARTED',
333                     /*i18n-content*/'HOST_SETUP_STARTED_DISABLE_SLEEP');
334   } else if (state == remoting.HostSetupFlow.State.UPDATED_PIN) {
335     showDoneMessage(/*i18n-content*/'HOST_SETUP_UPDATED_PIN');
336   } else if (state == remoting.HostSetupFlow.State.HOST_STOPPED) {
337     showDoneMessage(/*i18n-content*/'HOST_SETUP_STOPPED');
338   } else if (state == remoting.HostSetupFlow.State.REGISTRATION_FAILED) {
339     showErrorMessage(/*i18n-content*/'ERROR_HOST_REGISTRATION_FAILED');
340   } else if (state == remoting.HostSetupFlow.State.START_HOST_FAILED) {
341     showErrorMessage(/*i18n-content*/'HOST_SETUP_HOST_FAILED');
342   } else if (state == remoting.HostSetupFlow.State.UPDATE_PIN_FAILED) {
343     showErrorMessage(/*i18n-content*/'HOST_SETUP_UPDATE_PIN_FAILED');
344   } else if (state == remoting.HostSetupFlow.State.STOP_HOST_FAILED) {
345     showErrorMessage(/*i18n-content*/'HOST_SETUP_STOP_FAILED');
346   }
350  * Shows the prompt that asks the user to install the host.
351  */
352 remoting.HostSetupDialog.prototype.installHost_ = function() {
353   /** @type {remoting.HostSetupDialog} */
354   var that = this;
355   /** @type {remoting.HostSetupFlow} */
356   var flow = this.flow_;
358   var onDone = function() {
359     that.hostController_.getLocalHostState(onHostState);
360   };
362   /** @param {remoting.Error} error */
363   var onError = function(error) {
364     flow.switchToErrorState(error);
365     that.updateState_();
366   }
368   /** @param {remoting.HostController.State} state */
369   var onHostState = function(state) {
370     // Verify if the host has been installed. If not then try to prompt the user
371     // again.
372     var installed =
373         state != remoting.HostController.State.NOT_INSTALLED &&
374         state != remoting.HostController.State.INSTALLING;
375     if (installed) {
376       that.flow_.switchToNextStep();
377       that.updateState_();
378     } else {
379       hostInstallDialog.tryAgain();
380     }
381   }
383   /** @type {remoting.HostInstallDialog} */
384   var hostInstallDialog = new remoting.HostInstallDialog();
385   hostInstallDialog.show(onDone, onError);
389  * Registers and starts the host.
390  */
391 remoting.HostSetupDialog.prototype.startHost_ = function() {
392   /** @type {remoting.HostSetupDialog} */
393   var that = this;
394   /** @type {remoting.HostSetupFlow} */
395   var flow = this.flow_;
397   /** @return {boolean} */
398   function isFlowActive() {
399     if (flow !== that.flow_ ||
400         flow.getState() != remoting.HostSetupFlow.State.STARTING_HOST) {
401       console.error('Host setup was interrupted when starting the host');
402       return false;
403     }
404     return true;
405   }
407   function onHostStarted() {
408     if (isFlowActive()) {
409       flow.switchToNextStep();
410       that.updateState_();
411     }
412   }
414   /** @param {remoting.Error} error */
415   function onError(error) {
416     if (isFlowActive()) {
417       flow.switchToErrorState(error);
418       that.updateState_();
419     }
420   }
422   this.hostController_.start(this.flow_.pin, this.flow_.consent, onHostStarted,
423                              onError);
426 remoting.HostSetupDialog.prototype.updatePin_ = function() {
427   /** @type {remoting.HostSetupDialog} */
428   var that = this;
429   /** @type {remoting.HostSetupFlow} */
430   var flow = this.flow_;
432   /** @return {boolean} */
433   function isFlowActive() {
434     if (flow !== that.flow_ ||
435         flow.getState() != remoting.HostSetupFlow.State.UPDATING_PIN) {
436       console.error('Host setup was interrupted when updating PIN');
437       return false;
438     }
439     return true;
440   }
442   function onPinUpdated() {
443     if (isFlowActive()) {
444       flow.switchToNextStep();
445       that.updateState_();
446     }
447   }
449   /** @param {remoting.Error} error */
450   function onError(error) {
451     if (isFlowActive()) {
452       flow.switchToErrorState(error);
453       that.updateState_();
454     }
455   }
457   this.hostController_.updatePin(flow.pin, onPinUpdated, onError);
461  * Stops the host.
462  */
463 remoting.HostSetupDialog.prototype.stopHost_ = function() {
464   /** @type {remoting.HostSetupDialog} */
465   var that = this;
466   /** @type {remoting.HostSetupFlow} */
467   var flow = this.flow_;
469   /** @return {boolean} */
470   function isFlowActive() {
471     if (flow !== that.flow_ ||
472         flow.getState() != remoting.HostSetupFlow.State.STOPPING_HOST) {
473       console.error('Host setup was interrupted when stopping the host');
474       return false;
475     }
476     return true;
477   }
479   function onHostStopped() {
480     if (isFlowActive()) {
481       flow.switchToNextStep();
482       that.updateState_();
483     }
484   }
486   /** @param {remoting.Error} error */
487   function onError(error) {
488     if (isFlowActive()) {
489       flow.switchToErrorState(error);
490       that.updateState_();
491     }
492   }
494   this.hostController_.stop(onHostStopped, onError);
498  * Validates the PIN and shows an error message if it's invalid.
499  * @return {boolean} true if the PIN is valid, false otherwise.
500  * @private
501  */
502 remoting.HostSetupDialog.prototype.validatePin_ = function() {
503   var pin = this.pinEntry_.value;
504   var pinIsValid = remoting.HostSetupDialog.validPin_(pin);
505   if (!pinIsValid) {
506     l10n.localizeElementFromTag(
507         this.pinErrorMessage_, /*i18n-content*/'INVALID_PIN');
508   }
509   this.pinErrorDiv_.hidden = pinIsValid;
510   return pinIsValid;
513 /** @private */
514 remoting.HostSetupDialog.prototype.onPinSubmit_ = function() {
515   if (this.flow_.getState() != remoting.HostSetupFlow.State.ASK_PIN) {
516     console.error('PIN submitted in an invalid state', this.flow_.getState());
517     return;
518   }
519   var pin1 = this.pinEntry_.value;
520   var pin2 = this.pinConfirm_.value;
521   if (pin1 != pin2) {
522     l10n.localizeElementFromTag(
523         this.pinErrorMessage_, /*i18n-content*/'PINS_NOT_EQUAL');
524     this.pinErrorDiv_.hidden = false;
525     this.prepareForPinEntry_();
526     return;
527   }
528   if (!this.validatePin_()) {
529     this.prepareForPinEntry_();
530     return;
531   }
532   this.flow_.pin = pin1;
533   this.flow_.consent = !this.usageStats_.hidden &&
534       this.usageStatsCheckbox_.checked;
535   this.flow_.switchToNextStep();
536   this.updateState_();
539 /** @private */
540 remoting.HostSetupDialog.prototype.prepareForPinEntry_ = function() {
541   this.pinEntry_.value = '';
542   this.pinConfirm_.value = '';
543   this.pinEntry_.focus();
547  * Returns whether a PIN is valid.
549  * @private
550  * @param {string} pin A PIN.
551  * @return {boolean} Whether the PIN is valid.
552  */
553 remoting.HostSetupDialog.validPin_ = function(pin) {
554   if (pin.length < 6) {
555     return false;
556   }
557   for (var i = 0; i < pin.length; i++) {
558     var c = pin.charAt(i);
559     if ((c < '0') || (c > '9')) {
560       return false;
561     }
562   }
563   return true;
566 /** @type {remoting.HostSetupDialog} */
567 remoting.hostSetupDialog = null;