"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.installDevConsoleObservers = installDevConsoleObservers;
const helpers_1 = require("../helpers");
const database_1 = require("../database");
const mutex_1 = require("../helpers/redis/mutex");
const srpc_1 = require("../srpc");
const worker_1 = require("../services/worker");
const store_1 = require("./store");
function installDevConsoleObservers(app, store) {
    const cleanup = [
        app.http.registerObserver(entry => recordHttpObservation(store, entry)),
        (0, database_1.registerDatabaseQueryObserver)(entry => {
            const query = {
                id: entry.id,
                timestamp: entry.startedAt,
                sql: entry.sql,
                params: entry.bindings,
                status: entry.phase === 'start' ? 'running' : entry.error ? 'error' : 'ok',
                durationMs: entry.phase === 'finish' ? entry.durationMs : undefined,
                error: entry.error instanceof Error ? entry.error.message : entry.error ? String(entry.error) : undefined
            };
            if (entry.phase === 'start')
                store.addDatabaseQuery(query);
            else
                store.completeDatabaseQuery(query);
        }),
        (0, worker_1.registerWorkerObserver)(entry => recordWorkerObservation(store, entry)),
        (0, mutex_1.registerMutexObserver)(entry => recordMutexObservation(store, entry)),
        (0, srpc_1.registerSrpcObserver)(entry => recordSrpcObservation(store, entry))
    ];
    return () => {
        for (const item of cleanup.reverse())
            item();
    };
}
function recordHttpObservation(store, entry) {
    if (entry.request.path.startsWith('/_devconsole'))
        return;
    store.addHttpEntry({
        id: (0, helpers_1.uuid7)(),
        timestamp: entry.startedAt,
        method: entry.request.method,
        url: entry.request.url,
        remoteAddress: entry.request.getRemoteAddress(),
        requestHeaders: entry.request.headers,
        requestBody: bufferToBoundedText(entry.request.body),
        statusCode: entry.response.statusCode,
        responseHeaders: entry.response.headers,
        responseBody: bufferToBoundedText(entry.response.body),
        durationMs: entry.durationMs,
        error: (0, store_1.serializeErrorInfo)(entry.error)
    });
}
function recordWorkerObservation(store, entry) {
    if (entry.type === 'completed' || entry.type === 'failed') {
        store.broadcastWorkerEvent('job', {
            id: entry.record.id,
            queue: entry.record.queue,
            queueId: entry.record.queueId,
            name: entry.record.name,
            data: entry.record.data,
            status: entry.record.status,
            result: entry.record.result,
            attempt: entry.record.attempt,
            traceId: entry.record.traceId,
            createdAt: entry.record.createdAt.getTime(),
            shouldExecuteAt: entry.record.shouldExecuteAt.getTime(),
            executedAt: entry.record.executedAt.getTime(),
            completedAt: entry.record.completedAt.getTime()
        });
        return;
    }
    store.broadcastWorkerEvent(entry.type, {
        id: entry.job.id,
        queue: entry.job.queue,
        name: entry.job.name,
        status: entry.type
    });
}
function recordMutexObservation(store, entry) {
    if (entry.type === 'pending') {
        store.addMutexPending({
            id: entry.id,
            key: entry.key,
            status: 'pending',
            startedAt: entry.startedAt
        });
    }
    else if (entry.type === 'acquired') {
        store.updateMutexAcquired(entry.id, entry.waited, entry.at);
    }
    else if (entry.type === 'released') {
        store.updateMutexReleased(entry.id, entry.at);
    }
    else if (entry.type === 'error') {
        store.updateMutexError(entry.id, entry.error, entry.at);
    }
    else {
        store.addMutexFailed({
            id: entry.id,
            key: entry.key,
            status: 'failed',
            startedAt: entry.startedAt,
            releasedAt: entry.at,
            durationMs: entry.at - entry.startedAt,
            waited: entry.waited,
            error: entry.error instanceof Error ? entry.error.message : entry.error ? String(entry.error) : undefined
        });
    }
}
function recordSrpcObservation(store, entry) {
    const meta = entry.stream.meta;
    if (meta.devconsole === true)
        return;
    if (entry.type === 'connection') {
        store.addSrpcConnection({
            streamId: entry.stream.id,
            clientId: entry.stream.clientId,
            clientStreamId: entry.stream.clientStreamId,
            appVersion: entry.stream.appVersion,
            address: entry.stream.address,
            connectedAt: entry.stream.connectedAt,
            lastPingAt: entry.stream.lastPingAt,
            meta,
            messageCount: 0
        });
        return;
    }
    if (entry.type === 'disconnection') {
        store.removeSrpcConnection(entry.stream.id, entry.stream.clientId, entry.cause);
        return;
    }
    const messageType = getSrpcMessageType(entry.data);
    if (!messageType || messageType === 'pingPong')
        return;
    store.addSrpcMessage({
        id: String(entry.data.requestId ?? (0, helpers_1.uuid7)()),
        timestamp: entry.at,
        streamId: entry.stream.id,
        clientId: entry.stream.clientId,
        direction: entry.direction,
        messageType,
        isReply: entry.data.reply === true,
        data: toRecord(entry.data),
        error: entry.data.error,
        isUserError: entry.data.userError
    });
}
function getSrpcMessageType(message) {
    for (const [key, value] of Object.entries(message)) {
        if (value == null)
            continue;
        if (['requestId', 'reply', 'error', 'userError', 'trace'].includes(key))
            continue;
        return key.replace(/Request$|Response$/, '');
    }
    return undefined;
}
function toRecord(value) {
    return value && typeof value === 'object' ? value : { value };
}
function bufferToBoundedText(body) {
    if (!body?.length)
        return null;
    const maxBytes = 64 * 1024;
    const prefix = body.subarray(0, maxBytes).toString('utf8');
    return body.length > maxBytes ? `${prefix}\n... truncated ${body.length - maxBytes} byte(s)` : prefix;
}
//# sourceMappingURL=observers.js.map