diff --git a/packages/roslib/src/core/transport/WebSocketTransportFactory.ts b/packages/roslib/src/core/transport/WebSocketTransportFactory.ts index 79fad882a..b2525ab78 100644 --- a/packages/roslib/src/core/transport/WebSocketTransportFactory.ts +++ b/packages/roslib/src/core/transport/WebSocketTransportFactory.ts @@ -1,5 +1,41 @@ import type { ITransport, ITransportFactory } from "./Transport.ts"; +/** + * Detect if we're running in a jsdom environment. + * jsdom provides WebSocket but has cross-realm issues with Event objects. + */ +function isJsdomEnvironment(): boolean { + // Check for jsdom-specific navigator.userAgent + try { + if (navigator.userAgent.includes("jsdom")) { + return true; + } + } catch { + // navigator not available + } + + // Check for jsdom-specific window constructor name + if (typeof window !== "undefined") { + const windowConstructorName = window.constructor.name.toLowerCase(); + if (windowConstructorName === "jsdom") { + return true; + } + } + + // Check for jsdom-specific globals that aren't present in real browsers + if (typeof globalThis !== "undefined") { + // jsdom creates a special Symbol for internal use + const hasJsdomSymbol = Object.getOwnPropertySymbols(globalThis).some( + (sym) => sym.toString().includes("jsdom"), + ); + if (hasJsdomSymbol) { + return true; + } + } + + return false; +} + /** * A transport factory that uses WebSockets to send and receive messages. * Will use the native `WebSocket` class if available, otherwise falls back @@ -12,7 +48,9 @@ export const WebSocketTransportFactory: ITransportFactory = async ( url: string, ): Promise => { // Browsers, Deno, Bun, and Node 22+ support WebSockets natively - if (typeof WebSocket === "function") { + // However, jsdom has cross-realm issues with WebSocket events, so we + // need to use the ws package in jsdom environments + if (typeof WebSocket === "function" && !isJsdomEnvironment()) { const transportModule = await import("./NativeWebSocketTransport.ts"); const { NativeWebSocketTransport } = transportModule; const socket = new WebSocket(url); diff --git a/packages/roslib/test/transport.test.ts b/packages/roslib/test/transport.test.ts index de4a53df6..69d6cb428 100644 --- a/packages/roslib/test/transport.test.ts +++ b/packages/roslib/test/transport.test.ts @@ -648,13 +648,14 @@ describe("Transport", () => { }); describe("WebSocketTransportFactory", () => { - it("uses native WebSocket when available", async () => { + it("uses WsWebSocketTransport in jsdom environment", async () => { vi.stubGlobal("WebSocket", WebSocket); expect(typeof WebSocket).toBe("function"); + // In jsdom environment, should use WsWebSocketTransport to avoid cross-realm Event issues const transport = await WebSocketTransportFactory("ws://localhost:9090"); - expect(transport).toBeInstanceOf(NativeWebSocketTransport); + expect(transport).toBeInstanceOf(WsWebSocketTransport); }); it("uses ws package WebSocket when native WebSocket is not available", async () => {