"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.SrpcServer = void 0;
const tslib_1 = require("tslib");
const __ΩReturnType = ['T', 'args', '', 'ReturnType', 'l>e"!R"RPde#!P"@2"h"!/#qk#\'QRb!Pde"!p)w$y'];
const __ΩRecord = ['K', 'T', 'Record', 'l\'e#"Rb!b"Pde"!N#!w#y'];
const __ΩNonNullable = ['T', 'NonNullable', 'b!Pe"!PMKw"y'];
const __ΩPartial = ['T', 'Partial', 'l+e#!e"!fRb!Pde"!gN#"w"y'];
function __assignType(fn, args) {
    fn.__type = args;
    return fn;
}
const framework_1 = require("@deepkit/framework");
/*@ts-ignore*/
var { __ΩScopedLogger } = require('@deepkit/logger');
const type_1 = require("@deepkit/type");
const crypto_1 = require("crypto");
const ws_1 = tslib_1.__importDefault(require("ws"));
const resolver_1 = require("../app/resolver");
const helpers_1 = require("../helpers");
const services_1 = require("../services");
const telemetry_1 = require("../telemetry");
const SrpcByteStream_1 = require("./SrpcByteStream");
/*@ts-ignore*/
var { __ΩSrpcStream, __ΩRequestKeys, __ΩTSrpcMessageHandlerFnOrClass, __ΩSrpcDisconnectCause, __ΩIQueuedRequest, __ΩSrpcMeta, __ΩBaseMessage, __ΩSrpcMessageFns, __ΩISrpcServerOptions, __ΩHandlerRequestData, __ΩResponseData, __ΩRequestData } = require('./types');
const types_1 = require("./types");
const WebSocketUpgradeHandler_1 = require("./WebSocketUpgradeHandler");
const StreamInfoSymbol = Symbol('srpc-info');
class SrpcServer {
    constructor(options) {
        this.options = options;
        this.streamConnectionHandlers = (Set.Ω = [[() => __ΩSrpcStream, 'stream', '', 'P!o!"2"P$$`J/#']], new Set());
        this.blockedClientRequests = (WeakSet.Ω = [[() => __ΩSrpcStream, '!o!"']], new WeakSet());
        this.pendingClientRequests = (WeakMap.Ω = [[() => __ΩSrpcStream, '!o!"'], ['!F']], new WeakMap());
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        this.streamMessageHandlers = (Map.Ω = [[() => __ΩRequestKeys, '!o!"'], ['resultType', () => __ΩTSrpcMessageHandlerFnOrClass, () => __ΩSrpcStream, 'handler', 'P&4!!o#"""o"$4$M']], new Map());
        this.streamDisconnectionHandlers = (Set.Ω = [[() => __ΩSrpcStream, 'stream', () => __ΩSrpcDisconnectCause, 'cause', '', 'P!o!"2"n#2$$/%']], new Set());
        this.streamsById = (Map.Ω = [['&'], [() => __ΩSrpcStream, '!o!"']], new Map());
        this.streamsByClientId = (Map.Ω = [['&'], [() => __ΩSrpcStream, '!o!"']], new Map());
        this.pendingStreamsByClientId = (Map.Ω = [['&'], [() => __ΩSrpcStream, '!o!"']], new Map());
        const logLevel = options.logLevel ?? 'info';
        if (logLevel === false) {
            const noop = (() => { });
            this.logger = { info: noop, warn: noop, error: noop, debug: noop };
            options.debug = false;
        }
        else if (logLevel === 'debug') {
            const base = options.logger;
            this.logger = {
                info: base.debug.bind(base),
                warn: base.warn.bind(base),
                error: base.error.bind(base),
                debug: base.debug.bind(base)
            };
        }
        else {
            this.logger = options.logger;
        }
        this.inboundType = options.clientMessage;
        this.outboundType = options.serverMessage;
        const appConfig = (0, resolver_1.getAppConfig)();
        this.standardSrpcAuthKey = appConfig.SRPC_AUTH_SECRET;
        this.authClockDriftMs = appConfig.SRPC_AUTH_CLOCK_DRIFT_MS;
        this.useRealIpHeader = appConfig.USE_REAL_IP_HEADER ?? false;
        // WebSocket server setup — use noServer mode so multiple ws servers
        // can coexist on the same HTTP server without aborting each other's
        // upgrade requests (the default `{ server, path }` mode aborts
        // non-matching upgrades, breaking other ws servers on the same port).
        const app = (0, resolver_1.r)(framework_1.ApplicationServer);
        this.httpServer = app.getHttpWorker()['server'];
        this.wsServer = new ws_1.default.Server({ noServer: true });
        this.wsServer.on('connection', this.attachConnection.bind(this));
        (0, WebSocketUpgradeHandler_1.installWebSocketUpgradeHandler)({
            httpServer: this.httpServer,
            wsPath: options.wsPath,
            wsServer: this.wsServer,
            verifyClient: this.verifyClient.bind(this)
        });
        this.logger.info('WebSocket server listening', { path: options.wsPath });
        this.inactivityCheckInterval = setInterval(() => this.terminateInactiveStreams(), 15_000);
    }
    ////////////////////////////////////////
    // WebSocket Connection Handling
    verifyClient(info, cb) {
        const url = new URL(info.req.url, 'http://localhost');
        const q = Object.fromEntries(url.searchParams.entries());
        const { id: clientStreamId, cid: clientId, appv: appVersion } = q;
        const address = (this.useRealIpHeader && info.req.headers['x-real-ip']) || info.req.socket.remoteAddress;
        this.logger.info('Incoming streaming WebSocket request', {
            address,
            clientStreamId,
            clientId,
            appVersion
        });
        if (!clientStreamId || !clientId || !appVersion) {
            this.logger.warn('Client missing required query parameters', { clientId, clientStreamId });
            cb(false, 400, 'Missing required query parameters');
            return;
        }
        this.validateClientAuth(q, info.req).then(__assignType(result => {
            if (!result) {
                this.logger.warn('Client failed authentication', { clientId, clientStreamId });
                // eslint-disable-next-line promise/no-callback-in-promise
                return cb(false, 403, 'Failed authentication');
            }
            const authMeta = result !== true ? result : undefined;
            const meta = Object.fromEntries(Object.entries(q)
                .filter(__assignType(([key]) => key.startsWith('m--'), ['param0', '', 'P"2!"/"']))
                .map(__assignType(([key, value]) => [key.slice(3), value], ['param0', '', 'P"2!"/"'])));
            // eslint-disable-next-line @typescript-eslint/no-explicit-any
            info.req[StreamInfoSymbol] = {
                clientStreamId,
                clientId,
                appVersion,
                configureTs: parseInt(q.ts ?? '0'),
                protocolVersion: parseInt(q._v ?? '1'),
                supersede: q._supersede === '1',
                address,
                meta: {
                    ...meta,
                    ...authMeta
                }
            };
            // eslint-disable-next-line promise/no-callback-in-promise
            cb(true);
        }, ['result', '', 'P"2!"/"']), __assignType(err => {
            this.logger.error('Error validating client auth', err, { clientId, clientStreamId });
            // eslint-disable-next-line promise/no-callback-in-promise
            cb(false, 500, 'Error during authentication');
        }, ['err', '', 'P"2!"/"']));
    }
    attachConnection(ws, req) {
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        const { clientId, clientStreamId, appVersion, address, configureTs, protocolVersion, supersede, meta } = req[StreamInfoSymbol];
        const streamId = (0, type_1.uuid)();
        this.logger.info('WebSocket request answered', {
            address,
            streamId,
            clientId,
            clientStreamId,
            meta
        });
        const now = Date.now();
        const stream = {
            $ws: ws,
            $queue: (Map.Ω = [['&'], [() => __ΩIQueuedRequest, 'n!']], new Map()),
            id: streamId,
            clientStreamId,
            address,
            clientId,
            appVersion,
            configureTs,
            protocolVersion,
            supersede,
            connectedAt: now,
            isActivated: false,
            lastPingAt: now,
            meta,
            byteStream: {
                parentStreamId: streamId,
                // eslint-disable-next-line @typescript-eslint/no-explicit-any
                write: __assignType((substreamId, chunk) => {
                    this.write(ws, { byteStreamOperation: { streamId: substreamId, write: { chunk } } });
                    return true;
                }, ['substreamId', 'chunk', '', 'P\'2!"2""/#']),
                finish: __assignType((substreamId) => {
                    this.write(ws, { byteStreamOperation: { streamId: substreamId, finish: {} } });
                }, ['substreamId', '', 'P\'2!"/"']),
                // eslint-disable-next-line @typescript-eslint/no-explicit-any
                destroy: __assignType((substreamId, err) => {
                    this.write(ws, {
                        byteStreamOperation: { streamId: substreamId, destroy: { error: err ? String(err) : undefined } }
                    });
                }, ['substreamId', 'err', '', 'P\'2!"2"8"/#']),
                attachDisconnectHandler: __assignType(function attachDisconnectHandler(handler) {
                    ws.on('close', handler);
                }, ['', 'handler', 'attachDisconnectHandler', 'PP$/!2""/#']),
                detachDisconnectHandler: __assignType(function detachDisconnectHandler(handler) {
                    ws.off('close', handler);
                }, ['', 'handler', 'detachDisconnectHandler', 'PP$/!2""/#']),
                getBufferedAmount: __assignType(function getBufferedAmount() {
                    return ws.bufferedAmount;
                }, ['getBufferedAmount', 'P"/!'])
            }
        };
        SrpcByteStream_1.SrpcByteStream.init(stream, { startId: 2, step: 2 });
        ws.on('error', __assignType(err => this.handleStreamError(stream, err), ['err', '', 'P"2!"/"']));
        ws.on('close', () => this.handleStreamDisconnected(stream));
        this.handleStreamEstablished(stream, supersede);
    }
    handleWsMessage(stream, data) {
        if (!(data instanceof Buffer)) {
            return this.closeStreamWithError(stream, 'badArg', 'Received non-binary data');
        }
        try {
            const decoded = this.inboundType.decode(data);
            this.handleStreamDataReceived(stream, decoded);
        }
        catch (error) {
            this.logger.warn('Failed to decode message', { error, streamId: stream.id, clientId: stream.clientId });
            return this.closeStreamWithError(stream, 'badArg', 'Invalid message format');
        }
    }
    write(ws, message) {
        const encoded = this.outboundType.encode(message).finish();
        ws.send(encoded);
    }
    writeToStream(stream, data) {
        this.write(stream.$ws, data);
    }
    getCurrentStreamByClientId(clientId) {
        return this.pendingStreamsByClientId.get(clientId) ?? this.streamsByClientId.get(clientId);
    }
    isCurrentStream(stream) {
        return this.getCurrentStreamByClientId(stream.clientId) === stream;
    }
    activateStream(stream) {
        if (stream.lastPingAt < 0 || !this.isCurrentStream(stream)) {
            return;
        }
        this.pendingStreamsByClientId.delete(stream.clientId);
        stream.isActivated = true;
        this.streamsByClientId.set(stream.clientId, stream);
    }
    closeStreamWithError(stream, cause, message) {
        stream.$ws.close(SrpcServer.CLOSE_CODES[cause], message.substring(0, 123));
    }
    ////////////////////////////////////////
    // Authentication
    async validateClientAuth(meta, req) {
        if (this.clientAuthorizer) {
            return this.clientAuthorizer(meta, req);
        }
        const { authv, appv, ts, id, cid, signature } = meta;
        if (!meta.authv || !meta.appv || !meta.ts || !meta.signature) {
            throw new Error('Missing authorization metadata');
        }
        const clientKey = await this.fetchClientKey(cid);
        if (clientKey === false)
            return false;
        const tsInt = parseInt(ts, 10);
        if (isNaN(tsInt)) {
            this.logger.warn('Invalid timestamp');
            return false;
        }
        if (Math.abs(Date.now() - tsInt) > this.authClockDriftMs) {
            this.logger.warn('Timestamp expired');
            return false;
        }
        const computedSignature = (0, crypto_1.createHmac)('sha256', clientKey).update(`${authv}\n${appv}\n${ts}\n${id}\n${cid}\n`).digest('hex');
        const sigBuf = Buffer.from(signature ?? '');
        const computedBuf = Buffer.from(computedSignature);
        if (sigBuf.length !== computedBuf.length || !(0, crypto_1.timingSafeEqual)(sigBuf, computedBuf)) {
            this.logger.warn('Invalid signature');
            return false;
        }
        return true;
    }
    async fetchClientKey(clientId) {
        if (this.clientKeyFetcher)
            return this.clientKeyFetcher(clientId);
        if (!this.standardSrpcAuthKey) {
            throw new Error('SRPC_AUTH_SECRET is not configured.');
        }
        return this.standardSrpcAuthKey;
    }
    setClientAuthorizer(authorizer) {
        this.clientAuthorizer = authorizer;
    }
    setClientKeyFetcher(fetcher) {
        this.clientKeyFetcher = fetcher;
    }
    close() {
        clearInterval(this.inactivityCheckInterval);
        for (const stream of this.streamsById.values()) {
            this.cleanupStream(stream, 'disconnect');
        }
        this.wsServer.close();
    }
    ////////////////////////////////////////
    // Stream Lifecycle
    handleStreamEstablished(stream, supersede) {
        const conflictingStream = this.getCurrentStreamByClientId(stream.clientId);
        if (conflictingStream) {
            if (stream.protocolVersion >= 2 && !supersede) {
                this.logger.warn('Rejecting new connection due to existing client ID', {
                    streamId: stream.id,
                    clientId: stream.clientId,
                    existingStreamId: conflictingStream.id
                });
                stream.lastPingAt = -1; // mark as cleaned up so cleanupStream no-ops on ws close
                this.closeStreamWithError(stream, 'conflict', 'Client ID already connected');
                return;
            }
            this.logger.warn('Kicking existing stream with same client ID', {
                streamId: stream.id,
                clientId: stream.clientId,
                conflictingStreamId: conflictingStream.id
            });
            this.cleanupStream(conflictingStream, 'supersede');
        }
        // Register in local maps before the async hook so lifecycle chains
        // can observe the replacement stream without exposing it for
        // clientId-based delivery until activation succeeds.
        this.streamsById.set(stream.id, stream);
        this.pendingStreamsByClientId.set(stream.clientId, stream);
        this.blockedClientRequests.add(stream);
        // Async hook for subclass checks (e.g. cross-pod mesh registration).
        // Activation is deferred until the hook resolves, so no handlers run
        // and no RPCs are processed until the subclass confirms the stream
        // should proceed.
        this.postEstablishCheck(stream)
            .then(__assignType(async (rejected) => {
            if (rejected)
                return; // subclass already called cleanupStream
            // Stream may have been cleaned up during the async gap
            if (stream.lastPingAt < 0)
                return;
            // Preserve pingPong as the first server frame. Client traffic sent
            // before activation completes is queued until activation succeeds,
            // unless it is a reply to a server-initiated request.
            this.writeToStream(stream, { pingPong: {} });
            stream.$ws.on('message', __assignType(data => this.handleWsMessage(stream, data), ['data', '', 'P"2!"/"']));
            try {
                await this.onStreamConnected(stream);
            }
            catch (err) {
                this.logger.error('onStreamConnected failed, disconnecting stream', err, {
                    streamId: stream.id,
                    clientId: stream.clientId
                });
                this.cleanupStream(stream, 'disconnect');
                return;
            }
            if (stream.lastPingAt < 0)
                return;
            this.activateStream(stream);
            if (!stream.isActivated)
                return;
            try {
                await this.onStreamActivated(stream);
            }
            catch (err) {
                this.logger.error('onStreamActivated failed, disconnecting stream', err, {
                    streamId: stream.id,
                    clientId: stream.clientId
                });
                this.cleanupStream(stream, 'disconnect');
                return;
            }
            if (stream.lastPingAt < 0 || !stream.isActivated)
                return;
            this.openClientRequests(stream);
        }, ['rejected', '', 'P"2!"/"']))
            .catch(__assignType(err => {
            this.logger.error('postEstablishCheck failed, disconnecting stream', err, {
                streamId: stream.id,
                clientId: stream.clientId
            });
            this.cleanupStream(stream, 'disconnect');
        }, ['err', '', 'P"2!"/"']));
    }
    /**
     * Async hook called after a stream is registered in local maps but before
     * the initial handshake ping is sent. Return true to reject the stream;
     * the subclass must call cleanupStream() before returning true. The base
     * implementation accepts all streams immediately.
     */
    // eslint-disable-next-line @typescript-eslint/no-unused-vars
    postEstablishCheck(_stream) {
        return Promise.resolve(false);
    }
    handleStreamDisconnected(stream) {
        const { id: streamId, clientId } = stream;
        this.logger.info('Stream disconnected', { streamId, clientId });
        this.cleanupStream(stream);
    }
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    handleStreamError(stream, err) {
        const { id: streamId, clientId } = stream;
        this.logger.warn('Stream error', { err, streamId, clientId });
        this.cleanupStream(stream);
    }
    terminateInactiveStreams() {
        const deadlineMs = Date.now() - 75_000;
        for (const stream of this.streamsById.values()) {
            if (stream.lastPingAt < deadlineMs) {
                this.logger.warn('Terminating inactive stream', { streamId: stream.id, clientId: stream.clientId });
                this.cleanupStream(stream, 'timeout');
            }
        }
    }
    cleanupStream(stream, forceCause) {
        if (stream.lastPingAt < 0)
            return; // already cleaned up
        stream.lastPingAt = -1;
        if (forceCause)
            this.closeStreamWithError(stream, forceCause, `Stream terminated with cause: ${forceCause}`);
        this.onStreamDisconnected(stream, forceCause ?? 'disconnect');
        stream.$queue.forEach(__assignType(item => item.reject(new Error('Stream disconnected')), ['item', '', 'P"2!"/"']));
        stream.$queue.clear();
        this.blockedClientRequests.delete(stream);
        this.pendingClientRequests.delete(stream);
        this.streamsById.delete(stream.id);
        if (this.pendingStreamsByClientId.get(stream.clientId) === stream) {
            this.pendingStreamsByClientId.delete(stream.clientId);
        }
        if (this.streamsByClientId.get(stream.clientId) === stream) {
            this.streamsByClientId.delete(stream.clientId);
        }
    }
    ////////////////////////////////////////
    // Message Handling
    handleStreamDataReceived(stream, data) {
        if (data.pingPong) {
            stream.lastPingAt = Date.now();
            return this.writeToStream(stream, { pingPong: {} });
        }
        if (data.byteStreamOperation) {
            return this.handleByteSubstreamOperation(stream, data.byteStreamOperation);
        }
        const { id: streamId, clientId } = stream;
        const { requestId, reply } = data;
        if ((!stream.isActivated || this.blockedClientRequests.has(stream)) && !reply) {
            const queued = this.pendingClientRequests.get(stream) ?? [];
            queued.push(data);
            this.pendingClientRequests.set(stream, queued);
            this.logger.debug('Queueing client request before stream activation', { streamId, clientId, requestId });
            return;
        }
        const logObject = {
            streamId,
            clientId,
            requestId,
            reply,
            error: data.userError ? 'user' : data.error ? 'application' : undefined
        };
        this.logger[this.options.debug ? 'info' : 'debug']('Client message received', this.options.debug ? { ...logObject, data } : logObject);
        if (!requestId) {
            this.logger.warn('Invalid request ID', { srpc: { streamId, clientId, requestId } });
            return this.closeStreamWithError(stream, 'badArg', 'Invalid request ID');
        }
        if (reply) {
            const queueItem = stream.$queue.get(requestId);
            if (!queueItem) {
                this.logger.warn('Unknown request ID for reply', { srpc: { streamId, clientId, requestId } });
                return this.closeStreamWithError(stream, 'badArg', 'Unknown request ID');
            }
            stream.$queue.delete(requestId);
            if (data.error) {
                return queueItem.reject(new types_1.SrpcError(data.error, data.userError));
            }
            return queueItem.resolve(data);
        }
        this.handleClientRequest(stream, requestId, data).then(__assignType(response => {
            // eslint-disable-next-line @typescript-eslint/no-explicit-any
            this.writeToStream(stream, { requestId, reply: true, ...response });
        }, ['response', '', 'P"2!"/"']), __assignType(err => {
            this.logger.warn('Error processing client request', err, { srpc: { streamId, clientId, requestId } });
            const isUserError = err instanceof types_1.SrpcError && err.isUserError;
            this.writeToStream(stream, {
                requestId,
                reply: true,
                error: isUserError ? err.message : String(err),
                userError: isUserError
            });
        }, ['err', '', 'P"2!"/"']));
    }
    handleByteSubstreamOperation(stream, op) {
        if (op.write) {
            return SrpcByteStream_1.SrpcByteStream.writeReceiver(stream, op.streamId, op.write.chunk);
        }
        if (op.finish) {
            return SrpcByteStream_1.SrpcByteStream.finishReceiver(stream, op.streamId);
        }
        if (op.destroy) {
            return SrpcByteStream_1.SrpcByteStream.destroySubstream(stream, op.streamId, op.destroy.error);
        }
    }
    async handleClientRequest(stream, requestId, message) {
        const logMeta = { streamId: stream.id, clientId: stream.clientId, requestId };
        for (const key of this.streamMessageHandlers.keys()) {
            if (message[key]) {
                Object.assign(logMeta, { requestType: key });
                return (0, telemetry_1.withRemoteSpan)('srpc:handleClientRequest', message.trace, logMeta, () => (0, services_1.withLoggerContext)({ srpc: logMeta }, async () => {
                    try {
                        this.logger.info('Client request received');
                        const handlerMeta = this.streamMessageHandlers.get(key);
                        const result = await this.runMessageHandler(handlerMeta.handler, stream, message[key]);
                        this.logger.info('Client request processed');
                        return {
                            [handlerMeta.resultType]: result
                            // eslint-disable-next-line @typescript-eslint/no-explicit-any
                        };
                    }
                    catch (err) {
                        this.logger.warn('Client request failed', err);
                        throw err;
                    }
                }));
            }
        }
        throw new Error('Unhandled message type');
    }
    openClientRequests(stream) {
        this.blockedClientRequests.delete(stream);
        const queued = this.pendingClientRequests.get(stream);
        if (!queued?.length)
            return;
        this.pendingClientRequests.delete(stream);
        for (const request of queued) {
            this.handleStreamDataReceived(stream, request);
        }
    }
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    async runMessageHandler(handler, stream, data) {
        if ((0, types_1.isSrpcMessageHandlerClass)(handler)) {
            // eslint-disable-next-line @typescript-eslint/no-explicit-any
            const handlerInstance = new handler();
            return handlerInstance.handle(stream, data);
        }
        return handler(stream, data);
    }
    async onStreamConnected(stream) {
        for (const handler of this.streamConnectionHandlers) {
            await handler(stream);
        }
    }
    onStreamActivated(_stream) { }
    onStreamDisconnected(stream, cause) {
        this.streamDisconnectionHandlers.forEach(__assignType(handler => handler(stream, cause), ['handler', '', 'P"2!"/"']));
    }
    ////////////////////////////////////////
    // Public API
    /**
     * Register a handler that runs after the initial handshake ping is sent.
     * Throwing or rejecting aborts activation and disconnects the stream.
     */
    registerConnectionHandler(handler) {
        this.streamConnectionHandlers.add(handler);
    }
    /**
     * Register a handler for client requests.
     * @example server.registerMessageHandler('uEcho', async (stream, data) => ({ message: `Echo: ${data.message}` }))
     */
    registerMessageHandler(prefix, handler) {
        const actionType = `${prefix}Request`;
        const resultType = `${prefix}Response`;
        this.streamMessageHandlers.set(actionType, {
            resultType,
            handler
        });
    }
    registerDisconnectHandler(handler) {
        this.streamDisconnectionHandlers.add(handler);
    }
    /**
     * Invoke a client method.
     * @example server.invoke(stream, 'dNotify', { notification: 'hello' })
     */
    invoke(stream, prefix, data, timeoutMs = 30_000) {
        const requestType = `${prefix}Request`;
        const resultType = `${prefix}Response`;
        const requestId = (0, helpers_1.uuid7)();
        const traceContext = (0, telemetry_1.getTraceContext)();
        const trace = traceContext ? { traceId: traceContext.traceId, spanId: traceContext.spanId, traceFlags: traceContext.traceFlags } : undefined;
        const logMeta = { streamId: stream.id, clientId: stream.clientId, requestId, requestType };
        return (0, telemetry_1.withSpan)('srpc:invokeClient', logMeta, () => (0, services_1.withLoggerContext)({ srpc: logMeta }, async () => {
            try {
                this.logger.info('Requesting client invocation', this.options.debug ? { data } : undefined);
                // eslint-disable-next-line @typescript-eslint/no-explicit-any
                const response = await (Promise.Ω = [['"']], new Promise(__assignType((resolve, reject) => {
                    stream.$queue.set(requestId, {
                        exp: Date.now() + timeoutMs,
                        resolve,
                        reject
                    });
                    this.writeToStream(stream, { requestId, trace, [requestType]: data });
                }, ['resolve', 'reject', '', 'P"2!"2""/#'])));
                // Use null check instead of `in` for consistency with ts-proto field materialization
                // eslint-disable-next-line @typescript-eslint/no-explicit-any
                if (response[resultType] == null) {
                    throw new Error('Invalid response from client');
                }
                this.logger.info('Client invocation completed');
                // eslint-disable-next-line @typescript-eslint/no-explicit-any
                return response[resultType];
            }
            catch (err) {
                const isUserError = err instanceof types_1.SrpcError && err.isUserError;
                if (isUserError) {
                    this.logger.warn('Client invocation returned user error', { err: err.message });
                }
                else {
                    this.logger.warn('Client invocation failed', err);
                }
                throw err;
            }
        }));
    }
    static createInvoke(instanceFn) {
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        const fn = __assignType((...args) => {
            const instance = instanceFn();
            // eslint-disable-next-line prefer-spread, @typescript-eslint/no-explicit-any
            return instance.invoke.apply(instance, args);
        }, ['args', '', 'P"@2!"/"']);
        return fn;
    }
}
exports.SrpcServer = SrpcServer;
// IMPORTANT: SrpcClient.parseDisconnectCause() matches on the close code number
// to determine the disconnect cause. If you change these codes, update the client too.
SrpcServer.CLOSE_CODES = {
    disconnect: 1000,
    badArg: 4000,
    conflict: 4001,
    supersede: 4002,
    timeout: 4003
};
SrpcServer.__type = [() => __ΩSrpcMeta, 'TMeta', () => __ΩBaseMessage, 'TClientOutput', () => __ΩBaseMessage, 'TServerOutput', 'standardSrpcAuthKey', 'authClockDriftMs', 'useRealIpHeader', () => __ΩScopedLogger, 'logger', 'wsServer', 'httpServer', () => __ΩSrpcMessageFns, 'inboundType', () => __ΩSrpcMessageFns, 'outboundType', () => __ΩSrpcMeta, 'metadata', 'req', () => __ΩSrpcMeta, '', 'clientAuthorizer', 'clientId', false, 'clientKeyFetcher', 'streamConnectionHandlers', function () { return (Set.Ω = [[() => __ΩSrpcStream, 'stream', '', 'P!o!"2"P$$`J/#']], new Set()); }, 'blockedClientRequests', function () { return (WeakSet.Ω = [[() => __ΩSrpcStream, '!o!"']], new WeakSet()); }, 'pendingClientRequests', function () { return (WeakMap.Ω = [[() => __ΩSrpcStream, '!o!"'], ['!F']], new WeakMap()); }, 'streamMessageHandlers', function () { return (Map.Ω = [[() => __ΩRequestKeys, '!o!"'], ['resultType', () => __ΩTSrpcMessageHandlerFnOrClass, () => __ΩSrpcStream, 'handler', 'P&4!!o#"""o"$4$M']], new Map()); }, 'streamDisconnectionHandlers', function () { return (Set.Ω = [[() => __ΩSrpcStream, 'stream', () => __ΩSrpcDisconnectCause, 'cause', '', 'P!o!"2"n#2$$/%']], new Set()); }, () => __ΩReturnType, () => setInterval, 'inactivityCheckInterval', 'streamsById', function () { return (Map.Ω = [['&'], [() => __ΩSrpcStream, '!o!"']], new Map()); }, 'streamsByClientId', function () { return (Map.Ω = [['&'], [() => __ΩSrpcStream, '!o!"']], new Map()); }, 'pendingStreamsByClientId', function () { return (Map.Ω = [['&'], [() => __ΩSrpcStream, '!o!"']], new Map()); }, () => __ΩISrpcServerOptions, 'options', 'constructor', 'origin', 'secure', 'info', 'res', 'code', 'message', 'cb', 'verifyClient', () => ws_1.default, 'ws', 'attachConnection', () => __ΩSrpcStream, 'stream', 'data', 'handleWsMessage', () => ws_1.default, 'write', () => __ΩSrpcStream, 'writeToStream', () => __ΩSrpcStream, 'getCurrentStreamByClientId', () => __ΩSrpcStream, 'isCurrentStream', () => __ΩSrpcStream, 'activateStream', () => __ΩRecord, () => __ΩSrpcDisconnectCause, 'CLOSE_CODES', function () { return {
        disconnect: 1000,
        badArg: 4000,
        conflict: 4001,
        supersede: 4002,
        timeout: 4003
    }; }, () => __ΩSrpcStream, () => __ΩSrpcDisconnectCause, 'cause', 'closeStreamWithError', () => __ΩSrpcMeta, 'meta', 'validateClientAuth', 'fetchClientKey', () => __ΩSrpcMeta, () => __ΩSrpcMeta, 'authorizer', 'setClientAuthorizer', false, 'fetcher', 'setClientKeyFetcher', 'close', () => __ΩSrpcStream, 'supersede', 'handleStreamEstablished', () => __ΩSrpcStream, '_stream', 'postEstablishCheck', () => __ΩSrpcStream, 'handleStreamDisconnected', () => __ΩSrpcStream, 'err', 'handleStreamError', 'terminateInactiveStreams', () => __ΩSrpcStream, () => __ΩSrpcDisconnectCause, 'forceCause', 'cleanupStream', () => __ΩSrpcStream, 'handleStreamDataReceived', () => __ΩSrpcStream, () => __ΩNonNullable, "byteStreamOperation", 'op', 'handleByteSubstreamOperation', () => __ΩSrpcStream, 'requestId', () => __ΩPartial, 'handleClientRequest', () => __ΩSrpcStream, 'openClientRequests', () => __ΩTSrpcMessageHandlerFnOrClass, () => __ΩSrpcStream, 'handler', () => __ΩSrpcStream, 'runMessageHandler', () => __ΩSrpcStream, 'onStreamConnected', () => __ΩSrpcStream, 'onStreamActivated', () => __ΩSrpcStream, () => __ΩSrpcDisconnectCause, 'onStreamDisconnected', () => __ΩSrpcStream, 'registerConnectionHandler', 'prefix', () => __ΩTSrpcMessageHandlerFnOrClass, () => __ΩSrpcStream, () => __ΩHandlerRequestData, () => __ΩResponseData, 'registerMessageHandler', () => __ΩSrpcStream, () => __ΩSrpcDisconnectCause, 'registerDisconnectHandler', () => __ΩSrpcStream, () => __ΩRequestData, 'timeoutMs', () => __ΩResponseData, 'invoke', () => SrpcServer, 'instanceFn', () => SrpcServer, "invoke", 'createInvoke', 'SrpcServer', 'n!c"n#c$n%c&&3\'8;\'3(;)3);n*3+;!3,;!3-;e!"o."3/;e!#o0"31;Pn223!24P)n5J`/6378;P&28P.9&J`/63:8;!3;;><!3=;>>!3?;>@!3A;>B!3C;>DiFoE"3G;!3H9>I!3J9>K!3L9<>MPe""e"#oN#2O;"0PPP&4Q)4R!44M2SP)2T\'2U8&2V8$/62W"0X;PP7Y2Z!24"0[;Pe"!o\\"2]!2^"0_;PP7`2Ze"#2V"0a;Pe"!ob"2]e"#2^$0c;P&28Pe#!od"-J0e<Pe"!of"2])0g<Pe"!oh"2]$0i;nk\'oj#3l9;s>mPe"!on"2]no2p&2V"0q;Pnr2s!24"0t;P&28"0u;PPnv23!24P)nwJ`/62x"0yPP&28P.z&J`/62{"0|P"0}Pe"!o~"2])2"0\u0080;Pe"!o\u0081"2\u0082)`0\u0083<Pe"!o\u0084"2]$0\u0085;Pe"!o\u0086"2]"2\u0087$0\u0088;P"0\u0089;Pe"!o\u008A"2]n\u008B2\u008C8"0\u008D<Pe"!o\u008E"2]e""2^$0\u008F;Pe"!o\u0090"2]e"".\u0092fo\u0091"2\u0093$0\u0094;Pe"!o\u0095"2]&2\u0096e""2Ve"#o\u0097"`0\u0098;Pe"!o\u0099"2]$0\u009A;Pe"!o\u009C"""o\u009B$2\u009De"!o\u009E"2]"2^"0\u009F<Pe"!o\u00A0"2]$`0\u00A1<Pe"!o\u00A2"2\u0082P$$`J0\u00A3<Pe"!o\u00A4"2]n\u00A52p$0\u00A6<PPe#!o\u00A7"2]P$$`J/62\u009D"0\u00A8P"2\u00A9e"!o\u00AB"e"""o\u00AC#e"#"o\u00AD#o\u00AA$2\u009D"0\u00AEPPe#!o\u00AF"2]n\u00B02p$/62\u009D"0\u00B1Pe"!o\u00B2"2]"2\u00A9e"#"o\u00B3#2^"2\u00B4e"""o\u00B5#`0\u00B6PPP"""7\u00B7/62\u00B8P"""7\u00B9.\u00BAf0\u00BBs5w\u00BC'];
//# sourceMappingURL=SrpcServer.js.map