Upstream version 5.34.104.0
[platform/framework/web/crosswalk.git] / src / remoting / webapp / host_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 'host screen' for Chromoting.
8  */
9
10 'use strict';
11
12 /** @suppress {duplicate} */
13 var remoting = remoting || {};
14
15 /**
16  * @type {boolean} Whether or not the last share was cancelled by the user.
17  *     This controls what screen is shown when the host signals completion.
18  * @private
19  */
20 var lastShareWasCancelled_ = false;
21
22 /**
23  * Start a host session. This is the main entry point for the host screen,
24  * called directly from the onclick action of a button on the home screen.
25  * It first verifies that the native host components are installed and asks
26  * to install them if necessary.
27  */
28 remoting.tryShare = function() {
29   /** @type {remoting.HostIt2MeDispatcher} */
30   var hostDispatcher = new remoting.HostIt2MeDispatcher();
31
32   /** @type {remoting.HostInstallDialog} */
33   var hostInstallDialog = null;
34
35   var tryInitializeDispatcher = function() {
36     hostDispatcher.initialize(createPluginForIt2Me,
37                               onDispatcherInitialized,
38                               onDispatcherInitializationFailed);
39   }
40
41  /** @return {remoting.HostPlugin} */
42   var createPluginForIt2Me = function() {
43     return remoting.createNpapiPlugin(
44         document.getElementById('host-plugin-container'));
45   }
46
47   var onDispatcherInitialized = function () {
48     remoting.startHostUsingDispatcher_(hostDispatcher);
49   }
50
51   /** @param {remoting.Error} error */
52   var onDispatcherInitializationFailed = function(error) {
53     if (error != remoting.Error.MISSING_PLUGIN) {
54       showShareError_(error);
55       return;
56     }
57
58     // If we failed to initialize dispatcher then prompt the user to install the
59     // host.
60     if (hostInstallDialog == null) {
61       hostInstallDialog = new remoting.HostInstallDialog();
62
63       (/** @type {remoting.HostInstallDialog} */ hostInstallDialog)
64           .show(tryInitializeDispatcher, onInstallPromptError);
65     } else {
66       (/** @type {remoting.HostInstallDialog} */ hostInstallDialog).tryAgain();
67     }
68   }
69
70   /** @param {remoting.Error} error */
71   var onInstallPromptError = function(error) {
72     if (error == remoting.Error.CANCELLED) {
73       remoting.setMode(remoting.AppMode.HOME);
74     } else {
75       showShareError_(error);
76     }
77   }
78
79   tryInitializeDispatcher();
80 };
81
82 /**
83  * @param {remoting.HostIt2MeDispatcher} hostDispatcher An initialized
84  *     HostIt2MeDispatcher.
85  */
86 remoting.startHostUsingDispatcher_ = function(hostDispatcher) {
87   console.log('Attempting to share...');
88   remoting.identity.callWithToken(
89       remoting.tryShareWithToken_.bind(null, hostDispatcher),
90       remoting.showErrorMessage);
91 }
92
93 /**
94  * @param {remoting.HostIt2MeDispatcher} hostDispatcher An initialized
95  *     HostIt2MeDispatcher.
96  * @param {string} token The OAuth access token.
97  * @private
98  */
99 remoting.tryShareWithToken_ = function(hostDispatcher, token) {
100   lastShareWasCancelled_ = false;
101   onNatTraversalPolicyChanged_(true);  // Hide warning by default.
102   remoting.setMode(remoting.AppMode.HOST_WAITING_FOR_CODE);
103   document.getElementById('cancel-share-button').disabled = false;
104   disableTimeoutCountdown_();
105
106   remoting.hostSession = new remoting.HostSession();
107   var email = /** @type {string} */remoting.identity.getCachedEmail();
108   remoting.hostSession.connect(
109       hostDispatcher, email, token, onHostStateChanged_,
110       onNatTraversalPolicyChanged_, logDebugInfo_, it2meConnectFailed_);
111 };
112
113 /**
114  * Callback for the host plugin to notify the web app of state changes.
115  * @param {remoting.HostSession.State} state The new state of the plugin.
116  * @return {void} Nothing.
117  * @private
118  */
119 function onHostStateChanged_(state) {
120   if (state == remoting.HostSession.State.STARTING) {
121     // Nothing to do here.
122     console.log('Host state: STARTING');
123
124   } else if (state == remoting.HostSession.State.REQUESTED_ACCESS_CODE) {
125     // Nothing to do here.
126     console.log('Host state: REQUESTED_ACCESS_CODE');
127
128   } else if (state == remoting.HostSession.State.RECEIVED_ACCESS_CODE) {
129     console.log('Host state: RECEIVED_ACCESS_CODE');
130     var accessCode = remoting.hostSession.getAccessCode();
131     var accessCodeDisplay = document.getElementById('access-code-display');
132     accessCodeDisplay.innerText = '';
133     // Display the access code in groups of four digits for readability.
134     var kDigitsPerGroup = 4;
135     for (var i = 0; i < accessCode.length; i += kDigitsPerGroup) {
136       var nextFourDigits = document.createElement('span');
137       nextFourDigits.className = 'access-code-digit-group';
138       nextFourDigits.innerText = accessCode.substring(i, i + kDigitsPerGroup);
139       accessCodeDisplay.appendChild(nextFourDigits);
140     }
141     accessCodeExpiresIn_ = remoting.hostSession.getAccessCodeLifetime();
142     if (accessCodeExpiresIn_ > 0) {  // Check it hasn't expired.
143       accessCodeTimerId_ = setInterval(decrementAccessCodeTimeout_, 1000);
144       timerRunning_ = true;
145       updateAccessCodeTimeoutElement_();
146       updateTimeoutStyles_();
147       remoting.setMode(remoting.AppMode.HOST_WAITING_FOR_CONNECTION);
148     } else {
149       // This can only happen if the cloud tells us that the code lifetime is
150       // <= 0s, which shouldn't happen so we don't care how clean this UX is.
151       console.error('Access code already invalid on receipt!');
152       remoting.cancelShare();
153     }
154
155   } else if (state == remoting.HostSession.State.CONNECTED) {
156     console.log('Host state: CONNECTED');
157     var element = document.getElementById('host-shared-message');
158     var client = remoting.hostSession.getClient();
159     l10n.localizeElement(element, client);
160     remoting.setMode(remoting.AppMode.HOST_SHARED);
161     disableTimeoutCountdown_();
162
163   } else if (state == remoting.HostSession.State.DISCONNECTING) {
164     console.log('Host state: DISCONNECTING');
165
166   } else if (state == remoting.HostSession.State.DISCONNECTED) {
167     console.log('Host state: DISCONNECTED');
168     if (remoting.currentMode != remoting.AppMode.HOST_SHARE_FAILED) {
169       // If an error is being displayed, then the plugin should not be able to
170       // hide it by setting the state. Errors must be dismissed by the user
171       // clicking OK, which puts the app into mode HOME.
172       if (lastShareWasCancelled_) {
173         remoting.setMode(remoting.AppMode.HOME);
174       } else {
175         remoting.setMode(remoting.AppMode.HOST_SHARE_FINISHED);
176       }
177     }
178     remoting.hostSession.cleanup();
179
180   } else if (state == remoting.HostSession.State.ERROR) {
181     console.error('Host state: ERROR');
182     showShareError_(remoting.Error.UNEXPECTED);
183   } else if (state == remoting.HostSession.State.INVALID_DOMAIN_ERROR) {
184     console.error('Host state: INVALID_DOMAIN_ERROR');
185     showShareError_(remoting.Error.INVALID_HOST_DOMAIN);
186   } else {
187     console.error('Unknown state -> ' + state);
188   }
189 }
190
191 /**
192  * This is the callback that the host plugin invokes to indicate that there
193  * is additional debug log info to display.
194  * @param {string} msg The message (which will not be localized) to be logged.
195  * @private
196  */
197 function logDebugInfo_(msg) {
198   console.log('plugin: ' + msg);
199 }
200
201 /**
202  * Show a host-side error message.
203  *
204  * @param {string} errorTag The error message to be localized and displayed.
205  * @return {void} Nothing.
206  * @private
207  */
208 function showShareError_(errorTag) {
209   var errorDiv = document.getElementById('host-plugin-error');
210   l10n.localizeElementFromTag(errorDiv, errorTag);
211   console.error('Sharing error: ' + errorTag);
212   remoting.setMode(remoting.AppMode.HOST_SHARE_FAILED);
213 }
214
215 /**
216  * Show a sharing error with error code UNEXPECTED .
217  *
218  * @return {void} Nothing.
219  * @private
220  */
221 function it2meConnectFailed_() {
222   // TODO (weitaosu): Instruct the user to install the native messaging host.
223   // We probably want to add a new error code (with the corresponding error
224   // message for sharing error.
225   console.error('Cannot share desktop.');
226   showShareError_(remoting.Error.UNEXPECTED);
227 }
228
229 /**
230  * Cancel an active or pending it2me share operation.
231  *
232  * @return {void} Nothing.
233  */
234 remoting.cancelShare = function() {
235   document.getElementById('cancel-share-button').disabled = true;
236   console.log('Canceling share...');
237   remoting.lastShareWasCancelled = true;
238   try {
239     remoting.hostSession.disconnect();
240   } catch (error) {
241     // Hack to force JSCompiler type-safety.
242     var errorTyped = /** @type {{description: string}} */ error;
243     console.error('Error disconnecting: ' + errorTyped.description +
244                   '. The host probably crashed.');
245     // TODO(jamiewalch): Clean this up. We should have a class representing
246     // the host plugin, like we do for the client, which should handle crash
247     // reporting and it should use a more detailed error message than the
248     // default 'generic' one. See crbug.com/94624
249     showShareError_(remoting.Error.UNEXPECTED);
250   }
251   disableTimeoutCountdown_();
252 };
253
254 /**
255  * @type {boolean} Whether or not the access code timeout countdown is running.
256  * @private
257  */
258 var timerRunning_ = false;
259
260 /**
261  * @type {number} The id of the access code expiry countdown timer.
262  * @private
263  */
264 var accessCodeTimerId_ = 0;
265
266 /**
267  * @type {number} The number of seconds until the access code expires.
268  * @private
269  */
270 var accessCodeExpiresIn_ = 0;
271
272 /**
273  * The timer callback function
274  * @return {void} Nothing.
275  * @private
276  */
277 function decrementAccessCodeTimeout_() {
278   --accessCodeExpiresIn_;
279   updateAccessCodeTimeoutElement_();
280 };
281
282 /**
283  * Stop the access code timeout countdown if it is running.
284  * @return {void} Nothing.
285  * @private
286  */
287 function disableTimeoutCountdown_() {
288   if (timerRunning_) {
289     clearInterval(accessCodeTimerId_);
290     timerRunning_ = false;
291     updateTimeoutStyles_();
292   }
293 }
294
295 /**
296  * Constants controlling the access code timer countdown display.
297  * @private
298  */
299 var ACCESS_CODE_TIMER_DISPLAY_THRESHOLD_ = 30;
300 var ACCESS_CODE_RED_THRESHOLD_ = 10;
301
302 /**
303  * Show/hide or restyle various elements, depending on the remaining countdown
304  * and timer state.
305  *
306  * @return {boolean} True if the timeout is in progress, false if it has
307  * expired.
308  * @private
309  */
310 function updateTimeoutStyles_() {
311   if (timerRunning_) {
312     if (accessCodeExpiresIn_ <= 0) {
313       remoting.cancelShare();
314       return false;
315     }
316     var accessCode = document.getElementById('access-code-display');
317     if (accessCodeExpiresIn_ <= ACCESS_CODE_RED_THRESHOLD_) {
318       accessCode.classList.add('expiring');
319     } else {
320       accessCode.classList.remove('expiring');
321     }
322   }
323   document.getElementById('access-code-countdown').hidden =
324       (accessCodeExpiresIn_ > ACCESS_CODE_TIMER_DISPLAY_THRESHOLD_) ||
325       !timerRunning_;
326   return true;
327 }
328
329 /**
330  * Update the text and appearance of the access code timeout element to
331  * reflect the time remaining.
332  * @return {void} Nothing.
333  * @private
334  */
335 function updateAccessCodeTimeoutElement_() {
336   var pad = (accessCodeExpiresIn_ < 10) ? '0:0' : '0:';
337   l10n.localizeElement(document.getElementById('seconds-remaining'),
338                        pad + accessCodeExpiresIn_);
339   if (!updateTimeoutStyles_()) {
340     disableTimeoutCountdown_();
341   }
342 }
343
344 /**
345  * Callback to show or hide the NAT traversal warning when the policy changes.
346  * @param {boolean} enabled True if NAT traversal is enabled.
347  * @return {void} Nothing.
348  * @private
349  */
350 function onNatTraversalPolicyChanged_(enabled) {
351   var natBox = document.getElementById('nat-box');
352   if (enabled) {
353     natBox.classList.add('traversal-enabled');
354   } else {
355     natBox.classList.remove('traversal-enabled');
356   }
357 }