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;