TeaWeb/shared/js/stats.ts

243 lines
7.3 KiB
TypeScript
Raw Normal View History

import {LogCategory} from "./log";
import * as log from "./log";
import { tr } from "./i18n/localize";
2020-03-30 11:44:18 +00:00
enum CloseCodes {
UNSET = 3000,
RECONNECT = 3001,
INTERNAL_ERROR = 3002,
BANNED = 3100,
}
enum ConnectionState {
CONNECTING,
INITIALIZING,
CONNECTED,
UNSET
}
export class SessionConfig {
/*
* All collected statistics will only be cached by the stats server.
* No data will be saved.
*/
volatile_collection_only?: boolean;
/*
* Anonymize all IP addresses which will be provided while the stats collection.
* This option is quite useless when volatile_collection_only is active.
*/
anonymize_ip_addresses?: boolean;
}
export class Config extends SessionConfig {
verbose?: boolean;
reconnect_interval?: number;
}
export interface UserCountData {
online_users: number;
unique_online_users: number;
}
export type UserCountListener = (data: UserCountData) => any;
let reconnect_timer: number;
2020-03-30 11:44:18 +00:00
let current_config: Config;
let last_user_count_update: number;
let user_count_listener: UserCountListener[] = [];
const DEFAULT_CONFIG: Config = {
verbose: true,
reconnect_interval: 5000,
anonymize_ip_addresses: true,
volatile_collection_only: false
};
function initialize_config_object(target_object: any, source_object: any) : any {
for(const key of Object.keys(source_object)) {
if(typeof(source_object[key]) === 'object')
initialize_config_object(target_object[key] || (target_object[key] = {}), source_object[key]);
if(typeof(target_object[key]) !== 'undefined')
continue;
target_object[key] = source_object[key];
2019-02-17 15:41:26 +00:00
}
2020-03-30 11:44:18 +00:00
return target_object;
}
2019-02-17 15:41:26 +00:00
2020-03-30 11:44:18 +00:00
export function initialize(config: Config) {
current_config = initialize_config_object(config || {}, DEFAULT_CONFIG);
if(current_config.verbose)
log.info(LogCategory.STATISTICS, tr("Initializing statistics with this config: %o"), current_config);
2019-02-17 15:41:26 +00:00
2020-03-30 11:44:18 +00:00
connection.start_connection();
}
2019-02-17 15:41:26 +00:00
2020-03-30 11:44:18 +00:00
export function register_user_count_listener(listener: UserCountListener) {
user_count_listener.push(listener);
}
2019-02-17 15:41:26 +00:00
2020-03-30 11:44:18 +00:00
export function all_user_count_listener() : UserCountListener[] {
return user_count_listener;
}
2019-02-17 15:41:26 +00:00
2020-03-30 11:44:18 +00:00
export function deregister_user_count_listener(listener: UserCountListener) {
user_count_listener.remove(listener);
}
2019-02-17 15:41:26 +00:00
2020-03-30 11:44:18 +00:00
namespace connection {
let connection: WebSocket;
export let connection_state: ConnectionState = ConnectionState.UNSET;
2019-02-17 15:41:26 +00:00
2020-03-30 11:44:18 +00:00
export function start_connection() {
cancel_reconnect();
close_connection();
2019-02-17 15:41:26 +00:00
2020-03-30 11:44:18 +00:00
connection_state = ConnectionState.CONNECTING;
2019-02-17 15:41:26 +00:00
2020-03-30 11:44:18 +00:00
connection = new WebSocket('wss://web-stats.teaspeak.de:27790');
if(!connection)
connection = new WebSocket('wss://localhost:27788');
2019-02-17 15:41:26 +00:00
2020-03-30 11:44:18 +00:00
{
const connection_copy = connection;
connection.onclose = (event: CloseEvent) => {
if(connection_copy !== connection) return;
2019-02-17 15:41:26 +00:00
2020-03-30 11:44:18 +00:00
if(current_config.verbose)
log.warn(LogCategory.STATISTICS, tr("Lost connection to statistics server (Connection closed). Reason: %o. Event object: %o"), CloseCodes[event.code] || event.code, event);
2019-02-17 15:41:26 +00:00
2020-03-30 11:44:18 +00:00
if(event.code != CloseCodes.BANNED)
invoke_reconnect();
};
2019-02-17 15:41:26 +00:00
2020-03-30 11:44:18 +00:00
connection.onopen = () => {
if(connection_copy !== connection) return;
2019-02-17 15:41:26 +00:00
2020-03-30 11:44:18 +00:00
if(current_config.verbose)
log.debug(LogCategory.STATISTICS, tr("Successfully connected to server. Initializing session."));
2019-02-17 15:41:26 +00:00
2020-03-30 11:44:18 +00:00
connection_state = ConnectionState.INITIALIZING;
initialize_session();
};
2019-02-17 15:41:26 +00:00
2020-03-30 11:44:18 +00:00
connection.onerror = (event: ErrorEvent) => {
if(connection_copy !== connection) return;
2019-02-17 15:41:26 +00:00
2020-03-30 11:44:18 +00:00
if(current_config.verbose)
log.warn(LogCategory.STATISTICS, tr("Received an error. Closing connection. Object: %o"), event);
2019-02-17 15:41:26 +00:00
2020-03-30 11:44:18 +00:00
connection.close(CloseCodes.INTERNAL_ERROR);
invoke_reconnect();
};
2019-02-17 15:41:26 +00:00
2020-03-30 11:44:18 +00:00
connection.onmessage = (event: MessageEvent) => {
if(connection_copy !== connection) return;
2019-02-17 15:41:26 +00:00
2020-03-30 11:44:18 +00:00
if(typeof(event.data) !== 'string') {
2019-02-17 15:41:26 +00:00
if(current_config.verbose)
log.warn(LogCategory.STATISTICS, tr("Received an message which isn't a string. Event object: %o"), event);
2020-03-30 11:44:18 +00:00
return;
}
2019-02-17 15:41:26 +00:00
2020-03-30 11:44:18 +00:00
handle_message(event.data as string);
};
}
}
2019-02-17 15:41:26 +00:00
2020-03-30 11:44:18 +00:00
export function close_connection() {
if(connection) {
const connection_copy = connection;
connection = undefined;
2019-02-17 15:41:26 +00:00
2020-03-30 11:44:18 +00:00
try {
connection_copy.close(3001);
} catch(_) {}
2019-02-17 15:41:26 +00:00
}
2020-03-30 11:44:18 +00:00
}
2019-02-17 15:41:26 +00:00
2020-03-30 11:44:18 +00:00
function invoke_reconnect() {
close_connection();
2019-02-25 14:59:42 +00:00
2020-03-30 11:44:18 +00:00
if(reconnect_timer) {
clearTimeout(reconnect_timer);
reconnect_timer = undefined;
2019-02-25 14:59:42 +00:00
}
2020-03-30 11:44:18 +00:00
if(current_config.verbose)
log.info(LogCategory.STATISTICS, tr("Scheduled reconnect in %dms"), current_config.reconnect_interval);
2019-02-17 15:41:26 +00:00
2020-03-30 11:44:18 +00:00
reconnect_timer = setTimeout(() => {
2019-02-17 15:41:26 +00:00
if(current_config.verbose)
2020-03-30 11:44:18 +00:00
log.info(LogCategory.STATISTICS, tr("Reconnecting"));
start_connection();
}, current_config.reconnect_interval);
}
2019-02-17 15:41:26 +00:00
2020-03-30 11:44:18 +00:00
export function cancel_reconnect() {
if(reconnect_timer) {
clearTimeout(reconnect_timer);
reconnect_timer = undefined;
2019-02-17 15:41:26 +00:00
}
2020-03-30 11:44:18 +00:00
}
2019-02-17 15:41:26 +00:00
2020-03-30 11:44:18 +00:00
function send_message(type: string, data: any) {
connection.send(JSON.stringify({
type: type,
data: data
}));
}
2019-02-17 15:41:26 +00:00
2020-03-30 11:44:18 +00:00
function initialize_session() {
const config_object = {};
for(const key in SessionConfig) {
if(SessionConfig.hasOwnProperty(key))
config_object[key] = current_config[key];
2019-02-17 15:41:26 +00:00
}
2020-03-30 11:44:18 +00:00
send_message('initialize', {
config: config_object
})
}
2019-02-17 15:41:26 +00:00
2020-03-30 11:44:18 +00:00
function handle_message(message: string) {
const data_object = JSON.parse(message);
const type = data_object.type as string;
const data = data_object.data;
2019-02-17 15:41:26 +00:00
2020-03-30 11:44:18 +00:00
if(typeof(handler[type]) === 'function') {
if(current_config.verbose)
log.trace(LogCategory.STATISTICS, tr("Handling message of type %s"), type);
2020-03-30 11:44:18 +00:00
handler[type](data);
} else if(current_config.verbose) {
log.warn(LogCategory.STATISTICS, tr("Received message with an unknown type (%s). Dropping message. Full message: %o"), type, data_object);
2019-02-17 15:41:26 +00:00
}
2020-03-30 11:44:18 +00:00
}
2019-02-17 15:41:26 +00:00
2020-03-30 11:44:18 +00:00
namespace handler {
interface NotifyUserCount extends UserCountData { }
2019-02-17 15:41:26 +00:00
2020-03-30 11:44:18 +00:00
function handle_notify_user_count(data: NotifyUserCount) {
last_user_count_update = Date.now();
for(const listener of [...user_count_listener])
listener(data);
}
2019-02-17 15:41:26 +00:00
2020-03-30 11:44:18 +00:00
interface NotifyInitialized {}
function handle_notify_initialized(json: NotifyInitialized) {
if(current_config.verbose)
log.info(LogCategory.STATISTICS, tr("Session successfully initialized."));
2019-02-17 15:41:26 +00:00
2020-03-30 11:44:18 +00:00
connection_state = ConnectionState.CONNECTED;
2019-02-17 15:41:26 +00:00
}
2020-03-30 11:44:18 +00:00
handler["notifyinitialized"] = handle_notify_initialized;
handler["notifyusercount"] = handle_notify_user_count;
2019-02-17 15:41:26 +00:00
}
}