Rewrite AndroidSyncSettings to be significantly simpler.
[chromium-blink-merge.git] / remoting / webapp / crd / js / host_setup_dialog.js
blob072aff6642aee000ae569ddc9310c425e129d61a
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.getToken().then(
167         that.showForStartWithToken_.bind(that, state),
168         remoting.Error.handler(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     // Hide the usage stats check box if it is not supported or the policy
194     // doesn't allow usage stats collection.
195     var checkBoxLabel = that.usageStats_.querySelector('.checkbox-label');
196     that.usageStats_.hidden = !supported || (set_by_policy && !allowed);
197     that.usageStatsCheckbox_.checked = allowed;
199     that.usageStatsCheckbox_.disabled = set_by_policy;
200     checkBoxLabel.classList.toggle('disabled', set_by_policy);
202     if (set_by_policy) {
203       that.usageStats_.title = l10n.getTranslationOrError(
204           /*i18n-content*/ 'SETTING_MANAGED_BY_POLICY');
205     } else {
206       that.usageStats_.title = '';
207     }
208   }
210   /** @param {remoting.Error} error */
211   function onError(error) {
212     console.error('Error getting consent status: ' + error);
213   }
215   this.usageStats_.hidden = true;
216   this.usageStatsCheckbox_.checked = false;
218   // Prevent user from ticking the box until the current consent status is
219   // known.
220   this.usageStatsCheckbox_.disabled = true;
222   this.hostController_.getConsent(onGetConsent, onError);
224   var flow = [
225       remoting.HostSetupFlow.State.INSTALL_HOST,
226       remoting.HostSetupFlow.State.ASK_PIN,
227       remoting.HostSetupFlow.State.STARTING_HOST,
228       remoting.HostSetupFlow.State.HOST_STARTED];
230   var installed =
231       state != remoting.HostController.State.NOT_INSTALLED &&
232       state != remoting.HostController.State.UNKNOWN;
234   // Skip the installation step when the host is already installed.
235   if (installed) {
236     flow.shift();
237   }
239   this.startNewFlow_(flow);
243  * Show the dialog in order to change the PIN associated with a running daemon.
245  * @return {void} Nothing.
246  */
247 remoting.HostSetupDialog.prototype.showForPin = function() {
248   this.usageStats_.hidden = true;
249   this.startNewFlow_(
250       [remoting.HostSetupFlow.State.ASK_PIN,
251        remoting.HostSetupFlow.State.UPDATING_PIN,
252        remoting.HostSetupFlow.State.UPDATED_PIN]);
256  * Show the dialog in order to stop the daemon.
258  * @return {void} Nothing.
259  */
260 remoting.HostSetupDialog.prototype.showForStop = function() {
261   // TODO(sergeyu): Add another step to unregister the host, crubg.com/121146 .
262   this.startNewFlow_(
263       [remoting.HostSetupFlow.State.STOPPING_HOST,
264        remoting.HostSetupFlow.State.HOST_STOPPED]);
268  * @return {void} Nothing.
269  */
270 remoting.HostSetupDialog.prototype.hide = function() {
271   remoting.setMode(remoting.AppMode.HOME);
275  * Starts new flow with the specified sequence of steps.
276  * @param {Array<remoting.HostSetupFlow.State>} sequence Sequence of steps.
277  * @private
278  */
279 remoting.HostSetupDialog.prototype.startNewFlow_ = function(sequence) {
280   this.flow_ = new remoting.HostSetupFlow(sequence);
281   this.pinEntry_.value = '';
282   this.pinConfirm_.value = '';
283   this.pinErrorDiv_.hidden = true;
284   this.updateState_();
288  * Updates current UI mode according to the current state of the setup
289  * flow and start the action corresponding to the current step (if
290  * any).
291  * @private
292  */
293 remoting.HostSetupDialog.prototype.updateState_ = function() {
294   remoting.updateLocalHostState();
296   /** @param {string} tag1
297    *  @param {string=} opt_tag2 */
298   function showDoneMessage(tag1, opt_tag2) {
299     var messageDiv = document.getElementById('host-setup-done-message');
300     l10n.localizeElementFromTag(messageDiv, tag1);
301     messageDiv = document.getElementById('host-setup-done-message-2');
302     if (opt_tag2) {
303       l10n.localizeElementFromTag(messageDiv, opt_tag2);
304     } else {
305       messageDiv.innerText = '';
306     }
307     remoting.setMode(remoting.AppMode.HOST_SETUP_DONE);
308   }
309   /** @param {string} tag */
310   function showErrorMessage(tag) {
311     var errorDiv = document.getElementById('host-setup-error-message');
312     l10n.localizeElementFromTag(errorDiv, tag);
313     remoting.setMode(remoting.AppMode.HOST_SETUP_ERROR);
314   }
316   var state = this.flow_.getState();
317   if (state == remoting.HostSetupFlow.State.NONE) {
318     this.hide();
319   } else if (state == remoting.HostSetupFlow.State.ASK_PIN) {
320     remoting.setMode(remoting.AppMode.HOST_SETUP_ASK_PIN);
321   } else if (state == remoting.HostSetupFlow.State.INSTALL_HOST) {
322     this.installHost_();
323   } else if (state == remoting.HostSetupFlow.State.STARTING_HOST) {
324     remoting.showSetupProcessingMessage(/*i18n-content*/'HOST_SETUP_STARTING');
325     this.startHost_();
326   } else if (state == remoting.HostSetupFlow.State.UPDATING_PIN) {
327     remoting.showSetupProcessingMessage(
328         /*i18n-content*/'HOST_SETUP_UPDATING_PIN');
329     this.updatePin_();
330   } else if (state == remoting.HostSetupFlow.State.STOPPING_HOST) {
331     remoting.showSetupProcessingMessage(/*i18n-content*/'HOST_SETUP_STOPPING');
332     this.stopHost_();
333   } else if (state == remoting.HostSetupFlow.State.HOST_STARTED) {
334     // TODO(jamiewalch): Only display the second string if the computer's power
335     // management settings indicate that it's necessary.
336     showDoneMessage(/*i18n-content*/'HOST_SETUP_STARTED',
337                     /*i18n-content*/'HOST_SETUP_STARTED_DISABLE_SLEEP');
338   } else if (state == remoting.HostSetupFlow.State.UPDATED_PIN) {
339     showDoneMessage(/*i18n-content*/'HOST_SETUP_UPDATED_PIN');
340   } else if (state == remoting.HostSetupFlow.State.HOST_STOPPED) {
341     showDoneMessage(/*i18n-content*/'HOST_SETUP_STOPPED');
342   } else if (state == remoting.HostSetupFlow.State.REGISTRATION_FAILED) {
343     showErrorMessage(/*i18n-content*/'ERROR_HOST_REGISTRATION_FAILED');
344   } else if (state == remoting.HostSetupFlow.State.START_HOST_FAILED) {
345     showErrorMessage(/*i18n-content*/'HOST_SETUP_HOST_FAILED');
346   } else if (state == remoting.HostSetupFlow.State.UPDATE_PIN_FAILED) {
347     showErrorMessage(/*i18n-content*/'HOST_SETUP_UPDATE_PIN_FAILED');
348   } else if (state == remoting.HostSetupFlow.State.STOP_HOST_FAILED) {
349     showErrorMessage(/*i18n-content*/'HOST_SETUP_STOP_FAILED');
350   }
354  * Shows the prompt that asks the user to install the host.
355  */
356 remoting.HostSetupDialog.prototype.installHost_ = function() {
357   /** @type {remoting.HostSetupDialog} */
358   var that = this;
359   /** @type {remoting.HostSetupFlow} */
360   var flow = this.flow_;
362   /** @param {remoting.Error} error */
363   var onError = function(error) {
364     flow.switchToErrorState(error);
365     that.updateState_();
366   };
368   var onDone = function() {
369     that.hostController_.getLocalHostState(onHostState);
370   };
372   /** @param {remoting.HostController.State} state */
373   var onHostState = function(state) {
374     var installed =
375         state != remoting.HostController.State.NOT_INSTALLED &&
376         state != remoting.HostController.State.UNKNOWN;
378     if (installed) {
379       that.flow_.switchToNextStep();
380       that.updateState_();
381     } else {
382       // Prompt the user again if the host is not installed.
383       hostInstallDialog.tryAgain();
384     }
385   };
387   /** @type {remoting.HostInstallDialog} */
388   var hostInstallDialog = new remoting.HostInstallDialog();
389   hostInstallDialog.show(onDone, onError);
393  * Registers and starts the host.
394  */
395 remoting.HostSetupDialog.prototype.startHost_ = function() {
396   /** @type {remoting.HostSetupDialog} */
397   var that = this;
398   /** @type {remoting.HostSetupFlow} */
399   var flow = this.flow_;
401   /** @return {boolean} */
402   function isFlowActive() {
403     if (flow !== that.flow_ ||
404         flow.getState() != remoting.HostSetupFlow.State.STARTING_HOST) {
405       console.error('Host setup was interrupted when starting the host');
406       return false;
407     }
408     return true;
409   }
411   function onHostStarted() {
412     if (isFlowActive()) {
413       flow.switchToNextStep();
414       that.updateState_();
415     }
416   }
418   /** @param {remoting.Error} error */
419   function onError(error) {
420     if (isFlowActive()) {
421       flow.switchToErrorState(error);
422       that.updateState_();
423     }
424   }
426   this.hostController_.start(this.flow_.pin, this.flow_.consent, onHostStarted,
427                              onError);
430 remoting.HostSetupDialog.prototype.updatePin_ = function() {
431   /** @type {remoting.HostSetupDialog} */
432   var that = this;
433   /** @type {remoting.HostSetupFlow} */
434   var flow = this.flow_;
436   /** @return {boolean} */
437   function isFlowActive() {
438     if (flow !== that.flow_ ||
439         flow.getState() != remoting.HostSetupFlow.State.UPDATING_PIN) {
440       console.error('Host setup was interrupted when updating PIN');
441       return false;
442     }
443     return true;
444   }
446   function onPinUpdated() {
447     if (isFlowActive()) {
448       flow.switchToNextStep();
449       that.updateState_();
450     }
451   }
453   /** @param {remoting.Error} error */
454   function onError(error) {
455     if (isFlowActive()) {
456       flow.switchToErrorState(error);
457       that.updateState_();
458     }
459   }
461   this.hostController_.updatePin(flow.pin, onPinUpdated, onError);
465  * Stops the host.
466  */
467 remoting.HostSetupDialog.prototype.stopHost_ = function() {
468   /** @type {remoting.HostSetupDialog} */
469   var that = this;
470   /** @type {remoting.HostSetupFlow} */
471   var flow = this.flow_;
473   /** @return {boolean} */
474   function isFlowActive() {
475     if (flow !== that.flow_ ||
476         flow.getState() != remoting.HostSetupFlow.State.STOPPING_HOST) {
477       console.error('Host setup was interrupted when stopping the host');
478       return false;
479     }
480     return true;
481   }
483   function onHostStopped() {
484     if (isFlowActive()) {
485       flow.switchToNextStep();
486       that.updateState_();
487     }
488   }
490   /** @param {remoting.Error} error */
491   function onError(error) {
492     if (isFlowActive()) {
493       flow.switchToErrorState(error);
494       that.updateState_();
495     }
496   }
498   this.hostController_.stop(onHostStopped, onError);
502  * Validates the PIN and shows an error message if it's invalid.
503  * @return {boolean} true if the PIN is valid, false otherwise.
504  * @private
505  */
506 remoting.HostSetupDialog.prototype.validatePin_ = function() {
507   var pin = this.pinEntry_.value;
508   var pinIsValid = remoting.HostSetupDialog.validPin_(pin);
509   if (!pinIsValid) {
510     l10n.localizeElementFromTag(
511         this.pinErrorMessage_, /*i18n-content*/'INVALID_PIN');
512   }
513   this.pinErrorDiv_.hidden = pinIsValid;
514   return pinIsValid;
517 /** @private */
518 remoting.HostSetupDialog.prototype.onPinSubmit_ = function() {
519   if (this.flow_.getState() != remoting.HostSetupFlow.State.ASK_PIN) {
520     console.error('PIN submitted in an invalid state', this.flow_.getState());
521     return;
522   }
523   var pin1 = this.pinEntry_.value;
524   var pin2 = this.pinConfirm_.value;
525   if (pin1 != pin2) {
526     l10n.localizeElementFromTag(
527         this.pinErrorMessage_, /*i18n-content*/'PINS_NOT_EQUAL');
528     this.pinErrorDiv_.hidden = false;
529     this.prepareForPinEntry_();
530     return;
531   }
532   if (!this.validatePin_()) {
533     this.prepareForPinEntry_();
534     return;
535   }
536   this.flow_.pin = pin1;
537   this.flow_.consent = !this.usageStats_.hidden &&
538       this.usageStatsCheckbox_.checked;
539   this.flow_.switchToNextStep();
540   this.updateState_();
543 /** @private */
544 remoting.HostSetupDialog.prototype.prepareForPinEntry_ = function() {
545   this.pinEntry_.value = '';
546   this.pinConfirm_.value = '';
547   this.pinEntry_.focus();
551  * Returns whether a PIN is valid.
553  * @private
554  * @param {string} pin A PIN.
555  * @return {boolean} Whether the PIN is valid.
556  */
557 remoting.HostSetupDialog.validPin_ = function(pin) {
558   if (pin.length < 6) {
559     return false;
560   }
561   for (var i = 0; i < pin.length; i++) {
562     var c = pin.charAt(i);
563     if ((c < '0') || (c > '9')) {
564       return false;
565     }
566   }
567   return true;
570 /** @type {remoting.HostSetupDialog} */
571 remoting.hostSetupDialog = null;