• 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 'client screen' for Chromoting.
8 */
9
10'use strict';
11
12/** @suppress {duplicate} */
13var remoting = remoting || {};
14
15/**
16 * @type {remoting.SessionConnector} The connector object, set when a connection
17 *     is initiated.
18 */
19remoting.connector = null;
20
21/**
22 * @type {remoting.ClientSession} The client session object, set once the
23 *     connector has invoked its onOk callback.
24 */
25remoting.clientSession = null;
26
27/**
28 * Initiate an IT2Me connection.
29 */
30remoting.connectIT2Me = function() {
31  if (!remoting.connector) {
32    remoting.connector = new remoting.SessionConnector(
33        document.getElementById('session-mode'),
34        remoting.onConnected,
35        showConnectError_);
36  }
37  var accessCode = document.getElementById('access-code-entry').value;
38  remoting.setMode(remoting.AppMode.CLIENT_CONNECTING);
39  remoting.connector.connectIT2Me(accessCode);
40};
41
42/**
43 * Update the remoting client layout in response to a resize event.
44 *
45 * @return {void} Nothing.
46 */
47remoting.onResize = function() {
48  if (remoting.clientSession) {
49    remoting.clientSession.onResize();
50  }
51};
52
53/**
54 * Handle changes in the visibility of the window, for example by pausing video.
55 *
56 * @return {void} Nothing.
57 */
58remoting.onVisibilityChanged = function() {
59  if (remoting.clientSession) {
60    remoting.clientSession.pauseVideo(
61      ('hidden' in document) ? document.hidden : document.webkitHidden);
62  }
63}
64
65/**
66 * Disconnect the remoting client.
67 *
68 * @return {void} Nothing.
69 */
70remoting.disconnect = function() {
71  if (!remoting.clientSession) {
72    return;
73  }
74  if (remoting.clientSession.getMode() == remoting.ClientSession.Mode.IT2ME) {
75    remoting.setMode(remoting.AppMode.CLIENT_SESSION_FINISHED_IT2ME);
76  } else {
77    remoting.setMode(remoting.AppMode.CLIENT_SESSION_FINISHED_ME2ME);
78  }
79  remoting.clientSession.disconnect(true);
80  remoting.clientSession = null;
81  console.log('Disconnected.');
82};
83
84/**
85 * Sends a Ctrl-Alt-Del sequence to the remoting client.
86 *
87 * @return {void} Nothing.
88 */
89remoting.sendCtrlAltDel = function() {
90  if (remoting.clientSession) {
91    console.log('Sending Ctrl-Alt-Del.');
92    remoting.clientSession.sendCtrlAltDel();
93  }
94};
95
96/**
97 * Sends a Print Screen keypress to the remoting client.
98 *
99 * @return {void} Nothing.
100 */
101remoting.sendPrintScreen = function() {
102  if (remoting.clientSession) {
103    console.log('Sending Print Screen.');
104    remoting.clientSession.sendPrintScreen();
105  }
106};
107
108/**
109 * Callback function called when the state of the client plugin changes. The
110 * current state is available via the |state| member variable.
111 *
112 * @param {number} oldState The previous state of the plugin.
113 * @param {number} newState The current state of the plugin.
114 */
115function onClientStateChange_(oldState, newState) {
116  switch (newState) {
117    case remoting.ClientSession.State.CLOSED:
118      console.log('Connection closed by host');
119      if (remoting.clientSession.getMode() ==
120          remoting.ClientSession.Mode.IT2ME) {
121        remoting.setMode(remoting.AppMode.CLIENT_SESSION_FINISHED_IT2ME);
122      } else {
123        remoting.setMode(remoting.AppMode.CLIENT_SESSION_FINISHED_ME2ME);
124      }
125      break;
126
127    case remoting.ClientSession.State.FAILED:
128      var error = remoting.clientSession.getError();
129      console.error('Client plugin reported connection failed: ' + error);
130      if (error == null) {
131        error = remoting.Error.UNEXPECTED;
132      }
133      showConnectError_(error);
134      break;
135
136    default:
137      console.error('Unexpected client plugin state: ' + newState);
138      // This should only happen if the web-app and client plugin get out of
139      // sync, so MISSING_PLUGIN is a suitable error.
140      showConnectError_(remoting.Error.MISSING_PLUGIN);
141      break;
142  }
143  remoting.clientSession.disconnect(false);
144  remoting.clientSession.removePlugin();
145  remoting.clientSession = null;
146}
147
148/**
149 * Show a client-side error message.
150 *
151 * @param {remoting.Error} errorTag The error to be localized and
152 *     displayed.
153 * @return {void} Nothing.
154 */
155function showConnectError_(errorTag) {
156  console.error('Connection failed: ' + errorTag);
157  var errorDiv = document.getElementById('connect-error-message');
158  l10n.localizeElementFromTag(errorDiv, /** @type {string} */ (errorTag));
159  remoting.accessCode = '';
160  var mode = remoting.clientSession ? remoting.clientSession.getMode()
161                                    : remoting.connector.getConnectionMode();
162  if (mode == remoting.ClientSession.Mode.IT2ME) {
163    remoting.setMode(remoting.AppMode.CLIENT_CONNECT_FAILED_IT2ME);
164  } else {
165    remoting.setMode(remoting.AppMode.CLIENT_CONNECT_FAILED_ME2ME);
166  }
167}
168
169/**
170 * Set the text on the buttons shown under the error message so that they are
171 * easy to understand in the case where a successful connection failed, as
172 * opposed to the case where a connection never succeeded.
173 */
174function setConnectionInterruptedButtonsText_() {
175  var button1 = document.getElementById('client-reconnect-button');
176  l10n.localizeElementFromTag(button1, /*i18n-content*/'RECONNECT');
177  button1.removeAttribute('autofocus');
178  var button2 = document.getElementById('client-finished-me2me-button');
179  l10n.localizeElementFromTag(button2, /*i18n-content*/'OK');
180  button2.setAttribute('autofocus', 'autofocus');
181}
182
183/**
184 * Timer callback to update the statistics panel.
185 */
186function updateStatistics_() {
187  if (!remoting.clientSession ||
188      remoting.clientSession.getState() !=
189      remoting.ClientSession.State.CONNECTED) {
190    return;
191  }
192  var perfstats = remoting.clientSession.getPerfStats();
193  remoting.stats.update(perfstats);
194  remoting.clientSession.logStatistics(perfstats);
195  // Update the stats once per second.
196  window.setTimeout(updateStatistics_, 1000);
197}
198
199/**
200 * Entry-point for Me2Me connections, handling showing of the host-upgrade nag
201 * dialog if necessary.
202 *
203 * @param {string} hostId The unique id of the host.
204 * @return {void} Nothing.
205 */
206remoting.connectMe2Me = function(hostId) {
207  var host = remoting.hostList.getHostForId(hostId);
208  if (!host) {
209    showConnectError_(remoting.Error.HOST_IS_OFFLINE);
210    return;
211  }
212  var webappVersion = chrome.runtime.getManifest().version;
213  if (remoting.Host.needsUpdate(host, webappVersion)) {
214    var needsUpdateMessage =
215        document.getElementById('host-needs-update-message');
216    l10n.localizeElementFromTag(needsUpdateMessage,
217                                /*i18n-content*/'HOST_NEEDS_UPDATE_TITLE',
218                                host.hostName);
219    /** @type {Element} */
220    var connect = document.getElementById('host-needs-update-connect-button');
221    /** @type {Element} */
222    var cancel = document.getElementById('host-needs-update-cancel-button');
223    /** @param {Event} event */
224    var onClick = function(event) {
225      connect.removeEventListener('click', onClick, false);
226      cancel.removeEventListener('click', onClick, false);
227      if (event.target == connect) {
228        remoting.connectMe2MeHostVersionAcknowledged_(host);
229      } else {
230        remoting.setMode(remoting.AppMode.HOME);
231      }
232    }
233    connect.addEventListener('click', onClick, false);
234    cancel.addEventListener('click', onClick, false);
235    remoting.setMode(remoting.AppMode.CLIENT_HOST_NEEDS_UPGRADE);
236  } else {
237    remoting.connectMe2MeHostVersionAcknowledged_(host);
238  }
239};
240
241/**
242 * Shows PIN entry screen localized to include the host name, and registers
243 * a host-specific one-shot event handler for the form submission.
244 *
245 * @param {remoting.Host} host The Me2Me host to which to connect.
246 * @return {void} Nothing.
247 */
248remoting.connectMe2MeHostVersionAcknowledged_ = function(host) {
249  if (!remoting.connector) {
250    remoting.connector = new remoting.SessionConnector(
251        document.getElementById('session-mode'),
252        remoting.onConnected,
253        showConnectError_);
254  }
255  remoting.setMode(remoting.AppMode.CLIENT_CONNECTING);
256
257  /**
258   * @param {string} tokenUrl Token-issue URL received from the host.
259   * @param {string} scope OAuth scope to request the token for.
260   * @param {string} hostPublicKey Host public key (DER and Base64 encoded).
261   * @param {function(string, string):void} onThirdPartyTokenFetched Callback.
262   */
263  var fetchThirdPartyToken = function(
264      tokenUrl, hostPublicKey, scope, onThirdPartyTokenFetched) {
265    var thirdPartyTokenFetcher = new remoting.ThirdPartyTokenFetcher(
266        tokenUrl, hostPublicKey, scope, host.tokenUrlPatterns,
267        onThirdPartyTokenFetched);
268    thirdPartyTokenFetcher.fetchToken();
269  };
270
271  /**
272   * @param {boolean} supportsPairing
273   * @param {function(string):void} onPinFetched
274   */
275  var requestPin = function(supportsPairing, onPinFetched) {
276    /** @type {Element} */
277    var pinForm = document.getElementById('pin-form');
278    /** @type {Element} */
279    var pinCancel = document.getElementById('cancel-pin-entry-button');
280    /** @type {Element} */
281    var rememberPin = document.getElementById('remember-pin');
282    /** @type {Element} */
283    var rememberPinCheckbox = document.getElementById('remember-pin-checkbox');
284    /**
285     * Event handler for both the 'submit' and 'cancel' actions. Using
286     * a single handler for both greatly simplifies the task of making
287     * them one-shot. If separate handlers were used, each would have
288     * to unregister both itself and the other.
289     *
290     * @param {Event} event The click or submit event.
291     */
292    var onSubmitOrCancel = function(event) {
293      pinForm.removeEventListener('submit', onSubmitOrCancel, false);
294      pinCancel.removeEventListener('click', onSubmitOrCancel, false);
295      var pinField = document.getElementById('pin-entry');
296      var pin = pinField.value;
297      pinField.value = '';
298      if (event.target == pinForm) {
299        event.preventDefault();
300
301        // Set the focus away from the password field. This has to be done
302        // before the password field gets hidden, to work around a Blink
303        // clipboard-handling bug - http://crbug.com/281523.
304        document.getElementById('pin-connect-button').focus();
305
306        remoting.setMode(remoting.AppMode.CLIENT_CONNECTING);
307        onPinFetched(pin);
308        if (/** @type {boolean} */(rememberPinCheckbox.checked)) {
309          remoting.connector.pairingRequested = true;
310        }
311      } else {
312        remoting.setMode(remoting.AppMode.HOME);
313      }
314    };
315    pinForm.addEventListener('submit', onSubmitOrCancel, false);
316    pinCancel.addEventListener('click', onSubmitOrCancel, false);
317    rememberPin.hidden = !supportsPairing;
318    rememberPinCheckbox.checked = false;
319    var message = document.getElementById('pin-message');
320    l10n.localizeElement(message, host.hostName);
321    remoting.setMode(remoting.AppMode.CLIENT_PIN_PROMPT);
322  };
323
324  /** @param {Object} settings */
325  var connectMe2MeHostSettingsRetrieved = function(settings) {
326    /** @type {string} */
327    var clientId = '';
328    /** @type {string} */
329    var sharedSecret = '';
330    var pairingInfo = /** @type {Object} */ (settings['pairingInfo']);
331    if (pairingInfo) {
332      clientId = /** @type {string} */ (pairingInfo['clientId']);
333      sharedSecret = /** @type {string} */ (pairingInfo['sharedSecret']);
334    }
335    remoting.connector.connectMe2Me(host, requestPin, fetchThirdPartyToken,
336                                    clientId, sharedSecret);
337  }
338
339  remoting.HostSettings.load(host.hostId, connectMe2MeHostSettingsRetrieved);
340};
341
342/** @param {remoting.ClientSession} clientSession */
343remoting.onConnected = function(clientSession) {
344  remoting.clientSession = clientSession;
345  remoting.clientSession.setOnStateChange(onClientStateChange_);
346  setConnectionInterruptedButtonsText_();
347  var connectedTo = document.getElementById('connected-to');
348  connectedTo.innerText = remoting.connector.getHostDisplayName();
349  document.getElementById('access-code-entry').value = '';
350  remoting.setMode(remoting.AppMode.IN_SESSION);
351  remoting.toolbar.center();
352  remoting.toolbar.preview();
353  remoting.clipboard.startSession();
354  updateStatistics_();
355  if (remoting.connector.pairingRequested) {
356    /**
357     * @param {string} clientId
358     * @param {string} sharedSecret
359     */
360    var onPairingComplete = function(clientId, sharedSecret) {
361      var pairingInfo = {
362        pairingInfo: {
363          clientId: clientId,
364          sharedSecret: sharedSecret
365        }
366      };
367      remoting.HostSettings.save(remoting.connector.getHostId(), pairingInfo);
368      remoting.connector.updatePairingInfo(clientId, sharedSecret);
369    };
370    // Use the platform name as a proxy for the local computer name.
371    // TODO(jamiewalch): Use a descriptive name for the local computer, for
372    // example, its Chrome Sync name.
373    var clientName = '';
374    if (navigator.platform.indexOf('Mac') != -1) {
375      clientName = 'Mac';
376    } else if (navigator.platform.indexOf('Win32') != -1) {
377      clientName = 'Windows';
378    } else if (navigator.userAgent.match(/\bCrOS\b/)) {
379      clientName = 'ChromeOS';
380    } else if (navigator.platform.indexOf('Linux') != -1) {
381      clientName = 'Linux';
382    } else {
383      console.log('Unrecognized client platform. Using navigator.platform.');
384      clientName = navigator.platform;
385    }
386    clientSession.requestPairing(clientName, onPairingComplete);
387  }
388};
389