mirror of
https://github.com/PR0M3TH3AN/bitlogin.git
synced 2026-10-29 09:31:21 +00:00
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 <[email protected]>
231 lines
14 KiB
HTML
231 lines
14 KiB
HTML
<!doctype html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="utf-8" />
|
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
<title>Integrate BitLogin — docs</title>
|
|
<meta name="description" content="How to add the BitLogin login widget to any static site in a few minutes." />
|
|
<link rel="icon" href="./assets/icon.svg" type="image/svg+xml" />
|
|
<link rel="manifest" href="./manifest.webmanifest" />
|
|
<link rel="stylesheet" href="./assets/site.css" />
|
|
</head>
|
|
<body>
|
|
|
|
<nav class="nav">
|
|
<a class="brand" href="./index.html"><span class="dot"></span>BitLogin</a>
|
|
<div class="links">
|
|
<a href="./index.html">Overview</a>
|
|
<a class="active" href="./docs.html">Integrate</a>
|
|
<a href="./account.html">Try it</a>
|
|
</div>
|
|
</nav>
|
|
|
|
<header class="hero" style="padding-top:48px">
|
|
<h1 style="font-size:38px">Add BitLogin to your site</h1>
|
|
<p class="lead">No npm install required. Two files, one custom element.</p>
|
|
</header>
|
|
|
|
<section class="container narrow">
|
|
<ol class="step-list">
|
|
<li>
|
|
<h3>Copy the widget files next to your site</h3>
|
|
<p>Grab <code>bitlogin.js</code> and <code>cryptoWorker.js</code> (plus the shared chunk they both import) from a BitLogin release (or build <code>@bitlogin/widget</code> yourself) and drop them in a folder your site serves, e.g. <code>/vendor/bitlogin/</code>. All of them must live in the same directory — the main script loads the worker by a relative URL.</p>
|
|
</li>
|
|
<li>
|
|
<h3>Include the script and the element</h3>
|
|
<pre><code><script type="module" src="/vendor/bitlogin/bitlogin.js"></script>
|
|
<bitlogin-auth
|
|
vault-relays="wss://relay-one.example,wss://relay-two.example,wss://relay-three.example"
|
|
discovery-relays="wss://discovery-one.example,wss://discovery-two.example">
|
|
</bitlogin-auth></code></pre>
|
|
<p class="section-sub" style="margin:10px 0 0">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.</p>
|
|
<div class="callout">
|
|
<strong>Mount it somewhere your own re-renders won't touch.</strong> Removing
|
|
<code><bitlogin-auth></code> from the DOM (including indirectly, by resetting an
|
|
ancestor's <code>innerHTML</code> — 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 <code>hidden</code>
|
|
attribute instead of removing the element. See the account manager demo's source for a
|
|
single-page example, or BitRoad's <code>src/nostr/bitloginAdapter.mjs</code> /
|
|
<code>#bitloginMount</code> (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.
|
|
</div>
|
|
</li>
|
|
<li>
|
|
<h3>React to sign-in and sign-out</h3>
|
|
<pre><code>const bl = document.querySelector('bitlogin-auth');
|
|
|
|
bl.addEventListener('bitlogin-login', (e) => {
|
|
console.log('signed in as', e.detail.publicKey);
|
|
});
|
|
|
|
bl.addEventListener('bitlogin-logout', () => {
|
|
console.log('signed out');
|
|
});</code></pre>
|
|
</li>
|
|
<li>
|
|
<h3>Sign events from anywhere on the page</h3>
|
|
<p>Once a user is signed in, BitLogin installs a <code>window.nostr</code> provider shaped like the de facto NIP-07 interface used by browser extensions (Alby, nos2x, …). Any existing Nostr web app code that already calls <code>window.nostr</code> works unmodified.</p>
|
|
<pre><code>const pubkey = await window.nostr.getPublicKey();
|
|
|
|
const signed = await window.nostr.signEvent({
|
|
kind: 1,
|
|
content: 'hello from my app',
|
|
tags: [],
|
|
created_at: Math.floor(Date.now() / 1000),
|
|
});
|
|
|
|
// NIP-44 encrypted DMs
|
|
const cipher = await window.nostr.nip44.encrypt(peerPubkeyHex, 'hi there');
|
|
const plain = await window.nostr.nip44.decrypt(peerPubkeyHex, cipher);</code></pre>
|
|
<div class="callout">
|
|
<strong>Private keys never leave a dedicated Web Worker.</strong> The main thread — your page's JS — only ever
|
|
sees public keys, signed events, and ciphertext. <code>window.nostr</code> calls are proxied into the worker
|
|
and back.
|
|
</div>
|
|
</li>
|
|
<li>
|
|
<h3>Or talk to the element directly — no <code>window.nostr</code> contention</h3>
|
|
<p><code><bitlogin-auth></code> mirrors the same four calls as instance methods, scoped to that specific
|
|
element rather than the single shared <code>window.nostr</code> slot:</p>
|
|
<pre><code>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);</code></pre>
|
|
<p>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 <code>window.nostr</code>-reading app code. It keeps working even if something else
|
|
currently owns <code>window.nostr</code> (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 <code>src/nostr/bitloginAdapter.mjs</code> is a real example: its signer adapter calls these four
|
|
methods directly and never touches <code>window.nostr</code> at all.</p>
|
|
</li>
|
|
<li>
|
|
<h3>Supporting multiple signing methods (extensions, NIP-46, BitLogin)</h3>
|
|
<p><code>window.nostr</code> is a single global slot — only one provider can occupy it at a time. If a user also
|
|
has a NIP-07 extension (Alby, nos2x, …) installed, BitLogin will not overwrite it: it only installs
|
|
<code>window.nostr</code> when nothing is already there. The reverse isn't in BitLogin's control — an extension
|
|
that loads after BitLogin can still overwrite it, same as any two NIP-07 providers would collide.</p>
|
|
<p>To know specifically whether BitLogin is the active signer (as opposed to an extension), check
|
|
<code>window.bitlogin?.isActiveSigner()</code> rather than just <code>typeof window.nostr !== 'undefined'</code> —
|
|
the latter only tells you <em>a</em> signer is present, not which one. <code>window.bitlogin</code> is installed
|
|
as soon as the script loads, independent of whether <code><bitlogin-auth></code> has signed anyone in yet,
|
|
so it's safe to feature-detect immediately.</p>
|
|
<pre><code>if (window.bitlogin) {
|
|
console.log('BitLogin is available, version', window.bitlogin.version);
|
|
}
|
|
if (window.bitlogin?.isActiveSigner()) {
|
|
// window.nostr is currently backed by BitLogin specifically
|
|
}</code></pre>
|
|
<p>NIP-46 (remote "bunker" signers) is a different transport — it doesn't typically hook
|
|
<code>window.nostr</code> directly, so it isn't something BitLogin needs to avoid colliding with. A site can
|
|
offer "Sign in with BitLogin," "Connect an extension," and "Connect a bunker" as separate options; whichever
|
|
the user picks becomes the active signer, and your app code should call whatever's currently active rather
|
|
than assume a specific one.</p>
|
|
<div class="callout">
|
|
<strong>Don't give your <code><bitlogin-auth></code> element <code>id="bitlogin"</code>.</strong>
|
|
Browsers automatically expose any element with a matching <code>id</code> or <code>name</code> attribute as
|
|
a same-named global — an element with <code>id="bitlogin"</code> becomes <code>window.bitlogin</code>,
|
|
shadowing the API object described above. Use something like <code>id="bitlogin-widget"</code> instead
|
|
(as this demo does).
|
|
</div>
|
|
<h4 style="margin-top:20px">Letting a user switch away from BitLogin without reloading</h4>
|
|
<p>By default, once <code><bitlogin-auth></code> mounts it claims <code>window.nostr</code> and keeps it —
|
|
even after the user logs out of BitLogin, since they might just be about to log back in with the same widget.
|
|
If your site offers a signer picker and the user switches to a different method, call
|
|
<code>window.bitlogin.releaseSigner()</code> to hand the slot back (it's a no-op, safe to call speculatively,
|
|
if BitLogin doesn't currently hold it):</p>
|
|
<pre><code>// User picked a different signing method in your own UI
|
|
if (window.bitlogin?.releaseSigner()) {
|
|
// window.nostr is now undefined; install the other method's provider
|
|
}</code></pre>
|
|
<p>If they switch back to BitLogin later, call <code>claimSigner()</code> on the element instance to reclaim
|
|
the slot (this also happens automatically every time a user completes sign-in, sign-up, or recovery through
|
|
the widget itself, so you only need this for a picker UI that lives outside the widget):</p>
|
|
<pre><code>document.querySelector('bitlogin-auth').claimSigner();</code></pre>
|
|
<p>Both <code>claimSigner()</code>/<code>releaseSigner()</code> on the element and
|
|
<code>window.bitlogin.releaseSigner()</code> also fire <code>bitlogin-signer-claimed</code> /
|
|
<code>bitlogin-signer-released</code> events (on the element and on <code>window</code>, respectively) if you'd
|
|
rather react to the change than check a return value.</p>
|
|
<div class="callout">
|
|
<strong><code>claimSigner()</code> can fail without breaking anything.</strong> Some NIP-07 extensions
|
|
install <code>window.nostr</code> as a non-configurable, non-writable property specifically to stop another
|
|
script from overwriting it. When that happens, <code>claimSigner()</code> returns <code>false</code> instead
|
|
of throwing (and <code>bitlogin-signer-claimed</code>'s <code>event.detail</code> carries
|
|
<code>{ windowNostrClaimed: false, error }</code>), and every BitLogin sign-in flow completes normally
|
|
regardless — the failure only means <code>window.nostr</code> 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
|
|
<code>window.nostr</code> itself is affected, and only for as long as the other extension holds the slot.
|
|
</div>
|
|
</li>
|
|
<li>
|
|
<h3>Choose your relays</h3>
|
|
<p>BitLogin ships with a small built-in bootstrap relay list, but you should point <code>vault-relays</code> and
|
|
<code>discovery-relays</code> 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.</p>
|
|
</li>
|
|
<li>
|
|
<h3>Make it look like your site</h3>
|
|
<p>The widget renders in a shadow root, but every color, radius, font, and its max-width are all read from CSS
|
|
custom properties on <code>:host</code> — 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:</p>
|
|
<pre><code>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 */
|
|
}</code></pre>
|
|
<p class="section-sub" style="margin:10px 0 0">Overriding none of these is a valid choice — the widget falls
|
|
back to its own light theme, a <code>prefers-color-scheme: dark</code> media query, and a
|
|
<code>data-theme="dark"|"light"</code> attribute override for pages with their own theme toggle. See BitRoad's
|
|
<code>bitlogin-auth { ... }</code> rule in its <code>src/styles.css</code> for a real integration that maps
|
|
every one of these onto its own design tokens.</p>
|
|
</li>
|
|
<li>
|
|
<h3>That's the whole integration</h3>
|
|
<p>No account database, no password-reset email, no server-side session. The <a href="./account.html">account manager demo</a>
|
|
shows the full loop: create an account, sign a test event, rotate the password, and export a recovery file.</p>
|
|
</li>
|
|
</ol>
|
|
|
|
<div class="callout">
|
|
<strong>Password-manager save prompts.</strong> Because the widget's login form lives inside
|
|
a shadow root and submits via JavaScript (never a real form POST), browsers' passive
|
|
"offer to save this password" heuristics don't reliably fire — and the generated-password
|
|
screen never puts the password into a real input at all, since it's only shown as text.
|
|
The widget works around this by explicitly calling the Credential Management API
|
|
(<code>navigator.credentials.store()</code>) after every successful registration, login,
|
|
password rotation, and recovery. This is Chromium-only (Chrome, Edge, Brave, Opera) —
|
|
Firefox and Safari never implemented <code>PasswordCredential</code>, so users on those
|
|
browsers will need to save their generated password manually.
|
|
</div>
|
|
</section>
|
|
|
|
<footer>
|
|
BitLogin is an experimental alpha protocol. This demo is a reference implementation, not a production account service.
|
|
<div class="footer-links"><a href="https://github.com/PR0M3TH3AN/bitlogin">Source on GitHub</a> · MIT licensed — <a href="https://github.com/PR0M3TH3AN/bitlogin/blob/main/LICENSE">LICENSE</a></div>
|
|
</footer>
|
|
|
|
|
|
<script>
|
|
if ("serviceWorker" in navigator) {
|
|
navigator.serviceWorker.register("./sw.js").catch(() => {});
|
|
}
|
|
</script>
|
|
</body>
|
|
</html>
|