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} */
13var remoting = remoting || {};
14
15/**
16 * @constructor
17 */
18remoting.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 */
33remoting.LogToServer.SESSION_ID_ALPHABET_ =
34    'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890';
35/** @private */
36remoting.LogToServer.SESSION_ID_LEN_ = 20;
37
38// The maximum age of a session ID, in milliseconds.
39remoting.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.
43remoting.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 */
52remoting.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 */
98remoting.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 */
112remoting.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 */
126remoting.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 */
139remoting.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 */
160remoting.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 */
178remoting.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 */
203remoting.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 */
213remoting.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 */
227remoting.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 */
248remoting.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