"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.SrpcServer = void 0;
const tslib_1 = require("tslib");
const _a = tslib_1.__importStar(require("@zyno-io/ts-server-foundation/type-metadata-runtime"));
const node_crypto_1 = require("node:crypto");
const ws_1 = tslib_1.__importDefault(require("ws"));
const app_1 = require("../app");
const helpers_1 = require("../helpers");
const SrpcByteStream_1 = require("./SrpcByteStream");
const observer_1 = require("./observer");
const types_1 = require("./types");
const http_1 = require("../http");
const __tsf_metadata_type = _a.createCompactMetadataRegistryV1("[1,[{\"kind\":2},{\"kind\":22,\"type\":{\"kind\":8}},{\"kind\":6},{\"kind\":8},{\"kind\":22,\"type\":{\"kind\":2}},{\"kind\":22,\"type\":{\"kind\":3}},{\"kind\":12,\"types\":[{\"kind\":3},{\"kind\":22,\"type\":{\"kind\":3}}]},{\"kind\":12,\"types\":[{\"kind\":10,\"literal\":\"disconnect\"},{\"kind\":10,\"literal\":\"conflict\"},{\"kind\":10,\"literal\":\"supersede\"},{\"kind\":10,\"literal\":\"timeout\"},{\"kind\":10,\"literal\":\"badArg\"}],\"typeName\":\"SrpcDisconnectCause\"},{\"kind\":3},{\"kind\":2,\"typeName\":\"(metadata: any, req: IncomingMessage) =\\u003e Promise\\u003cboolean | Partial\\u003cTMeta\\u003e\\u003e | boolean | Partial\\u003cTMeta\\u003e\"},{\"kind\":2,\"typeName\":\"(clientId: string) =\\u003e Promise\\u003cfalse | string\\u003e | false | string\"},{\"kind\":2,\"typeName\":\"(stream: SrpcStream\\u003cTMeta\\u003e) =\\u003e void | Promise\\u003cvoid\\u003e\"},{\"kind\":2,\"typeName\":\"P\"},{\"kind\":2,\"typeName\":\"(stream: SrpcStream\\u003cTMeta\\u003e, cause: SrpcDisconnectCause) =\\u003e void\"},{\"kind\":0},{\"kind\":18,\"typeName\":\"ResponseData\\u003cTClientOutput, P\\u003e\",\"types\":[{\"kind\":20,\"name\":\"\u00FE@toStringTag@64\",\"type\":{\"kind\":6},\"optional\":false}]}]]", []);
const StreamInfoSymbol = Symbol('srpc-info');
const noopLogger = {
    info() { },
    warn() { },
    error() { },
    debug() { }
};
let _SrpcServerMetadata_1;
class SrpcServer {
    options;
    logger;
    wsServer = new ws_1.default.Server({ noServer: true });
    streamConnectionHandlers = new Set();
    streamDisconnectionHandlers = new Set();
    streamMessageHandlers = new Map();
    blockedClientRequests = new WeakSet();
    pendingClientRequests = new WeakMap();
    cleanupUpgradeHandler;
    inactivityCheckInterval;
    clientAuthorizer;
    clientKeyFetcher;
    streamsById = new Map();
    streamsByClientId = new Map();
    pendingStreamsByClientId = new Map();
    constructor(options) {
        this.options = options;
        this.logger = createLogger(options.logger, options.logLevel);
        this.wsServer.on('connection', (ws, request) => this.attachConnection(ws, request));
        if (options.httpServer) {
            const handler = (0, http_1.installWebSocketUpgradeHandler)({
                httpServer: options.httpServer,
                wsPath: options.wsPath,
                wsServer: this.wsServer,
                verifyClient: this.verifyClient
            });
            this.cleanupUpgradeHandler = () => (0, http_1.removeWebSocketUpgradeHandler)(options.httpServer, options.wsPath, handler);
        }
        else {
            const app = (0, app_1.getCurrentApp)();
            const handler = (0, http_1.createWebSocketUpgradeHandler)({
                wsPath: options.wsPath,
                wsServer: this.wsServer,
                verifyClient: this.verifyClient
            });
            this.cleanupUpgradeHandler = app.http.registerUpgradeHandler(handler);
        }
        this.inactivityCheckInterval = setInterval(() => this.terminateInactiveStreams(), 15000);
        this.inactivityCheckInterval.unref?.();
        this.logger.info('WebSocket server listening', { path: options.wsPath });
    }
    verifyClient = (info, cb) => {
        const url = new URL(info.req.url ?? '', 'http://localhost');
        const query = Object.fromEntries(url.searchParams.entries());
        const { id: clientStreamId, cid: clientId, appv: appVersion } = query;
        const address = this.getRemoteAddress(info.req);
        if (!clientStreamId || !clientId || !appVersion) {
            cb(false, 400, 'Missing required query parameters');
            return;
        }
        this.validateClientAuth(query, info.req).then(result => {
            if (!result) {
                cb(false, 403, 'Failed authentication');
                return;
            }
            const queryMeta = Object.fromEntries(Object.entries(query)
                .filter(([key]) => key.startsWith('m--'))
                .map(([key, value]) => [key.slice(3), value]));
            const authMeta = result === true ? {} : result;
            info.req[StreamInfoSymbol] = {
                clientStreamId,
                clientId,
                appVersion,
                configureTs: Number(query.ts ?? 0),
                protocolVersion: Number(query._v ?? 1),
                supersede: query._supersede === '1',
                address,
                meta: { ...queryMeta, ...authMeta }
            };
            cb(true);
        }, error => {
            this.logger.warn('Error validating SRPC client auth', error);
            cb(false, 403, 'Failed authentication');
        });
    };
    attachConnection(ws, request) {
        const info = request[StreamInfoSymbol];
        if (!info) {
            ws.close(4000, 'Missing stream info');
            return;
        }
        const stream = this.createStream(ws, info);
        ws.on('error', error => this.handleStreamError(stream, error));
        ws.on('close', code => this.handleStreamDisconnected(stream, code));
        this.handleStreamEstablished(stream);
    }
    createStream(ws, info) {
        const streamId = (0, helpers_1.uuid7)();
        const stream = {
            $ws: ws,
            $queue: new Map(),
            id: streamId,
            clientStreamId: info.clientStreamId,
            address: info.address,
            clientId: info.clientId,
            appVersion: info.appVersion,
            configureTs: info.configureTs,
            protocolVersion: info.protocolVersion,
            supersede: info.supersede,
            meta: info.meta,
            connectedAt: Date.now(),
            isActivated: false,
            lastPingAt: Date.now(),
            byteStream: {
                parentStreamId: streamId,
                write: (substreamId, chunk) => this.writeToStreamAsync(stream, {
                    byteStreamOperation: { streamId: substreamId, write: { chunk: chunk } }
                }),
                finish: substreamId => this.writeToStream(stream, {
                    byteStreamOperation: { streamId: substreamId, finish: {} }
                }),
                destroy: (substreamId, error) => this.writeToStream(stream, {
                    byteStreamOperation: {
                        streamId: substreamId,
                        destroy: { error: error ? String(error) : undefined }
                    }
                }),
                attachDisconnectHandler: handler => ws.on('close', handler),
                detachDisconnectHandler: handler => ws.off('close', handler),
                getBufferedAmount: () => ws.bufferedAmount
            }
        };
        SrpcByteStream_1.SrpcByteStream.init(stream, { startId: 2, step: 2 });
        return stream;
    }
    handleStreamEstablished(stream) {
        const conflictingStream = this.getCurrentStreamByClientId(stream.clientId);
        if (conflictingStream) {
            if (stream.protocolVersion >= 2 && !stream.supersede) {
                stream.lastPingAt = -1;
                this.closeStreamWithError(stream, 'conflict', 'Client ID already connected');
                return;
            }
            this.cleanupStream(conflictingStream, 'supersede');
        }
        this.streamsById.set(stream.id, stream);
        this.pendingStreamsByClientId.set(stream.clientId, stream);
        this.blockedClientRequests.add(stream);
        this.postEstablishCheck(stream)
            .then(async (rejected) => {
            if (rejected || stream.lastPingAt < 0 || !this.isCurrentStream(stream))
                return;
            stream.$ws.on('message', data => this.handleWsMessage(stream, data));
            this.writeToStream(stream, { pingPong: {} });
            await this.onStreamConnected(stream);
            if (stream.lastPingAt < 0)
                return;
            this.activateStream(stream);
            if (!stream.isActivated)
                return;
            await this.onStreamActivated(stream);
            if (stream.lastPingAt >= 0 && stream.isActivated)
                this.openClientRequests(stream);
        })
            .catch(error => {
            this.logger.error('SRPC connection handler failed', error);
            this.cleanupStream(stream, 'disconnect');
        });
    }
    postEstablishCheck(_stream) {
        return Promise.resolve(false);
    }
    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);
        (0, observer_1.notifySrpcObservers)({ type: 'connection', stream, at: Date.now() });
    }
    handleWsMessage(stream, data) {
        try {
            const decoded = this.options.clientMessage.decode(toBuffer(data));
            this.handleStreamDataReceived(stream, decoded);
        }
        catch (error) {
            this.logger.warn('Failed to decode SRPC message', error);
            this.closeStreamWithError(stream, 'badArg', 'Invalid message format');
        }
    }
    handleStreamDataReceived(stream, data) {
        (0, observer_1.notifySrpcObservers)({ type: 'message', stream, direction: 'inbound', data, at: Date.now() });
        if (data.pingPong) {
            stream.lastPingAt = Date.now();
            this.writeToStream(stream, { pingPong: {} });
            return;
        }
        if (data.byteStreamOperation) {
            this.handleByteSubstreamOperation(stream, data.byteStreamOperation);
            return;
        }
        if (!data.requestId) {
            this.closeStreamWithError(stream, 'badArg', 'Invalid request ID');
            return;
        }
        if (data.reply) {
            const queueItem = stream.$queue.get(data.requestId);
            if (!queueItem) {
                this.closeStreamWithError(stream, 'badArg', 'Unknown request ID');
                return;
            }
            stream.$queue.delete(data.requestId);
            if (data.error)
                queueItem.reject(new types_1.SrpcError(data.error, data.userError));
            else
                queueItem.resolve(data);
            return;
        }
        if (!stream.isActivated || this.blockedClientRequests.has(stream)) {
            const pending = this.pendingClientRequests.get(stream) ?? [];
            pending.push(data);
            this.pendingClientRequests.set(stream, pending);
            return;
        }
        this.handleClientRequest(stream, data.requestId, data)
            .then(response => this.writeToStream(stream, {
            requestId: data.requestId,
            reply: true,
            ...response
        }))
            .catch(error => {
            const isUserError = error instanceof types_1.SrpcError && error.isUserError;
            this.writeToStream(stream, {
                requestId: data.requestId,
                reply: true,
                error: isUserError ? error.message : String(error),
                userError: isUserError
            });
        });
    }
    openClientRequests(stream) {
        this.blockedClientRequests.delete(stream);
        const pending = this.pendingClientRequests.get(stream);
        if (!pending?.length)
            return;
        this.pendingClientRequests.delete(stream);
        for (const message of pending)
            this.handleStreamDataReceived(stream, message);
    }
    handleByteSubstreamOperation(stream, op) {
        if (op.write)
            SrpcByteStream_1.SrpcByteStream.writeReceiver(stream, op.streamId, op.write.chunk);
        else if (op.finish)
            SrpcByteStream_1.SrpcByteStream.finishReceiver(stream, op.streamId);
        else if (op.destroy)
            SrpcByteStream_1.SrpcByteStream.destroySubstream(stream, op.streamId, op.destroy.error);
    }
    async handleClientRequest(stream, _requestId, message) {
        for (const [key, handlerMeta] of this.streamMessageHandlers) {
            const requestData = message[key];
            if (requestData == null)
                continue;
            const result = await this.runMessageHandler(handlerMeta.handler, stream, requestData);
            return { [handlerMeta.resultType]: result };
        }
        throw new Error('Unhandled message type');
    }
    async runMessageHandler(handler, stream, data) {
        if ((0, types_1.isSrpcMessageHandlerClass)(handler)) {
            const instance = new handler();
            return instance.handle(stream, data);
        }
        return handler(stream, data);
    }
    async onStreamConnected(stream) {
        for (const handler of this.streamConnectionHandlers)
            await handler(stream);
    }
    onStreamActivated(_stream) { }
    onStreamDisconnected(stream, cause) {
        for (const handler of this.streamDisconnectionHandlers)
            handler(stream, cause);
    }
    cleanupStream(stream, cause = 'disconnect') {
        if (stream.lastPingAt < 0)
            return;
        stream.lastPingAt = -1;
        for (const queueItem of stream.$queue.values())
            queueItem.reject(new Error('Stream disconnected'));
        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);
        this.onStreamDisconnected(stream, cause);
        (0, observer_1.notifySrpcObservers)({ type: 'disconnection', stream, cause, at: Date.now() });
        if (stream.$ws.readyState === ws_1.default.OPEN || stream.$ws.readyState === ws_1.default.CONNECTING) {
            stream.$ws.close(closeCodeForCause(cause), `Stream terminated with cause: ${cause}`);
        }
    }
    handleStreamDisconnected(stream, code) {
        this.cleanupStream(stream, causeForCloseCode(code));
    }
    handleStreamError(stream, error) {
        this.logger.warn('SRPC stream error', error);
        this.cleanupStream(stream);
    }
    terminateInactiveStreams() {
        const deadline = Date.now() - 75000;
        for (const stream of this.streamsById.values()) {
            if (stream.lastPingAt >= 0 && stream.lastPingAt < deadline)
                this.cleanupStream(stream, 'timeout');
        }
    }
    async validateClientAuth(meta, request) {
        if (this.clientAuthorizer)
            return this.clientAuthorizer(meta, request);
        const authv = String(meta.authv ?? '');
        const appv = String(meta.appv ?? '');
        const ts = String(meta.ts ?? '');
        const id = String(meta.id ?? '');
        const cid = String(meta.cid ?? '');
        const signature = String(meta.signature ?? '');
        if (!authv || !appv || !ts || !id || !cid || !signature)
            return false;
        const tsInt = Number(ts);
        if (!Number.isFinite(tsInt))
            return false;
        const config = getOptionalAppConfig();
        const driftMs = config?.SRPC_AUTH_CLOCK_DRIFT_MS ?? 30000;
        if (Math.abs(Date.now() - tsInt) > driftMs)
            return false;
        const clientKey = await this.fetchClientKey(cid);
        if (clientKey === false)
            return false;
        const computedSignature = (0, node_crypto_1.createHmac)('sha256', clientKey).update(`${authv}\n${appv}\n${ts}\n${id}\n${cid}\n`).digest('hex');
        const signatureBuffer = Buffer.from(signature);
        const computedBuffer = Buffer.from(computedSignature);
        return signatureBuffer.length === computedBuffer.length && (0, node_crypto_1.timingSafeEqual)(signatureBuffer, computedBuffer);
    }
    async fetchClientKey(clientId) {
        if (this.clientKeyFetcher)
            return this.clientKeyFetcher(clientId);
        const key = (0, app_1.getCurrentApp)().config.SRPC_AUTH_SECRET;
        if (!key)
            throw new Error('SRPC_AUTH_SECRET is not configured.');
        return key;
    }
    getRemoteAddress(request) {
        const config = getOptionalAppConfig();
        const realIp = request.headers['x-real-ip'];
        if (config?.USE_REAL_IP_HEADER && typeof realIp === 'string')
            return realIp;
        return request.socket.remoteAddress ?? '127.0.0.1';
    }
    writeToStream(stream, data) {
        if (stream.$ws.readyState !== ws_1.default.OPEN)
            return false;
        stream.$ws.send((0, types_1.encodeSrpcMessage)(this.options.serverMessage, data));
        (0, observer_1.notifySrpcObservers)({ type: 'message', stream, direction: 'outbound', data, at: Date.now() });
        return true;
    }
    writeToStreamAsync(stream, data) {
        if (stream.$ws.readyState !== ws_1.default.OPEN)
            return Promise.reject(new Error('Failed to send SRPC message: not connected'));
        const encoded = (0, types_1.encodeSrpcMessage)(this.options.serverMessage, data);
        return new Promise((resolve, reject) => {
            stream.$ws.send(encoded, error => {
                if (error)
                    reject(error);
                else {
                    (0, observer_1.notifySrpcObservers)({
                        type: 'message',
                        stream,
                        direction: 'outbound',
                        data,
                        at: Date.now()
                    });
                    resolve();
                }
            });
        });
    }
    closeStreamWithError(stream, cause, message) {
        stream.$ws.close(closeCodeForCause(cause), message.slice(0, 123));
    }
    setClientAuthorizer(authorizer) {
        this.clientAuthorizer = authorizer;
    }
    setClientKeyFetcher(fetcher) {
        this.clientKeyFetcher = fetcher;
    }
    registerConnectionHandler(handler) {
        this.streamConnectionHandlers.add(handler);
    }
    registerMessageHandler(prefix, handler) {
        const actionType = `${prefix}Request`;
        this.streamMessageHandlers.set(actionType, {
            resultType: `${prefix}Response`,
            handler: handler
        });
    }
    registerDisconnectHandler(handler) {
        this.streamDisconnectionHandlers.add(handler);
    }
    invoke(stream, prefix, data, timeoutMs = 30000) {
        const requestType = `${prefix}Request`;
        const resultType = `${prefix}Response`;
        const requestId = (0, helpers_1.uuid7)();
        return new Promise((resolve, reject) => {
            const timeout = setTimeout(() => {
                stream.$queue.delete(requestId);
                reject(new Error(`Request timeout after ${timeoutMs}ms`));
            }, timeoutMs);
            stream.$queue.set(requestId, {
                exp: Date.now() + timeoutMs,
                resolve: response => {
                    clearTimeout(timeout);
                    const result = response[resultType];
                    if (result == null)
                        reject(new Error('Invalid response from client'));
                    else
                        resolve(result);
                },
                reject: error => {
                    clearTimeout(timeout);
                    reject(error);
                }
            });
            const sent = this.writeToStream(stream, {
                requestId,
                [requestType]: data
            });
            if (!sent) {
                stream.$queue.delete(requestId);
                clearTimeout(timeout);
                reject(new Error('Failed to send request: not connected'));
            }
        });
    }
    close() {
        clearInterval(this.inactivityCheckInterval);
        this.cleanupUpgradeHandler?.();
        for (const stream of [...this.streamsById.values()])
            this.cleanupStream(stream);
        this.wsServer.close();
    }
    static createInvoke(instanceFn) {
        return ((...args) => instanceFn().invoke(...args));
    }
    static __tsfType = _SrpcServerMetadata_1 = _a.decodeCompactMetadataV1("[1,{\"kind\":16,\"name\":\"SrpcServer\",\"typeName\":\"SrpcServer\",\"classType\":{\"$tsf\":0},\"properties\":[{\"name\":\"streamsById\",\"type\":{\"$tsfType\":0},\"optional\":false},{\"name\":\"streamsByClientId\",\"type\":{\"$tsfType\":0},\"optional\":false},{\"name\":\"pendingStreamsByClientId\",\"type\":{\"$tsfType\":0},\"optional\":false}],\"methods\":[{\"name\":\"postEstablishCheck\",\"parameters\":[{\"name\":\"_stream\",\"type\":{\"kind\":16,\"typeName\":\"SrpcStream\",\"classType\":{\"$tsfImport\":[1,\"./types\",\"SrpcStream\"]},\"typeArguments\":[{\"kind\":16,\"typeName\":\"TMeta\",\"classType\":{\"$tsf\":2}}]},\"optional\":false,\"default\":false}],\"returnType\":{\"$tsfType\":1}},{\"name\":\"getCurrentStreamByClientId\",\"parameters\":[{\"name\":\"clientId\",\"type\":{\"$tsfType\":2},\"optional\":false,\"default\":false}],\"returnType\":{\"kind\":12,\"types\":[{\"kind\":16,\"typeName\":\"SrpcStream\",\"classType\":{\"$tsfImport\":[1,\"./types\",\"SrpcStream\"]},\"typeArguments\":[{\"kind\":16,\"typeName\":\"TMeta\",\"classType\":{\"$tsf\":2}}]},{\"kind\":4}]}},{\"name\":\"isCurrentStream\",\"parameters\":[{\"name\":\"stream\",\"type\":{\"kind\":16,\"typeName\":\"SrpcStream\",\"classType\":{\"$tsfImport\":[1,\"./types\",\"SrpcStream\"]},\"typeArguments\":[{\"kind\":16,\"typeName\":\"TMeta\",\"classType\":{\"$tsf\":2}}]},\"optional\":false,\"default\":false}],\"returnType\":{\"$tsfType\":3}},{\"name\":\"runMessageHandler\",\"parameters\":[{\"name\":\"handler\",\"type\":{\"kind\":12,\"types\":[{\"kind\":2,\"typeName\":\"SrpcMessageHandlerFn\",\"typeArguments\":[{\"kind\":16,\"typeName\":\"SrpcStream\",\"classType\":{\"$tsf\":3},\"typeArguments\":[{\"kind\":16,\"typeName\":\"TMeta\",\"classType\":{\"$tsf\":2}}]},{\"kind\":2},{\"kind\":2}]},{\"kind\":2,\"typeName\":\"ClassType\",\"typeArguments\":[{\"kind\":16,\"typeName\":\"ISrpcMessageHandler\",\"classType\":{\"$tsf\":4},\"typeArguments\":[{\"kind\":16,\"typeName\":\"SrpcStream\",\"classType\":{\"$tsf\":3},\"typeArguments\":[{\"kind\":16,\"typeName\":\"TMeta\",\"classType\":{\"$tsf\":2}}]},{\"kind\":2},{\"kind\":2}]}],\"typeName\":\"TSrpcMessageHandlerClass\"}],\"typeName\":\"TSrpcMessageHandlerFnOrClass\"},\"optional\":false,\"default\":false},{\"name\":\"stream\",\"type\":{\"kind\":16,\"typeName\":\"SrpcStream\",\"classType\":{\"$tsfImport\":[1,\"./types\",\"SrpcStream\"]},\"typeArguments\":[{\"kind\":16,\"typeName\":\"TMeta\",\"classType\":{\"$tsf\":2}}]},\"optional\":false,\"default\":false},{\"name\":\"data\",\"type\":{\"$tsfType\":0},\"optional\":false,\"default\":false}],\"returnType\":{\"$tsfType\":4}},{\"name\":\"onStreamConnected\",\"parameters\":[{\"name\":\"stream\",\"type\":{\"kind\":16,\"typeName\":\"SrpcStream\",\"classType\":{\"$tsfImport\":[1,\"./types\",\"SrpcStream\"]},\"typeArguments\":[{\"kind\":16,\"typeName\":\"TMeta\",\"classType\":{\"$tsf\":2}}]},\"optional\":false,\"default\":false}],\"returnType\":{\"$tsfType\":5}},{\"name\":\"onStreamActivated\",\"parameters\":[{\"name\":\"_stream\",\"type\":{\"kind\":16,\"typeName\":\"SrpcStream\",\"classType\":{\"$tsfImport\":[1,\"./types\",\"SrpcStream\"]},\"typeArguments\":[{\"kind\":16,\"typeName\":\"TMeta\",\"classType\":{\"$tsf\":2}}]},\"optional\":false,\"default\":false}],\"returnType\":{\"$tsfType\":6}},{\"name\":\"onStreamDisconnected\",\"parameters\":[{\"name\":\"stream\",\"type\":{\"kind\":16,\"typeName\":\"SrpcStream\",\"classType\":{\"$tsfImport\":[1,\"./types\",\"SrpcStream\"]},\"typeArguments\":[{\"kind\":16,\"typeName\":\"TMeta\",\"classType\":{\"$tsf\":2}}]},\"optional\":false,\"default\":false},{\"name\":\"cause\",\"type\":{\"$tsfType\":7},\"optional\":false,\"default\":false}],\"returnType\":{\"$tsfType\":8}},{\"name\":\"cleanupStream\",\"parameters\":[{\"name\":\"stream\",\"type\":{\"kind\":16,\"typeName\":\"SrpcStream\",\"classType\":{\"$tsfImport\":[1,\"./types\",\"SrpcStream\"]},\"typeArguments\":[{\"kind\":16,\"typeName\":\"TMeta\",\"classType\":{\"$tsf\":2}}]},\"optional\":false,\"default\":false},{\"name\":\"cause\",\"type\":{\"$tsfType\":7},\"optional\":true,\"default\":true}],\"returnType\":{\"$tsfType\":8}},{\"name\":\"setClientAuthorizer\",\"parameters\":[{\"name\":\"authorizer\",\"type\":{\"$tsfType\":9},\"optional\":false,\"default\":false}],\"returnType\":{\"$tsfType\":8}},{\"name\":\"setClientKeyFetcher\",\"parameters\":[{\"name\":\"fetcher\",\"type\":{\"$tsfType\":10},\"optional\":false,\"default\":false}],\"returnType\":{\"$tsfType\":8}},{\"name\":\"registerConnectionHandler\",\"parameters\":[{\"name\":\"handler\",\"type\":{\"$tsfType\":11},\"optional\":false,\"default\":false}],\"returnType\":{\"$tsfType\":8}},{\"name\":\"registerMessageHandler\",\"parameters\":[{\"name\":\"prefix\",\"type\":{\"$tsfType\":12},\"optional\":false,\"default\":false},{\"name\":\"handler\",\"type\":{\"kind\":12,\"types\":[{\"kind\":2,\"typeName\":\"SrpcMessageHandlerFn\",\"typeArguments\":[{\"kind\":16,\"typeName\":\"SrpcStream\",\"classType\":{\"$tsf\":3},\"typeArguments\":[{\"kind\":16,\"typeName\":\"TMeta\",\"classType\":{\"$tsf\":2}}]},{\"kind\":2,\"typeName\":\"HandlerRequestData\",\"typeArguments\":[{\"kind\":16,\"typeName\":\"TClientOutput\",\"classType\":{\"$tsf\":5}},{\"kind\":2,\"typeName\":\"P\"}]},{\"kind\":2,\"typeName\":\"ResponseData\",\"typeArguments\":[{\"kind\":16,\"typeName\":\"TServerOutput\",\"classType\":{\"$tsf\":6}},{\"kind\":2,\"typeName\":\"P\"}]}]},{\"kind\":2,\"typeName\":\"ClassType\",\"typeArguments\":[{\"kind\":16,\"typeName\":\"ISrpcMessageHandler\",\"classType\":{\"$tsf\":4},\"typeArguments\":[{\"kind\":16,\"typeName\":\"SrpcStream\",\"classType\":{\"$tsf\":3},\"typeArguments\":[{\"kind\":16,\"typeName\":\"TMeta\",\"classType\":{\"$tsf\":2}}]},{\"kind\":2,\"typeName\":\"HandlerRequestData\",\"typeArguments\":[{\"kind\":16,\"typeName\":\"TClientOutput\",\"classType\":{\"$tsf\":5}},{\"kind\":2,\"typeName\":\"P\"}]},{\"kind\":2,\"typeName\":\"ResponseData\",\"typeArguments\":[{\"kind\":16,\"typeName\":\"TServerOutput\",\"classType\":{\"$tsf\":6}},{\"kind\":2,\"typeName\":\"P\"}]}]}],\"typeName\":\"TSrpcMessageHandlerClass\"}],\"typeName\":\"TSrpcMessageHandlerFnOrClass\"},\"optional\":false,\"default\":false}],\"returnType\":{\"$tsfType\":8}},{\"name\":\"registerDisconnectHandler\",\"parameters\":[{\"name\":\"handler\",\"type\":{\"$tsfType\":13},\"optional\":false,\"default\":false}],\"returnType\":{\"$tsfType\":8}},{\"name\":\"invoke\",\"parameters\":[{\"name\":\"stream\",\"type\":{\"kind\":16,\"typeName\":\"SrpcStream\",\"classType\":{\"$tsfImport\":[1,\"./types\",\"SrpcStream\"]},\"typeArguments\":[{\"kind\":16,\"typeName\":\"TMeta\",\"classType\":{\"$tsf\":2}}]},\"optional\":false,\"default\":false},{\"name\":\"prefix\",\"type\":{\"$tsfType\":12},\"optional\":false,\"default\":false},{\"name\":\"data\",\"type\":{\"$tsfType\":14},\"optional\":false,\"default\":false},{\"name\":\"timeoutMs\",\"type\":{\"$tsfType\":0},\"optional\":true,\"default\":true}],\"returnType\":{\"$tsfType\":15}},{\"name\":\"close\",\"parameters\":[],\"returnType\":{\"$tsfType\":8}}],\"hasConstructor\":true,\"constructorParameters\":[{\"name\":\"options\",\"type\":{\"kind\":16,\"typeName\":\"ISrpcServerOptions\",\"classType\":{\"$tsfImport\":[1,\"./types\",\"ISrpcServerOptions\"]},\"typeArguments\":[{\"kind\":16,\"typeName\":\"TClientOutput\",\"classType\":{\"$tsf\":5}},{\"kind\":16,\"typeName\":\"TServerOutput\",\"classType\":{\"$tsf\":6}}]},\"optional\":false,\"default\":false}]}]", [() => SrpcServer, require, () => (typeof TMeta !== "undefined" ? TMeta : (typeof exports !== "undefined" ? exports.TMeta : undefined)), () => (typeof SrpcStream !== "undefined" ? SrpcStream : (typeof exports !== "undefined" ? exports.SrpcStream : undefined)), () => (typeof ISrpcMessageHandler !== "undefined" ? ISrpcMessageHandler : (typeof exports !== "undefined" ? exports.ISrpcMessageHandler : undefined)), () => (typeof TClientOutput !== "undefined" ? TClientOutput : (typeof exports !== "undefined" ? exports.TClientOutput : undefined)), () => (typeof TServerOutput !== "undefined" ? TServerOutput : (typeof exports !== "undefined" ? exports.TServerOutput : undefined))], __tsf_metadata_type);
}
exports.SrpcServer = SrpcServer;
_SrpcServerMetadata_1.classType = SrpcServer;
SrpcServer.__tsfType = _SrpcServerMetadata_1;
function createLogger(logger, logLevel) {
    if (logLevel === false)
        return noopLogger;
    if (logLevel === 'debug') {
        return {
            info: logger.debug.bind(logger),
            warn: logger.warn.bind(logger),
            error: logger.error.bind(logger),
            debug: logger.debug.bind(logger)
        };
    }
    return logger;
}
function toBuffer(data) {
    if (Buffer.isBuffer(data))
        return data;
    if (Array.isArray(data))
        return Buffer.concat(data);
    return Buffer.from(data);
}
function closeCodeForCause(cause) {
    return { disconnect: 1000, badArg: 4000, conflict: 4001, supersede: 4002, timeout: 4003 }[cause];
}
function causeForCloseCode(code) {
    if (code === 4000)
        return 'badArg';
    if (code === 4001)
        return 'conflict';
    if (code === 4002)
        return 'supersede';
    if (code === 4003)
        return 'timeout';
    return 'disconnect';
}
function getOptionalAppConfig() {
    try {
        return (0, app_1.getCurrentApp)().config;
    }
    catch {
        return undefined;
    }
}
//# sourceMappingURL=SrpcServer.js.map