Separate Simple Backend creation from initialization.
[chromium-blink-merge.git] / remoting / webapp / host_setup_dialog.js
blobc3b76e9d4993e362fe785a9a84ea5540d40bbc39
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.
5 'use strict';
7 /** @suppress {duplicate} */
8 var remoting = remoting || {};
10 /**
11  * @param {Array.<remoting.HostSetupFlow.State>} sequence Sequence of
12  *     steps for the flow.
13  * @constructor
14  */
15 remoting.HostSetupFlow = function(sequence) {
16   this.sequence_ = sequence;
17   this.currentStep_ = 0;
18   this.state_ = sequence[0];
19   this.pin = '';
20   this.consent = false;
23 /** @enum {number} */
24 remoting.HostSetupFlow.State = {
25   NONE: 0,
27   // Dialog states.
28   ASK_PIN: 1,
30   // Used on Mac OS X to prompt the user to manually install a .dmg package.
31   INSTALL_HOST: 2,
33   // Processing states.
34   STARTING_HOST: 3,
35   UPDATING_PIN: 4,
36   STOPPING_HOST: 5,
38   // Done states.
39   HOST_STARTED: 6,
40   UPDATED_PIN: 7,
41   HOST_STOPPED: 8,
43   // Failure states.
44   REGISTRATION_FAILED: 9,
45   START_HOST_FAILED: 10,
46   UPDATE_PIN_FAILED: 11,
47   STOP_HOST_FAILED: 12
50 /** @return {remoting.HostSetupFlow.State} Current state of the flow. */
51 remoting.HostSetupFlow.prototype.getState = function() {
52   return this.state_;
55 /**
56  * @param {remoting.HostController.AsyncResult} result Result of the
57  * current step.
58  * @return {remoting.HostSetupFlow.State} New state.
59  */
60 remoting.HostSetupFlow.prototype.switchToNextStep = function(result) {
61   if (this.state_ == remoting.HostSetupFlow.State.NONE) {
62     return this.state_;
63   }
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_];
70     } else {
71       this.state_ = remoting.HostSetupFlow.State.NONE;
72     }
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;
76   } else {
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;
81       } else {
82         this.state_ = remoting.HostSetupFlow.State.START_HOST_FAILED;
83       }
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;
88     } else {
89       // TODO(sergeyu): Add other error states and use them here.
90       this.state_ = remoting.HostSetupFlow.State.START_HOST_FAILED;
91     }
92   }
93   return this.state_;
96 /**
97  * @param {remoting.HostController} hostController The HostController
98  * responsible for the host daemon.
99  * @constructor
100  */
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} */
125   var that = this;
126   /** @param {Event} event The event. */
127   var onPinSubmit = function(event) {
128     event.preventDefault();
129     that.onPinSubmit_();
130   };
131   var onPinConfirmFocus = function() {
132     that.validatePin_();
133   };
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();
142     }
143   };
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.
148     }
149     if ((event.which >= 48) && (event.which <= 57)) {
150       return;
151     }
152     event.preventDefault();
153   };
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.
169  */
170 remoting.HostSetupDialog.prototype.showForStart = function() {
171   /** @type {remoting.HostSetupDialog} */
172   var that = this;
174   /**
175    * @param {remoting.HostController.State} state
176    */
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);
184   };
186   this.hostController_.getLocalHostState(onState);
190  * @param {remoting.HostController.State} state The current state of the local
191  *     host.
192  * @param {string} token The OAuth2 token.
193  * @private
194  */
195 remoting.HostSetupDialog.prototype.showForStartWithToken_ =
196     function(state, token) {
197   /** @type {remoting.HostSetupDialog} */
198   var that = this;
200   /**
201    * @param {boolean} supported True if crash dump reporting is supported by
202    *     the host.
203    * @param {boolean} allowed True if crash dump reporting is allowed.
204    * @param {boolean} set_by_policy True if crash dump reporting is controlled
205    *     by policy.
206    */
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;
211   };
212   this.usageStats_.hidden = false;
213   this.usageStatsCheckbox_.checked = false;
214   this.hostController_.getConsent(onGetConsent);
216   var flow = [
217       remoting.HostSetupFlow.State.ASK_PIN,
218       remoting.HostSetupFlow.State.STARTING_HOST,
219       remoting.HostSetupFlow.State.HOST_STARTED];
221   var installed =
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);
227   }
229   this.startNewFlow_(flow);
233  * Show the dialog in order to change the PIN associated with a running daemon.
235  * @return {void} Nothing.
236  */
237 remoting.HostSetupDialog.prototype.showForPin = function() {
238   this.usageStats_.hidden = true;
239   this.startNewFlow_(
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.
249  */
250 remoting.HostSetupDialog.prototype.showForStop = function() {
251   // TODO(sergeyu): Add another step to unregister the host, crubg.com/121146 .
252   this.startNewFlow_(
253       [remoting.HostSetupFlow.State.STOPPING_HOST,
254        remoting.HostSetupFlow.State.HOST_STOPPED]);
258  * @return {void} Nothing.
259  */
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.
267  * @private
268  */
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;
274   this.updateState_();
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
280  * any).
281  * @private
282  */
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);
291   }
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');
298     if (opt_tag2) {
299       l10n.localizeElementFromTag(messageDiv, opt_tag2);
300     } else {
301       messageDiv.innerText = '';
302     }
303     remoting.setMode(remoting.AppMode.HOST_SETUP_DONE);
304   }
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);
310   }
312   var state = this.flow_.getState();
313   if (state == remoting.HostSetupFlow.State.NONE) {
314     this.hide();
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);
319     window.location =
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');
323     this.startHost_();
324   } else if (state == remoting.HostSetupFlow.State.UPDATING_PIN) {
325     showProcessingMessage(/*i18n-content*/'HOST_SETUP_UPDATING_PIN');
326     this.updatePin_();
327   } else if (state == remoting.HostSetupFlow.State.STOPPING_HOST) {
328     showProcessingMessage(/*i18n-content*/'HOST_SETUP_STOPPING');
329     this.stopHost_();
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');
347   }
351  * Registers and starts the host.
352  */
353 remoting.HostSetupDialog.prototype.startHost_ = function() {
354   /** @type {remoting.HostSetupDialog} */
355   var that = this;
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');
364       return;
365     }
367     flow.switchToNextStep(result);
368     that.updateState_();
369   }
370   this.hostController_.start(this.flow_.pin, this.flow_.consent, onHostStarted);
373 remoting.HostSetupDialog.prototype.updatePin_ = function() {
374   /** @type {remoting.HostSetupDialog} */
375   var that = this;
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');
384       return;
385     }
387     flow.switchToNextStep(result);
388     that.updateState_();
389   }
391   this.hostController_.updatePin(flow.pin, onPinUpdated);
395  * Stops the host.
396  */
397 remoting.HostSetupDialog.prototype.stopHost_ = function() {
398   /** @type {remoting.HostSetupDialog} */
399   var that = this;
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');
408       return;
409     }
411     flow.switchToNextStep(result);
412     that.updateState_();
413   }
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.
420  * @private
421  */
422 remoting.HostSetupDialog.prototype.validatePin_ = function() {
423   var pin = this.pinEntry_.value;
424   var pinIsValid = remoting.HostSetupDialog.validPin_(pin);
425   if (!pinIsValid) {
426     l10n.localizeElementFromTag(
427         this.pinErrorMessage_, /*i18n-content*/'INVALID_PIN');
428   }
429   this.pinErrorDiv_.hidden = pinIsValid;
430   return pinIsValid;
433 /** @private */
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());
437     return;
438   }
439   var pin1 = this.pinEntry_.value;
440   var pin2 = this.pinConfirm_.value;
441   if (pin1 != pin2) {
442     l10n.localizeElementFromTag(
443         this.pinErrorMessage_, /*i18n-content*/'PINS_NOT_EQUAL');
444     this.pinErrorDiv_.hidden = false;
445     this.prepareForPinEntry_();
446     return;
447   }
448   if (!this.validatePin_()) {
449     this.prepareForPinEntry_();
450     return;
451   }
452   this.flow_.pin = pin1;
453   this.flow_.consent = !this.usageStats_.hidden &&
454       this.usageStatsCheckbox_.checked;
455   this.flow_.switchToNextStep(remoting.HostController.AsyncResult.OK);
456   this.updateState_();
459 /** @private */
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.
469  * @private
470  * @param {string} pin A PIN.
471  * @return {boolean} Whether the PIN is valid.
472  */
473 remoting.HostSetupDialog.validPin_ = function(pin) {
474   if (pin.length < 6) {
475     return false;
476   }
477   for (var i = 0; i < pin.length; i++) {
478     var c = pin.charAt(i);
479     if ((c < '0') || (c > '9')) {
480       return false;
481     }
482   }
483   return true;
487  * @return {void} Nothing.
488  */
489 remoting.HostSetupDialog.prototype.onInstallDialogOk = function() {
490   this.continueInstallButton_.disabled = true;
491   this.cancelInstallButton_.disabled = true;
493   /** @type {remoting.HostSetupDialog} */
494   var that = this;
496   /** @param {remoting.HostController.State} state */
497   var onHostState = function(state) {
498     that.continueInstallButton_.disabled = false;
499     that.cancelInstallButton_.disabled = false;
500     var installed =
501         state != remoting.HostController.State.NOT_INSTALLED &&
502         state != remoting.HostController.State.INSTALLING;
503     if (installed) {
504       that.flow_.switchToNextStep(remoting.HostController.AsyncResult.OK);
505       that.updateState_();
506     } else {
507       remoting.setMode(remoting.AppMode.HOST_SETUP_INSTALL_PENDING);
508     }
509   };
511   this.hostController_.getLocalHostState(onHostState);
515  * @return {void} Nothing.
516  */
517 remoting.HostSetupDialog.prototype.onInstallDialogRetry = function() {
518   remoting.setMode(remoting.AppMode.HOST_SETUP_INSTALL);
521 /** @type {remoting.HostSetupDialog} */
522 remoting.hostSetupDialog = null;