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