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 or when using
223 // NPAPI plugin on Windows (because on Windows the plugin takes care of
225 if (installed || (navigator.platform == 'Win32' &&
226 this.hostController_.usingNpapiPlugin())) {
230 this.startNewFlow_(flow);
234 * Show the dialog in order to change the PIN associated with a running daemon.
236 * @return {void} Nothing.
238 remoting.HostSetupDialog.prototype.showForPin = function() {
239 this.usageStats_.hidden = true;
241 [remoting.HostSetupFlow.State.ASK_PIN,
242 remoting.HostSetupFlow.State.UPDATING_PIN,
243 remoting.HostSetupFlow.State.UPDATED_PIN]);
247 * Show the dialog in order to stop the daemon.
249 * @return {void} Nothing.
251 remoting.HostSetupDialog.prototype.showForStop = function() {
252 // TODO(sergeyu): Add another step to unregister the host, crubg.com/121146 .
254 [remoting.HostSetupFlow.State.STOPPING_HOST,
255 remoting.HostSetupFlow.State.HOST_STOPPED]);
259 * @return {void} Nothing.
261 remoting.HostSetupDialog.prototype.hide = function() {
262 remoting.setMode(remoting.AppMode.HOME);
266 * Starts new flow with the specified sequence of steps.
267 * @param {Array.<remoting.HostSetupFlow.State>} sequence Sequence of steps.
270 remoting.HostSetupDialog.prototype.startNewFlow_ = function(sequence) {
271 this.flow_ = new remoting.HostSetupFlow(sequence);
272 this.pinEntry_.value = '';
273 this.pinConfirm_.value = '';
274 this.pinErrorDiv_.hidden = true;
279 * Updates current UI mode according to the current state of the setup
280 * flow and start the action corresponding to the current step (if
284 remoting.HostSetupDialog.prototype.updateState_ = function() {
285 remoting.updateLocalHostState();
287 /** @param {string} tag */
288 function showProcessingMessage(tag) {
289 var messageDiv = document.getElementById('host-setup-processing-message');
290 l10n.localizeElementFromTag(messageDiv, tag);
291 remoting.setMode(remoting.AppMode.HOST_SETUP_PROCESSING);
293 /** @param {string} tag1
294 * @param {string=} opt_tag2 */
295 function showDoneMessage(tag1, opt_tag2) {
296 var messageDiv = document.getElementById('host-setup-done-message');
297 l10n.localizeElementFromTag(messageDiv, tag1);
298 messageDiv = document.getElementById('host-setup-done-message-2');
300 l10n.localizeElementFromTag(messageDiv, opt_tag2);
302 messageDiv.innerText = '';
304 remoting.setMode(remoting.AppMode.HOST_SETUP_DONE);
306 /** @param {string} tag */
307 function showErrorMessage(tag) {
308 var errorDiv = document.getElementById('host-setup-error-message');
309 l10n.localizeElementFromTag(errorDiv, tag);
310 remoting.setMode(remoting.AppMode.HOST_SETUP_ERROR);
313 var state = this.flow_.getState();
314 if (state == remoting.HostSetupFlow.State.NONE) {
316 } else if (state == remoting.HostSetupFlow.State.ASK_PIN) {
317 remoting.setMode(remoting.AppMode.HOST_SETUP_ASK_PIN);
318 } else if (state == remoting.HostSetupFlow.State.INSTALL_HOST) {
320 } else if (state == remoting.HostSetupFlow.State.STARTING_HOST) {
321 showProcessingMessage(/*i18n-content*/'HOST_SETUP_STARTING');
323 } else if (state == remoting.HostSetupFlow.State.UPDATING_PIN) {
324 showProcessingMessage(/*i18n-content*/'HOST_SETUP_UPDATING_PIN');
326 } else if (state == remoting.HostSetupFlow.State.STOPPING_HOST) {
327 showProcessingMessage(/*i18n-content*/'HOST_SETUP_STOPPING');
329 } else if (state == remoting.HostSetupFlow.State.HOST_STARTED) {
330 // TODO(jamiewalch): Only display the second string if the computer's power
331 // management settings indicate that it's necessary.
332 showDoneMessage(/*i18n-content*/'HOST_SETUP_STARTED',
333 /*i18n-content*/'HOST_SETUP_STARTED_DISABLE_SLEEP');
334 } else if (state == remoting.HostSetupFlow.State.UPDATED_PIN) {
335 showDoneMessage(/*i18n-content*/'HOST_SETUP_UPDATED_PIN');
336 } else if (state == remoting.HostSetupFlow.State.HOST_STOPPED) {
337 showDoneMessage(/*i18n-content*/'HOST_SETUP_STOPPED');
338 } else if (state == remoting.HostSetupFlow.State.REGISTRATION_FAILED) {
339 showErrorMessage(/*i18n-content*/'ERROR_HOST_REGISTRATION_FAILED');
340 } else if (state == remoting.HostSetupFlow.State.START_HOST_FAILED) {
341 showErrorMessage(/*i18n-content*/'HOST_SETUP_HOST_FAILED');
342 } else if (state == remoting.HostSetupFlow.State.UPDATE_PIN_FAILED) {
343 showErrorMessage(/*i18n-content*/'HOST_SETUP_UPDATE_PIN_FAILED');
344 } else if (state == remoting.HostSetupFlow.State.STOP_HOST_FAILED) {
345 showErrorMessage(/*i18n-content*/'HOST_SETUP_STOP_FAILED');
350 * Shows the prompt that asks the user to install the host.
352 remoting.HostSetupDialog.prototype.installHost_ = function() {
353 /** @type {remoting.HostSetupDialog} */
355 /** @type {remoting.HostSetupFlow} */
356 var flow = this.flow_;
358 var onDone = function() {
359 that.hostController_.getLocalHostState(onHostState);
362 /** @param {remoting.Error} error */
363 var onError = function(error) {
364 flow.switchToErrorState(error);
368 /** @param {remoting.HostController.State} state */
369 var onHostState = function(state) {
370 // Verify if the host has been installed. If not then try to prompt the user
373 state != remoting.HostController.State.NOT_INSTALLED &&
374 state != remoting.HostController.State.INSTALLING;
376 that.flow_.switchToNextStep();
379 hostInstallDialog.tryAgain();
383 /** @type {remoting.HostInstallDialog} */
384 var hostInstallDialog = new remoting.HostInstallDialog();
385 hostInstallDialog.show(onDone, onError);
389 * Registers and starts the host.
391 remoting.HostSetupDialog.prototype.startHost_ = function() {
392 /** @type {remoting.HostSetupDialog} */
394 /** @type {remoting.HostSetupFlow} */
395 var flow = this.flow_;
397 /** @return {boolean} */
398 function isFlowActive() {
399 if (flow !== that.flow_ ||
400 flow.getState() != remoting.HostSetupFlow.State.STARTING_HOST) {
401 console.error('Host setup was interrupted when starting the host');
407 function onHostStarted() {
408 if (isFlowActive()) {
409 flow.switchToNextStep();
414 /** @param {remoting.Error} error */
415 function onError(error) {
416 if (isFlowActive()) {
417 flow.switchToErrorState(error);
422 this.hostController_.start(this.flow_.pin, this.flow_.consent, onHostStarted,
426 remoting.HostSetupDialog.prototype.updatePin_ = function() {
427 /** @type {remoting.HostSetupDialog} */
429 /** @type {remoting.HostSetupFlow} */
430 var flow = this.flow_;
432 /** @return {boolean} */
433 function isFlowActive() {
434 if (flow !== that.flow_ ||
435 flow.getState() != remoting.HostSetupFlow.State.UPDATING_PIN) {
436 console.error('Host setup was interrupted when updating PIN');
442 function onPinUpdated() {
443 if (isFlowActive()) {
444 flow.switchToNextStep();
449 /** @param {remoting.Error} error */
450 function onError(error) {
451 if (isFlowActive()) {
452 flow.switchToErrorState(error);
457 this.hostController_.updatePin(flow.pin, onPinUpdated, onError);
463 remoting.HostSetupDialog.prototype.stopHost_ = function() {
464 /** @type {remoting.HostSetupDialog} */
466 /** @type {remoting.HostSetupFlow} */
467 var flow = this.flow_;
469 /** @return {boolean} */
470 function isFlowActive() {
471 if (flow !== that.flow_ ||
472 flow.getState() != remoting.HostSetupFlow.State.STOPPING_HOST) {
473 console.error('Host setup was interrupted when stopping the host');
479 function onHostStopped() {
480 if (isFlowActive()) {
481 flow.switchToNextStep();
486 /** @param {remoting.Error} error */
487 function onError(error) {
488 if (isFlowActive()) {
489 flow.switchToErrorState(error);
494 this.hostController_.stop(onHostStopped, onError);
498 * Validates the PIN and shows an error message if it's invalid.
499 * @return {boolean} true if the PIN is valid, false otherwise.
502 remoting.HostSetupDialog.prototype.validatePin_ = function() {
503 var pin = this.pinEntry_.value;
504 var pinIsValid = remoting.HostSetupDialog.validPin_(pin);
506 l10n.localizeElementFromTag(
507 this.pinErrorMessage_, /*i18n-content*/'INVALID_PIN');
509 this.pinErrorDiv_.hidden = pinIsValid;
514 remoting.HostSetupDialog.prototype.onPinSubmit_ = function() {
515 if (this.flow_.getState() != remoting.HostSetupFlow.State.ASK_PIN) {
516 console.error('PIN submitted in an invalid state', this.flow_.getState());
519 var pin1 = this.pinEntry_.value;
520 var pin2 = this.pinConfirm_.value;
522 l10n.localizeElementFromTag(
523 this.pinErrorMessage_, /*i18n-content*/'PINS_NOT_EQUAL');
524 this.pinErrorDiv_.hidden = false;
525 this.prepareForPinEntry_();
528 if (!this.validatePin_()) {
529 this.prepareForPinEntry_();
532 this.flow_.pin = pin1;
533 this.flow_.consent = !this.usageStats_.hidden &&
534 this.usageStatsCheckbox_.checked;
535 this.flow_.switchToNextStep();
540 remoting.HostSetupDialog.prototype.prepareForPinEntry_ = function() {
541 this.pinEntry_.value = '';
542 this.pinConfirm_.value = '';
543 this.pinEntry_.focus();
547 * Returns whether a PIN is valid.
550 * @param {string} pin A PIN.
551 * @return {boolean} Whether the PIN is valid.
553 remoting.HostSetupDialog.validPin_ = function(pin) {
554 if (pin.length < 6) {
557 for (var i = 0; i < pin.length; i++) {
558 var c = pin.charAt(i);
559 if ((c < '0') || (c > '9')) {
566 /** @type {remoting.HostSetupDialog} */
567 remoting.hostSetupDialog = null;