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 // Used on Mac OS X to prompt the user to manually install a .dmg 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() {
56 * @param {remoting.HostController.AsyncResult} result Result of the
58 * @return {remoting.HostSetupFlow.State} New state.
60 remoting.HostSetupFlow.prototype.switchToNextStep = function(result) {
61 if (this.state_ == remoting.HostSetupFlow.State.NONE) {
64 if (result == remoting.HostController.AsyncResult.OK) {
65 // If the current step was successful then switch to the next
66 // step in the sequence.
67 if (this.currentStep_ < this.sequence_.length - 1) {
68 this.currentStep_ += 1;
69 this.state_ = this.sequence_[this.currentStep_];
71 this.state_ = remoting.HostSetupFlow.State.NONE;
73 } else if (result == remoting.HostController.AsyncResult.CANCELLED) {
74 // Stop the setup flow if user rejected one of the actions.
75 this.state_ = remoting.HostSetupFlow.State.NONE;
77 // Current step failed, so switch to corresponding error state.
78 if (this.state_ == remoting.HostSetupFlow.State.STARTING_HOST) {
79 if (result == remoting.HostController.AsyncResult.FAILED_DIRECTORY) {
80 this.state_ = remoting.HostSetupFlow.State.REGISTRATION_FAILED;
82 this.state_ = remoting.HostSetupFlow.State.START_HOST_FAILED;
84 } else if (this.state_ == remoting.HostSetupFlow.State.UPDATING_PIN) {
85 this.state_ = remoting.HostSetupFlow.State.UPDATE_PIN_FAILED;
86 } else if (this.state_ == remoting.HostSetupFlow.State.STOPPING_HOST) {
87 this.state_ = remoting.HostSetupFlow.State.STOP_HOST_FAILED;
89 // TODO(sergeyu): Add other error states and use them here.
90 this.state_ = remoting.HostSetupFlow.State.START_HOST_FAILED;
97 * @param {remoting.HostController} hostController The HostController
98 * responsible for the host daemon.
101 remoting.HostSetupDialog = function(hostController) {
102 this.hostController_ = hostController;
103 this.pinEntry_ = document.getElementById('daemon-pin-entry');
104 this.pinConfirm_ = document.getElementById('daemon-pin-confirm');
105 this.pinErrorDiv_ = document.getElementById('daemon-pin-error-div');
106 this.pinErrorMessage_ = document.getElementById('daemon-pin-error-message');
107 this.continueInstallButton_ = document.getElementById(
108 'host-config-install-continue');
109 this.cancelInstallButton_ = document.getElementById(
110 'host-config-install-dismiss');
111 this.retryInstallButton_ = document.getElementById(
112 'host-config-install-retry');
114 this.continueInstallButton_.addEventListener(
115 'click', this.onInstallDialogOk.bind(this), false);
116 this.cancelInstallButton_.addEventListener(
117 'click', this.hide.bind(this), false);
118 this.retryInstallButton_.addEventListener(
119 'click', this.onInstallDialogRetry.bind(this), false);
121 /** @type {remoting.HostSetupFlow} */
122 this.flow_ = new remoting.HostSetupFlow([remoting.HostSetupFlow.State.NONE]);
124 /** @type {remoting.HostSetupDialog} */
126 /** @param {Event} event The event. */
127 var onPinSubmit = function(event) {
128 event.preventDefault();
131 var onPinConfirmFocus = function() {
135 var form = document.getElementById('ask-pin-form');
136 form.addEventListener('submit', onPinSubmit, false);
137 /** @param {Event} event The event. */
138 var onDaemonPinEntryKeyPress = function(event) {
139 if (event.which == 13) {
140 document.getElementById('daemon-pin-confirm').focus();
141 event.preventDefault();
144 /** @param {Event} event A keypress event. */
145 var noDigitsInPin = function(event) {
146 if (event.which == 13) {
147 return; // Otherwise the "submit" action can't be triggered by Enter.
149 if ((event.which >= 48) && (event.which <= 57)) {
152 event.preventDefault();
154 this.pinEntry_.addEventListener('keypress', onDaemonPinEntryKeyPress, false);
155 this.pinEntry_.addEventListener('keypress', noDigitsInPin, false);
156 this.pinConfirm_.addEventListener('focus', onPinConfirmFocus, false);
157 this.pinConfirm_.addEventListener('keypress', noDigitsInPin, false);
159 this.usageStats_ = document.getElementById('usagestats-consent');
160 this.usageStatsCheckbox_ = /** @type {HTMLInputElement} */
161 document.getElementById('usagestats-consent-checkbox');
165 * Show the dialog in order to get a PIN prior to starting the daemon. When the
166 * user clicks OK, the dialog shows a spinner until the daemon has started.
168 * @return {void} Nothing.
170 remoting.HostSetupDialog.prototype.showForStart = function() {
171 /** @type {remoting.HostSetupDialog} */
175 * @param {remoting.HostController.State} state
177 var onState = function(state) {
178 // Although we don't need an access token in order to start the host,
179 // using callWithToken here ensures consistent error handling in the
180 // case where the refresh token is invalid.
181 remoting.identity.callWithToken(
182 that.showForStartWithToken_.bind(that, state),
183 remoting.showErrorMessage);
186 this.hostController_.getLocalHostState(onState);
190 * @param {remoting.HostController.State} state The current state of the local
192 * @param {string} token The OAuth2 token.
195 remoting.HostSetupDialog.prototype.showForStartWithToken_ =
196 function(state, token) {
197 /** @type {remoting.HostSetupDialog} */
201 * @param {boolean} supported True if crash dump reporting is supported by
203 * @param {boolean} allowed True if crash dump reporting is allowed.
204 * @param {boolean} set_by_policy True if crash dump reporting is controlled
207 var onGetConsent = function(supported, allowed, set_by_policy) {
208 that.usageStats_.hidden = !supported;
209 that.usageStatsCheckbox_.checked = allowed;
210 that.usageStatsCheckbox_.disabled = set_by_policy;
212 this.usageStats_.hidden = false;
213 this.usageStatsCheckbox_.checked = false;
214 this.hostController_.getConsent(onGetConsent);
217 remoting.HostSetupFlow.State.ASK_PIN,
218 remoting.HostSetupFlow.State.STARTING_HOST,
219 remoting.HostSetupFlow.State.HOST_STARTED];
222 state != remoting.HostController.State.NOT_INSTALLED &&
223 state != remoting.HostController.State.INSTALLING;
225 if (navigator.platform.indexOf('Mac') != -1 && !installed) {
226 flow.unshift(remoting.HostSetupFlow.State.INSTALL_HOST);
229 this.startNewFlow_(flow);
233 * Show the dialog in order to change the PIN associated with a running daemon.
235 * @return {void} Nothing.
237 remoting.HostSetupDialog.prototype.showForPin = function() {
238 this.usageStats_.hidden = true;
240 [remoting.HostSetupFlow.State.ASK_PIN,
241 remoting.HostSetupFlow.State.UPDATING_PIN,
242 remoting.HostSetupFlow.State.UPDATED_PIN]);
246 * Show the dialog in order to stop the daemon.
248 * @return {void} Nothing.
250 remoting.HostSetupDialog.prototype.showForStop = function() {
251 // TODO(sergeyu): Add another step to unregister the host, crubg.com/121146 .
253 [remoting.HostSetupFlow.State.STOPPING_HOST,
254 remoting.HostSetupFlow.State.HOST_STOPPED]);
258 * @return {void} Nothing.
260 remoting.HostSetupDialog.prototype.hide = function() {
261 remoting.setMode(remoting.AppMode.HOME);
265 * Starts new flow with the specified sequence of steps.
266 * @param {Array.<remoting.HostSetupFlow.State>} sequence Sequence of steps.
269 remoting.HostSetupDialog.prototype.startNewFlow_ = function(sequence) {
270 this.flow_ = new remoting.HostSetupFlow(sequence);
271 this.pinEntry_.value = '';
272 this.pinConfirm_.value = '';
273 this.pinErrorDiv_.hidden = true;
278 * Updates current UI mode according to the current state of the setup
279 * flow and start the action corresponding to the current step (if
283 remoting.HostSetupDialog.prototype.updateState_ = function() {
284 remoting.updateLocalHostState();
286 /** @param {string} tag */
287 function showProcessingMessage(tag) {
288 var messageDiv = document.getElementById('host-setup-processing-message');
289 l10n.localizeElementFromTag(messageDiv, tag);
290 remoting.setMode(remoting.AppMode.HOST_SETUP_PROCESSING);
292 /** @param {string} tag1
293 * @param {string=} opt_tag2 */
294 function showDoneMessage(tag1, opt_tag2) {
295 var messageDiv = document.getElementById('host-setup-done-message');
296 l10n.localizeElementFromTag(messageDiv, tag1);
297 messageDiv = document.getElementById('host-setup-done-message-2');
299 l10n.localizeElementFromTag(messageDiv, opt_tag2);
301 messageDiv.innerText = '';
303 remoting.setMode(remoting.AppMode.HOST_SETUP_DONE);
305 /** @param {string} tag */
306 function showErrorMessage(tag) {
307 var errorDiv = document.getElementById('host-setup-error-message');
308 l10n.localizeElementFromTag(errorDiv, tag);
309 remoting.setMode(remoting.AppMode.HOST_SETUP_ERROR);
312 var state = this.flow_.getState();
313 if (state == remoting.HostSetupFlow.State.NONE) {
315 } else if (state == remoting.HostSetupFlow.State.ASK_PIN) {
316 remoting.setMode(remoting.AppMode.HOST_SETUP_ASK_PIN);
317 } else if (state == remoting.HostSetupFlow.State.INSTALL_HOST) {
318 remoting.setMode(remoting.AppMode.HOST_SETUP_INSTALL);
320 'https://dl.google.com/chrome-remote-desktop/chromeremotedesktop.dmg';
321 } else if (state == remoting.HostSetupFlow.State.STARTING_HOST) {
322 showProcessingMessage(/*i18n-content*/'HOST_SETUP_STARTING');
324 } else if (state == remoting.HostSetupFlow.State.UPDATING_PIN) {
325 showProcessingMessage(/*i18n-content*/'HOST_SETUP_UPDATING_PIN');
327 } else if (state == remoting.HostSetupFlow.State.STOPPING_HOST) {
328 showProcessingMessage(/*i18n-content*/'HOST_SETUP_STOPPING');
330 } else if (state == remoting.HostSetupFlow.State.HOST_STARTED) {
331 // TODO(jamiewalch): Only display the second string if the computer's power
332 // management settings indicate that it's necessary.
333 showDoneMessage(/*i18n-content*/'HOST_SETUP_STARTED',
334 /*i18n-content*/'HOST_SETUP_STARTED_DISABLE_SLEEP');
335 } else if (state == remoting.HostSetupFlow.State.UPDATED_PIN) {
336 showDoneMessage(/*i18n-content*/'HOST_SETUP_UPDATED_PIN');
337 } else if (state == remoting.HostSetupFlow.State.HOST_STOPPED) {
338 showDoneMessage(/*i18n-content*/'HOST_SETUP_STOPPED');
339 } else if (state == remoting.HostSetupFlow.State.REGISTRATION_FAILED) {
340 showErrorMessage(/*i18n-content*/'HOST_SETUP_REGISTRATION_FAILED');
341 } else if (state == remoting.HostSetupFlow.State.START_HOST_FAILED) {
342 showErrorMessage(/*i18n-content*/'HOST_SETUP_HOST_FAILED');
343 } else if (state == remoting.HostSetupFlow.State.UPDATE_PIN_FAILED) {
344 showErrorMessage(/*i18n-content*/'HOST_SETUP_UPDATE_PIN_FAILED');
345 } else if (state == remoting.HostSetupFlow.State.STOP_HOST_FAILED) {
346 showErrorMessage(/*i18n-content*/'HOST_SETUP_STOP_FAILED');
351 * Registers and starts the host.
353 remoting.HostSetupDialog.prototype.startHost_ = function() {
354 /** @type {remoting.HostSetupDialog} */
356 /** @type {remoting.HostSetupFlow} */
357 var flow = this.flow_;
359 /** @param {remoting.HostController.AsyncResult} result */
360 function onHostStarted(result) {
361 if (flow !== that.flow_ ||
362 flow.getState() != remoting.HostSetupFlow.State.STARTING_HOST) {
363 console.error('Host setup was interrupted when starting the host');
367 flow.switchToNextStep(result);
370 this.hostController_.start(this.flow_.pin, this.flow_.consent, onHostStarted);
373 remoting.HostSetupDialog.prototype.updatePin_ = function() {
374 /** @type {remoting.HostSetupDialog} */
376 /** @type {remoting.HostSetupFlow} */
377 var flow = this.flow_;
379 /** @param {remoting.HostController.AsyncResult} result */
380 function onPinUpdated(result) {
381 if (flow !== that.flow_ ||
382 flow.getState() != remoting.HostSetupFlow.State.UPDATING_PIN) {
383 console.error('Host setup was interrupted when updating PIN');
387 flow.switchToNextStep(result);
391 this.hostController_.updatePin(flow.pin, onPinUpdated);
397 remoting.HostSetupDialog.prototype.stopHost_ = function() {
398 /** @type {remoting.HostSetupDialog} */
400 /** @type {remoting.HostSetupFlow} */
401 var flow = this.flow_;
403 /** @param {remoting.HostController.AsyncResult} result */
404 function onHostStopped(result) {
405 if (flow !== that.flow_ ||
406 flow.getState() != remoting.HostSetupFlow.State.STOPPING_HOST) {
407 console.error('Host setup was interrupted when stopping the host');
411 flow.switchToNextStep(result);
414 this.hostController_.stop(onHostStopped);
418 * Validates the PIN and shows an error message if it's invalid.
419 * @return {boolean} true if the PIN is valid, false otherwise.
422 remoting.HostSetupDialog.prototype.validatePin_ = function() {
423 var pin = this.pinEntry_.value;
424 var pinIsValid = remoting.HostSetupDialog.validPin_(pin);
426 l10n.localizeElementFromTag(
427 this.pinErrorMessage_, /*i18n-content*/'INVALID_PIN');
429 this.pinErrorDiv_.hidden = pinIsValid;
434 remoting.HostSetupDialog.prototype.onPinSubmit_ = function() {
435 if (this.flow_.getState() != remoting.HostSetupFlow.State.ASK_PIN) {
436 console.error('PIN submitted in an invalid state', this.flow_.getState());
439 var pin1 = this.pinEntry_.value;
440 var pin2 = this.pinConfirm_.value;
442 l10n.localizeElementFromTag(
443 this.pinErrorMessage_, /*i18n-content*/'PINS_NOT_EQUAL');
444 this.pinErrorDiv_.hidden = false;
445 this.prepareForPinEntry_();
448 if (!this.validatePin_()) {
449 this.prepareForPinEntry_();
452 this.flow_.pin = pin1;
453 this.flow_.consent = !this.usageStats_.hidden &&
454 this.usageStatsCheckbox_.checked;
455 this.flow_.switchToNextStep(remoting.HostController.AsyncResult.OK);
460 remoting.HostSetupDialog.prototype.prepareForPinEntry_ = function() {
461 this.pinEntry_.value = '';
462 this.pinConfirm_.value = '';
463 this.pinEntry_.focus();
467 * Returns whether a PIN is valid.
470 * @param {string} pin A PIN.
471 * @return {boolean} Whether the PIN is valid.
473 remoting.HostSetupDialog.validPin_ = function(pin) {
474 if (pin.length < 6) {
477 for (var i = 0; i < pin.length; i++) {
478 var c = pin.charAt(i);
479 if ((c < '0') || (c > '9')) {
487 * @return {void} Nothing.
489 remoting.HostSetupDialog.prototype.onInstallDialogOk = function() {
490 this.continueInstallButton_.disabled = true;
491 this.cancelInstallButton_.disabled = true;
493 /** @type {remoting.HostSetupDialog} */
496 /** @param {remoting.HostController.State} state */
497 var onHostState = function(state) {
498 that.continueInstallButton_.disabled = false;
499 that.cancelInstallButton_.disabled = false;
501 state != remoting.HostController.State.NOT_INSTALLED &&
502 state != remoting.HostController.State.INSTALLING;
504 that.flow_.switchToNextStep(remoting.HostController.AsyncResult.OK);
507 remoting.setMode(remoting.AppMode.HOST_SETUP_INSTALL_PENDING);
511 this.hostController_.getLocalHostState(onHostState);
515 * @return {void} Nothing.
517 remoting.HostSetupDialog.prototype.onInstallDialogRetry = function() {
518 remoting.setMode(remoting.AppMode.HOST_SETUP_INSTALL);
521 /** @type {remoting.HostSetupDialog} */
522 remoting.hostSetupDialog = null;