fix: stabilize relay E2EE handshake
This commit is contained in:
@@ -152,6 +152,12 @@ describe("Relay transport (E2EE) - daemon E2E", () => {
|
||||
});
|
||||
|
||||
expect(received).toEqual({ type: "pong" });
|
||||
} catch (err) {
|
||||
const tail = lines.slice(-50).join("");
|
||||
// Only prints on failure to help diagnose relay handshake issues.
|
||||
// eslint-disable-next-line no-console
|
||||
console.error("daemon logs (tail):\n", tail);
|
||||
throw err;
|
||||
} finally {
|
||||
await daemon.close();
|
||||
await relay.stop();
|
||||
|
||||
@@ -66,7 +66,7 @@ export async function loadOrCreateDaemonKeyPair(
|
||||
privateKeyJwk: privateKeyJwk as Record<string, unknown>,
|
||||
};
|
||||
|
||||
writeFileSync(filePath, JSON.stringify(payload, null, 2) + "\n");
|
||||
writeFileSync(filePath, JSON.stringify(payload, null, 2) + "\n", { mode: 0o600 });
|
||||
log?.info({ filePath }, "Saved daemon keypair");
|
||||
|
||||
return { keyPair, publicKeyB64 };
|
||||
|
||||
@@ -149,8 +149,8 @@ function createRelayTransportAdapter(socket: WebSocket): RelayTransport {
|
||||
onerror: null,
|
||||
};
|
||||
|
||||
socket.on("message", (data) => {
|
||||
relayTransport.onmessage?.(normalizeMessageData(data));
|
||||
socket.on("message", (data, isBinary) => {
|
||||
relayTransport.onmessage?.(normalizeMessageData(data, isBinary));
|
||||
});
|
||||
socket.on("close", (code, reason) => {
|
||||
relayTransport.onclose?.(code, reason.toString());
|
||||
@@ -200,24 +200,53 @@ function createEncryptedSocket(
|
||||
};
|
||||
}
|
||||
|
||||
function normalizeMessageData(data: unknown): string | ArrayBuffer {
|
||||
if (typeof data === "string") return data;
|
||||
function normalizeMessageData(data: unknown, isBinary: boolean): string | ArrayBuffer {
|
||||
if (!isBinary) {
|
||||
if (typeof data === "string") return data;
|
||||
const buffer = bufferFromWsData(data);
|
||||
if (buffer) return buffer.toString("utf8");
|
||||
return String(data);
|
||||
}
|
||||
|
||||
if (data instanceof ArrayBuffer) return data;
|
||||
if (ArrayBuffer.isView(data)) {
|
||||
const view = new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
|
||||
const out = new Uint8Array(view.byteLength);
|
||||
out.set(view);
|
||||
return out.buffer;
|
||||
}
|
||||
if (Buffer.isBuffer(data)) {
|
||||
const view = new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
|
||||
|
||||
const buffer = bufferFromWsData(data);
|
||||
if (buffer) {
|
||||
const view = new Uint8Array(buffer.buffer, buffer.byteOffset, buffer.byteLength);
|
||||
const out = new Uint8Array(view.byteLength);
|
||||
out.set(view);
|
||||
return out.buffer;
|
||||
}
|
||||
|
||||
return String(data);
|
||||
}
|
||||
|
||||
function bufferFromWsData(data: unknown): Buffer | null {
|
||||
if (Buffer.isBuffer(data)) return data;
|
||||
if (Array.isArray(data)) {
|
||||
const buffers: Buffer[] = [];
|
||||
for (const part of data) {
|
||||
if (Buffer.isBuffer(part)) {
|
||||
buffers.push(part);
|
||||
} else if (part instanceof ArrayBuffer) {
|
||||
buffers.push(Buffer.from(part));
|
||||
} else if (ArrayBuffer.isView(part)) {
|
||||
buffers.push(Buffer.from(part.buffer, part.byteOffset, part.byteLength));
|
||||
} else if (typeof part === "string") {
|
||||
buffers.push(Buffer.from(part, "utf8"));
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return Buffer.concat(buffers);
|
||||
}
|
||||
if (data instanceof ArrayBuffer) return Buffer.from(data);
|
||||
if (ArrayBuffer.isView(data)) {
|
||||
return Buffer.from(data.buffer, data.byteOffset, data.byteLength);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function buildRelayWebSocketUrl(
|
||||
relayEndpoint: string,
|
||||
sessionId: string,
|
||||
|
||||
@@ -21,6 +21,19 @@ import type pino from "pino";
|
||||
|
||||
export type AgentMcpTransportFactory = () => Promise<Transport>;
|
||||
|
||||
function bufferFromWsData(data: Buffer | ArrayBuffer | Buffer[] | string): Buffer {
|
||||
if (typeof data === "string") return Buffer.from(data, "utf8");
|
||||
if (Array.isArray(data)) {
|
||||
return Buffer.concat(
|
||||
data.map((item) =>
|
||||
Buffer.isBuffer(item) ? item : Buffer.from(item as ArrayBuffer)
|
||||
)
|
||||
);
|
||||
}
|
||||
if (Buffer.isBuffer(data)) return data;
|
||||
return Buffer.from(data as ArrayBuffer);
|
||||
}
|
||||
|
||||
type WebSocketLike = {
|
||||
readyState: number;
|
||||
send: (data: string) => void;
|
||||
@@ -156,7 +169,8 @@ export class WebSocketSessionBridge {
|
||||
data: Buffer | ArrayBuffer | Buffer[] | string
|
||||
): Promise<void> {
|
||||
try {
|
||||
const parsed = JSON.parse(data.toString());
|
||||
const buffer = bufferFromWsData(data);
|
||||
const parsed = JSON.parse(buffer.toString());
|
||||
const message = WSInboundMessageSchema.parse(parsed);
|
||||
|
||||
const messageSummary = {
|
||||
@@ -203,13 +217,7 @@ export class WebSocketSessionBridge {
|
||||
let parsedPayload: unknown = null;
|
||||
|
||||
try {
|
||||
const buffer = Array.isArray(data)
|
||||
? Buffer.concat(
|
||||
data.map((item) => (Buffer.isBuffer(item) ? item : Buffer.from(item as ArrayBuffer)))
|
||||
)
|
||||
: Buffer.isBuffer(data)
|
||||
? data
|
||||
: Buffer.from(data as ArrayBuffer);
|
||||
const buffer = bufferFromWsData(data);
|
||||
rawPayload = buffer.toString();
|
||||
parsedPayload = JSON.parse(rawPayload);
|
||||
} catch (payloadError) {
|
||||
|
||||
Reference in New Issue
Block a user