From a77a8a3d840576d63e23bcda482a20c4c136b710 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Cichoci=C5=84ski?= Date: Mon, 11 May 2026 12:45:29 +0200 Subject: [PATCH] fix: guard SharedWorker reference on runtimes without the global `SharedWorker` is not defined on mobile Safari, in-app WebViews, and hardened browser modes. The bare `guest instanceof SharedWorker` checks in `host.connect()` throw `ReferenceError` on those runtimes before we even reach the iframe-target codepath, which is what most consumers actually need. Adds an `isSharedWorker(guest)` helper that gates the `instanceof` check with `typeof SharedWorker !== "undefined"`, mirroring the existing `isWorkerLike` pattern for `Worker`. Use it at both `host.ts` call sites. --- src/helpers.ts | 7 +++++++ src/host.ts | 5 +++-- tests/messaging.test.ts | 34 ++++++++++++++++++++++++++++++++++ 3 files changed, 44 insertions(+), 2 deletions(-) diff --git a/src/helpers.ts b/src/helpers.ts index f8a9485..b66d71c 100644 --- a/src/helpers.ts +++ b/src/helpers.ts @@ -200,6 +200,13 @@ export function isWorkerLike(guest: Guest): guest is WorkerLike { return isNodeWorker(guest) || (typeof Worker !== "undefined" && guest instanceof Worker); } +// `SharedWorker` is unavailable on mobile Safari, in-app WebViews, and +// hardened browser modes. Bare `instanceof SharedWorker` throws ReferenceError +// in those runtimes; gate the check with `typeof`. +export function isSharedWorker(guest: Guest): guest is SharedWorker { + return typeof SharedWorker !== "undefined" && guest instanceof SharedWorker; +} + export function addEventListener(target: Target, event: string, handler: EventListenerOrEventListenerObject) { if (isNodeWorker(target)) { target.on(event, handler); diff --git a/src/host.ts b/src/host.ts index 0d4bd23..e2d0e05 100644 --- a/src/host.ts +++ b/src/host.ts @@ -6,6 +6,7 @@ import { getOriginFromURL, isNodeEnv, isNodeWorker, + isSharedWorker, isWorkerLike, postMessageToTarget, removeEventListener, @@ -50,7 +51,7 @@ function connect(guest: Guest, schema: Schema = {}): Promise { const guestIsWorker = isWorkerLike(guest); const listenTo = - guestIsWorker || isNodeEnv() ? (guest as Worker) : guest instanceof SharedWorker ? guest.port : window; + guestIsWorker || isNodeEnv() ? (guest as Worker) : isSharedWorker(guest) ? guest.port : window; return new Promise((resolve) => { const connectionID = generateId(); @@ -58,7 +59,7 @@ function connect(guest: Guest, schema: Schema = {}): Promise { // on handshake request function handleHandshake(event: any) { const sendTo = - guestIsWorker || isNodeEnv() ? (guest as Worker) : guest instanceof SharedWorker ? guest.port : event.source; + guestIsWorker || isNodeEnv() ? (guest as Worker) : isSharedWorker(guest) ? guest.port : event.source; if (!guestIsWorker && !isNodeEnv() && !isValidTarget(guest, event)) return; diff --git a/tests/messaging.test.ts b/tests/messaging.test.ts index 0633105..3d22b37 100644 --- a/tests/messaging.test.ts +++ b/tests/messaging.test.ts @@ -55,6 +55,40 @@ describe("isNodeWorker and isWorkerLike", () => { }); }); +describe("isSharedWorker", () => { + afterEach(() => { + vi.resetModules(); + }); + + it("returns false (without throwing) when SharedWorker is not defined", async () => { + const original = (globalThis as any).SharedWorker; + delete (globalThis as any).SharedWorker; + try { + const helpers = await loadHelpers(); + expect(() => helpers.isSharedWorker({} as any)).not.toThrow(); + expect(helpers.isSharedWorker({} as any)).toBe(false); + } finally { + if (original !== undefined) { + (globalThis as any).SharedWorker = original; + } + } + }); + + it("detects SharedWorker instances when the global is present", async () => { + class FakeSharedWorker { + port = {}; + } + (globalThis as any).SharedWorker = FakeSharedWorker as any; + try { + const helpers = await loadHelpers(); + expect(helpers.isSharedWorker(new FakeSharedWorker() as any)).toBe(true); + expect(helpers.isSharedWorker({} as any)).toBe(false); + } finally { + delete (globalThis as any).SharedWorker; + } + }); +}); + describe("generateId", () => { it("creates ids of requested length", async () => { const helpers = await loadHelpers();