"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.WebSocketServer = void 0;
const os = require("os");
const WebSocket = require("ws");
const WebSocketServerClient_1 = require("./WebSocketServerClient");
const Crypt_1 = require("./Crypt");
const Message_1 = require("./Messages/Message");
const Config_1 = require("./Config");
const openflow_api_1 = require("@openiap/openflow-api");
const Logger_1 = require("./Logger");
const DatabaseConnection_1 = require("./DatabaseConnection");
const WebServer_1 = require("./WebServer");
const { RateLimiterMemory } = require('rate-limiter-flexible');
class WebSocketServer {
    static configure(server, parent) {
        var _a, _b, _c, _d, _e;
        const span = Logger_1.Logger.otel.startSubSpan("WebSocketServer.configure", parent);
        try {
            WebSocketServer.BaseRateLimiter = new RateLimiterMemory({
                points: Config_1.Config.socket_rate_limit_points,
                duration: Config_1.Config.socket_rate_limit_duration,
            });
            WebSocketServer.ErrorRateLimiter = new RateLimiterMemory({
                points: Config_1.Config.socket_error_rate_limit_points,
                duration: Config_1.Config.socket_error_rate_limit_duration,
            });
            this._clients = [];
            this._socketserver = new WebSocket.Server({ server: server });
            this._socketserver.on("connection", async (socketObject, req) => {
                let remoteip = "unknown";
                if (Config_1.Config.otel_trace_connection_ips) {
                    if (!openflow_api_1.NoderedUtil.IsNullUndefinded(req)) {
                        remoteip = WebSocketServerClient_1.WebSocketServerClient.remoteip(req);
                    }
                    remoteip = remoteip.split(":").join("-");
                }
                if (!this.total_connections_count[remoteip])
                    this.total_connections_count[remoteip] = 0;
                this.total_connections_count[remoteip]++;
                if (await WebServer_1.WebServer.isBlocked(req)) {
                    remoteip = WebSocketServerClient_1.WebSocketServerClient.remoteip(req);
                    Logger_1.Logger.instanse.error("WebSocketServer", "connection", remoteip + " is blocked");
                    try {
                        socketObject.close();
                    }
                    catch (error) {
                        Logger_1.Logger.instanse.error("WebSocketServer", "connection", error);
                    }
                    return;
                }
                this._clients.push(new WebSocketServerClient_1.WebSocketServerClient(socketObject, req));
            });
            this._socketserver.on("error", (error) => {
                Logger_1.Logger.instanse.error("WebSocketServer", "configure", error);
            });
            if (!openflow_api_1.NoderedUtil.IsNullUndefinded(Logger_1.Logger.otel) && !openflow_api_1.NoderedUtil.IsNullUndefinded(Logger_1.Logger.otel.meter)) {
                WebSocketServer.p_all = Logger_1.Logger.otel.meter.createObservableUpDownCounter("openflow_websocket_online_clients", {
                    description: 'Total number of online websocket clients'
                }); // "agent", "version"
                let p_all = {};
                (_a = WebSocketServer.p_all) === null || _a === void 0 ? void 0 : _a.addCallback(res => {
                    let keys = Object.keys(p_all);
                    keys.forEach(key => {
                        p_all[key] = 0;
                    });
                    for (let i = 0; i < WebSocketServer._clients.length; i++) {
                        try {
                            const cli = WebSocketServer._clients[i];
                            if (!openflow_api_1.NoderedUtil.IsNullUndefinded(WebSocketServer.p_all)) {
                                if (!openflow_api_1.NoderedUtil.IsNullEmpty(cli.clientagent)) {
                                    if (openflow_api_1.NoderedUtil.IsNullUndefinded(p_all[cli.clientagent]))
                                        p_all[cli.clientagent] = 0;
                                    p_all[cli.clientagent] += 1;
                                }
                                else {
                                    if (openflow_api_1.NoderedUtil.IsNullUndefinded(p_all["unknown"]))
                                        p_all["unknown"] = 0;
                                    p_all["unknown"] += 1;
                                }
                            }
                        }
                        catch (error) {
                            span === null || span === void 0 ? void 0 : span.recordException(error);
                            Logger_1.Logger.instanse.error("WebSocketServer", "observeClients", error);
                        }
                    }
                    keys = Object.keys(p_all);
                    keys.forEach(key => {
                        if (p_all[key] > 0) {
                            res.observe(p_all[key], { ...Logger_1.Logger.otel.defaultlabels, agent: key });
                        }
                        else {
                            res.observe(null, { ...Logger_1.Logger.otel.defaultlabels, agent: key });
                        }
                    });
                });
                WebSocketServer.websocket_queue_count = Logger_1.Logger.otel.meter.createObservableUpDownCounter("openflow_websocket_queue", {
                    description: 'Total number of registered queues'
                }); // "clientid"
                (_b = WebSocketServer.websocket_queue_count) === null || _b === void 0 ? void 0 : _b.addCallback(res => {
                    for (let i = 0; i < WebSocketServer._clients.length; i++) {
                        const cli = WebSocketServer._clients[i];
                        res.observe(cli._queues.length, { ...Logger_1.Logger.otel.defaultlabels, clientid: cli.id });
                    }
                });
                WebSocketServer.websocket_queue_message_count = Logger_1.Logger.otel.meter.createCounter("openflow_websocket_queue_message", {
                    description: 'Total number of queues messages'
                }); // "queuename"
                WebSocketServer.websocket_rate_limit = Logger_1.Logger.otel.meter.createCounter("openflow_websocket_rate_limit", {
                    description: 'Total number of rate limited messages'
                }); // "command"
                WebSocketServer.websocket_errors = Logger_1.Logger.otel.meter.createCounter("openflow_websocket_errors", {
                    description: 'Total number of websocket errors'
                }); // 
                WebSocketServer.websocket_messages = Logger_1.Logger.otel.meter.createHistogram('openflow_websocket_messages_duration_seconds', {
                    description: 'Duration for handling websocket requests', valueType: 1, unit: 's'
                }); // "command"
                WebSocketServer.message_queue_count = Logger_1.Logger.otel.meter.createObservableUpDownCounter("openflow_message_queue", {
                    description: 'Total number messages waiting on reply from client'
                }); // "clientid"
                (_c = WebSocketServer.message_queue_count) === null || _c === void 0 ? void 0 : _c.addCallback(res => {
                    if (!Config_1.Config.prometheus_measure_queued_messages)
                        return;
                    for (let i = 0; i < WebSocketServer._clients.length; i++) {
                        const cli = WebSocketServer._clients[i];
                        const keys = Object.keys(cli.messageQueue);
                        res.observe(keys.length, { ...Logger_1.Logger.otel.defaultlabels, clientid: cli.id });
                    }
                });
                WebSocketServer.mongodb_watch_count = Logger_1.Logger.otel.meter.createObservableUpDownCounter("mongodb_watch", {
                    description: 'Total number af steams  watching for changes'
                }); // "agent", "clientid"
                (_d = WebSocketServer.mongodb_watch_count) === null || _d === void 0 ? void 0 : _d.addCallback(res => {
                    if (!Config_1.Config.prometheus_measure__mongodb_watch)
                        return;
                    if (openflow_api_1.NoderedUtil.IsNullUndefinded(WebSocketServer.mongodb_watch_count))
                        return;
                    const result = {};
                    let total = 0;
                    for (let i = WebSocketServer._clients.length - 1; i >= 0; i--) {
                        const cli = WebSocketServer._clients[i];
                        const keys = Object.keys(cli.watches);
                        res.observe(keys.length, { ...Logger_1.Logger.otel.defaultlabels, clientid: cli.id, agent: cli.clientagent });
                    }
                });
                WebSocketServer.websocket_connections_count = Logger_1.Logger.otel.meter.createObservableUpDownCounter('openflow_websocket_connections_count', {
                    description: 'Total number of connection requests'
                }); // "command"
                (_e = WebSocketServer.websocket_connections_count) === null || _e === void 0 ? void 0 : _e.addCallback(res => {
                    const keys = Object.keys(this.total_connections_count);
                    keys.forEach(key => {
                        key = key.split(":").join("-");
                        res.observe(this.total_connections_count[key], { ...Logger_1.Logger.otel.defaultlabels, remoteip: key });
                    });
                });
            }
            setTimeout(this.pingClients.bind(this), Config_1.Config.ping_clients_interval);
        }
        catch (error) {
            span === null || span === void 0 ? void 0 : span.recordException(error);
            Logger_1.Logger.instanse.error("WebSocketServer", "configure", error);
            return;
        }
        finally {
            Logger_1.Logger.otel.endSpan(span);
        }
    }
    static async DumpClients() {
        const jwt = Crypt_1.Crypt.rootToken();
        const hostname = (Config_1.Config.getEnv("HOSTNAME", undefined) || os.hostname()) || "unknown";
        const clients = [];
        for (let i = WebSocketServer._clients.length - 1; i >= 0; i--) {
            const cli = WebSocketServer._clients[i];
            var c = {};
            c._type = "websocketclient";
            c.api = hostname;
            c.id = cli.id;
            c.clientagent = cli.clientagent;
            c.clientversion = cli.clientversion;
            c._exchanges = cli._exchanges;
            c._queues = cli._queues;
            c.lastheartbeat = cli.lastheartbeat;
            c.created = cli.created;
            c.remoteip = cli.remoteip;
            c.user = cli.user;
            c.username = cli.username;
            c.watches = cli.watches;
            clients.push(c);
        }
        Config_1.Config.db.InsertMany(clients, "websocketclients", 1, false, jwt, null);
    }
    static async pingClients() {
        const span = (Config_1.Config.otel_trace_pingclients ? Logger_1.Logger.otel.startSpan("WebSocketServer.pingClients") : null);
        try {
            let count = WebSocketServer._clients.length;
            for (let i = WebSocketServer._clients.length - 1; i >= 0; i--) {
                const cli = WebSocketServer._clients[i];
                try {
                    if (!openflow_api_1.NoderedUtil.IsNullEmpty(cli.jwt)) {
                        const payload = Crypt_1.Crypt.decryptToken(cli.jwt);
                        const clockTimestamp = Math.floor(Date.now() / 1000);
                        if ((payload.exp - clockTimestamp) < 60) {
                            Logger_1.Logger.instanse.debug("WebSocketServer", "pingClients", "Token for " + cli.id + "/" + cli.user.name + "/" + cli.clientagent + " expires in less than 1 minute, send new jwt to client");
                            const tuser = await Message_1.Message.DoSignin(cli, null);
                            if (tuser != null) {
                                span === null || span === void 0 ? void 0 : span.addEvent("Token for " + cli.id + "/" + cli.user.name + "/" + cli.clientagent + " expires in less than 1 minute, send new jwt to client");
                                const l = new openflow_api_1.SigninMessage();
                                cli.jwt = Crypt_1.Crypt.createToken(tuser, Config_1.Config.shorttoken_expires_in);
                                l.jwt = cli.jwt;
                                l.user = tuser;
                                const m = new Message_1.Message();
                                m.command = "refreshtoken";
                                m.data = JSON.stringify(l);
                                cli.Send(m);
                            }
                            else {
                                cli.Close();
                            }
                        }
                    }
                }
                catch (error) {
                    span === null || span === void 0 ? void 0 : span.recordException(error);
                    Logger_1.Logger.instanse.error("WebSocketServer", "pingClients", error);
                    cli.Close();
                }
                const now = new Date();
                const seconds = (now.getTime() - cli.lastheartbeat.getTime()) / 1000;
                cli.lastheartbeatsec = seconds.toString();
                if (seconds >= Config_1.Config.client_heartbeat_timeout) {
                    if (cli.user != null) {
                        span === null || span === void 0 ? void 0 : span.addEvent("client " + cli.id + "/" + cli.user.name + "/" + cli.clientagent + " timeout, close down");
                        Logger_1.Logger.instanse.debug("WebSocketServer", "pingClients", "client " + cli.id + "/" + cli.user.name + "/" + cli.clientagent + " timeout, close down");
                    }
                    else {
                        span === null || span === void 0 ? void 0 : span.addEvent("client not signed/" + cli.id + "/" + cli.clientagent + " timeout, close down");
                        Logger_1.Logger.instanse.debug("WebSocketServer", "pingClients", "client not signed/" + cli.id + "/" + cli.clientagent + " timeout, close down");
                    }
                    cli.Close();
                }
                cli.ping(span);
                if (!cli.connected() && cli.queuecount() == 0) {
                    if (cli.user != null) {
                        Logger_1.Logger.instanse.debug("WebSocketServer", "pingClients", "removing disconnected client " + cli.id + "/" + cli.user.name + "/" + cli.clientagent);
                        span === null || span === void 0 ? void 0 : span.addEvent("removing disconnected client " + cli.id + "/" + cli.user.name + "/" + cli.clientagent);
                    }
                    else {
                        Logger_1.Logger.instanse.debug("WebSocketServer", "pingClients", "removing disconnected client " + cli.id + "/" + cli.clientagent);
                        span === null || span === void 0 ? void 0 : span.addEvent("removing disconnected client " + cli.id + "/" + cli.clientagent);
                    }
                    try {
                        cli.CloseConsumers(span);
                        WebSocketServer._clients.splice(i, 1);
                    }
                    catch (error) {
                        span === null || span === void 0 ? void 0 : span.recordException(error);
                        Logger_1.Logger.instanse.error("WebSocketServer", "pingClients", error);
                    }
                }
            }
            if (count !== WebSocketServer._clients.length) {
                Logger_1.Logger.instanse.debug("WebSocketServer", "pingClients", "new client count: " + WebSocketServer._clients.length);
                span === null || span === void 0 ? void 0 : span.setAttribute("clientcount", WebSocketServer._clients.length);
            }
            const p_all = {};
            const bulkUpdates = [];
            for (let i = 0; i < WebSocketServer._clients.length; i++) {
                try {
                    const cli = WebSocketServer._clients[i];
                    if (cli.user != null) {
                        if (!openflow_api_1.NoderedUtil.IsNullEmpty(cli.clientagent)) {
                            if (!openflow_api_1.NoderedUtil.IsNullUndefinded(WebSocketServer.p_all)) {
                                if (openflow_api_1.NoderedUtil.IsNullUndefinded(p_all[cli.clientagent]))
                                    p_all[cli.clientagent] = 0;
                                p_all[cli.clientagent] += 1;
                            }
                        }
                        var updateDoc = await Logger_1.Logger.DBHelper.UpdateHeartbeat(cli);
                        if (updateDoc != null) {
                            bulkUpdates.push({
                                updateOne: {
                                    filter: { _id: cli.user._id },
                                    update: updateDoc
                                }
                            });
                        }
                    }
                }
                catch (error) {
                    span === null || span === void 0 ? void 0 : span.recordException(error);
                    Logger_1.Logger.instanse.error("WebSocketServer", "pingClients", error);
                }
            }
            if (bulkUpdates.length > 0) {
                this.lastUserUpdate = Date.now();
                let ot_end = Logger_1.Logger.otel.startTimer();
                var bulkresult = await Config_1.Config.db.db.collection("users").bulkWrite(bulkUpdates);
                Logger_1.Logger.otel.endTimer(ot_end, DatabaseConnection_1.DatabaseConnection.mongodb_updatemany, { collection: "users" });
            }
        }
        catch (error) {
            span === null || span === void 0 ? void 0 : span.recordException(error);
            Logger_1.Logger.instanse.error("WebSocketServer", "pingClients", error);
        }
        finally {
            Logger_1.Logger.otel.endSpan(span);
            setTimeout(this.pingClients.bind(this), Config_1.Config.ping_clients_interval);
        }
    }
}
exports.WebSocketServer = WebSocketServer;
//public static total_connections_count: number = 0;
WebSocketServer.total_connections_count = {};
WebSocketServer.lastUserUpdate = Date.now();
//# sourceMappingURL=WebSocketServer.js.map