Upstream version 10.39.225.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
17  *     connection 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  * Callback function called when the state of the client plugin changes. The
81  * current and previous states are available via the |state| member variable.
82  *
83  * @param {remoting.ClientSession.StateEvent} state
84  */
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);
95       } else {
96         remoting.setMode(remoting.AppMode.CLIENT_SESSION_FINISHED_ME2ME);
97       }
98       break;
99
100     case remoting.ClientSession.State.FAILED:
101       var error = remoting.clientSession.getError();
102       console.error('Client plugin reported connection failed: ' + error);
103       if (error == null) {
104         error = remoting.Error.UNEXPECTED;
105       }
106       showConnectError_(error);
107       break;
108
109     default:
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);
114       break;
115   }
116
117   remoting.clientSession.removeEventListener('stateChanged',
118                                              onClientStateChange_);
119   remoting.clientSession.cleanup();
120   remoting.clientSession = null;
121 }
122
123 /**
124  * Show a client-side error message.
125  *
126  * @param {remoting.Error} errorTag The error to be localized and
127  *     displayed.
128  * @return {void} Nothing.
129  */
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
142     );
143   } else {
144     remoting.setMode(remoting.AppMode.CLIENT_CONNECT_FAILED_ME2ME);
145   }
146 }
147
148 /**
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.
152  */
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');
160 }
161
162 /**
163  * Timer callback to update the statistics panel.
164  */
165 function updateStatistics_() {
166   if (!remoting.clientSession ||
167       remoting.clientSession.getState() !=
168       remoting.ClientSession.State.CONNECTED) {
169     return;
170   }
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);
176 }
177
178 /**
179  * Entry-point for Me2Me connections, handling showing of the host-upgrade nag
180  * dialog if necessary.
181  *
182  * @param {string} hostId The unique id of the host.
183  * @return {void} Nothing.
184  */
185 remoting.connectMe2Me = function(hostId) {
186   var host = remoting.hostList.getHostForId(hostId);
187   if (!host) {
188     showConnectError_(remoting.Error.HOST_IS_OFFLINE);
189     return;
190   }
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',
197                                 host.hostName);
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);
208       } else {
209         remoting.setMode(remoting.AppMode.HOME);
210       }
211     }
212     connect.addEventListener('click', onClick, false);
213     cancel.addEventListener('click', onClick, false);
214     remoting.setMode(remoting.AppMode.CLIENT_HOST_NEEDS_UPGRADE);
215   } else {
216     remoting.connectMe2MeHostVersionAcknowledged_(host);
217   }
218 };
219
220 /**
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.
223  *
224  * @param {remoting.Host} host The Me2Me host to which to connect.
225  * @return {void} Nothing.
226  */
227 remoting.connectMe2MeHostVersionAcknowledged_ = function(host) {
228   remoting.ensureSessionConnector_();
229   remoting.setMode(remoting.AppMode.CLIENT_CONNECTING);
230
231   /**
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.
236    */
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();
243   };
244
245   /**
246    * @param {boolean} supportsPairing
247    * @param {function(string):void} onPinFetched
248    */
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');
258     /**
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.
263      *
264      * @param {Event} event The click or submit event.
265      */
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;
271       pinField.value = '';
272       if (event.target == pinForm) {
273         event.preventDefault();
274
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();
279
280         remoting.setMode(remoting.AppMode.CLIENT_CONNECTING);
281         onPinFetched(pin);
282         if (/** @type {boolean} */(rememberPinCheckbox.checked)) {
283           /** @type {boolean} */
284           remoting.pairingRequested = true;
285         }
286       } else {
287         remoting.setMode(remoting.AppMode.HOME);
288       }
289     };
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);
297   };
298
299   /** @param {Object} settings */
300   var connectMe2MeHostSettingsRetrieved = function(settings) {
301     /** @type {string} */
302     var clientId = '';
303     /** @type {string} */
304     var sharedSecret = '';
305     var pairingInfo = /** @type {Object} */ (settings['pairingInfo']);
306     if (pairingInfo) {
307       clientId = /** @type {string} */ (pairingInfo['clientId']);
308       sharedSecret = /** @type {string} */ (pairingInfo['sharedSecret']);
309     }
310     remoting.connector.connectMe2Me(host, requestPin, fetchThirdPartyToken,
311                                     clientId, sharedSecret);
312   }
313
314   remoting.HostSettings.load(host.hostId, connectMe2MeHostSettingsRetrieved);
315 };
316
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();
327   }
328   remoting.clipboard.startSession();
329   updateStatistics_();
330   remoting.hangoutSessionEvents.raiseEvent(
331       remoting.hangoutSessionEvents.sessionStateChanged,
332       remoting.ClientSession.State.CONNECTED
333   );
334   if (remoting.pairingRequested) {
335     /**
336      * @param {string} clientId
337      * @param {string} sharedSecret
338      */
339     var onPairingComplete = function(clientId, sharedSecret) {
340       var pairingInfo = {
341         pairingInfo: {
342           clientId: clientId,
343           sharedSecret: sharedSecret
344         }
345       };
346       remoting.HostSettings.save(remoting.connector.getHostId(), pairingInfo);
347       remoting.connector.updatePairingInfo(clientId, sharedSecret);
348     };
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.
352     var clientName = '';
353     if (remoting.platformIsMac()) {
354       clientName = 'Mac';
355     } else if (remoting.platformIsWindows()) {
356       clientName = 'Windows';
357     } else if (remoting.platformIsChromeOS()) {
358       clientName = 'ChromeOS';
359     } else if (remoting.platformIsLinux()) {
360       clientName = 'Linux';
361     } else {
362       console.log('Unrecognized client platform. Using navigator.platform.');
363       clientName = navigator.platform;
364     }
365     clientSession.requestPairing(clientName, onPairingComplete);
366   }
367 };
368
369 /**
370  * Extension message handler.
371  *
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.
375  */
376 remoting.onExtensionMessage = function(type, data) {
377   if (remoting.clientSession) {
378     return remoting.clientSession.handleExtensionMessage(type, data);
379   }
380   return false;
381 };
382
383 /**
384  * Create a session connector if one doesn't already exist.
385  */
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);
392   }
393 };