Updating trunk VERSION from 2139.0 to 2140.0
[chromium-blink-merge.git] / remoting / webapp / remoting.js
blobfc70aa4483c101bb97f2e5a96060b3920d885afd
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.
5 'use strict';
7 /** @suppress {duplicate} */
8 var remoting = remoting || {};
10 /** @type {remoting.HostSession} */ remoting.hostSession = null;
12 /**
13  * @type {base.EventSource} An event source object for handling global events.
14  *    This is an interim hack.  Eventually, we should move functionalities
15  *    away from the remoting namespace and into smaller objects.
16  */
17 remoting.testEvents;
19 /**
20  * Show the authorization consent UI and register a one-shot event handler to
21  * continue the authorization process.
22  *
23  * @param {function():void} authContinue Callback to invoke when the user
24  *     clicks "Continue".
25  */
26 function consentRequired_(authContinue) {
27   /** @type {HTMLElement} */
28   var dialog = document.getElementById('auth-dialog');
29   /** @type {HTMLElement} */
30   var button = document.getElementById('auth-button');
31   var consentGranted = function(event) {
32     dialog.hidden = true;
33     button.removeEventListener('click', consentGranted, false);
34     authContinue();
35   };
36   dialog.hidden = false;
37   button.addEventListener('click', consentGranted, false);
40 /**
41  * Entry point for app initialization.
42  */
43 remoting.init = function() {
44   if (base.isAppsV2()) {
45     var htmlNode = /** @type {HTMLElement} */ (document.body.parentNode);
46     htmlNode.classList.add('apps-v2');
47   } else {
48     migrateLocalToChromeStorage_();
49   }
51   console.log(remoting.getExtensionInfo());
52   l10n.localize();
54   // Create global objects.
55   remoting.settings = new remoting.Settings();
56   if (base.isAppsV2()) {
57     remoting.identity = new remoting.Identity(consentRequired_);
58     remoting.fullscreen = new remoting.FullscreenAppsV2();
59     remoting.windowFrame = new remoting.WindowFrame(
60         document.getElementById('title-bar'));
61   } else {
62     remoting.oauth2 = new remoting.OAuth2();
63     if (!remoting.oauth2.isAuthenticated()) {
64       document.getElementById('auth-dialog').hidden = false;
65     }
66     remoting.identity = remoting.oauth2;
67     remoting.fullscreen = new remoting.FullscreenAppsV1();
68   }
69   remoting.stats = new remoting.ConnectionStats(
70       document.getElementById('statistics'));
71   remoting.formatIq = new remoting.FormatIq();
72   remoting.hostList = new remoting.HostList(
73       document.getElementById('host-list'),
74       document.getElementById('host-list-empty'),
75       document.getElementById('host-list-error-message'),
76       document.getElementById('host-list-refresh-failed-button'),
77       document.getElementById('host-list-loading-indicator'));
78   remoting.toolbar = new remoting.Toolbar(
79       document.getElementById('session-toolbar'));
80   remoting.clipboard = new remoting.Clipboard();
81   var sandbox = /** @type {HTMLIFrameElement} */
82       document.getElementById('wcs-sandbox');
83   remoting.wcsSandbox = new remoting.WcsSandboxContainer(sandbox.contentWindow);
84   var homeFeedback = new remoting.MenuButton(
85       document.getElementById('help-feedback-main'));
86   var toolbarFeedback = new remoting.MenuButton(
87       document.getElementById('help-feedback-toolbar'));
88   remoting.manageHelpAndFeedback(
89       document.getElementById('title-bar'));
90   remoting.manageHelpAndFeedback(
91       document.getElementById('help-feedback-toolbar'));
92   remoting.manageHelpAndFeedback(
93       document.getElementById('help-feedback-main'));
95   /** @param {remoting.Error} error */
96   var onGetEmailError = function(error) {
97     // No need to show the error message for NOT_AUTHENTICATED
98     // because we will show "auth-dialog".
99     if (error != remoting.Error.NOT_AUTHENTICATED) {
100       remoting.showErrorMessage(error);
101     }
102   }
103   remoting.identity.getEmail(remoting.onEmail, onGetEmailError);
105   remoting.showOrHideIT2MeUi();
106   remoting.showOrHideMe2MeUi();
108   // The plugin's onFocus handler sends a paste command to |window|, because
109   // it can't send one to the plugin element itself.
110   window.addEventListener('paste', pluginGotPaste_, false);
111   window.addEventListener('copy', pluginGotCopy_, false);
113   remoting.initModalDialogs();
115   if (isHostModeSupported_()) {
116     var noShare = document.getElementById('chrome-os-no-share');
117     noShare.parentNode.removeChild(noShare);
118   } else {
119     var button = document.getElementById('share-button');
120     button.disabled = true;
121   }
123   /**
124    * @return {Promise} A promise that resolves to the id of the current
125    * containing tab/window.
126    */
127   var getCurrentId = function () {
128     if (base.isAppsV2()) {
129       return Promise.resolve(chrome.app.window.current().id);
130     }
132     /**
133      * @param {function(*=):void} resolve
134      * @param {function(*=):void} reject
135      */
136     return new Promise(function(resolve, reject) {
137       /** @param {chrome.Tab} tab */
138       chrome.tabs.getCurrent(function(tab){
139         if (tab) {
140           resolve(String(tab.id));
141         }
142         reject('Cannot retrieve the current tab.');
143       });
144     });
145   };
147   var onLoad = function() {
148     // Parse URL parameters.
149     var urlParams = getUrlParameters_();
150     if ('mode' in urlParams) {
151       if (urlParams['mode'] === 'me2me') {
152         var hostId = urlParams['hostId'];
153         remoting.connectMe2Me(hostId);
154         return;
155       } else if (urlParams['mode'] === 'hangout') {
156         /** @param {*} id */
157         getCurrentId().then(function(id) {
158           /** @type {string} */
159           var accessCode = urlParams['accessCode'];
160           remoting.ensureSessionConnector_();
161           remoting.setMode(remoting.AppMode.CLIENT_CONNECTING);
162           remoting.connector.connectIT2Me(accessCode);
164           document.body.classList.add('hangout-remote-desktop');
165           var senderId = /** @type {string} */ String(id);
166           var hangoutSession = new remoting.HangoutSession(senderId);
167           hangoutSession.init();
168         });
169         return;
170       }
171     }
172     // No valid URL parameters, start up normally.
173     remoting.initHomeScreenUi();
174   }
175   remoting.hostList.load(onLoad);
177   // For Apps v1, check the tab type to warn the user if they are not getting
178   // the best keyboard experience.
179   if (!base.isAppsV2() && !remoting.platformIsMac()) {
180     /** @param {boolean} isWindowed */
181     var onIsWindowed = function(isWindowed) {
182       if (!isWindowed) {
183         document.getElementById('startup-mode-box-me2me').hidden = false;
184         document.getElementById('startup-mode-box-it2me').hidden = false;
185       }
186     };
187     isWindowed_(onIsWindowed);
188   }
190   remoting.testEvents = new base.EventSource();
192   /** @enum {string} */
193   remoting.testEvents.Names = {
194     uiModeChanged: 'uiModeChanged'
195   };
196   remoting.testEvents.defineEvents(base.values(remoting.testEvents.Names));
198   remoting.ClientPlugin.preload();
202  * Returns whether or not IT2Me is supported via the host NPAPI plugin.
204  * @return {boolean}
205  */
206 function isIT2MeSupported_() {
207   // Currently, IT2Me on Chromebooks is not supported.
208   return !remoting.runningOnChromeOS();
212  * Returns true if the current platform is fully supported. It's only used when
213  * we detect that host native messaging components are not installed. In that
214  * case the result of this function determines if the webapp should show the
215  * controls that allow to install and enable Me2Me host.
217  * @return {boolean}
218  */
219 remoting.isMe2MeInstallable = function() {
220   // The chromoting host is currently not installable on ChromeOS.
221   // For Linux, we have a install package for Ubuntu but not other distros.
222   // Since we cannot tell from javascript alone the Linux distro the client is
223   // on, we don't show the daemon-control UI for Linux unless the host is
224   // installed.
225   return remoting.platformIsWindows() || remoting.platformIsMac();
229  * Display the user's email address and allow access to the rest of the app,
230  * including parsing URL parameters.
232  * @param {string} email The user's email address.
233  * @return {void} Nothing.
234  */
235 remoting.onEmail = function(email) {
236   document.getElementById('current-email').innerText = email;
237   document.getElementById('get-started-it2me').disabled = false;
238   document.getElementById('get-started-me2me').disabled = false;
242  * initHomeScreenUi is called if the app is not starting up in session mode,
243  * and also if the user cancels pin entry or the connection in session mode.
244  */
245 remoting.initHomeScreenUi = function() {
246   remoting.hostController = new remoting.HostController();
247   document.getElementById('share-button').disabled = !isIT2MeSupported_();
248   remoting.setMode(remoting.AppMode.HOME);
249   remoting.hostSetupDialog =
250       new remoting.HostSetupDialog(remoting.hostController);
251   var dialog = document.getElementById('paired-clients-list');
252   var message = document.getElementById('paired-client-manager-message');
253   var deleteAll = document.getElementById('delete-all-paired-clients');
254   var close = document.getElementById('close-paired-client-manager-dialog');
255   var working = document.getElementById('paired-client-manager-dialog-working');
256   var error = document.getElementById('paired-client-manager-dialog-error');
257   var noPairedClients = document.getElementById('no-paired-clients');
258   remoting.pairedClientManager =
259       new remoting.PairedClientManager(remoting.hostController, dialog, message,
260                                        deleteAll, close, noPairedClients,
261                                        working, error);
262   // Display the cached host list, then asynchronously update and re-display it.
263   remoting.updateLocalHostState();
264   remoting.hostList.refresh(remoting.updateLocalHostState);
265   remoting.butterBar = new remoting.ButterBar();
269  * Fetches local host state and updates the DOM accordingly.
270  */
271 remoting.updateLocalHostState = function() {
272   /**
273    * @param {remoting.HostController.State} state Host state.
274    */
275   var onHostState = function(state) {
276     if (state == remoting.HostController.State.STARTED) {
277       remoting.hostController.getLocalHostId(onHostId.bind(null, state));
278     } else {
279       onHostId(state, null);
280     }
281   };
283   /**
284    * @param {remoting.HostController.State} state Host state.
285    * @param {string?} hostId Host id.
286    */
287   var onHostId = function(state, hostId) {
288     remoting.hostList.setLocalHostStateAndId(state, hostId);
289     remoting.hostList.display();
290   };
292   /**
293    * @param {boolean} response True if the feature is present.
294    */
295   var onHasFeatureResponse = function(response) {
296     /**
297      * @param {remoting.Error} error
298      */
299     var onError = function(error) {
300       console.error('Failed to get pairing status: ' + error);
301       remoting.pairedClientManager.setPairedClients([]);
302     };
304     if (response) {
305       remoting.hostController.getPairedClients(
306           remoting.pairedClientManager.setPairedClients.bind(
307               remoting.pairedClientManager),
308           onError);
309     } else {
310       console.log('Pairing registry not supported by host.');
311       remoting.pairedClientManager.setPairedClients([]);
312     }
313   };
315   remoting.hostController.hasFeature(
316       remoting.HostController.Feature.PAIRING_REGISTRY, onHasFeatureResponse);
317   remoting.hostController.getLocalHostState(onHostState);
321  * @return {string} Information about the current extension.
322  */
323 remoting.getExtensionInfo = function() {
324   var v2OrLegacy = base.isAppsV2() ? " (v2)" : " (legacy)";
325   var manifest = chrome.runtime.getManifest();
326   if (manifest && manifest.version) {
327     var name = chrome.i18n.getMessage('PRODUCT_NAME');
328     return name + ' version: ' + manifest.version + v2OrLegacy;
329   } else {
330     return 'Failed to get product version. Corrupt manifest?';
331   }
335  * Returns Chrome version.
336  * @return {string?}
337  */
338 remoting.getChromeVersion = function() {
339   var match = new RegExp('Chrome/([0-9.]*)').exec(navigator.userAgent);
340   if (match && (match.length >= 2)) {
341     return match[1];
342   }
343   return null;
347  * If an IT2Me client or host is active then prompt the user before closing.
348  * If a Me2Me client is active then don't bother, since closing the window is
349  * the more intuitive way to end a Me2Me session, and re-connecting is easy.
350  */
351 remoting.promptClose = function() {
352   if (!remoting.clientSession ||
353       remoting.clientSession.getMode() == remoting.ClientSession.Mode.ME2ME) {
354     return null;
355   }
356   switch (remoting.currentMode) {
357     case remoting.AppMode.CLIENT_CONNECTING:
358     case remoting.AppMode.HOST_WAITING_FOR_CODE:
359     case remoting.AppMode.HOST_WAITING_FOR_CONNECTION:
360     case remoting.AppMode.HOST_SHARED:
361     case remoting.AppMode.IN_SESSION:
362       return chrome.i18n.getMessage(/*i18n-content*/'CLOSE_PROMPT');
363     default:
364       return null;
365   }
369  * Sign the user out of Chromoting by clearing (and revoking, if possible) the
370  * OAuth refresh token.
372  * Also clear all local storage, to avoid leaking information.
373  */
374 remoting.signOut = function() {
375   remoting.oauth2.clear();
376   chrome.storage.local.clear();
377   remoting.setMode(remoting.AppMode.HOME);
378   document.getElementById('auth-dialog').hidden = false;
382  * Returns whether the app is running on ChromeOS.
384  * @return {boolean} True if the app is running on ChromeOS.
385  */
386 remoting.runningOnChromeOS = function() {
387   return !!navigator.userAgent.match(/\bCrOS\b/);
391  * Callback function called when the browser window gets a paste operation.
393  * @param {Event} eventUncast
394  * @return {void} Nothing.
395  */
396 function pluginGotPaste_(eventUncast) {
397   var event = /** @type {remoting.ClipboardEvent} */ eventUncast;
398   if (event && event.clipboardData) {
399     remoting.clipboard.toHost(event.clipboardData);
400   }
404  * Callback function called when the browser window gets a copy operation.
406  * @param {Event} eventUncast
407  * @return {void} Nothing.
408  */
409 function pluginGotCopy_(eventUncast) {
410   var event = /** @type {remoting.ClipboardEvent} */ eventUncast;
411   if (event && event.clipboardData) {
412     if (remoting.clipboard.toOs(event.clipboardData)) {
413       // The default action may overwrite items that we added to clipboardData.
414       event.preventDefault();
415     }
416   }
420  * Returns whether Host mode is supported on this platform.
422  * @return {boolean} True if Host mode is supported.
423  */
424 function isHostModeSupported_() {
425   // Currently, sharing on Chromebooks is not supported.
426   return !remoting.runningOnChromeOS();
430  * @return {Object.<string, string>} The URL parameters.
431  */
432 function getUrlParameters_() {
433   var result = {};
434   var parts = window.location.search.substring(1).split('&');
435   for (var i = 0; i < parts.length; i++) {
436     var pair = parts[i].split('=');
437     result[pair[0]] = decodeURIComponent(pair[1]);
438   }
439   return result;
443  * @param {string} jsonString A JSON-encoded string.
444  * @return {*} The decoded object, or undefined if the string cannot be parsed.
445  */
446 function jsonParseSafe(jsonString) {
447   try {
448     return JSON.parse(jsonString);
449   } catch (err) {
450     return undefined;
451   }
455  * Return the current time as a formatted string suitable for logging.
457  * @return {string} The current time, formatted as [mmdd/hhmmss.xyz]
458  */
459 remoting.timestamp = function() {
460   /**
461    * @param {number} num A number.
462    * @param {number} len The required length of the answer.
463    * @return {string} The number, formatted as a string of the specified length
464    *     by prepending zeroes as necessary.
465    */
466   var pad = function(num, len) {
467     var result = num.toString();
468     if (result.length < len) {
469       result = new Array(len - result.length + 1).join('0') + result;
470     }
471     return result;
472   };
473   var now = new Date();
474   var timestamp = pad(now.getMonth() + 1, 2) + pad(now.getDate(), 2) + '/' +
475       pad(now.getHours(), 2) + pad(now.getMinutes(), 2) +
476       pad(now.getSeconds(), 2) + '.' + pad(now.getMilliseconds(), 3);
477   return '[' + timestamp + ']';
481  * Show an error message, optionally including a short-cut for signing in to
482  * Chromoting again.
484  * @param {remoting.Error} error
485  * @return {void} Nothing.
486  */
487 remoting.showErrorMessage = function(error) {
488   l10n.localizeElementFromTag(
489       document.getElementById('token-refresh-error-message'),
490       error);
491   var auth_failed = (error == remoting.Error.AUTHENTICATION_FAILED);
492   document.getElementById('token-refresh-auth-failed').hidden = !auth_failed;
493   document.getElementById('token-refresh-other-error').hidden = auth_failed;
494   remoting.setMode(remoting.AppMode.TOKEN_REFRESH_FAILED);
498  * Determine whether or not the app is running in a window.
499  * @param {function(boolean):void} callback Callback to receive whether or not
500  *     the current tab is running in windowed mode.
501  */
502 function isWindowed_(callback) {
503   /** @param {chrome.Window} win The current window. */
504   var windowCallback = function(win) {
505     callback(win.type == 'popup');
506   };
507   /** @param {chrome.Tab} tab The current tab. */
508   var tabCallback = function(tab) {
509     if (tab.pinned) {
510       callback(false);
511     } else {
512       chrome.windows.get(tab.windowId, null, windowCallback);
513     }
514   };
515   if (chrome.tabs) {
516     chrome.tabs.getCurrent(tabCallback);
517   } else {
518     console.error('chome.tabs is not available.');
519   }
523  * Migrate settings in window.localStorage to chrome.storage.local so that
524  * users of older web-apps that used the former do not lose their settings.
525  */
526 function migrateLocalToChromeStorage_() {
527   // The OAuth2 class still uses window.localStorage, so don't migrate any of
528   // those settings.
529   var oauthSettings = [
530       'oauth2-refresh-token',
531       'oauth2-refresh-token-revokable',
532       'oauth2-access-token',
533       'oauth2-xsrf-token',
534       'remoting-email'
535   ];
536   for (var setting in window.localStorage) {
537     if (oauthSettings.indexOf(setting) == -1) {
538       var copy = {}
539       copy[setting] = window.localStorage.getItem(setting);
540       chrome.storage.local.set(copy);
541       window.localStorage.removeItem(setting);
542     }
543   }
547  * Generate a nonce, to be used as an xsrf protection token.
549  * @return {string} A URL-Safe Base64-encoded 128-bit random value. */
550 remoting.generateXsrfToken = function() {
551   var random = new Uint8Array(16);
552   window.crypto.getRandomValues(random);
553   var base64Token = window.btoa(String.fromCharCode.apply(null, random));
554   return base64Token.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
558  * Tests whether we are running on Mac.
560  * @return {boolean} True if the platform is Mac.
561  */
562 remoting.platformIsMac = function() {
563   return navigator.platform.indexOf('Mac') != -1;
567  * Tests whether we are running on Windows.
569  * @return {boolean} True if the platform is Windows.
570  */
571 remoting.platformIsWindows = function() {
572   return (navigator.platform.indexOf('Win32') != -1) ||
573          (navigator.platform.indexOf('Win64') != -1);
577  * Tests whether we are running on Linux.
579  * @return {boolean} True if the platform is Linux.
580  */
581 remoting.platformIsLinux = function() {
582   return (navigator.platform.indexOf('Linux') != -1) &&
583          !remoting.platformIsChromeOS();
587  * Tests whether we are running on ChromeOS.
589  * @return {boolean} True if the platform is ChromeOS.
590  */
591 remoting.platformIsChromeOS = function() {
592   return navigator.userAgent.match(/\bCrOS\b/) != null;