mirror of
https://github.com/PR0M3TH3AN/bitlogin.git
synced 2026-10-27 08:31:21 +00:00
widget: lightweight, widget-free session reader
Reading "who is signed in" on a page required mounting the whole widget
(bitlogin.js + crypto worker + wasm, ~1.6MB) because that was the only
thing that restored the per-origin session. Multi-page sites paid the
full load on every page just to render a "Signed in as …" header.
Write a cleartext, public-only record ({ pubkey, npub, name? } — never
key material) beside the encrypted session in IndexedDB, in lockstep with
it: the worker writes it at the single persistSession() choke point,
self-heals it on restoreSession (so sessions cached before this feature
upgrade with no re-login), and clears it on logout, so a reported session
can never outlive the real one.
Add @bitlogin/widget/session -> bitlogin-session.js: a self-contained
~1KB gzip reader (zero imports, no worker/wasm) exposing
readCachedSession(): Promise<{ pubkey, npub, name? } | null>. Mirror the
same call on window.bitlogin.readCachedSession() for pages that already
load the full widget.
The private key stays in the AES-GCM record in sessionCache.ts, exactly
as protected as before. Reader falls through to null for every failure
mode (DB/store absent, IndexedDB blocked), which reads as "not signed in".
Addresses the top item of the Tessera integration feedback (docs/TODO.md).
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Claude-Session: https://claude.ai/code/session_01BXQCvJPA6sVSQ5j8uBd9bB
This commit is contained in:
1 parent
3fa8ea0b61
commit
c9791b9840
9 files changed
+394
-4
No files matched your search
@@ -103,6 +103,31 @@ decrypted key sits in this browser's IndexedDB until logout," the same
|
||||
tradeoff a NIP-07 extension already makes. Calling `logout()` (or the
|
||||
element's dashboard "Log out" button) clears it.
|
||||
|
||||
**Reading "who's signed in" without loading the widget.** A multi-page site
|
||||
often wants a persistent "Signed in as …" indicator in the header on *every*
|
||||
page, but only needs the full widget on the pages where a user actually signs
|
||||
in or manages their account. Loading `bitlogin.js` (plus the worker and wasm)
|
||||
on every page just to read the pubkey is a lot of bytes for a label. The
|
||||
`@bitlogin/widget/session` entry is a ~1 KB, dependency-free reader for exactly
|
||||
this:
|
||||
|
||||
```html
|
||||
<script type="module">
|
||||
import { readCachedSession } from "/vendor/bitlogin/bitlogin-session.js";
|
||||
const who = await readCachedSession(); // { pubkey, npub, name? } | null
|
||||
if (who) header.textContent = `Signed in as ${who.name ?? who.npub.slice(0, 12)}…`;
|
||||
</script>
|
||||
```
|
||||
|
||||
It reads a cleartext, **public-only** record (pubkey / npub / optional display
|
||||
name — never key material) that the widget writes beside its encrypted session
|
||||
and clears on logout, so it can't report a session that has ended. It cannot
|
||||
sign: when the user clicks "sign in" or "manage account," load the full
|
||||
`<bitlogin-auth>` then and defer the heavy cost until it's needed. The same
|
||||
call is also on the full widget as `window.bitlogin.readCachedSession()`, so
|
||||
pages that already load `bitlogin.js` can use it without a second import.
|
||||
Deploy `bitlogin-session.js` alongside `bitlogin.js`.
|
||||
|
||||
The `<bitlogin-auth>` element also mirrors `window.nostr` directly on the
|
||||
element instance (`getPublicKey`, `signEvent`, `nip44Encrypt`, `nip44Decrypt`)
|
||||
— prefer this over `window.nostr` when your host page holds a reference to
|
||||
|
||||
@@ -356,3 +356,54 @@ A developer or AI agent can take `bitlogin.js`, `bitgate.js`, `bitfeed.js`, and
|
||||
`bitblocks.js` and build a functional static Nostr application with almost no
|
||||
infrastructure or integration boilerplate, while each individual library
|
||||
remains usable without BitBlocks.
|
||||
|
||||
## Feedback from integrating into Tessera (2026-08-11)
|
||||
|
||||
Notes from embedding `@bitlogin/widget` in the Tessera static site. Grouped by
|
||||
how much they cost.
|
||||
|
||||
### High-value
|
||||
|
||||
- **[DONE 2026-08-11] Lightweight session-state read without the full widget.**
|
||||
The single biggest cost: to know *whether a user is signed in* on a page, you
|
||||
used to have to mount the whole widget (bitlogin.js + crypto worker + wasm,
|
||||
~1.6MB) because it was the only thing that restored the per-origin session.
|
||||
**Shipped:** the widget now writes a cleartext, public-only record
|
||||
(`{ pubkey, npub, name? }` — never key material) beside its encrypted session
|
||||
in IndexedDB and clears it on logout, and a new `@bitlogin/widget/session`
|
||||
entry point (`bitlogin-session.js`, ~1 KB gzip, zero deps, no worker/wasm)
|
||||
exposes `readCachedSession(): Promise<{ pubkey, npub, name? } | null>`. The
|
||||
same call is mirrored on `window.bitlogin.readCachedSession()` for pages that
|
||||
already load the full widget. See `src/publicSession.ts`, `src/session.ts`,
|
||||
and the README "Reading who's signed in without loading the widget" section.
|
||||
Remaining polish: have the widget also record the profile display `name` once
|
||||
fetched (currently `name` is only set when the caller supplies it), and add an
|
||||
`@bitlogin/widget/session` `.d.ts` (tracked under the Small item below).
|
||||
- **A headless session-restore API.** Related: expose session restore +
|
||||
`getPublicKey`/`signEvent` without the custom element, so a host app can wire
|
||||
its own UI and only load the visual widget for login/management. Right now the
|
||||
element is the only entry point.
|
||||
|
||||
### Medium
|
||||
|
||||
- **The unmount-kills-the-worker footfooter deserves a guardrail.** The docs
|
||||
warn that removing `<bitlogin-auth>` tears down the crypto worker and hangs
|
||||
every later signing call with no error. I hit exactly this. Consider: (a) a
|
||||
console.error when the element is disconnected while a session is active, and
|
||||
(b) `signEvent` rejecting with a clear "widget was unmounted" error instead of
|
||||
hanging forever. Silent infinite-hang is the worst failure mode.
|
||||
- **Styling hooks / CSS parts.** The widget renders in a shadow root, so a host
|
||||
can only frame it, not theme it. A few `::part()` hooks (button, panel, text)
|
||||
would let it match a host design system instead of looking pasted in. Tessera
|
||||
worked around this by wrapping it in a bordered panel with its own header.
|
||||
- **[RESOLVED — already the behavior] Session-change event on restore.**
|
||||
Confirmed: `bitlogin-login` already fires on silent session restore across a
|
||||
page load, not just interactive login, so a host listener needn't distinguish
|
||||
the two (README "Sessions persist across a page reload"). No new event needed.
|
||||
For pages that don't mount the widget at all, use `readCachedSession()` above.
|
||||
|
||||
### 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.
|
||||
@@ -6,6 +6,10 @@
|
||||
"type": "module",
|
||||
"description": "BitLogin embeddable login widget: a <bitlogin-auth> custom element plus a window.nostr NIP-07-compatible provider, backed by a dedicated crypto Web Worker.",
|
||||
"main": "./dist/bitlogin.js",
|
||||
"exports": {
|
||||
".": "./dist/bitlogin.js",
|
||||
"./session": "./dist/bitlogin-session.js"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "vite build",
|
||||
"dev": "vite build --watch",
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
/** Entry point: any static site includes this file and drops in <bitlogin-auth>. */
|
||||
import { BitLoginAuthElement } from "./element.js";
|
||||
import { getActiveSession, type ActiveSessionInfo } from "./globalSession.js";
|
||||
import { readCachedSession } from "./session.js";
|
||||
import type { PublicSessionInfo } from "./publicSession.js";
|
||||
import type { SignerMethod } from "./signers/types.js";
|
||||
|
||||
if (!customElements.get("bitlogin-auth")) {
|
||||
@@ -37,8 +39,18 @@ export interface BitLoginGlobal {
|
||||
*/
|
||||
activeMethod(): SignerMethod | null;
|
||||
/** The current session's public identity ({ method, publicKey, npub }), or
|
||||
* null. Public data only -- never key material. */
|
||||
* null. Public data only -- never key material. In-memory: reflects a session
|
||||
* established or restored on THIS page, so it's null until the widget mounts
|
||||
* and restores. For a persistent "signed in as …" across page loads without
|
||||
* loading the widget, use readCachedSession() (or import
|
||||
* "@bitlogin/widget/session"). */
|
||||
activeSession(): ActiveSessionInfo | null;
|
||||
/** Reads the persisted public identity for this origin ({ pubkey, npub,
|
||||
* name? }), or null. Cheap and widget-free: it reads the cleartext
|
||||
* public-session record rather than restoring the signer, so a header on any
|
||||
* page can show who's signed in without loading the crypto worker. Cannot
|
||||
* sign — load <bitlogin-auth> for that. */
|
||||
readCachedSession(): Promise<PublicSessionInfo | null>;
|
||||
}
|
||||
|
||||
declare global {
|
||||
@@ -77,9 +89,12 @@ window.bitlogin = {
|
||||
},
|
||||
activeSession(): ActiveSessionInfo | null {
|
||||
return getActiveSession();
|
||||
}
|
||||
},
|
||||
readCachedSession
|
||||
};
|
||||
|
||||
export { readCachedSession } from "./session.js";
|
||||
export type { PublicSessionInfo } from "./publicSession.js";
|
||||
export { BitLoginAuthElement } from "./element.js";
|
||||
export { WorkerClient } from "./worker/workerClient.js";
|
||||
export { createNip07Provider } from "./provider.js";
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import type { KeyValueStore } from "@bitlogin/core/storage";
|
||||
import {
|
||||
PUBLIC_SESSION_KEY,
|
||||
clearPublicSession,
|
||||
loadPublicSession,
|
||||
parsePublicSession,
|
||||
savePublicSession
|
||||
} from "./publicSession.js";
|
||||
|
||||
class TestStore implements KeyValueStore {
|
||||
values = new Map<string, string>();
|
||||
async get(key: string): Promise<string | undefined> {
|
||||
return this.values.get(key);
|
||||
}
|
||||
async set(key: string, value: string): Promise<void> {
|
||||
this.values.set(key, value);
|
||||
}
|
||||
async delete(key: string): Promise<void> {
|
||||
this.values.delete(key);
|
||||
}
|
||||
}
|
||||
|
||||
const info = { pubkey: "a".repeat(64), npub: "npub1example", name: "Ada" };
|
||||
|
||||
describe("public session record", () => {
|
||||
it("round-trips the public identity", async () => {
|
||||
const store = new TestStore();
|
||||
await savePublicSession(store, info);
|
||||
await expect(loadPublicSession(store)).resolves.toEqual(info);
|
||||
});
|
||||
|
||||
it("stores only public data -- never key material", async () => {
|
||||
const store = new TestStore();
|
||||
await savePublicSession(store, info);
|
||||
const raw = store.values.get(PUBLIC_SESSION_KEY) || "";
|
||||
// Sanity: the record is the pubkey/npub/name and nothing that looks like a
|
||||
// 32-byte hex private key field.
|
||||
expect(raw).toContain(info.pubkey);
|
||||
expect(raw).not.toMatch(/privatekey|nsec|everyday/i);
|
||||
});
|
||||
|
||||
it("omits name when the widget doesn't know one", async () => {
|
||||
const store = new TestStore();
|
||||
await savePublicSession(store, { pubkey: info.pubkey, npub: info.npub });
|
||||
const loaded = await loadPublicSession(store);
|
||||
expect(loaded).toEqual({ pubkey: info.pubkey, npub: info.npub });
|
||||
expect(loaded).not.toHaveProperty("name");
|
||||
});
|
||||
|
||||
it("returns null when nothing is stored", async () => {
|
||||
await expect(loadPublicSession(new TestStore())).resolves.toBeNull();
|
||||
});
|
||||
|
||||
it("clear removes the record so 'signed in' can't outlive logout", async () => {
|
||||
const store = new TestStore();
|
||||
await savePublicSession(store, info);
|
||||
await clearPublicSession(store);
|
||||
expect(store.values.has(PUBLIC_SESSION_KEY)).toBe(false);
|
||||
await expect(loadPublicSession(store)).resolves.toBeNull();
|
||||
});
|
||||
|
||||
describe("parsePublicSession", () => {
|
||||
it("accepts a well-formed record", () => {
|
||||
expect(parsePublicSession(JSON.stringify(info))).toEqual(info);
|
||||
});
|
||||
|
||||
it("treats corrupt or partial records as no session, not an error", () => {
|
||||
expect(parsePublicSession(null)).toBeNull();
|
||||
expect(parsePublicSession("")).toBeNull();
|
||||
expect(parsePublicSession("not json")).toBeNull();
|
||||
expect(parsePublicSession("{}")).toBeNull();
|
||||
expect(parsePublicSession(JSON.stringify({ pubkey: "a".repeat(64) }))).toBeNull();
|
||||
expect(parsePublicSession(JSON.stringify({ npub: "npub1x" }))).toBeNull();
|
||||
});
|
||||
|
||||
it("drops an empty name rather than surfacing a blank identity", () => {
|
||||
const parsed = parsePublicSession(JSON.stringify({ ...info, name: "" }));
|
||||
expect(parsed).toEqual({ pubkey: info.pubkey, npub: info.npub });
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,80 @@
|
||||
/**
|
||||
* Cleartext, public-only session identity — the companion to the AES-GCM
|
||||
* secret session in `worker/sessionCache.ts`.
|
||||
*
|
||||
* It holds ONLY data that is already public: the account's Nostr public key
|
||||
* (x-only hex), its npub, and an optional display name. None of it is secret —
|
||||
* the same pubkey appears on every event the user signs and on every relay —
|
||||
* so it is stored unencrypted, on purpose. That lets a read-only page answer
|
||||
* "who, if anyone, is signed in?" WITHOUT loading the crypto worker, wasm, or
|
||||
* the full widget (see `session.ts` for the lightweight reader).
|
||||
*
|
||||
* The everyday private key is NOT here and never will be: it stays in the
|
||||
* encrypted record in `sessionCache.ts`, exactly as protected as before. This
|
||||
* record is written and cleared in lockstep with that one (crypto worker), so
|
||||
* "signed in" here can never outlive the real session.
|
||||
*/
|
||||
import type { KeyValueStore } from "@bitlogin/core/storage";
|
||||
|
||||
/** IndexedDB key for the cleartext public-session record (sibling of
|
||||
* `bitlogin:session:v1`, which stays encrypted). */
|
||||
export const PUBLIC_SESSION_KEY = "bitlogin:session-public:v1";
|
||||
|
||||
export interface PublicSessionInfo {
|
||||
/** The account's Nostr public key, x-only, lowercase hex. */
|
||||
pubkey: string;
|
||||
/** NIP-19 npub encoding of the same key. */
|
||||
npub: string;
|
||||
/** Display name, when the widget knows one. Absent otherwise. */
|
||||
name?: string;
|
||||
}
|
||||
|
||||
/** Validates and narrows a raw stored string. Returns null for anything that
|
||||
* isn't a well-formed record, so a corrupt entry reads as "no session," never
|
||||
* an error. Shared by the store reader and the standalone reader so both agree
|
||||
* on the shape. */
|
||||
export function parsePublicSession(raw: string | null | undefined): PublicSessionInfo | null {
|
||||
if (!raw) return null;
|
||||
try {
|
||||
const parsed = JSON.parse(raw) as Partial<PublicSessionInfo>;
|
||||
if (typeof parsed.pubkey !== "string" || typeof parsed.npub !== "string") return null;
|
||||
return {
|
||||
pubkey: parsed.pubkey,
|
||||
npub: parsed.npub,
|
||||
...(typeof parsed.name === "string" && parsed.name ? { name: parsed.name } : {})
|
||||
};
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/** Best-effort write, mirroring `saveCachedSession`: a full disk or a browser
|
||||
* blocking IndexedDB must never break the login/rotate call that triggered it. */
|
||||
export async function savePublicSession(store: KeyValueStore, info: PublicSessionInfo): Promise<void> {
|
||||
try {
|
||||
const record: PublicSessionInfo = {
|
||||
pubkey: info.pubkey,
|
||||
npub: info.npub,
|
||||
...(info.name ? { name: info.name } : {})
|
||||
};
|
||||
await store.set(PUBLIC_SESSION_KEY, JSON.stringify(record));
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
export async function loadPublicSession(store: KeyValueStore): Promise<PublicSessionInfo | null> {
|
||||
try {
|
||||
return parsePublicSession(await store.get(PUBLIC_SESSION_KEY));
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export async function clearPublicSession(store: KeyValueStore): Promise<void> {
|
||||
try {
|
||||
await store.delete(PUBLIC_SESSION_KEY);
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
/**
|
||||
* Lightweight, read-only session reader — the cheap way to ask "who, if anyone,
|
||||
* is signed in?" on a page that isn't the login/manage surface.
|
||||
*
|
||||
* Import this INSTEAD of the full widget (`bitlogin.js`) when you only need to
|
||||
* show an identity — e.g. a "Signed in as …" chip in the header on every page.
|
||||
* It opens the same per-origin IndexedDB the widget writes and returns the
|
||||
* public identity WITHOUT loading the crypto worker, wasm, or bitlogin.js. It
|
||||
* cannot sign and never touches key material; when the user actually signs or
|
||||
* manages the account, load `<bitlogin-auth>` and pay the heavy cost then.
|
||||
*
|
||||
* ESM: import { readCachedSession } from "@bitlogin/widget/session";
|
||||
* const who = await readCachedSession(); // { pubkey, npub, name? } | null
|
||||
*
|
||||
* No build: <script type="module" src="bitlogin-session.js"></script>
|
||||
* const who = await window.bitlogin.readCachedSession();
|
||||
*
|
||||
* This file is deliberately dependency-free so it bundles to a few KB with no
|
||||
* shared chunk pulling in the widget.
|
||||
*/
|
||||
import type { PublicSessionInfo } from "./publicSession.js";
|
||||
|
||||
export type { PublicSessionInfo } from "./publicSession.js";
|
||||
|
||||
// The two constants and the parser below are duplicated from
|
||||
// storage/indexedDbStore.ts and publicSession.ts ON PURPOSE. Importing their
|
||||
// runtime would let rollup hoist a shared chunk, turning this "drop in one file"
|
||||
// reader into two. The DB names are a stable on-disk contract (indexedDbStore.ts
|
||||
// is their source of truth); the record shape is validated identically to
|
||||
// publicSession.parsePublicSession, which owns the write side.
|
||||
const DB_NAME = "bitlogin";
|
||||
const STORE_NAME = "kv";
|
||||
const PUBLIC_SESSION_KEY = "bitlogin:session-public:v1";
|
||||
|
||||
function parsePublicSession(raw: string | null | undefined): PublicSessionInfo | null {
|
||||
if (!raw) return null;
|
||||
try {
|
||||
const parsed = JSON.parse(raw) as Partial<PublicSessionInfo>;
|
||||
if (typeof parsed.pubkey !== "string" || typeof parsed.npub !== "string") return null;
|
||||
return {
|
||||
pubkey: parsed.pubkey,
|
||||
npub: parsed.npub,
|
||||
...(typeof parsed.name === "string" && parsed.name ? { name: parsed.name } : {})
|
||||
};
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/** Opens the widget's DB read-only and reads one string key. Resolves undefined
|
||||
* for every failure mode — DB absent, store absent (widget never ran here),
|
||||
* IndexedDB blocked — because "can't tell" is indistinguishable from "not
|
||||
* signed in" for this reader's purpose. Opened without a version so it never
|
||||
* triggers an upgrade against whatever version the widget is on. */
|
||||
function readRaw(key: string): Promise<string | undefined> {
|
||||
return new Promise((resolve) => {
|
||||
let request: IDBOpenDBRequest;
|
||||
try {
|
||||
request = indexedDB.open(DB_NAME);
|
||||
} catch {
|
||||
resolve(undefined);
|
||||
return;
|
||||
}
|
||||
// A brand-new DB (widget never ran on this origin) opens at version 1 with
|
||||
// no object stores; the contains() check below then returns undefined.
|
||||
request.onerror = () => resolve(undefined);
|
||||
request.onblocked = () => resolve(undefined);
|
||||
request.onsuccess = () => {
|
||||
const db = request.result;
|
||||
if (!db.objectStoreNames.contains(STORE_NAME)) {
|
||||
db.close();
|
||||
resolve(undefined);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const tx = db.transaction(STORE_NAME, "readonly");
|
||||
const getRequest = tx.objectStore(STORE_NAME).get(key);
|
||||
getRequest.onsuccess = () => resolve(getRequest.result as string | undefined);
|
||||
getRequest.onerror = () => resolve(undefined);
|
||||
tx.oncomplete = () => db.close();
|
||||
tx.onerror = () => {
|
||||
resolve(undefined);
|
||||
db.close();
|
||||
};
|
||||
} catch {
|
||||
db.close();
|
||||
resolve(undefined);
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/** The current signed-in identity for this origin, or null. Public data only —
|
||||
* never key material, and it cannot sign. */
|
||||
export async function readCachedSession(): Promise<PublicSessionInfo | null> {
|
||||
if (typeof indexedDB === "undefined") return null;
|
||||
return parsePublicSession(await readRaw(PUBLIC_SESSION_KEY));
|
||||
}
|
||||
|
||||
// No-build convenience: expose on window.bitlogin for the <script> path without
|
||||
// clobbering the full widget. Cast rather than augmenting the global type here;
|
||||
// index.ts owns the canonical BitLoginGlobal shape (readCachedSession included),
|
||||
// so whichever of the two loads, the method is present and identical.
|
||||
if (typeof window !== "undefined") {
|
||||
const w = window as unknown as { bitlogin?: Record<string, unknown> };
|
||||
w.bitlogin ??= {};
|
||||
if (typeof w.bitlogin.readCachedSession !== "function") {
|
||||
w.bitlogin.readCachedSession = readCachedSession;
|
||||
}
|
||||
}
|
||||
@@ -42,6 +42,7 @@ import {
|
||||
} from "@bitlogin/core/vault";
|
||||
import { IndexedDbKeyValueStore } from "../storage/indexedDbStore.js";
|
||||
import { saveCachedSession, loadCachedSession, clearCachedSession } from "./sessionCache.js";
|
||||
import { savePublicSession, clearPublicSession } from "../publicSession.js";
|
||||
import { SerialQueue } from "./serialQueue.js";
|
||||
import type { VaultConnectionSummary } from "./protocol.js";
|
||||
import type {
|
||||
@@ -162,6 +163,17 @@ async function persistSession(): Promise<void> {
|
||||
...(session.connectionVaultRoot ? { connectionVaultRoot: session.connectionVaultRoot } : {}),
|
||||
...(session.vaultKnown ? { vaultEnabled: session.connectionVaultRoot !== null } : {})
|
||||
});
|
||||
await persistPublicSession();
|
||||
}
|
||||
|
||||
// Writes the cleartext public-only identity (publicSession.ts) beside the
|
||||
// encrypted record so a read-only page can show "signed in as …" without
|
||||
// loading this worker. Public data only -- never key material. Kept in lockstep
|
||||
// with persistSession()/logout so it can't outlive the real session.
|
||||
async function persistPublicSession(): Promise<void> {
|
||||
if (!session.signer) return;
|
||||
const pubkey = session.signer.getPublicKey();
|
||||
await savePublicSession(store, { pubkey, npub: encodeNpub(pubkey) });
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -671,12 +683,17 @@ async function handle(action: string, payload: unknown): Promise<unknown> {
|
||||
// cache entry leaves it false so requireVault says "sign in again"
|
||||
// rather than falsely claiming the account has no vault.
|
||||
session.vaultKnown = cached.vaultEnabled !== undefined;
|
||||
// Self-heal: a session cached before the public-record feature has an
|
||||
// encrypted entry but no cleartext one. Writing it here means the very
|
||||
// next lightweight read sees the identity, no re-login required.
|
||||
await persistPublicSession();
|
||||
return { restored: true, everydayPublicKey: session.signer.getPublicKey(), accountId: cached.accountId };
|
||||
}
|
||||
|
||||
case "logout": {
|
||||
clearSession();
|
||||
await clearCachedSession(store);
|
||||
await clearPublicSession(store);
|
||||
return {};
|
||||
}
|
||||
|
||||
|
||||
@@ -7,14 +7,20 @@ export default defineConfig({
|
||||
emptyOutDir: true,
|
||||
target: "es2022",
|
||||
lib: {
|
||||
// Two independent entry points, each a self-contained ES module with a fixed,
|
||||
// Independent entry points, each a self-contained ES module with a fixed,
|
||||
// non-hashed filename: bitlogin.js (main thread) and cryptoWorker.js (worker).
|
||||
// workerClient.ts resolves the worker's URL at runtime relative to its own
|
||||
// import.meta.url, so both files just need to be deployed side by side --
|
||||
// at any path, not necessarily the site root (see workerClient.ts comment).
|
||||
//
|
||||
// bitlogin-session.js is the lightweight, read-only identity reader
|
||||
// (session.ts): a few KB with no worker/wasm, for pages that only need to
|
||||
// show who is signed in. It is dependency-free by design so it does not
|
||||
// share a chunk with the widget.
|
||||
entry: {
|
||||
bitlogin: resolve(__dirname, "src/index.ts"),
|
||||
cryptoWorker: resolve(__dirname, "src/worker/cryptoWorker.ts")
|
||||
cryptoWorker: resolve(__dirname, "src/worker/cryptoWorker.ts"),
|
||||
"bitlogin-session": resolve(__dirname, "src/session.ts")
|
||||
},
|
||||
formats: ["es"],
|
||||
fileName: (_format, entryName) => `${entryName}.js`
|
||||
|
||||
Reference in new issue
Block a user