| OLD | NEW | 
 | (Empty) | 
|    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 {boolean} Whether or not the plugin should scale itself. |  | 
|   17  */ |  | 
|   18 remoting.scaleToFit = false; |  | 
|   19  |  | 
|   20 /** |  | 
|   21  * @type {remoting.ClientSession} The client session object, set once the |  | 
|   22  *     access code has been successfully verified. |  | 
|   23  */ |  | 
|   24 remoting.clientSession = null; |  | 
|   25  |  | 
|   26 /** |  | 
|   27  * @type {string} The normalized access code. |  | 
|   28  */ |  | 
|   29 remoting.accessCode = ''; |  | 
|   30  |  | 
|   31 /** |  | 
|   32  * @type {string} The host's JID, returned by the server. |  | 
|   33  */ |  | 
|   34 remoting.hostJid = ''; |  | 
|   35  |  | 
|   36 /** |  | 
|   37  * @type {string} For Me2Me connections, the id of the current host. |  | 
|   38  */ |  | 
|   39 remoting.hostId = ''; |  | 
|   40  |  | 
|   41 /** |  | 
|   42  * @type {boolean} For Me2Me connections. Set to true if connection |  | 
|   43  * must be retried on failure. |  | 
|   44  */ |  | 
|   45 remoting.retryIfOffline = false; |  | 
|   46  |  | 
|   47 /** |  | 
|   48  * @type {string} The host's public key, returned by the server. |  | 
|   49  */ |  | 
|   50 remoting.hostPublicKey = ''; |  | 
|   51  |  | 
|   52 /** |  | 
|   53  * @type {XMLHttpRequest} The XHR object corresponding to the current |  | 
|   54  *     support-hosts request, if there is one outstanding. |  | 
|   55  * @private |  | 
|   56  */ |  | 
|   57 remoting.supportHostsXhr_ = null; |  | 
|   58  |  | 
|   59 /** |  | 
|   60  * @enum {string} |  | 
|   61  */ |  | 
|   62 remoting.ConnectionType = { |  | 
|   63   It2Me: 'It2Me', |  | 
|   64   Me2Me: 'Me2Me' |  | 
|   65 }; |  | 
|   66  |  | 
|   67 /** |  | 
|   68  * @type {remoting.ConnectionType?} |  | 
|   69  */ |  | 
|   70 remoting.currentConnectionType = null; |  | 
|   71  |  | 
|   72 /** |  | 
|   73  * Entry point for the 'connect' functionality. This function defers to the |  | 
|   74  * WCS loader to call it back with an access token. |  | 
|   75  */ |  | 
|   76 remoting.connectIt2Me = function() { |  | 
|   77   remoting.currentConnectionType = remoting.ConnectionType.It2Me; |  | 
|   78   document.getElementById('cancel-button').disabled = false; |  | 
|   79   remoting.WcsLoader.load(connectIt2MeWithAccessToken_); |  | 
|   80 }; |  | 
|   81  |  | 
|   82 /** |  | 
|   83  * Cancel an incomplete connect operation. |  | 
|   84  * |  | 
|   85  * @return {void} Nothing. |  | 
|   86  */ |  | 
|   87 remoting.cancelConnect = function() { |  | 
|   88   if (remoting.supportHostsXhr_) { |  | 
|   89     remoting.supportHostsXhr_.abort(); |  | 
|   90     remoting.supportHostsXhr_ = null; |  | 
|   91   } |  | 
|   92   if (remoting.clientSession) { |  | 
|   93     remoting.clientSession.removePlugin(); |  | 
|   94     remoting.clientSession = null; |  | 
|   95   } |  | 
|   96   remoting.setMode(remoting.AppMode.HOME); |  | 
|   97 }; |  | 
|   98  |  | 
|   99 /** |  | 
|  100  * Enable or disable scale-to-fit. |  | 
|  101  * |  | 
|  102  * @param {Event} event The click event. The style of the target is updated to |  | 
|  103  *     reflect the new scaling state. |  | 
|  104  * @return {void} Nothing. |  | 
|  105  */ |  | 
|  106 remoting.toggleScaleToFit = function(event) { |  | 
|  107   var button = /** @type Element */(event.target); |  | 
|  108   remoting.scaleToFit = !remoting.scaleToFit; |  | 
|  109   if (remoting.scaleToFit) { |  | 
|  110     addClass(button, 'toggle-button-active'); |  | 
|  111   } else { |  | 
|  112     removeClass(button, 'toggle-button-active'); |  | 
|  113   } |  | 
|  114   remoting.clientSession.updateDimensions(); |  | 
|  115 }; |  | 
|  116  |  | 
|  117 /** |  | 
|  118  * Update the remoting client layout in response to a resize event. |  | 
|  119  * |  | 
|  120  * @return {void} Nothing. |  | 
|  121  */ |  | 
|  122 remoting.onResize = function() { |  | 
|  123   if (remoting.clientSession) |  | 
|  124     remoting.clientSession.onResize(); |  | 
|  125 }; |  | 
|  126  |  | 
|  127 /** |  | 
|  128  * Disconnect the remoting client. |  | 
|  129  * |  | 
|  130  * @return {void} Nothing. |  | 
|  131  */ |  | 
|  132 remoting.disconnect = function() { |  | 
|  133   if (remoting.clientSession) { |  | 
|  134     remoting.clientSession.disconnect(); |  | 
|  135     remoting.clientSession = null; |  | 
|  136     remoting.debug.log('Disconnected.'); |  | 
|  137     if (remoting.currentConnectionType == remoting.ConnectionType.It2Me) { |  | 
|  138       remoting.setMode(remoting.AppMode.CLIENT_SESSION_FINISHED_IT2ME); |  | 
|  139     } else { |  | 
|  140       remoting.setMode(remoting.AppMode.CLIENT_SESSION_FINISHED_ME2ME); |  | 
|  141     } |  | 
|  142   } |  | 
|  143 }; |  | 
|  144  |  | 
|  145 /** |  | 
|  146  * If WCS was successfully loaded, proceed with the connection, otherwise |  | 
|  147  * report an error. |  | 
|  148  * |  | 
|  149  * @param {string?} token The OAuth2 access token, or null if an error occurred. |  | 
|  150  * @return {void} Nothing. |  | 
|  151  */ |  | 
|  152 function connectIt2MeWithAccessToken_(token) { |  | 
|  153   if (token) { |  | 
|  154     var accessCode = document.getElementById('access-code-entry').value; |  | 
|  155     remoting.accessCode = normalizeAccessCode_(accessCode); |  | 
|  156     // At present, only 12-digit access codes are supported, of which the first |  | 
|  157     // 7 characters are the supportId. |  | 
|  158     var kSupportIdLen = 7; |  | 
|  159     var kHostSecretLen = 5; |  | 
|  160     var kAccessCodeLen = kSupportIdLen + kHostSecretLen; |  | 
|  161     if (remoting.accessCode.length != kAccessCodeLen) { |  | 
|  162       remoting.debug.log('Bad access code length'); |  | 
|  163       showConnectError_(remoting.Error.INVALID_ACCESS_CODE); |  | 
|  164     } else { |  | 
|  165       var supportId = remoting.accessCode.substring(0, kSupportIdLen); |  | 
|  166       remoting.setMode(remoting.AppMode.CLIENT_CONNECTING); |  | 
|  167       resolveSupportId(supportId); |  | 
|  168     } |  | 
|  169   } else { |  | 
|  170     showConnectError_(remoting.Error.AUTHENTICATION_FAILED); |  | 
|  171   } |  | 
|  172 } |  | 
|  173  |  | 
|  174 /** |  | 
|  175  * Callback function called when the state of the client plugin changes. The |  | 
|  176  * current state is available via the |state| member variable. |  | 
|  177  * |  | 
|  178  * @param {number} oldState The previous state of the plugin. |  | 
|  179  * @param {number} newState The current state of the plugin. |  | 
|  180  */ |  | 
|  181 // TODO(jamiewalch): Make this pass both the current and old states to avoid |  | 
|  182 // race conditions. |  | 
|  183 function onClientStateChange_(oldState, newState) { |  | 
|  184   if (!remoting.clientSession) { |  | 
|  185     // If the connection has been cancelled, then we no longer have a reference |  | 
|  186     // to the session object and should ignore any state changes. |  | 
|  187     return; |  | 
|  188   } |  | 
|  189   if (newState == remoting.ClientSession.State.CREATED) { |  | 
|  190     remoting.debug.log('Created plugin'); |  | 
|  191  |  | 
|  192   } else if (newState == remoting.ClientSession.State.BAD_PLUGIN_VERSION) { |  | 
|  193     showConnectError_(remoting.Error.BAD_PLUGIN_VERSION); |  | 
|  194  |  | 
|  195   } else if (newState == remoting.ClientSession.State.CONNECTING) { |  | 
|  196     remoting.debug.log('Connecting as ' + remoting.oauth2.getCachedEmail()); |  | 
|  197  |  | 
|  198   } else if (newState == remoting.ClientSession.State.INITIALIZING) { |  | 
|  199     remoting.debug.log('Initializing connection'); |  | 
|  200  |  | 
|  201   } else if (newState == remoting.ClientSession.State.CONNECTED) { |  | 
|  202     if (remoting.clientSession) { |  | 
|  203       remoting.setMode(remoting.AppMode.IN_SESSION); |  | 
|  204       remoting.toolbar.center(); |  | 
|  205       remoting.toolbar.preview(); |  | 
|  206       updateStatistics_(); |  | 
|  207     } |  | 
|  208  |  | 
|  209   } else if (newState == remoting.ClientSession.State.CLOSED) { |  | 
|  210     if (oldState == remoting.ClientSession.State.CONNECTED) { |  | 
|  211       remoting.clientSession.removePlugin(); |  | 
|  212       remoting.clientSession = null; |  | 
|  213       remoting.debug.log('Connection closed by host'); |  | 
|  214       if (remoting.currentConnectionType == remoting.ConnectionType.It2Me) { |  | 
|  215         remoting.setMode(remoting.AppMode.CLIENT_SESSION_FINISHED_IT2ME); |  | 
|  216       } else { |  | 
|  217         remoting.setMode(remoting.AppMode.CLIENT_SESSION_FINISHED_ME2ME); |  | 
|  218       } |  | 
|  219     } else { |  | 
|  220       // The transition from CONNECTING to CLOSED state may happen |  | 
|  221       // only with older client plugins. Current version should go the |  | 
|  222       // FAILED state when connection fails. |  | 
|  223       showConnectError_(remoting.Error.INVALID_ACCESS_CODE); |  | 
|  224     } |  | 
|  225  |  | 
|  226   } else if (newState == remoting.ClientSession.State.CONNECTION_FAILED) { |  | 
|  227     remoting.debug.log('Client plugin reported connection failed: ' + |  | 
|  228                        remoting.clientSession.error); |  | 
|  229     if (remoting.clientSession.error == |  | 
|  230         remoting.ClientSession.ConnectionError.HOST_IS_OFFLINE) { |  | 
|  231       retryConnectOrReportOffline_(); |  | 
|  232     } else if (remoting.clientSession.error == |  | 
|  233                remoting.ClientSession.ConnectionError.SESSION_REJECTED) { |  | 
|  234       showConnectError_(remoting.Error.INVALID_ACCESS_CODE); |  | 
|  235     } else if (remoting.clientSession.error == |  | 
|  236                remoting.ClientSession.ConnectionError.INCOMPATIBLE_PROTOCOL) { |  | 
|  237       showConnectError_(remoting.Error.INCOMPATIBLE_PROTOCOL); |  | 
|  238     } else if (remoting.clientSession.error == |  | 
|  239                remoting.ClientSession.ConnectionError.NETWORK_FAILURE) { |  | 
|  240       showConnectError_(remoting.Error.GENERIC); |  | 
|  241     } else { |  | 
|  242       showConnectError_(remoting.Error.GENERIC); |  | 
|  243     } |  | 
|  244  |  | 
|  245   } else { |  | 
|  246     remoting.debug.log('Unexpected client plugin state: ' + newState); |  | 
|  247     // This should only happen if the web-app and client plugin get out of |  | 
|  248     // sync, and even then the version check should allow compatibility. |  | 
|  249     showConnectError_(remoting.Error.MISSING_PLUGIN); |  | 
|  250   } |  | 
|  251 } |  | 
|  252  |  | 
|  253 /** |  | 
|  254  * If we have a hostId to retry, try refreshing it and connecting again. If not, |  | 
|  255  * then show the 'host offline' error message. |  | 
|  256  * |  | 
|  257  * @return {void} Nothing. |  | 
|  258  */ |  | 
|  259 function retryConnectOrReportOffline_() { |  | 
|  260   if (remoting.hostId && remoting.retryIfOffline) { |  | 
|  261     console.log('Connection failed. Retrying.'); |  | 
|  262     /** @param {boolean} success True if the refresh was successful. */ |  | 
|  263     var onDone = function(success) { |  | 
|  264       if (success) { |  | 
|  265         remoting.connectMe2Me(remoting.hostId, false); |  | 
|  266       } else { |  | 
|  267         showConnectError_(remoting.Error.HOST_IS_OFFLINE); |  | 
|  268       } |  | 
|  269     }; |  | 
|  270     remoting.hostList.refresh(onDone); |  | 
|  271   } else { |  | 
|  272     console.log('Connection failed. Not retrying.'); |  | 
|  273     showConnectError_(remoting.Error.HOST_IS_OFFLINE); |  | 
|  274   } |  | 
|  275 } |  | 
|  276  |  | 
|  277 /** |  | 
|  278  * Create the client session object and initiate the connection. |  | 
|  279  * |  | 
|  280  * @return {void} Nothing. |  | 
|  281  */ |  | 
|  282 function startSession_() { |  | 
|  283   remoting.debug.log('Starting session...'); |  | 
|  284   var accessCode = document.getElementById('access-code-entry'); |  | 
|  285   accessCode.value = '';  // The code has been validated and won't work again. |  | 
|  286   remoting.clientSession = |  | 
|  287       new remoting.ClientSession( |  | 
|  288           remoting.hostJid, remoting.hostPublicKey, |  | 
|  289           remoting.accessCode, |  | 
|  290           /** @type {string} */ (remoting.oauth2.getCachedEmail()), |  | 
|  291           onClientStateChange_); |  | 
|  292   /** @param {string} token The auth token. */ |  | 
|  293   var createPluginAndConnect = function(token) { |  | 
|  294     remoting.clientSession.createPluginAndConnect( |  | 
|  295         document.getElementById('session-mode'), |  | 
|  296         token); |  | 
|  297   }; |  | 
|  298   remoting.oauth2.callWithToken(createPluginAndConnect); |  | 
|  299 } |  | 
|  300  |  | 
|  301 /** |  | 
|  302  * Show a client-side error message. |  | 
|  303  * |  | 
|  304  * @param {remoting.Error} errorTag The error to be localized and |  | 
|  305  *     displayed. |  | 
|  306  * @return {void} Nothing. |  | 
|  307  */ |  | 
|  308 function showConnectError_(errorTag) { |  | 
|  309   remoting.debug.log('Connection failed: ' + errorTag); |  | 
|  310   var errorDiv = document.getElementById('connect-error-message'); |  | 
|  311   l10n.localizeElementFromTag(errorDiv, /** @type {string} */ (errorTag)); |  | 
|  312   remoting.accessCode = ''; |  | 
|  313   if (remoting.clientSession) { |  | 
|  314     remoting.clientSession.disconnect(); |  | 
|  315     remoting.clientSession = null; |  | 
|  316   } |  | 
|  317   if (remoting.currentConnectionType == remoting.ConnectionType.It2Me) { |  | 
|  318     remoting.setMode(remoting.AppMode.CLIENT_CONNECT_FAILED_IT2ME); |  | 
|  319   } else { |  | 
|  320     remoting.setMode(remoting.AppMode.CLIENT_CONNECT_FAILED_ME2ME); |  | 
|  321   } |  | 
|  322 } |  | 
|  323  |  | 
|  324 /** |  | 
|  325  * Parse the response from the server to a request to resolve a support id. |  | 
|  326  * |  | 
|  327  * @param {XMLHttpRequest} xhr The XMLHttpRequest object. |  | 
|  328  * @return {void} Nothing. |  | 
|  329  */ |  | 
|  330 function parseServerResponse_(xhr) { |  | 
|  331   remoting.supportHostsXhr_ = null; |  | 
|  332   remoting.debug.log('parseServerResponse: status = ' + xhr.status); |  | 
|  333   if (xhr.status == 200) { |  | 
|  334     var host = /** @type {{data: {jabberId: string, publicKey: string}}} */ |  | 
|  335         JSON.parse(xhr.responseText); |  | 
|  336     if (host.data && host.data.jabberId && host.data.publicKey) { |  | 
|  337       remoting.hostJid = host.data.jabberId; |  | 
|  338       remoting.hostPublicKey = host.data.publicKey; |  | 
|  339       var split = remoting.hostJid.split('/'); |  | 
|  340       document.getElementById('connected-to').innerText = split[0]; |  | 
|  341       startSession_(); |  | 
|  342       return; |  | 
|  343     } |  | 
|  344   } |  | 
|  345   var errorMsg = remoting.Error.GENERIC; |  | 
|  346   if (xhr.status == 404) { |  | 
|  347     errorMsg = remoting.Error.INVALID_ACCESS_CODE; |  | 
|  348   } else if (xhr.status == 0) { |  | 
|  349     errorMsg = remoting.Error.NO_RESPONSE; |  | 
|  350   } else if (xhr.status == 503) { |  | 
|  351     errorMsg = remoting.Error.SERVICE_UNAVAILABLE; |  | 
|  352   } else { |  | 
|  353     remoting.debug.log('The server responded: ' + xhr.responseText); |  | 
|  354   } |  | 
|  355   showConnectError_(errorMsg); |  | 
|  356 } |  | 
|  357  |  | 
|  358 /** |  | 
|  359  * Normalize the access code entered by the user. |  | 
|  360  * |  | 
|  361  * @param {string} accessCode The access code, as entered by the user. |  | 
|  362  * @return {string} The normalized form of the code (whitespace removed). |  | 
|  363  */ |  | 
|  364 function normalizeAccessCode_(accessCode) { |  | 
|  365   // Trim whitespace. |  | 
|  366   // TODO(sergeyu): Do we need to do any other normalization here? |  | 
|  367   return accessCode.replace(/\s/g, ''); |  | 
|  368 } |  | 
|  369  |  | 
|  370 /** |  | 
|  371  * Initiate a request to the server to resolve a support ID. |  | 
|  372  * |  | 
|  373  * @param {string} supportId The canonicalized support ID. |  | 
|  374  */ |  | 
|  375 function resolveSupportId(supportId) { |  | 
|  376   var headers = { |  | 
|  377     'Authorization': 'OAuth ' + remoting.oauth2.getAccessToken() |  | 
|  378   }; |  | 
|  379  |  | 
|  380   remoting.supportHostsXhr_ = remoting.xhr.get( |  | 
|  381       'https://www.googleapis.com/chromoting/v1/support-hosts/' + |  | 
|  382           encodeURIComponent(supportId), |  | 
|  383       parseServerResponse_, |  | 
|  384       '', |  | 
|  385       headers); |  | 
|  386 } |  | 
|  387  |  | 
|  388 /** |  | 
|  389  * Timer callback to update the statistics panel. |  | 
|  390  */ |  | 
|  391 function updateStatistics_() { |  | 
|  392   if (!remoting.clientSession || |  | 
|  393       remoting.clientSession.state != remoting.ClientSession.State.CONNECTED) { |  | 
|  394     return; |  | 
|  395   } |  | 
|  396   var stats = remoting.clientSession.stats(); |  | 
|  397   remoting.debug.updateStatistics(stats); |  | 
|  398   remoting.clientSession.logStatistics(stats); |  | 
|  399   // Update the stats once per second. |  | 
|  400   window.setTimeout(updateStatistics_, 1000); |  | 
|  401 } |  | 
|  402  |  | 
|  403  |  | 
|  404 /** |  | 
|  405  * Shows PIN entry screen. |  | 
|  406  * |  | 
|  407  * @param {string} hostId The unique id of the host. |  | 
|  408  * @param {boolean} retryIfOffline If true and the host can't be contacted, |  | 
|  409  *     refresh the host list and try again. This allows bookmarked hosts to |  | 
|  410  *     work even if they reregister with Talk and get a different Jid. |  | 
|  411  * @return {void} Nothing. |  | 
|  412  */ |  | 
|  413 remoting.connectMe2Me = function(hostId, retryIfOffline) { |  | 
|  414   remoting.currentConnectionType = remoting.ConnectionType.Me2Me; |  | 
|  415   remoting.hostId = hostId; |  | 
|  416   remoting.retryIfOffline = retryIfOffline; |  | 
|  417  |  | 
|  418   // TODO(jamiewalch): Reinstate the PIN screen once it's supported. |  | 
|  419   // remoting.setMode(remoting.AppMode.CLIENT_PIN_PROMPT); |  | 
|  420   remoting.connectMe2MeWithPin(); |  | 
|  421 } |  | 
|  422  |  | 
|  423 /** |  | 
|  424  * Start a connection to the specified host, using the cached details |  | 
|  425  * and the PIN entered by the user. |  | 
|  426  * |  | 
|  427  * @return {void} Nothing. |  | 
|  428  */ |  | 
|  429 remoting.connectMe2MeWithPin = function() { |  | 
|  430   remoting.debug.log('Connecting to host...'); |  | 
|  431   remoting.setMode(remoting.AppMode.CLIENT_CONNECTING); |  | 
|  432  |  | 
|  433   var host = remoting.hostList.getHostForId(remoting.hostId); |  | 
|  434   if (!host) { |  | 
|  435     retryConnectOrReportOffline_(); |  | 
|  436     return; |  | 
|  437   } |  | 
|  438   remoting.hostJid = host.jabberId; |  | 
|  439   remoting.hostPublicKey = host.publicKey; |  | 
|  440   document.getElementById('connected-to').innerText = host.hostName; |  | 
|  441   document.title = document.title + ': ' + host.hostName; |  | 
|  442  |  | 
|  443   remoting.WcsLoader.load(connectMe2MeWithAccessToken_); |  | 
|  444 }; |  | 
|  445  |  | 
|  446 /** |  | 
|  447  * Continue making the connection to a host, once WCS has initialized. |  | 
|  448  * |  | 
|  449  * @param {string?} token The OAuth2 access token, or null if an error occurred. |  | 
|  450  * @return {void} Nothing. |  | 
|  451  */ |  | 
|  452 function connectMe2MeWithAccessToken_(token) { |  | 
|  453   if (token) { |  | 
|  454     /** @type {string} */ |  | 
|  455     var pin = document.getElementById('pin-entry').value; |  | 
|  456     document.getElementById('pin-entry').value = ''; |  | 
|  457  |  | 
|  458     remoting.clientSession = |  | 
|  459         new remoting.ClientSession( |  | 
|  460             remoting.hostJid, remoting.hostPublicKey, |  | 
|  461             pin, /** @type {string} */ (remoting.oauth2.getCachedEmail()), |  | 
|  462             onClientStateChange_); |  | 
|  463     remoting.clientSession.createPluginAndConnect( |  | 
|  464         document.getElementById('session-mode'), |  | 
|  465         token); |  | 
|  466   } else { |  | 
|  467     showConnectError_(remoting.Error.AUTHENTICATION_FAILED); |  | 
|  468   } |  | 
|  469 } |  | 
| OLD | NEW |