UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

131 lines (130 loc) 5.24 kB
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 };