Roll src/third_party/WebKit 3529d49:06e8485 (svn 202554:202555)
[chromium-blink-merge.git] / remoting / webapp / crd / js / host_setup_dialog.js
blobdf0b7e32970e47d5c2bd0c6ad13168196dfaf115
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.hasTag(remoting.Error.Tag.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.hasTag(remoting.Error.Tag.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  * @param {function(!remoting.Error)} onError Function to call when an error
98  *     occurs.
99  * @constructor
100  */
101 remoting.HostSetupDialog = function(hostController, onError) {
102   this.hostController_ = hostController;
103   this.onError_ = onError;
105   this.pinEntry_ = document.getElementById('daemon-pin-entry');
106   this.pinConfirm_ = document.getElementById('daemon-pin-confirm');
107   this.pinErrorDiv_ = document.getElementById('daemon-pin-error-div');
108   this.pinErrorMessage_ = document.getElementById('daemon-pin-error-message');
110   /** @type {remoting.HostSetupFlow} */
111   this.flow_ = new remoting.HostSetupFlow([remoting.HostSetupFlow.State.NONE]);
113   /** @type {remoting.HostSetupDialog} */
114   var that = this;
115   /** @param {Event} event The event. */
116   var onPinSubmit = function(event) {
117     event.preventDefault();
118     that.onPinSubmit_();
119   };
120   var onPinConfirmFocus = function() {
121     that.validatePin_();
122   };
124   var form = document.getElementById('ask-pin-form');
125   form.addEventListener('submit', onPinSubmit, false);
126   /** @param {Event} event The event. */
127   var onDaemonPinEntryKeyPress = function(event) {
128     if (event.which == 13) {
129       document.getElementById('daemon-pin-confirm').focus();
130       event.preventDefault();
131     }
132   };
133   /** @param {Event} event A keypress event. */
134   var noDigitsInPin = function(event) {
135     if (event.which == 13) {
136       return;  // Otherwise the "submit" action can't be triggered by Enter.
137     }
138     if ((event.which >= 48) && (event.which <= 57)) {
139       return;
140     }
141     event.preventDefault();
142   };
143   this.pinEntry_.addEventListener('keypress', onDaemonPinEntryKeyPress, false);
144   this.pinEntry_.addEventListener('keypress', noDigitsInPin, false);
145   this.pinConfirm_.addEventListener('focus', onPinConfirmFocus, false);
146   this.pinConfirm_.addEventListener('keypress', noDigitsInPin, false);
148   this.usageStats_ = document.getElementById('usagestats-consent');
149   this.usageStatsCheckbox_ = /** @type {HTMLInputElement} */
150       (document.getElementById('usagestats-consent-checkbox'));
154  * Show the dialog in order to get a PIN prior to starting the daemon. When the
155  * user clicks OK, the dialog shows a spinner until the daemon has started.
157  * @return {void} Nothing.
158  */
159 remoting.HostSetupDialog.prototype.showForStart = function() {
160   /** @type {remoting.HostSetupDialog} */
161   var that = this;
163   /**
164    * @param {remoting.HostController.State} state
165    */
166   var onState = function(state) {
167     // Although we don't need an access token in order to start the host,
168     // using callWithToken here ensures consistent error handling in the
169     // case where the refresh token is invalid.
170     remoting.identity.getToken().then(
171         that.showForStartWithToken_.bind(that, state),
172         remoting.Error.handler(that.onError_));
173   };
175   this.hostController_.getLocalHostState(onState);
179  * @param {remoting.HostController.State} state The current state of the local
180  *     host.
181  * @param {string} token The OAuth2 token.
182  * @private
183  */
184 remoting.HostSetupDialog.prototype.showForStartWithToken_ =
185     function(state, token) {
186   /** @type {remoting.HostSetupDialog} */
187   var that = this;
189   /**
190    * @param {remoting.UsageStatsConsent} consent
191    */
192   function onGetConsent(consent) {
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 = !consent.supported ||
197         (consent.setByPolicy && !consent.allowed);
198     that.usageStatsCheckbox_.checked = consent.allowed;
200     that.usageStatsCheckbox_.disabled = consent.setByPolicy;
201     checkBoxLabel.classList.toggle('disabled', consent.setByPolicy);
203     if (consent.setByPolicy) {
204       that.usageStats_.title = l10n.getTranslationOrError(
205           /*i18n-content*/ 'SETTING_MANAGED_BY_POLICY');
206     } else {
207       that.usageStats_.title = '';
208     }
209   }
211   /** @param {!remoting.Error} error */
212   function onError(error) {
213     console.error('Error getting consent status: ' + error.toString());
214   }
216   this.usageStats_.hidden = true;
217   this.usageStatsCheckbox_.checked = false;
219   // Prevent user from ticking the box until the current consent status is
220   // known.
221   this.usageStatsCheckbox_.disabled = true;
223   this.hostController_.getConsent().then(
224       onGetConsent, remoting.Error.handler(onError));
226   var flow = [
227       remoting.HostSetupFlow.State.INSTALL_HOST,
228       remoting.HostSetupFlow.State.ASK_PIN,
229       remoting.HostSetupFlow.State.STARTING_HOST,
230       remoting.HostSetupFlow.State.HOST_STARTED];
232   var installed =
233       state != remoting.HostController.State.NOT_INSTALLED &&
234       state != remoting.HostController.State.UNKNOWN;
236   // Skip the installation step when the host is already installed.
237   if (installed) {
238     flow.shift();
239   }
241   this.startNewFlow_(flow);
245  * Show the dialog in order to change the PIN associated with a running daemon.
247  * @return {void} Nothing.
248  */
249 remoting.HostSetupDialog.prototype.showForPin = function() {
250   this.usageStats_.hidden = true;
251   this.startNewFlow_(
252       [remoting.HostSetupFlow.State.ASK_PIN,
253        remoting.HostSetupFlow.State.UPDATING_PIN,
254        remoting.HostSetupFlow.State.UPDATED_PIN]);
258  * Show the dialog in order to stop the daemon.
260  * @return {void} Nothing.
261  */
262 remoting.HostSetupDialog.prototype.showForStop = function() {
263   // TODO(sergeyu): Add another step to unregister the host, crubg.com/121146 .
264   this.startNewFlow_(
265       [remoting.HostSetupFlow.State.STOPPING_HOST,
266        remoting.HostSetupFlow.State.HOST_STOPPED]);
270  * @return {void} Nothing.
271  */
272 remoting.HostSetupDialog.prototype.hide = function() {
273   remoting.setMode(remoting.AppMode.HOME);
277  * Starts new flow with the specified sequence of steps.
278  * @param {Array<remoting.HostSetupFlow.State>} sequence Sequence of steps.
279  * @private
280  */
281 remoting.HostSetupDialog.prototype.startNewFlow_ = function(sequence) {
282   this.flow_ = new remoting.HostSetupFlow(sequence);
283   this.pinEntry_.value = '';
284   this.pinConfirm_.value = '';
285   this.pinErrorDiv_.hidden = true;
286   this.updateState_();
290  * Updates current UI mode according to the current state of the setup
291  * flow and start the action corresponding to the current step (if
292  * any).
293  * @private
294  */
295 remoting.HostSetupDialog.prototype.updateState_ = function() {
296   remoting.updateLocalHostState();
298   /** @param {string} tag1
299    *  @param {string=} opt_tag2 */
300   function showDoneMessage(tag1, opt_tag2) {
301     var messageDiv = document.getElementById('host-setup-done-message');
302     l10n.localizeElementFromTag(messageDiv, tag1);
303     messageDiv = document.getElementById('host-setup-done-message-2');
304     if (opt_tag2) {
305       l10n.localizeElementFromTag(messageDiv, opt_tag2);
306     } else {
307       messageDiv.innerText = '';
308     }
309     remoting.setMode(remoting.AppMode.HOST_SETUP_DONE);
310   }
311   /** @param {string} tag */
312   function showErrorMessage(tag) {
313     var errorDiv = document.getElementById('host-setup-error-message');
314     l10n.localizeElementFromTag(errorDiv, tag);
315     remoting.setMode(remoting.AppMode.HOST_SETUP_ERROR);
316   }
318   var state = this.flow_.getState();
319   if (state == remoting.HostSetupFlow.State.NONE) {
320     this.hide();
321   } else if (state == remoting.HostSetupFlow.State.ASK_PIN) {
322     remoting.setMode(remoting.AppMode.HOST_SETUP_ASK_PIN);
323   } else if (state == remoting.HostSetupFlow.State.INSTALL_HOST) {
324     this.installHost_();
325   } else if (state == remoting.HostSetupFlow.State.STARTING_HOST) {
326     remoting.showSetupProcessingMessage(/*i18n-content*/'HOST_SETUP_STARTING');
327     this.startHost_();
328   } else if (state == remoting.HostSetupFlow.State.UPDATING_PIN) {
329     remoting.showSetupProcessingMessage(
330         /*i18n-content*/'HOST_SETUP_UPDATING_PIN');
331     this.updatePin_();
332   } else if (state == remoting.HostSetupFlow.State.STOPPING_HOST) {
333     remoting.showSetupProcessingMessage(/*i18n-content*/'HOST_SETUP_STOPPING');
334     this.stopHost_();
335   } else if (state == remoting.HostSetupFlow.State.HOST_STARTED) {
336     // TODO(jamiewalch): Only display the second string if the computer's power
337     // management settings indicate that it's necessary.
338     showDoneMessage(/*i18n-content*/'HOST_SETUP_STARTED',
339                     /*i18n-content*/'HOST_SETUP_STARTED_DISABLE_SLEEP');
340   } else if (state == remoting.HostSetupFlow.State.UPDATED_PIN) {
341     showDoneMessage(/*i18n-content*/'HOST_SETUP_UPDATED_PIN');
342   } else if (state == remoting.HostSetupFlow.State.HOST_STOPPED) {
343     showDoneMessage(/*i18n-content*/'HOST_SETUP_STOPPED');
344   } else if (state == remoting.HostSetupFlow.State.REGISTRATION_FAILED) {
345     showErrorMessage(/*i18n-content*/'ERROR_HOST_REGISTRATION_FAILED');
346   } else if (state == remoting.HostSetupFlow.State.START_HOST_FAILED) {
347     showErrorMessage(/*i18n-content*/'HOST_SETUP_HOST_FAILED');
348   } else if (state == remoting.HostSetupFlow.State.UPDATE_PIN_FAILED) {
349     showErrorMessage(/*i18n-content*/'HOST_SETUP_UPDATE_PIN_FAILED');
350   } else if (state == remoting.HostSetupFlow.State.STOP_HOST_FAILED) {
351     showErrorMessage(/*i18n-content*/'HOST_SETUP_STOP_FAILED');
352   }
356  * Shows the prompt that asks the user to install the host.
357  */
358 remoting.HostSetupDialog.prototype.installHost_ = function() {
359   /** @type {remoting.HostSetupDialog} */
360   var that = this;
361   /** @type {remoting.HostSetupFlow} */
362   var flow = this.flow_;
364   /** @param {!remoting.Error} error */
365   var onError = function(error) {
366     flow.switchToErrorState(error);
367     that.updateState_();
368   };
370   var onDone = function() {
371     that.hostController_.getLocalHostState(onHostState);
372   };
374   /** @param {remoting.HostController.State} state */
375   var onHostState = function(state) {
376     var installed =
377         state != remoting.HostController.State.NOT_INSTALLED &&
378         state != remoting.HostController.State.UNKNOWN;
380     if (installed) {
381       that.flow_.switchToNextStep();
382       that.updateState_();
383     } else {
384       // Prompt the user again if the host is not installed.
385       hostInstallDialog.tryAgain();
386     }
387   };
389   /** @type {remoting.HostInstallDialog} */
390   var hostInstallDialog = new remoting.HostInstallDialog();
391   hostInstallDialog.show(onDone, onError);
395  * Registers and starts the host.
396  */
397 remoting.HostSetupDialog.prototype.startHost_ = function() {
398   /** @type {remoting.HostSetupDialog} */
399   var that = this;
400   /** @type {remoting.HostSetupFlow} */
401   var flow = this.flow_;
403   /** @return {boolean} */
404   function isFlowActive() {
405     if (flow !== that.flow_ ||
406         flow.getState() != remoting.HostSetupFlow.State.STARTING_HOST) {
407       console.error('Host setup was interrupted when starting the host');
408       return false;
409     }
410     return true;
411   }
413   function onHostStarted() {
414     if (isFlowActive()) {
415       flow.switchToNextStep();
416       that.updateState_();
417     }
418   }
420   /** @param {!remoting.Error} error */
421   function onError(error) {
422     if (isFlowActive()) {
423       flow.switchToErrorState(error);
424       that.updateState_();
425     }
426   }
428   this.hostController_.start(this.flow_.pin, this.flow_.consent).then(
429       onHostStarted
430   ).catch(
431       remoting.Error.handler(onError)
432   );
435 remoting.HostSetupDialog.prototype.updatePin_ = function() {
436   /** @type {remoting.HostSetupDialog} */
437   var that = this;
438   /** @type {remoting.HostSetupFlow} */
439   var flow = this.flow_;
441   /** @return {boolean} */
442   function isFlowActive() {
443     if (flow !== that.flow_ ||
444         flow.getState() != remoting.HostSetupFlow.State.UPDATING_PIN) {
445       console.error('Host setup was interrupted when updating PIN');
446       return false;
447     }
448     return true;
449   }
451   function onPinUpdated() {
452     if (isFlowActive()) {
453       flow.switchToNextStep();
454       that.updateState_();
455     }
456   }
458   /** @param {!remoting.Error} error */
459   function onError(error) {
460     if (isFlowActive()) {
461       flow.switchToErrorState(error);
462       that.updateState_();
463     }
464   }
466   this.hostController_.updatePin(flow.pin, onPinUpdated, onError);
470  * Stops the host.
471  */
472 remoting.HostSetupDialog.prototype.stopHost_ = function() {
473   /** @type {remoting.HostSetupDialog} */
474   var that = this;
475   /** @type {remoting.HostSetupFlow} */
476   var flow = this.flow_;
478   /** @return {boolean} */
479   function isFlowActive() {
480     if (flow !== that.flow_ ||
481         flow.getState() != remoting.HostSetupFlow.State.STOPPING_HOST) {
482       console.error('Host setup was interrupted when stopping the host');
483       return false;
484     }
485     return true;
486   }
488   function onHostStopped() {
489     if (isFlowActive()) {
490       flow.switchToNextStep();
491       that.updateState_();
492     }
493   }
495   /** @param {!remoting.Error} error */
496   function onError(error) {
497     if (isFlowActive()) {
498       flow.switchToErrorState(error);
499       that.updateState_();
500     }
501   }
503   this.hostController_.stop(onHostStopped, onError);
507  * Validates the PIN and shows an error message if it's invalid.
508  * @return {boolean} true if the PIN is valid, false otherwise.
509  * @private
510  */
511 remoting.HostSetupDialog.prototype.validatePin_ = function() {
512   var pin = this.pinEntry_.value;
513   var pinIsValid = remoting.HostSetupDialog.validPin_(pin);
514   if (!pinIsValid) {
515     l10n.localizeElementFromTag(
516         this.pinErrorMessage_, /*i18n-content*/'INVALID_PIN');
517   }
518   this.pinErrorDiv_.hidden = pinIsValid;
519   return pinIsValid;
522 /** @private */
523 remoting.HostSetupDialog.prototype.onPinSubmit_ = function() {
524   if (this.flow_.getState() != remoting.HostSetupFlow.State.ASK_PIN) {
525     console.error('PIN submitted in an invalid state', this.flow_.getState());
526     return;
527   }
528   var pin1 = this.pinEntry_.value;
529   var pin2 = this.pinConfirm_.value;
530   if (pin1 != pin2) {
531     l10n.localizeElementFromTag(
532         this.pinErrorMessage_, /*i18n-content*/'PINS_NOT_EQUAL');
533     this.pinErrorDiv_.hidden = false;
534     this.prepareForPinEntry_();
535     return;
536   }
537   if (!this.validatePin_()) {
538     this.prepareForPinEntry_();
539     return;
540   }
541   this.flow_.pin = pin1;
542   this.flow_.consent = !this.usageStats_.hidden &&
543       this.usageStatsCheckbox_.checked;
544   this.flow_.switchToNextStep();
545   this.updateState_();
548 /** @private */
549 remoting.HostSetupDialog.prototype.prepareForPinEntry_ = function() {
550   this.pinEntry_.value = '';
551   this.pinConfirm_.value = '';
552   this.pinEntry_.focus();
556  * Returns whether a PIN is valid.
558  * @private
559  * @param {string} pin A PIN.
560  * @return {boolean} Whether the PIN is valid.
561  */
562 remoting.HostSetupDialog.validPin_ = function(pin) {
563   if (pin.length < 6) {
564     return false;
565   }
566   for (var i = 0; i < pin.length; i++) {
567     var c = pin.charAt(i);
568     if ((c < '0') || (c > '9')) {
569       return false;
570     }
571   }
572   return true;
575 /** @type {remoting.HostSetupDialog} */
576 remoting.hostSetupDialog = null;