"use strict";
// This file is part of HFS - Copyright 2021-2023, Massimo Melina <a@rejetto.com> - License https://www.gnu.org/licenses/gpl-3.0.txt
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.disconnectionsLog = exports.Connection = void 0;
exports.normalizeIp = normalizeIp;
exports.newConnection = newConnection;
exports.getConnections = getConnections;
exports.socket2connection = socket2connection;
exports.getConnection = getConnection;
exports.updateConnectionForCtx = updateConnectionForCtx;
exports.updateConnection = updateConnection;
exports.disconnect = disconnect;
const events_1 = __importDefault(require("./events"));
const geo_1 = require("./geo");
const lodash_1 = __importDefault(require("lodash"));
class Connection {
    socket;
    started = new Date();
    sent = 0; // socket-scoped, not request-scoped
    got = 0;
    outSpeedKb;
    inSpeedKb;
    ctx; // this is set externally, during koa middleware, using updateConnectionForCtx, but only for regular requests; some connections may never have a ctx
    country;
    _cachedIp;
    // the sockets we collect are the plain ones, as soon as the tcp layer is connected
    constructor(socket) {
        this.socket = socket;
        all.push(this);
        socket.on('close', () => {
            all.splice(all.indexOf(this), 1);
            events_1.default.emit('connectionClosed', this);
        });
        events_1.default.emit('connection', this);
    }
    get ip() {
        if (this._cachedIp && this.ctx && this._cachedIp !== this.ctx.ip) {
            events_1.default.emit('connectionNewIp', this, this._cachedIp, this.ctx.ip);
            this._cachedIp = undefined;
        }
        return this.ctx?.ip || (this._cachedIp ??= normalizeIp(this.socket.remoteAddress || ''));
    }
    get secure() {
        return this.socket.server.cert > '';
    }
}
exports.Connection = Connection;
function normalizeIp(ip) {
    return ip.replace(/^::ffff:/, ''); // simplify ipv6-mapped addresses
}
const all = [];
function newConnection(socket) {
    const ip = normalizeIp(socket.remoteAddress || '');
    const res = events_1.default.emit('newSocket', { socket, ip });
    const msg = res?.isDefaultPrevented() ? 'plugin (newSocket)' : res?.find(lodash_1.default.isString);
    if (!msg)
        return new Connection(socket);
    disconnect(socket, msg);
}
function getConnections() {
    return all;
}
function socket2connection(socket) {
    return all.find(x => // socket exposed by Koa is TLSSocket which encapsulates simple Socket, and I've found no way to access it for simple comparison
     x.socket.remotePort === socket.remotePort // but we can still match them because IP:PORT is key
        && x.socket.remoteAddress === socket.remoteAddress);
}
function getConnection(ctx) {
    return ctx.state.connection;
}
function updateConnectionForCtx(ctx) {
    const conn = getConnection(ctx);
    if (conn)
        updateConnection(conn, { ctx });
    return conn;
}
function updateConnection(conn, change, changeState) {
    const { ctx } = conn;
    if (changeState && ctx) {
        Object.assign(ctx.state, changeState);
        Object.assign(change, { ctx });
    }
    Object.assign(conn, change);
    events_1.default.emit('connectionUpdated', conn, change);
}
exports.disconnectionsLog = [];
function disconnect(what, logMessage = '') {
    if ('socket' in what)
        what = what.socket;
    const ip = normalizeIp(what.remoteAddress || '');
    if (logMessage)
        console.debug("Disconnection:", logMessage, ip);
    (0, geo_1.ip2country)(ip).then(res => {
        const rec = { ip, country: res || undefined, ts: new Date, msg: logMessage || undefined };
        exports.disconnectionsLog.unshift(rec);
        exports.disconnectionsLog.length = Math.min(1000, exports.disconnectionsLog.length);
        events_1.default.emit('disconnection', rec);
    });
    return what.destroy();
}
events_1.default.once('app', app => {
    app.context.isAborted = function () {
        return this.res.destroyed || this.req.aborted // investigate: "aborted" is deprecated, but "destroyed" will cause failure of some tests
            || this.socket.destroyed;
    };
    app.context.disconnect = function (logMessage = '') {
        return disconnect(this, logMessage);
    };
});
