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
.hasTag(remoting
.Error
.Tag
.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
.hasTag(remoting
.Error
.Tag
.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.
97 * @param {function(!remoting.Error)} onError Function to call when an error
101 remoting
.HostSetupDialog = function(hostController
, onError
) {
102 this.hostController_
= hostController
;
103 this.onError_
= onError
;
105 this.pinEntry_
= document
.getElementById('daemon-pin-entry');
106 this.pinConfirm_
= document
.getElementById('daemon-pin-confirm');
107 this.pinErrorDiv_
= document
.getElementById('daemon-pin-error-div');
108 this.pinErrorMessage_
= document
.getElementById('daemon-pin-error-message');
110 /** @type {remoting.HostSetupFlow} */
111 this.flow_
= new remoting
.HostSetupFlow([remoting
.HostSetupFlow
.State
.NONE
]);
113 /** @type {remoting.HostSetupDialog} */
115 /** @param {Event} event The event. */
116 var onPinSubmit = function(event
) {
117 event
.preventDefault();
120 var onPinConfirmFocus = function() {
124 var form
= document
.getElementById('ask-pin-form');
125 form
.addEventListener('submit', onPinSubmit
, false);
126 /** @param {Event} event The event. */
127 var onDaemonPinEntryKeyPress = function(event
) {
128 if (event
.which
== 13) {
129 document
.getElementById('daemon-pin-confirm').focus();
130 event
.preventDefault();
133 /** @param {Event} event A keypress event. */
134 var noDigitsInPin = function(event
) {
135 if (event
.which
== 13) {
136 return; // Otherwise the "submit" action can't be triggered by Enter.
138 if ((event
.which
>= 48) && (event
.which
<= 57)) {
141 event
.preventDefault();
143 this.pinEntry_
.addEventListener('keypress', onDaemonPinEntryKeyPress
, false);
144 this.pinEntry_
.addEventListener('keypress', noDigitsInPin
, false);
145 this.pinConfirm_
.addEventListener('focus', onPinConfirmFocus
, false);
146 this.pinConfirm_
.addEventListener('keypress', noDigitsInPin
, false);
148 this.usageStats_
= document
.getElementById('usagestats-consent');
149 this.usageStatsCheckbox_
= /** @type {HTMLInputElement} */
150 (document
.getElementById('usagestats-consent-checkbox'));
154 * Show the dialog in order to get a PIN prior to starting the daemon. When the
155 * user clicks OK, the dialog shows a spinner until the daemon has started.
157 * @return {void} Nothing.
159 remoting
.HostSetupDialog
.prototype.showForStart = function() {
160 /** @type {remoting.HostSetupDialog} */
164 * @param {remoting.HostController.State} state
166 var onState = function(state
) {
167 // Although we don't need an access token in order to start the host,
168 // using callWithToken here ensures consistent error handling in the
169 // case where the refresh token is invalid.
170 remoting
.identity
.getToken().then(
171 that
.showForStartWithToken_
.bind(that
, state
),
172 remoting
.Error
.handler(that
.onError_
));
175 this.hostController_
.getLocalHostState(onState
);
179 * @param {remoting.HostController.State} state The current state of the local
181 * @param {string} token The OAuth2 token.
184 remoting
.HostSetupDialog
.prototype.showForStartWithToken_
=
185 function(state
, token
) {
186 /** @type {remoting.HostSetupDialog} */
190 * @param {remoting.UsageStatsConsent} consent
192 function onGetConsent(consent
) {
193 // Hide the usage stats check box if it is not supported or the policy
194 // doesn't allow usage stats collection.
195 var checkBoxLabel
= that
.usageStats_
.querySelector('.checkbox-label');
196 that
.usageStats_
.hidden
= !consent
.supported
||
197 (consent
.setByPolicy
&& !consent
.allowed
);
198 that
.usageStatsCheckbox_
.checked
= consent
.allowed
;
200 that
.usageStatsCheckbox_
.disabled
= consent
.setByPolicy
;
201 checkBoxLabel
.classList
.toggle('disabled', consent
.setByPolicy
);
203 if (consent
.setByPolicy
) {
204 that
.usageStats_
.title
= l10n
.getTranslationOrError(
205 /*i18n-content*/ 'SETTING_MANAGED_BY_POLICY');
207 that
.usageStats_
.title
= '';
211 /** @param {!remoting.Error} error */
212 function onError(error
) {
213 console
.error('Error getting consent status: ' + error
.toString());
216 this.usageStats_
.hidden
= true;
217 this.usageStatsCheckbox_
.checked
= false;
219 // Prevent user from ticking the box until the current consent status is
221 this.usageStatsCheckbox_
.disabled
= true;
223 this.hostController_
.getConsent().then(
224 onGetConsent
, remoting
.Error
.handler(onError
));
227 remoting
.HostSetupFlow
.State
.INSTALL_HOST
,
228 remoting
.HostSetupFlow
.State
.ASK_PIN
,
229 remoting
.HostSetupFlow
.State
.STARTING_HOST
,
230 remoting
.HostSetupFlow
.State
.HOST_STARTED
];
233 state
!= remoting
.HostController
.State
.NOT_INSTALLED
&&
234 state
!= remoting
.HostController
.State
.UNKNOWN
;
236 // Skip the installation step when the host is already installed.
241 this.startNewFlow_(flow
);
245 * Show the dialog in order to change the PIN associated with a running daemon.
247 * @return {void} Nothing.
249 remoting
.HostSetupDialog
.prototype.showForPin = function() {
250 this.usageStats_
.hidden
= true;
252 [remoting
.HostSetupFlow
.State
.ASK_PIN
,
253 remoting
.HostSetupFlow
.State
.UPDATING_PIN
,
254 remoting
.HostSetupFlow
.State
.UPDATED_PIN
]);
258 * Show the dialog in order to stop the daemon.
260 * @return {void} Nothing.
262 remoting
.HostSetupDialog
.prototype.showForStop = function() {
263 // TODO(sergeyu): Add another step to unregister the host, crubg.com/121146 .
265 [remoting
.HostSetupFlow
.State
.STOPPING_HOST
,
266 remoting
.HostSetupFlow
.State
.HOST_STOPPED
]);
270 * @return {void} Nothing.
272 remoting
.HostSetupDialog
.prototype.hide = function() {
273 remoting
.setMode(remoting
.AppMode
.HOME
);
277 * Starts new flow with the specified sequence of steps.
278 * @param {Array<remoting.HostSetupFlow.State>} sequence Sequence of steps.
281 remoting
.HostSetupDialog
.prototype.startNewFlow_ = function(sequence
) {
282 this.flow_
= new remoting
.HostSetupFlow(sequence
);
283 this.pinEntry_
.value
= '';
284 this.pinConfirm_
.value
= '';
285 this.pinErrorDiv_
.hidden
= true;
290 * Updates current UI mode according to the current state of the setup
291 * flow and start the action corresponding to the current step (if
295 remoting
.HostSetupDialog
.prototype.updateState_ = function() {
296 remoting
.updateLocalHostState();
298 /** @param {string} tag1
299 * @param {string=} opt_tag2 */
300 function showDoneMessage(tag1
, opt_tag2
) {
301 var messageDiv
= document
.getElementById('host-setup-done-message');
302 l10n
.localizeElementFromTag(messageDiv
, tag1
);
303 messageDiv
= document
.getElementById('host-setup-done-message-2');
305 l10n
.localizeElementFromTag(messageDiv
, opt_tag2
);
307 messageDiv
.innerText
= '';
309 remoting
.setMode(remoting
.AppMode
.HOST_SETUP_DONE
);
311 /** @param {string} tag */
312 function showErrorMessage(tag
) {
313 var errorDiv
= document
.getElementById('host-setup-error-message');
314 l10n
.localizeElementFromTag(errorDiv
, tag
);
315 remoting
.setMode(remoting
.AppMode
.HOST_SETUP_ERROR
);
318 var state
= this.flow_
.getState();
319 if (state
== remoting
.HostSetupFlow
.State
.NONE
) {
321 } else if (state
== remoting
.HostSetupFlow
.State
.ASK_PIN
) {
322 remoting
.setMode(remoting
.AppMode
.HOST_SETUP_ASK_PIN
);
323 } else if (state
== remoting
.HostSetupFlow
.State
.INSTALL_HOST
) {
325 } else if (state
== remoting
.HostSetupFlow
.State
.STARTING_HOST
) {
326 remoting
.showSetupProcessingMessage(/*i18n-content*/'HOST_SETUP_STARTING');
328 } else if (state
== remoting
.HostSetupFlow
.State
.UPDATING_PIN
) {
329 remoting
.showSetupProcessingMessage(
330 /*i18n-content*/'HOST_SETUP_UPDATING_PIN');
332 } else if (state
== remoting
.HostSetupFlow
.State
.STOPPING_HOST
) {
333 remoting
.showSetupProcessingMessage(/*i18n-content*/'HOST_SETUP_STOPPING');
335 } else if (state
== remoting
.HostSetupFlow
.State
.HOST_STARTED
) {
336 // TODO(jamiewalch): Only display the second string if the computer's power
337 // management settings indicate that it's necessary.
338 showDoneMessage(/*i18n-content*/'HOST_SETUP_STARTED',
339 /*i18n-content*/'HOST_SETUP_STARTED_DISABLE_SLEEP');
340 } else if (state
== remoting
.HostSetupFlow
.State
.UPDATED_PIN
) {
341 showDoneMessage(/*i18n-content*/'HOST_SETUP_UPDATED_PIN');
342 } else if (state
== remoting
.HostSetupFlow
.State
.HOST_STOPPED
) {
343 showDoneMessage(/*i18n-content*/'HOST_SETUP_STOPPED');
344 } else if (state
== remoting
.HostSetupFlow
.State
.REGISTRATION_FAILED
) {
345 showErrorMessage(/*i18n-content*/'ERROR_HOST_REGISTRATION_FAILED');
346 } else if (state
== remoting
.HostSetupFlow
.State
.START_HOST_FAILED
) {
347 showErrorMessage(/*i18n-content*/'HOST_SETUP_HOST_FAILED');
348 } else if (state
== remoting
.HostSetupFlow
.State
.UPDATE_PIN_FAILED
) {
349 showErrorMessage(/*i18n-content*/'HOST_SETUP_UPDATE_PIN_FAILED');
350 } else if (state
== remoting
.HostSetupFlow
.State
.STOP_HOST_FAILED
) {
351 showErrorMessage(/*i18n-content*/'HOST_SETUP_STOP_FAILED');
356 * Shows the prompt that asks the user to install the host.
358 remoting
.HostSetupDialog
.prototype.installHost_ = function() {
359 /** @type {remoting.HostSetupDialog} */
361 /** @type {remoting.HostSetupFlow} */
362 var flow
= this.flow_
;
364 /** @param {!remoting.Error} error */
365 var onError = function(error
) {
366 flow
.switchToErrorState(error
);
370 var onDone = function() {
371 that
.hostController_
.getLocalHostState(onHostState
);
374 /** @param {remoting.HostController.State} state */
375 var onHostState = function(state
) {
377 state
!= remoting
.HostController
.State
.NOT_INSTALLED
&&
378 state
!= remoting
.HostController
.State
.UNKNOWN
;
381 that
.flow_
.switchToNextStep();
384 // Prompt the user again if the host is not installed.
385 hostInstallDialog
.tryAgain();
389 /** @type {remoting.HostInstallDialog} */
390 var hostInstallDialog
= new remoting
.HostInstallDialog();
391 hostInstallDialog
.show(onDone
, onError
);
395 * Registers and starts the host.
397 remoting
.HostSetupDialog
.prototype.startHost_ = function() {
398 /** @type {remoting.HostSetupDialog} */
400 /** @type {remoting.HostSetupFlow} */
401 var flow
= this.flow_
;
403 /** @return {boolean} */
404 function isFlowActive() {
405 if (flow
!== that
.flow_
||
406 flow
.getState() != remoting
.HostSetupFlow
.State
.STARTING_HOST
) {
407 console
.error('Host setup was interrupted when starting the host');
413 function onHostStarted() {
414 if (isFlowActive()) {
415 flow
.switchToNextStep();
420 /** @param {!remoting.Error} error */
421 function onError(error
) {
422 if (isFlowActive()) {
423 flow
.switchToErrorState(error
);
428 this.hostController_
.start(this.flow_
.pin
, this.flow_
.consent
).then(
431 remoting
.Error
.handler(onError
)
435 remoting
.HostSetupDialog
.prototype.updatePin_ = function() {
436 /** @type {remoting.HostSetupDialog} */
438 /** @type {remoting.HostSetupFlow} */
439 var flow
= this.flow_
;
441 /** @return {boolean} */
442 function isFlowActive() {
443 if (flow
!== that
.flow_
||
444 flow
.getState() != remoting
.HostSetupFlow
.State
.UPDATING_PIN
) {
445 console
.error('Host setup was interrupted when updating PIN');
451 function onPinUpdated() {
452 if (isFlowActive()) {
453 flow
.switchToNextStep();
458 /** @param {!remoting.Error} error */
459 function onError(error
) {
460 if (isFlowActive()) {
461 flow
.switchToErrorState(error
);
466 this.hostController_
.updatePin(flow
.pin
, onPinUpdated
, onError
);
472 remoting
.HostSetupDialog
.prototype.stopHost_ = function() {
473 /** @type {remoting.HostSetupDialog} */
475 /** @type {remoting.HostSetupFlow} */
476 var flow
= this.flow_
;
478 /** @return {boolean} */
479 function isFlowActive() {
480 if (flow
!== that
.flow_
||
481 flow
.getState() != remoting
.HostSetupFlow
.State
.STOPPING_HOST
) {
482 console
.error('Host setup was interrupted when stopping the host');
488 function onHostStopped() {
489 if (isFlowActive()) {
490 flow
.switchToNextStep();
495 /** @param {!remoting.Error} error */
496 function onError(error
) {
497 if (isFlowActive()) {
498 flow
.switchToErrorState(error
);
503 this.hostController_
.stop(onHostStopped
, onError
);
507 * Validates the PIN and shows an error message if it's invalid.
508 * @return {boolean} true if the PIN is valid, false otherwise.
511 remoting
.HostSetupDialog
.prototype.validatePin_ = function() {
512 var pin
= this.pinEntry_
.value
;
513 var pinIsValid
= remoting
.HostSetupDialog
.validPin_(pin
);
515 l10n
.localizeElementFromTag(
516 this.pinErrorMessage_
, /*i18n-content*/'INVALID_PIN');
518 this.pinErrorDiv_
.hidden
= pinIsValid
;
523 remoting
.HostSetupDialog
.prototype.onPinSubmit_ = function() {
524 if (this.flow_
.getState() != remoting
.HostSetupFlow
.State
.ASK_PIN
) {
525 console
.error('PIN submitted in an invalid state', this.flow_
.getState());
528 var pin1
= this.pinEntry_
.value
;
529 var pin2
= this.pinConfirm_
.value
;
531 l10n
.localizeElementFromTag(
532 this.pinErrorMessage_
, /*i18n-content*/'PINS_NOT_EQUAL');
533 this.pinErrorDiv_
.hidden
= false;
534 this.prepareForPinEntry_();
537 if (!this.validatePin_()) {
538 this.prepareForPinEntry_();
541 this.flow_
.pin
= pin1
;
542 this.flow_
.consent
= !this.usageStats_
.hidden
&&
543 this.usageStatsCheckbox_
.checked
;
544 this.flow_
.switchToNextStep();
549 remoting
.HostSetupDialog
.prototype.prepareForPinEntry_ = function() {
550 this.pinEntry_
.value
= '';
551 this.pinConfirm_
.value
= '';
552 this.pinEntry_
.focus();
556 * Returns whether a PIN is valid.
559 * @param {string} pin A PIN.
560 * @return {boolean} Whether the PIN is valid.
562 remoting
.HostSetupDialog
.validPin_ = function(pin
) {
563 if (pin
.length
< 6) {
566 for (var i
= 0; i
< pin
.length
; i
++) {
567 var c
= pin
.charAt(i
);
568 if ((c
< '0') || (c
> '9')) {
575 /** @type {remoting.HostSetupDialog} */
576 remoting
.hostSetupDialog
= null;