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 {remoting.UsageStatsConsent} consent
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');
207 that.usageStats_.title = '';
211 /** @param {!remoting.Error} error */
212 function onError(error) {
213 console.error('Error getting consent status: ' + error.toString());
216 this.usageStats_.hidden = true;
217 this.usageStatsCheckbox_.checked = false;
219 // Prevent user from ticking the box until the current consent status is
221 this.usageStatsCheckbox_.disabled = true;
223 this.hostController_.getConsent().then(
224 onGetConsent, remoting.Error.handler(onError));
227 remoting.HostSetupFlow.State.INSTALL_HOST,
228 remoting.HostSetupFlow.State.ASK_PIN,
229 remoting.HostSetupFlow.State.STARTING_HOST,
230 remoting.HostSetupFlow.State.HOST_STARTED];
233 state != remoting.HostController.State.NOT_INSTALLED &&
234 state != remoting.HostController.State.UNKNOWN;
236 // Skip the installation step when the host is already installed.
241 this.startNewFlow_(flow);
245 * Show the dialog in order to change the PIN associated with a running daemon.
247 * @return {void} Nothing.
249 remoting.HostSetupDialog.prototype.showForPin = function() {
250 this.usageStats_.hidden = true;
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.
262 remoting.HostSetupDialog.prototype.showForStop = function() {
263 // TODO(sergeyu): Add another step to unregister the host, crubg.com/121146 .
265 [remoting.HostSetupFlow.State.STOPPING_HOST,
266 remoting.HostSetupFlow.State.HOST_STOPPED]);
270 * @return {void} Nothing.
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.
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;
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
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');
305 l10n.localizeElementFromTag(messageDiv, opt_tag2);
307 messageDiv.innerText = '';
309 remoting.setMode(remoting.AppMode.HOST_SETUP_DONE);
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);
318 var state = this.flow_.getState();
319 if (state == remoting.HostSetupFlow.State.NONE) {
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) {
325 } else if (state == remoting.HostSetupFlow.State.STARTING_HOST) {
326 remoting.showSetupProcessingMessage(/*i18n-content*/'HOST_SETUP_STARTING');
328 } else if (state == remoting.HostSetupFlow.State.UPDATING_PIN) {
329 remoting.showSetupProcessingMessage(
330 /*i18n-content*/'HOST_SETUP_UPDATING_PIN');
332 } else if (state == remoting.HostSetupFlow.State.STOPPING_HOST) {
333 remoting.showSetupProcessingMessage(/*i18n-content*/'HOST_SETUP_STOPPING');
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');
356 * Shows the prompt that asks the user to install the host.
358 remoting.HostSetupDialog.prototype.installHost_ = function() {
359 /** @type {remoting.HostSetupDialog} */
361 /** @type {remoting.HostSetupFlow} */
362 var flow = this.flow_;
364 /** @param {!remoting.Error} error */
365 var onError = function(error) {
366 flow.switchToErrorState(error);
370 var onDone = function() {
371 that.hostController_.getLocalHostState(onHostState);
374 /** @param {remoting.HostController.State} state */
375 var onHostState = function(state) {
377 state != remoting.HostController.State.NOT_INSTALLED &&
378 state != remoting.HostController.State.UNKNOWN;
381 that.flow_.switchToNextStep();
384 // Prompt the user again if the host is not installed.
385 hostInstallDialog.tryAgain();
389 /** @type {remoting.HostInstallDialog} */
390 var hostInstallDialog = new remoting.HostInstallDialog();
391 hostInstallDialog.show(onDone, onError);
395 * Registers and starts the host.
397 remoting.HostSetupDialog.prototype.startHost_ = function() {
398 /** @type {remoting.HostSetupDialog} */
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');
413 function onHostStarted() {
414 if (isFlowActive()) {
415 flow.switchToNextStep();
420 /** @param {!remoting.Error} error */
421 function onError(error) {
422 if (isFlowActive()) {
423 flow.switchToErrorState(error);
428 this.hostController_.start(this.flow_.pin, this.flow_.consent).then(
431 remoting.Error.handler(onError)
435 remoting.HostSetupDialog.prototype.updatePin_ = function() {
436 /** @type {remoting.HostSetupDialog} */
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');
451 function onPinUpdated() {
452 if (isFlowActive()) {
453 flow.switchToNextStep();
458 /** @param {!remoting.Error} error */
459 function onError(error) {
460 if (isFlowActive()) {
461 flow.switchToErrorState(error);
466 this.hostController_.updatePin(flow.pin, onPinUpdated, onError);
472 remoting.HostSetupDialog.prototype.stopHost_ = function() {
473 /** @type {remoting.HostSetupDialog} */
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');
488 function onHostStopped() {
489 if (isFlowActive()) {
490 flow.switchToNextStep();
495 /** @param {!remoting.Error} error */
496 function onError(error) {
497 if (isFlowActive()) {
498 flow.switchToErrorState(error);
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.
511 remoting.HostSetupDialog.prototype.validatePin_ = function() {
512 var pin = this.pinEntry_.value;
513 var pinIsValid = remoting.HostSetupDialog.validPin_(pin);
515 l10n.localizeElementFromTag(
516 this.pinErrorMessage_, /*i18n-content*/'INVALID_PIN');
518 this.pinErrorDiv_.hidden = pinIsValid;
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());
528 var pin1 = this.pinEntry_.value;
529 var pin2 = this.pinConfirm_.value;
531 l10n.localizeElementFromTag(
532 this.pinErrorMessage_, /*i18n-content*/'PINS_NOT_EQUAL');
533 this.pinErrorDiv_.hidden = false;
534 this.prepareForPinEntry_();
537 if (!this.validatePin_()) {
538 this.prepareForPinEntry_();
541 this.flow_.pin = pin1;
542 this.flow_.consent = !this.usageStats_.hidden &&
543 this.usageStatsCheckbox_.checked;
544 this.flow_.switchToNextStep();
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.
559 * @param {string} pin A PIN.
560 * @return {boolean} Whether the PIN is valid.
562 remoting.HostSetupDialog.validPin_ = function(pin) {
563 if (pin.length < 6) {
566 for (var i = 0; i < pin.length; i++) {
567 var c = pin.charAt(i);
568 if ((c < '0') || (c > '9')) {
575 /** @type {remoting.HostSetupDialog} */
576 remoting.hostSetupDialog = null;