openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
131 lines (130 loc) • 5.24 kB
JavaScript
import { j as resolveIntegerOption } from "./number-coercion-CLj0HTDM.js";
import { t as pruneMapToMaxSize } from "./map-size-CNcWiFKu.js";
import { y as runOutsideGatewayRootWorkAdmission } from "./gateway-work-admission-R1IpuDim.js";
import { a as MAX_PAYLOAD_BYTES } from "./server-constants-BrVEC7RW.js";
import { t as BoundedSerialQueue } from "./bounded-serial-queue-3JaVUeMO.js";
import { performance } from "node:perf_hooks";
import { setImmediate } from "node:timers/promises";
//#region src/gateway/server/ws-connection/handshake-auth-log-limiter.ts
/** Per-key log limiter that reports suppressed auth attempts on the next emitted log. */
var HandshakeAuthLogLimiter = class {
constructor(options) {
this.entries = /* @__PURE__ */ new Map();
this.intervalMs = resolveIntegerOption(options?.intervalMs, 3e4, { min: 1 });
this.maxEntries = resolveIntegerOption(options?.maxEntries, 256, { min: 1 });
}
/** Register one auth event key and decide whether it should be logged now. */
register(key, nowMs = Date.now()) {
const entry = this.entries.get(key);
if (!entry) {
pruneMapToMaxSize(this.entries, this.maxEntries - 1);
this.entries.set(key, {
lastLoggedAtMs: nowMs,
suppressedSinceLastLog: 0
});
return {
shouldLog: true,
suppressedSinceLastLog: 0
};
}
if (nowMs - entry.lastLoggedAtMs < this.intervalMs) {
entry.suppressedSinceLastLog += 1;
return {
shouldLog: false,
suppressedSinceLastLog: 0
};
}
const suppressedSinceLastLog = entry.suppressedSinceLastLog;
entry.lastLoggedAtMs = nowMs;
entry.suppressedSinceLastLog = 0;
return {
shouldLog: true,
suppressedSinceLastLog
};
}
};
/** Build the limiter key from auth failure context. */
function buildHandshakeAuthLogKey(params) {
return [
params.reason ?? "unknown",
params.remoteAddr ?? "?",
params.client ?? "?",
params.mode ?? "?",
params.authProvided ?? "?"
].join("|");
}
/** Return whether a missing-credential failure should use log rate limiting. */
function shouldLimitMissingCredentialAuthLog(params) {
return params.authProvided === "none" && (params.reason === "token_missing" || params.reason === "password_missing");
}
//#endregion
//#region src/gateway/server/ws-connection/request-start.ts
const PERMESSAGE_DEFLATE_EXTENSION = "permessage-deflate";
function hasWritablePayloadLimit(target) {
return typeof target?.["_maxPayload"] === "number" && Object.getOwnPropertyDescriptor(target, "_maxPayload")?.writable === true;
}
/**
* Resolves the ws receiver and every payload limit an authenticated frame passes through.
* A negotiated permessage-deflate extension checks inflated size against its own copy of
* the server maxPayload, so raising only the receiver would still reject compressed
* post-auth frames above the preauth cap. Null when any limit is not writable.
*/
function gatewayReceiverPayloadLimits(socket) {
const receiver = socket["_receiver"];
if (!hasWritablePayloadLimit(receiver)) return null;
const deflate = receiver["_extensions"]?.[PERMESSAGE_DEFLATE_EXTENSION];
if (deflate === void 0) return {
receiver,
limits: [receiver]
};
return hasWritablePayloadLimit(deflate) ? {
receiver,
limits: [receiver, deflate]
} : null;
}
/** Raises the authenticated frame limit on the receiver and its deflate extension together. */
function raiseGatewayReceiverPayloadLimit(socket, maxPayload) {
const resolved = gatewayReceiverPayloadLimits(socket);
if (!resolved) return false;
for (const limit of resolved.limits) limit["_maxPayload"] = maxPayload;
return true;
}
function prepareGatewayReceiverHandoff(socket, role) {
const resolved = gatewayReceiverPayloadLimits(socket);
if (!resolved) return null;
const { receiver, limits } = resolved;
if (role === "operator" && (typeof receiver["_allowSynchronousEvents"] !== "boolean" || Object.getOwnPropertyDescriptor(receiver, "_allowSynchronousEvents")?.writable !== true)) return null;
return () => {
for (const limit of limits) limit["_maxPayload"] = MAX_PAYLOAD_BYTES;
if (role === "operator") receiver["_allowSynchronousEvents"] = true;
};
}
const requestStarts = new BoundedSerialQueue({
maxPendingCount: 256,
maxPendingWeight: 52428800
});
const MAX_STARTS_PER_TURN = 64;
const START_WORK_BUDGET_MS = 12;
let turnStartedAt = 0;
let turnStarts = 0;
/** Grants operator router-start permission, or null when its waiting budget is exhausted. */
function scheduleGatewayRequestStart(frameBytes) {
return runOutsideGatewayRootWorkAdmission(() => {
const wasIdle = requestStarts.isIdle;
const admission = requestStarts.enqueue(async () => {
await new Promise(queueMicrotask);
if (wasIdle || turnStarts >= MAX_STARTS_PER_TURN || performance.now() - turnStartedAt >= START_WORK_BUDGET_MS) {
await setImmediate();
turnStartedAt = performance.now();
turnStarts = 0;
}
turnStarts++;
}, {
weight: frameBytes,
sealOnOverflow: false
});
return admission.accepted ? admission.completion : null;
});
}
//#endregion
export { buildHandshakeAuthLogKey as a, HandshakeAuthLogLimiter as i, raiseGatewayReceiverPayloadLimit as n, shouldLimitMissingCredentialAuthLog as o, scheduleGatewayRequestStart as r, prepareGatewayReceiverHandoff as t };