From ce6ade73b467dcd4b7e6c84cff2c46f3ee5fd9f2 Mon Sep 17 00:00:00 2001 From: thePR0M3TH3AN <53631862+PR0M3TH3AN@users.noreply.github.com> Date: Tue, 21 Jul 2026 15:53:17 -0400 Subject: [PATCH] docs: bring the Integrate page and version references up to date docs.html was missing everything from this session's work: - The disconnectedCallback/crypto-worker-teardown gotcha (found via the BitRoad integration) -- now a callout right where the element is first mounted, since that's the step where it actually bites. - Talking to the element directly (getPublicKey/signEvent/nip44Encrypt/ nip44Decrypt as instance methods) as an alternative to window.nostr, with BitRoad's adapter as the worked example. - claimSigner() failing gracefully (returns false, doesn't throw) when a NIP-07 extension has locked window.nostr down -- was previously undocumented entirely; this is the exact bug just fixed. - Theming via CSS custom properties, with the full property list and BitRoad's mapping as a real example. Also fixed two stale "(v0.3)" references to docs/spec.md in README.md and CONTRIBUTING.md -- the spec was bumped to v0.4 earlier this session for the rollback-detection changes but these two mentions were missed. Co-Authored-By: Claude Sonnet 5 --- CONTRIBUTING.md | 2 +- README.md | 2 +- packages/demo/public/docs.html | 68 ++++++++++++++++++++++++++++++++++ 3 files changed, 70 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c6dd240..a3f4863 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -51,7 +51,7 @@ reachable from most contributor sandboxes either. ## The spec is canonical -`docs/spec.md` (currently v0.3) is the source of truth for the protocol +`docs/spec.md` (currently v0.4) is the source of truth for the protocol itself — wire formats, KDF parameters, capsule structure, derivation paths, threat model. Code comments reference it by section (`§11.2`, `§17.4`, …). diff --git a/README.md b/README.md index 9f1211b..d355125 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ password that unlocks a portable Nostr identity, plus an independent 12-word recovery phrase. Encrypted account material lives on public Nostr relays — no account server, no password-reset email, no database. -This repo implements the protocol from `docs/spec.md` (v0.3) as a modular +This repo implements the protocol from `docs/spec.md` (v0.4) as a modular system any static site can embed for login and Nostr signing. ## Layout diff --git a/packages/demo/public/docs.html b/packages/demo/public/docs.html index bbefb1c..362454f 100644 --- a/packages/demo/public/docs.html +++ b/packages/demo/public/docs.html @@ -39,6 +39,20 @@ discovery-relays="wss://discovery-one.example,wss://discovery-two.example"> </bitlogin-auth>

That's it — the element renders its own sign-in/create/recover UI inside a shadow root, so it won't collide with your site's CSS.

+
+ Mount it somewhere your own re-renders won't touch. Removing + <bitlogin-auth> from the DOM (including indirectly, by resetting an + ancestor's innerHTML — a common pattern in hand-rolled UI without a + framework's reconciler) tears down its crypto Web Worker. If that happens mid-session, + every signing call made afterward just hangs forever with no error, because the worker + is gone and nothing ever replies — a call already in flight when it happens can even + look like it succeeded. If your app re-renders the container this element lives in, + give it a container that's never replaced wholesale and toggle the hidden + attribute instead of removing the element. See the account manager demo's source for a + single-page example, or BitRoad's src/nostr/bitloginAdapter.mjs / + #bitloginMount (a sibling Nostr commerce app that embeds BitLogin) for a + worked example of keeping the element alive across a host app's own re-renders. +
  • React to sign-in and sign-out

    @@ -73,6 +87,23 @@ const plain = await window.nostr.nip44.decrypt(peerPubkeyHex, cipher);
  • +
  • +

    Or talk to the element directly — no window.nostr contention

    +

    <bitlogin-auth> mirrors the same four calls as instance methods, scoped to that specific + element rather than the single shared window.nostr slot:

    +
    const bl = document.querySelector('bitlogin-auth');
    +
    +const pubkey = await bl.getPublicKey();
    +const signed = await bl.signEvent({ kind: 1, content: 'hello', tags: [] });
    +const cipher = await bl.nip44Encrypt(peerPubkeyHex, 'hi there');
    +const plain  = await bl.nip44Decrypt(peerPubkeyHex, cipher);
    +

    Prefer this if your app already holds a reference to the element — e.g. you built the sign-in UI yourself + instead of relying on window.nostr-reading app code. It keeps working even if something else + currently owns window.nostr (another extension, a different widget instance), and it's what a host + app should use if it wants a specific, known signer rather than "whichever provider is active this moment." + BitRoad's src/nostr/bitloginAdapter.mjs is a real example: its signer adapter calls these four + methods directly and never touches window.nostr at all.

    +
  • Supporting multiple signing methods (extensions, NIP-46, BitLogin)

    window.nostr is a single global slot — only one provider can occupy it at a time. If a user also @@ -120,6 +151,16 @@ if (window.bitlogin?.releaseSigner()) { window.bitlogin.releaseSigner() also fire bitlogin-signer-claimed / bitlogin-signer-released events (on the element and on window, respectively) if you'd rather react to the change than check a return value.

    +
    + claimSigner() can fail without breaking anything. Some NIP-07 extensions + install window.nostr as a non-configurable, non-writable property specifically to stop another + script from overwriting it. When that happens, claimSigner() returns false instead + of throwing (and bitlogin-signer-claimed's event.detail carries + { windowNostrClaimed: false, error }), and every BitLogin sign-in flow completes normally + regardless — the failure only means window.nostr still points at that other extension. If your + app talks to the element directly (previous step) this never matters at all; only code that reads + window.nostr itself is affected, and only for as long as the other extension holds the slot. +
  • Choose your relays

    @@ -127,6 +168,33 @@ if (window.bitlogin?.releaseSigner()) { discovery-relays at relays you trust for your users. Use at least three vault relays for redundancy — capsule publication and reads use a quorum, so no single relay is a single point of failure.

  • +
  • +

    Make it look like your site

    +

    The widget renders in a shadow root, but every color, radius, font, and its max-width are all read from CSS + custom properties on :host — and custom properties inherit through the shadow boundary, so your + page's own CSS can override every one of them without touching the widget's internals:

    +
    bitlogin-auth {
    +  --bl-accent: #3d9bff;       /* primary button / link / focus ring color */
    +  --bl-accent-hover: #2f86e0; /* primary button hover */
    +  --bl-accent-fg: #04101f;    /* text color on top of --bl-accent */
    +  --bl-bg: #12151f;           /* card background */
    +  --bl-fg: #eaeef6;           /* body text */
    +  --bl-muted: #98a2b6;        /* secondary text */
    +  --bl-border: rgba(255, 255, 255, 0.10);
    +  --bl-input-bg: #1a1e2b;     /* input / credential-box background */
    +  --bl-danger: #ff6b6b;
    +  --bl-danger-bg: rgba(255, 107, 107, 0.16);
    +  --bl-radius: 14px;
    +  --bl-font-family: Inter, ui-sans-serif, system-ui, sans-serif;
    +  --bl-max-width: none;       /* default 380px keeps it a fixed-width card;
    +                                  override to fill a wider container */
    +}
    +

    Overriding none of these is a valid choice — the widget falls + back to its own light theme, a prefers-color-scheme: dark media query, and a + data-theme="dark"|"light" attribute override for pages with their own theme toggle. See BitRoad's + bitlogin-auth { ... } rule in its src/styles.css for a real integration that maps + every one of these onto its own design tokens.

    +
  • That's the whole integration

    No account database, no password-reset email, no server-side session. The account manager demo