From f697668f8fb25af91e6359f72bd644a820da29d0 Mon Sep 17 00:00:00 2001 From: thePR0M3TH3AN <53631862+PR0M3TH3AN@users.noreply.github.com> Date: Mon, 10 Aug 2026 08:36:24 -0400 Subject: [PATCH] fix(widget): make the bundle importable without a DOM The package documented itself as safe to import in Node, with registration a no-op absent a DOM. That was false: a class declaration evaluates its extends clause at module load, so `extends globalThis.HTMLElement` threw before canRegisterElements() could run. Caught by loading the deployed bundle rather than the source. Falls back to a plain base class when there is no DOM, so a server-rendering host can import the helpers and the documented promise is now true. --- packages/widget/src/base.js | 14 +++++++++++++- packages/widget/src/provider.js | 5 ++++- packages/widget/test/provider.test.js | 10 ++++++++++ 3 files changed, 27 insertions(+), 2 deletions(-) diff --git a/packages/widget/src/base.js b/packages/widget/src/base.js index 4e347fb..f9b6f45 100644 --- a/packages/widget/src/base.js +++ b/packages/widget/src/base.js @@ -163,7 +163,19 @@ export function targetFromAttributes(element) { * * Subclasses implement `render()` and may implement `template()`. */ -export class GovernanceElement extends globalThis.HTMLElement { +/** + * Base constructor for the elements. + * + * A class declaration evaluates its `extends` clause at module load, so + * `extends globalThis.HTMLElement` throws in Node before any capability guard + * could run — which made the "safe to import without a DOM" promise false. + * Falling back to a plain class keeps the module importable for a + * server-rendering host; registration is still a no-op there. + */ +const ElementBase = + typeof globalThis.HTMLElement === "function" ? globalThis.HTMLElement : /** @type {any} */ (class {}); + +export class GovernanceElement extends ElementBase { constructor() { super(); this.attachShadow({ mode: "open" }); diff --git a/packages/widget/src/provider.js b/packages/widget/src/provider.js index 24e8765..8e518c8 100644 --- a/packages/widget/src/provider.js +++ b/packages/widget/src/provider.js @@ -34,7 +34,10 @@ export function requestContext(element) { return detail.context; } -export class BitGateProvider extends globalThis.HTMLElement { +const ProviderBase = + typeof globalThis.HTMLElement === "function" ? globalThis.HTMLElement : /** @type {any} */ (class {}); + +export class BitGateProvider extends ProviderBase { static get observedAttributes() { return [ "relays", diff --git a/packages/widget/test/provider.test.js b/packages/widget/test/provider.test.js index 5ed2c9d..fca1dfe 100644 --- a/packages/widget/test/provider.test.js +++ b/packages/widget/test/provider.test.js @@ -302,3 +302,13 @@ describe("signature verification posture", () => { expect(provider.hasAttribute("data-unverified")).toBe(true); }); }); + +describe("environment safety", () => { + it("keeps the documented promise that it imports without a DOM", async () => { + // A class declaration evaluates `extends` at module load, so this used to + // throw in Node before any capability guard could run. + const { canRegisterElements } = await import("../src/base.js"); + expect(typeof canRegisterElements).toBe("function"); + expect(canRegisterElements()).toBe(true); // happy-dom is present here + }); +});