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.callWithToken(
167 that.showForStartWithToken_.bind(that, state),
168 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 that.usageStats_.hidden = !supported;
194 that.usageStatsCheckbox_.checked = allowed;
195 that.usageStatsCheckbox_.disabled = set_by_policy;
198 /** @param {remoting.Error} error */
199 function onError(error) {
200 console.error('Error getting consent status: ' + error);
203 this.usageStats_.hidden = false;
204 this.usageStatsCheckbox_.checked = false;
206 // Prevent user from ticking the box until the current consent status is
208 this.usageStatsCheckbox_.disabled = true;
210 this.hostController_.getConsent(onGetConsent, onError);
213 remoting.HostSetupFlow.State.INSTALL_HOST,
214 remoting.HostSetupFlow.State.ASK_PIN,
215 remoting.HostSetupFlow.State.STARTING_HOST,
216 remoting.HostSetupFlow.State.HOST_STARTED];
219 state != remoting.HostController.State.NOT_INSTALLED &&
220 state != remoting.HostController.State.INSTALLING;
222 // Skip the installation step when the host is already installed.
227 this.startNewFlow_(flow);
231 * Show the dialog in order to change the PIN associated with a running daemon.
233 * @return {void} Nothing.
235 remoting.HostSetupDialog.prototype.showForPin = function() {
236 this.usageStats_.hidden = true;
238 [remoting.HostSetupFlow.State.ASK_PIN,
239 remoting.HostSetupFlow.State.UPDATING_PIN,
240 remoting.HostSetupFlow.State.UPDATED_PIN]);
244 * Show the dialog in order to stop the daemon.
246 * @return {void} Nothing.
248 remoting.HostSetupDialog.prototype.showForStop = function() {
249 // TODO(sergeyu): Add another step to unregister the host, crubg.com/121146 .
251 [remoting.HostSetupFlow.State.STOPPING_HOST,
252 remoting.HostSetupFlow.State.HOST_STOPPED]);
256 * @return {void} Nothing.
258 remoting.HostSetupDialog.prototype.hide = function() {
259 remoting.setMode(remoting.AppMode.HOME);
263 * Starts new flow with the specified sequence of steps.
264 * @param {Array.<remoting.HostSetupFlow.State>} sequence Sequence of steps.
267 remoting.HostSetupDialog.prototype.startNewFlow_ = function(sequence) {
268 this.flow_ = new remoting.HostSetupFlow(sequence);
269 this.pinEntry_.value = '';
270 this.pinConfirm_.value = '';
271 this.pinErrorDiv_.hidden = true;
276 * Updates current UI mode according to the current state of the setup
277 * flow and start the action corresponding to the current step (if
281 remoting.HostSetupDialog.prototype.updateState_ = function() {
282 remoting.updateLocalHostState();
284 /** @param {string} tag1
285 * @param {string=} opt_tag2 */
286 function showDoneMessage(tag1, opt_tag2) {
287 var messageDiv = document.getElementById('host-setup-done-message');
288 l10n.localizeElementFromTag(messageDiv, tag1);
289 messageDiv = document.getElementById('host-setup-done-message-2');
291 l10n.localizeElementFromTag(messageDiv, opt_tag2);
293 messageDiv.innerText = '';
295 remoting.setMode(remoting.AppMode.HOST_SETUP_DONE);
297 /** @param {string} tag */
298 function showErrorMessage(tag) {
299 var errorDiv = document.getElementById('host-setup-error-message');
300 l10n.localizeElementFromTag(errorDiv, tag);
301 remoting.setMode(remoting.AppMode.HOST_SETUP_ERROR);
304 var state = this.flow_.getState();
305 if (state == remoting.HostSetupFlow.State.NONE) {
307 } else if (state == remoting.HostSetupFlow.State.ASK_PIN) {
308 remoting.setMode(remoting.AppMode.HOST_SETUP_ASK_PIN);
309 } else if (state == remoting.HostSetupFlow.State.INSTALL_HOST) {
311 } else if (state == remoting.HostSetupFlow.State.STARTING_HOST) {
312 remoting.showSetupProcessingMessage(/*i18n-content*/'HOST_SETUP_STARTING');
314 } else if (state == remoting.HostSetupFlow.State.UPDATING_PIN) {
315 remoting.showSetupProcessingMessage(
316 /*i18n-content*/'HOST_SETUP_UPDATING_PIN');
318 } else if (state == remoting.HostSetupFlow.State.STOPPING_HOST) {
319 remoting.showSetupProcessingMessage(/*i18n-content*/'HOST_SETUP_STOPPING');
321 } else if (state == remoting.HostSetupFlow.State.HOST_STARTED) {
322 // TODO(jamiewalch): Only display the second string if the computer's power
323 // management settings indicate that it's necessary.
324 showDoneMessage(/*i18n-content*/'HOST_SETUP_STARTED',
325 /*i18n-content*/'HOST_SETUP_STARTED_DISABLE_SLEEP');
326 } else if (state == remoting.HostSetupFlow.State.UPDATED_PIN) {
327 showDoneMessage(/*i18n-content*/'HOST_SETUP_UPDATED_PIN');
328 } else if (state == remoting.HostSetupFlow.State.HOST_STOPPED) {
329 showDoneMessage(/*i18n-content*/'HOST_SETUP_STOPPED');
330 } else if (state == remoting.HostSetupFlow.State.REGISTRATION_FAILED) {
331 showErrorMessage(/*i18n-content*/'ERROR_HOST_REGISTRATION_FAILED');
332 } else if (state == remoting.HostSetupFlow.State.START_HOST_FAILED) {
333 showErrorMessage(/*i18n-content*/'HOST_SETUP_HOST_FAILED');
334 } else if (state == remoting.HostSetupFlow.State.UPDATE_PIN_FAILED) {
335 showErrorMessage(/*i18n-content*/'HOST_SETUP_UPDATE_PIN_FAILED');
336 } else if (state == remoting.HostSetupFlow.State.STOP_HOST_FAILED) {
337 showErrorMessage(/*i18n-content*/'HOST_SETUP_STOP_FAILED');
342 * Shows the prompt that asks the user to install the host.
344 remoting.HostSetupDialog.prototype.installHost_ = function() {
345 /** @type {remoting.HostSetupDialog} */
347 /** @type {remoting.HostSetupFlow} */
348 var flow = this.flow_;
350 /** @param {remoting.Error} error */
351 var onError = function(error) {
352 flow.switchToErrorState(error);
356 var onDone = function() {
357 that.hostController_.getLocalHostState(onHostState);
360 /** @param {remoting.HostController.State} state */
361 var onHostState = function(state) {
363 state != remoting.HostController.State.NOT_INSTALLED &&
364 state != remoting.HostController.State.INSTALLING;
367 that.flow_.switchToNextStep();
370 // Prompt the user again if the host is not installed.
371 hostInstallDialog.tryAgain();
375 /** @type {remoting.HostInstallDialog} */
376 var hostInstallDialog = new remoting.HostInstallDialog();
377 hostInstallDialog.show(onDone, onError);
381 * Registers and starts the host.
383 remoting.HostSetupDialog.prototype.startHost_ = function() {
384 /** @type {remoting.HostSetupDialog} */
386 /** @type {remoting.HostSetupFlow} */
387 var flow = this.flow_;
389 /** @return {boolean} */
390 function isFlowActive() {
391 if (flow !== that.flow_ ||
392 flow.getState() != remoting.HostSetupFlow.State.STARTING_HOST) {
393 console.error('Host setup was interrupted when starting the host');
399 function onHostStarted() {
400 if (isFlowActive()) {
401 flow.switchToNextStep();
406 /** @param {remoting.Error} error */
407 function onError(error) {
408 if (isFlowActive()) {
409 flow.switchToErrorState(error);
414 this.hostController_.start(this.flow_.pin, this.flow_.consent, onHostStarted,
418 remoting.HostSetupDialog.prototype.updatePin_ = function() {
419 /** @type {remoting.HostSetupDialog} */
421 /** @type {remoting.HostSetupFlow} */
422 var flow = this.flow_;
424 /** @return {boolean} */
425 function isFlowActive() {
426 if (flow !== that.flow_ ||
427 flow.getState() != remoting.HostSetupFlow.State.UPDATING_PIN) {
428 console.error('Host setup was interrupted when updating PIN');
434 function onPinUpdated() {
435 if (isFlowActive()) {
436 flow.switchToNextStep();
441 /** @param {remoting.Error} error */
442 function onError(error) {
443 if (isFlowActive()) {
444 flow.switchToErrorState(error);
449 this.hostController_.updatePin(flow.pin, onPinUpdated, onError);
455 remoting.HostSetupDialog.prototype.stopHost_ = function() {
456 /** @type {remoting.HostSetupDialog} */
458 /** @type {remoting.HostSetupFlow} */
459 var flow = this.flow_;
461 /** @return {boolean} */
462 function isFlowActive() {
463 if (flow !== that.flow_ ||
464 flow.getState() != remoting.HostSetupFlow.State.STOPPING_HOST) {
465 console.error('Host setup was interrupted when stopping the host');
471 function onHostStopped() {
472 if (isFlowActive()) {
473 flow.switchToNextStep();
478 /** @param {remoting.Error} error */
479 function onError(error) {
480 if (isFlowActive()) {
481 flow.switchToErrorState(error);
486 this.hostController_.stop(onHostStopped, onError);
490 * Validates the PIN and shows an error message if it's invalid.
491 * @return {boolean} true if the PIN is valid, false otherwise.
494 remoting.HostSetupDialog.prototype.validatePin_ = function() {
495 var pin = this.pinEntry_.value;
496 var pinIsValid = remoting.HostSetupDialog.validPin_(pin);
498 l10n.localizeElementFromTag(
499 this.pinErrorMessage_, /*i18n-content*/'INVALID_PIN');
501 this.pinErrorDiv_.hidden = pinIsValid;
506 remoting.HostSetupDialog.prototype.onPinSubmit_ = function() {
507 if (this.flow_.getState() != remoting.HostSetupFlow.State.ASK_PIN) {
508 console.error('PIN submitted in an invalid state', this.flow_.getState());
511 var pin1 = this.pinEntry_.value;
512 var pin2 = this.pinConfirm_.value;
514 l10n.localizeElementFromTag(
515 this.pinErrorMessage_, /*i18n-content*/'PINS_NOT_EQUAL');
516 this.pinErrorDiv_.hidden = false;
517 this.prepareForPinEntry_();
520 if (!this.validatePin_()) {
521 this.prepareForPinEntry_();
524 this.flow_.pin = pin1;
525 this.flow_.consent = !this.usageStats_.hidden &&
526 this.usageStatsCheckbox_.checked;
527 this.flow_.switchToNextStep();
532 remoting.HostSetupDialog.prototype.prepareForPinEntry_ = function() {
533 this.pinEntry_.value = '';
534 this.pinConfirm_.value = '';
535 this.pinEntry_.focus();
539 * Returns whether a PIN is valid.
542 * @param {string} pin A PIN.
543 * @return {boolean} Whether the PIN is valid.
545 remoting.HostSetupDialog.validPin_ = function(pin) {
546 if (pin.length < 6) {
549 for (var i = 0; i < pin.length; i++) {
550 var c = pin.charAt(i);
551 if ((c < '0') || (c > '9')) {
558 /** @type {remoting.HostSetupDialog} */
559 remoting.hostSetupDialog = null;