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.
7 /** @suppress {duplicate} */
8 var remoting = remoting || {};
11 * @param {Array<remoting.HostSetupFlow.State>} sequence Sequence of
15 remoting.HostSetupFlow = function(sequence) {
16 this.sequence_ = sequence;
17 this.currentStep_ = 0;
18 this.state_ = sequence[0];
24 remoting.HostSetupFlow.State = {
30 // Prompts the user to install the host package.
44 REGISTRATION_FAILED: 9,
45 START_HOST_FAILED: 10,
46 UPDATE_PIN_FAILED: 11,
50 /** @return {remoting.HostSetupFlow.State} Current state of the flow. */
51 remoting.HostSetupFlow.prototype.getState = function() {
55 remoting.HostSetupFlow.prototype.switchToNextStep = function() {
56 if (this.state_ == remoting.HostSetupFlow.State.NONE) {
60 if (this.currentStep_ < this.sequence_.length - 1) {
61 this.currentStep_ += 1;
62 this.state_ = this.sequence_[this.currentStep_];
64 this.state_ = remoting.HostSetupFlow.State.NONE;
69 * @param {remoting.Error} error
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;
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;
81 this.state_ = remoting.HostSetupFlow.State.START_HOST_FAILED;
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;
88 // TODO(sergeyu): Add other error states and use them here.
89 this.state_ = remoting.HostSetupFlow.State.START_HOST_FAILED;
95 * @param {remoting.HostController} hostController The HostController
96 * responsible for the host daemon.
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} */
111 /** @param {Event} event The event. */
112 var onPinSubmit = function(event) {
113 event.preventDefault();
116 var onPinConfirmFocus = function() {
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();
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.
134 if ((event.which >= 48) && (event.which <= 57)) {
137 event.preventDefault();
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.
155 remoting.HostSetupDialog.prototype.showForStart = function() {
156 /** @type {remoting.HostSetupDialog} */
160 * @param {remoting.HostController.State} state
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));
171 this.hostController_.getLocalHostState(onState);
175 * @param {remoting.HostController.State} state The current state of the local
177 * @param {string} token The OAuth2 token.
180 remoting.HostSetupDialog.prototype.showForStartWithToken_ =
181 function(state, token) {
182 /** @type {remoting.HostSetupDialog} */
186 * @param {boolean} supported True if crash dump reporting is supported by
188 * @param {boolean} allowed True if crash dump reporting is allowed.
189 * @param {boolean} set_by_policy True if crash dump reporting is controlled
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);
203 that.usageStats_.title = l10n.getTranslationOrError(
204 /*i18n-content*/ 'SETTING_MANAGED_BY_POLICY');
206 that.usageStats_.title = '';
210 /** @param {remoting.Error} error */
211 function onError(error) {
212 console.error('Error getting consent status: ' + error);
215 this.usageStats_.hidden = true;
216 this.usageStatsCheckbox_.checked = false;
218 // Prevent user from ticking the box until the current consent status is
220 this.usageStatsCheckbox_.disabled = true;
222 this.hostController_.getConsent(onGetConsent, onError);
225 remoting.HostSetupFlow.State.INSTALL_HOST,
226 remoting.HostSetupFlow.State.ASK_PIN,
227 remoting.HostSetupFlow.State.STARTING_HOST,
228 remoting.HostSetupFlow.State.HOST_STARTED];
231 state != remoting.HostController.State.NOT_INSTALLED &&
232 state != remoting.HostController.State.UNKNOWN;
234 // Skip the installation step when the host is already installed.
239 this.startNewFlow_(flow);
243 * Show the dialog in order to change the PIN associated with a running daemon.
245 * @return {void} Nothing.
247 remoting.HostSetupDialog.prototype.showForPin = function() {
248 this.usageStats_.hidden = true;
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.
260 remoting.HostSetupDialog.prototype.showForStop = function() {
261 // TODO(sergeyu): Add another step to unregister the host, crubg.com/121146 .
263 [remoting.HostSetupFlow.State.STOPPING_HOST,
264 remoting.HostSetupFlow.State.HOST_STOPPED]);
268 * @return {void} Nothing.
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.
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;
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
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');
303 l10n.localizeElementFromTag(messageDiv, opt_tag2);
305 messageDiv.innerText = '';
307 remoting.setMode(remoting.AppMode.HOST_SETUP_DONE);
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);
316 var state = this.flow_.getState();
317 if (state == remoting.HostSetupFlow.State.NONE) {
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) {
323 } else if (state == remoting.HostSetupFlow.State.STARTING_HOST) {
324 remoting.showSetupProcessingMessage(/*i18n-content*/'HOST_SETUP_STARTING');
326 } else if (state == remoting.HostSetupFlow.State.UPDATING_PIN) {
327 remoting.showSetupProcessingMessage(
328 /*i18n-content*/'HOST_SETUP_UPDATING_PIN');
330 } else if (state == remoting.HostSetupFlow.State.STOPPING_HOST) {
331 remoting.showSetupProcessingMessage(/*i18n-content*/'HOST_SETUP_STOPPING');
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');
354 * Shows the prompt that asks the user to install the host.
356 remoting.HostSetupDialog.prototype.installHost_ = function() {
357 /** @type {remoting.HostSetupDialog} */
359 /** @type {remoting.HostSetupFlow} */
360 var flow = this.flow_;
362 /** @param {remoting.Error} error */
363 var onError = function(error) {
364 flow.switchToErrorState(error);
368 var onDone = function() {
369 that.hostController_.getLocalHostState(onHostState);
372 /** @param {remoting.HostController.State} state */
373 var onHostState = function(state) {
375 state != remoting.HostController.State.NOT_INSTALLED &&
376 state != remoting.HostController.State.UNKNOWN;
379 that.flow_.switchToNextStep();
382 // Prompt the user again if the host is not installed.
383 hostInstallDialog.tryAgain();
387 /** @type {remoting.HostInstallDialog} */
388 var hostInstallDialog = new remoting.HostInstallDialog();
389 hostInstallDialog.show(onDone, onError);
393 * Registers and starts the host.
395 remoting.HostSetupDialog.prototype.startHost_ = function() {
396 /** @type {remoting.HostSetupDialog} */
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');
411 function onHostStarted() {
412 if (isFlowActive()) {
413 flow.switchToNextStep();
418 /** @param {remoting.Error} error */
419 function onError(error) {
420 if (isFlowActive()) {
421 flow.switchToErrorState(error);
426 this.hostController_.start(this.flow_.pin, this.flow_.consent, onHostStarted,
430 remoting.HostSetupDialog.prototype.updatePin_ = function() {
431 /** @type {remoting.HostSetupDialog} */
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');
446 function onPinUpdated() {
447 if (isFlowActive()) {
448 flow.switchToNextStep();
453 /** @param {remoting.Error} error */
454 function onError(error) {
455 if (isFlowActive()) {
456 flow.switchToErrorState(error);
461 this.hostController_.updatePin(flow.pin, onPinUpdated, onError);
467 remoting.HostSetupDialog.prototype.stopHost_ = function() {
468 /** @type {remoting.HostSetupDialog} */
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');
483 function onHostStopped() {
484 if (isFlowActive()) {
485 flow.switchToNextStep();
490 /** @param {remoting.Error} error */
491 function onError(error) {
492 if (isFlowActive()) {
493 flow.switchToErrorState(error);
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.
506 remoting.HostSetupDialog.prototype.validatePin_ = function() {
507 var pin = this.pinEntry_.value;
508 var pinIsValid = remoting.HostSetupDialog.validPin_(pin);
510 l10n.localizeElementFromTag(
511 this.pinErrorMessage_, /*i18n-content*/'INVALID_PIN');
513 this.pinErrorDiv_.hidden = pinIsValid;
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());
523 var pin1 = this.pinEntry_.value;
524 var pin2 = this.pinConfirm_.value;
526 l10n.localizeElementFromTag(
527 this.pinErrorMessage_, /*i18n-content*/'PINS_NOT_EQUAL');
528 this.pinErrorDiv_.hidden = false;
529 this.prepareForPinEntry_();
532 if (!this.validatePin_()) {
533 this.prepareForPinEntry_();
536 this.flow_.pin = pin1;
537 this.flow_.consent = !this.usageStats_.hidden &&
538 this.usageStatsCheckbox_.checked;
539 this.flow_.switchToNextStep();
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.
554 * @param {string} pin A PIN.
555 * @return {boolean} Whether the PIN is valid.
557 remoting.HostSetupDialog.validPin_ = function(pin) {
558 if (pin.length < 6) {
561 for (var i = 0; i < pin.length; i++) {
562 var c = pin.charAt(i);
563 if ((c < '0') || (c > '9')) {
570 /** @type {remoting.HostSetupDialog} */
571 remoting.hostSetupDialog = null;