• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.
4
5'use strict';
6
7/** @suppress {duplicate} */
8var remoting = remoting || {};
9
10/**
11 * @param {Array.<remoting.HostSetupFlow.State>} sequence Sequence of
12 *     steps for the flow.
13 * @constructor
14 */
15remoting.HostSetupFlow = function(sequence) {
16  this.sequence_ = sequence;
17  this.currentStep_ = 0;
18  this.state_ = sequence[0];
19  this.pin = '';
20  this.consent = false;
21};
22
23/** @enum {number} */
24remoting.HostSetupFlow.State = {
25  NONE: 0,
26
27  // Dialog states.
28  ASK_PIN: 1,
29
30  // Prompts the user to install the host package.
31  INSTALL_HOST: 2,
32
33  // Processing states.
34  STARTING_HOST: 3,
35  UPDATING_PIN: 4,
36  STOPPING_HOST: 5,
37
38  // Done states.
39  HOST_STARTED: 6,
40  UPDATED_PIN: 7,
41  HOST_STOPPED: 8,
42
43  // Failure states.
44  REGISTRATION_FAILED: 9,
45  START_HOST_FAILED: 10,
46  UPDATE_PIN_FAILED: 11,
47  STOP_HOST_FAILED: 12
48};
49
50/** @return {remoting.HostSetupFlow.State} Current state of the flow. */
51remoting.HostSetupFlow.prototype.getState = function() {
52  return this.state_;
53};
54
55remoting.HostSetupFlow.prototype.switchToNextStep = function() {
56  if (this.state_ == remoting.HostSetupFlow.State.NONE) {
57    return;
58  }
59
60  if (this.currentStep_ < this.sequence_.length - 1) {
61    this.currentStep_ += 1;
62    this.state_ = this.sequence_[this.currentStep_];
63  } else {
64    this.state_ = remoting.HostSetupFlow.State.NONE;
65  }
66};
67
68/**
69 * @param {remoting.Error} error
70 */
71remoting.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;
75  } else {
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;
80      } else {
81        this.state_ = remoting.HostSetupFlow.State.START_HOST_FAILED;
82      }
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;
87    } else {
88      // TODO(sergeyu): Add other error states and use them here.
89      this.state_ = remoting.HostSetupFlow.State.START_HOST_FAILED;
90    }
91  }
92};
93
94/**
95 * @param {remoting.HostController} hostController The HostController
96 * responsible for the host daemon.
97 * @constructor
98 */
99remoting.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
106  /** @type {remoting.HostSetupFlow} */
107  this.flow_ = new remoting.HostSetupFlow([remoting.HostSetupFlow.State.NONE]);
108
109  /** @type {remoting.HostSetupDialog} */
110  var that = this;
111  /** @param {Event} event The event. */
112  var onPinSubmit = function(event) {
113    event.preventDefault();
114    that.onPinSubmit_();
115  };
116  var onPinConfirmFocus = function() {
117    that.validatePin_();
118  };
119
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();
127    }
128  };
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.
133    }
134    if ((event.which >= 48) && (event.which <= 57)) {
135      return;
136    }
137    event.preventDefault();
138  };
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);
143
144  this.usageStats_ = document.getElementById('usagestats-consent');
145  this.usageStatsCheckbox_ = /** @type {HTMLInputElement} */
146      document.getElementById('usagestats-consent-checkbox');
147};
148
149/**
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.
152 *
153 * @return {void} Nothing.
154 */
155remoting.HostSetupDialog.prototype.showForStart = function() {
156  /** @type {remoting.HostSetupDialog} */
157  var that = this;
158
159  /**
160   * @param {remoting.HostController.State} state
161   */
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);
169  };
170
171  this.hostController_.getLocalHostState(onState);
172};
173
174/**
175 * @param {remoting.HostController.State} state The current state of the local
176 *     host.
177 * @param {string} token The OAuth2 token.
178 * @private
179 */
180remoting.HostSetupDialog.prototype.showForStartWithToken_ =
181    function(state, token) {
182  /** @type {remoting.HostSetupDialog} */
183  var that = this;
184
185  /**
186   * @param {boolean} supported True if crash dump reporting is supported by
187   *     the host.
188   * @param {boolean} allowed True if crash dump reporting is allowed.
189   * @param {boolean} set_by_policy True if crash dump reporting is controlled
190   *     by policy.
191   */
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;
196  }
197
198  /** @param {remoting.Error} error */
199  function onError(error) {
200    console.error('Error getting consent status: ' + error);
201  }
202
203  this.usageStats_.hidden = false;
204  this.usageStatsCheckbox_.checked = false;
205
206  // Prevent user from ticking the box until the current consent status is
207  // known.
208  this.usageStatsCheckbox_.disabled = true;
209
210  this.hostController_.getConsent(onGetConsent, onError);
211
212  var flow = [
213      remoting.HostSetupFlow.State.INSTALL_HOST,
214      remoting.HostSetupFlow.State.ASK_PIN,
215      remoting.HostSetupFlow.State.STARTING_HOST,
216      remoting.HostSetupFlow.State.HOST_STARTED];
217
218  var installed =
219      state != remoting.HostController.State.NOT_INSTALLED &&
220      state != remoting.HostController.State.INSTALLING;
221
222  // Skip the installation step when the host is already installed.
223  if (installed) {
224    flow.shift();
225  }
226
227  this.startNewFlow_(flow);
228};
229
230/**
231 * Show the dialog in order to change the PIN associated with a running daemon.
232 *
233 * @return {void} Nothing.
234 */
235remoting.HostSetupDialog.prototype.showForPin = function() {
236  this.usageStats_.hidden = true;
237  this.startNewFlow_(
238      [remoting.HostSetupFlow.State.ASK_PIN,
239       remoting.HostSetupFlow.State.UPDATING_PIN,
240       remoting.HostSetupFlow.State.UPDATED_PIN]);
241};
242
243/**
244 * Show the dialog in order to stop the daemon.
245 *
246 * @return {void} Nothing.
247 */
248remoting.HostSetupDialog.prototype.showForStop = function() {
249  // TODO(sergeyu): Add another step to unregister the host, crubg.com/121146 .
250  this.startNewFlow_(
251      [remoting.HostSetupFlow.State.STOPPING_HOST,
252       remoting.HostSetupFlow.State.HOST_STOPPED]);
253};
254
255/**
256 * @return {void} Nothing.
257 */
258remoting.HostSetupDialog.prototype.hide = function() {
259  remoting.setMode(remoting.AppMode.HOME);
260};
261
262/**
263 * Starts new flow with the specified sequence of steps.
264 * @param {Array.<remoting.HostSetupFlow.State>} sequence Sequence of steps.
265 * @private
266 */
267remoting.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;
272  this.updateState_();
273};
274
275/**
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
278 * any).
279 * @private
280 */
281remoting.HostSetupDialog.prototype.updateState_ = function() {
282  remoting.updateLocalHostState();
283
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');
290    if (opt_tag2) {
291      l10n.localizeElementFromTag(messageDiv, opt_tag2);
292    } else {
293      messageDiv.innerText = '';
294    }
295    remoting.setMode(remoting.AppMode.HOST_SETUP_DONE);
296  }
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);
302  }
303
304  var state = this.flow_.getState();
305  if (state == remoting.HostSetupFlow.State.NONE) {
306    this.hide();
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) {
310    this.installHost_();
311  } else if (state == remoting.HostSetupFlow.State.STARTING_HOST) {
312    remoting.showSetupProcessingMessage(/*i18n-content*/'HOST_SETUP_STARTING');
313    this.startHost_();
314  } else if (state == remoting.HostSetupFlow.State.UPDATING_PIN) {
315    remoting.showSetupProcessingMessage(
316        /*i18n-content*/'HOST_SETUP_UPDATING_PIN');
317    this.updatePin_();
318  } else if (state == remoting.HostSetupFlow.State.STOPPING_HOST) {
319    remoting.showSetupProcessingMessage(/*i18n-content*/'HOST_SETUP_STOPPING');
320    this.stopHost_();
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');
338  }
339};
340
341/**
342 * Shows the prompt that asks the user to install the host.
343 */
344remoting.HostSetupDialog.prototype.installHost_ = function() {
345  /** @type {remoting.HostSetupDialog} */
346  var that = this;
347  /** @type {remoting.HostSetupFlow} */
348  var flow = this.flow_;
349
350  /** @param {remoting.Error} error */
351  var onError = function(error) {
352    flow.switchToErrorState(error);
353    that.updateState_();
354  };
355
356  var onDone = function() {
357    that.hostController_.getLocalHostState(onHostState);
358  };
359
360  /** @param {remoting.HostController.State} state */
361  var onHostState = function(state) {
362    var installed =
363        state != remoting.HostController.State.NOT_INSTALLED &&
364        state != remoting.HostController.State.INSTALLING;
365
366    if (installed) {
367      that.flow_.switchToNextStep();
368      that.updateState_();
369    } else {
370      // Prompt the user again if the host is not installed.
371      hostInstallDialog.tryAgain();
372    }
373  };
374
375  /** @type {remoting.HostInstallDialog} */
376  var hostInstallDialog = new remoting.HostInstallDialog();
377  hostInstallDialog.show(onDone, onError);
378}
379
380/**
381 * Registers and starts the host.
382 */
383remoting.HostSetupDialog.prototype.startHost_ = function() {
384  /** @type {remoting.HostSetupDialog} */
385  var that = this;
386  /** @type {remoting.HostSetupFlow} */
387  var flow = this.flow_;
388
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');
394      return false;
395    }
396    return true;
397  }
398
399  function onHostStarted() {
400    if (isFlowActive()) {
401      flow.switchToNextStep();
402      that.updateState_();
403    }
404  }
405
406  /** @param {remoting.Error} error */
407  function onError(error) {
408    if (isFlowActive()) {
409      flow.switchToErrorState(error);
410      that.updateState_();
411    }
412  }
413
414  this.hostController_.start(this.flow_.pin, this.flow_.consent, onHostStarted,
415                             onError);
416};
417
418remoting.HostSetupDialog.prototype.updatePin_ = function() {
419  /** @type {remoting.HostSetupDialog} */
420  var that = this;
421  /** @type {remoting.HostSetupFlow} */
422  var flow = this.flow_;
423
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');
429      return false;
430    }
431    return true;
432  }
433
434  function onPinUpdated() {
435    if (isFlowActive()) {
436      flow.switchToNextStep();
437      that.updateState_();
438    }
439  }
440
441  /** @param {remoting.Error} error */
442  function onError(error) {
443    if (isFlowActive()) {
444      flow.switchToErrorState(error);
445      that.updateState_();
446    }
447  }
448
449  this.hostController_.updatePin(flow.pin, onPinUpdated, onError);
450};
451
452/**
453 * Stops the host.
454 */
455remoting.HostSetupDialog.prototype.stopHost_ = function() {
456  /** @type {remoting.HostSetupDialog} */
457  var that = this;
458  /** @type {remoting.HostSetupFlow} */
459  var flow = this.flow_;
460
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');
466      return false;
467    }
468    return true;
469  }
470
471  function onHostStopped() {
472    if (isFlowActive()) {
473      flow.switchToNextStep();
474      that.updateState_();
475    }
476  }
477
478  /** @param {remoting.Error} error */
479  function onError(error) {
480    if (isFlowActive()) {
481      flow.switchToErrorState(error);
482      that.updateState_();
483    }
484  }
485
486  this.hostController_.stop(onHostStopped, onError);
487};
488
489/**
490 * Validates the PIN and shows an error message if it's invalid.
491 * @return {boolean} true if the PIN is valid, false otherwise.
492 * @private
493 */
494remoting.HostSetupDialog.prototype.validatePin_ = function() {
495  var pin = this.pinEntry_.value;
496  var pinIsValid = remoting.HostSetupDialog.validPin_(pin);
497  if (!pinIsValid) {
498    l10n.localizeElementFromTag(
499        this.pinErrorMessage_, /*i18n-content*/'INVALID_PIN');
500  }
501  this.pinErrorDiv_.hidden = pinIsValid;
502  return pinIsValid;
503};
504
505/** @private */
506remoting.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());
509    return;
510  }
511  var pin1 = this.pinEntry_.value;
512  var pin2 = this.pinConfirm_.value;
513  if (pin1 != pin2) {
514    l10n.localizeElementFromTag(
515        this.pinErrorMessage_, /*i18n-content*/'PINS_NOT_EQUAL');
516    this.pinErrorDiv_.hidden = false;
517    this.prepareForPinEntry_();
518    return;
519  }
520  if (!this.validatePin_()) {
521    this.prepareForPinEntry_();
522    return;
523  }
524  this.flow_.pin = pin1;
525  this.flow_.consent = !this.usageStats_.hidden &&
526      this.usageStatsCheckbox_.checked;
527  this.flow_.switchToNextStep();
528  this.updateState_();
529};
530
531/** @private */
532remoting.HostSetupDialog.prototype.prepareForPinEntry_ = function() {
533  this.pinEntry_.value = '';
534  this.pinConfirm_.value = '';
535  this.pinEntry_.focus();
536};
537
538/**
539 * Returns whether a PIN is valid.
540 *
541 * @private
542 * @param {string} pin A PIN.
543 * @return {boolean} Whether the PIN is valid.
544 */
545remoting.HostSetupDialog.validPin_ = function(pin) {
546  if (pin.length < 6) {
547    return false;
548  }
549  for (var i = 0; i < pin.length; i++) {
550    var c = pin.charAt(i);
551    if ((c < '0') || (c > '9')) {
552      return false;
553    }
554  }
555  return true;
556};
557
558/** @type {remoting.HostSetupDialog} */
559remoting.hostSetupDialog = null;
560