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() {
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');
105 this.continueInstallButton_
= document
.getElementById(
106 'host-config-install-continue');
107 this.cancelInstallButton_
= document
.getElementById(
108 'host-config-install-dismiss');
109 this.retryInstallButton_
= document
.getElementById(
110 'host-config-install-retry');
112 this.continueInstallButton_
.addEventListener(
113 'click', this.onInstallDialogOk
.bind(this), false);
114 this.cancelInstallButton_
.addEventListener(
115 'click', this.hide
.bind(this), false);
116 this.retryInstallButton_
.addEventListener(
117 'click', this.onInstallDialogRetry
.bind(this), false);
119 /** @type {remoting.HostSetupFlow} */
120 this.flow_
= new remoting
.HostSetupFlow([remoting
.HostSetupFlow
.State
.NONE
]);
122 /** @type {remoting.HostSetupDialog} */
124 /** @param {Event} event The event. */
125 var onPinSubmit = function(event
) {
126 event
.preventDefault();
129 var onPinConfirmFocus = function() {
133 var form
= document
.getElementById('ask-pin-form');
134 form
.addEventListener('submit', onPinSubmit
, false);
135 /** @param {Event} event The event. */
136 var onDaemonPinEntryKeyPress = function(event
) {
137 if (event
.which
== 13) {
138 document
.getElementById('daemon-pin-confirm').focus();
139 event
.preventDefault();
142 /** @param {Event} event A keypress event. */
143 var noDigitsInPin = function(event
) {
144 if (event
.which
== 13) {
145 return; // Otherwise the "submit" action can't be triggered by Enter.
147 if ((event
.which
>= 48) && (event
.which
<= 57)) {
150 event
.preventDefault();
152 this.pinEntry_
.addEventListener('keypress', onDaemonPinEntryKeyPress
, false);
153 this.pinEntry_
.addEventListener('keypress', noDigitsInPin
, false);
154 this.pinConfirm_
.addEventListener('focus', onPinConfirmFocus
, false);
155 this.pinConfirm_
.addEventListener('keypress', noDigitsInPin
, false);
157 this.usageStats_
= document
.getElementById('usagestats-consent');
158 this.usageStatsCheckbox_
= /** @type {HTMLInputElement} */
159 document
.getElementById('usagestats-consent-checkbox');
163 * Show the dialog in order to get a PIN prior to starting the daemon. When the
164 * user clicks OK, the dialog shows a spinner until the daemon has started.
166 * @return {void} Nothing.
168 remoting
.HostSetupDialog
.prototype.showForStart = function() {
169 /** @type {remoting.HostSetupDialog} */
173 * @param {remoting.HostController.State} state
175 var onState = function(state
) {
176 // Although we don't need an access token in order to start the host,
177 // using callWithToken here ensures consistent error handling in the
178 // case where the refresh token is invalid.
179 remoting
.identity
.callWithToken(
180 that
.showForStartWithToken_
.bind(that
, state
),
181 remoting
.showErrorMessage
);
184 this.hostController_
.getLocalHostState(onState
);
188 * @param {remoting.HostController.State} state The current state of the local
190 * @param {string} token The OAuth2 token.
193 remoting
.HostSetupDialog
.prototype.showForStartWithToken_
=
194 function(state
, token
) {
195 /** @type {remoting.HostSetupDialog} */
199 * @param {boolean} supported True if crash dump reporting is supported by
201 * @param {boolean} allowed True if crash dump reporting is allowed.
202 * @param {boolean} set_by_policy True if crash dump reporting is controlled
205 function onGetConsent(supported
, allowed
, set_by_policy
) {
206 that
.usageStats_
.hidden
= !supported
;
207 that
.usageStatsCheckbox_
.checked
= allowed
;
208 that
.usageStatsCheckbox_
.disabled
= set_by_policy
;
211 /** @param {remoting.Error} error */
212 function onError(error
) {
213 console
.error('Error getting consent status: ' + error
);
216 this.usageStats_
.hidden
= false;
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(onGetConsent
, onError
);
226 remoting
.HostSetupFlow
.State
.ASK_PIN
,
227 remoting
.HostSetupFlow
.State
.STARTING_HOST
,
228 remoting
.HostSetupFlow
.State
.HOST_STARTED
];
231 state
!= remoting
.HostController
.State
.NOT_INSTALLED
&&
232 state
!= remoting
.HostController
.State
.INSTALLING
;
234 if (navigator
.platform
.indexOf('Mac') != -1 && !installed
) {
235 flow
.unshift(remoting
.HostSetupFlow
.State
.INSTALL_HOST
);
238 this.startNewFlow_(flow
);
242 * Show the dialog in order to change the PIN associated with a running daemon.
244 * @return {void} Nothing.
246 remoting
.HostSetupDialog
.prototype.showForPin = function() {
247 this.usageStats_
.hidden
= true;
249 [remoting
.HostSetupFlow
.State
.ASK_PIN
,
250 remoting
.HostSetupFlow
.State
.UPDATING_PIN
,
251 remoting
.HostSetupFlow
.State
.UPDATED_PIN
]);
255 * Show the dialog in order to stop the daemon.
257 * @return {void} Nothing.
259 remoting
.HostSetupDialog
.prototype.showForStop = function() {
260 // TODO(sergeyu): Add another step to unregister the host, crubg.com/121146 .
262 [remoting
.HostSetupFlow
.State
.STOPPING_HOST
,
263 remoting
.HostSetupFlow
.State
.HOST_STOPPED
]);
267 * @return {void} Nothing.
269 remoting
.HostSetupDialog
.prototype.hide = function() {
270 remoting
.setMode(remoting
.AppMode
.HOME
);
274 * Starts new flow with the specified sequence of steps.
275 * @param {Array.<remoting.HostSetupFlow.State>} sequence Sequence of steps.
278 remoting
.HostSetupDialog
.prototype.startNewFlow_ = function(sequence
) {
279 this.flow_
= new remoting
.HostSetupFlow(sequence
);
280 this.pinEntry_
.value
= '';
281 this.pinConfirm_
.value
= '';
282 this.pinErrorDiv_
.hidden
= true;
287 * Updates current UI mode according to the current state of the setup
288 * flow and start the action corresponding to the current step (if
292 remoting
.HostSetupDialog
.prototype.updateState_ = function() {
293 remoting
.updateLocalHostState();
295 /** @param {string} tag */
296 function showProcessingMessage(tag
) {
297 var messageDiv
= document
.getElementById('host-setup-processing-message');
298 l10n
.localizeElementFromTag(messageDiv
, tag
);
299 remoting
.setMode(remoting
.AppMode
.HOST_SETUP_PROCESSING
);
301 /** @param {string} tag1
302 * @param {string=} opt_tag2 */
303 function showDoneMessage(tag1
, opt_tag2
) {
304 var messageDiv
= document
.getElementById('host-setup-done-message');
305 l10n
.localizeElementFromTag(messageDiv
, tag1
);
306 messageDiv
= document
.getElementById('host-setup-done-message-2');
308 l10n
.localizeElementFromTag(messageDiv
, opt_tag2
);
310 messageDiv
.innerText
= '';
312 remoting
.setMode(remoting
.AppMode
.HOST_SETUP_DONE
);
314 /** @param {string} tag */
315 function showErrorMessage(tag
) {
316 var errorDiv
= document
.getElementById('host-setup-error-message');
317 l10n
.localizeElementFromTag(errorDiv
, tag
);
318 remoting
.setMode(remoting
.AppMode
.HOST_SETUP_ERROR
);
321 var state
= this.flow_
.getState();
322 if (state
== remoting
.HostSetupFlow
.State
.NONE
) {
324 } else if (state
== remoting
.HostSetupFlow
.State
.ASK_PIN
) {
325 remoting
.setMode(remoting
.AppMode
.HOST_SETUP_ASK_PIN
);
326 } else if (state
== remoting
.HostSetupFlow
.State
.INSTALL_HOST
) {
327 remoting
.setMode(remoting
.AppMode
.HOST_SETUP_INSTALL
);
329 'https://dl.google.com/chrome-remote-desktop/chromeremotedesktop.dmg';
330 } else if (state
== remoting
.HostSetupFlow
.State
.STARTING_HOST
) {
331 showProcessingMessage(/*i18n-content*/'HOST_SETUP_STARTING');
333 } else if (state
== remoting
.HostSetupFlow
.State
.UPDATING_PIN
) {
334 showProcessingMessage(/*i18n-content*/'HOST_SETUP_UPDATING_PIN');
336 } else if (state
== remoting
.HostSetupFlow
.State
.STOPPING_HOST
) {
337 showProcessingMessage(/*i18n-content*/'HOST_SETUP_STOPPING');
339 } else if (state
== remoting
.HostSetupFlow
.State
.HOST_STARTED
) {
340 // TODO(jamiewalch): Only display the second string if the computer's power
341 // management settings indicate that it's necessary.
342 showDoneMessage(/*i18n-content*/'HOST_SETUP_STARTED',
343 /*i18n-content*/'HOST_SETUP_STARTED_DISABLE_SLEEP');
344 } else if (state
== remoting
.HostSetupFlow
.State
.UPDATED_PIN
) {
345 showDoneMessage(/*i18n-content*/'HOST_SETUP_UPDATED_PIN');
346 } else if (state
== remoting
.HostSetupFlow
.State
.HOST_STOPPED
) {
347 showDoneMessage(/*i18n-content*/'HOST_SETUP_STOPPED');
348 } else if (state
== remoting
.HostSetupFlow
.State
.REGISTRATION_FAILED
) {
349 showErrorMessage(/*i18n-content*/'ERROR_HOST_REGISTRATION_FAILED');
350 } else if (state
== remoting
.HostSetupFlow
.State
.START_HOST_FAILED
) {
351 showErrorMessage(/*i18n-content*/'HOST_SETUP_HOST_FAILED');
352 } else if (state
== remoting
.HostSetupFlow
.State
.UPDATE_PIN_FAILED
) {
353 showErrorMessage(/*i18n-content*/'HOST_SETUP_UPDATE_PIN_FAILED');
354 } else if (state
== remoting
.HostSetupFlow
.State
.STOP_HOST_FAILED
) {
355 showErrorMessage(/*i18n-content*/'HOST_SETUP_STOP_FAILED');
360 * Registers and starts the host.
362 remoting
.HostSetupDialog
.prototype.startHost_ = function() {
363 /** @type {remoting.HostSetupDialog} */
365 /** @type {remoting.HostSetupFlow} */
366 var flow
= this.flow_
;
368 /** @return {boolean} */
369 function isFlowActive() {
370 if (flow
!== that
.flow_
||
371 flow
.getState() != remoting
.HostSetupFlow
.State
.STARTING_HOST
) {
372 console
.error('Host setup was interrupted when starting the host');
378 function onHostStarted() {
379 if (isFlowActive()) {
380 flow
.switchToNextStep();
385 /** @param {remoting.Error} error */
386 function onError(error
) {
387 if (isFlowActive()) {
388 flow
.switchToErrorState(error
);
393 this.hostController_
.start(this.flow_
.pin
, this.flow_
.consent
, onHostStarted
,
397 remoting
.HostSetupDialog
.prototype.updatePin_ = 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
.UPDATING_PIN
) {
407 console
.error('Host setup was interrupted when updating PIN');
413 function onPinUpdated() {
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_
.updatePin(flow
.pin
, onPinUpdated
, onError
);
434 remoting
.HostSetupDialog
.prototype.stopHost_ = function() {
435 /** @type {remoting.HostSetupDialog} */
437 /** @type {remoting.HostSetupFlow} */
438 var flow
= this.flow_
;
440 /** @return {boolean} */
441 function isFlowActive() {
442 if (flow
!== that
.flow_
||
443 flow
.getState() != remoting
.HostSetupFlow
.State
.STOPPING_HOST
) {
444 console
.error('Host setup was interrupted when stopping the host');
450 function onHostStopped() {
451 if (isFlowActive()) {
452 flow
.switchToNextStep();
457 /** @param {remoting.Error} error */
458 function onError(error
) {
459 if (isFlowActive()) {
460 flow
.switchToErrorState(error
);
465 this.hostController_
.stop(onHostStopped
, onError
);
469 * Validates the PIN and shows an error message if it's invalid.
470 * @return {boolean} true if the PIN is valid, false otherwise.
473 remoting
.HostSetupDialog
.prototype.validatePin_ = function() {
474 var pin
= this.pinEntry_
.value
;
475 var pinIsValid
= remoting
.HostSetupDialog
.validPin_(pin
);
477 l10n
.localizeElementFromTag(
478 this.pinErrorMessage_
, /*i18n-content*/'INVALID_PIN');
480 this.pinErrorDiv_
.hidden
= pinIsValid
;
485 remoting
.HostSetupDialog
.prototype.onPinSubmit_ = function() {
486 if (this.flow_
.getState() != remoting
.HostSetupFlow
.State
.ASK_PIN
) {
487 console
.error('PIN submitted in an invalid state', this.flow_
.getState());
490 var pin1
= this.pinEntry_
.value
;
491 var pin2
= this.pinConfirm_
.value
;
493 l10n
.localizeElementFromTag(
494 this.pinErrorMessage_
, /*i18n-content*/'PINS_NOT_EQUAL');
495 this.pinErrorDiv_
.hidden
= false;
496 this.prepareForPinEntry_();
499 if (!this.validatePin_()) {
500 this.prepareForPinEntry_();
503 this.flow_
.pin
= pin1
;
504 this.flow_
.consent
= !this.usageStats_
.hidden
&&
505 this.usageStatsCheckbox_
.checked
;
506 this.flow_
.switchToNextStep();
511 remoting
.HostSetupDialog
.prototype.prepareForPinEntry_ = function() {
512 this.pinEntry_
.value
= '';
513 this.pinConfirm_
.value
= '';
514 this.pinEntry_
.focus();
518 * Returns whether a PIN is valid.
521 * @param {string} pin A PIN.
522 * @return {boolean} Whether the PIN is valid.
524 remoting
.HostSetupDialog
.validPin_ = function(pin
) {
525 if (pin
.length
< 6) {
528 for (var i
= 0; i
< pin
.length
; i
++) {
529 var c
= pin
.charAt(i
);
530 if ((c
< '0') || (c
> '9')) {
538 * @return {void} Nothing.
540 remoting
.HostSetupDialog
.prototype.onInstallDialogOk = function() {
541 this.continueInstallButton_
.disabled
= true;
542 this.cancelInstallButton_
.disabled
= true;
544 /** @type {remoting.HostSetupDialog} */
547 /** @param {remoting.HostController.State} state */
548 var onHostState = function(state
) {
549 that
.continueInstallButton_
.disabled
= false;
550 that
.cancelInstallButton_
.disabled
= false;
552 state
!= remoting
.HostController
.State
.NOT_INSTALLED
&&
553 state
!= remoting
.HostController
.State
.INSTALLING
;
555 that
.flow_
.switchToNextStep();
558 remoting
.setMode(remoting
.AppMode
.HOST_SETUP_INSTALL_PENDING
);
562 this.hostController_
.getLocalHostState(onHostState
);
566 * @return {void} Nothing.
568 remoting
.HostSetupDialog
.prototype.onInstallDialogRetry = function() {
569 remoting
.setMode(remoting
.AppMode
.HOST_SETUP_INSTALL
);
572 /** @type {remoting.HostSetupDialog} */
573 remoting
.hostSetupDialog
= null;