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
17 * connection is initiated.
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 * Callback function called when the state of the client plugin changes. The
81 * current and previous states are available via the |state| member variable.
83 * @param {remoting.ClientSession.StateEvent} state
85 function onClientStateChange_(state
) {
86 switch (state
.current
) {
87 case remoting
.ClientSession
.State
.CLOSED
:
88 console
.log('Connection closed by host');
89 if (remoting
.clientSession
.getMode() ==
90 remoting
.ClientSession
.Mode
.IT2ME
) {
91 remoting
.setMode(remoting
.AppMode
.CLIENT_SESSION_FINISHED_IT2ME
);
92 remoting
.hangoutSessionEvents
.raiseEvent(
93 remoting
.hangoutSessionEvents
.sessionStateChanged
,
94 remoting
.ClientSession
.State
.CLOSED
);
96 remoting
.setMode(remoting
.AppMode
.CLIENT_SESSION_FINISHED_ME2ME
);
100 case remoting
.ClientSession
.State
.FAILED
:
101 var error
= remoting
.clientSession
.getError();
102 console
.error('Client plugin reported connection failed: ' + error
);
104 error
= remoting
.Error
.UNEXPECTED
;
106 showConnectError_(error
);
110 console
.error('Unexpected client plugin state: ' + state
.current
);
111 // This should only happen if the web-app and client plugin get out of
112 // sync, so MISSING_PLUGIN is a suitable error.
113 showConnectError_(remoting
.Error
.MISSING_PLUGIN
);
117 remoting
.clientSession
.removeEventListener('stateChanged',
118 onClientStateChange_
);
119 remoting
.clientSession
.cleanup();
120 remoting
.clientSession
= null;
124 * Show a client-side error message.
126 * @param {remoting.Error} errorTag The error to be localized and
128 * @return {void} Nothing.
130 function showConnectError_(errorTag
) {
131 console
.error('Connection failed: ' + errorTag
);
132 var errorDiv
= document
.getElementById('connect-error-message');
133 l10n
.localizeElementFromTag(errorDiv
, /** @type {string} */ (errorTag
));
134 remoting
.accessCode
= '';
135 var mode
= remoting
.clientSession
? remoting
.clientSession
.getMode()
136 : remoting
.connector
.getConnectionMode();
137 if (mode
== remoting
.ClientSession
.Mode
.IT2ME
) {
138 remoting
.setMode(remoting
.AppMode
.CLIENT_CONNECT_FAILED_IT2ME
);
139 remoting
.hangoutSessionEvents
.raiseEvent(
140 remoting
.hangoutSessionEvents
.sessionStateChanged
,
141 remoting
.ClientSession
.State
.FAILED
144 remoting
.setMode(remoting
.AppMode
.CLIENT_CONNECT_FAILED_ME2ME
);
149 * Set the text on the buttons shown under the error message so that they are
150 * easy to understand in the case where a successful connection failed, as
151 * opposed to the case where a connection never succeeded.
153 function setConnectionInterruptedButtonsText_() {
154 var button1
= document
.getElementById('client-reconnect-button');
155 l10n
.localizeElementFromTag(button1
, /*i18n-content*/'RECONNECT');
156 button1
.removeAttribute('autofocus');
157 var button2
= document
.getElementById('client-finished-me2me-button');
158 l10n
.localizeElementFromTag(button2
, /*i18n-content*/'OK');
159 button2
.setAttribute('autofocus', 'autofocus');
163 * Timer callback to update the statistics panel.
165 function updateStatistics_() {
166 if (!remoting
.clientSession
||
167 remoting
.clientSession
.getState() !=
168 remoting
.ClientSession
.State
.CONNECTED
) {
171 var perfstats
= remoting
.clientSession
.getPerfStats();
172 remoting
.stats
.update(perfstats
);
173 remoting
.clientSession
.logStatistics(perfstats
);
174 // Update the stats once per second.
175 window
.setTimeout(updateStatistics_
, 1000);
179 * Entry-point for Me2Me connections, handling showing of the host-upgrade nag
180 * dialog if necessary.
182 * @param {string} hostId The unique id of the host.
183 * @return {void} Nothing.
185 remoting
.connectMe2Me = function(hostId
) {
186 var host
= remoting
.hostList
.getHostForId(hostId
);
188 showConnectError_(remoting
.Error
.HOST_IS_OFFLINE
);
191 var webappVersion
= chrome
.runtime
.getManifest().version
;
192 if (remoting
.Host
.needsUpdate(host
, webappVersion
)) {
193 var needsUpdateMessage
=
194 document
.getElementById('host-needs-update-message');
195 l10n
.localizeElementFromTag(needsUpdateMessage
,
196 /*i18n-content*/'HOST_NEEDS_UPDATE_TITLE',
198 /** @type {Element} */
199 var connect
= document
.getElementById('host-needs-update-connect-button');
200 /** @type {Element} */
201 var cancel
= document
.getElementById('host-needs-update-cancel-button');
202 /** @param {Event} event */
203 var onClick = function(event
) {
204 connect
.removeEventListener('click', onClick
, false);
205 cancel
.removeEventListener('click', onClick
, false);
206 if (event
.target
== connect
) {
207 remoting
.connectMe2MeHostVersionAcknowledged_(host
);
209 remoting
.setMode(remoting
.AppMode
.HOME
);
212 connect
.addEventListener('click', onClick
, false);
213 cancel
.addEventListener('click', onClick
, false);
214 remoting
.setMode(remoting
.AppMode
.CLIENT_HOST_NEEDS_UPGRADE
);
216 remoting
.connectMe2MeHostVersionAcknowledged_(host
);
221 * Shows PIN entry screen localized to include the host name, and registers
222 * a host-specific one-shot event handler for the form submission.
224 * @param {remoting.Host} host The Me2Me host to which to connect.
225 * @return {void} Nothing.
227 remoting
.connectMe2MeHostVersionAcknowledged_ = function(host
) {
228 remoting
.ensureSessionConnector_();
229 remoting
.setMode(remoting
.AppMode
.CLIENT_CONNECTING
);
232 * @param {string} tokenUrl Token-issue URL received from the host.
233 * @param {string} scope OAuth scope to request the token for.
234 * @param {string} hostPublicKey Host public key (DER and Base64 encoded).
235 * @param {function(string, string):void} onThirdPartyTokenFetched Callback.
237 var fetchThirdPartyToken = function(
238 tokenUrl
, hostPublicKey
, scope
, onThirdPartyTokenFetched
) {
239 var thirdPartyTokenFetcher
= new remoting
.ThirdPartyTokenFetcher(
240 tokenUrl
, hostPublicKey
, scope
, host
.tokenUrlPatterns
,
241 onThirdPartyTokenFetched
);
242 thirdPartyTokenFetcher
.fetchToken();
246 * @param {boolean} supportsPairing
247 * @param {function(string):void} onPinFetched
249 var requestPin = function(supportsPairing
, onPinFetched
) {
250 /** @type {Element} */
251 var pinForm
= document
.getElementById('pin-form');
252 /** @type {Element} */
253 var pinCancel
= document
.getElementById('cancel-pin-entry-button');
254 /** @type {Element} */
255 var rememberPin
= document
.getElementById('remember-pin');
256 /** @type {Element} */
257 var rememberPinCheckbox
= document
.getElementById('remember-pin-checkbox');
259 * Event handler for both the 'submit' and 'cancel' actions. Using
260 * a single handler for both greatly simplifies the task of making
261 * them one-shot. If separate handlers were used, each would have
262 * to unregister both itself and the other.
264 * @param {Event} event The click or submit event.
266 var onSubmitOrCancel = function(event
) {
267 pinForm
.removeEventListener('submit', onSubmitOrCancel
, false);
268 pinCancel
.removeEventListener('click', onSubmitOrCancel
, false);
269 var pinField
= document
.getElementById('pin-entry');
270 var pin
= pinField
.value
;
272 if (event
.target
== pinForm
) {
273 event
.preventDefault();
275 // Set the focus away from the password field. This has to be done
276 // before the password field gets hidden, to work around a Blink
277 // clipboard-handling bug - http://crbug.com/281523.
278 document
.getElementById('pin-connect-button').focus();
280 remoting
.setMode(remoting
.AppMode
.CLIENT_CONNECTING
);
282 if (/** @type {boolean} */(rememberPinCheckbox
.checked
)) {
283 /** @type {boolean} */
284 remoting
.pairingRequested
= true;
287 remoting
.setMode(remoting
.AppMode
.HOME
);
290 pinForm
.addEventListener('submit', onSubmitOrCancel
, false);
291 pinCancel
.addEventListener('click', onSubmitOrCancel
, false);
292 rememberPin
.hidden
= !supportsPairing
;
293 rememberPinCheckbox
.checked
= false;
294 var message
= document
.getElementById('pin-message');
295 l10n
.localizeElement(message
, host
.hostName
);
296 remoting
.setMode(remoting
.AppMode
.CLIENT_PIN_PROMPT
);
299 /** @param {Object} settings */
300 var connectMe2MeHostSettingsRetrieved = function(settings
) {
301 /** @type {string} */
303 /** @type {string} */
304 var sharedSecret
= '';
305 var pairingInfo
= /** @type {Object} */ (settings
['pairingInfo']);
307 clientId
= /** @type {string} */ (pairingInfo
['clientId']);
308 sharedSecret
= /** @type {string} */ (pairingInfo
['sharedSecret']);
310 remoting
.connector
.connectMe2Me(host
, requestPin
, fetchThirdPartyToken
,
311 clientId
, sharedSecret
);
314 remoting
.HostSettings
.load(host
.hostId
, connectMe2MeHostSettingsRetrieved
);
317 /** @param {remoting.ClientSession} clientSession */
318 remoting
.onConnected = function(clientSession
) {
319 remoting
.clientSession
= clientSession
;
320 remoting
.clientSession
.addEventListener('stateChanged', onClientStateChange_
);
321 setConnectionInterruptedButtonsText_();
322 document
.getElementById('access-code-entry').value
= '';
323 remoting
.setMode(remoting
.AppMode
.IN_SESSION
);
324 if (!base
.isAppsV2()) {
325 remoting
.toolbar
.center();
326 remoting
.toolbar
.preview();
328 remoting
.clipboard
.startSession();
330 remoting
.hangoutSessionEvents
.raiseEvent(
331 remoting
.hangoutSessionEvents
.sessionStateChanged
,
332 remoting
.ClientSession
.State
.CONNECTED
334 if (remoting
.pairingRequested
) {
336 * @param {string} clientId
337 * @param {string} sharedSecret
339 var onPairingComplete = function(clientId
, sharedSecret
) {
343 sharedSecret
: sharedSecret
346 remoting
.HostSettings
.save(remoting
.connector
.getHostId(), pairingInfo
);
347 remoting
.connector
.updatePairingInfo(clientId
, sharedSecret
);
349 // Use the platform name as a proxy for the local computer name.
350 // TODO(jamiewalch): Use a descriptive name for the local computer, for
351 // example, its Chrome Sync name.
353 if (remoting
.platformIsMac()) {
355 } else if (remoting
.platformIsWindows()) {
356 clientName
= 'Windows';
357 } else if (remoting
.platformIsChromeOS()) {
358 clientName
= 'ChromeOS';
359 } else if (remoting
.platformIsLinux()) {
360 clientName
= 'Linux';
362 console
.log('Unrecognized client platform. Using navigator.platform.');
363 clientName
= navigator
.platform
;
365 clientSession
.requestPairing(clientName
, onPairingComplete
);
370 * Extension message handler.
372 * @param {string} type The type of the extension message.
373 * @param {string} data The payload of the extension message.
374 * @return {boolean} Return true if the extension message was recognized.
376 remoting
.onExtensionMessage = function(type
, data
) {
377 if (remoting
.clientSession
) {
378 return remoting
.clientSession
.handleExtensionMessage(type
, data
);
384 * Create a session connector if one doesn't already exist.
386 remoting
.ensureSessionConnector_ = function() {
387 if (!remoting
.connector
) {
388 remoting
.connector
= remoting
.SessionConnector
.factory
.createConnector(
389 document
.getElementById('video-container'),
390 remoting
.onConnected
,
391 showConnectError_
, remoting
.onExtensionMessage
);