fix(app): keep browser relay WebSockets protocol-neutral (#2976)
Use the standard browser WebSocket constructor without the Node/React Native options object, while retaining the options-capable factory for supported runtimes and the desktop bridge.
This commit is contained in:
@@ -9,7 +9,6 @@ import {
|
||||
encodeUtf8String,
|
||||
extractRelayMessage,
|
||||
} from "./daemon-client-transport.js";
|
||||
import { defaultWebSocketFactory } from "./daemon-client-websocket-transport.js";
|
||||
|
||||
const createClientChannelMock = vi.hoisted(() => vi.fn());
|
||||
|
||||
@@ -18,28 +17,6 @@ vi.mock("@getpaseo/relay/e2ee", () => ({
|
||||
}));
|
||||
|
||||
describe("daemon-client transport helpers", () => {
|
||||
test("defaultWebSocketFactory passes custom headers to native WebSocket options", () => {
|
||||
const constructorSpy = vi.fn();
|
||||
function MockWebSocket(
|
||||
url: string,
|
||||
protocols?: string | string[],
|
||||
options?: { headers?: Record<string, string> },
|
||||
) {
|
||||
constructorSpy(url, protocols, options);
|
||||
}
|
||||
vi.stubGlobal("WebSocket", MockWebSocket);
|
||||
|
||||
defaultWebSocketFactory("ws://example.test", {
|
||||
protocols: ["paseo.test"],
|
||||
headers: { "X-Tenant": "acme" },
|
||||
});
|
||||
|
||||
expect(constructorSpy).toHaveBeenCalledWith("ws://example.test", ["paseo.test"], {
|
||||
headers: { "X-Tenant": "acme" },
|
||||
});
|
||||
vi.unstubAllGlobals();
|
||||
});
|
||||
|
||||
test("createEncryptedTransport closes handshake failures with browser-safe code", async () => {
|
||||
createClientChannelMock.mockReset();
|
||||
createClientChannelMock.mockRejectedValueOnce(new Error("handshake failed"));
|
||||
|
||||
@@ -5,23 +5,38 @@ import type {
|
||||
} from "./daemon-client-transport-types.js";
|
||||
import { extractRelayMessage } from "./daemon-client-transport-utils.js";
|
||||
|
||||
type GlobalWebSocketConstructor = new (
|
||||
url: string,
|
||||
protocols?: string | string[],
|
||||
options?: { headers?: Record<string, string> },
|
||||
) => WebSocketLike;
|
||||
|
||||
function getGlobalWebSocket(): GlobalWebSocketConstructor {
|
||||
const globalWs = (globalThis as { WebSocket?: GlobalWebSocketConstructor }).WebSocket;
|
||||
if (!globalWs) {
|
||||
throw new Error("WebSocket is not available in this runtime");
|
||||
}
|
||||
return globalWs;
|
||||
}
|
||||
|
||||
export function defaultWebSocketFactory(
|
||||
url: string,
|
||||
options?: { headers?: Record<string, string>; protocols?: string[] },
|
||||
): WebSocketLike {
|
||||
const globalWs = (
|
||||
globalThis as {
|
||||
WebSocket?: new (
|
||||
url: string,
|
||||
protocols?: string | string[],
|
||||
options?: { headers?: Record<string, string> },
|
||||
) => WebSocketLike;
|
||||
}
|
||||
).WebSocket;
|
||||
if (!globalWs) {
|
||||
throw new Error("WebSocket is not available in this runtime");
|
||||
const globalWs = getGlobalWebSocket();
|
||||
if (options?.protocols === undefined) {
|
||||
return new globalWs(url);
|
||||
}
|
||||
return new globalWs(url, options?.protocols, { headers: options?.headers });
|
||||
return new globalWs(url, options.protocols);
|
||||
}
|
||||
|
||||
// React Native and Node-compatible WebSocket implementations accept this extra options object.
|
||||
// Keep it out of the standard browser factory above.
|
||||
export function nativeWebSocketFactory(
|
||||
url: string,
|
||||
options?: { headers?: Record<string, string>; protocols?: string[] },
|
||||
): WebSocketLike {
|
||||
return new (getGlobalWebSocket())(url, options?.protocols, { headers: options?.headers });
|
||||
}
|
||||
|
||||
export function createWebSocketTransportFactory(factory: WebSocketFactory): DaemonTransportFactory {
|
||||
|
||||
Reference in New Issue
Block a user