• 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/**
6 * @fileoverview
7 * Functions related to the 'host screen' for Chromoting.
8 */
9
10'use strict';
11
12/** @suppress {duplicate} */
13var remoting = remoting || {};
14
15/**
16 * @type {boolean} Whether or not the last share was cancelled by the user.
17 *     This controls what screen is shown when the host plugin signals
18 *     completion.
19 * @private
20 */
21var lastShareWasCancelled_ = false;
22
23/**
24 * Start a host session. This is the main entry point for the host screen,
25 * called directly from the onclick action of a button on the home screen.
26 */
27remoting.tryShare = function() {
28  console.log('Attempting to share...');
29  remoting.identity.callWithToken(remoting.tryShareWithToken_,
30                                  remoting.showErrorMessage);
31};
32
33/**
34 * @param {string} token The OAuth access token.
35 * @private
36 */
37remoting.tryShareWithToken_ = function(token) {
38  lastShareWasCancelled_ = false;
39  onNatTraversalPolicyChanged_(true);  // Hide warning by default.
40  remoting.setMode(remoting.AppMode.HOST_WAITING_FOR_CODE);
41  document.getElementById('cancel-share-button').disabled = false;
42  disableTimeoutCountdown_();
43
44  var div = document.getElementById('host-plugin-container');
45  remoting.hostSession = new remoting.HostSession();
46  remoting.hostSession.createPluginAndConnect(
47      document.getElementById('host-plugin-container'),
48      /** @type {string} */(remoting.identity.getCachedEmail()),
49      token,
50      onNatTraversalPolicyChanged_,
51      onHostStateChanged_,
52      logDebugInfo_);
53};
54
55/**
56 * Callback for the host plugin to notify the web app of state changes.
57 * @param {remoting.HostSession.State} state The new state of the plugin.
58 */
59function onHostStateChanged_(state) {
60  if (state == remoting.HostSession.State.STARTING) {
61    // Nothing to do here.
62    console.log('Host plugin state: STARTING');
63
64  } else if (state == remoting.HostSession.State.REQUESTED_ACCESS_CODE) {
65    // Nothing to do here.
66    console.log('Host plugin state: REQUESTED_ACCESS_CODE');
67
68  } else if (state == remoting.HostSession.State.RECEIVED_ACCESS_CODE) {
69    console.log('Host plugin state: RECEIVED_ACCESS_CODE');
70    var accessCode = remoting.hostSession.getAccessCode();
71    var accessCodeDisplay = document.getElementById('access-code-display');
72    accessCodeDisplay.innerText = '';
73    // Display the access code in groups of four digits for readability.
74    var kDigitsPerGroup = 4;
75    for (var i = 0; i < accessCode.length; i += kDigitsPerGroup) {
76      var nextFourDigits = document.createElement('span');
77      nextFourDigits.className = 'access-code-digit-group';
78      nextFourDigits.innerText = accessCode.substring(i, i + kDigitsPerGroup);
79      accessCodeDisplay.appendChild(nextFourDigits);
80    }
81    accessCodeExpiresIn_ = remoting.hostSession.getAccessCodeLifetime();
82    if (accessCodeExpiresIn_ > 0) {  // Check it hasn't expired.
83      accessCodeTimerId_ = setInterval(
84          remoting.decrementAccessCodeTimeout_, 1000);
85      timerRunning_ = true;
86      updateAccessCodeTimeoutElement_();
87      updateTimeoutStyles_();
88      remoting.setMode(remoting.AppMode.HOST_WAITING_FOR_CONNECTION);
89    } else {
90      // This can only happen if the cloud tells us that the code lifetime is
91      // <= 0s, which shouldn't happen so we don't care how clean this UX is.
92      console.error('Access code already invalid on receipt!');
93      remoting.cancelShare();
94    }
95
96  } else if (state == remoting.HostSession.State.CONNECTED) {
97    console.log('Host plugin state: CONNECTED');
98    var element = document.getElementById('host-shared-message');
99    var client = remoting.hostSession.getClient();
100    l10n.localizeElement(element, client);
101    remoting.setMode(remoting.AppMode.HOST_SHARED);
102    disableTimeoutCountdown_();
103
104  } else if (state == remoting.HostSession.State.DISCONNECTING) {
105    console.log('Host plugin state: DISCONNECTING');
106
107  } else if (state == remoting.HostSession.State.DISCONNECTED) {
108    console.log('Host plugin state: DISCONNECTED');
109    if (remoting.currentMode != remoting.AppMode.HOST_SHARE_FAILED) {
110      // If an error is being displayed, then the plugin should not be able to
111      // hide it by setting the state. Errors must be dismissed by the user
112      // clicking OK, which puts the app into mode HOME.
113      if (lastShareWasCancelled_) {
114        remoting.setMode(remoting.AppMode.HOME);
115      } else {
116        remoting.setMode(remoting.AppMode.HOST_SHARE_FINISHED);
117      }
118    }
119    remoting.hostSession.removePlugin();
120
121  } else if (state == remoting.HostSession.State.ERROR) {
122    console.error('Host plugin state: ERROR');
123    showShareError_(remoting.Error.UNEXPECTED);
124  } else if (state == remoting.HostSession.State.INVALID_DOMAIN_ERROR) {
125    console.error('Host plugin state: INVALID_DOMAIN_ERROR');
126    showShareError_(remoting.Error.INVALID_HOST_DOMAIN);
127  } else {
128    console.error('Unknown state -> ' + state);
129  }
130}
131
132/**
133 * This is the callback that the host plugin invokes to indicate that there
134 * is additional debug log info to display.
135 * @param {string} msg The message (which will not be localized) to be logged.
136 */
137function logDebugInfo_(msg) {
138  console.log('plugin: ' + msg);
139}
140
141/**
142 * Show a host-side error message.
143 *
144 * @param {string} errorTag The error message to be localized and displayed.
145 * @return {void} Nothing.
146 */
147function showShareError_(errorTag) {
148  var errorDiv = document.getElementById('host-plugin-error');
149  l10n.localizeElementFromTag(errorDiv, errorTag);
150  console.error('Sharing error: ' + errorTag);
151  remoting.setMode(remoting.AppMode.HOST_SHARE_FAILED);
152}
153
154/**
155 * Cancel an active or pending share operation.
156 *
157 * @return {void} Nothing.
158 */
159remoting.cancelShare = function() {
160  document.getElementById('cancel-share-button').disabled = true;
161  console.log('Canceling share...');
162  remoting.lastShareWasCancelled = true;
163  try {
164    remoting.hostSession.disconnect();
165  } catch (error) {
166    // Hack to force JSCompiler type-safety.
167    var errorTyped = /** @type {{description: string}} */ error;
168    console.error('Error disconnecting: ' + errorTyped.description +
169                '. The host plugin probably crashed.');
170    // TODO(jamiewalch): Clean this up. We should have a class representing
171    // the host plugin, like we do for the client, which should handle crash
172    // reporting and it should use a more detailed error message than the
173    // default 'generic' one. See crbug.com/94624
174    showShareError_(remoting.Error.UNEXPECTED);
175  }
176  disableTimeoutCountdown_();
177};
178
179/**
180 * @type {boolean} Whether or not the access code timeout countdown is running.
181 * @private
182 */
183var timerRunning_ = false;
184
185/**
186 * @type {number} The id of the access code expiry countdown timer.
187 * @private
188 */
189var accessCodeTimerId_ = 0;
190
191/**
192 * @type {number} The number of seconds until the access code expires.
193 * @private
194 */
195var accessCodeExpiresIn_ = 0;
196
197/**
198 * The timer callback function, which needs to be visible from the global
199 * namespace.
200 * @private
201 */
202remoting.decrementAccessCodeTimeout_ = function() {
203  --accessCodeExpiresIn_;
204  updateAccessCodeTimeoutElement_();
205};
206
207/**
208 * Stop the access code timeout countdown if it is running.
209 */
210function disableTimeoutCountdown_() {
211  if (timerRunning_) {
212    clearInterval(accessCodeTimerId_);
213    timerRunning_ = false;
214    updateTimeoutStyles_();
215  }
216}
217
218/**
219 * Constants controlling the access code timer countdown display.
220 * @private
221 */
222var ACCESS_CODE_TIMER_DISPLAY_THRESHOLD_ = 30;
223var ACCESS_CODE_RED_THRESHOLD_ = 10;
224
225/**
226 * Show/hide or restyle various elements, depending on the remaining countdown
227 * and timer state.
228 *
229 * @return {boolean} True if the timeout is in progress, false if it has
230 * expired.
231 */
232function updateTimeoutStyles_() {
233  if (timerRunning_) {
234    if (accessCodeExpiresIn_ <= 0) {
235      remoting.cancelShare();
236      return false;
237    }
238    var accessCode = document.getElementById('access-code-display');
239    if (accessCodeExpiresIn_ <= ACCESS_CODE_RED_THRESHOLD_) {
240      accessCode.classList.add('expiring');
241    } else {
242      accessCode.classList.remove('expiring');
243    }
244  }
245  document.getElementById('access-code-countdown').hidden =
246      (accessCodeExpiresIn_ > ACCESS_CODE_TIMER_DISPLAY_THRESHOLD_) ||
247      !timerRunning_;
248  return true;
249}
250
251/**
252 * Update the text and appearance of the access code timeout element to
253 * reflect the time remaining.
254 */
255function updateAccessCodeTimeoutElement_() {
256  var pad = (accessCodeExpiresIn_ < 10) ? '0:0' : '0:';
257  l10n.localizeElement(document.getElementById('seconds-remaining'),
258                       pad + accessCodeExpiresIn_);
259  if (!updateTimeoutStyles_()) {
260    disableTimeoutCountdown_();
261  }
262}
263
264/**
265 * Callback to show or hide the NAT traversal warning when the policy changes.
266 * @param {boolean} enabled True if NAT traversal is enabled.
267 * @return {void} Nothing.
268 */
269function onNatTraversalPolicyChanged_(enabled) {
270  var natBox = document.getElementById('nat-box');
271  if (enabled) {
272    natBox.classList.add('traversal-enabled');
273  } else {
274    natBox.classList.remove('traversal-enabled');
275  }
276}
277