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  * Module for sending log entries to the server.
      8  */
      9 
     10 'use strict';
     11 
     12 /** @suppress {duplicate} */
     13 var remoting = remoting || {};
     14 
     15 /**
     16  * @constructor
     17  */
     18 remoting.LogToServer = function() {
     19   /** @type Array.<string> */
     20   this.pendingEntries = [];
     21   /** @type {remoting.StatsAccumulator} */
     22   this.statsAccumulator = new remoting.StatsAccumulator();
     23   /** @type string */
     24   this.sessionId = '';
     25   /** @type number */
     26   this.sessionIdGenerationTime = 0;
     27   /** @type number */
     28   this.sessionStartTime = 0;
     29 };
     30 
     31 // Constants used for generating a session ID.
     32 /** @private */
     33 remoting.LogToServer.SESSION_ID_ALPHABET_ =
     34     'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890';
     35 /** @private */
     36 remoting.LogToServer.SESSION_ID_LEN_ = 20;
     37 
     38 // The maximum age of a session ID, in milliseconds.
     39 remoting.LogToServer.MAX_SESSION_ID_AGE = 24 * 60 * 60 * 1000;
     40 
     41 // The time over which to accumulate connection statistics before logging them
     42 // to the server, in milliseconds.
     43 remoting.LogToServer.CONNECTION_STATS_ACCUMULATE_TIME = 60 * 1000;
     44 
     45 /**
     46  * Logs a client session state change.
     47  *
     48  * @param {remoting.ClientSession.State} state
     49  * @param {remoting.Error} connectionError
     50  * @param {remoting.ClientSession.Mode} mode
     51  */
     52 remoting.LogToServer.prototype.logClientSessionStateChange =
     53     function(state, connectionError, mode) {
     54   this.maybeExpireSessionId(mode);
     55   // Maybe set the session ID and start time.
     56   if (remoting.LogToServer.isStartOfSession(state)) {
     57     if (this.sessionId == '') {
     58       this.setSessionId();
     59     }
     60     if (this.sessionStartTime == 0) {
     61       this.sessionStartTime = new Date().getTime();
     62     }
     63   }
     64   // Log the session state change.
     65   var entry = remoting.ServerLogEntry.makeClientSessionStateChange(
     66       state, connectionError, mode);
     67   entry.addHostFields();
     68   entry.addChromeVersionField();
     69   entry.addWebappVersionField();
     70   entry.addSessionIdField(this.sessionId);
     71   // Maybe clear the session start time, and log the session duration.
     72   if (remoting.LogToServer.shouldAddDuration(state) &&
     73       (this.sessionStartTime != 0)) {
     74     entry.addSessionDurationField(
     75         (new Date().getTime() - this.sessionStartTime) / 1000.0);
     76     if (remoting.LogToServer.isEndOfSession(state)) {
     77       this.sessionStartTime = 0;
     78     }
     79   }
     80   this.log(entry);
     81   // Don't accumulate connection statistics across state changes.
     82   this.logAccumulatedStatistics(mode);
     83   this.statsAccumulator.empty();
     84   // Maybe clear the session ID.
     85   if (remoting.LogToServer.isEndOfSession(state)) {
     86     this.clearSessionId();
     87   }
     88 };
     89 
     90 /**
     91  * Whether a session state is one of the states that occurs at the start of
     92  * a session.
     93  *
     94  * @private
     95  * @param {remoting.ClientSession.State} state
     96  * @return {boolean}
     97  */
     98 remoting.LogToServer.isStartOfSession = function(state) {
     99   return ((state == remoting.ClientSession.State.CONNECTING) ||
    100       (state == remoting.ClientSession.State.INITIALIZING) ||
    101       (state == remoting.ClientSession.State.CONNECTED));
    102 };
    103 
    104 /**
    105  * Whether a session state is one of the states that occurs at the end of
    106  * a session.
    107  *
    108  * @private
    109  * @param {remoting.ClientSession.State} state
    110  * @return {boolean}
    111  */
    112 remoting.LogToServer.isEndOfSession = function(state) {
    113   return ((state == remoting.ClientSession.State.CLOSED) ||
    114       (state == remoting.ClientSession.State.FAILED) ||
    115       (state == remoting.ClientSession.State.CONNECTION_DROPPED) ||
    116       (state == remoting.ClientSession.State.CONNECTION_CANCELED));
    117 };
    118 
    119 /**
    120  * Whether the duration should be added to the log entry for this state.
    121  *
    122  * @private
    123  * @param {remoting.ClientSession.State} state
    124  * @return {boolean}
    125  */
    126 remoting.LogToServer.shouldAddDuration = function(state) {
    127   // Duration is added to log entries at the end of the session, as well as at
    128   // some intermediate states where it is relevant (e.g. to determine how long
    129   // it took for a session to become CONNECTED).
    130   return (remoting.LogToServer.isEndOfSession(state) ||
    131       (state == remoting.ClientSession.State.CONNECTED));
    132 };
    133 
    134 /**
    135  * Logs connection statistics.
    136  * @param {Object.<string, number>} stats the connection statistics
    137  * @param {remoting.ClientSession.Mode} mode
    138  */
    139 remoting.LogToServer.prototype.logStatistics = function(stats, mode) {
    140   this.maybeExpireSessionId(mode);
    141   // Store the statistics.
    142   this.statsAccumulator.add(stats);
    143   // Send statistics to the server if they've been accumulating for at least
    144   // 60 seconds.
    145   if (this.statsAccumulator.getTimeSinceFirstValue() >=
    146       remoting.LogToServer.CONNECTION_STATS_ACCUMULATE_TIME) {
    147     this.logAccumulatedStatistics(mode);
    148   }
    149 };
    150 
    151 /**
    152  * Moves connection statistics from the accumulator to the log server.
    153  *
    154  * If all the statistics are zero, then the accumulator is still emptied,
    155  * but the statistics are not sent to the log server.
    156  *
    157  * @private
    158  * @param {remoting.ClientSession.Mode} mode
    159  */
    160 remoting.LogToServer.prototype.logAccumulatedStatistics = function(mode) {
    161   var entry = remoting.ServerLogEntry.makeStats(this.statsAccumulator, mode);
    162   if (entry) {
    163     entry.addHostFields();
    164     entry.addChromeVersionField();
    165     entry.addWebappVersionField();
    166     entry.addSessionIdField(this.sessionId);
    167     this.log(entry);
    168   }
    169   this.statsAccumulator.empty();
    170 };
    171 
    172 /**
    173  * Sends a log entry to the server.
    174  *
    175  * @private
    176  * @param {remoting.ServerLogEntry} entry
    177  */
    178 remoting.LogToServer.prototype.log = function(entry) {
    179   // Send the stanza to the debug log.
    180   console.log('Enqueueing log entry:');
    181   entry.toDebugLog(1);
    182   // Store a stanza for the entry.
    183   this.pendingEntries.push(entry.toStanza());
    184   // Send all pending entries to the server.
    185   console.log('Sending ' + this.pendingEntries.length + ' log ' +
    186               ((this.pendingEntries.length == 1) ? 'entry' : 'entries') +
    187               '  to the server.');
    188   var stanza = '<cli:iq to="' +
    189       remoting.settings.DIRECTORY_BOT_JID + '" type="set" ' +
    190       'xmlns:cli="jabber:client"><gr:log xmlns:gr="google:remoting">';
    191   while (this.pendingEntries.length > 0) {
    192     stanza += /** @type string */ this.pendingEntries.shift();
    193   }
    194   stanza += '</gr:log></cli:iq>';
    195   remoting.wcsSandbox.sendIq(stanza);
    196 };
    197 
    198 /**
    199  * Sets the session ID to a random string.
    200  *
    201  * @private
    202  */
    203 remoting.LogToServer.prototype.setSessionId = function() {
    204   this.sessionId = remoting.LogToServer.generateSessionId();
    205   this.sessionIdGenerationTime = new Date().getTime();
    206 };
    207 
    208 /**
    209  * Clears the session ID.
    210  *
    211  * @private
    212  */
    213 remoting.LogToServer.prototype.clearSessionId = function() {
    214   this.sessionId = '';
    215   this.sessionIdGenerationTime = 0;
    216 };
    217 
    218 /**
    219  * Sets a new session ID, if the current session ID has reached its maximum age.
    220  *
    221  * This method also logs the old and new session IDs to the server, in separate
    222  * log entries.
    223  *
    224  * @private
    225  * @param {remoting.ClientSession.Mode} mode
    226  */
    227 remoting.LogToServer.prototype.maybeExpireSessionId = function(mode) {
    228   if ((this.sessionId != '') &&
    229       (new Date().getTime() - this.sessionIdGenerationTime >=
    230       remoting.LogToServer.MAX_SESSION_ID_AGE)) {
    231     // Log the old session ID.
    232     var entry = remoting.ServerLogEntry.makeSessionIdOld(this.sessionId, mode);
    233     this.log(entry);
    234     // Generate a new session ID.
    235     this.setSessionId();
    236     // Log the new session ID.
    237     entry = remoting.ServerLogEntry.makeSessionIdNew(this.sessionId, mode);
    238     this.log(entry);
    239   }
    240 };
    241 
    242 /**
    243  * Generates a string that can be used as a session ID.
    244  *
    245  * @private
    246  * @return {string} a session ID
    247  */
    248 remoting.LogToServer.generateSessionId = function() {
    249   var idArray = [];
    250   for (var i = 0; i < remoting.LogToServer.SESSION_ID_LEN_; i++) {
    251     var index =
    252         Math.random() * remoting.LogToServer.SESSION_ID_ALPHABET_.length;
    253     idArray.push(
    254         remoting.LogToServer.SESSION_ID_ALPHABET_.slice(index, index + 1));
    255   }
    256   return idArray.join('');
    257 };
    258