- add sources.
[platform/framework/web/crosswalk.git] / src / remoting / webapp / client_screen.js
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} */
13 var remoting = remoting || {};
14
15 /**
16  * @type {remoting.SessionConnector} The connector object, set when a connection
17  *     is initiated.
18  */
19 remoting.connector = null;
20
21 /**
22  * @type {remoting.ClientSession} The client session object, set once the
23  *     connector has invoked its onOk callback.
24  */
25 remoting.clientSession = null;
26
27 /**
28  * Initiate an IT2Me connection.
29  */
30 remoting.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  */
47 remoting.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  */
58 remoting.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  */
69 remoting.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  */
88 remoting.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  */
100 remoting.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  */
114 function 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  */
154 function 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  */
173 function 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  */
185 function 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  */
205 remoting.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  */
247 remoting.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
300         // Set the focus away from the password field. This has to be done
301         // before the password field gets hidden, to work around a Blink
302         // clipboard-handling bug - http://crbug.com/281523.
303         document.getElementById('pin-connect-button').focus();
304
305         remoting.setMode(remoting.AppMode.CLIENT_CONNECTING);
306         onPinFetched(pin);
307         if (/** @type {boolean} */(rememberPinCheckbox.checked)) {
308           remoting.connector.pairingRequested = true;
309         }
310       } else {
311         remoting.setMode(remoting.AppMode.HOME);
312       }
313     };
314     pinForm.addEventListener('submit', onSubmitOrCancel, false);
315     pinCancel.addEventListener('click', onSubmitOrCancel, false);
316     rememberPin.hidden = !supportsPairing;
317     rememberPinCheckbox.checked = false;
318     var message = document.getElementById('pin-message');
319     l10n.localizeElement(message, host.hostName);
320     remoting.setMode(remoting.AppMode.CLIENT_PIN_PROMPT);
321   };
322
323   /** @param {Object} settings */
324   var connectMe2MeHostSettingsRetrieved = function(settings) {
325     /** @type {string} */
326     var clientId = '';
327     /** @type {string} */
328     var sharedSecret = '';
329     var pairingInfo = /** @type {Object} */ (settings['pairingInfo']);
330     if (pairingInfo) {
331       clientId = /** @type {string} */ (pairingInfo['clientId']);
332       sharedSecret = /** @type {string} */ (pairingInfo['sharedSecret']);
333     }
334     remoting.connector.connectMe2Me(host, requestPin, fetchThirdPartyToken,
335                                     clientId, sharedSecret);
336   }
337
338   remoting.HostSettings.load(host.hostId, connectMe2MeHostSettingsRetrieved);
339 };
340
341 /** @param {remoting.ClientSession} clientSession */
342 remoting.onConnected = function(clientSession) {
343   remoting.clientSession = clientSession;
344   remoting.clientSession.setOnStateChange(onClientStateChange_);
345   setConnectionInterruptedButtonsText_();
346   var connectedTo = document.getElementById('connected-to');
347   connectedTo.innerText = remoting.connector.getHostDisplayName();
348   document.getElementById('access-code-entry').value = '';
349   remoting.setMode(remoting.AppMode.IN_SESSION);
350   remoting.toolbar.center();
351   remoting.toolbar.preview();
352   remoting.clipboard.startSession();
353   updateStatistics_();
354   if (remoting.connector.pairingRequested) {
355     /**
356      * @param {string} clientId
357      * @param {string} sharedSecret
358      */
359     var onPairingComplete = function(clientId, sharedSecret) {
360       var pairingInfo = {
361         pairingInfo: {
362           clientId: clientId,
363           sharedSecret: sharedSecret
364         }
365       };
366       remoting.HostSettings.save(remoting.connector.getHostId(), pairingInfo);
367       remoting.connector.updatePairingInfo(clientId, sharedSecret);
368     };
369     // Use the platform name as a proxy for the local computer name.
370     // TODO(jamiewalch): Use a descriptive name for the local computer, for
371     // example, its Chrome Sync name.
372     var clientName = '';
373     if (navigator.platform.indexOf('Mac') != -1) {
374       clientName = 'Mac';
375     } else if (navigator.platform.indexOf('Win32') != -1) {
376       clientName = 'Windows';
377     } else if (navigator.userAgent.match(/\bCrOS\b/)) {
378       clientName = 'ChromeOS';
379     } else if (navigator.platform.indexOf('Linux') != -1) {
380       clientName = 'Linux';
381     } else {
382       console.log('Unrecognized client platform. Using navigator.platform.');
383       clientName = navigator.platform;
384     }
385     clientSession.requestPairing(clientName, onPairingComplete);
386   }
387 };