From 5ae41ed9db48cd75bcf87cd180cebdc518a80e39 Mon Sep 17 00:00:00 2001 From: thePR0M3TH3AN <53631862+PR0M3TH3AN@users.noreply.github.com> Date: Tue, 11 Aug 2026 19:37:32 -0400 Subject: [PATCH] widget: ship generated type declarations + typed element/events Integrators had to read the demo to learn the API: the package built via vite with no .d.ts and no types field, so @bitlogin/widget and @bitlogin/widget/session resolved to `any`. Emit declarations from source (tsconfig.types.json -> dist-types, build:types wired into build) and point the package types/exports conditions at them, for both the root and the /session entry. Generated, so they can't drift from the implementation. Add the type surface integrators actually want: - HTMLElementTagNameMap["bitlogin-auth"], so querySelector/createElement return the typed element. - Typed addEventListener/removeEventListener overloads (declaration-merged interface) for the bitlogin-* events. - Exported event-detail types and BitLoginEventMap (src/events.ts), matching element.ts's dispatchEvent shapes. Verified with a consumer conformance typecheck (typed detail with no cast, typed element methods, readCachedSession return type). README gains a "TypeScript" section. Addresses the ".d.ts / documented API surface" item of the Tessera integration feedback (docs/TODO.md). Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01BXQCvJPA6sVSQ5j8uBd9bB --- README.md | 27 ++++++++++++++++++ docs/TODO.md | 15 ++++++++-- packages/widget/package.json | 14 +++++++-- packages/widget/src/element.ts | 37 ++++++++++++++++++++++++ packages/widget/src/events.ts | 44 +++++++++++++++++++++++++++++ packages/widget/src/index.ts | 7 +++++ packages/widget/tsconfig.types.json | 16 +++++++++++ 7 files changed, 154 insertions(+), 6 deletions(-) create mode 100644 packages/widget/src/events.ts create mode 100644 packages/widget/tsconfig.types.json diff --git a/README.md b/README.md index f5b8ef2..a27f38a 100644 --- a/README.md +++ b/README.md @@ -242,6 +242,33 @@ Nostr signer (browser extension) is active in this browser and couldn't be replaced...") using the same warning banner already shown for rollback and relay-disagreement conditions. +### TypeScript + +The package ships type declarations (`@bitlogin/widget` and +`@bitlogin/widget/session`), generated from source so they can't drift. +Registering the widget augments the DOM types, so integrators get real +autocomplete instead of reading the demo: + +```ts +import "@bitlogin/widget"; // registers and its types + +const el = document.querySelector("bitlogin-auth"); // typed as BitLoginAuthElement +el?.addEventListener("bitlogin-login", (e) => { + // e.detail is typed: { publicKey?: string; method; capabilities } + console.log(e.detail.method, e.detail.capabilities.nip44); +}); +await el?.getPublicKey(); // typed element methods + +import { readCachedSession } from "@bitlogin/widget/session"; +const who = await readCachedSession(); // { pubkey, npub, name? } | null +``` + +Exported types include `BitLoginAuthElement`, the `bitlogin-*` +`...EventDetail` shapes and `BitLoginEventMap`, `PublicSessionInfo`, +`SignerMethod`/`SignerCapabilities`, `BitLoginConfig`, and the +`window.bitlogin` (`BitLoginGlobal`) interface. Run `npm run build` (or +`build:types`) to emit them into `dist-types/`. + ### Theming `` renders in a shadow root, but its colors, radius, font, and diff --git a/docs/TODO.md b/docs/TODO.md index d3ffd75..d8845e1 100644 --- a/docs/TODO.md +++ b/docs/TODO.md @@ -416,6 +416,15 @@ how much they cost. ### Small -- Ship a `.d.ts` (or documented event/method surface) for the element: - `getPublicKey`, `signEvent`, `nip44Encrypt/Decrypt`, and the event - `detail` shapes. Integrators currently read the demo to learn the API. +- **[DONE 2026-08-11]** Ship a `.d.ts` (or documented event/method surface) for + the element. **Shipped:** the widget now emits type declarations from source + (`tsconfig.types.json` → `dist-types/`, wired via `build:types` and the + package `types`/`exports` conditions for both `@bitlogin/widget` and + `@bitlogin/widget/session`) — zero-drift because they're generated, not + hand-written. Added the type surface integrators actually want: a + `HTMLElementTagNameMap["bitlogin-auth"]` augmentation (typed + `querySelector`/`createElement`), typed `addEventListener` overloads via a + declaration-merged interface, and exported event-detail types + (`BitLoginLoginEventDetail` etc. + `BitLoginEventMap`) in `src/events.ts`. + Verified with a consumer conformance typecheck; README "TypeScript" section + added. diff --git a/packages/widget/package.json b/packages/widget/package.json index 3ecbe0c..275edef 100644 --- a/packages/widget/package.json +++ b/packages/widget/package.json @@ -6,12 +6,20 @@ "type": "module", "description": "BitLogin embeddable login widget: a custom element plus a window.nostr NIP-07-compatible provider, backed by a dedicated crypto Web Worker.", "main": "./dist/bitlogin.js", + "types": "./dist-types/index.d.ts", "exports": { - ".": "./dist/bitlogin.js", - "./session": "./dist/bitlogin-session.js" + ".": { + "types": "./dist-types/index.d.ts", + "default": "./dist/bitlogin.js" + }, + "./session": { + "types": "./dist-types/session.d.ts", + "default": "./dist/bitlogin-session.js" + } }, "scripts": { - "build": "vite build", + "build": "vite build && npm run build:types", + "build:types": "tsc -p tsconfig.types.json", "dev": "vite build --watch", "typecheck": "tsc --noEmit" }, diff --git a/packages/widget/src/element.ts b/packages/widget/src/element.ts index be96378..845d1b1 100644 --- a/packages/widget/src/element.ts +++ b/packages/widget/src/element.ts @@ -20,6 +20,7 @@ import { Nip46Signer } from "./signers/nip46.js"; import type { Signer, SignerCapabilities, SignerMethod } from "./signers/types.js"; import { renderQrSvg } from "./qr.js"; import { setActiveSession, clearActiveSession } from "./globalSession.js"; +import type { BitLoginEventMap } from "./events.js"; import { buildPasskeyCreateOptions, buildPasskeyGetOptions, @@ -2217,3 +2218,39 @@ function pickRandomIndices(max: number, count: number): number[] { function escapeHtml(input: string): string { return input.replace(/[&<>"']/gu, (ch) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[ch]!); } + +// Declaration merge: give BitLoginAuthElement typed addEventListener/ +// removeEventListener overloads for its own bitlogin-* events, so a host gets +// `e.detail` typed without a cast. The string overload preserves the base +// signature for every other event. Merging an interface of the same name adds +// these to the class type; the runtime implementation stays HTMLElement's. +export interface BitLoginAuthElement { + addEventListener( + type: K, + listener: (this: BitLoginAuthElement, ev: BitLoginEventMap[K]) => void, + options?: boolean | AddEventListenerOptions + ): void; + addEventListener( + type: string, + listener: EventListenerOrEventListenerObject, + options?: boolean | AddEventListenerOptions + ): void; + removeEventListener( + type: K, + listener: (this: BitLoginAuthElement, ev: BitLoginEventMap[K]) => void, + options?: boolean | EventListenerOptions + ): void; + removeEventListener( + type: string, + listener: EventListenerOrEventListenerObject, + options?: boolean | EventListenerOptions + ): void; +} + +declare global { + interface HTMLElementTagNameMap { + // So document.querySelector("bitlogin-auth") and + // document.createElement("bitlogin-auth") are typed as the element. + "bitlogin-auth": BitLoginAuthElement; + } +} diff --git a/packages/widget/src/events.ts b/packages/widget/src/events.ts new file mode 100644 index 0000000..b033212 --- /dev/null +++ b/packages/widget/src/events.ts @@ -0,0 +1,44 @@ +/** + * Public event contract for ``. These are the CustomEvents the + * element dispatches on itself; the detail shapes here match element.ts's + * `dispatchEvent` calls exactly. Integrators previously had to read the demo to + * learn them — the typed `addEventListener` overloads on BitLoginAuthElement + * (see element.ts) now surface them in editors. + */ +import type { SignerMethod, SignerCapabilities } from "./signers/types.js"; + +/** `bitlogin-login` — a session was established OR silently restored on page + * load (the two are intentionally indistinguishable to a listener). */ +export interface BitLoginLoginEventDetail { + /** The signed-in account's public key (hex), when known. */ + publicKey?: string; + /** How the session was established. */ + method: SignerMethod; + /** What the active signer supports (NIP-07 extensions vary). */ + capabilities: SignerCapabilities; +} + +/** `bitlogin-connection-granted` — the user shared an NWC wallet connection with + * an origin (Connection Vault, reveal mode). */ +export interface BitLoginConnectionGrantedEventDetail { + origin: string; +} + +/** `bitlogin-signer-claimed` — the element tried to install itself as + * `window.nostr`; `windowNostrClaimed` says whether it won the slot. */ +export interface BitLoginSignerClaimedEventDetail { + windowNostrClaimed: boolean; + error?: string; +} + +/** Maps each `bitlogin-*` event name to its concrete event type. Events without + * a detail payload are plain `CustomEvent`. */ +export interface BitLoginEventMap { + "bitlogin-login": CustomEvent; + "bitlogin-logout": CustomEvent; + "bitlogin-connection-granted": CustomEvent; + "bitlogin-signer-claimed": CustomEvent; + "bitlogin-signer-released": CustomEvent; + "bitlogin-request-pending": CustomEvent; + "bitlogin-offer-pending": CustomEvent; +} diff --git a/packages/widget/src/index.ts b/packages/widget/src/index.ts index 34462c4..f979ff9 100644 --- a/packages/widget/src/index.ts +++ b/packages/widget/src/index.ts @@ -96,6 +96,13 @@ window.bitlogin = { export { readCachedSession } from "./session.js"; export type { PublicSessionInfo } from "./publicSession.js"; export { BitLoginAuthElement } from "./element.js"; +export type { + BitLoginEventMap, + BitLoginLoginEventDetail, + BitLoginConnectionGrantedEventDetail, + BitLoginSignerClaimedEventDetail +} from "./events.js"; +export type { SignerMethod, SignerCapabilities } from "./signers/types.js"; export { WorkerClient } from "./worker/workerClient.js"; export { createNip07Provider } from "./provider.js"; export type { BitLoginConfig } from "./config.js"; diff --git a/packages/widget/tsconfig.types.json b/packages/widget/tsconfig.types.json new file mode 100644 index 0000000..0e9c76c --- /dev/null +++ b/packages/widget/tsconfig.types.json @@ -0,0 +1,16 @@ +{ + "//": "Emits public .d.ts for @bitlogin/widget (and /session) into dist-types. Kept separate from tsconfig.json (which is noEmit typecheck-only). Tests and the e2e tree are excluded so they don't ship as declarations.", + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "outDir": "./dist-types", + "rootDir": "src", + "noEmit": false, + "emitDeclarationOnly": true, + "declaration": true, + "declarationMap": false, + "sourceMap": false, + "lib": ["ES2022", "DOM", "DOM.Iterable"] + }, + "include": ["src"], + "exclude": ["src/e2e", "**/*.test.ts"] +}