Home | History | Annotate | Download | only in webapp
      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 plugin signals
     18  *     completion.
     19  * @private
     20  */
     21 var lastShareWasCancelled_ = false;
     22 
     23 /**
     24  * Start a host session. This is the main entry point for the host screen,
     25  * called directly from the onclick action of a button on the home screen.
     26  */
     27 remoting.tryShare = function() {
     28   console.log('Attempting to share...');
     29   remoting.identity.callWithToken(remoting.tryShareWithToken_,
     30                                   remoting.showErrorMessage);
     31 };
     32 
     33 /**
     34  * @param {string} token The OAuth access token.
     35  * @private
     36  */
     37 remoting.tryShareWithToken_ = function(token) {
     38   lastShareWasCancelled_ = false;
     39   onNatTraversalPolicyChanged_(true);  // Hide warning by default.
     40   remoting.setMode(remoting.AppMode.HOST_WAITING_FOR_CODE);
     41   document.getElementById('cancel-share-button').disabled = false;
     42   disableTimeoutCountdown_();
     43 
     44   var div = document.getElementById('host-plugin-container');
     45   remoting.hostSession = new remoting.HostSession();
     46   remoting.hostSession.createPluginAndConnect(
     47       document.getElementById('host-plugin-container'),
     48       /** @type {string} */(remoting.identity.getCachedEmail()),
     49       token,
     50       onNatTraversalPolicyChanged_,
     51       onHostStateChanged_,
     52       logDebugInfo_);
     53 };
     54 
     55 /**
     56  * Callback for the host plugin to notify the web app of state changes.
     57  * @param {remoting.HostSession.State} state The new state of the plugin.
     58  */
     59 function onHostStateChanged_(state) {
     60   if (state == remoting.HostSession.State.STARTING) {
     61     // Nothing to do here.
     62     console.log('Host plugin state: STARTING');
     63 
     64   } else if (state == remoting.HostSession.State.REQUESTED_ACCESS_CODE) {
     65     // Nothing to do here.
     66     console.log('Host plugin state: REQUESTED_ACCESS_CODE');
     67 
     68   } else if (state == remoting.HostSession.State.RECEIVED_ACCESS_CODE) {
     69     console.log('Host plugin state: RECEIVED_ACCESS_CODE');
     70     var accessCode = remoting.hostSession.getAccessCode();
     71     var accessCodeDisplay = document.getElementById('access-code-display');
     72     accessCodeDisplay.innerText = '';
     73     // Display the access code in groups of four digits for readability.
     74     var kDigitsPerGroup = 4;
     75     for (var i = 0; i < accessCode.length; i += kDigitsPerGroup) {
     76       var nextFourDigits = document.createElement('span');
     77       nextFourDigits.className = 'access-code-digit-group';
     78       nextFourDigits.innerText = accessCode.substring(i, i + kDigitsPerGroup);
     79       accessCodeDisplay.appendChild(nextFourDigits);
     80     }
     81     accessCodeExpiresIn_ = remoting.hostSession.getAccessCodeLifetime();
     82     if (accessCodeExpiresIn_ > 0) {  // Check it hasn't expired.
     83       accessCodeTimerId_ = setInterval(
     84           remoting.decrementAccessCodeTimeout_, 1000);
     85       timerRunning_ = true;
     86       updateAccessCodeTimeoutElement_();
     87       updateTimeoutStyles_();
     88       remoting.setMode(remoting.AppMode.HOST_WAITING_FOR_CONNECTION);
     89     } else {
     90       // This can only happen if the cloud tells us that the code lifetime is
     91       // <= 0s, which shouldn't happen so we don't care how clean this UX is.
     92       console.error('Access code already invalid on receipt!');
     93       remoting.cancelShare();
     94     }
     95 
     96   } else if (state == remoting.HostSession.State.CONNECTED) {
     97     console.log('Host plugin state: CONNECTED');
     98     var element = document.getElementById('host-shared-message');
     99     var client = remoting.hostSession.getClient();
    100     l10n.localizeElement(element, client);
    101     remoting.setMode(remoting.AppMode.HOST_SHARED);
    102     disableTimeoutCountdown_();
    103 
    104   } else if (state == remoting.HostSession.State.DISCONNECTING) {
    105     console.log('Host plugin state: DISCONNECTING');
    106 
    107   } else if (state == remoting.HostSession.State.DISCONNECTED) {
    108     console.log('Host plugin state: DISCONNECTED');
    109     if (remoting.currentMode != remoting.AppMode.HOST_SHARE_FAILED) {
    110       // If an error is being displayed, then the plugin should not be able to
    111       // hide it by setting the state. Errors must be dismissed by the user
    112       // clicking OK, which puts the app into mode HOME.
    113       if (lastShareWasCancelled_) {
    114         remoting.setMode(remoting.AppMode.HOME);
    115       } else {
    116         remoting.setMode(remoting.AppMode.HOST_SHARE_FINISHED);
    117       }
    118     }
    119     remoting.hostSession.removePlugin();
    120 
    121   } else if (state == remoting.HostSession.State.ERROR) {
    122     console.error('Host plugin state: ERROR');
    123     showShareError_(remoting.Error.UNEXPECTED);
    124   } else if (state == remoting.HostSession.State.INVALID_DOMAIN_ERROR) {
    125     console.error('Host plugin state: INVALID_DOMAIN_ERROR');
    126     showShareError_(remoting.Error.INVALID_HOST_DOMAIN);
    127   } else {
    128     console.error('Unknown state -> ' + state);
    129   }
    130 }
    131 
    132 /**
    133  * This is the callback that the host plugin invokes to indicate that there
    134  * is additional debug log info to display.
    135  * @param {string} msg The message (which will not be localized) to be logged.
    136  */
    137 function logDebugInfo_(msg) {
    138   console.log('plugin: ' + msg);
    139 }
    140 
    141 /**
    142  * Show a host-side error message.
    143  *
    144  * @param {string} errorTag The error message to be localized and displayed.
    145  * @return {void} Nothing.
    146  */
    147 function showShareError_(errorTag) {
    148   var errorDiv = document.getElementById('host-plugin-error');
    149   l10n.localizeElementFromTag(errorDiv, errorTag);
    150   console.error('Sharing error: ' + errorTag);
    151   remoting.setMode(remoting.AppMode.HOST_SHARE_FAILED);
    152 }
    153 
    154 /**
    155  * Cancel an active or pending share operation.
    156  *
    157  * @return {void} Nothing.
    158  */
    159 remoting.cancelShare = function() {
    160   document.getElementById('cancel-share-button').disabled = true;
    161   console.log('Canceling share...');
    162   remoting.lastShareWasCancelled = true;
    163   try {
    164     remoting.hostSession.disconnect();
    165   } catch (error) {
    166     // Hack to force JSCompiler type-safety.
    167     var errorTyped = /** @type {{description: string}} */ error;
    168     console.error('Error disconnecting: ' + errorTyped.description +
    169                 '. The host plugin probably crashed.');
    170     // TODO(jamiewalch): Clean this up. We should have a class representing
    171     // the host plugin, like we do for the client, which should handle crash
    172     // reporting and it should use a more detailed error message than the
    173     // default 'generic' one. See crbug.com/94624
    174     showShareError_(remoting.Error.UNEXPECTED);
    175   }
    176   disableTimeoutCountdown_();
    177 };
    178 
    179 /**
    180  * @type {boolean} Whether or not the access code timeout countdown is running.
    181  * @private
    182  */
    183 var timerRunning_ = false;
    184 
    185 /**
    186  * @type {number} The id of the access code expiry countdown timer.
    187  * @private
    188  */
    189 var accessCodeTimerId_ = 0;
    190 
    191 /**
    192  * @type {number} The number of seconds until the access code expires.
    193  * @private
    194  */
    195 var accessCodeExpiresIn_ = 0;
    196 
    197 /**
    198  * The timer callback function, which needs to be visible from the global
    199  * namespace.
    200  * @private
    201  */
    202 remoting.decrementAccessCodeTimeout_ = function() {
    203   --accessCodeExpiresIn_;
    204   updateAccessCodeTimeoutElement_();
    205 };
    206 
    207 /**
    208  * Stop the access code timeout countdown if it is running.
    209  */
    210 function disableTimeoutCountdown_() {
    211   if (timerRunning_) {
    212     clearInterval(accessCodeTimerId_);
    213     timerRunning_ = false;
    214     updateTimeoutStyles_();
    215   }
    216 }
    217 
    218 /**
    219  * Constants controlling the access code timer countdown display.
    220  * @private
    221  */
    222 var ACCESS_CODE_TIMER_DISPLAY_THRESHOLD_ = 30;
    223 var ACCESS_CODE_RED_THRESHOLD_ = 10;
    224 
    225 /**
    226  * Show/hide or restyle various elements, depending on the remaining countdown
    227  * and timer state.
    228  *
    229  * @return {boolean} True if the timeout is in progress, false if it has
    230  * expired.
    231  */
    232 function updateTimeoutStyles_() {
    233   if (timerRunning_) {
    234     if (accessCodeExpiresIn_ <= 0) {
    235       remoting.cancelShare();
    236       return false;
    237     }
    238     var accessCode = document.getElementById('access-code-display');
    239     if (accessCodeExpiresIn_ <= ACCESS_CODE_RED_THRESHOLD_) {
    240       accessCode.classList.add('expiring');
    241     } else {
    242       accessCode.classList.remove('expiring');
    243     }
    244   }
    245   document.getElementById('access-code-countdown').hidden =
    246       (accessCodeExpiresIn_ > ACCESS_CODE_TIMER_DISPLAY_THRESHOLD_) ||
    247       !timerRunning_;
    248   return true;
    249 }
    250 
    251 /**
    252  * Update the text and appearance of the access code timeout element to
    253  * reflect the time remaining.
    254  */
    255 function updateAccessCodeTimeoutElement_() {
    256   var pad = (accessCodeExpiresIn_ < 10) ? '0:0' : '0:';
    257   l10n.localizeElement(document.getElementById('seconds-remaining'),
    258                        pad + accessCodeExpiresIn_);
    259   if (!updateTimeoutStyles_()) {
    260     disableTimeoutCountdown_();
    261   }
    262 }
    263 
    264 /**
    265  * Callback to show or hide the NAT traversal warning when the policy changes.
    266  * @param {boolean} enabled True if NAT traversal is enabled.
    267  * @return {void} Nothing.
    268  */
    269 function onNatTraversalPolicyChanged_(enabled) {
    270   var natBox = document.getElementById('nat-box');
    271   if (enabled) {
    272     natBox.classList.add('traversal-enabled');
    273   } else {
    274     natBox.classList.remove('traversal-enabled');
    275   }
    276 }
    277