| // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| 'use strict'; |
| |
| /** @suppress {duplicate} */ |
| var remoting = remoting || {}; |
| |
| /** |
| * @param {Array.<remoting.HostSetupFlow.State>} sequence Sequence of |
| * steps for the flow. |
| * @constructor |
| */ |
| remoting.HostSetupFlow = function(sequence) { |
| this.sequence_ = sequence; |
| this.currentStep_ = 0; |
| this.state_ = sequence[0]; |
| this.pin = ''; |
| this.consent = false; |
| }; |
| |
| /** @enum {number} */ |
| remoting.HostSetupFlow.State = { |
| NONE: 0, |
| |
| // Dialog states. |
| ASK_PIN: 1, |
| |
| // Used on Mac OS X to prompt the user to manually install a .dmg package. |
| INSTALL_HOST: 2, |
| |
| // Processing states. |
| STARTING_HOST: 3, |
| UPDATING_PIN: 4, |
| STOPPING_HOST: 5, |
| |
| // Done states. |
| HOST_STARTED: 6, |
| UPDATED_PIN: 7, |
| HOST_STOPPED: 8, |
| |
| // Failure states. |
| REGISTRATION_FAILED: 9, |
| START_HOST_FAILED: 10, |
| UPDATE_PIN_FAILED: 11, |
| STOP_HOST_FAILED: 12 |
| }; |
| |
| /** @return {remoting.HostSetupFlow.State} Current state of the flow. */ |
| remoting.HostSetupFlow.prototype.getState = function() { |
| return this.state_; |
| }; |
| |
| remoting.HostSetupFlow.prototype.switchToNextStep = function() { |
| if (this.state_ == remoting.HostSetupFlow.State.NONE) { |
| return; |
| } |
| |
| if (this.currentStep_ < this.sequence_.length - 1) { |
| this.currentStep_ += 1; |
| this.state_ = this.sequence_[this.currentStep_]; |
| } else { |
| this.state_ = remoting.HostSetupFlow.State.NONE; |
| } |
| }; |
| |
| /** |
| * @param {remoting.Error} error |
| */ |
| remoting.HostSetupFlow.prototype.switchToErrorState = function(error) { |
| if (error == remoting.Error.CANCELLED) { |
| // Stop the setup flow if user rejected one of the actions. |
| this.state_ = remoting.HostSetupFlow.State.NONE; |
| } else { |
| // Current step failed, so switch to corresponding error state. |
| if (this.state_ == remoting.HostSetupFlow.State.STARTING_HOST) { |
| if (error == remoting.Error.REGISTRATION_FAILED) { |
| this.state_ = remoting.HostSetupFlow.State.REGISTRATION_FAILED; |
| } else { |
| this.state_ = remoting.HostSetupFlow.State.START_HOST_FAILED; |
| } |
| } else if (this.state_ == remoting.HostSetupFlow.State.UPDATING_PIN) { |
| this.state_ = remoting.HostSetupFlow.State.UPDATE_PIN_FAILED; |
| } else if (this.state_ == remoting.HostSetupFlow.State.STOPPING_HOST) { |
| this.state_ = remoting.HostSetupFlow.State.STOP_HOST_FAILED; |
| } else { |
| // TODO(sergeyu): Add other error states and use them here. |
| this.state_ = remoting.HostSetupFlow.State.START_HOST_FAILED; |
| } |
| } |
| }; |
| |
| /** |
| * @param {remoting.HostController} hostController The HostController |
| * responsible for the host daemon. |
| * @constructor |
| */ |
| remoting.HostSetupDialog = function(hostController) { |
| this.hostController_ = hostController; |
| this.pinEntry_ = document.getElementById('daemon-pin-entry'); |
| this.pinConfirm_ = document.getElementById('daemon-pin-confirm'); |
| this.pinErrorDiv_ = document.getElementById('daemon-pin-error-div'); |
| this.pinErrorMessage_ = document.getElementById('daemon-pin-error-message'); |
| this.continueInstallButton_ = document.getElementById( |
| 'host-config-install-continue'); |
| this.cancelInstallButton_ = document.getElementById( |
| 'host-config-install-dismiss'); |
| this.retryInstallButton_ = document.getElementById( |
| 'host-config-install-retry'); |
| |
| this.continueInstallButton_.addEventListener( |
| 'click', this.onInstallDialogOk.bind(this), false); |
| this.cancelInstallButton_.addEventListener( |
| 'click', this.hide.bind(this), false); |
| this.retryInstallButton_.addEventListener( |
| 'click', this.onInstallDialogRetry.bind(this), false); |
| |
| /** @type {remoting.HostSetupFlow} */ |
| this.flow_ = new remoting.HostSetupFlow([remoting.HostSetupFlow.State.NONE]); |
| |
| /** @type {remoting.HostSetupDialog} */ |
| var that = this; |
| /** @param {Event} event The event. */ |
| var onPinSubmit = function(event) { |
| event.preventDefault(); |
| that.onPinSubmit_(); |
| }; |
| var onPinConfirmFocus = function() { |
| that.validatePin_(); |
| }; |
| |
| var form = document.getElementById('ask-pin-form'); |
| form.addEventListener('submit', onPinSubmit, false); |
| /** @param {Event} event The event. */ |
| var onDaemonPinEntryKeyPress = function(event) { |
| if (event.which == 13) { |
| document.getElementById('daemon-pin-confirm').focus(); |
| event.preventDefault(); |
| } |
| }; |
| /** @param {Event} event A keypress event. */ |
| var noDigitsInPin = function(event) { |
| if (event.which == 13) { |
| return; // Otherwise the "submit" action can't be triggered by Enter. |
| } |
| if ((event.which >= 48) && (event.which <= 57)) { |
| return; |
| } |
| event.preventDefault(); |
| }; |
| this.pinEntry_.addEventListener('keypress', onDaemonPinEntryKeyPress, false); |
| this.pinEntry_.addEventListener('keypress', noDigitsInPin, false); |
| this.pinConfirm_.addEventListener('focus', onPinConfirmFocus, false); |
| this.pinConfirm_.addEventListener('keypress', noDigitsInPin, false); |
| |
| this.usageStats_ = document.getElementById('usagestats-consent'); |
| this.usageStatsCheckbox_ = /** @type {HTMLInputElement} */ |
| document.getElementById('usagestats-consent-checkbox'); |
| }; |
| |
| /** |
| * Show the dialog in order to get a PIN prior to starting the daemon. When the |
| * user clicks OK, the dialog shows a spinner until the daemon has started. |
| * |
| * @return {void} Nothing. |
| */ |
| remoting.HostSetupDialog.prototype.showForStart = function() { |
| /** @type {remoting.HostSetupDialog} */ |
| var that = this; |
| |
| /** |
| * @param {remoting.HostController.State} state |
| */ |
| var onState = function(state) { |
| // Although we don't need an access token in order to start the host, |
| // using callWithToken here ensures consistent error handling in the |
| // case where the refresh token is invalid. |
| remoting.identity.callWithToken( |
| that.showForStartWithToken_.bind(that, state), |
| remoting.showErrorMessage); |
| }; |
| |
| this.hostController_.getLocalHostState(onState); |
| }; |
| |
| /** |
| * @param {remoting.HostController.State} state The current state of the local |
| * host. |
| * @param {string} token The OAuth2 token. |
| * @private |
| */ |
| remoting.HostSetupDialog.prototype.showForStartWithToken_ = |
| function(state, token) { |
| /** @type {remoting.HostSetupDialog} */ |
| var that = this; |
| |
| /** |
| * @param {boolean} supported True if crash dump reporting is supported by |
| * the host. |
| * @param {boolean} allowed True if crash dump reporting is allowed. |
| * @param {boolean} set_by_policy True if crash dump reporting is controlled |
| * by policy. |
| */ |
| function onGetConsent(supported, allowed, set_by_policy) { |
| that.usageStats_.hidden = !supported; |
| that.usageStatsCheckbox_.checked = allowed; |
| that.usageStatsCheckbox_.disabled = set_by_policy; |
| } |
| |
| /** @param {remoting.Error} error */ |
| function onError(error) { |
| console.error('Error getting consent status: ' + error); |
| } |
| |
| this.usageStats_.hidden = false; |
| this.usageStatsCheckbox_.checked = false; |
| |
| // Prevent user from ticking the box until the current consent status is |
| // known. |
| this.usageStatsCheckbox_.disabled = true; |
| |
| this.hostController_.getConsent(onGetConsent, onError); |
| |
| var flow = [ |
| remoting.HostSetupFlow.State.ASK_PIN, |
| remoting.HostSetupFlow.State.STARTING_HOST, |
| remoting.HostSetupFlow.State.HOST_STARTED]; |
| |
| var installed = |
| state != remoting.HostController.State.NOT_INSTALLED && |
| state != remoting.HostController.State.INSTALLING; |
| |
| if (navigator.platform.indexOf('Mac') != -1 && !installed) { |
| flow.unshift(remoting.HostSetupFlow.State.INSTALL_HOST); |
| } |
| |
| this.startNewFlow_(flow); |
| }; |
| |
| /** |
| * Show the dialog in order to change the PIN associated with a running daemon. |
| * |
| * @return {void} Nothing. |
| */ |
| remoting.HostSetupDialog.prototype.showForPin = function() { |
| this.usageStats_.hidden = true; |
| this.startNewFlow_( |
| [remoting.HostSetupFlow.State.ASK_PIN, |
| remoting.HostSetupFlow.State.UPDATING_PIN, |
| remoting.HostSetupFlow.State.UPDATED_PIN]); |
| }; |
| |
| /** |
| * Show the dialog in order to stop the daemon. |
| * |
| * @return {void} Nothing. |
| */ |
| remoting.HostSetupDialog.prototype.showForStop = function() { |
| // TODO(sergeyu): Add another step to unregister the host, crubg.com/121146 . |
| this.startNewFlow_( |
| [remoting.HostSetupFlow.State.STOPPING_HOST, |
| remoting.HostSetupFlow.State.HOST_STOPPED]); |
| }; |
| |
| /** |
| * @return {void} Nothing. |
| */ |
| remoting.HostSetupDialog.prototype.hide = function() { |
| remoting.setMode(remoting.AppMode.HOME); |
| }; |
| |
| /** |
| * Starts new flow with the specified sequence of steps. |
| * @param {Array.<remoting.HostSetupFlow.State>} sequence Sequence of steps. |
| * @private |
| */ |
| remoting.HostSetupDialog.prototype.startNewFlow_ = function(sequence) { |
| this.flow_ = new remoting.HostSetupFlow(sequence); |
| this.pinEntry_.value = ''; |
| this.pinConfirm_.value = ''; |
| this.pinErrorDiv_.hidden = true; |
| this.updateState_(); |
| }; |
| |
| /** |
| * Updates current UI mode according to the current state of the setup |
| * flow and start the action corresponding to the current step (if |
| * any). |
| * @private |
| */ |
| remoting.HostSetupDialog.prototype.updateState_ = function() { |
| remoting.updateLocalHostState(); |
| |
| /** @param {string} tag */ |
| function showProcessingMessage(tag) { |
| var messageDiv = document.getElementById('host-setup-processing-message'); |
| l10n.localizeElementFromTag(messageDiv, tag); |
| remoting.setMode(remoting.AppMode.HOST_SETUP_PROCESSING); |
| } |
| /** @param {string} tag1 |
| * @param {string=} opt_tag2 */ |
| function showDoneMessage(tag1, opt_tag2) { |
| var messageDiv = document.getElementById('host-setup-done-message'); |
| l10n.localizeElementFromTag(messageDiv, tag1); |
| messageDiv = document.getElementById('host-setup-done-message-2'); |
| if (opt_tag2) { |
| l10n.localizeElementFromTag(messageDiv, opt_tag2); |
| } else { |
| messageDiv.innerText = ''; |
| } |
| remoting.setMode(remoting.AppMode.HOST_SETUP_DONE); |
| } |
| /** @param {string} tag */ |
| function showErrorMessage(tag) { |
| var errorDiv = document.getElementById('host-setup-error-message'); |
| l10n.localizeElementFromTag(errorDiv, tag); |
| remoting.setMode(remoting.AppMode.HOST_SETUP_ERROR); |
| } |
| |
| var state = this.flow_.getState(); |
| if (state == remoting.HostSetupFlow.State.NONE) { |
| this.hide(); |
| } else if (state == remoting.HostSetupFlow.State.ASK_PIN) { |
| remoting.setMode(remoting.AppMode.HOST_SETUP_ASK_PIN); |
| } else if (state == remoting.HostSetupFlow.State.INSTALL_HOST) { |
| remoting.setMode(remoting.AppMode.HOST_SETUP_INSTALL); |
| window.location = |
| 'https://dl.google.com/chrome-remote-desktop/chromeremotedesktop.dmg'; |
| } else if (state == remoting.HostSetupFlow.State.STARTING_HOST) { |
| showProcessingMessage(/*i18n-content*/'HOST_SETUP_STARTING'); |
| this.startHost_(); |
| } else if (state == remoting.HostSetupFlow.State.UPDATING_PIN) { |
| showProcessingMessage(/*i18n-content*/'HOST_SETUP_UPDATING_PIN'); |
| this.updatePin_(); |
| } else if (state == remoting.HostSetupFlow.State.STOPPING_HOST) { |
| showProcessingMessage(/*i18n-content*/'HOST_SETUP_STOPPING'); |
| this.stopHost_(); |
| } else if (state == remoting.HostSetupFlow.State.HOST_STARTED) { |
| // TODO(jamiewalch): Only display the second string if the computer's power |
| // management settings indicate that it's necessary. |
| showDoneMessage(/*i18n-content*/'HOST_SETUP_STARTED', |
| /*i18n-content*/'HOST_SETUP_STARTED_DISABLE_SLEEP'); |
| } else if (state == remoting.HostSetupFlow.State.UPDATED_PIN) { |
| showDoneMessage(/*i18n-content*/'HOST_SETUP_UPDATED_PIN'); |
| } else if (state == remoting.HostSetupFlow.State.HOST_STOPPED) { |
| showDoneMessage(/*i18n-content*/'HOST_SETUP_STOPPED'); |
| } else if (state == remoting.HostSetupFlow.State.REGISTRATION_FAILED) { |
| showErrorMessage(/*i18n-content*/'ERROR_HOST_REGISTRATION_FAILED'); |
| } else if (state == remoting.HostSetupFlow.State.START_HOST_FAILED) { |
| showErrorMessage(/*i18n-content*/'HOST_SETUP_HOST_FAILED'); |
| } else if (state == remoting.HostSetupFlow.State.UPDATE_PIN_FAILED) { |
| showErrorMessage(/*i18n-content*/'HOST_SETUP_UPDATE_PIN_FAILED'); |
| } else if (state == remoting.HostSetupFlow.State.STOP_HOST_FAILED) { |
| showErrorMessage(/*i18n-content*/'HOST_SETUP_STOP_FAILED'); |
| } |
| }; |
| |
| /** |
| * Registers and starts the host. |
| */ |
| remoting.HostSetupDialog.prototype.startHost_ = function() { |
| /** @type {remoting.HostSetupDialog} */ |
| var that = this; |
| /** @type {remoting.HostSetupFlow} */ |
| var flow = this.flow_; |
| |
| /** @return {boolean} */ |
| function isFlowActive() { |
| if (flow !== that.flow_ || |
| flow.getState() != remoting.HostSetupFlow.State.STARTING_HOST) { |
| console.error('Host setup was interrupted when starting the host'); |
| return false; |
| } |
| return true; |
| } |
| |
| function onHostStarted() { |
| if (isFlowActive()) { |
| flow.switchToNextStep(); |
| that.updateState_(); |
| } |
| } |
| |
| /** @param {remoting.Error} error */ |
| function onError(error) { |
| if (isFlowActive()) { |
| flow.switchToErrorState(error); |
| that.updateState_(); |
| } |
| } |
| |
| this.hostController_.start(this.flow_.pin, this.flow_.consent, onHostStarted, |
| onError); |
| }; |
| |
| remoting.HostSetupDialog.prototype.updatePin_ = function() { |
| /** @type {remoting.HostSetupDialog} */ |
| var that = this; |
| /** @type {remoting.HostSetupFlow} */ |
| var flow = this.flow_; |
| |
| /** @return {boolean} */ |
| function isFlowActive() { |
| if (flow !== that.flow_ || |
| flow.getState() != remoting.HostSetupFlow.State.UPDATING_PIN) { |
| console.error('Host setup was interrupted when updating PIN'); |
| return false; |
| } |
| return true; |
| } |
| |
| function onPinUpdated() { |
| if (isFlowActive()) { |
| flow.switchToNextStep(); |
| that.updateState_(); |
| } |
| } |
| |
| /** @param {remoting.Error} error */ |
| function onError(error) { |
| if (isFlowActive()) { |
| flow.switchToErrorState(error); |
| that.updateState_(); |
| } |
| } |
| |
| this.hostController_.updatePin(flow.pin, onPinUpdated, onError); |
| }; |
| |
| /** |
| * Stops the host. |
| */ |
| remoting.HostSetupDialog.prototype.stopHost_ = function() { |
| /** @type {remoting.HostSetupDialog} */ |
| var that = this; |
| /** @type {remoting.HostSetupFlow} */ |
| var flow = this.flow_; |
| |
| /** @return {boolean} */ |
| function isFlowActive() { |
| if (flow !== that.flow_ || |
| flow.getState() != remoting.HostSetupFlow.State.STOPPING_HOST) { |
| console.error('Host setup was interrupted when stopping the host'); |
| return false; |
| } |
| return true; |
| } |
| |
| function onHostStopped() { |
| if (isFlowActive()) { |
| flow.switchToNextStep(); |
| that.updateState_(); |
| } |
| } |
| |
| /** @param {remoting.Error} error */ |
| function onError(error) { |
| if (isFlowActive()) { |
| flow.switchToErrorState(error); |
| that.updateState_(); |
| } |
| } |
| |
| this.hostController_.stop(onHostStopped, onError); |
| }; |
| |
| /** |
| * Validates the PIN and shows an error message if it's invalid. |
| * @return {boolean} true if the PIN is valid, false otherwise. |
| * @private |
| */ |
| remoting.HostSetupDialog.prototype.validatePin_ = function() { |
| var pin = this.pinEntry_.value; |
| var pinIsValid = remoting.HostSetupDialog.validPin_(pin); |
| if (!pinIsValid) { |
| l10n.localizeElementFromTag( |
| this.pinErrorMessage_, /*i18n-content*/'INVALID_PIN'); |
| } |
| this.pinErrorDiv_.hidden = pinIsValid; |
| return pinIsValid; |
| }; |
| |
| /** @private */ |
| remoting.HostSetupDialog.prototype.onPinSubmit_ = function() { |
| if (this.flow_.getState() != remoting.HostSetupFlow.State.ASK_PIN) { |
| console.error('PIN submitted in an invalid state', this.flow_.getState()); |
| return; |
| } |
| var pin1 = this.pinEntry_.value; |
| var pin2 = this.pinConfirm_.value; |
| if (pin1 != pin2) { |
| l10n.localizeElementFromTag( |
| this.pinErrorMessage_, /*i18n-content*/'PINS_NOT_EQUAL'); |
| this.pinErrorDiv_.hidden = false; |
| this.prepareForPinEntry_(); |
| return; |
| } |
| if (!this.validatePin_()) { |
| this.prepareForPinEntry_(); |
| return; |
| } |
| this.flow_.pin = pin1; |
| this.flow_.consent = !this.usageStats_.hidden && |
| this.usageStatsCheckbox_.checked; |
| this.flow_.switchToNextStep(); |
| this.updateState_(); |
| }; |
| |
| /** @private */ |
| remoting.HostSetupDialog.prototype.prepareForPinEntry_ = function() { |
| this.pinEntry_.value = ''; |
| this.pinConfirm_.value = ''; |
| this.pinEntry_.focus(); |
| }; |
| |
| /** |
| * Returns whether a PIN is valid. |
| * |
| * @private |
| * @param {string} pin A PIN. |
| * @return {boolean} Whether the PIN is valid. |
| */ |
| remoting.HostSetupDialog.validPin_ = function(pin) { |
| if (pin.length < 6) { |
| return false; |
| } |
| for (var i = 0; i < pin.length; i++) { |
| var c = pin.charAt(i); |
| if ((c < '0') || (c > '9')) { |
| return false; |
| } |
| } |
| return true; |
| }; |
| |
| /** |
| * @return {void} Nothing. |
| */ |
| remoting.HostSetupDialog.prototype.onInstallDialogOk = function() { |
| this.continueInstallButton_.disabled = true; |
| this.cancelInstallButton_.disabled = true; |
| |
| /** @type {remoting.HostSetupDialog} */ |
| var that = this; |
| |
| /** @param {remoting.HostController.State} state */ |
| var onHostState = function(state) { |
| that.continueInstallButton_.disabled = false; |
| that.cancelInstallButton_.disabled = false; |
| var installed = |
| state != remoting.HostController.State.NOT_INSTALLED && |
| state != remoting.HostController.State.INSTALLING; |
| if (installed) { |
| that.flow_.switchToNextStep(); |
| that.updateState_(); |
| } else { |
| remoting.setMode(remoting.AppMode.HOST_SETUP_INSTALL_PENDING); |
| } |
| }; |
| |
| this.hostController_.getLocalHostState(onHostState); |
| }; |
| |
| /** |
| * @return {void} Nothing. |
| */ |
| remoting.HostSetupDialog.prototype.onInstallDialogRetry = function() { |
| remoting.setMode(remoting.AppMode.HOST_SETUP_INSTALL); |
| }; |
| |
| /** @type {remoting.HostSetupDialog} */ |
| remoting.hostSetupDialog = null; |