perf(relay): reduce encrypted binary traffic overhead (#2480)
Preserve application frame identity through encryption so negotiated binary traffic avoids base64 expansion while mixed-version peers remain compatible.
This commit is contained in:
@@ -8,15 +8,12 @@ import type {
|
||||
DaemonTransportFactory,
|
||||
TransportLogger,
|
||||
} from "./daemon-client-transport-types.js";
|
||||
import {
|
||||
extractRelayMessageData,
|
||||
normalizeTransportPayload,
|
||||
} from "./daemon-client-transport-utils.js";
|
||||
import { extractRelayMessage, normalizeTransportPayload } from "./daemon-client-transport-utils.js";
|
||||
|
||||
type OpenHandler = () => void;
|
||||
type CloseHandler = (event?: unknown) => void;
|
||||
type ErrorHandler = (event?: unknown) => void;
|
||||
type MessageHandler = (data: unknown) => void;
|
||||
type MessageHandler = (data: unknown, isBinary: boolean) => void;
|
||||
|
||||
export function createRelayE2eeTransportFactory(args: {
|
||||
baseFactory: DaemonTransportFactory;
|
||||
@@ -70,7 +67,7 @@ export function createEncryptedTransport(
|
||||
if (closed) {
|
||||
return;
|
||||
}
|
||||
emitHandlers(messageHandlers, data);
|
||||
emitHandlers(messageHandlers, data, data instanceof ArrayBuffer);
|
||||
};
|
||||
|
||||
const relayTransport: RelayTransport = {
|
||||
@@ -115,8 +112,8 @@ export function createEncryptedTransport(
|
||||
base.onOpen(() => {
|
||||
void startHandshake();
|
||||
});
|
||||
base.onMessage((event) => {
|
||||
relayTransport.onmessage?.(extractRelayMessageData(event));
|
||||
base.onMessage((data, isBinary) => {
|
||||
relayTransport.onmessage?.(extractRelayMessage(data, isBinary));
|
||||
});
|
||||
base.onClose((event) => {
|
||||
const record = event as { code?: number; reason?: string } | undefined;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
export interface DaemonTransport {
|
||||
send: (data: string | Uint8Array | ArrayBuffer) => void;
|
||||
close: (code?: number, reason?: string) => void;
|
||||
onMessage: (handler: (data: unknown) => void) => () => void;
|
||||
onMessage: (handler: (data: unknown, isBinary: boolean) => void) => () => void;
|
||||
onOpen: (handler: () => void) => () => void;
|
||||
onClose: (handler: (event?: unknown) => void) => () => void;
|
||||
onError: (handler: (event?: unknown) => void) => () => void;
|
||||
|
||||
@@ -14,17 +14,25 @@ export function normalizeTransportPayload(
|
||||
return copyArrayBufferViewToBuffer(data);
|
||||
}
|
||||
|
||||
export function extractRelayMessageData(event: unknown): string | ArrayBuffer {
|
||||
export interface RelayTransportMessage {
|
||||
data: string | ArrayBuffer;
|
||||
isBinary: boolean;
|
||||
}
|
||||
|
||||
export function extractRelayMessage(event: unknown, nodeIsBinary?: boolean): RelayTransportMessage {
|
||||
const raw =
|
||||
event && typeof event === "object" && "data" in event
|
||||
? (event as { data: unknown }).data
|
||||
: event;
|
||||
if (typeof raw === "string") return raw;
|
||||
if (raw instanceof ArrayBuffer) return raw;
|
||||
if (ArrayBuffer.isView(raw)) {
|
||||
return copyArrayBufferViewToBuffer(raw);
|
||||
const isBinary = nodeIsBinary ?? typeof raw !== "string";
|
||||
if (!isBinary) {
|
||||
return { data: decodeMessageData(raw) ?? String(raw ?? ""), isBinary: false };
|
||||
}
|
||||
return String(raw ?? "");
|
||||
if (raw instanceof ArrayBuffer) return { data: raw, isBinary: true };
|
||||
if (ArrayBuffer.isView(raw)) {
|
||||
return { data: copyArrayBufferViewToBuffer(raw), isBinary: true };
|
||||
}
|
||||
return { data: String(raw ?? ""), isBinary: true };
|
||||
}
|
||||
|
||||
export function describeTransportClose(event?: unknown): string {
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
describeTransportClose,
|
||||
describeTransportError,
|
||||
encodeUtf8String,
|
||||
extractRelayMessageData,
|
||||
extractRelayMessage,
|
||||
} from "./daemon-client-transport.js";
|
||||
|
||||
const createClientChannelMock = vi.hoisted(() => vi.fn());
|
||||
@@ -130,7 +130,7 @@ describe("daemon-client transport helpers", () => {
|
||||
|
||||
const message = { data: "payload" };
|
||||
listeners.get("message")?.(message);
|
||||
expect(onMessage).toHaveBeenCalledWith(message);
|
||||
expect(onMessage).toHaveBeenCalledWith("payload", false);
|
||||
|
||||
unsubscribe();
|
||||
expect(ws.removeEventListener).toHaveBeenCalledWith("message", expect.any(Function));
|
||||
@@ -165,13 +165,16 @@ describe("daemon-client transport helpers", () => {
|
||||
expect(describeTransportError()).toBe("Transport error");
|
||||
});
|
||||
|
||||
test("extractRelayMessageData returns strings and array buffers", () => {
|
||||
expect(extractRelayMessageData({ data: "hello" })).toBe("hello");
|
||||
test("extractRelayMessage preserves browser and Node WebSocket frame kind", () => {
|
||||
expect(extractRelayMessage({ data: "hello" })).toEqual({ data: "hello", isBinary: false });
|
||||
|
||||
const view = new Uint8Array([1, 2, 3]);
|
||||
const extracted = extractRelayMessageData({ data: view });
|
||||
expect(extracted).toBeInstanceOf(ArrayBuffer);
|
||||
expect(Array.from(new Uint8Array(extracted as ArrayBuffer))).toEqual([1, 2, 3]);
|
||||
const binary = extractRelayMessage(view, true);
|
||||
expect(binary.isBinary).toBe(true);
|
||||
expect(Array.from(new Uint8Array(binary.data as ArrayBuffer))).toEqual([1, 2, 3]);
|
||||
|
||||
const text = extractRelayMessage(view, false);
|
||||
expect(text).toEqual({ data: "\u0001\u0002\u0003", isBinary: false });
|
||||
});
|
||||
|
||||
test("decodeMessageData decodes strings, array buffers, and typed arrays", () => {
|
||||
|
||||
@@ -10,7 +10,7 @@ export {
|
||||
describeTransportClose,
|
||||
describeTransportError,
|
||||
encodeUtf8String,
|
||||
extractRelayMessageData,
|
||||
extractRelayMessage,
|
||||
normalizeTransportPayload,
|
||||
safeRandomId,
|
||||
} from "./daemon-client-transport-utils.js";
|
||||
|
||||
@@ -3,6 +3,7 @@ import type {
|
||||
WebSocketFactory,
|
||||
WebSocketLike,
|
||||
} from "./daemon-client-transport-types.js";
|
||||
import { extractRelayMessage } from "./daemon-client-transport-utils.js";
|
||||
|
||||
export function defaultWebSocketFactory(
|
||||
url: string,
|
||||
@@ -52,11 +53,25 @@ export function createWebSocketTransportFactory(factory: WebSocketFactory): Daem
|
||||
onOpen: (handler) => bindWsHandler(ws, "open", handler),
|
||||
onClose: (handler) => bindWsHandler(ws, "close", handler),
|
||||
onError: (handler) => bindWsHandler(ws, "error", handler),
|
||||
onMessage: (handler) => bindWsHandler(ws, "message", handler),
|
||||
onMessage: (handler) => bindWsMessageHandler(ws, handler),
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
function bindWsMessageHandler(
|
||||
ws: WebSocketLike,
|
||||
handler: (data: unknown, isBinary: boolean) => void,
|
||||
): () => void {
|
||||
const listener = (...args: unknown[]) => {
|
||||
const message = extractRelayMessage(
|
||||
args[0],
|
||||
typeof args[1] === "boolean" ? args[1] : undefined,
|
||||
);
|
||||
handler(message.data, message.isBinary);
|
||||
};
|
||||
return bindWsHandler(ws, "message", listener);
|
||||
}
|
||||
|
||||
function bindTemporaryEarlyCloseErrorHandler(ws: WebSocketLike): () => void {
|
||||
const noop = () => {};
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ class FakeWebSocket {
|
||||
}
|
||||
|
||||
message(data: string): void {
|
||||
this.onmessage?.(data);
|
||||
this.onmessage?.({ data });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user