// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/**
* @fileoverview
* Module for sending log entries to the server.
*/
'use strict';
/** @suppress {duplicate} */
var remoting = remoting || {};
/**
* @constructor
*/
remoting.LogToServer = function() {
/** @type Array.<string> */
this.pendingEntries = [];
/** @type {remoting.StatsAccumulator} */
this.statsAccumulator = new remoting.StatsAccumulator();
/** @type string */
this.sessionId = '';
/** @type number */
this.sessionIdGenerationTime = 0;
/** @type number */
this.sessionStartTime = 0;
};
// Constants used for generating a session ID.
/** @private */
remoting.LogToServer.SESSION_ID_ALPHABET_ =
'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890';
/** @private */
remoting.LogToServer.SESSION_ID_LEN_ = 20;
// The maximum age of a session ID, in milliseconds.
remoting.LogToServer.MAX_SESSION_ID_AGE = 24 * 60 * 60 * 1000;
// The time over which to accumulate connection statistics before logging them
// to the server, in milliseconds.
remoting.LogToServer.CONNECTION_STATS_ACCUMULATE_TIME = 60 * 1000;
/**
* Logs a client session state change.
*
* @param {remoting.ClientSession.State} state
* @param {remoting.Error} connectionError
* @param {remoting.ClientSession.Mode} mode
*/
remoting.LogToServer.prototype.logClientSessionStateChange =
function(state, connectionError, mode) {
this.maybeExpireSessionId(mode);
// Maybe set the session ID and start time.
if (remoting.LogToServer.isStartOfSession(state)) {
if (this.sessionId == '') {
this.setSessionId();
}
if (this.sessionStartTime == 0) {
this.sessionStartTime = new Date().getTime();
}
}
// Log the session state change.
var entry = remoting.ServerLogEntry.makeClientSessionStateChange(
state, connectionError, mode);
entry.addHostFields();
entry.addChromeVersionField();
entry.addWebappVersionField();
entry.addSessionIdField(this.sessionId);
// Maybe clear the session start time, and log the session duration.
if (remoting.LogToServer.shouldAddDuration(state) &&
(this.sessionStartTime != 0)) {
entry.addSessionDurationField(
(new Date().getTime() - this.sessionStartTime) / 1000.0);
if (remoting.LogToServer.isEndOfSession(state)) {
this.sessionStartTime = 0;
}
}
this.log(entry);
// Don't accumulate connection statistics across state changes.
this.logAccumulatedStatistics(mode);
this.statsAccumulator.empty();
// Maybe clear the session ID.
if (remoting.LogToServer.isEndOfSession(state)) {
this.clearSessionId();
}
};
/**
* Whether a session state is one of the states that occurs at the start of
* a session.
*
* @private
* @param {remoting.ClientSession.State} state
* @return {boolean}
*/
remoting.LogToServer.isStartOfSession = function(state) {
return ((state == remoting.ClientSession.State.CONNECTING) ||
(state == remoting.ClientSession.State.INITIALIZING) ||
(state == remoting.ClientSession.State.CONNECTED));
};
/**
* Whether a session state is one of the states that occurs at the end of
* a session.
*
* @private
* @param {remoting.ClientSession.State} state
* @return {boolean}
*/
remoting.LogToServer.isEndOfSession = function(state) {
return ((state == remoting.ClientSession.State.CLOSED) ||
(state == remoting.ClientSession.State.FAILED) ||
(state == remoting.ClientSession.State.CONNECTION_DROPPED) ||
(state == remoting.ClientSession.State.CONNECTION_CANCELED));
};
/**
* Whether the duration should be added to the log entry for this state.
*
* @private
* @param {remoting.ClientSession.State} state
* @return {boolean}
*/
remoting.LogToServer.shouldAddDuration = function(state) {
// Duration is added to log entries at the end of the session, as well as at
// some intermediate states where it is relevant (e.g. to determine how long
// it took for a session to become CONNECTED).
return (remoting.LogToServer.isEndOfSession(state) ||
(state == remoting.ClientSession.State.CONNECTED));
};
/**
* Logs connection statistics.
* @param {Object.<string, number>} stats the connection statistics
* @param {remoting.ClientSession.Mode} mode
*/
remoting.LogToServer.prototype.logStatistics = function(stats, mode) {
this.maybeExpireSessionId(mode);
// Store the statistics.
this.statsAccumulator.add(stats);
// Send statistics to the server if they've been accumulating for at least
// 60 seconds.
if (this.statsAccumulator.getTimeSinceFirstValue() >=
remoting.LogToServer.CONNECTION_STATS_ACCUMULATE_TIME) {
this.logAccumulatedStatistics(mode);
}
};
/**
* Moves connection statistics from the accumulator to the log server.
*
* If all the statistics are zero, then the accumulator is still emptied,
* but the statistics are not sent to the log server.
*
* @private
* @param {remoting.ClientSession.Mode} mode
*/
remoting.LogToServer.prototype.logAccumulatedStatistics = function(mode) {
var entry = remoting.ServerLogEntry.makeStats(this.statsAccumulator, mode);
if (entry) {
entry.addHostFields();
entry.addChromeVersionField();
entry.addWebappVersionField();
entry.addSessionIdField(this.sessionId);
this.log(entry);
}
this.statsAccumulator.empty();
};
/**
* Sends a log entry to the server.
*
* @private
* @param {remoting.ServerLogEntry} entry
*/
remoting.LogToServer.prototype.log = function(entry) {
// Send the stanza to the debug log.
console.log('Enqueueing log entry:');
entry.toDebugLog(1);
// Store a stanza for the entry.
this.pendingEntries.push(entry.toStanza());
// Send all pending entries to the server.
console.log('Sending ' + this.pendingEntries.length + ' log ' +
((this.pendingEntries.length == 1) ? 'entry' : 'entries') +
' to the server.');
var stanza = '<cli:iq to="' +
remoting.settings.DIRECTORY_BOT_JID + '" type="set" ' +
'xmlns:cli="jabber:client"><gr:log xmlns:gr="google:remoting">';
while (this.pendingEntries.length > 0) {
stanza += /** @type string */ this.pendingEntries.shift();
}
stanza += '</gr:log></cli:iq>';
remoting.wcsSandbox.sendIq(stanza);
};
/**
* Sets the session ID to a random string.
*
* @private
*/
remoting.LogToServer.prototype.setSessionId = function() {
this.sessionId = remoting.LogToServer.generateSessionId();
this.sessionIdGenerationTime = new Date().getTime();
};
/**
* Clears the session ID.
*
* @private
*/
remoting.LogToServer.prototype.clearSessionId = function() {
this.sessionId = '';
this.sessionIdGenerationTime = 0;
};
/**
* Sets a new session ID, if the current session ID has reached its maximum age.
*
* This method also logs the old and new session IDs to the server, in separate
* log entries.
*
* @private
* @param {remoting.ClientSession.Mode} mode
*/
remoting.LogToServer.prototype.maybeExpireSessionId = function(mode) {
if ((this.sessionId != '') &&
(new Date().getTime() - this.sessionIdGenerationTime >=
remoting.LogToServer.MAX_SESSION_ID_AGE)) {
// Log the old session ID.
var entry = remoting.ServerLogEntry.makeSessionIdOld(this.sessionId, mode);
this.log(entry);
// Generate a new session ID.
this.setSessionId();
// Log the new session ID.
entry = remoting.ServerLogEntry.makeSessionIdNew(this.sessionId, mode);
this.log(entry);
}
};
/**
* Generates a string that can be used as a session ID.
*
* @private
* @return {string} a session ID
*/
remoting.LogToServer.generateSessionId = function() {
var idArray = [];
for (var i = 0; i < remoting.LogToServer.SESSION_ID_LEN_; i++) {
var index =
Math.random() * remoting.LogToServer.SESSION_ID_ALPHABET_.length;
idArray.push(
remoting.LogToServer.SESSION_ID_ALPHABET_.slice(index, index + 1));
}
return idArray.join('');
};