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.hasTag(remoting.Error.Tag.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.hasTag(remoting.Error.Tag.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.
97 * @param {function(!remoting.Error)} onError Function to call when an error
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} */
115 /** @param {Event} event The event. */
116 var onPinSubmit = function(event) {
117 event.preventDefault();
120 var onPinConfirmFocus = function() {
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();
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.
138 if ((event.which >= 48) && (event.which <= 57)) {
141 event.preventDefault();
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.
159 remoting.HostSetupDialog.prototype.showForStart = function() {
160 /** @type {remoting.HostSetupDialog} */
164 * @param {remoting.HostController.State} state
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_));
175 this.hostController_.getLocalHostState(onState);
179 * @param {remoting.HostController.State} state The current state of the local
181 * @param {string} token The OAuth2 token.
184 remoting.HostSetupDialog.prototype.showForStartWithToken_ =
185 function(state, token) {
186 /** @type {remoting.HostSetupDialog} */
190 * @param {boolean} supported True if crash dump reporting is supported by
192 * @param {boolean} allowed True if crash dump reporting is allowed.
193 * @param {boolean} set_by_policy True if crash dump reporting is controlled
196 function onGetConsent(supported, allowed, set_by_policy) {
197 // Hide the usage stats check box if it is not supported or the policy
198 // doesn't allow usage stats collection.
199 var checkBoxLabel = that.usageStats_.querySelector('.checkbox-label');
200 that.usageStats_.hidden = !supported || (set_by_policy && !allowed);
201 that.usageStatsCheckbox_.checked = allowed;
203 that.usageStatsCheckbox_.disabled = set_by_policy;
204 checkBoxLabel.classList.toggle('disabled', set_by_policy);
207 that.usageStats_.title = l10n.getTranslationOrError(
208 /*i18n-content*/ 'SETTING_MANAGED_BY_POLICY');
210 that.usageStats_.title = '';
214 /** @param {!remoting.Error} error */
215 function onError(error) {
216 console.error('Error getting consent status: ' + error.toString());
219 this.usageStats_.hidden = true;
220 this.usageStatsCheckbox_.checked = false;
222 // Prevent user from ticking the box until the current consent status is
224 this.usageStatsCheckbox_.disabled = true;
226 this.hostController_.getConsent(onGetConsent, onError);
229 remoting.HostSetupFlow.State.INSTALL_HOST,
230 remoting.HostSetupFlow.State.ASK_PIN,
231 remoting.HostSetupFlow.State.STARTING_HOST,
232 remoting.HostSetupFlow.State.HOST_STARTED];
235 state != remoting.HostController.State.NOT_INSTALLED &&
236 state != remoting.HostController.State.UNKNOWN;
238 // Skip the installation step when the host is already installed.
243 this.startNewFlow_(flow);
247 * Show the dialog in order to change the PIN associated with a running daemon.
249 * @return {void} Nothing.
251 remoting.HostSetupDialog.prototype.showForPin = function() {
252 this.usageStats_.hidden = true;
254 [remoting.HostSetupFlow.State.ASK_PIN,
255 remoting.HostSetupFlow.State.UPDATING_PIN,
256 remoting.HostSetupFlow.State.UPDATED_PIN]);
260 * Show the dialog in order to stop the daemon.
262 * @return {void} Nothing.
264 remoting.HostSetupDialog.prototype.showForStop = function() {
265 // TODO(sergeyu): Add another step to unregister the host, crubg.com/121146 .
267 [remoting.HostSetupFlow.State.STOPPING_HOST,
268 remoting.HostSetupFlow.State.HOST_STOPPED]);
272 * @return {void} Nothing.
274 remoting.HostSetupDialog.prototype.hide = function() {
275 remoting.setMode(remoting.AppMode.HOME);
279 * Starts new flow with the specified sequence of steps.
280 * @param {Array<remoting.HostSetupFlow.State>} sequence Sequence of steps.
283 remoting.HostSetupDialog.prototype.startNewFlow_ = function(sequence) {
284 this.flow_ = new remoting.HostSetupFlow(sequence);
285 this.pinEntry_.value = '';
286 this.pinConfirm_.value = '';
287 this.pinErrorDiv_.hidden = true;
292 * Updates current UI mode according to the current state of the setup
293 * flow and start the action corresponding to the current step (if
297 remoting.HostSetupDialog.prototype.updateState_ = function() {
298 remoting.updateLocalHostState();
300 /** @param {string} tag1
301 * @param {string=} opt_tag2 */
302 function showDoneMessage(tag1, opt_tag2) {
303 var messageDiv = document.getElementById('host-setup-done-message');
304 l10n.localizeElementFromTag(messageDiv, tag1);
305 messageDiv = document.getElementById('host-setup-done-message-2');
307 l10n.localizeElementFromTag(messageDiv, opt_tag2);
309 messageDiv.innerText = '';
311 remoting.setMode(remoting.AppMode.HOST_SETUP_DONE);
313 /** @param {string} tag */
314 function showErrorMessage(tag) {
315 var errorDiv = document.getElementById('host-setup-error-message');
316 l10n.localizeElementFromTag(errorDiv, tag);
317 remoting.setMode(remoting.AppMode.HOST_SETUP_ERROR);
320 var state = this.flow_.getState();
321 if (state == remoting.HostSetupFlow.State.NONE) {
323 } else if (state == remoting.HostSetupFlow.State.ASK_PIN) {
324 remoting.setMode(remoting.AppMode.HOST_SETUP_ASK_PIN);
325 } else if (state == remoting.HostSetupFlow.State.INSTALL_HOST) {
327 } else if (state == remoting.HostSetupFlow.State.STARTING_HOST) {
328 remoting.showSetupProcessingMessage(/*i18n-content*/'HOST_SETUP_STARTING');
330 } else if (state == remoting.HostSetupFlow.State.UPDATING_PIN) {
331 remoting.showSetupProcessingMessage(
332 /*i18n-content*/'HOST_SETUP_UPDATING_PIN');
334 } else if (state == remoting.HostSetupFlow.State.STOPPING_HOST) {
335 remoting.showSetupProcessingMessage(/*i18n-content*/'HOST_SETUP_STOPPING');
337 } else if (state == remoting.HostSetupFlow.State.HOST_STARTED) {
338 // TODO(jamiewalch): Only display the second string if the computer's power
339 // management settings indicate that it's necessary.
340 showDoneMessage(/*i18n-content*/'HOST_SETUP_STARTED',
341 /*i18n-content*/'HOST_SETUP_STARTED_DISABLE_SLEEP');
342 } else if (state == remoting.HostSetupFlow.State.UPDATED_PIN) {
343 showDoneMessage(/*i18n-content*/'HOST_SETUP_UPDATED_PIN');
344 } else if (state == remoting.HostSetupFlow.State.HOST_STOPPED) {
345 showDoneMessage(/*i18n-content*/'HOST_SETUP_STOPPED');
346 } else if (state == remoting.HostSetupFlow.State.REGISTRATION_FAILED) {
347 showErrorMessage(/*i18n-content*/'ERROR_HOST_REGISTRATION_FAILED');
348 } else if (state == remoting.HostSetupFlow.State.START_HOST_FAILED) {
349 showErrorMessage(/*i18n-content*/'HOST_SETUP_HOST_FAILED');
350 } else if (state == remoting.HostSetupFlow.State.UPDATE_PIN_FAILED) {
351 showErrorMessage(/*i18n-content*/'HOST_SETUP_UPDATE_PIN_FAILED');
352 } else if (state == remoting.HostSetupFlow.State.STOP_HOST_FAILED) {
353 showErrorMessage(/*i18n-content*/'HOST_SETUP_STOP_FAILED');
358 * Shows the prompt that asks the user to install the host.
360 remoting.HostSetupDialog.prototype.installHost_ = function() {
361 /** @type {remoting.HostSetupDialog} */
363 /** @type {remoting.HostSetupFlow} */
364 var flow = this.flow_;
366 /** @param {!remoting.Error} error */
367 var onError = function(error) {
368 flow.switchToErrorState(error);
372 var onDone = function() {
373 that.hostController_.getLocalHostState(onHostState);
376 /** @param {remoting.HostController.State} state */
377 var onHostState = function(state) {
379 state != remoting.HostController.State.NOT_INSTALLED &&
380 state != remoting.HostController.State.UNKNOWN;
383 that.flow_.switchToNextStep();
386 // Prompt the user again if the host is not installed.
387 hostInstallDialog.tryAgain();
391 /** @type {remoting.HostInstallDialog} */
392 var hostInstallDialog = new remoting.HostInstallDialog();
393 hostInstallDialog.show(onDone, onError);
397 * Registers and starts the host.
399 remoting.HostSetupDialog.prototype.startHost_ = function() {
400 /** @type {remoting.HostSetupDialog} */
402 /** @type {remoting.HostSetupFlow} */
403 var flow = this.flow_;
405 /** @return {boolean} */
406 function isFlowActive() {
407 if (flow !== that.flow_ ||
408 flow.getState() != remoting.HostSetupFlow.State.STARTING_HOST) {
409 console.error('Host setup was interrupted when starting the host');
415 function onHostStarted() {
416 if (isFlowActive()) {
417 flow.switchToNextStep();
422 /** @param {!remoting.Error} error */
423 function onError(error) {
424 if (isFlowActive()) {
425 flow.switchToErrorState(error);
430 this.hostController_.start(this.flow_.pin, this.flow_.consent, onHostStarted,
434 remoting.HostSetupDialog.prototype.updatePin_ = function() {
435 /** @type {remoting.HostSetupDialog} */
437 /** @type {remoting.HostSetupFlow} */
438 var flow = this.flow_;
440 /** @return {boolean} */
441 function isFlowActive() {
442 if (flow !== that.flow_ ||
443 flow.getState() != remoting.HostSetupFlow.State.UPDATING_PIN) {
444 console.error('Host setup was interrupted when updating PIN');
450 function onPinUpdated() {
451 if (isFlowActive()) {
452 flow.switchToNextStep();
457 /** @param {!remoting.Error} error */
458 function onError(error) {
459 if (isFlowActive()) {
460 flow.switchToErrorState(error);
465 this.hostController_.updatePin(flow.pin, onPinUpdated, onError);
471 remoting.HostSetupDialog.prototype.stopHost_ = function() {
472 /** @type {remoting.HostSetupDialog} */
474 /** @type {remoting.HostSetupFlow} */
475 var flow = this.flow_;
477 /** @return {boolean} */
478 function isFlowActive() {
479 if (flow !== that.flow_ ||
480 flow.getState() != remoting.HostSetupFlow.State.STOPPING_HOST) {
481 console.error('Host setup was interrupted when stopping the host');
487 function onHostStopped() {
488 if (isFlowActive()) {
489 flow.switchToNextStep();
494 /** @param {!remoting.Error} error */
495 function onError(error) {
496 if (isFlowActive()) {
497 flow.switchToErrorState(error);
502 this.hostController_.stop(onHostStopped, onError);
506 * Validates the PIN and shows an error message if it's invalid.
507 * @return {boolean} true if the PIN is valid, false otherwise.
510 remoting.HostSetupDialog.prototype.validatePin_ = function() {
511 var pin = this.pinEntry_.value;
512 var pinIsValid = remoting.HostSetupDialog.validPin_(pin);
514 l10n.localizeElementFromTag(
515 this.pinErrorMessage_, /*i18n-content*/'INVALID_PIN');
517 this.pinErrorDiv_.hidden = pinIsValid;
522 remoting.HostSetupDialog.prototype.onPinSubmit_ = function() {
523 if (this.flow_.getState() != remoting.HostSetupFlow.State.ASK_PIN) {
524 console.error('PIN submitted in an invalid state', this.flow_.getState());
527 var pin1 = this.pinEntry_.value;
528 var pin2 = this.pinConfirm_.value;
530 l10n.localizeElementFromTag(
531 this.pinErrorMessage_, /*i18n-content*/'PINS_NOT_EQUAL');
532 this.pinErrorDiv_.hidden = false;
533 this.prepareForPinEntry_();
536 if (!this.validatePin_()) {
537 this.prepareForPinEntry_();
540 this.flow_.pin = pin1;
541 this.flow_.consent = !this.usageStats_.hidden &&
542 this.usageStatsCheckbox_.checked;
543 this.flow_.switchToNextStep();
548 remoting.HostSetupDialog.prototype.prepareForPinEntry_ = function() {
549 this.pinEntry_.value = '';
550 this.pinConfirm_.value = '';
551 this.pinEntry_.focus();
555 * Returns whether a PIN is valid.
558 * @param {string} pin A PIN.
559 * @return {boolean} Whether the PIN is valid.
561 remoting.HostSetupDialog.validPin_ = function(pin) {
562 if (pin.length < 6) {
565 for (var i = 0; i < pin.length; i++) {
566 var c = pin.charAt(i);
567 if ((c < '0') || (c > '9')) {
574 /** @type {remoting.HostSetupDialog} */
575 remoting.hostSetupDialog = null;