"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.__tsfTypeAliases = void 0;
exports.createWebSocketUpgradeHandler = createWebSocketUpgradeHandler;
exports.installWebSocketUpgradeHandler = installWebSocketUpgradeHandler;
exports.removeWebSocketUpgradeHandler = removeWebSocketUpgradeHandler;
exports.installUpgradeClaimHandling = installUpgradeClaimHandling;
const tslib_1 = require("tslib");
const _a = tslib_1.__importStar(require("@zyno-io/ts-reflection/type-metadata-runtime"));
const UpgradeClaimedSymbol = Symbol.for('@zyno-io/ts-server-foundation:http-upgrade-claimed');
const UpgradeClaimHandlingInstalledSymbol = Symbol.for('@zyno-io/ts-server-foundation:http-upgrade-claim-handling-installed');
const UpgradeRejectTimerSymbol = Symbol.for('@zyno-io/ts-server-foundation:http-upgrade-reject-timer');
const InstalledUpgradeHandlersSymbol = Symbol.for('@zyno-io/ts-server-foundation:http-installed-upgrade-handlers');
const UpgradeClaimStateSymbol = Symbol.for('@zyno-io/ts-server-foundation:http-upgrade-claim-state');
const UpgradeWriteRestoreSymbol = Symbol.for('@zyno-io/ts-server-foundation:http-upgrade-write-restore');
function createWebSocketUpgradeHandler(options) {
    const { wsPath, wsServer, verifyClient } = options;
    return (request, socket, head) => {
        const pathname = request.url?.split('?')[0];
        if (pathname !== wsPath)
            return;
        markUpgradeClaimed(socket);
        verifyClient({ origin: String(request.headers.origin ?? ''), secure: false, req: request }, (allowed, code, message) => {
            if (!allowed) {
                socket.write(`HTTP/1.1 ${code ?? 403} ${message ?? 'Forbidden'}\r\n\r\n`);
                socket.destroy();
                return;
            }
            wsServer.handleUpgrade(request, socket, head, ws => {
                wsServer.emit('connection', ws, request);
            });
        });
    };
}
function installWebSocketUpgradeHandler(options) {
    installUpgradeClaimHandling(options.httpServer);
    const installedHandlers = getInstalledHandlers(options.httpServer);
    const installed = installedHandlers.get(options.wsPath);
    if (installed) {
        installed.count++;
        return installed.handler;
    }
    const handler = createWebSocketUpgradeHandler(options);
    options.httpServer.prependListener('upgrade', handler);
    installedHandlers.set(options.wsPath, { handler, count: 1 });
    return handler;
}
function removeWebSocketUpgradeHandler(httpServer, wsPath, handler) {
    const installedHandlers = getInstalledHandlers(httpServer);
    const installed = installedHandlers.get(wsPath);
    if (!installed || installed.handler !== handler)
        return;
    installed.count--;
    if (installed.count > 0)
        return;
    httpServer.off('upgrade', handler);
    installedHandlers.delete(wsPath);
    if (installedHandlers.size === 0)
        uninstallUpgradeClaimHandling(httpServer);
}
function installUpgradeClaimHandling(server) {
    const anyServer = server;
    if (anyServer[UpgradeClaimHandlingInstalledSymbol])
        return;
    anyServer[UpgradeClaimHandlingInstalledSymbol] = true;
    const originalEmit = server.emit;
    server.emit = function (event, ...args) {
        if (event !== 'upgrade')
            return originalEmit.apply(this, [event, ...args]);
        const socket = args[1];
        installUpgradeWriteClaimDetection(socket);
        const listeners = this.rawListeners('upgrade').slice();
        for (const listener of listeners) {
            listener.apply(this, args);
            if (socket[UpgradeClaimedSymbol])
                break;
        }
        return listeners.length > 0;
    };
    const fallbackHandler = (_request, socket) => {
        clearUpgradeRejectionTimer(socket);
        const timer = setTimeout(() => {
            if (!socket[UpgradeClaimedSymbol] && !socket.destroyed) {
                socket.write('HTTP/1.1 400 Bad Request\r\n\r\n');
                socket.destroy();
            }
            clearUpgradeRejectionTimer(socket);
            clearUpgradeWriteClaimDetection(socket);
        }, 1000);
        timer.unref?.();
        socket[UpgradeRejectTimerSymbol] = timer;
        socket.once('close', () => {
            clearUpgradeRejectionTimer(socket);
            clearUpgradeWriteClaimDetection(socket);
        });
    };
    anyServer[UpgradeClaimStateSymbol] = { originalEmit, fallbackHandler };
    server.on('upgrade', fallbackHandler);
}
function uninstallUpgradeClaimHandling(server) {
    const anyServer = server;
    const state = anyServer[UpgradeClaimStateSymbol];
    if (!state)
        return;
    server.off('upgrade', state.fallbackHandler);
    server.emit = state.originalEmit;
    delete anyServer[UpgradeClaimHandlingInstalledSymbol];
    delete anyServer[UpgradeClaimStateSymbol];
}
function markUpgradeClaimed(socket) {
    socket[UpgradeClaimedSymbol] = true;
    clearUpgradeRejectionTimer(socket);
    clearUpgradeWriteClaimDetection(socket);
}
function installUpgradeWriteClaimDetection(socket) {
    const anySocket = socket;
    if (anySocket[UpgradeWriteRestoreSymbol])
        return;
    const originalWrite = socket.write;
    const patchedWrite = function (chunk, ...args) {
        if (isSuccessfulUpgradeResponse(chunk))
            markUpgradeClaimed(this);
        return originalWrite.apply(this, [chunk, ...args]);
    };
    socket.write = patchedWrite;
    anySocket[UpgradeWriteRestoreSymbol] = () => {
        if (socket.write === patchedWrite)
            socket.write = originalWrite;
    };
}
function clearUpgradeWriteClaimDetection(socket) {
    const anySocket = socket;
    const restore = anySocket[UpgradeWriteRestoreSymbol];
    if (!restore)
        return;
    delete anySocket[UpgradeWriteRestoreSymbol];
    restore();
}
function isSuccessfulUpgradeResponse(chunk) {
    if (typeof chunk === 'string')
        return /^HTTP\/1\.[01] 101\b/i.test(chunk);
    if (Buffer.isBuffer(chunk))
        return /^HTTP\/1\.[01] 101\b/i.test(chunk.toString('latin1', 0, Math.min(chunk.length, 32)));
    return false;
}
function clearUpgradeRejectionTimer(socket) {
    const timer = socket[UpgradeRejectTimerSymbol];
    if (!timer)
        return;
    clearTimeout(timer);
    delete socket[UpgradeRejectTimerSymbol];
}
function getInstalledHandlers(httpServer) {
    const anyServer = httpServer;
    anyServer[InstalledUpgradeHandlersSymbol] ??= new Map();
    return anyServer[InstalledUpgradeHandlersSymbol];
}
exports.__tsfTypeAliases = _a.decodeCompactMetadataV1("[1,{\"HttpUpgradeHandler\":{\"kind\":2,\"typeName\":\"(request: IncomingMessage, socket: Socket, head: Buffer) =\\u003e void\",\"typeName\":\"HttpUpgradeHandler\"},\"InstallWebSocketUpgradeHandlerOptions\":{\"kind\":18,\"typeName\":\"InstallWebSocketUpgradeHandlerOptions\",\"types\":[{\"kind\":20,\"name\":\"wsPath\",\"type\":{\"kind\":6},\"optional\":false},{\"kind\":20,\"name\":\"wsServer\",\"type\":{\"kind\":16,\"typeName\":\"WebSocket.Server\",\"classType\":{\"$tsf\":0}},\"optional\":false},{\"kind\":20,\"name\":\"verifyClient\",\"type\":{\"kind\":2,\"typeName\":\"(\\n    info: { origin: string; secure: boolean; req: IncomingMessage },\\n    cb: (res: boolean, code?: number, message?: string) =\\u003e void\\n) =\\u003e void\",\"typeName\":\"VerifyClientFn\"},\"optional\":false},{\"kind\":20,\"name\":\"httpServer\",\"type\":{\"$tsfAlias\":[1,\"node:http\",\"Server\",\"Server\"]},\"optional\":false}]},\"VerifyClientFn\":{\"kind\":2,\"typeName\":\"(\\n    info: { origin: string; secure: boolean; req: IncomingMessage },\\n    cb: (res: boolean, code?: number, message?: string) =\\u003e void\\n) =\\u003e void\",\"typeName\":\"VerifyClientFn\"},\"WebSocketUpgradeHandlerOptions\":{\"kind\":18,\"typeName\":\"WebSocketUpgradeHandlerOptions\",\"types\":[{\"kind\":20,\"name\":\"wsPath\",\"type\":{\"kind\":6},\"optional\":false},{\"kind\":20,\"name\":\"wsServer\",\"type\":{\"kind\":16,\"typeName\":\"WebSocket.Server\",\"classType\":{\"$tsf\":0}},\"optional\":false},{\"kind\":20,\"name\":\"verifyClient\",\"type\":{\"kind\":2,\"typeName\":\"(\\n    info: { origin: string; secure: boolean; req: IncomingMessage },\\n    cb: (res: boolean, code?: number, message?: string) =\\u003e void\\n) =\\u003e void\",\"typeName\":\"VerifyClientFn\"},\"optional\":false}]}}]", [() => WebSocket.Server, require]);
//# sourceMappingURL=upgrade.js.map