Upstream version 7.36.149.0
[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   remoting.ensureSessionConnector_();
32   var accessCode = document.getElementById('access-code-entry').value;
33   remoting.setMode(remoting.AppMode.CLIENT_CONNECTING);
34   remoting.connector.connectIT2Me(accessCode);
35 };
36
37 /**
38  * Update the remoting client layout in response to a resize event.
39  *
40  * @return {void} Nothing.
41  */
42 remoting.onResize = function() {
43   if (remoting.clientSession) {
44     remoting.clientSession.onResize();
45   }
46 };
47
48 /**
49  * Handle changes in the visibility of the window, for example by pausing video.
50  *
51  * @return {void} Nothing.
52  */
53 remoting.onVisibilityChanged = function() {
54   if (remoting.clientSession) {
55     remoting.clientSession.pauseVideo(
56       ('hidden' in document) ? document.hidden : document.webkitHidden);
57   }
58 }
59
60 /**
61  * Disconnect the remoting client.
62  *
63  * @return {void} Nothing.
64  */
65 remoting.disconnect = function() {
66   if (!remoting.clientSession) {
67     return;
68   }
69   if (remoting.clientSession.getMode() == remoting.ClientSession.Mode.IT2ME) {
70     remoting.setMode(remoting.AppMode.CLIENT_SESSION_FINISHED_IT2ME);
71   } else {
72     remoting.setMode(remoting.AppMode.CLIENT_SESSION_FINISHED_ME2ME);
73   }
74   remoting.clientSession.disconnect(remoting.Error.NONE);
75   remoting.clientSession = null;
76   console.log('Disconnected.');
77 };
78
79 /**
80  * Sends a Ctrl-Alt-Del sequence to the remoting client.
81  *
82  * @return {void} Nothing.
83  */
84 remoting.sendCtrlAltDel = function() {
85   if (remoting.clientSession) {
86     console.log('Sending Ctrl-Alt-Del.');
87     remoting.clientSession.sendCtrlAltDel();
88   }
89 };
90
91 /**
92  * Sends a Print Screen keypress to the remoting client.
93  *
94  * @return {void} Nothing.
95  */
96 remoting.sendPrintScreen = function() {
97   if (remoting.clientSession) {
98     console.log('Sending Print Screen.');
99     remoting.clientSession.sendPrintScreen();
100   }
101 };
102
103 /**
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.
106  *
107  * @param {remoting.ClientSession.StateEvent} state
108  */
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);
116       } else {
117         remoting.setMode(remoting.AppMode.CLIENT_SESSION_FINISHED_ME2ME);
118       }
119       break;
120
121     case remoting.ClientSession.State.FAILED:
122       var error = remoting.clientSession.getError();
123       console.error('Client plugin reported connection failed: ' + error);
124       if (error == null) {
125         error = remoting.Error.UNEXPECTED;
126       }
127       showConnectError_(error);
128       break;
129
130     default:
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);
135       break;
136   }
137
138   remoting.clientSession.removeEventListener('stateChanged',
139                                              onClientStateChange_);
140   remoting.clientSession.cleanup();
141   remoting.clientSession = null;
142 }
143
144 /**
145  * Show a client-side error message.
146  *
147  * @param {remoting.Error} errorTag The error to be localized and
148  *     displayed.
149  * @return {void} Nothing.
150  */
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);
160   } else {
161     remoting.setMode(remoting.AppMode.CLIENT_CONNECT_FAILED_ME2ME);
162   }
163 }
164
165 /**
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.
169  */
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');
177 }
178
179 /**
180  * Timer callback to update the statistics panel.
181  */
182 function updateStatistics_() {
183   if (!remoting.clientSession ||
184       remoting.clientSession.getState() !=
185       remoting.ClientSession.State.CONNECTED) {
186     return;
187   }
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);
193 }
194
195 /**
196  * Entry-point for Me2Me connections, handling showing of the host-upgrade nag
197  * dialog if necessary.
198  *
199  * @param {string} hostId The unique id of the host.
200  * @return {void} Nothing.
201  */
202 remoting.connectMe2Me = function(hostId) {
203   var host = remoting.hostList.getHostForId(hostId);
204   if (!host) {
205     showConnectError_(remoting.Error.HOST_IS_OFFLINE);
206     return;
207   }
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',
214                                 host.hostName);
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);
225       } else {
226         remoting.setMode(remoting.AppMode.HOME);
227       }
228     }
229     connect.addEventListener('click', onClick, false);
230     cancel.addEventListener('click', onClick, false);
231     remoting.setMode(remoting.AppMode.CLIENT_HOST_NEEDS_UPGRADE);
232   } else {
233     remoting.connectMe2MeHostVersionAcknowledged_(host);
234   }
235 };
236
237 /**
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.
240  *
241  * @param {remoting.Host} host The Me2Me host to which to connect.
242  * @return {void} Nothing.
243  */
244 remoting.connectMe2MeHostVersionAcknowledged_ = function(host) {
245   remoting.ensureSessionConnector_();
246   remoting.setMode(remoting.AppMode.CLIENT_CONNECTING);
247
248   /**
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.
253    */
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();
260   };
261
262   /**
263    * @param {boolean} supportsPairing
264    * @param {function(string):void} onPinFetched
265    */
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');
275     /**
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.
280      *
281      * @param {Event} event The click or submit event.
282      */
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;
288       pinField.value = '';
289       if (event.target == pinForm) {
290         event.preventDefault();
291
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();
296
297         remoting.setMode(remoting.AppMode.CLIENT_CONNECTING);
298         onPinFetched(pin);
299         if (/** @type {boolean} */(rememberPinCheckbox.checked)) {
300           remoting.connector.pairingRequested = true;
301         }
302       } else {
303         remoting.setMode(remoting.AppMode.HOME);
304       }
305     };
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);
313   };
314
315   /** @param {Object} settings */
316   var connectMe2MeHostSettingsRetrieved = function(settings) {
317     /** @type {string} */
318     var clientId = '';
319     /** @type {string} */
320     var sharedSecret = '';
321     var pairingInfo = /** @type {Object} */ (settings['pairingInfo']);
322     if (pairingInfo) {
323       clientId = /** @type {string} */ (pairingInfo['clientId']);
324       sharedSecret = /** @type {string} */ (pairingInfo['sharedSecret']);
325     }
326     remoting.connector.connectMe2Me(host, requestPin, fetchThirdPartyToken,
327                                     clientId, sharedSecret);
328   }
329
330   remoting.HostSettings.load(host.hostId, connectMe2MeHostSettingsRetrieved);
331 };
332
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();
345   updateStatistics_();
346   if (remoting.connector.pairingRequested) {
347     /**
348      * @param {string} clientId
349      * @param {string} sharedSecret
350      */
351     var onPairingComplete = function(clientId, sharedSecret) {
352       var pairingInfo = {
353         pairingInfo: {
354           clientId: clientId,
355           sharedSecret: sharedSecret
356         }
357       };
358       remoting.HostSettings.save(remoting.connector.getHostId(), pairingInfo);
359       remoting.connector.updatePairingInfo(clientId, sharedSecret);
360     };
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.
364     var clientName = '';
365     if (navigator.platform.indexOf('Mac') != -1) {
366       clientName = 'Mac';
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';
373     } else {
374       console.log('Unrecognized client platform. Using navigator.platform.');
375       clientName = navigator.platform;
376     }
377     clientSession.requestPairing(clientName, onPairingComplete);
378   }
379 };
380
381 /**
382  * Extension message handler.
383  *
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.
387  */
388 remoting.onExtensionMessage = function(type, data) {
389   return false;
390 };
391
392 /**
393  * Create a session connector if one doesn't already exist.
394  */
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);
401   }
402 };