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 * Functions related to the 'client screen' for Chromoting.
12 /** @suppress {duplicate} */
13 var remoting = remoting || {};
16 * @type {remoting.SessionConnector} The connector object, set when a connection
19 remoting.connector = null;
22 * @type {remoting.ClientSession} The client session object, set once the
23 * connector has invoked its onOk callback.
25 remoting.clientSession = null;
28 * Initiate an IT2Me connection.
30 remoting.connectIT2Me = function() {
31 remoting.ensureSessionConnector_();
32 var accessCode = document.getElementById('access-code-entry').value;
33 remoting.setMode(remoting.AppMode.CLIENT_CONNECTING);
34 remoting.connector.connectIT2Me(accessCode);
38 * Update the remoting client layout in response to a resize event.
40 * @return {void} Nothing.
42 remoting.onResize = function() {
43 if (remoting.clientSession) {
44 remoting.clientSession.onResize();
49 * Handle changes in the visibility of the window, for example by pausing video.
51 * @return {void} Nothing.
53 remoting.onVisibilityChanged = function() {
54 if (remoting.clientSession) {
55 remoting.clientSession.pauseVideo(
56 ('hidden' in document) ? document.hidden : document.webkitHidden);
61 * Disconnect the remoting client.
63 * @return {void} Nothing.
65 remoting.disconnect = function() {
66 if (!remoting.clientSession) {
69 if (remoting.clientSession.getMode() == remoting.ClientSession.Mode.IT2ME) {
70 remoting.setMode(remoting.AppMode.CLIENT_SESSION_FINISHED_IT2ME);
72 remoting.setMode(remoting.AppMode.CLIENT_SESSION_FINISHED_ME2ME);
74 remoting.clientSession.disconnect(remoting.Error.NONE);
75 remoting.clientSession = null;
76 console.log('Disconnected.');
80 * Sends a Ctrl-Alt-Del sequence to the remoting client.
82 * @return {void} Nothing.
84 remoting.sendCtrlAltDel = function() {
85 if (remoting.clientSession) {
86 console.log('Sending Ctrl-Alt-Del.');
87 remoting.clientSession.sendCtrlAltDel();
92 * Sends a Print Screen keypress to the remoting client.
94 * @return {void} Nothing.
96 remoting.sendPrintScreen = function() {
97 if (remoting.clientSession) {
98 console.log('Sending Print Screen.');
99 remoting.clientSession.sendPrintScreen();
104 * Callback function called when the state of the client plugin changes. The
105 * current and previous states are available via the |state| member variable.
107 * @param {remoting.ClientSession.StateEvent} state
109 function onClientStateChange_(state) {
110 switch (state.current) {
111 case remoting.ClientSession.State.CLOSED:
112 console.log('Connection closed by host');
113 if (remoting.clientSession.getMode() ==
114 remoting.ClientSession.Mode.IT2ME) {
115 remoting.setMode(remoting.AppMode.CLIENT_SESSION_FINISHED_IT2ME);
117 remoting.setMode(remoting.AppMode.CLIENT_SESSION_FINISHED_ME2ME);
121 case remoting.ClientSession.State.FAILED:
122 var error = remoting.clientSession.getError();
123 console.error('Client plugin reported connection failed: ' + error);
125 error = remoting.Error.UNEXPECTED;
127 showConnectError_(error);
131 console.error('Unexpected client plugin state: ' + state.current);
132 // This should only happen if the web-app and client plugin get out of
133 // sync, so MISSING_PLUGIN is a suitable error.
134 showConnectError_(remoting.Error.MISSING_PLUGIN);
138 remoting.clientSession.removeEventListener('stateChanged',
139 onClientStateChange_);
140 remoting.clientSession.cleanup();
141 remoting.clientSession = null;
145 * Show a client-side error message.
147 * @param {remoting.Error} errorTag The error to be localized and
149 * @return {void} Nothing.
151 function showConnectError_(errorTag) {
152 console.error('Connection failed: ' + errorTag);
153 var errorDiv = document.getElementById('connect-error-message');
154 l10n.localizeElementFromTag(errorDiv, /** @type {string} */ (errorTag));
155 remoting.accessCode = '';
156 var mode = remoting.clientSession ? remoting.clientSession.getMode()
157 : remoting.connector.getConnectionMode();
158 if (mode == remoting.ClientSession.Mode.IT2ME) {
159 remoting.setMode(remoting.AppMode.CLIENT_CONNECT_FAILED_IT2ME);
161 remoting.setMode(remoting.AppMode.CLIENT_CONNECT_FAILED_ME2ME);
166 * Set the text on the buttons shown under the error message so that they are
167 * easy to understand in the case where a successful connection failed, as
168 * opposed to the case where a connection never succeeded.
170 function setConnectionInterruptedButtonsText_() {
171 var button1 = document.getElementById('client-reconnect-button');
172 l10n.localizeElementFromTag(button1, /*i18n-content*/'RECONNECT');
173 button1.removeAttribute('autofocus');
174 var button2 = document.getElementById('client-finished-me2me-button');
175 l10n.localizeElementFromTag(button2, /*i18n-content*/'OK');
176 button2.setAttribute('autofocus', 'autofocus');
180 * Timer callback to update the statistics panel.
182 function updateStatistics_() {
183 if (!remoting.clientSession ||
184 remoting.clientSession.getState() !=
185 remoting.ClientSession.State.CONNECTED) {
188 var perfstats = remoting.clientSession.getPerfStats();
189 remoting.stats.update(perfstats);
190 remoting.clientSession.logStatistics(perfstats);
191 // Update the stats once per second.
192 window.setTimeout(updateStatistics_, 1000);
196 * Entry-point for Me2Me connections, handling showing of the host-upgrade nag
197 * dialog if necessary.
199 * @param {string} hostId The unique id of the host.
200 * @return {void} Nothing.
202 remoting.connectMe2Me = function(hostId) {
203 var host = remoting.hostList.getHostForId(hostId);
205 showConnectError_(remoting.Error.HOST_IS_OFFLINE);
208 var webappVersion = chrome.runtime.getManifest().version;
209 if (remoting.Host.needsUpdate(host, webappVersion)) {
210 var needsUpdateMessage =
211 document.getElementById('host-needs-update-message');
212 l10n.localizeElementFromTag(needsUpdateMessage,
213 /*i18n-content*/'HOST_NEEDS_UPDATE_TITLE',
215 /** @type {Element} */
216 var connect = document.getElementById('host-needs-update-connect-button');
217 /** @type {Element} */
218 var cancel = document.getElementById('host-needs-update-cancel-button');
219 /** @param {Event} event */
220 var onClick = function(event) {
221 connect.removeEventListener('click', onClick, false);
222 cancel.removeEventListener('click', onClick, false);
223 if (event.target == connect) {
224 remoting.connectMe2MeHostVersionAcknowledged_(host);
226 remoting.setMode(remoting.AppMode.HOME);
229 connect.addEventListener('click', onClick, false);
230 cancel.addEventListener('click', onClick, false);
231 remoting.setMode(remoting.AppMode.CLIENT_HOST_NEEDS_UPGRADE);
233 remoting.connectMe2MeHostVersionAcknowledged_(host);
238 * Shows PIN entry screen localized to include the host name, and registers
239 * a host-specific one-shot event handler for the form submission.
241 * @param {remoting.Host} host The Me2Me host to which to connect.
242 * @return {void} Nothing.
244 remoting.connectMe2MeHostVersionAcknowledged_ = function(host) {
245 remoting.ensureSessionConnector_();
246 remoting.setMode(remoting.AppMode.CLIENT_CONNECTING);
249 * @param {string} tokenUrl Token-issue URL received from the host.
250 * @param {string} scope OAuth scope to request the token for.
251 * @param {string} hostPublicKey Host public key (DER and Base64 encoded).
252 * @param {function(string, string):void} onThirdPartyTokenFetched Callback.
254 var fetchThirdPartyToken = function(
255 tokenUrl, hostPublicKey, scope, onThirdPartyTokenFetched) {
256 var thirdPartyTokenFetcher = new remoting.ThirdPartyTokenFetcher(
257 tokenUrl, hostPublicKey, scope, host.tokenUrlPatterns,
258 onThirdPartyTokenFetched);
259 thirdPartyTokenFetcher.fetchToken();
263 * @param {boolean} supportsPairing
264 * @param {function(string):void} onPinFetched
266 var requestPin = function(supportsPairing, onPinFetched) {
267 /** @type {Element} */
268 var pinForm = document.getElementById('pin-form');
269 /** @type {Element} */
270 var pinCancel = document.getElementById('cancel-pin-entry-button');
271 /** @type {Element} */
272 var rememberPin = document.getElementById('remember-pin');
273 /** @type {Element} */
274 var rememberPinCheckbox = document.getElementById('remember-pin-checkbox');
276 * Event handler for both the 'submit' and 'cancel' actions. Using
277 * a single handler for both greatly simplifies the task of making
278 * them one-shot. If separate handlers were used, each would have
279 * to unregister both itself and the other.
281 * @param {Event} event The click or submit event.
283 var onSubmitOrCancel = function(event) {
284 pinForm.removeEventListener('submit', onSubmitOrCancel, false);
285 pinCancel.removeEventListener('click', onSubmitOrCancel, false);
286 var pinField = document.getElementById('pin-entry');
287 var pin = pinField.value;
289 if (event.target == pinForm) {
290 event.preventDefault();
292 // Set the focus away from the password field. This has to be done
293 // before the password field gets hidden, to work around a Blink
294 // clipboard-handling bug - http://crbug.com/281523.
295 document.getElementById('pin-connect-button').focus();
297 remoting.setMode(remoting.AppMode.CLIENT_CONNECTING);
299 if (/** @type {boolean} */(rememberPinCheckbox.checked)) {
300 remoting.connector.pairingRequested = true;
303 remoting.setMode(remoting.AppMode.HOME);
306 pinForm.addEventListener('submit', onSubmitOrCancel, false);
307 pinCancel.addEventListener('click', onSubmitOrCancel, false);
308 rememberPin.hidden = !supportsPairing;
309 rememberPinCheckbox.checked = false;
310 var message = document.getElementById('pin-message');
311 l10n.localizeElement(message, host.hostName);
312 remoting.setMode(remoting.AppMode.CLIENT_PIN_PROMPT);
315 /** @param {Object} settings */
316 var connectMe2MeHostSettingsRetrieved = function(settings) {
317 /** @type {string} */
319 /** @type {string} */
320 var sharedSecret = '';
321 var pairingInfo = /** @type {Object} */ (settings['pairingInfo']);
323 clientId = /** @type {string} */ (pairingInfo['clientId']);
324 sharedSecret = /** @type {string} */ (pairingInfo['sharedSecret']);
326 remoting.connector.connectMe2Me(host, requestPin, fetchThirdPartyToken,
327 clientId, sharedSecret);
330 remoting.HostSettings.load(host.hostId, connectMe2MeHostSettingsRetrieved);
333 /** @param {remoting.ClientSession} clientSession */
334 remoting.onConnected = function(clientSession) {
335 remoting.clientSession = clientSession;
336 remoting.clientSession.addEventListener('stateChanged', onClientStateChange_);
337 setConnectionInterruptedButtonsText_();
338 var connectedTo = document.getElementById('connected-to');
339 connectedTo.innerText = remoting.connector.getHostDisplayName();
340 document.getElementById('access-code-entry').value = '';
341 remoting.setMode(remoting.AppMode.IN_SESSION);
342 remoting.toolbar.center();
343 remoting.toolbar.preview();
344 remoting.clipboard.startSession();
346 if (remoting.connector.pairingRequested) {
348 * @param {string} clientId
349 * @param {string} sharedSecret
351 var onPairingComplete = function(clientId, sharedSecret) {
355 sharedSecret: sharedSecret
358 remoting.HostSettings.save(remoting.connector.getHostId(), pairingInfo);
359 remoting.connector.updatePairingInfo(clientId, sharedSecret);
361 // Use the platform name as a proxy for the local computer name.
362 // TODO(jamiewalch): Use a descriptive name for the local computer, for
363 // example, its Chrome Sync name.
365 if (navigator.platform.indexOf('Mac') != -1) {
367 } else if (navigator.platform.indexOf('Win32') != -1) {
368 clientName = 'Windows';
369 } else if (navigator.userAgent.match(/\bCrOS\b/)) {
370 clientName = 'ChromeOS';
371 } else if (navigator.platform.indexOf('Linux') != -1) {
372 clientName = 'Linux';
374 console.log('Unrecognized client platform. Using navigator.platform.');
375 clientName = navigator.platform;
377 clientSession.requestPairing(clientName, onPairingComplete);
382 * Extension message handler.
384 * @param {string} type The type of the extension message.
385 * @param {string} data The payload of the extension message.
386 * @return {boolean} Return true if the extension message was recognized.
388 remoting.onExtensionMessage = function(type, data) {
393 * Create a session connector if one doesn't already exist.
395 remoting.ensureSessionConnector_ = function() {
396 if (!remoting.connector) {
397 remoting.connector = new remoting.SessionConnector(
398 document.getElementById('client-plugin-container'),
399 remoting.onConnected,
400 showConnectError_, remoting.onExtensionMessage);