mirror of
https://github.com/multica-ai/multica.git
synced 2026-06-17 03:38:32 +02:00
fix(ws): truncate unparseable frame payload in client warn log (#2974)
The post-#2946 onmessage guard logs the raw event.data alongside the warning. A malformed or rogue server can stream arbitrarily large garbage and bloat the renderer / desktop main-process log buffers, so cap the logged payload to the first 200 chars and append a "(truncated, N chars total)" suffix when truncation occurs. MUL-2490 Co-authored-by: multica-agent <github@multica.ai>
This commit is contained in:
@@ -73,6 +73,27 @@ describe("WSClient", () => {
|
||||
expect(url.searchParams.has("client_os")).toBe(false);
|
||||
});
|
||||
|
||||
it("truncates the logged payload when an unparseable frame is large", () => {
|
||||
const logger = {
|
||||
debug: vi.fn(),
|
||||
info: vi.fn(),
|
||||
warn: vi.fn(),
|
||||
error: vi.fn(),
|
||||
};
|
||||
const ws = new WSClient("ws://example.test/ws", { logger });
|
||||
ws.connect();
|
||||
|
||||
const huge = "x".repeat(5000);
|
||||
FakeWebSocket.lastInstance!.onmessage?.({ data: huge });
|
||||
|
||||
expect(logger.warn).toHaveBeenCalledTimes(1);
|
||||
const [, summary] = logger.warn.mock.calls[0] as [string, string];
|
||||
expect(summary.length).toBeLessThan(huge.length);
|
||||
expect(summary).toContain("truncated");
|
||||
expect(summary).toContain("5000");
|
||||
expect(summary.startsWith("x".repeat(200))).toBe(true);
|
||||
});
|
||||
|
||||
it("logs and skips malformed frames without breaking later messages", () => {
|
||||
const logger = {
|
||||
debug: vi.fn(),
|
||||
|
||||
@@ -3,6 +3,17 @@ import { type Logger, noopLogger } from "../logger";
|
||||
|
||||
type EventHandler = (payload: unknown, actorId?: string, actorType?: string) => void;
|
||||
|
||||
// Cap how much of an unparseable frame we put into the log. A malformed or
|
||||
// rogue server can stream arbitrarily large garbage, and the warn handler may
|
||||
// be a console / IPC bridge whose buffers we don't want to blow.
|
||||
const UNPARSEABLE_LOG_MAX_CHARS = 200;
|
||||
|
||||
function summarizeUnparseable(data: unknown): string {
|
||||
const text = typeof data === "string" ? data : String(data);
|
||||
if (text.length <= UNPARSEABLE_LOG_MAX_CHARS) return text;
|
||||
return `${text.slice(0, UNPARSEABLE_LOG_MAX_CHARS)}… (truncated, ${text.length} chars total)`;
|
||||
}
|
||||
|
||||
/** Identifies the WS client to the server. Sent as `client_platform`,
|
||||
* `client_version`, and `client_os` query parameters on the upgrade URL —
|
||||
* browsers cannot set custom headers on WebSocket handshakes, so query
|
||||
@@ -79,7 +90,10 @@ export class WSClient {
|
||||
try {
|
||||
msg = JSON.parse(event.data as string) as WSMessage;
|
||||
} catch {
|
||||
this.logger.warn("ws: received unparseable message", event.data);
|
||||
this.logger.warn(
|
||||
"ws: received unparseable message",
|
||||
summarizeUnparseable(event.data),
|
||||
);
|
||||
return;
|
||||
}
|
||||
if ((msg as any).type === "auth_ack") {
|
||||
|
||||
Reference in New Issue
Block a user