@ghostlytools/cli 1.3.0 → 1.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -3
- package/dist/assets/{api-CrxVxtY1.js → api-Dq2QXQDk.js} +141 -40
- package/dist/assets/api-Dq2QXQDk.js.map +1 -0
- package/dist/assets/{api-BiaX6l6f.js → api-TiB5QSG9.js} +2 -2
- package/dist/assets/{apiKit-CajiDxdK.js → apiKit-CA2x5GJF.js} +7 -3
- package/dist/assets/apiKit-CA2x5GJF.js.map +1 -0
- package/dist/assets/{apps-CbfiCSIw.js → apps-BxV9i_Y4.js} +3 -4
- package/dist/assets/{apps-CbfiCSIw.js.map → apps-BxV9i_Y4.js.map} +1 -1
- package/dist/assets/{apps-t23gkc28.js → apps-C-T8jeji.js} +1 -1
- package/dist/assets/{arkade-D4BVJFEQ.js → arkade-C_qDmHnm.js} +2 -2
- package/dist/assets/{arkade-D4BVJFEQ.js.map → arkade-C_qDmHnm.js.map} +1 -1
- package/dist/assets/{backup-Cg52WeQi.js → backup-Bugnn3Ue.js} +1 -1
- package/dist/assets/{backup-BAMi2lAq.js → backup-CnMN2oUK.js} +8 -8
- package/dist/assets/backup-CnMN2oUK.js.map +1 -0
- package/dist/assets/bark_ffi_wasm_bg-EY2uc0Bx.js.map +1 -1
- package/dist/assets/bitcoind-CZchoh43.js +349 -0
- package/dist/assets/bitcoind-CZchoh43.js.map +1 -0
- package/dist/assets/bitcoindProvider-DGw3xBSa.js +97 -0
- package/dist/assets/bitcoindProvider-DGw3xBSa.js.map +1 -0
- package/dist/assets/bitcoindevkit_bg-B-iLWrmX.js.map +1 -1
- package/dist/assets/buttons-Bj7Vf7Pn.js +2 -0
- package/dist/assets/{buttons-CX1pKB0j.js → buttons-CvNBzh2S.js} +4 -5
- package/dist/assets/{buttons-CX1pKB0j.js.map → buttons-CvNBzh2S.js.map} +1 -1
- package/dist/assets/{endpoint-Dn7H8qE1.js → endpoint-BOTFMpfS.js} +44 -12
- package/dist/assets/endpoint-BOTFMpfS.js.map +1 -0
- package/dist/assets/{envelope-BvU0fLEt.js → envelope-WjingYSc.js} +2 -2
- package/dist/assets/{envelope-BvU0fLEt.js.map → envelope-WjingYSc.js.map} +1 -1
- package/dist/assets/{errors-my6VJR_0.js → errors-DO5z6h_l.js} +29 -1
- package/dist/assets/errors-DO5z6h_l.js.map +1 -0
- package/dist/assets/groupFiles-IEUW3DQz.js +3202 -0
- package/dist/assets/groupFiles-IEUW3DQz.js.map +1 -0
- package/dist/assets/{host-Cmd205Dc.js → host-CWt-jtqz.js} +41 -16
- package/dist/assets/host-CWt-jtqz.js.map +1 -0
- package/dist/assets/{hyperdhtRelay-CEmBpT0Q.js → hyperdhtRelay-CPYwSLLS.js} +2 -3
- package/dist/assets/{hyperdhtRelay-CEmBpT0Q.js.map → hyperdhtRelay-CPYwSLLS.js.map} +1 -1
- package/dist/assets/{irohWeb--YyEA-hX.js → irohWeb-4oChIOIp.js} +2 -3
- package/dist/assets/{irohWeb--YyEA-hX.js.map → irohWeb-4oChIOIp.js.map} +1 -1
- package/dist/assets/{main-BzNN6bkx.js → main-DbqcdShN.js} +142 -35
- package/dist/assets/main-DbqcdShN.js.map +1 -0
- package/dist/assets/{mainline-D5D5C-jT.js → mainline-ZkiOnevs.js} +2 -2
- package/dist/assets/{mainline-D5D5C-jT.js.map → mainline-ZkiOnevs.js.map} +1 -1
- package/dist/assets/{networkReason-DxZWHw6v.js → networkReason-CetKkTPo.js} +2 -2
- package/dist/assets/{networkReason-DxZWHw6v.js.map → networkReason-CetKkTPo.js.map} +1 -1
- package/dist/assets/nwc-CgkqyvsE.js +612 -0
- package/dist/assets/nwc-CgkqyvsE.js.map +1 -0
- package/dist/assets/nwcProvider-4eVz_8HP.js +77 -0
- package/dist/assets/nwcProvider-4eVz_8HP.js.map +1 -0
- package/dist/assets/{profiles-B6F4oa9K.js → profiles-BHc9pEYg.js} +2 -2
- package/dist/assets/{profiles-B6F4oa9K.js.map → profiles-BHc9pEYg.js.map} +1 -1
- package/dist/assets/registry-DlAKO0Fy.js +3793 -0
- package/dist/assets/registry-DlAKO0Fy.js.map +1 -0
- package/dist/assets/registry-OBJBnHVQ.js +2 -0
- package/dist/assets/resolve-CouHhCpJ.js +591 -0
- package/dist/assets/resolve-CouHhCpJ.js.map +1 -0
- package/dist/assets/{server-CBCEYKMN.js → server-Ss0gJ-nw.js} +184 -1181
- package/dist/assets/server-Ss0gJ-nw.js.map +1 -0
- package/dist/assets/{src-DhfGgAUu.js → src-DDzSERyc.js} +2249 -72
- package/dist/assets/src-DDzSERyc.js.map +1 -0
- package/dist/assets/{turnRefs-ChEfgbSX.js → turnRefs-B87dtrHM.js} +2 -2
- package/dist/assets/{turnRefs-ChEfgbSX.js.map → turnRefs-B87dtrHM.js.map} +1 -1
- package/dist/assets/types-C4_KafL_.js +122 -0
- package/dist/assets/types-C4_KafL_.js.map +1 -0
- package/dist/assets/{usdt-C1Xao2xH.js → usdt-Fj8CQO2z.js} +3 -3
- package/dist/assets/{usdt-C1Xao2xH.js.map → usdt-Fj8CQO2z.js.map} +1 -1
- package/dist/ghostly.mjs +1 -1
- package/package.json +1 -1
- package/dist/assets/api-CrxVxtY1.js.map +0 -1
- package/dist/assets/apiKit-CajiDxdK.js.map +0 -1
- package/dist/assets/backup-BAMi2lAq.js.map +0 -1
- package/dist/assets/buttons-CWl2yNJu.js +0 -2
- package/dist/assets/endpoint-Dn7H8qE1.js.map +0 -1
- package/dist/assets/errors-my6VJR_0.js.map +0 -1
- package/dist/assets/groupFiles-BNTSDaoq.js +0 -7685
- package/dist/assets/groupFiles-BNTSDaoq.js.map +0 -1
- package/dist/assets/groupFiles-DBbjOLuD.js +0 -2088
- package/dist/assets/groupFiles-DBbjOLuD.js.map +0 -1
- package/dist/assets/host-Cmd205Dc.js.map +0 -1
- package/dist/assets/main-BzNN6bkx.js.map +0 -1
- package/dist/assets/server-CBCEYKMN.js.map +0 -1
- package/dist/assets/src-DhfGgAUu.js.map +0 -1
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"groupFiles-BNTSDaoq.js","names":["hex","redact","store","redact","store","redact","HEX64","hex","unhex","nip19Decode","cache","exactKeys","scopeFor","callbackParams","currentHost","hostOf","DID_DOCUMENT_MAX_BYTES","exactKeys"],"sources":["../../../browser/src/engine/paymentAdapters/away.ts","../../../browser/src/engine/paymentAdapters/modeGate.ts","../../../browser/src/shared/ice.ts","../../../browser/src/engine/paymentAdapters/providers/breezDatabases.ts","../../../browser/src/engine/paymentAdapters/providers/types.ts","../../../browser/src/engine/paymentAdapters/providers/cashuMint.ts","../../../browser/src/engine/paymentAdapters/providers/sources.ts","../../../browser/src/engine/paymentAdapters/providers/lightningService.ts","../../../browser/src/engine/paymentAdapters/providers/recoveryPhrase.ts","../../../browser/src/engine/paymentAdapters/providers/lightningCards.ts","../../../browser/src/proofs/verify.ts","../../../browser/src/plugins/bundled.ts","../../../browser/src/plugins/registry.ts","../../../browser/src/proofs/bitcoinWallets.ts","../../../browser/src/proofs/providers/bitcoin.ts","../../../browser/src/proofs/nostr.ts","../../../browser/src/proofs/providers/nostr.ts","../../../browser/src/proofs/domain.ts","../../../browser/src/proofs/providers/domain.ts","../../../browser/src/proofs/openpgp.ts","../../../browser/src/proofs/providers/openpgp.ts","../../../browser/src/proofs/sshForges.ts","../../../browser/src/proofs/providers/ssh.ts","../../../browser/src/host.ts","../../../browser/src/proofs/oidc/providers.ts","../../../browser/src/proofs/oidc/jwks.ts","../../../browser/src/proofs/oidc/verify.ts","../../../browser/src/proofs/oidc/flow.ts","../../../browser/src/proofs/providers/oidc.ts","../../../browser/src/proofs/did.ts","../../../browser/src/proofs/providers/did.ts","../../../browser/src/proofs/atproto/resolve.ts","../../../browser/src/proofs/atproto/oauth.ts","../../../browser/src/proofs/providers/atproto.ts","../../../browser/src/proofs/pubky.ts","../../../browser/src/proofs/providers/pubky.ts","../../../browser/src/proofs/testing.ts","../../../browser/src/proofs/registry.ts","../../../browser/src/shared/storedFiles.ts","../../../browser/src/shared/types.ts","../../../browser/src/engine/paymentAdapters/walletInstances.ts","../../../browser/src/shared/paymentIds.ts","../../../browser/src/shared/replies.ts","../../../browser/src/engine/groupFiles.ts"],"sourcesContent":["import { engineText } from \"@ghostly/core\";\n\n/*\n * Wallets at home on another device (WISP 06 § Wallets that stay home): their database lives there, and no other\n * device opens them. The engine says which, by wallet id (`<type>:<network>`, `lightning:<network>:<card>`), before\n * any wallet opens; each wallet asks here before it opens its SDK, so no path that opens one (a start, a retry, a\n * restore, an unlock) opens a wallet that is away.\n */\n\nlet away = new Map<string, string>();\n\n/** The wallets away from this device, by wallet id, with the name of their home device (\"\" when it has none here). */\nexport function setAwayWallets(next: ReadonlyMap<string, string>): void {\n away = new Map(next);\n}\n\n/** The home device's name of a wallet away from this device, or undefined when it is here. */\nexport function awayFrom(id: string): string | undefined {\n return away.get(id);\n}\n\n/** A wallet that cannot be used here: it is at home on another device. */\nexport class WalletAwayError extends Error {\n constructor(wallet: string, device: string) {\n super(device ? engineText(\"walletAway\", { wallet, device }) : engineText(\"walletAwayUnnamed\", { wallet }));\n this.name = \"WalletAwayError\";\n }\n}\n\n/**\n * Whether a single-writer wallet (Ark, Bark, Spark and the Breez source, Fedimint) may open its SDK now: the engine\n * says, from a good turn read under 60 seconds old (WISP 06 § Wallets). Every open asks, the retries of each wallet\n * included, so a device replaced while it was offline does not reopen one on a retry.\n */\nlet turnGate: () => Promise<boolean> = async () => true;\nexport function setSingleWriterGate(gate: (() => Promise<boolean>) | null): void {\n turnGate = gate ?? (async () => true);\n}\n\n/** Throws (the wallet tries again later, as it does when its server does not answer) without a fresh good turn read. */\nexport async function requireTurn(): Promise<void> {\n if (!(await turnGate().catch(() => false))) throw new Error(\"Checking which device is active\");\n}\n\n/** Throws when the wallet `id` is at home on another device. */\nexport function refuseAway(id: string, wallet: string): void {\n const device = awayFrom(id);\n if (device !== undefined) throw new WalletAwayError(wallet, device);\n}\n","import type { WalletNetwork } from \"@ghostly/core\";\nimport type { WalletMode } from \"../../shared/mints\";\n\n/** A wallet of the mode being left stopped opening: the switch goes ahead without waiting for its network. */\nexport class ModeChanged extends Error {\n constructor() { super(\"The wallets switched mode\"); this.name = \"ModeChanged\"; }\n}\n\nexport const networkLabel = (network: WalletNetwork) => network === \"mainnet\" ? \"Mainnet\" : \"Testnet\";\n\n/**\n * What was asked of one network's wallet belongs to the other network's (a backup, a chain): nothing was changed.\n * `network` is where it belongs, so the engine can hand it to that wallet instead.\n */\nexport class WrongNetworkError extends Error {\n constructor(readonly network: WalletNetwork, message: string) { super(message); this.name = \"WrongNetworkError\"; }\n}\n\n/**\n * Opening a wallet waits on its network (an Ark server, an RPC), which can take minutes or never answer.\n * A mode switch must not wait behind that: `within` gives up on the wait as soon as the mode changes, and\n * whatever still arrives afterwards is handed to `dispose`. Nothing is saved before that wait ends, so\n * giving up leaves the profile as it was. Every wait on the network inside a wallet's queue goes through it,\n * reading a balance too, or the switch, queued behind it, waits anyway.\n */\nexport class ModeGate {\n constructor(private target: WalletMode = \"mainnet\") {}\n private controller = new AbortController();\n /** The mode its owner works in, once it says so: a wait that starts while a switch away from it is queued ends at once. */\n private current?: WalletMode;\n\n /** A switch to `mode` was asked for: waits for a wallet of another mode end now. */\n switching(mode: WalletMode) {\n if (this.target === mode) return;\n this.target = mode;\n this.controller.abort();\n this.controller = new AbortController();\n }\n\n /** The owner's switch to `mode` is applied (its queue reached it): waits are for this mode again. */\n entered(mode: WalletMode) { this.current = mode; }\n\n /**\n * A creation took too long: the waits under way end now, and so does every wait that starts until `resume`\n * (a creation still sealing its seed has not reached its server yet, and must not wait there either).\n */\n interrupt() { this.cut = true; this.controller.abort(); this.controller = new AbortController(); }\n resume() { this.cut = false; }\n private cut = false;\n\n /** The wallet stopped: every wait ends now, and any later one at once. */\n close() { this.closed = true; this.controller.abort(); }\n private closed = false;\n\n within<T>(work: Promise<T>, dispose?: (value: T) => unknown): Promise<T> {\n const signal = this.controller.signal;\n return new Promise<T>((resolve, reject) => {\n const give = () => { reject(new ModeChanged()); void work.then((value) => dispose?.(value), () => {}); };\n if (this.closed || this.cut || (this.current && this.current !== this.target)) return give();\n signal.addEventListener(\"abort\", give, { once: true });\n work.then(\n (value) => { signal.removeEventListener(\"abort\", give); if (!signal.aborted) resolve(value); },\n (error) => { signal.removeEventListener(\"abort\", give); if (!signal.aborted) reject(error); },\n );\n });\n }\n}\n","import type { IceServerSetting } from \"./types\";\n\n/**\n * What is wrong with a TURN/STUN server, or null. Browsers refuse to create any WebRTC connection when\n * one server in the list is malformed, so a bad entry here would silently cut off calls and files.\n */\nexport function iceServerProblem(server: IceServerSetting): string | null {\n // A setting from outside the form (the CLI, `ghostly engine`, an import) may have no urls at all: before, a TypeError.\n if (typeof server?.urls !== \"string\") return \"Each ICE server needs urls: one address, or several comma-separated (turn:host:port)\";\n const urls = server.urls.split(/[\\s,]+/).filter(Boolean);\n if (!urls.length) return \"Enter the TURN server address\";\n for (const url of urls) {\n if (!/^(stun|turns?):[^\\s/?#]+(\\?transport=(udp|tcp))?$/i.test(url)) return `“${url}” is not a TURN or STUN address (turn:host:port)`;\n if (/^turns?:/i.test(url) && (!server.username?.trim() || !server.credential?.trim())) return \"A TURN server needs its username and credential\";\n }\n return null;\n}\n\n/**\n * A server as WebRTC writes it (`urls` a list, as every TURN guide does) in this setting's own form: the addresses\n * comma-separated. Anything else is left as it is, for `iceServerProblem` to say what is wrong.\n */\nexport function iceServerSetting(server: IceServerSetting): IceServerSetting {\n const urls: unknown = server?.urls;\n if (!Array.isArray(urls) || !urls.every((url) => typeof url === \"string\")) return server;\n return { ...server, urls: urls.join(\", \") };\n}\n","import { sha256 } from \"@noble/hashes/sha2.js\";\nimport { databaseName } from \"../../../shared/idb\";\n\n/**\n * Where the Breez SDK keeps a wallet on this device, and which profile it belongs to.\n *\n * The SDK keeps its IndexedDB databases under one storage name (`breezDatabaseFiles`). That name is one per **profile,\n * network and seed**: the Spark rail and a Breez Lightning card of the same phrase in one profile are one SDK instance\n * over one database (`openBreez`), two phrases are two wallets, and a profile restored as a copy on the same device\n * starts from the phrase with a database of its own. Two SDK instances over one database would each think the leaves\n * are theirs.\n *\n * Before this, the name came from the network and the seed only, so every profile of the origin holding that phrase\n * opened the same database. Such a database is kept, under its old name, for **one** profile: the first to open the\n * phrase on this device after the change claims it in a small database of the device (`ghostly-breez`), outside every\n * profile database (a backup copies a profile's database whole, so a mark in it would be the copy's too). Any other\n * profile gets a new name. The same register lists every name a profile uses, so deleting a profile deletes its Breez\n * databases without opening its sealed phrases.\n */\nexport type BreezDatabaseNetwork = \"mainnet\" | \"regtest\";\nconst netOf = (network: string): BreezDatabaseNetwork => (network === \"bitcoin\" || network === \"mainnet\" ? \"mainnet\" : \"regtest\");\nconst hex = (bytes: Uint8Array) => Array.from(bytes, (b) => b.toString(16).padStart(2, \"0\")).join(\"\");\nconst digest = (text: string) => hex(sha256(new TextEncoder().encode(text))).slice(0, 16);\n\n/** What a Breez database name looks like, old or new: nothing about the seed or the profile shows in it. */\nexport const BREEZ_DATABASE = /^ghostly-breez-(mainnet|regtest)-[0-9a-f]{16}$/;\n\n/** The name every profile of the origin used for this phrase before databases were per profile. */\nexport const breezLegacyName = (network: string, mnemonic: string) => `ghostly-breez-${netOf(network)}-${digest(`ghostly-breez:${netOf(network)}:${mnemonic}`)}`;\n\n/** The name of this phrase's Breez database in one profile (`space`: the profile's peer database, WISP 04). */\nexport const breezDatabaseName = (network: string, mnemonic: string, space: string = databaseName()) =>\n `ghostly-breez-${netOf(network)}-${digest(`ghostly-breez:${space}:${netOf(network)}:${mnemonic}`)}`;\n\n/**\n * The databases the SDK keeps under one storage name. SDK 0.26 roots them there: `<name>/<network>/<identity>` and\n * `<name>/<network>/<identity>-tree`, the identity read from the seed inside the SDK. Older builds kept `<name>`\n * itself. A browser without `indexedDB.databases()` cannot list them: only the bare names are known there.\n */\nexport async function breezDatabaseFiles(name: string): Promise<string[]> {\n const files = [name, `${name}-tree`];\n if (typeof indexedDB.databases !== \"function\") return files;\n const under = (await indexedDB.databases()).map((d) => d.name ?? \"\").filter((file) => file.startsWith(`${name}/`));\n return [...new Set([...files, ...under])];\n}\n\ninterface Entry { name: string; space: string; network: BreezDatabaseNetwork; legacy?: true }\nconst REGISTER = \"ghostly-breez\", STORE = \"databases\";\n\nconst request = <T>(r: IDBRequest<T>) => new Promise<T>((resolve, reject) => { r.onsuccess = () => resolve(r.result); r.onerror = () => reject(r.error); });\nfunction openRegister(): Promise<IDBDatabase> {\n return new Promise((resolve, reject) => {\n const open = indexedDB.open(REGISTER, 1);\n open.onupgradeneeded = () => { if (!open.result.objectStoreNames.contains(STORE)) open.result.createObjectStore(STORE); };\n open.onsuccess = () => resolve(open.result);\n open.onerror = () => reject(open.error);\n open.onblocked = () => reject(new Error(\"The Breez register is blocked\"));\n });\n}\nasync function withRegister<T>(mode: IDBTransactionMode, run: (store: IDBObjectStore) => Promise<T> | T): Promise<T> {\n const db = await openRegister();\n try {\n const tx = db.transaction(STORE, mode);\n const done = new Promise<void>((resolve, reject) => { tx.oncomplete = () => resolve(); tx.onerror = tx.onabort = () => reject(tx.error); });\n const result = await run(tx.objectStore(STORE));\n await done;\n return result;\n } finally { db.close(); }\n}\n\n/**\n * Whether the SDK keeps a wallet under this storage name on this device (`breezDatabaseFiles`). A browser that cannot\n * list databases is answered yes: the first profile then takes the old name whether or not it is there, which is\n * safe, since only one profile ever has it.\n */\nasync function exists(name: string): Promise<boolean> {\n if (typeof indexedDB.databases !== \"function\") return true;\n return (await indexedDB.databases()).some((d) => d.name === name || !!d.name?.startsWith(`${name}/`));\n}\n\n/**\n * The Breez database this profile opens for this phrase: the old shared one when this profile owns it (or is the\n * first to claim it), else the profile's own. Noted in the register either way. A device whose IndexedDB cannot be\n * read gets the profile's own name: never a database another profile may use.\n */\nexport async function breezDatabase(network: string, mnemonic: string, space: string = databaseName()): Promise<string> {\n const fresh = breezDatabaseName(network, mnemonic, space);\n if (typeof indexedDB === \"undefined\") return fresh;\n const legacy = breezLegacyName(network, mnemonic);\n try {\n const legacyHere = await exists(legacy);\n return await withRegister(\"readwrite\", async (store) => {\n // Both reads and the write in one transaction: two profiles opening at once cannot both claim the old one.\n const [owner, own] = await Promise.all([request(store.get(legacy)) as Promise<Entry | undefined>, request(store.get(fresh)) as Promise<Entry | undefined>]);\n if (owner?.space === space) return legacy;\n if (!own && !owner && legacyHere) { store.put({ name: legacy, space, network: netOf(network), legacy: true } satisfies Entry, legacy); return legacy; }\n if (!own) store.put({ name: fresh, space, network: netOf(network) } satisfies Entry, fresh);\n return fresh;\n });\n } catch { return fresh; }\n}\n\n/** The names this profile has noted for this phrase (nothing is claimed): what removing the wallet deletes. */\nexport async function breezDatabasesFor(network: string, mnemonic: string, space: string = databaseName()): Promise<string[]> {\n const names = [breezLegacyName(network, mnemonic), breezDatabaseName(network, mnemonic, space)];\n return (await breezDatabasesOf(space)).filter((name) => names.includes(name));\n}\n\n/**\n * The name this profile uses for this phrase, read without claiming anything: the one it noted (`breezDatabase`), else\n * the profile's own name. What a handoff deletes and compares (WISP 06 § Wallets): never the old shared database of\n * another profile that has not opened the phrase since the change.\n */\nexport async function breezDatabaseInUse(network: string, mnemonic: string, space: string = databaseName()): Promise<string> {\n return (await breezDatabasesFor(network, mnemonic, space))[0] ?? breezDatabaseName(network, mnemonic, space);\n}\n\n/** Every Breez database name noted for a profile of this device. */\nexport async function breezDatabasesOf(space: string): Promise<string[]> {\n if (typeof indexedDB === \"undefined\") return [];\n try { return ((await withRegister(\"readonly\", (store) => request(store.getAll()))) as Entry[]).filter((e) => e?.space === space && BREEZ_DATABASE.test(e.name)).map((e) => e.name); }\n catch { return []; }\n}\n\nconst deleteDatabase = (name: string) => new Promise<void>((resolve) => {\n try { const r = indexedDB.deleteDatabase(name); r.onsuccess = r.onerror = r.onblocked = () => resolve(); } catch { resolve(); }\n});\n\nconst deleteStrictly = (name: string) => new Promise<void>((resolve, reject) => {\n const r = indexedDB.deleteDatabase(name);\n r.onsuccess = () => resolve();\n r.onerror = () => reject(r.error ?? new Error(`${name} was not deleted`));\n r.onblocked = () => reject(new Error(`${name} is still open`));\n});\n\n/**\n * Deletes one Breez wallet's databases (all the SDK keeps under the name) and its note in the register. Anything else\n * is refused. `strict` (a handoff, WISP 06 § Wallets): a delete that is blocked or fails rejects and the note stays,\n * so the caller tries again.\n */\nexport async function dropBreezDatabase(name: string, options: { strict?: boolean } = {}): Promise<void> {\n if (!BREEZ_DATABASE.test(name) || typeof indexedDB === \"undefined\") return;\n for (const file of await breezDatabaseFiles(name).catch(() => [name, `${name}-tree`])) await (options.strict ? deleteStrictly(file) : deleteDatabase(file));\n await withRegister(\"readwrite\", (store) => { store.delete(name); }).catch(() => {});\n}\n\n/** Deletes every Breez database a profile used on this device (the profile is being deleted). */\nexport async function dropBreezDatabasesOf(space: string): Promise<void> {\n for (const name of await breezDatabasesOf(space)) await dropBreezDatabase(name);\n}\n","import type { BitcoinNetwork } from \"@ghostly/core\";\nimport type { WalletMode } from \"../../../shared/mints\";\n\n/**\n * What every wallet provider (a source of Lightning or of on-chain Bitcoin) shares: where it runs, how\n * it is configured, and the one distinction that keeps money safe. See PROVIDERS.md.\n */\n\n/** Where Ghostly runs: the web app, the browser extension (engine in an offscreen document), the Tauri app. */\nexport type ProviderPlatform = \"web\" | \"extension\" | \"desktop\";\nexport const PROVIDER_PLATFORMS: readonly ProviderPlatform[] = [\"web\", \"extension\", \"desktop\"];\nexport type ProviderKind = \"lightning\" | \"onchain\";\n/** The chain a provider's sats live on. Only `bitcoin` is real money; the others belong to the Testnet mode. */\nexport type ProviderNetwork = BitcoinNetwork;\nexport const PROVIDER_NETWORKS: readonly ProviderNetwork[] = [\"bitcoin\", \"signet\", \"testnet\", \"regtest\", \"mutinynet\"];\nexport const networkMode = (network: ProviderNetwork): WalletMode => (network === \"bitcoin\" ? \"mainnet\" : \"testnet\");\n\n/**\n * One field of a provider's configuration form. The wallet card renders these; a provider that needs\n * more registers its own form component instead (apps/ui/src/components/wallet/providers/forms.ts).\n * `secret` fields (an NWC URI, a macaroon, a rune, an API key, a password) are sealed before they are\n * stored, never shown again, never put in the engine state, and never logged.\n */\nexport interface ProviderField {\n name: string;\n label: string;\n kind: \"text\" | \"url\" | \"secret\" | \"select\" | \"textarea\";\n placeholder?: string;\n help?: string;\n optional?: boolean;\n /** `select` only. */\n options?: { value: string; label: string }[];\n /** Pre-filled per mode, e.g. a public test server for Testnet. */\n defaults?: Partial<Record<WalletMode, string>>;\n /**\n * Values offered under a `url` or `text` field (a list to pick from, anything may still be typed), each\n * only while the other fields hold `when`: e.g. a public Esplora server per network.\n */\n suggestions?: { value: string; label: string; when?: Record<string, string> }[];\n /**\n * A server address that can be changed on a saved source without its secrets being typed again, and\n * without changing whose money it is: an Esplora server of a BDK wallet (the wallet is the phrase). The\n * source's \"Change server\" edits these fields only. Never mark a field that picks another account (a\n * node's URL: another node is other money).\n */\n changeable?: boolean;\n}\n\n/** What the person typed, split by the field kinds: `config` is shown back, `secrets` never is. */\nexport interface ProviderSettings {\n config: Record<string, string>;\n secrets: Record<string, string>;\n}\n\n/** What the engine hands a provider's factory. */\nexport interface ProviderHost {\n platform: ProviderPlatform;\n /** The wallet mode the source is for: its provider must be on a network of this mode. */\n mode: WalletMode;\n /**\n * Reserved for the built-in Cashu mint source: the engine's own wallet. It is not part of the SDK, and\n * no other provider should read it.\n */\n cashu?: unknown;\n /** Reserved for the built-in Fedimint source: the engine's Fedimint wallet (its joined federations). Not part of the SDK. */\n fedimint?: unknown;\n /** Ends when the source is replaced, the mode switches or the engine stops: abort long waits on it. */\n signal: AbortSignal;\n /**\n * Desktop only: calls a command of the Tauri app (apps/desktop), for what a WebView cannot do (a pinned\n * certificate, no CORS, a raw socket). Absent on the web and in the extension.\n */\n invoke?: <T>(command: string, args: Record<string, unknown>) => Promise<T>;\n}\n\n/**\n * A provider module's export. Registering a provider is adding its module and one line in\n * `registry.ts`; everything else (storage, sealing, per-mode sources, the card, journals) is shared.\n */\nexport interface ProviderDescriptor<P> {\n id: string;\n label: string;\n kind: ProviderKind;\n /** One line under the name in the source picker: what it is and who holds the money. */\n description: string;\n /** Networks it can run on. A mode lists only providers with a network of that mode. */\n networks: readonly ProviderNetwork[];\n platforms: readonly ProviderPlatform[];\n fields: readonly ProviderField[];\n /** Custodial (someone else holds the sats) or still experimental: said in the picker. */\n custodial?: boolean;\n experimental?: boolean;\n /** Checks the form before anything is contacted; throws a message fit for the person. */\n validate?(settings: ProviderSettings, mode: WalletMode): void;\n /** Connects. The engine then asks `info()` and refuses a network of the other mode. */\n create(settings: ProviderSettings, host: ProviderHost): Promise<P>;\n}\n\n/** A provider can be offered here: it runs on this platform and on a network of this mode. */\nexport const offeredIn = (descriptor: { platforms: readonly ProviderPlatform[]; networks: readonly ProviderNetwork[] }, platform: ProviderPlatform, mode: WalletMode) =>\n descriptor.platforms.includes(platform) && descriptor.networks.some((network) => networkMode(network) === mode);\n\nconst FIELD_KINDS = [\"text\", \"url\", \"secret\", \"select\", \"textarea\"];\n/** What is stored with the profile and sent on the wire in journals: stable, short, lowercase. */\nexport const PROVIDER_ID = /^[a-z][a-z0-9-]{0,31}$/;\n\n/**\n * Problems with a provider descriptor, empty when it is well-formed. The registry refuses a descriptor\n * with any; the contract suite checks the same list, so an author sees them before the app does.\n */\nexport function providerDescriptorProblems(d: ProviderDescriptor<unknown>): string[] {\n const problems: string[] = [];\n if (!PROVIDER_ID.test(d.id ?? \"\")) problems.push(\"id must match ^[a-z][a-z0-9-]{0,31}$\");\n if (!d.label?.trim()) problems.push(\"label is empty\");\n if (d.kind !== \"lightning\" && d.kind !== \"onchain\") problems.push(\"kind must be lightning or onchain\");\n if (!d.description?.trim()) problems.push(\"description is empty: say what it is and who holds the money\");\n if (!Array.isArray(d.networks) || !d.networks.length) problems.push(\"no networks\");\n else for (const n of d.networks) if (!PROVIDER_NETWORKS.includes(n)) problems.push(`unknown network ${String(n)}`);\n if (!Array.isArray(d.platforms) || !d.platforms.length) problems.push(\"no platforms\");\n else for (const p of d.platforms) if (!PROVIDER_PLATFORMS.includes(p)) problems.push(`unknown platform ${String(p)}`);\n if (!Array.isArray(d.fields)) problems.push(\"fields must be a list (empty when there is nothing to configure)\");\n else {\n const names = d.fields.map((f) => f.name);\n if (new Set(names).size !== names.length) problems.push(\"field names are not unique\");\n for (const f of d.fields) {\n if (!/^[a-zA-Z][a-zA-Z0-9_-]{0,63}$/.test(f.name ?? \"\")) problems.push(`field ${String(f.name)} has an invalid name`);\n if (!f.label?.trim()) problems.push(`field ${f.name} has no label`);\n if (!FIELD_KINDS.includes(f.kind)) problems.push(`field ${f.name} has an unknown kind`);\n if (f.kind === \"select\" && !f.options?.length) problems.push(`field ${f.name} is a select without options`);\n if (f.suggestions !== undefined && (!Array.isArray(f.suggestions) || f.suggestions.some((o: { value?: unknown; label?: unknown } | undefined) => typeof o?.value !== \"string\" || typeof o?.label !== \"string\"))) problems.push(`field ${f.name} has malformed suggestions`);\n if (f.changeable && (f.kind === \"secret\" || f.kind === \"select\")) problems.push(`field ${f.name} cannot be changeable: only a server address can`);\n }\n }\n if (typeof d.create !== \"function\") problems.push(\"create is not a function\");\n if (d.validate !== undefined && typeof d.validate !== \"function\") problems.push(\"validate is not a function\");\n return problems;\n}\n\n/** The serializable part of a descriptor, for the UI (a function cannot cross the extension's port). */\nexport type ProviderDescriptorView = Omit<ProviderDescriptor<unknown>, \"create\" | \"validate\">;\n/** Only these fields reach the UI, whatever else a provider module keeps on its descriptor. */\nexport const describeProvider = ({ id, label, kind, description, networks, platforms, fields, custodial, experimental }: ProviderDescriptor<unknown>): ProviderDescriptorView =>\n ({ id, label, kind, description, networks, platforms, fields, custodial, experimental });\n\n/**\n * The provider proved that nothing left the wallet (refused before sending, no route found before any\n * HTLC, a transaction that was never broadcast). Safe to try again, with this or another source.\n *\n * Any other error from a spend means the outcome is UNKNOWN: the payment may be in flight or done. It is\n * recorded as such and only reconciled (asked about), never retried. When in doubt, do not throw this.\n */\nexport class NothingSpentError extends Error {\n constructor(message: string) { super(message); this.name = \"NothingSpentError\"; }\n}\n\n/**\n * The source could not be reached: no answer, a time-out, a refused connection, a server error. The\n * engine keeps trying by itself (with backoff) and shows the source as connecting meanwhile.\n */\nexport class SourceUnreachableError extends Error {\n constructor(message: string, cause?: unknown) { super(message); this.name = \"SourceUnreachableError\"; if (cause !== undefined) Object.assign(this, { cause }); }\n}\n\n/**\n * A setting of the source is wrong (a server on another network, a phrase that makes no wallet): trying\n * again changes nothing until the setting does. The engine shows it as unavailable at once.\n */\nexport class SourceConfigError extends Error {\n constructor(message: string, cause?: unknown) { super(message); this.name = \"SourceConfigError\"; if (cause !== undefined) Object.assign(this, { cause }); }\n}\n\n/** What went wrong connecting a source, by name (a plugin bundles its own copies of these classes). */\nexport type SourceProblem = \"unreachable\" | \"config\" | \"other\";\nexport const sourceProblem = (error: unknown): SourceProblem =>\n error instanceof Error && error.name === \"SourceUnreachableError\" ? \"unreachable\" : error instanceof Error && error.name === \"SourceConfigError\" ? \"config\" : \"other\";\n\n/**\n * Whether an error says nothing was spent. By name, not by class: a provider built outside the app (an\n * SDK plugin bundled with its own copy of `NothingSpentError`) must be understood the same way.\n */\nexport const isNothingSpentError = (error: unknown): error is NothingSpentError =>\n error instanceof NothingSpentError || (error instanceof Error && error.name === \"NothingSpentError\");\n\n/**\n * An error message fit to show and store: every secret the source was given is blanked out, in case a\n * library echoed a URI or a key back.\n */\nexport function redact(error: unknown, secrets: Record<string, string> = {}): string {\n let message = error instanceof Error ? error.message : String(error);\n for (const value of Object.values(secrets)) if (value && value.length >= 4) message = message.split(value).join(\"•••\");\n return message.slice(0, 300);\n}\n","import { decodeBolt11, engineError } from \"@ghostly/core\";\nimport type { WalletMode } from \"../../../shared/mints\";\nimport type { CashuWallet } from \"../../wallet\";\nimport type { LightningInvoice, LightningPaymentRef, LightningProvider, LightningProviderDescriptor } from \"./lightning\";\nimport { NothingSpentError, PROVIDER_PLATFORMS } from \"./types\";\n\nexport const CASHU_MINT_SOURCE = \"cashu-mint\";\n\ninterface MintRef { mint: string; quote: string }\nconst parseRef = (ref: string | undefined): MintRef => {\n const parsed = JSON.parse(ref ?? \"null\") as MintRef | null;\n if (!parsed?.mint || !parsed.quote) throw new Error(\"Unknown Cashu quote\");\n return parsed;\n};\n\n/**\n * Lightning through the Cashu mints: invoices are mint quotes that turn into ecash once paid, and\n * invoices are paid by melting ecash. The default source, and what Lightning was before sources.\n *\n * The Cashu wallet already polls its quotes and pending melts, mints and books them and says so\n * (`onQuotePaid`, `onMeltResolved`): the engine forwards those, and never polls this source itself.\n */\nexport class CashuMintLightning implements LightningProvider {\n readonly capabilities = { receive: true, send: true, balance: true, lookup: true };\n readonly settlesItself = true;\n /** The melt quote shown to the person is the one paid: quoting again could change the fee. */\n private readonly quotes = new Map<string, { quote: string; mint: string; amount: number; feeReserve: number }>();\n\n constructor(private readonly wallet: CashuWallet, private readonly mode: WalletMode) {}\n\n async info() {\n // Test mints issue invoices for worthless sats (often with a mainnet prefix): the mode says which it is.\n return { network: this.mode === \"mainnet\" ? \"bitcoin\" as const : \"testnet\" as const, balance: (await this.wallet.view(this.mode)).balance };\n }\n\n async createInvoice(amount: number): Promise<LightningInvoice> {\n const quote = await this.wallet.receiveLightning(amount, undefined, this.mode);\n const decoded = decodeBolt11(quote.invoice);\n if (!decoded?.paymentHash) throw new Error(\"The mint returned an invoice that cannot be read\");\n return { invoice: quote.invoice, paymentHash: decoded.paymentHash, amount, expiresAt: quote.expiresAt ?? decoded.expiresAt * 1000, ref: JSON.stringify({ mint: quote.mint, quote: quote.quote }) };\n }\n\n async invoiceStatus(invoice: LightningInvoice) {\n const { mint, quote } = parseRef(invoice.ref);\n // A test mint's invoice reads paid by itself: until a payer says it paid, it is open (see CashuWallet.vouch).\n const state = await this.wallet.isHeld(quote) ? \"UNPAID\" : await this.wallet.mintQuoteState(mint, quote);\n if (state === \"PAID\" || state === \"ISSUED\") return { state: \"paid\" as const, amount: invoice.amount };\n return { state: invoice.expiresAt < Date.now() ? \"expired\" as const : \"open\" as const };\n }\n\n /** A real melt quote: its fee reserve is what paying may cost at most. It is kept for `payInvoice`. */\n async estimateFee(invoice: string) {\n const quote = await this.wallet.quoteInvoice(invoice, this.mode);\n this.quotes.set(invoice.trim().toLowerCase(), quote);\n return quote.feeReserve;\n }\n\n /** The quote behind a fee just shown, when there is one: `payQuote` pays exactly that. */\n quoteFor(invoice: string) {\n return this.quotes.get(invoice.trim().toLowerCase());\n }\n\n /** The melt quote `payInvoice` pays, when its fee was shown first: what the mint is asked about later. */\n paymentRef(invoice: string) {\n const quote = this.quotes.get(invoice.trim().toLowerCase());\n return quote ? JSON.stringify({ mint: quote.mint, quote: quote.quote } satisfies MintRef) : undefined;\n }\n\n /**\n * A payment the app was cut off from (the tab closed while it was being paid). A melt written down is the wallet's\n * to settle and report. With none, the mint's word on the quote decides: paid, or never melted (the melt is written\n * down before the mint is handed anything, so an unpaid quote with no melt spent nothing).\n */\n async interruptedPayment(payment: LightningPaymentRef) {\n if (await this.wallet.meltInFlight(payment.invoice)) return \"pending\" as const;\n if (!payment.ref) return \"unknown\" as const;\n const { mint, quote } = parseRef(payment.ref);\n const state = await this.wallet.meltQuoteState(mint, quote);\n // Asked again: a melt written while the mint was being asked is the wallet's to settle.\n if (await this.wallet.meltInFlight(payment.invoice)) return \"pending\" as const;\n return state === \"PAID\" ? \"paid\" as const : state === \"PENDING\" ? \"pending\" as const : \"failed\" as const;\n }\n\n async payInvoice(invoice: string, maxFee: number, note?: string) {\n const key = invoice.trim().toLowerCase();\n let quote = this.quotes.get(key);\n // Nothing reaches the mint's melt before `payQuote`, and it throws only when the sats did not leave.\n try {\n quote ??= await this.wallet.quoteInvoice(invoice, this.mode);\n if (quote.feeReserve > maxFee) throw engineError(\"lightningFeeTooHigh\", { fee: quote.feeReserve });\n } catch (error) {\n throw new NothingSpentError(error instanceof Error ? error.message : String(error));\n }\n this.quotes.delete(key);\n const ref = JSON.stringify({ mint: quote.mint, quote: quote.quote });\n let paid: boolean;\n try { paid = await this.wallet.payQuote(quote.quote, quote.mint, note); }\n catch (error) { throw new NothingSpentError(error instanceof Error ? error.message : String(error)); }\n return paid ? { state: \"paid\" as const, ref } : { state: \"pending\" as const, ref };\n }\n\n async paymentStatus(payment: LightningPaymentRef) {\n const { mint, quote } = parseRef(payment.ref);\n const state = await this.wallet.meltQuoteState(mint, quote);\n return { state: state === \"PAID\" ? \"paid\" as const : state === \"PENDING\" ? \"pending\" as const : \"failed\" as const };\n }\n\n async close() { this.quotes.clear(); }\n}\n\nexport const cashuMint: LightningProviderDescriptor = {\n id: CASHU_MINT_SOURCE,\n label: \"Cashu mints\",\n kind: \"lightning\",\n description: \"Invoices are paid into, and paid from, your Cashu balance. The mints hold the sats.\",\n networks: [\"bitcoin\", \"testnet\"],\n platforms: PROVIDER_PLATFORMS,\n fields: [],\n custodial: true,\n async create(_settings, host) { return new CashuMintLightning(host.cashu as CashuWallet, host.mode); },\n};\n","import { STORES, store, transact, wrap } from \"../../../shared/idb\";\nimport type { WalletMode } from \"../../../shared/mints\";\nimport { ModeChanged, ModeGate } from \"../modeGate\";\nimport { newDeviceKey, sealSeed, unsealSeed, type EncryptedSeed } from \"../persistence\";\nimport { requireTurn } from \"../away\";\nimport { describeProvider, networkMode, offeredIn, redact, sourceProblem, type ProviderDescriptor, type ProviderDescriptorView, type ProviderHost, type ProviderKind, type ProviderNetwork, type ProviderSettings, type SourceProblem } from \"./types\";\n\n/**\n * What is stored for a source, under `<kind>Source-<mode>` (or `<kind>Source-<mode>-<key>`, one Lightning card of\n * several) in the settings store: never in the Settings object (which the UI sees), and its secrets sealed with a\n * device key, like the Ark and USDT seeds.\n */\ninterface StoredSource {\n providerId: string;\n config: Record<string, string>;\n secrets?: EncryptedSeed;\n deviceKey?: string;\n savedAt: number;\n}\n\n/** The last balance a source read, under `<kind>SourceSeen-<mode>`: shown while it reconnects after a restart. */\ninterface SeenBalance { providerId: string; balance?: number; unconfirmed?: number; at: number }\n\nexport interface SourceView {\n mode: WalletMode;\n /** Absent: no source (on-chain has none until one is chosen). */\n providerId?: string;\n label?: string;\n /** The built-in default of this kind (the Cashu mints for Lightning). */\n isDefault?: boolean;\n custodial?: boolean;\n /** The non-secret settings, as saved. */\n config?: Record<string, string>;\n /** The names of the secret fields that are saved. Their values never leave the engine. */\n secrets?: string[];\n status: \"none\" | \"connecting\" | \"ready\" | \"error\";\n network?: ProviderNetwork;\n alias?: string;\n /** Spendable sats, when the source says. */\n balance?: number;\n /** What the source holds was read once since it connected (or it says it shows none): until then `balance` says nothing. */\n read?: true;\n /** On-chain: sats in the mempool. */\n unconfirmed?: number;\n /**\n * The balance shown is the last one read (at this time), not a fresh one: the source is not connected.\n * Absent once it reads again.\n */\n balanceAt?: number;\n /**\n * While `connecting` after a failed attempt, why it failed (it is being tried again by itself); while\n * `error`, why the source is unavailable.\n */\n error?: string;\n /** What kind of failure `error` is: no answer (tried again by itself), a setting to change, or other. */\n problem?: SourceProblem;\n /** Attempts that failed in a row, and when the next one starts (it also starts on Retry, or on waking). */\n failures?: number;\n retryAt?: number;\n /** The providers that can be picked here (this platform, this mode), in registry order. */\n offered: ProviderDescriptorView[];\n}\n\ninterface Connectable { info(): Promise<{ network: ProviderNetwork; alias?: string }>; close(): Promise<void> }\n\nexport interface SourcesOptions<P> {\n kind: ProviderKind;\n /** The network this source belongs to: Mainnet and Testnet each have their own, both connected at once. */\n network: WalletMode;\n descriptors: () => readonly ProviderDescriptor<P>[];\n /** Read each time a source connects. */\n host: () => Omit<ProviderHost, \"mode\" | \"signal\">;\n changed: () => void;\n /** Used while nothing is stored for the mode. */\n defaultId?: string;\n /** One of several sources of this kind and mode (a Lightning card): its own storage keys. Absent: the mode's one. */\n key?: string;\n /** Asked before the source of a mode changes: a refusal (money in flight through it) stops the change. */\n refuseReplacing?: (providerId: string, mode: WalletMode) => Promise<string | undefined>;\n /** Balance and the like, read after connecting and then every `refreshMs`. */\n refresh?: (provider: P) => Promise<Partial<Pick<SourceView, \"balance\" | \"unconfirmed\">>>;\n refreshMs?: number;\n /** How long to wait before the next attempt after `failures` failed ones (tests shorten it). */\n backoff?: (failures: number) => number;\n}\n\n/** Where a source is stored: the mode's one, or one of several (`key`). */\nexport const sourceKey = (kind: ProviderKind, mode: WalletMode, key?: string) => `${kind}Source-${mode}${key ? `-${key}` : \"\"}`;\nconst seenKey = (kind: ProviderKind, mode: WalletMode, key?: string) => `${kind}SourceSeen-${mode}${key ? `-${key}` : \"\"}`;\n\n/**\n * A source that does not answer is tried again by itself: 2 s, 4 s, 8 s… up to 5 minutes, ±20 % so\n * that many apps coming back online do not knock at once.\n */\nexport const RETRY_MAX_MS = 5 * 60_000;\nexport function retryDelay(failures: number, random = Math.random): number {\n const base = Math.min(RETRY_MAX_MS, 2_000 * 2 ** Math.max(0, failures - 1));\n return Math.round(base * (0.8 + 0.4 * random()));\n}\n/**\n * \"Unavailable\" only once it has failed this many times in a row for at least this long: a slow server at\n * start-up is \"Connecting…\". A wrong setting is unavailable at once (waiting does not fix it).\n */\nexport const SUSTAINED_FAILURES = 3;\nexport const SUSTAINED_MS = 30_000;\n\n/**\n * Gives up on a connection when its signal aborts (a change of settings interrupts an attempt), even if the\n * provider does not listen to it; what still arrives afterwards is closed.\n */\nfunction abortable<P extends Connectable>(work: Promise<P>, signal: AbortSignal): Promise<P> {\n return new Promise<P>((resolve, reject) => {\n const give = () => { reject(new Error(\"The connection attempt was interrupted\")); void work.then((p) => p.close().catch(() => {}), () => {}); };\n if (signal.aborted) return give();\n signal.addEventListener(\"abort\", give, { once: true });\n work.then(\n (value) => { signal.removeEventListener(\"abort\", give); if (!signal.aborted) resolve(value); },\n (error) => { signal.removeEventListener(\"abort\", give); if (!signal.aborted) reject(error); },\n );\n });\n}\n\n/**\n * The active source of one kind (Lightning, on-chain) of one network: Mainnet and Testnet each have their\n * own, both connected at once. Every change is serialized.\n */\nexport class ProviderSources<P extends Connectable> {\n private readonly mode: WalletMode;\n private stored?: StoredSource;\n private descriptor?: ProviderDescriptor<P>;\n private provider?: P;\n /** The connected source's secrets, only to blank them out of error messages. */\n private secrets: Record<string, string> = {};\n private controller = new AbortController();\n private readonly gate = new ModeGate();\n private queue: Promise<unknown> = Promise.resolve();\n private connecting?: Promise<void>;\n private timer?: ReturnType<typeof setTimeout>;\n private retry?: ReturnType<typeof setTimeout>;\n /** Failed attempts in a row, and since when: what makes a failure sustained. */\n private failures = 0;\n private failingSince?: number;\n private seen?: SeenBalance;\n private stopped = false;\n view: SourceView = { mode: \"mainnet\", status: \"none\", offered: [] };\n\n constructor(private readonly options: SourcesOptions<P>) { this.mode = options.network; this.view = { mode: options.network, status: \"none\", offered: [] }; }\n\n private serial<T>(run: () => Promise<T>): Promise<T> { const next = this.queue.then(run, run); this.queue = next.catch(() => {}); return next; }\n private find(id: string) { return this.options.descriptors().find((d) => d.id === id); }\n private offered() { return this.options.descriptors().filter((d) => offeredIn(d, this.options.host().platform, this.mode)).map((d) => describeProvider(d as ProviderDescriptor<unknown>)); }\n private async load(mode: WalletMode) {\n const settings = await store(STORES.settings, \"readonly\");\n const [stored, seen] = await Promise.all([wrap<StoredSource | undefined>(settings.get(sourceKey(this.options.kind, mode, this.options.key))), wrap<SeenBalance | undefined>(settings.get(seenKey(this.options.kind, mode, this.options.key)))]);\n this.stored = stored; this.seen = seen;\n this.failures = 0; this.failingSince = undefined;\n }\n\n /** The source of the mode (stored, else the default), not connected yet: with the last balance it read. */\n private idle(): SourceView {\n const id = this.stored?.providerId ?? this.options.defaultId;\n const descriptor = id ? this.find(id) : undefined;\n const seen = id && this.seen?.providerId === id ? this.seen : undefined;\n return {\n mode: this.mode, offered: this.offered(), providerId: id, label: descriptor?.label ?? id, custodial: descriptor?.custodial,\n isDefault: !!id && id === this.options.defaultId, config: this.stored?.config,\n secrets: descriptor?.fields.filter((f) => f.kind === \"secret\" && this.stored?.secrets).map((f) => f.name),\n status: !id ? \"none\" : descriptor ? \"connecting\" : \"error\",\n error: id && !descriptor ? `${id} is not available in this version of Ghostly` : undefined,\n ...(seen ? { balance: seen.balance, unconfirmed: seen.unconfirmed, balanceAt: seen.at } : {}),\n };\n }\n\n async start() { await this.load(this.mode); this.view = this.idle(); }\n\n /** One saved secret of the source (a handoff asks whether an LND card pins its certificate, and a Breez phrase), or undefined. */\n async secret(name: string): Promise<string | undefined> {\n const stored = this.stored;\n if (!stored?.secrets) return undefined;\n const secrets = JSON.parse(await unsealSeed(stored.secrets, stored.deviceKey ?? \"\")) as Record<string, unknown>;\n const value = secrets[name];\n return typeof value === \"string\" && value ? value : undefined;\n }\n\n /** The list of providers changed (a plugin registered): the picker is told, nothing reconnects. */\n refreshOffered() { this.view = { ...this.view, offered: this.offered() }; this.options.changed(); }\n\n /**\n * Connects the mode's source in the background, and keeps trying while it cannot be reached: \"Connecting…\"\n * (with the last balance it read) until the failure is sustained, then \"Unavailable\", still retrying with\n * backoff. `eager` (Retry, waking up): shows \"Connecting…\" again while it tries.\n */\n ensureReady(eager = false): Promise<void> {\n return this.connecting ??= this.serial(() => this.connect(eager)).finally(() => { this.connecting = undefined; });\n }\n\n /** Retry, now: the wait before the next attempt is skipped. Resolves when the attempt ends. */\n retryNow(): Promise<void> {\n if (this.provider || this.stopped) return Promise.resolve();\n clearTimeout(this.retry);\n return this.ensureReady(true);\n }\n\n /**\n * The app is back (in front, online, the computer woke): a source that is not connected is tried now, and\n * a connected one reads its balance now.\n */\n wake() {\n if (this.stopped) return;\n if (this.provider) { void this.refresh(); return; }\n if (this.failures && !this.connecting) void this.retryNow();\n }\n\n private async connect(eager = false) {\n clearTimeout(this.retry);\n if (this.stopped || this.provider) return;\n const id = this.stored?.providerId ?? this.options.defaultId;\n const descriptor = id ? this.find(id) : undefined;\n if (!id || !descriptor) { this.view = this.idle(); this.options.changed(); return; }\n if (eager && this.view.status === \"error\") { this.view = { ...this.view, status: \"connecting\", retryAt: undefined }; this.options.changed(); }\n const signal = this.controller.signal;\n let secrets: Record<string, string> = {};\n try {\n if (this.stored?.secrets) secrets = JSON.parse(await unsealSeed(this.stored.secrets, this.stored.deviceKey ?? \"\")) as Record<string, string>;\n const { provider, network, alias } = await this.open(descriptor, { config: this.stored?.config ?? {}, secrets });\n this.descriptor = descriptor; this.provider = provider; this.secrets = secrets;\n this.failures = 0; this.failingSince = undefined;\n this.view = { ...this.idle(), status: \"ready\", network, alias };\n this.options.changed();\n void this.refresh();\n } catch (error) {\n // Stopped, another mode, or interrupted by a change of settings: whoever did it connects next.\n if (this.stopped || error instanceof ModeChanged || signal.aborted) return;\n const now = Date.now();\n this.failures++;\n this.failingSince ??= now;\n const problem = sourceProblem(error);\n const sustained = problem === \"config\" || (this.failures >= SUSTAINED_FAILURES && now - this.failingSince >= SUSTAINED_MS);\n const delay = (this.options.backoff ?? retryDelay)(this.failures);\n this.view = { ...this.idle(), status: sustained ? \"error\" : \"connecting\", error: `Could not connect to ${descriptor.label}: ${redact(error, secrets)}`, problem, failures: this.failures, retryAt: now + delay };\n this.options.changed();\n this.retry = setTimeout(() => void this.ensureReady(), delay);\n }\n }\n\n /** Creates a provider and checks it is on a network of this mode before anything uses it. */\n private async open(descriptor: ProviderDescriptor<P>, settings: ProviderSettings, controller = this.controller) {\n // The Breez source shares its database with the Spark wallet of its phrase: one writer, after a fresh turn read.\n if (descriptor.id === \"breez\") await requireTurn();\n const signal = controller.signal;\n const provider = await this.gate.within(abortable(descriptor.create(settings, { ...this.options.host(), mode: this.mode, signal }), signal), (p) => p.close());\n try {\n const { network, alias } = await this.gate.within(provider.info());\n if (!descriptor.networks.includes(network)) throw new Error(`it runs on ${network}, which ${descriptor.label} does not support here`);\n if (networkMode(network) !== this.mode) throw new Error(this.mode === \"mainnet\" ? `it runs on ${network}, a test network: set it up as a Testnet wallet instead` : \"it runs on Bitcoin, real money: set it up as a Mainnet wallet instead\");\n return { provider, network, alias };\n } catch (error) { await provider.close().catch(() => {}); throw error; }\n }\n\n /**\n * Makes `providerId` this mode's source, with the values typed in its form. It is connected and its\n * network checked first; only then are its settings saved (secrets sealed) and the old source closed.\n */\n set(providerId: string, values: Record<string, string>): Promise<void> {\n const interrupted = this.interrupt();\n return this.afterChange(interrupted, this.serial(async () => {\n const { descriptor, settings } = this.settingsOf(providerId, values);\n await this.guard();\n await this.apply(descriptor, settings, false);\n }));\n }\n\n /** The form's values as a source's settings, checked: its secrets apart. Throws what to fix. */\n settingsOf(providerId: string, values: Record<string, string>): { descriptor: ProviderDescriptor<P>; settings: ProviderSettings } {\n const descriptor = this.find(providerId);\n if (!descriptor || !offeredIn(descriptor, this.options.host().platform, this.mode)) throw new Error(\"That source is not available here\");\n const settings: ProviderSettings = { config: {}, secrets: {} };\n for (const field of descriptor.fields) {\n const value = (values[field.name] ?? \"\").trim();\n if (!value && !field.optional) throw new Error(`Enter ${field.label.toLowerCase()}`);\n if (field.kind === \"select\" && value && !field.options?.some((o) => o.value === value)) throw new Error(`Choose ${field.label.toLowerCase()}`);\n if (value) (field.kind === \"secret\" ? settings.secrets : settings.config)[field.name] = value;\n }\n descriptor.validate?.(settings, this.mode);\n return { descriptor, settings };\n }\n\n /**\n * Whether the saved source is this provider with these very settings (the same wallet added twice). Its secrets are\n * unsealed here to compare, and never leave.\n */\n async holds(providerId: string, settings: ProviderSettings): Promise<boolean> {\n const stored = this.stored;\n if (!stored || stored.providerId !== providerId) return false;\n const same = (a: Record<string, string>, b: Record<string, string>) => {\n const keys = Object.keys(a).sort();\n return JSON.stringify(keys) === JSON.stringify(Object.keys(b).sort()) && keys.every((k) => a[k] === b[k]);\n };\n if (!same(stored.config, settings.config)) return false;\n const secrets = stored.secrets ? JSON.parse(await unsealSeed(stored.secrets, stored.deviceKey ?? \"\")) as Record<string, string> : {};\n return same(secrets, settings.secrets);\n }\n\n /**\n * Changes the `changeable` fields (a server address) of this mode's saved source, keeping its sealed\n * secrets and whose money it is: the wallet stays, only where it reads the chain changes. Connected and\n * checked first, like `set`; a refusal leaves the saved source as it was.\n */\n reconfigure(values: Record<string, string>): Promise<void> {\n const interrupted = this.interrupt();\n return this.afterChange(interrupted, this.serial(async () => {\n const stored = this.stored;\n const descriptor = stored ? this.find(stored.providerId) : undefined;\n if (!stored || !descriptor) throw new Error(\"There is no saved source to change\");\n const changeable = descriptor.fields.filter((f) => f.changeable);\n if (!changeable.length) throw new Error(`${descriptor.label} has no server to change`);\n const config = { ...stored.config };\n for (const field of changeable) {\n if (!(field.name in values)) continue;\n const value = (values[field.name] ?? \"\").trim();\n if (!value && !field.optional) throw new Error(`Enter ${field.label.toLowerCase()}`);\n if (value) config[field.name] = value; else delete config[field.name];\n }\n const secrets = stored.secrets ? JSON.parse(await unsealSeed(stored.secrets, stored.deviceKey ?? \"\")) as Record<string, string> : {};\n const settings: ProviderSettings = { config, secrets };\n descriptor.validate?.(settings, this.mode);\n await this.apply(descriptor, settings, true);\n }));\n }\n\n /** A change refused after interrupting an attempt: the saved source goes on connecting as before. */\n private afterChange(interrupted: boolean, change: Promise<void>): Promise<void> {\n return change.finally(() => { if (interrupted && !this.provider && !this.stopped) void this.ensureReady(); });\n }\n\n /**\n * A connection attempt under way (or waiting to try again) gives way to a change of settings, instead of\n * holding it for its time-out. Says whether there was one.\n */\n private interrupt(): boolean {\n if (this.provider || (!this.connecting && !this.failures)) return false;\n clearTimeout(this.retry);\n this.controller.abort(); this.controller = new AbortController();\n return true;\n }\n\n /** Connects `settings`, then saves them and replaces the current source. `same`: the same wallet, reconfigured. */\n private async apply(descriptor: ProviderDescriptor<P>, settings: ProviderSettings, same: boolean) {\n const providerId = descriptor.id;\n let opened: Awaited<ReturnType<typeof this.open>>;\n // Its own signal: closing the source it replaces aborts that one's, not this one's.\n const controller = new AbortController();\n try { opened = await this.open(descriptor, settings, controller); }\n catch (error) { throw error instanceof ModeChanged ? error : new Error(`Could not connect to ${descriptor.label}: ${redact(error, settings.secrets)}`); }\n const deviceKey = newDeviceKey();\n const stored: StoredSource = { providerId, config: settings.config, savedAt: Date.now(),\n ...(Object.keys(settings.secrets).length ? { secrets: await sealSeed(JSON.stringify(settings.secrets), deviceKey), deviceKey } : {}) };\n // The default with nothing to configure is what an empty mode already uses: nothing to store.\n const plain = providerId === this.options.defaultId && !Object.keys(settings.config).length && !stored.secrets;\n // Another wallet's last balance is not this one's.\n if (!same) this.seen = undefined;\n try {\n await transact([STORES.settings], (s) => {\n if (plain) s[STORES.settings].delete(sourceKey(this.options.kind, this.mode, this.options.key)); else s[STORES.settings].put(stored, sourceKey(this.options.kind, this.mode, this.options.key));\n if (!same) s[STORES.settings].delete(seenKey(this.options.kind, this.mode, this.options.key));\n });\n }\n catch (error) { await opened.provider.close().catch(() => {}); throw error; }\n await this.disconnect();\n this.controller = controller;\n this.failures = 0; this.failingSince = undefined;\n this.stored = plain ? undefined : stored; this.descriptor = descriptor; this.provider = opened.provider; this.secrets = settings.secrets;\n this.view = { ...this.idle(), status: \"ready\", network: opened.network, alias: opened.alias };\n this.options.changed();\n void this.refresh();\n }\n\n /** Forgets this mode's source (and its sealed secrets): the default, if there is one, takes over. */\n clear(): Promise<void> {\n this.interrupt();\n return this.serial(async () => {\n await this.guard();\n await transact([STORES.settings], (s) => { s[STORES.settings].delete(sourceKey(this.options.kind, this.mode, this.options.key)); s[STORES.settings].delete(seenKey(this.options.kind, this.mode, this.options.key)); });\n await this.disconnect(true);\n this.stored = undefined; this.seen = undefined;\n this.failures = 0; this.failingSince = undefined;\n this.view = this.idle(); this.options.changed();\n }).then(() => this.ensureReady());\n }\n\n /**\n * Forgets this source for good (its card is removed): its settings, sealed secrets and last balance go, it is\n * closed, and nothing takes over. Refused while a payment through it has not ended.\n */\n forget(): Promise<void> {\n this.interrupt();\n return this.serial(async () => {\n await this.guard();\n await transact([STORES.settings], (s) => { s[STORES.settings].delete(sourceKey(this.options.kind, this.mode, this.options.key)); s[STORES.settings].delete(seenKey(this.options.kind, this.mode, this.options.key)); });\n this.stopped = true;\n this.gate.close();\n await this.disconnect(true);\n this.stored = undefined; this.seen = undefined;\n this.view = { mode: this.mode, status: \"none\", offered: [] };\n });\n }\n\n private async guard() {\n const current = this.stored?.providerId ?? this.options.defaultId;\n const refusal = current ? await this.options.refuseReplacing?.(current, this.mode) : undefined;\n if (refusal) throw new Error(refusal);\n }\n\n /** The connected source, waiting for a connection under way. Throws when there is none. */\n async use(): Promise<{ provider: P; descriptor: ProviderDescriptor<P> }> {\n if (!this.provider) await this.ensureReady();\n if (!this.provider || !this.descriptor) throw new Error(this.view.status === \"none\" ? `No ${this.options.kind === \"onchain\" ? \"Bitcoin\" : \"Lightning\"} source is set up` : this.view.error ?? \"The source is still connecting\");\n return { provider: this.provider, descriptor: this.descriptor };\n }\n\n /** A change waiting on its connection gives up now (a creation that took too long); nothing is saved. */\n cutShort() { this.gate.interrupt(); }\n resume() { this.gate.resume(); }\n\n /** The connected source's id, when one is connected right now. */\n get activeId(): string | undefined { return this.provider ? this.descriptor?.id : undefined; }\n get active(): P | undefined { return this.provider; }\n\n async refresh() {\n clearTimeout(this.timer);\n const provider = this.provider;\n if (!provider) return;\n // A kind of source with nothing to read shows no balance: what it shows is read.\n if (!this.options.refresh) { if (!this.view.read) { this.view = { ...this.view, read: true }; this.options.changed(); } return; }\n const before = this.view;\n try {\n const details = await this.options.refresh(provider);\n if (provider !== this.provider) return;\n this.view = { ...this.view, ...details, read: true, balanceAt: undefined, error: undefined };\n this.remember(details);\n } catch (error) {\n if (provider !== this.provider) return;\n this.view = { ...this.view, error: `Could not read the balance: ${redact(error, this.secrets)}` };\n }\n // A read that says what the view already did (the same balance, or none) changes nothing: every 15 s it made a\n // whole wallet view and engine state, with no wallet at all.\n const after = this.view as unknown as Record<string, unknown>, was = before as unknown as Record<string, unknown>;\n if (Object.keys({ ...was, ...after }).some(key => was[key] !== after[key])) this.options.changed();\n if (this.provider) this.timer = setTimeout(() => void this.refresh(), this.options.refreshMs ?? 15_000);\n }\n\n /** Keeps the balance read, for the next start: written only when it changed. */\n private remember({ balance, unconfirmed }: Partial<Pick<SourceView, \"balance\" | \"unconfirmed\">>) {\n const providerId = this.descriptor?.id;\n // The default (the Cashu mints) connects without a network: nothing to show meanwhile.\n if (!providerId || providerId === this.options.defaultId || (balance === undefined && unconfirmed === undefined)) return;\n const was = this.seen;\n if (was?.providerId === providerId && was.balance === balance && was.unconfirmed === unconfirmed) return;\n const seen: SeenBalance = { providerId, balance, unconfirmed, at: Date.now() }, key = seenKey(this.options.kind, this.mode, this.options.key);\n this.seen = seen;\n void transact([STORES.settings], (s) => { s[STORES.settings].put(seen, key); }).catch(() => {});\n }\n\n /** `forget`: the source is removed, so what it keeps on this device goes too (a provider's own `forget`). */\n private async disconnect(forget = false) {\n clearTimeout(this.timer); clearTimeout(this.retry);\n this.controller.abort(); this.controller = new AbortController();\n const provider = this.provider as (P & { forget?: () => Promise<void> }) | undefined;\n this.provider = undefined; this.descriptor = undefined; this.secrets = {};\n await (forget && provider?.forget ? provider.forget() : provider?.close())?.catch(() => {});\n }\n\n /** Shutting down: nothing reconnects afterwards. */\n async stop() { this.stopped = true; this.gate.close(); await this.serial(() => this.disconnect()); }\n}\n","import { decodeBolt11, engineError, requestLnurlInvoice, resolveLightningDestination, type LnurlPayParams, type LnurlSuccessAction } from \"@ghostly/core\";\nimport { STORES, store, transact, wrap } from \"../../../shared/idb\";\nimport { BITCOIN_INVOICE_ON_TESTNET, type WalletMode } from \"../../../shared/mints\";\nimport { CASHU_MINT_SOURCE, CashuMintLightning } from \"./cashuMint\";\nimport type { LightningInvoice, LightningProvider, LightningProviderDescriptor } from \"./lightning\";\nimport { ProviderSources, type SourceView } from \"./sources\";\nimport { isNothingSpentError, redact, type ProviderHost } from \"./types\";\n\n/**\n * One Lightning operation, journaled under `lightningOp-<direction>-<payment hash>` in the settings store\n * BEFORE the provider is asked to spend: after a crash or a lost answer it is only ever reconciled (asked\n * about), never paid again. Incoming invoices are journaled too, so whoever paid a chat request is told\n * once the source sees it paid.\n */\nexport interface LightningOp {\n direction: \"in\" | \"out\";\n providerId: string;\n mode: WalletMode;\n paymentHash: string;\n invoice: string;\n amount: number;\n /** The chat payment request this pays, or is the invoice of. */\n paymentId?: string;\n note?: string;\n ref?: string;\n expiresAt: number;\n createdAt: number;\n /** The source books and reports this itself (the Cashu wallet): the engine does not poll it. */\n selfSettled?: boolean;\n /**\n * The Lightning card it went through (see `lightningCards.ts`). Absent on operations journaled before a network could\n * have several cards: those belong to the card that took over the network's one source (`legacyCard`).\n */\n card?: string;\n /** in: open → paid | expired. out: sending → paid | pending | failed | unknown (a lost answer). */\n state: \"open\" | \"paid\" | \"expired\" | \"sending\" | \"pending\" | \"failed\" | \"unknown\";\n maxFee?: number;\n fee?: number;\n error?: string;\n settledAt?: number;\n}\nexport type LightningOpView = Omit<LightningOp, \"ref\" | \"note\" | \"selfSettled\">;\n\n/** Options of one Lightning card's service. Without `card`, it is the network's only one (tests, older callers). */\nexport interface LightningServiceOptions {\n fetch?: typeof fetch;\n /** The card this service is: its operations carry the id, and it reads only its own. */\n card?: string;\n /** Where its source is stored: `lightningSource-<network>-<key>`; absent, the network's one key. */\n key?: string;\n /** Whether a journaled operation is this card's. Default: one carrying its id, or one carrying none. */\n owns?: (op: LightningOp) => boolean;\n}\nexport interface LightningView extends SourceView {\n capabilities?: LightningProvider[\"capabilities\"];\n /** This mode's latest operations, newest first. */\n recent: LightningOpView[];\n}\n/** `mint`: the Cashu mint that will pay, when the source is the mints; otherwise the source's id. */\nexport interface LightningQuote { quote: string; mint: string; amount: number; feeReserve: number; source: string }\n/** What a person sees of a resolved Lightning address or LNURL before choosing an amount. Amounts are whole sats. */\nexport interface LnurlView {\n id: string;\n kind: \"address\" | \"lnurl\";\n /** The address or LNURL, as normalised. */\n text: string;\n /** The host that learned of the request. */\n domain: string;\n /** The host the invoice is asked from, when it is another. */\n callbackDomain: string;\n minSat: number;\n maxSat: number;\n /** What the service says the payment is for. */\n description: string;\n /** How long a comment may be (LUD-12); 0 when none is taken. */\n commentAllowed: number;\n}\n/** Who an event is about. `mint`: the Cashu mint it went through, when it did. */\nexport interface LightningEventOp { paymentId?: string; providerId: string; mint?: string }\n\nexport interface LightningEvents {\n changed(): void;\n /** One of our invoices was paid. `paymentId`: the chat request it belongs to. */\n received(op: LightningEventOp): void;\n /** A payment left pending or unknown ended, one way or the other. */\n resolved(op: LightningEventOp, paid: boolean): void;\n}\n\nconst PREFIX = \"lightningOp-\";\nconst opKey = (direction: LightningOp[\"direction\"], hash: string) => `${PREFIX}${direction}-${hash}`;\nconst RANGE = () => IDBKeyRange.bound(PREFIX, `${PREFIX}\\uffff`);\n/** Every Lightning operation journaled, of every network and card. */\n/** Lightning payments whose call to the source has not returned, on any card of this page (WISP 06 § Payments in flight). */\nlet paying = 0;\nexport const lightningPaying = (): number => paying;\n\nexport async function readLightningJournal(): Promise<LightningOp[]> { return wrap<LightningOp[]>((await store(STORES.settings, \"readonly\")).getAll(RANGE())); }\nconst IN_POLL_MS = 4_000;\nconst OUT_POLL_MS = 15_000;\nconst RECENT = 20;\n/** Resolved Lightning addresses are kept this long for the amount to be chosen; this many at most. */\nconst LNURL_TTL_MS = 10 * 60_000;\nconst LNURL_KEPT = 20;\n/** A source that cannot tell its fee beforehand gets this ceiling, the same one chat requests are paid under. */\nexport const defaultFeeCap = (amount: number) => Math.max(10, Math.ceil(amount * 0.03));\n\n/**\n * Lightning of one network: its Lightning card, the invoice a chat request on that network carries and paying a\n * contact's invoice on it all come here, and go to this network's active source. The engine has one per network.\n */\nexport class LightningService {\n readonly sources: ProviderSources<LightningProvider>;\n private readonly quotes = new Map<string, { providerId: string; provider: LightningProvider; invoice: string; amount: number; maxFee: number; paymentHash: string }>();\n private readonly mode: WalletMode;\n private timer?: ReturnType<typeof setTimeout>;\n private passes: Promise<void> = Promise.resolve();\n private stopped = false;\n private recent: LightningOpView[] = [];\n /** Operations written in this run (`put`): a reconcile pass that wrote one is news, recent or not. */\n private writes = 0;\n /** Lightning addresses and LNURLs resolved, until an amount is chosen for them. */\n private readonly lnurls = new Map<string, { params: LnurlPayParams; at: number }>();\n\n constructor(readonly network: WalletMode, descriptors: () => readonly LightningProviderDescriptor[], host: () => Omit<ProviderHost, \"mode\" | \"signal\">, private readonly events: LightningEvents, defaultId?: string, private readonly options: LightningServiceOptions = {}) {\n this.mode = network;\n this.sources = new ProviderSources<LightningProvider>({\n kind: \"lightning\", network, descriptors, host, defaultId, key: options.key,\n changed: () => { this.schedule(0); events.changed(); },\n refuseReplacing: (providerId, mode) => this.refusal(providerId, mode),\n refresh: async (provider) => (provider.capabilities.balance ? { balance: (await provider.info()).balance } : {}),\n });\n }\n\n /** The card this service is, when the network has several. */\n get card(): string | undefined { return this.options.card; }\n /** Whether a journaled operation went through this card (and this network). */\n owns(op: LightningOp): boolean {\n if (op.mode !== this.mode) return false;\n return this.options.owns ? this.options.owns(op) : op.card === undefined || op.card === this.options.card;\n }\n\n get view(): LightningView {\n return { ...this.sources.view, capabilities: this.sources.active?.capabilities, recent: this.recent };\n }\n\n async start() { await this.sources.start(); await this.loadRecent(); }\n ensureReady() { return this.sources.ensureReady(); }\n async stop() { this.stopped = true; clearTimeout(this.timer); await this.sources.stop(); }\n refreshOffered() { this.sources.refreshOffered(); }\n\n // -- receiving -----------------------------------------------------------------\n\n async createInvoice(amount: number, context: { memo?: string; paymentId?: string } = {}): Promise<LightningInvoice & { source: string }> {\n const { provider, descriptor } = await this.sources.use();\n if (!provider.capabilities.receive) throw new Error(`${descriptor.label} cannot create invoices`);\n const created = await provider.createInvoice(amount, context.memo ?? \"Ghostly\");\n const decoded = decodeBolt11(created.invoice);\n // What goes to a contact must be the invoice we asked for: this amount, this hash, the right chain.\n if (!decoded || decoded.amountSat !== amount || (decoded.paymentHash && decoded.paymentHash !== created.paymentHash) || created.amount !== amount) throw new Error(`${descriptor.label} returned an invoice that does not match the request`);\n this.checkNetwork(decoded.network);\n await this.put({ direction: \"in\", providerId: descriptor.id, mode: this.mode, ...this.cardField(), paymentHash: created.paymentHash, invoice: created.invoice, amount, paymentId: context.paymentId, ref: created.ref, expiresAt: created.expiresAt, createdAt: Date.now(), selfSettled: !!provider.settlesItself, state: \"open\" });\n this.schedule(IN_POLL_MS);\n return { ...created, source: descriptor.id };\n }\n\n /** The Cashu wallet minted the ecash of one of its invoices (it polls its own quotes). */\n async reportInvoicePaid(invoice: string, context: { paymentId?: string; mint?: string } = {}) {\n const hash = decodeBolt11(invoice)?.paymentHash;\n const op = hash ? await this.get(\"in\", hash) : undefined;\n if (op && op.state !== \"paid\") await this.put({ ...op, state: \"paid\", settledAt: Date.now() });\n // Quotes made before sources carry their chat request themselves.\n this.events.received({ paymentId: op?.paymentId ?? context.paymentId, providerId: op?.providerId ?? CASHU_MINT_SOURCE, mint: context.mint });\n await this.loadRecent(); this.events.changed();\n }\n\n // -- Lightning addresses and LNURLs (LUD-16, LUD-06) -----------------------------\n\n /**\n * Fetches what a Lightning address or LNURL asks for, and keeps it until an amount is chosen. The\n * service's domain learns of the request: the caller says so before asking.\n */\n async resolveDestination(text: string): Promise<LnurlView> {\n const params = await resolveLightningDestination(text, { fetch: this.options.fetch });\n const now = Date.now();\n for (const [id, entry] of this.lnurls) if (entry.at + LNURL_TTL_MS < now) this.lnurls.delete(id);\n while (this.lnurls.size >= LNURL_KEPT) this.lnurls.delete(this.lnurls.keys().next().value!);\n const id = crypto.randomUUID();\n this.lnurls.set(id, { params, at: now });\n const { destination, callbackDomain, minSat, maxSat, description, commentAllowed } = params;\n return { id, kind: destination.kind, text: destination.text, domain: destination.domain, callbackDomain, minSat, maxSat, description, commentAllowed };\n }\n\n /**\n * The invoice for `amountSat` from a resolved destination: checked (the amount, a commitment to what\n * was shown, not expired, a network of this mode) before it is quoted and paid like any other invoice.\n */\n async destinationInvoice(id: string, amountSat: number, comment?: string): Promise<{ invoice: string; successAction?: LnurlSuccessAction; note: string }> {\n const entry = this.lnurls.get(id);\n if (!entry || entry.at + LNURL_TTL_MS < Date.now()) { this.lnurls.delete(id); throw new Error(\"That address was resolved too long ago: check it again\"); }\n const { invoice, successAction } = await requestLnurlInvoice(entry.params, amountSat, comment, { fetch: this.options.fetch });\n this.checkNetwork(invoice.network);\n return { invoice: invoice.invoice, successAction, note: entry.params.destination.text };\n }\n\n /** Whether this card resolved that address (the invoice is asked where it was resolved). */\n hasDestination(id: string) { return this.lnurls.has(id); }\n\n // -- paying --------------------------------------------------------------------\n\n /** What paying this invoice costs at most, from the active source. Nothing is spent. */\n async quote(text: string): Promise<LightningQuote> {\n const decoded = decodeBolt11(text);\n if (!decoded) throw new Error(\"That is not a Lightning invoice\");\n if (decoded.amountSat === null) throw new Error(\"Invoices without an amount are not supported\");\n if (!decoded.paymentHash) throw new Error(\"That invoice has no payment hash\");\n if (decoded.expiresAt * 1000 < Date.now()) throw engineError(\"invoiceExpired\");\n this.checkNetwork(decoded.network);\n const { provider, descriptor } = await this.sources.use();\n // A Bitcoin invoice on Testnet: only the mints can tell test sats from real ones (`fakesLightning`, checked when\n // they quote it). Any other Testnet source that pays one is a real node, and would pay it with real money.\n if (this.mode === \"testnet\" && decoded.network === \"bitcoin\" && !(provider instanceof CashuMintLightning)) throw new Error(BITCOIN_INVOICE_ON_TESTNET);\n if (!provider.capabilities.send) throw new Error(`${descriptor.label} cannot pay invoices`);\n const maxFee = provider.estimateFee ? await provider.estimateFee(decoded.invoice, decoded.amountSat) : defaultFeeCap(decoded.amountSat);\n if (!Number.isSafeInteger(maxFee) || maxFee < 0) throw new Error(`${descriptor.label} returned an invalid fee`);\n const id = crypto.randomUUID();\n this.quotes.set(id, { providerId: descriptor.id, provider, invoice: decoded.invoice, amount: decoded.amountSat, maxFee, paymentHash: decoded.paymentHash });\n const mint = provider instanceof CashuMintLightning ? provider.quoteFor(decoded.invoice)?.mint : undefined;\n return { quote: id, mint: mint ?? descriptor.id, amount: decoded.amountSat, feeReserve: maxFee, source: descriptor.id };\n }\n\n hasQuote(id: string) { return this.quotes.has(id); }\n\n /**\n * Pays a quote. True once paid; false while in flight or unknown (reconciled in the background, never\n * paid again). Throws only when nothing left the wallet.\n */\n async pay(quoteId: string, context: { note?: string; paymentId?: string } = {}): Promise<boolean> {\n const quote = this.quotes.get(quoteId);\n if (!quote) throw new Error(\"This quote is no longer valid: check the invoice again\");\n if (this.sources.active !== quote.provider) { this.quotes.delete(quoteId); throw new Error(\"The Lightning source changed: check the invoice again\"); }\n this.quotes.delete(quoteId);\n const existing = await this.get(\"out\", quote.paymentHash);\n if (existing && existing.state !== \"failed\") throw new Error(existing.state === \"paid\" ? \"This invoice is already paid\" : \"This invoice is already being paid\");\n let op: LightningOp = { direction: \"out\", providerId: quote.providerId, mode: this.mode, ...this.cardField(), paymentHash: quote.paymentHash, invoice: quote.invoice, amount: quote.amount, paymentId: context.paymentId, note: context.note?.slice(0, 140), expiresAt: 0, createdAt: Date.now(), selfSettled: !!quote.provider.settlesItself, state: \"sending\", maxFee: quote.maxFee, ref: quote.provider.paymentRef?.(quote.invoice) };\n // Written down before the provider sees it: from here on the sats may be gone.\n await this.put(op);\n paying += 1;\n try {\n const result = await quote.provider.payInvoice(quote.invoice, quote.maxFee, context.note);\n op = result.state === \"paid\" ? { ...op, state: \"paid\", fee: result.fee, ref: result.ref ?? op.ref, settledAt: Date.now() } : { ...op, state: \"pending\", ref: result.ref ?? op.ref };\n } catch (error) {\n if (isNothingSpentError(error)) { await this.put({ ...op, state: \"failed\", error: redact(error) }); throw error; }\n // A source that cannot look payments up never settles this by itself: say so rather than \"being checked\".\n op = { ...op, state: \"unknown\", error: quote.provider.capabilities.lookup ? \"No answer from the source. It is being checked; nothing is paid again.\" : \"No answer from the source, and it cannot be asked: check this payment in the wallet itself. It is never paid again.\" };\n } finally { paying -= 1; }\n await this.put(op);\n if (op.state !== \"paid\") this.schedule(OUT_POLL_MS);\n return op.state === \"paid\";\n }\n\n /** The Cashu wallet settled a melt it had left pending. */\n async reportPaymentResolved(invoice: string, paid: boolean, context: { paymentId?: string; mint?: string } = {}) {\n const hash = decodeBolt11(invoice)?.paymentHash;\n const op = hash ? await this.get(\"out\", hash) : undefined;\n if (op) await this.put({ ...op, state: paid ? \"paid\" : \"failed\", settledAt: Date.now() });\n this.events.resolved({ paymentId: op?.paymentId ?? context.paymentId, providerId: op?.providerId ?? CASHU_MINT_SOURCE, mint: context.mint }, paid);\n await this.loadRecent(); this.events.changed();\n }\n\n // -- reconciling ---------------------------------------------------------------\n\n /** Asks the active source about everything of its that is open, pending or unknown. One pass at a time. */\n reconcile(): Promise<void> {\n const pass = this.passes.then(() => this.reconcileOnce());\n this.passes = pass.catch(() => {});\n return pass;\n }\n\n private async reconcileOnce(): Promise<void> {\n if (this.stopped) return;\n let again = 0;\n const writes = this.writes;\n try {\n const provider = this.sources.active, providerId = this.sources.activeId;\n for (const op of await this.list()) {\n if (!this.owns(op)) continue;\n if (op.selfSettled) {\n // The source settles and reports its own. One cut off before its answer was written (`recover`) it may\n // never have heard of, and would never report: the source is asked what became of it.\n if (op.direction !== \"out\" || op.state !== \"unknown\") continue;\n // No source connected yet: asked again. Another source's waits until its own is active again.\n if (!provider) { again = again || OUT_POLL_MS; continue; }\n if (op.providerId !== providerId || !provider.interruptedPayment) continue;\n try {\n const outcome = await provider.interruptedPayment({ invoice: op.invoice, paymentHash: op.paymentHash, ref: op.ref });\n if (outcome === \"pending\") again = again || OUT_POLL_MS;\n if (outcome !== \"paid\" && outcome !== \"failed\") continue;\n // Written only over the state that was asked about: the source's own report wins.\n if ((await this.get(\"out\", op.paymentHash))?.state !== \"unknown\") continue;\n await this.put({ ...op, state: outcome, error: outcome === \"failed\" ? \"The payment did not go through\" : undefined, settledAt: Date.now() });\n this.events.resolved(op, outcome === \"paid\");\n } catch { again = again || OUT_POLL_MS; }\n continue;\n }\n // `sending` is a spend under way in this process: its own answer decides. One left by a crash is\n // made `unknown` by `recover` at the next start, and only then asked about.\n const waiting = op.direction === \"in\" ? op.state === \"open\" : [\"pending\", \"unknown\"].includes(op.state);\n if (!waiting) continue;\n // Another source's operation waits until that source is active again: only it can answer.\n if (!provider || op.providerId !== providerId || !provider.capabilities.lookup) continue;\n try {\n const unchanged = async () => (await this.get(op.direction, op.paymentHash))?.state === op.state;\n if (op.direction === \"in\") {\n const status = await provider.invoiceStatus({ invoice: op.invoice, paymentHash: op.paymentHash, amount: op.amount, expiresAt: op.expiresAt, ref: op.ref });\n if (!(await unchanged())) continue;\n if (status.state === \"paid\") { await this.put({ ...op, state: \"paid\", settledAt: Date.now() }); this.events.received(op); }\n else if (status.state === \"expired\" || op.expiresAt + 60_000 < Date.now()) await this.put({ ...op, state: \"expired\" });\n else again = again ? Math.min(again, IN_POLL_MS) : IN_POLL_MS;\n } else {\n const status = await provider.paymentStatus({ invoice: op.invoice, paymentHash: op.paymentHash, ref: op.ref });\n if (status.state === \"pending\") { again = again || OUT_POLL_MS; continue; }\n // Written only over the state that was asked about: anything newer wins.\n if (!(await unchanged())) continue;\n await this.put({ ...op, state: status.state, fee: status.fee ?? op.fee, error: status.state === \"failed\" ? \"The payment did not go through\" : undefined, settledAt: Date.now() });\n this.events.resolved(op, status.state === \"paid\");\n }\n } catch {\n // The source cannot be asked right now: ask again later, nothing changes.\n again = again ? Math.min(again, op.direction === \"in\" ? IN_POLL_MS : OUT_POLL_MS) : op.direction === \"in\" ? IN_POLL_MS : OUT_POLL_MS;\n }\n }\n } finally {\n // Only a pass that settled something, or whose recent list reads otherwise, is news to the wallet view.\n if (await this.loadRecent() || this.writes !== writes) this.events.changed();\n if (again) this.schedule(again);\n }\n }\n\n private schedule(ms: number) {\n if (this.stopped) return;\n clearTimeout(this.timer);\n this.timer = setTimeout(() => void this.reconcile(), ms);\n }\n\n /** Changing the source is refused while a payment through it has not ended: only it can say how. */\n private async refusal(providerId: string, mode: WalletMode) {\n const open = (await this.list()).filter((op) => op.direction === \"out\" && !op.selfSettled && op.providerId === providerId && op.mode === mode && this.owns(op) && [\"sending\", \"pending\", \"unknown\"].includes(op.state));\n return open.length ? `A Lightning payment through this source has not ended yet (${open.length}). Wait for it before changing the source.` : undefined;\n }\n\n private cardField(): Pick<LightningOp, \"card\"> { return this.options.card === undefined ? {} : { card: this.options.card }; }\n\n /** A Mainnet source refuses an invoice on a test chain. The reverse (test mints use lnbc) is `quote`'s to refuse. */\n private checkNetwork(network: string) {\n if (this.mode === \"mainnet\" && network !== \"bitcoin\") throw new Error(`That invoice is for ${network}, a test network: pay it from a Testnet wallet`);\n }\n\n private async get(direction: LightningOp[\"direction\"], hash: string) { return wrap<LightningOp | undefined>((await store(STORES.settings, \"readonly\")).get(opKey(direction, hash))); }\n private async put(op: LightningOp) { this.writes++; await transact([STORES.settings], (s) => { s[STORES.settings].put(op, opKey(op.direction, op.paymentHash)); }); }\n list(): Promise<LightningOp[]> { return readLightningJournal(); }\n\n /** Crash recovery: a spend journaled as `sending` never got its answer written, so its outcome is unknown. */\n async recover() {\n for (const op of await this.list()) if (op.direction === \"out\" && op.state === \"sending\" && this.owns(op)) await this.put({ ...op, state: \"unknown\", error: \"Interrupted. It is being checked; nothing is paid again.\" });\n this.schedule(0);\n }\n\n /** True when the recent list reads otherwise than before. */\n private async loadRecent(): Promise<boolean> {\n const was = this.recent;\n this.recent = (await this.list()).filter((op) => this.owns(op)).sort((a, b) => b.createdAt - a.createdAt).slice(0, RECENT)\n .map(({ ref: _ref, note: _note, selfSettled: _self, ...view }) => view);\n return JSON.stringify(was) !== JSON.stringify(this.recent);\n }\n}\n","import { generateMnemonic, validateMnemonic } from \"@scure/bip39\";\nimport { wordlist } from \"@scure/bip39/wordlists/english.js\";\n\n/**\n * Recovery phrases (BIP 39, English) for providers whose wallet Ghostly makes: the form generates one for\n * a new wallet and checks one typed to restore. Small on purpose, so a form can use it without loading\n * the provider.\n */\nexport const newRecoveryPhrase = () => generateMnemonic(wordlist);\n/** A phrase as BIP 39 wants it, however it was typed or pasted. */\nexport const normalizePhrase = (text = \"\") => text.trim().toLowerCase().split(/\\s+/).join(\" \");\nexport const isRecoveryPhrase = (text: string) => validateMnemonic(normalizePhrase(text), wordlist);\n","import { STORES, transact } from \"../../../shared/idb\";\nimport type { WalletMode } from \"../../../shared/mints\";\nimport { CASHU_MINT_SOURCE } from \"./cashuMint\";\nimport type { LightningProviderDescriptor } from \"./lightning\";\nimport { LightningService, readLightningJournal, type LightningEvents, type LightningOp, type LightningView } from \"./lightningService\";\nimport { sourceKey } from \"./sources\";\nimport { awayFrom } from \"../away\";\nimport { breezDatabaseInUse } from \"./breezDatabases\";\nimport { normalizePhrase } from \"./recoveryPhrase\";\nimport type { ProviderHost } from \"./types\";\n\n/**\n * One Lightning card of a network, as stored under `lightningCards-<network>`. Its source (and the source's sealed\n * secrets) is stored apart, under `lightningSource-<network>-<key>`: the card that took over a network's one source\n * (`LEGACY_CARD`) has no key and keeps reading `lightningSource-<network>`, where that source always was.\n */\nexport interface StoredLightningCard {\n id: string;\n name: string;\n providerId: string;\n /** Where its source is stored. Absent: the network's one key (the card made from it). */\n key?: string;\n createdAt: number;\n}\ninterface StoredLightningCards {\n cards: StoredLightningCard[];\n /** The card a chat request's invoice and Receive come from, unless another is picked. */\n receive: string;\n}\n\n/** A Lightning card as the pages see it: its source's view, its name, and whether it is the one receiving. */\nexport interface LightningCardView extends LightningView {\n card: string;\n name: string;\n /** The network's default for receiving: chat requests and Receive use it unless another card is picked. */\n receive: boolean;\n}\n\n/** The card of Lightning through the Cashu mints: at most one per network, shown while the network has mints. */\nexport const CASHU_CARD = \"cashu\";\n/** The card made from the one source a network had before it could have several. */\nexport const LEGACY_CARD = \"main\";\nexport const cardsKey = (network: WalletMode) => `lightningCards-${network}`;\nconst NAME_MAX = 32;\n\n/** Short source names for a card's default name (\"Alby Hub (NWC)\"): the form's label otherwise. */\nconst SHORT: Record<string, string> = { nwc: \"NWC\", lnd: \"LND\", \"core-lightning\": \"Core Lightning\", breez: \"Breez\", [CASHU_MINT_SOURCE]: \"Cashu\", fedimint: \"Fedimint\", webln: \"WebLN\" };\n\n/**\n * The cards of a network, made the first time this version opens it from what an older one kept: the network's one\n * source (`lightningSource-<network>`) becomes a card reading that same key, so no secret moves and nothing asks\n * again; with no source saved, the network used the Cashu mints, and that is its card. Either becomes the default for\n * receiving. Read and written in one transaction; a second run finds the cards and changes nothing. Never reads a\n * secret: the source's record names its provider next to its sealed secrets.\n */\nexport async function loadLightningCards(network: WalletMode, label: (providerId: string) => string, now = Date.now()): Promise<StoredLightningCards> {\n let result: StoredLightningCards | undefined;\n await transact([STORES.settings], (stores) => {\n const settings = stores[STORES.settings];\n const saved = settings.get(cardsKey(network));\n saved.onsuccess = () => {\n const found = saved.result as StoredLightningCards | undefined;\n if (found?.cards?.length) { result = found; return; }\n const legacy = settings.get(sourceKey(\"lightning\", network));\n legacy.onsuccess = () => {\n const providerId = (legacy.result as { providerId?: unknown } | undefined)?.providerId;\n const card: StoredLightningCard = typeof providerId === \"string\" && providerId && providerId !== CASHU_MINT_SOURCE\n ? { id: LEGACY_CARD, name: shortName(providerId, label), providerId, createdAt: now }\n : cashuCard(now);\n result = { cards: [card], receive: card.id };\n settings.put(result, cardsKey(network));\n };\n };\n });\n return result!;\n}\n\nconst cashuCard = (now: number): StoredLightningCard => ({ id: CASHU_CARD, name: SHORT[CASHU_MINT_SOURCE], providerId: CASHU_MINT_SOURCE, key: CASHU_CARD, createdAt: now });\nconst shortName = (providerId: string, label: (providerId: string) => string) => SHORT[providerId] ?? label(providerId);\n\nexport interface LightningCardsHost {\n descriptors: () => readonly LightningProviderDescriptor[];\n host: () => Omit<ProviderHost, \"mode\" | \"signal\">;\n events: LightningEvents;\n /** Whether the network has Cashu mints: the Cashu card shows only then. */\n hasMints: () => boolean;\n fetch?: typeof fetch;\n}\n\n/**\n * The Lightning cards of one network, each with its own source, balance and journal: \"Alby Hub (NWC)\", \"Home LND\",\n * the Cashu mints. A payment goes through the card picked for it; a chat request's invoice and Receive come from the\n * default for receiving unless another is picked. Adding, removing and renaming are serialized.\n */\nexport class LightningCards {\n private stored: StoredLightningCards = { cards: [], receive: CASHU_CARD };\n private readonly services = new Map<string, LightningService>();\n private queue: Promise<unknown> = Promise.resolve();\n /** A card being added: a creation that takes too long cuts its connection short. */\n private adding?: LightningService;\n private stopped = false;\n\n constructor(readonly network: WalletMode, private readonly options: LightningCardsHost) {}\n\n private serial<T>(run: () => Promise<T>): Promise<T> { const next = this.queue.then(run, run); this.queue = next.catch(() => {}); return next; }\n private label = (providerId: string) => this.options.descriptors().find((d) => d.id === providerId)?.label ?? providerId;\n\n private make(card: StoredLightningCard): LightningService {\n return new LightningService(this.network, this.options.descriptors, this.options.host, this.options.events,\n card.providerId === CASHU_MINT_SOURCE ? CASHU_MINT_SOURCE : undefined,\n { fetch: this.options.fetch, card: card.id, key: card.key, owns: (op) => this.owner(op) === card.id });\n }\n\n private starting?: Promise<void>;\n /** Opens the cards (once: a use before the engine started opens them too). */\n start(): Promise<void> { return this.starting ??= this.open(); }\n private async open() {\n this.stored = await loadLightningCards(this.network, this.label);\n for (const card of this.stored.cards) {\n const service = this.make(card);\n this.services.set(card.id, service);\n await service.start();\n }\n }\n\n /**\n * The card an operation went through. One journaled before cards: the Cashu card's when the mints made it, else the\n * card made from the network's one source (none, when that source was replaced before cards: nobody shows it).\n */\n owner(op: LightningOp): string {\n if (op.card !== undefined) return op.card;\n return op.providerId === CASHU_MINT_SOURCE ? CASHU_CARD : LEGACY_CARD;\n }\n\n /** The cards a person sees: the Cashu card only while the network has mints. In the order they were added. */\n private shown(): StoredLightningCard[] { return this.stored.cards.filter((c) => c.id !== CASHU_CARD || this.options.hasMints()); }\n\n /** The default for receiving, or the first card shown when that one is not (the Cashu card of a network with no mints). */\n get receivingId(): string {\n const shown = this.shown();\n return shown.find((c) => c.id === this.stored.receive)?.id ?? shown[0]?.id ?? this.stored.receive;\n }\n get receiving(): LightningService { return this.services.get(this.receivingId) ?? [...this.services.values()][0]; }\n\n /** A card by its id; without one, the default for receiving. Throws when it is gone. */\n card(id?: string): LightningService {\n if (id === undefined) { if (!this.services.size) throw new Error(\"Lightning is not open yet\"); return this.receiving; }\n const service = this.services.get(id);\n if (!service) throw new Error(\"That Lightning card is gone: pick another\");\n return service;\n }\n has(id: string) { return this.services.has(id); }\n all(): LightningService[] { return [...this.services.values()]; }\n\n views(): LightningCardView[] {\n const receive = this.receivingId;\n return this.shown().filter((card) => this.services.has(card.id)).map((card) => ({ ...this.services.get(card.id)!.view, card: card.id, name: card.name, receive: card.id === receive }));\n }\n /** The default for receiving's view: what a caller from before cards reads as the network's Lightning. None before start. */\n get view(): LightningCardView | undefined {\n const card = this.stored.cards.find((c) => c.id === this.receivingId) ?? this.stored.cards[0];\n return card && this.services.has(card.id) ? { ...this.services.get(card.id)!.view, card: card.id, name: card.name, receive: true } : undefined;\n }\n\n /** The card holding this quote (each card quotes through its own source). */\n withQuote(quote: string): LightningService | undefined { return this.all().find((s) => s.hasQuote(quote)); }\n\n /**\n * Adds a card with `providerId` and the values of its form: connected and checked first, like any source; only then\n * saved. Nothing is saved when it fails. The same wallet twice (same source, same settings) is refused. A card of the\n * person's own takes receiving over from the mints' card (what a source chosen before cards did); next to another\n * card of their own, the default stays.\n */\n add(providerId: string, values: Record<string, string>): Promise<string> {\n return this.serial(async () => {\n if (providerId === CASHU_MINT_SOURCE) {\n if (this.services.has(CASHU_CARD)) throw new Error(\"Lightning through your Cashu mints is already a card\");\n if (!this.options.hasMints()) throw new Error(\"Create a Cashu wallet first: this card pays and receives through its mints\");\n const card = cashuCard(Date.now());\n const service = this.make(card);\n await service.start();\n await this.save({ ...this.stored, cards: [...this.stored.cards, card] }, card.id);\n this.services.set(card.id, service);\n void service.ensureReady();\n return card.id;\n }\n const id = `ln-${[...crypto.getRandomValues(new Uint8Array(4))].map((b) => b.toString(16).padStart(2, \"0\")).join(\"\")}`;\n const card: StoredLightningCard = { id, name: \"\", providerId, key: id, createdAt: Date.now() };\n const service = this.make(card);\n const same = await this.findSame(providerId, values);\n if (same) throw new Error(`That wallet is already your card “${this.stored.cards.find((c) => c.id === same)?.name}”`);\n await service.start();\n this.adding = service;\n try { await service.sources.set(providerId, values); }\n catch (error) { await service.stop(); throw error; }\n finally { this.adding = undefined; }\n card.name = this.freeName(service.view.alias ? `${service.view.alias.slice(0, 20)} (${shortName(providerId, this.label)})` : shortName(providerId, this.label));\n const receive = this.receivingId === CASHU_CARD ? id : this.stored.receive;\n // The mints' card of a network with no mints is not shown and holds nothing: a card of the person's own takes its\n // place, as the one source did before cards (a Cashu wallet made later does not bring it back).\n const hidden = this.services.has(CASHU_CARD) && !this.options.hasMints();\n const cards = [...this.stored.cards.filter((c) => !hidden || c.id !== CASHU_CARD), card];\n try { await this.save({ cards, receive }, card.id); }\n catch (error) { await service.sources.forget().catch(() => {}); await service.stop(); throw error; }\n this.services.set(id, service);\n if (hidden) { const gone = this.services.get(CASHU_CARD)!; this.services.delete(CASHU_CARD); await gone.stop(); }\n return id;\n });\n }\n\n /** The card that is already this wallet (the same source with the same settings), if one is. Throws what to fix in the form. */\n async findSame(providerId: string, values: Record<string, string>): Promise<string | undefined> {\n const { settings } = this.receiving.sources.settingsOf(providerId, values);\n for (const [id, service] of this.services) if (await service.sources.holds(providerId, settings)) return id;\n return undefined;\n }\n\n /** A name no other card of the network has: \"LND\", then \"LND 2\". */\n private freeName(name: string): string {\n const taken = new Set(this.stored.cards.map((c) => c.name.toLowerCase()));\n if (!taken.has(name.toLowerCase())) return name;\n for (let n = 2; ; n++) if (!taken.has(`${name} ${n}`.toLowerCase())) return `${name} ${n}`;\n }\n\n /**\n * Removes a card: its source and sealed secrets go (refused while a payment through it has not ended). The default\n * for receiving moves to the first card of the person's own left, else the mints' card. A network left with no card gets the Cashu card back, as before cards.\n */\n remove(id: string): Promise<void> {\n return this.serial(async () => {\n const service = this.services.get(id);\n if (!service) throw new Error(\"That Lightning card is gone\");\n await service.sources.forget();\n await service.stop();\n this.services.delete(id);\n let cards = this.stored.cards.filter((c) => c.id !== id);\n let added: LightningService | undefined;\n if (!cards.length) {\n const card = cashuCard(Date.now());\n cards = [card];\n added = this.make(card);\n this.services.set(card.id, added);\n await added.start();\n }\n const shown = cards.filter((c) => c.id !== CASHU_CARD || this.options.hasMints());\n const next = shown.find((c) => c.id !== CASHU_CARD) ?? shown[0] ?? cards[0];\n const receive = this.stored.receive === id || !cards.some((c) => c.id === this.stored.receive) ? next.id : this.stored.receive;\n await this.save({ cards, receive });\n if (added) void added.ensureReady();\n });\n }\n\n /** Makes a card the network's default for receiving. */\n setReceive(id: string): Promise<void> {\n return this.serial(async () => {\n if (!this.shown().some((c) => c.id === id)) throw new Error(\"That Lightning card is gone\");\n await this.save({ ...this.stored, receive: id });\n });\n }\n\n /** Renames a card: 1 to 32 characters, unique on its network. */\n rename(id: string, name: string): Promise<void> {\n return this.serial(async () => {\n const clean = name.replace(/\\s+/g, \" \").trim();\n if (!clean) throw new Error(\"Enter a name\");\n if (clean.length > NAME_MAX) throw new Error(`At most ${NAME_MAX} characters`);\n if (!this.services.has(id)) throw new Error(\"That Lightning card is gone\");\n if (this.stored.cards.some((c) => c.id !== id && c.name.toLowerCase() === clean.toLowerCase())) throw new Error(\"Another card has that name\");\n await this.save({ ...this.stored, cards: this.stored.cards.map((c) => (c.id === id ? { ...c, name: clean } : c)) });\n });\n }\n\n private async save(next: StoredLightningCards, receiveIfNone?: string) {\n const shown = next.cards.filter((c) => c.id !== CASHU_CARD || this.options.hasMints());\n // A first card shown takes the receiving over from one that is not (the Cashu card of a network with no mints).\n const receive = receiveIfNone && !shown.some((c) => c.id === next.receive) ? receiveIfNone : next.receive;\n const stored = { cards: next.cards, receive };\n await transact([STORES.settings], (s) => { s[STORES.settings].put(stored, cardsKey(this.network)); });\n this.stored = stored;\n this.options.events.changed();\n }\n\n // -- the Cashu wallet reports what its mints settled -------------------------------\n\n private async ownerOf(direction: LightningOp[\"direction\"], invoice: string): Promise<LightningService> {\n const op = (await readLightningJournal()).find((o) => o.direction === direction && o.invoice === invoice && o.mode === this.network);\n const owner = op ? this.owner(op) : CASHU_CARD;\n return (owner && this.services.get(owner)) || this.services.get(CASHU_CARD) || this.receiving;\n }\n async reportInvoicePaid(invoice: string, context: { paymentId?: string; mint?: string } = {}) { await (await this.ownerOf(\"in\", invoice)).reportInvoicePaid(invoice, context); }\n async reportPaymentResolved(invoice: string, paid: boolean, context: { paymentId?: string; mint?: string } = {}) { await (await this.ownerOf(\"out\", invoice)).reportPaymentResolved(invoice, paid, context); }\n\n // -- Lightning addresses: resolved by one card, and the invoice asked by that same one --------\n\n resolveDestination(text: string) { return this.receiving.resolveDestination(text); }\n destinationInvoice(id: string, amountSat: number, comment?: string) { return (this.all().find((s) => s.hasDestination(id)) ?? this.receiving).destinationInvoice(id, amountSat, comment); }\n\n // -- every card at once -----------------------------------------------------------\n\n list() { return readLightningJournal(); }\n /**\n * What a handoff needs to know of the cards (WISP 06 § Wallets), from their sealed settings: the LND cards that pin a\n * certificate, and each Breez card's database on this device, by wallet id.\n */\n async handoffFacts(): Promise<{ pinned: string[]; breez: Map<string, string> }> {\n const pinned: string[] = [], breez = new Map<string, string>();\n for (const [id, service] of this.services) {\n const wallet = `lightning:${this.network}:${id}`, providerId = service.view.providerId;\n if (providerId === \"lnd\" && await service.sources.secret(\"certificate\")) pinned.push(wallet);\n if (providerId === \"breez\") {\n const mnemonic = await service.sources.secret(\"mnemonic\");\n if (mnemonic) breez.set(wallet, await breezDatabaseInUse(this.network === \"mainnet\" ? \"bitcoin\" : \"regtest\", normalizePhrase(mnemonic)));\n }\n }\n return { pinned, breez };\n }\n\n /** The cards whose source is opened on this device: not one at home on another device (WISP 06 § Wallets that stay home). */\n private here(): LightningService[] { return [...this.services].filter(([id]) => awayFrom(`lightning:${this.network}:${id}`) === undefined).map(([, s]) => s); }\n async reconcile() { await Promise.all(this.here().map((s) => s.reconcile())); }\n async recover() { for (const s of this.here()) await s.recover(); }\n async ensureReady() { await Promise.all(this.here().map((s) => s.ensureReady())); }\n refreshOffered() { for (const s of this.all()) s.refreshOffered(); }\n wake() { for (const s of this.all()) s.sources.wake(); }\n cutShort() { this.adding?.sources.cutShort(); }\n resume() { this.adding?.sources.resume(); }\n async stop() { this.stopped = true; for (const s of this.all()) await s.stop(); }\n get isStopped() { return this.stopped; }\n}\n","import { boundVerifiedIdentity, IDENTITY_MAX_EVIDENCE, IDENTITY_MAX_VALIDITY, type IdentityStatement, type VerifiedIdentity } from \"@ghostly/core\";\nimport type { IdentityFetch, IdentityProofProvider, IdentitySigner, VerifyContext } from \"./contract\";\n\nconst DAY = 86_400;\n\n/**\n * The checks every proof goes through, whatever its provider: known provider, validity within the\n * provider's limit, bounded evidence, strict parsing, the provider's own check, and the category rule\n * (a self-custodied proof proves exactly the subject it names; an attested one says who attests it).\n * The engine verifies a contact's proof, and the person's own proof before saving it, only through this.\n */\nexport async function verifyIdentity(providers: readonly IdentityProofProvider[], statement: IdentityStatement, evidence: unknown, ctx: VerifyContext): Promise<VerifiedIdentity> {\n const { binding } = statement;\n const provider = providers.find(p => p.id === binding.provider);\n if (!provider) throw new Error(\"This app cannot verify this kind of identity\");\n if (binding.expiresAt - binding.issuedAt > Math.min(provider.validity.maxDays * DAY, IDENTITY_MAX_VALIDITY)) throw new Error(\"The proof lasts longer than this kind of identity allows\");\n if (JSON.stringify(evidence ?? null).length > IDENTITY_MAX_EVIDENCE) throw new Error(\"Evidence too large\");\n const parsed = provider.parseEvidence(evidence);\n const verified = boundVerifiedIdentity(await provider.verify(statement, parsed, ctx));\n if (provider.category === \"self-custodied\" && verified.subject !== binding.subject) throw new Error(\"The evidence is for another identity\");\n if (provider.category === \"provider-attested\" && !verified.attester) throw new Error(\"The evidence does not say who attests it\");\n return verified;\n}\n\n/** Problems with a provider's descriptor, empty when it is well-formed. The registry and the contract suite use it. */\nexport function descriptorProblems(p: IdentityProofProvider): string[] {\n const problems: string[] = [];\n if (!/^[a-z][a-z0-9-]{0,31}$/.test(p.id)) problems.push(\"id must match ^[a-z][a-z0-9-]{0,31}$\");\n if (!p.label?.trim()) problems.push(\"label is empty\");\n if (!p.description?.trim()) problems.push(\"description is empty\");\n if (!p.privacy?.trim()) problems.push(\"privacy is empty: say what verify contacts\");\n if (p.category !== \"self-custodied\" && p.category !== \"provider-attested\") problems.push(\"category is unknown\");\n if (!p.platforms.length) problems.push(\"no platforms\");\n const { defaultDays, maxDays } = p.validity;\n if (!(Number.isInteger(defaultDays) && Number.isInteger(maxDays) && defaultDays >= 1 && defaultDays <= maxDays && maxDays * DAY <= IDENTITY_MAX_VALIDITY)) problems.push(\"validity must be whole days, 1 ≤ default ≤ max ≤ 400\");\n if (p.recheck && !(Number.isInteger(p.recheck.afterSeconds) && p.recheck.afterSeconds >= 60)) problems.push(\"recheck.afterSeconds must be at least 60\");\n if (!p.signers.length) problems.push(\"no signers\");\n const ids = p.signers.map(s => s.id);\n if (new Set(ids).size !== ids.length) problems.push(\"signer ids are not unique\");\n for (const s of p.signers as readonly IdentitySigner<unknown>[]) {\n if (![\"in-app\", \"external-tool\", \"redirect\", \"publish\"].includes(s.kind)) problems.push(`signer ${s.id} has an unknown kind`);\n if (s.kind === \"in-app\" && typeof s.run !== \"function\") problems.push(`signer ${s.id} has no run()`);\n if (s.kind === \"external-tool\" && (typeof s.instructions !== \"function\" || typeof s.parse !== \"function\")) problems.push(`signer ${s.id} needs instructions() and parse()`);\n if (s.kind === \"redirect\" && typeof s.start !== \"function\") problems.push(`signer ${s.id} has no start()`);\n if (s.kind === \"publish\" && (typeof s.instructions !== \"function\" || typeof s.evidence !== \"function\")) problems.push(`signer ${s.id} needs instructions() and evidence()`);\n }\n return problems;\n}\n\n/** Signers of a provider that run on this platform and are available in this window. */\nexport function availableSigners<E>(p: IdentityProofProvider<E>, platform: IdentityProofProvider[\"platforms\"][number]): IdentitySigner<E>[] {\n return p.signers.filter(s => (s.platforms ?? p.platforms).includes(platform) && (s.available?.() ?? true));\n}\n\n/**\n * The engine's `ctx.fetch`: HTTPS GET only, no credentials or referrer, a time-out, a size cap,\n * redirects refused unless asked for.\n * `online` false refuses everything (the person turned the network off).\n */\nexport function boundedIdentityFetch(options: { online: () => boolean; timeoutMs?: number; fetcher?: typeof fetch } = { online: () => true }): IdentityFetch {\n return async (url, { headers, maxBytes = 64 * 1024, signal, redirect = \"error\" } = {}) => {\n if (!options.online()) throw new Error(\"Offline: this identity cannot be checked now\");\n const target = new URL(url);\n if (target.protocol !== \"https:\" || target.username || target.password) throw new Error(\"Identity checks use HTTPS only\");\n const timeout = AbortSignal.timeout(options.timeoutMs ?? 10_000);\n const response = await (options.fetcher ?? fetch)(target.href, {\n headers, signal: signal ? AbortSignal.any([signal, timeout]) : timeout,\n credentials: \"omit\", referrerPolicy: \"no-referrer\", cache: \"no-store\", redirect,\n });\n if (Number(response.headers.get(\"content-length\")) > maxBytes) throw new Error(\"Identity check response too large\");\n const reader = response.body?.getReader();\n const parts: Uint8Array[] = [];\n let length = 0;\n if (reader) {\n try {\n for (;;) {\n const { done, value } = await reader.read();\n if (done) break;\n length += value.length;\n if (length > maxBytes) throw new Error(\"Identity check response too large\");\n parts.push(value);\n }\n } finally { await reader.cancel().catch(() => {}); }\n }\n const bytes = new Uint8Array(length);\n let offset = 0;\n for (const part of parts) { bytes.set(part, offset); offset += part.length; }\n return { status: response.status, contentType: response.headers.get(\"content-type\") ?? \"\", text: new TextDecoder().decode(bytes), bytes };\n };\n}\n","import type { GhostlyAdapterPlugin } from \"./registry\";\n\n/**\n * The adapter plugins compiled into this build. Empty unless the build lists some: `GHOSTLY_PLUGINS`\n * names their modules (comma-separated paths from the repository root, each default-exporting a\n * `GhostlyAdapterPlugin`), and the Vite plugin in `packages/browser/vite-plugin.ts` swaps this module\n * for one that imports them. Release builds never set it; the e2e build carries the SDK example.\n */\nexport const BUNDLED_PLUGINS: readonly GhostlyAdapterPlugin[] = [];\n","import type { LightningProviderDescriptor } from \"../engine/paymentAdapters/providers/lightning\";\nimport type { OnchainProviderDescriptor } from \"../engine/paymentAdapters/providers/onchain\";\nimport { providerDescriptorProblems, type ProviderDescriptor, type ProviderKind } from \"../engine/paymentAdapters/providers/types\";\nimport type { IdentityProofProvider } from \"../proofs/contract\";\nimport { descriptorProblems } from \"../proofs/verify\";\nimport { BUNDLED_PLUGINS } from \"./bundled\";\n\n/**\n * Adapters written outside the app: wallet sources (Lightning, on-chain) and identity-proof providers\n * that register here instead of being added to the built-in lists. A plugin is a plain object naming\n * its adapters; the registries (`providers/registry.ts`, `proofs/registry.ts`) list what is registered\n * after the built-ins and before the test fakes, and the same platform and mode rules apply to all.\n *\n * Two ways in:\n * - at build time: `GHOSTLY_PLUGINS` lists the modules and `bundled.ts` is swapped for them;\n * - at run time: code already running in the engine's realm calls `registerAdapters` before the\n * pickers are shown (or after: the engine re-reads the lists when something registers).\n *\n * Trust: a plugin runs with the app's privileges, in the app's own JavaScript realm. There is no\n * sandbox, no permission prompt and no store; whoever builds a plugin in, or loads one, trusts its\n * author with everything the app can do, keys and sats included. A manifest with permissions and an\n * isolated runtime are a later phase (docs/wisps/ADAPTER-ROADMAP.md, \"Plugins, apps, catalogs\").\n *\n * The state lives on `globalThis` under a well-known symbol, so a plugin bundled with its own copy of\n * the SDK (a second copy of this module) still registers into the same lists the app reads.\n */\n\n/** The generation of the adapter contracts. A plugin says which one it was written against. */\nexport const SDK_API = 1;\nexport const PLUGIN_ID = /^[a-z][a-z0-9-]{0,63}$/;\n\nexport interface GhostlyAdapterPlugin {\n /** Stable, unique among plugins: `^[a-z][a-z0-9-]{0,63}$`. */\n id: string;\n /** The plugin's own version, for people. */\n version?: string;\n /** The contract generation it was written against: `SDK_API`. Another one is refused. */\n sdk: number;\n lightning?: readonly LightningProviderDescriptor[];\n onchain?: readonly OnchainProviderDescriptor[];\n identities?: readonly IdentityProofProvider[];\n}\n\nexport type AdapterKind = ProviderKind | \"identity\";\n\ninterface Store {\n plugins: Map<string, GhostlyAdapterPlugin>;\n /** Ids the built-in registries own, per kind: a plugin cannot take one. */\n reserved: Record<AdapterKind, Set<string>>;\n listeners: Set<() => void>;\n seeded: boolean;\n}\n\nconst KEY = Symbol.for(\"ghostly.adapters/1\");\nfunction store(): Store {\n const g = globalThis as unknown as Record<symbol, Store | undefined>;\n return (g[KEY] ??= { plugins: new Map(), reserved: { lightning: new Set(), onchain: new Set(), identity: new Set() }, listeners: new Set(), seeded: false });\n}\n\nconst ids = (kind: AdapterKind, plugin: GhostlyAdapterPlugin): string[] => {\n // Not trusted to be well formed: pluginProblems reads it before saying what is wrong with it.\n const list: unknown = kind === \"identity\" ? plugin.identities : kind === \"lightning\" ? plugin.lightning : plugin.onchain;\n return Array.isArray(list) ? list.map((a: { id?: string } | null) => a?.id as string) : [];\n};\nconst KINDS: readonly AdapterKind[] = [\"lightning\", \"onchain\", \"identity\"];\n\n/** Problems with a plugin, empty when it can be registered. Does not look at what is already registered. */\nexport function pluginProblems(plugin: GhostlyAdapterPlugin): string[] {\n const problems: string[] = [];\n if (!plugin || typeof plugin !== \"object\") return [\"not a plugin object\"];\n if (!PLUGIN_ID.test(plugin.id ?? \"\")) problems.push(\"plugin id must match ^[a-z][a-z0-9-]{0,63}$\");\n if (plugin.sdk !== SDK_API) problems.push(`plugin was written for SDK API ${String(plugin.sdk)}; this app speaks ${SDK_API}`);\n if (plugin.version !== undefined && (typeof plugin.version !== \"string\" || plugin.version.length > 64)) problems.push(\"version must be a short string\");\n for (const kind of [\"lightning\", \"onchain\"] as const) {\n const list = plugin[kind];\n if (list === undefined) continue;\n if (!Array.isArray(list)) { problems.push(`${kind} must be a list of descriptors`); continue; }\n for (const d of list as ProviderDescriptor<unknown>[]) {\n if (!d || typeof d !== \"object\") { problems.push(`${kind} ${String(d)}: not a descriptor`); continue; }\n const own = providerDescriptorProblems(d);\n if (d?.kind && d.kind !== kind) own.push(`is listed under ${kind} but says kind ${String(d.kind)}`);\n problems.push(...own.map((p) => `${kind} ${String(d?.id)}: ${p}`));\n }\n }\n if (plugin.identities !== undefined) {\n if (!Array.isArray(plugin.identities)) problems.push(\"identities must be a list of providers\");\n else for (const p of plugin.identities) {\n if (!p || typeof p !== \"object\") problems.push(`identity ${String(p)}: not a provider`);\n else problems.push(...descriptorProblems(p).map((m) => `identity ${String(p.id)}: ${m}`));\n }\n }\n for (const kind of KINDS) {\n const list = ids(kind, plugin);\n if (new Set(list).size !== list.length) problems.push(`${kind} ids are not unique within the plugin`);\n }\n if (!KINDS.some((kind) => ids(kind, plugin).length)) problems.push(\"the plugin brings no adapter\");\n return problems;\n}\n\n/** The built-in registries say which ids are theirs. A plugin using one is refused. */\nexport function reserveAdapterIds(kind: AdapterKind, list: readonly string[]): void {\n const s = store();\n for (const id of list) s.reserved[kind].add(id);\n}\n\n/**\n * Registers a plugin's adapters. Throws, registering nothing, when the plugin is malformed, its id or\n * one of its adapters' ids is taken (by a built-in or by another plugin), or its contract generation\n * is another one. Returns the function that unregisters it.\n */\nexport function registerAdapters(plugin: GhostlyAdapterPlugin): () => void {\n const problems = pluginProblems(plugin);\n const s = store();\n if (!problems.length) {\n if (s.plugins.has(plugin.id)) problems.push(`a plugin with id ${plugin.id} is already registered`);\n for (const kind of KINDS) for (const id of ids(kind, plugin)) {\n if (s.reserved[kind].has(id)) problems.push(`${kind} ${id} is a built-in of this app`);\n for (const other of s.plugins.values()) if (ids(kind, other).includes(id)) problems.push(`${kind} ${id} is already registered by plugin ${other.id}`);\n }\n }\n if (problems.length) throw new Error(`Cannot register plugin ${String(plugin?.id)}: ${problems.join(\"; \")}`);\n s.plugins.set(plugin.id, plugin);\n notify(s);\n return () => { if (s.plugins.get(plugin.id) === plugin) { s.plugins.delete(plugin.id); notify(s); } };\n}\n\nfunction notify(s: Store) { for (const listener of s.listeners) { try { listener(); } catch { /* a listener's problem */ } } }\n\n/** Called when a plugin registers or unregisters: the engine re-reads the lists. */\nexport function onAdaptersChanged(listener: () => void): () => void {\n const s = store();\n s.listeners.add(listener);\n return () => { s.listeners.delete(listener); };\n}\n\n/** Every registered plugin, in registration order (bundled ones first). */\nexport function registeredPlugins(): readonly GhostlyAdapterPlugin[] { seed(); return [...store().plugins.values()]; }\nexport function registeredLightningProviders(): readonly LightningProviderDescriptor[] { return registeredPlugins().flatMap((p) => p.lightning ?? []); }\nexport function registeredOnchainProviders(): readonly OnchainProviderDescriptor[] { return registeredPlugins().flatMap((p) => p.onchain ?? []); }\nexport function registeredIdentityProviders(): readonly IdentityProofProvider[] { return registeredPlugins().flatMap((p) => p.identities ?? []); }\n\n/**\n * The plugins compiled into this build register once, when they are first asked for. A broken one is\n * reported and skipped rather than taking the app down with it; the e2e that expects it fails instead.\n */\nfunction seed() {\n const s = store();\n if (s.seeded) return;\n s.seeded = true;\n for (const plugin of BUNDLED_PLUGINS) {\n try { registerAdapters(plugin); }\n catch (error) { console.error(`[ghostly] a bundled adapter plugin was not loaded: ${error instanceof Error ? error.message : String(error)}`); }\n }\n}\n\n/** Tests only: forget every registration, including the bundled ones. */\nexport function resetAdapterRegistry(): void {\n const s = store();\n s.plugins.clear(); s.seeded = false;\n for (const kind of KINDS) s.reserved[kind].clear();\n notify(s);\n}\n","import type { BitcoinScriptType } from \"@ghostly/core\";\n\n/**\n * Person-facing steps to sign Ghostly's proof statement for a Bitcoin address, per common wallet: which\n * menu, what to paste where, which format to pick. Ghostly never sees the wallet or its keys; the person\n * pastes the signature back.\n *\n * Only what each wallet's own documentation or UI shows is claimed here. Hardware wallets differ by model\n * and firmware (ADAPTER-ROADMAP.md, \"Hardware and signing\"); their entries point to the vendor's guide.\n */\n\nexport type BitcoinSigningFormat = \"bip322\" | \"legacy\";\n\nexport interface BitcoinWalletGuide {\n id: string;\n name: string;\n /** Address types this wallet can sign for in a format Ghostly accepts. */\n scripts: readonly BitcoinScriptType[];\n format: BitcoinSigningFormat;\n steps: readonly string[];\n /** How to use the test networks, for Testnet mode. */\n testnet?: string;\n note?: string;\n docs?: string;\n}\n\n/** What a proof proves and what it does not; shown next to every Bitcoin proof, given or received. */\nexport const BITCOIN_PROOF_LIMITS = [\n \"Proves: whoever made this signature could sign with the key behind this address when they signed.\",\n \"Does not prove the address holds any balance, now or ever.\",\n \"Does not prove they sent or received any past payment.\",\n \"Does not prove they would pay you, or spend from this address at all.\",\n] as const;\n\nexport const BITCOIN_WALLET_GUIDES: readonly BitcoinWalletGuide[] = [\n {\n id: \"sparrow\",\n name: \"Sparrow Wallet\",\n scripts: [\"p2wpkh\", \"p2tr\", \"p2pkh\"],\n format: \"bip322\",\n steps: [\n \"Open the wallet that holds the address, then Tools → Sign/Verify Message.\",\n \"Address: paste the address you are proving.\",\n \"Message: paste the statement Ghostly shows, exactly as it is.\",\n \"Format: BIP322 (Simple) for a bc1q… or bc1p… address (tb1… on test networks). For a legacy 1… address, Standard (Electrum).\",\n \"Sign Message, then copy the whole Signature (it starts with smp…) and paste it into Ghostly.\",\n ],\n testnet: \"Tools → Restart In → Testnet or Signet, with a wallet on that network.\",\n note: \"Sparrow offers BIP322 (Simple) for software wallets only; a connected hardware wallet signs the Standard (Electrum) format, which Ghostly accepts only for a legacy 1… address.\",\n docs: \"https://sparrowwallet.com/docs/\",\n },\n {\n id: \"bitcoin-core\",\n name: \"Bitcoin Core\",\n scripts: [\"p2pkh\"],\n format: \"legacy\",\n steps: [\n \"Bitcoin Core signs messages in the legacy format only, for a legacy address (1… on mainnet, m… or n… on test networks). Make one in the Receive tab with the Base58 (Legacy) type, or with `getnewaddress \\\"\\\" legacy`.\",\n \"GUI: File → Sign message…; paste the address, paste the statement, Sign Message, copy the signature.\",\n \"Command line: bitcoin-cli signmessage \\\"<address>\\\" '<statement>' (single quotes keep the statement's double quotes intact).\",\n ],\n testnet: \"Run it with -signet, -testnet4 or -regtest; add the same flag to bitcoin-cli.\",\n docs: \"https://developer.bitcoin.org/reference/rpc/signmessage.html\",\n },\n {\n id: \"electrum\",\n name: \"Electrum\",\n scripts: [\"p2pkh\"],\n format: \"legacy\",\n steps: [\n \"Electrum signs in the legacy format, which Ghostly accepts only for a legacy 1… address (m…/n… on test networks); its default bc1… wallets need another wallet.\",\n \"Addresses tab (View → Show Addresses), right-click the address → Sign/verify message.\",\n \"Paste the statement into Message, click Sign, copy the Signature and paste it into Ghostly.\",\n ],\n testnet: \"Start Electrum with --testnet or --signet.\",\n docs: \"https://electrum.readthedocs.io/\",\n },\n {\n id: \"coldcard\",\n name: \"COLDCARD\",\n scripts: [\"p2wpkh\", \"p2tr\", \"p2pkh\"],\n format: \"bip322\",\n steps: [\n \"Follow COLDCARD's BIP-322 guide for your firmware: it signs the statement for one of its addresses via MicroSD, NFC or QR, depending on the model.\",\n \"Give it the statement exactly as Ghostly shows it and the address you are proving.\",\n \"Paste the signature it produces into Ghostly.\",\n ],\n note: \"Older firmware signs only the legacy format (Ghostly accepts that for a legacy 1… address) and limits message length and characters.\",\n docs: \"https://coldcard.com/docs/bip322/\",\n },\n {\n id: \"trezor\",\n name: \"Trezor Suite\",\n scripts: [\"p2pkh\"],\n format: \"legacy\",\n steps: [\n \"Trezor signs messages in the legacy format; Ghostly accepts it for an address of a Legacy account (1…).\",\n \"Open the Legacy account and choose Sign & verify in its menu.\",\n \"Paste the statement into Message, pick the address, Sign, confirm on the device, copy the signature into Ghostly.\",\n ],\n },\n {\n id: \"other\",\n name: \"Another wallet\",\n scripts: [\"p2wpkh\", \"p2tr\", \"p2sh\", \"p2pkh\"],\n format: \"bip322\",\n steps: [\n \"Look for \\\"Sign message\\\" in the wallet. Choose BIP-322 when it asks for a format.\",\n \"Sign the statement exactly as Ghostly shows it, with the address you are proving.\",\n \"A nested SegWit address (3…, or 2… on test networks) needs a full BIP-322 signature (it starts with ful…).\",\n \"Paste the signature into Ghostly; it tells you right away if it does not match.\",\n ],\n },\n];\n\n/** The guides that can sign for an address of this type. */\nexport function guidesFor(script: BitcoinScriptType): BitcoinWalletGuide[] {\n return BITCOIN_WALLET_GUIDES.filter(g => g.scripts.includes(script));\n}\n","import { BitcoinAddressError, decodeAnyBitcoinAddress, verifyBitcoinMessage, type BitcoinMessageVerdict, type BitcoinProofNetwork, type BitcoinScriptType, type IdentityStatement } from \"@ghostly/core\";\nimport type { ExternalToolSigner, IdentityProofProvider, InstructionStep } from \"../contract\";\nimport { BITCOIN_PROOF_LIMITS, BITCOIN_WALLET_GUIDES, guidesFor, type BitcoinWalletGuide } from \"../bitcoinWallets\";\n\n/**\n * Bitcoin address proofs: the person signs the statement with the key behind an address, in their own\n * wallet, and pastes the signature. BIP-322 (simple or full) or, for a legacy P2PKH address only, the\n * legacy `signmessage` format; checked on this device by `verifyBitcoinMessage` (@ghostly/core, pinned to\n * BIP-322 2.0.0). No blockchain lookup: the proof says nothing about coins. See\n * docs/wisps/306-bitcoin.md.\n *\n * The address's own prefix says its network: a proof for a test-network address (tb1…, bcrt1…, m…, n…, 2…)\n * is verified as such and labelled \"test network\" wherever it is shown.\n *\n * TODO(ghostly-wallet signer): \"Sign with my Ghostly wallet\" belongs here as an `in-app` signer once an\n * on-chain source can sign messages. None can today: `OnchainProvider` (engine/paymentAdapters/providers/\n * onchain.ts) has no such method, Bitcoin Core's `signmessage` is legacy-only, and bdk-wallet-web has no\n * message signing. The BDK source keeps its phrase in the engine, so the path is an optional\n * `signMessage?(address, message)` on `OnchainProvider`, implemented in bdk.ts with a BIP-322 simple\n * witness for its own P2WPKH/P2TR address, exposed through an engine RPC that shows the statement and\n * asks first; never sign silently, and never hand the key to the UI.\n */\n\nexport interface BitcoinEvidence {\n /** As the wallet printed it: `smp…`/`ful…`, unprefixed BIP-322, or a legacy base64 signature. */\n signature: string;\n}\n\nconst MAX_SIGNATURE = 8192;\nconst SIGNATURE = /^(smp|ful|pof)?[A-Za-z0-9+/]+={0,2}$/;\n\nconst SCRIPT_NAMES: Record<BitcoinScriptType, string> = {\n p2wpkh: \"native SegWit (bc1q…)\", p2tr: \"Taproot (bc1p…)\", p2sh: \"nested SegWit (3…)\", p2pkh: \"legacy (1…)\",\n p2wsh: \"script / multisig (P2WSH)\", \"witness-unknown\": \"future SegWit version\",\n};\n\nfunction normalizeAddress(input: string): string {\n const value = input.trim();\n let info;\n try { info = decodeAnyBitcoinAddress(value); } catch (e) {\n throw Object.assign(new Error(e instanceof BitcoinAddressError ? e.message : \"Enter a Bitcoin address\"), { cause: e });\n }\n if (info.type === \"p2wsh\" || info.type === \"witness-unknown\")\n throw new Error(\"Ghostly can check proofs for single-key addresses only (bc1q…, bc1p…, 3…, 1…), not multisig or script addresses\");\n // Bech32 may be written in upper case; its canonical form is lower case. Base58 is case-sensitive.\n return info.witnessVersion === undefined ? value : value.toLowerCase();\n}\n\nconst isTestAddress = (address: string) => { try { return decodeAnyBitcoinAddress(address).network === \"testnet\"; } catch { return false; } };\n\nfunction shortAddress(address: string): string {\n return `${address.slice(0, address.startsWith(\"bcrt1\") ? 9 : 7)}…${address.slice(-4)}${isTestAddress(address) ? \" · test network\" : \"\"}`;\n}\n\n/** Wallet steps for this statement's address: the guide's own, or why this wallet cannot sign for it. */\nfunction walletInstructions(guide: BitcoinWalletGuide, statement: IdentityStatement) {\n const address = statement.binding.subject;\n const info = decodeAnyBitcoinAddress(address);\n const steps: InstructionStep[] = [];\n if (!guide.scripts.includes(info.type)) {\n const others = guidesFor(info.type).filter(g => g.id !== \"other\").map(g => g.name);\n steps.push({ text: `${guide.name} cannot sign for a ${SCRIPT_NAMES[info.type]} address in a format Ghostly accepts.${others.length ? ` Try ${others.join(\" or \")}, or another wallet that signs BIP-322.` : \" Use a wallet that signs BIP-322.\"}` });\n } else {\n steps.push({ text: \"Sign exactly this statement, as one line, without adding a line break:\", copy: statement.text });\n steps.push({ text: \"With this address:\", copy: address });\n for (const text of guide.steps) steps.push({ text });\n if (guide.id === \"bitcoin-core\") steps.push({ text: `Command line${info.network === \"testnet\" ? \" (add -signet, -testnet4 or -regtest)\" : \"\"}:`, copy: `bitcoin-cli signmessage \"${address}\" '${statement.text}'` });\n if (info.network === \"testnet\" && guide.testnet) steps.push({ text: `Test networks: ${guide.testnet}` });\n if (guide.note) steps.push({ text: guide.note });\n }\n steps.push({ text: \"This shows you can sign for the address. It does not show a balance, a past payment, or that you would pay.\" });\n return { steps, paste: { label: \"Signature\", placeholder: \"smp… (BIP-322) or the base64 signature your wallet shows\", multiline: true } };\n}\n\nfunction cleanSignature(pasted: string): string {\n const signature = pasted.replace(/\\s+/g, \"\");\n if (!signature) throw new Error(\"Paste the signature your wallet made\");\n if (signature.length > MAX_SIGNATURE || !SIGNATURE.test(signature)) throw new Error(\"That is not a Bitcoin message signature: paste the whole signature, as the wallet shows it\");\n return signature;\n}\n\nfunction check(statement: IdentityStatement, signature: string): { verdict: Extract<BitcoinMessageVerdict, { state: \"valid\" }>; network: BitcoinProofNetwork } {\n const address = statement.binding.subject;\n const network = decodeAnyBitcoinAddress(address).network;\n const verdict = verifyBitcoinMessage({ address, message: statement.text, signature, network });\n if (verdict.state !== \"valid\") throw new Error(verdict.reason);\n return { verdict, network };\n}\n\nfunction walletSigner(guide: BitcoinWalletGuide): ExternalToolSigner<BitcoinEvidence> {\n return {\n id: guide.id, kind: \"external-tool\", label: guide.name,\n description: guide.format === \"legacy\" ? \"Legacy message signature: for a legacy 1… address only.\" : \"BIP-322 message signature.\",\n instructions: statement => walletInstructions(guide, statement),\n parse(pasted, statement) {\n const signature = cleanSignature(pasted);\n // Say right away when it does not match, while the wallet is still open.\n check(statement, signature);\n return { signature };\n },\n };\n}\n\nexport const bitcoin: IdentityProofProvider<BitcoinEvidence> = {\n id: \"bitcoin\",\n label: \"Bitcoin address\",\n category: \"self-custodied\",\n summary: \"Sign a message in your own wallet\",\n description: \"Proves you can sign with the key behind a Bitcoin address: your wallet signs the statement and you paste the signature.\",\n limits: \"It does not prove a balance, a past payment, or that you would pay.\",\n platforms: [\"web\", \"extension\", \"desktop\"],\n subject: {\n label: \"Bitcoin address\",\n placeholder: \"bc1q… (tb1… on test networks)\",\n help: BITCOIN_PROOF_LIMITS.join(\" \"),\n normalize: normalizeAddress,\n short: shortAddress,\n },\n // BIP-21\n publicUri: address => `bitcoin:${address}`,\n validity: { defaultDays: 90, maxDays: 365 },\n privacy: \"Nothing: the signature is checked on this device, with no blockchain lookup. Contacts you share it with learn the address.\",\n signers: BITCOIN_WALLET_GUIDES.map(walletSigner),\n parseEvidence(raw) {\n const e = raw as BitcoinEvidence;\n if (!e || typeof e !== \"object\" || Array.isArray(e) || Object.keys(e).join(\",\") !== \"signature\" || typeof e.signature !== \"string\" ||\n e.signature.length > MAX_SIGNATURE || !SIGNATURE.test(e.signature)) throw new Error(\"That is not a Bitcoin message signature\");\n return { signature: e.signature };\n },\n async verify(statement, evidence) {\n const { verdict, network } = check(statement, evidence.signature);\n const format = verdict.format === \"legacy\" ? \"Legacy signmessage\" : verdict.format === \"bip322-full\" ? \"BIP-322 full\" : \"BIP-322 simple\";\n const script = { p2pkh: \"P2PKH\", p2wpkh: \"P2WPKH\", \"p2sh-p2wpkh\": \"P2SH-P2WPKH\", p2tr: \"P2TR\" }[verdict.script];\n return { subject: statement.binding.subject, source: `${format} signature, ${script}${network === \"testnet\" ? \", test network\" : \"\"}` };\n },\n};\n","import { BunkerSigner, type BunkerPointer } from 'nostr-tools/nip46';\nimport { SimplePool } from 'nostr-tools/pool';\nimport { generateSecretKey } from 'nostr-tools/pure';\nimport type { NostrProofEvent } from '@ghostly/core';\n\nexport interface NostrSigner {\n getPublicKey(): Promise<string>;\n signEvent(event: { kind: number; created_at: number; tags: string[][]; content: string }): Promise<NostrProofEvent>;\n}\nexport function extensionSigner(): NostrSigner | undefined {\n return (globalThis as typeof globalThis & { nostr?: NostrSigner }).nostr;\n}\n/** No NIP-05 lookup, arbitrary fetch, private key import or automatic relay switching. */\nexport function parseProofBunker(input: string): BunkerPointer {\n if (input.length > 4096) throw new Error('Bunker URL too long');\n let uri: URL;\n try { uri = new URL(input); } catch { throw new Error('Enter a bunker:// connection URL from your signer'); }\n const relays = uri.searchParams.getAll('relay');\n if (uri.protocol !== 'bunker:' || !/^[a-f0-9]{64}$/.test(uri.hostname) || uri.username || uri.password || uri.port || uri.hash || (uri.pathname && uri.pathname !== '/') ||\n relays.length < 1 || relays.length > 3) throw new Error('Invalid bunker connection URL');\n for (const address of relays) {\n const relay = new URL(address);\n if (relay.username || relay.password || relay.hash || !(relay.protocol === 'wss:' ||\n (relay.protocol === 'ws:' && ['127.0.0.1', 'localhost', '[::1]'].includes(relay.hostname))))\n throw new Error('Use secure wss:// relays (ws:// is allowed only on loopback for local testing)');\n }\n return { pubkey: uri.hostname, relays, secret: uri.searchParams.get('secret') };\n}\n/**\n * Runs `work` with the person's Nostr signer: the page's NIP-07 extension, or a NIP-46 bunker. `permissions`\n * is what the bunker is asked for (NIP-46 `connect`): the identity proof's `sign_event:30078` by default;\n * the social layer asks for the kinds it publishes.\n */\nexport async function withNostrSigner<T>(options: { bunker?: string; signal: AbortSignal; onAuth(url: string): void; permissions?: string; appName?: string }, work: (signer: NostrSigner) => Promise<T>): Promise<T> {\n options.signal.throwIfAborted();\n let signer: BunkerSigner | undefined;\n let pool: SimplePool | undefined;\n let clientKey: Uint8Array | undefined;\n let timer: ReturnType<typeof setTimeout> | undefined;\n let abort: (() => void) | undefined;\n let closed = false;\n const guard: NostrSigner = {\n getPublicKey: async () => { if (closed) throw new Error('Signer request cancelled'); const key = await active!.getPublicKey(); if (closed) throw new Error('Signer request cancelled'); return key; },\n signEvent: async event => { if (closed) throw new Error('Signer request cancelled'); const signed = await active!.signEvent(event); if (closed) throw new Error('Signer request cancelled'); return signed; },\n };\n let active: NostrSigner | undefined;\n try {\n const timeout = new Promise<never>((_, reject) => {\n abort = () => { closed = true; reject(new Error('Signing cancelled')); };\n options.signal.addEventListener('abort', abort, { once: true });\n timer = setTimeout(() => { closed = true; reject(new Error('Signer timed out. Your Ghostly identity is unchanged.')); }, 120_000);\n });\n const action = async () => {\n if (options.bunker) {\n const bp = parseProofBunker(options.bunker);\n pool = new SimplePool(); clientKey = generateSecretKey();\n signer = BunkerSigner.fromBunker(clientKey, bp, { pool, skipSwitchRelays: true, onauth: url => {\n try { const u = new URL(url); if (!closed && u.protocol === 'https:' && !u.username && !u.password) options.onAuth(u.href); } catch { /* Invalid signer URL: never navigate. */ }\n } });\n await signer.sendRequest('connect', [bp.pubkey, bp.secret ?? '', options.permissions ?? 'sign_event:30078', JSON.stringify({ name: options.appName ?? 'Ghostly — optional conversation proof' })]);\n active = signer;\n } else {\n active = extensionSigner();\n if (!active) throw new Error('No NIP-07 signer in this window. Use a bunker connection from your Nostr signer.');\n }\n if (closed) throw new Error('Signer request cancelled');\n return work(guard);\n };\n return await Promise.race([action(), timeout]);\n } finally {\n closed = true;\n if (timer) clearTimeout(timer);\n if (abort) options.signal.removeEventListener('abort', abort);\n await signer?.close(); pool?.destroy(); clientKey?.fill(0);\n }\n}\n","import { schnorr } from \"@noble/curves/secp256k1.js\";\nimport { sha256 } from \"@noble/hashes/sha2.js\";\nimport { npubEncode, decode as nip19Decode } from \"nostr-tools/nip19\";\nimport { utf8Encode, type IdentityStatement } from \"@ghostly/core\";\nimport type { IdentityProofProvider, InAppSigner, SignerSession } from \"../contract\";\nimport { extensionSigner, withNostrSigner } from \"../nostr\";\n\n/** A signed Nostr event (NIP-01). The evidence of a Nostr proof. */\nexport interface NostrEvent { id: string; pubkey: string; created_at: number; kind: number; tags: string[][]; content: string; sig: string }\n\n/**\n * NIP-78 application data (kind 30078): no new kind is claimed. Signers show it as such; it is never\n * published to a relay — the event travels only to the contacts the person shares it with.\n */\nexport const NOSTR_PROOF_KIND = 30078;\nconst HEX64 = /^[a-f0-9]{64}$/;\n\n/** The exact event a signer is asked to sign: the statement as content, dated at issue, expiring with it. */\nexport function nostrProofTemplate(statement: IdentityStatement) {\n const { issuedAt, expiresAt } = statement.binding;\n return { kind: NOSTR_PROOF_KIND, created_at: issuedAt, content: statement.text,\n tags: [[\"d\", \"ghostly-identity-proof\"], [\"expiration\", String(expiresAt)]] };\n}\n\nconst hex = (b: Uint8Array) => Array.from(b, x => x.toString(16).padStart(2, \"0\")).join(\"\");\nconst unhex = (s: string) => Uint8Array.from(s.match(/../g)!, x => parseInt(x, 16));\n\nfunction normalizeNostrKey(input: string): string {\n const value = input.trim();\n if (HEX64.test(value.toLowerCase())) return value.toLowerCase();\n if (value.startsWith(\"npub1\")) {\n try { const decoded = nip19Decode(value); if (decoded.type === \"npub\") return decoded.data; } catch { /* fall through */ }\n }\n throw new Error(\"Enter a Nostr public key: npub1… or 64 hex characters\");\n}\n\nfunction nostrSigner(id: \"nip07\" | \"nip46\"): InAppSigner<NostrEvent> {\n return {\n id, kind: \"in-app\",\n label: id === \"nip07\" ? \"Browser extension (NIP-07)\" : \"Remote signer (NIP-46)\",\n description: id === \"nip07\" ? \"Approve in the Nostr extension of this browser.\" : \"Paste the bunker:// link from your signer app, then approve there.\",\n fields: id === \"nip46\" ? [{ name: \"bunker\", label: \"Signer connection link\", kind: \"secret\", placeholder: \"bunker://…\" }] : undefined,\n platforms: id === \"nip07\" ? [\"web\", \"desktop\"] : undefined,\n available: id === \"nip07\" ? () => !!extensionSigner() : undefined,\n run: (ctx, work) => withNostrSigner({ bunker: id === \"nip46\" ? ctx.values.bunker : undefined, signal: ctx.signal, onAuth: ctx.onAuthUrl }, signer => {\n const session: SignerSession<NostrEvent> = {\n subject: async () => normalizeNostrKey(await signer.getPublicKey()),\n sign: async statement => {\n ctx.onProgress(\"Approve the Ghostly identity statement in your signer.\");\n return (await signer.signEvent(nostrProofTemplate(statement))) as NostrEvent;\n },\n };\n return work(session);\n }),\n };\n}\n\nexport const nostr: IdentityProofProvider<NostrEvent> = {\n id: \"nostr\",\n label: \"Nostr\",\n category: \"self-custodied\",\n summary: \"Sign once with your Nostr signer\",\n description: \"Proves you hold a Nostr key: your signer signs the statement, your key never leaves it.\",\n platforms: [\"web\", \"extension\", \"desktop\"],\n subject: {\n label: \"Public key\", placeholder: \"npub1…\",\n normalize: normalizeNostrKey,\n short: key => { const npub = npubEncode(key); return `${npub.slice(0, 10)}…${npub.slice(-4)}`; },\n },\n // NIP-21\n publicUri: key => `nostr:${npubEncode(key)}`,\n validity: { defaultDays: 90, maxDays: 365 },\n privacy: \"Nothing: the signature is checked on this device. The proof is never published to a relay.\",\n signers: [nostrSigner(\"nip07\"), nostrSigner(\"nip46\")],\n parseEvidence(raw) {\n const e = raw as NostrEvent;\n if (!e || typeof e !== \"object\" || Array.isArray(e) || Object.keys(e).sort().join(\",\") !== \"content,created_at,id,kind,pubkey,sig,tags\" ||\n typeof e.id !== \"string\" || !HEX64.test(e.id) || typeof e.pubkey !== \"string\" || !HEX64.test(e.pubkey) ||\n typeof e.sig !== \"string\" || !/^[a-f0-9]{128}$/.test(e.sig) || !Number.isSafeInteger(e.kind) || !Number.isSafeInteger(e.created_at) ||\n typeof e.content !== \"string\" || e.content.length > 2048 || !Array.isArray(e.tags) || e.tags.length > 4 ||\n !e.tags.every(t => Array.isArray(t) && t.length <= 4 && t.every(x => typeof x === \"string\" && x.length <= 128)))\n throw new Error(\"That is not a signed Nostr event\");\n return { id: e.id, pubkey: e.pubkey, created_at: e.created_at, kind: e.kind, tags: e.tags.map(t => [...t]), content: e.content, sig: e.sig };\n },\n async verify(statement, event) {\n const expected = nostrProofTemplate(statement);\n if (event.pubkey !== statement.binding.subject) throw new Error(\"Signed by another Nostr key\");\n if (event.kind !== expected.kind || event.created_at !== expected.created_at || event.content !== expected.content ||\n JSON.stringify(event.tags) !== JSON.stringify(expected.tags)) throw new Error(\"The signer did not sign the exact Ghostly statement\");\n // NIP-01 id, recomputed here: never trust the id or a verification flag the event carries.\n const id = hex(sha256(utf8Encode(JSON.stringify([0, event.pubkey, event.created_at, event.kind, event.tags, event.content]))));\n if (event.id !== id) throw new Error(\"The Nostr event id does not match its content\");\n let valid: boolean;\n try { valid = schnorr.verify(unhex(event.sig), unhex(id), unhex(event.pubkey)); } catch { valid = false; }\n if (!valid) throw new Error(\"Invalid Nostr signature\");\n return { subject: event.pubkey, source: \"Nostr signature (NIP-01, BIP-340)\" };\n },\n // The public profile, follows and notes of a verified key are the Nostr social layer's\n // (engine/nostrSocial.ts): loaded on request from the relays the person configured, not here.\n};\n","import {\n DOMAIN_FILE_MAX_BYTES, decodeDnsResponse, dnsProofName, encodeDnsQuery, isPublicAddress, nip05RootKey, normalizeDomain,\n recordsFromTxt, recordsFromWellKnown, recordsName, toBase64Url, txtValue, wellKnownUrl,\n type DnsResponse, type DnsType, type DomainMethod, type DomainRecord,\n} from '@ghostly/core';\nimport type { IdentityFetch } from './contract';\n\n/**\n * Network side of domain proofs: DNS TXT through DNS over HTTPS and the\n * `/.well-known/` files, under strict limits. Nothing here trusts the prover:\n * the domain comes from the signed binding, the URLs are built here, every\n * answer is bounded and parsed strictly, and lookups are cached only briefly.\n *\n * What a check reveals, and to whom:\n * - DNS: the chosen resolver learns that this device looked up `_ghostly.<domain>`\n * (the next one in DOH_RESOLVERS too, when the chosen one gives no answer).\n * The domain's own servers see only the resolver.\n * - HTTPS / NIP-05: the resolver learns the domain (its addresses are looked up\n * first, to refuse private networks), and the domain's web server — run by\n * the person being checked — sees this device's IP address and when.\n */\n\nexport type DohResolverId = 'quad9' | 'cloudflare' | 'google';\nexport interface DohResolver { id: DohResolverId; name: string; url: string }\n/** Public resolvers that answer RFC 8484 wire-format GETs with CORS (checked 2026-09-23). */\nexport const DOH_RESOLVERS: readonly DohResolver[] = [\n { id: 'quad9', name: 'Quad9', url: 'https://dns.quad9.net/dns-query' },\n { id: 'cloudflare', name: 'Cloudflare', url: 'https://cloudflare-dns.com/dns-query' },\n { id: 'google', name: 'Google', url: 'https://dns.google/dns-query' },\n];\nexport const DEFAULT_RESOLVER: DohResolverId = 'quad9';\nexport const resolverById = (id: string | null | undefined): DohResolver =>\n DOH_RESOLVERS.find(r => r.id === id) ?? DOH_RESOLVERS.find(r => r.id === DEFAULT_RESOLVER)!;\n\n/** The person's resolver choice, per browser profile (UI and engine share the origin). */\nexport const RESOLVER_SETTING = 'ghostly-doh-resolver';\nexport function chosenResolver(): DohResolver {\n try { return resolverById(globalThis.localStorage?.getItem(RESOLVER_SETTING)); } catch { return resolverById(undefined); }\n}\nexport function chooseResolver(id: DohResolverId): void {\n try { globalThis.localStorage?.setItem(RESOLVER_SETTING, resolverById(id).id); } catch { /* not persisted: the default stays */ }\n}\n\nexport const DNS_MAX_BYTES = 16 * 1024;\n/**\n * Lookups are shared between checks for the DNS TTL, capped: never staler than the resolver itself would\n * be, so a re-check sees a removal as soon as DNS does. Files have no TTL and are only shared in flight.\n */\nconst CACHE_MAX_MS = 30_000, CACHE_FILE_MS = 0;\n\n/** The contract's `ctx.fetch`: HTTPS GET, no credentials, bounded, redirects refused unless asked, with raw bytes. */\nexport type DomainFetch = IdentityFetch;\n\nexport interface DomainLookupOptions {\n fetch: DomainFetch;\n resolver?: DohResolverId;\n signal?: AbortSignal;\n /** Milliseconds. */\n now?: () => number;\n}\n\n/** A check that did not confirm the record, with a message for people. */\nexport class DomainCheckError extends Error {}\n\n/** A resolver that gave no DNS answer at all: not reached, or it refused the request. The next one may answer. */\nclass ResolverUnavailable extends DomainCheckError {}\n\n/** One RFC 8484 GET to one resolver. The query has ID 0 and no client subnet, so it carries only the name. */\nasync function askResolver(resolver: DohResolver, name: string, type: DnsType, options: DomainLookupOptions): Promise<DnsResponse> {\n let response: Awaited<ReturnType<DomainFetch>>;\n try {\n response = await options.fetch(`${resolver.url}?dns=${toBase64Url(encodeDnsQuery(name, type))}`, {\n headers: { accept: 'application/dns-message' }, maxBytes: DNS_MAX_BYTES, signal: options.signal, redirect: 'error' });\n } catch (e) {\n throw /too large/i.test(String(e))\n ? new DomainCheckError(`${resolver.name} sent an answer that is too large`)\n : new ResolverUnavailable(`${resolver.name} could not be reached`);\n }\n if (response.status !== 200 || !/^application\\/dns-message\\b/i.test(response.contentType))\n throw new ResolverUnavailable(`${resolver.name} refused the lookup`);\n let answer: DnsResponse;\n try { answer = decodeDnsResponse(response.bytes, name, type); } catch { throw new DomainCheckError(`${resolver.name} sent an unusable answer`); }\n if (answer.rcode !== 0 && answer.rcode !== 3) throw new DomainCheckError(`${resolver.name} could not resolve ${name}`);\n return answer;\n}\n\n/**\n * A lookup through the chosen resolver, and through the others in turn (in list order) only when it gives no DNS\n * answer at all. Quad9, for one, cannot be read from WebKit (the Mac desktop app, Safari): its HTTP/3 answers carry\n * no CORS header. An answer, even \"no such name\" or a failure, is never asked again elsewhere. `resolver`: who answered.\n */\nexport async function dohQuery(name: string, type: DnsType, options: DomainLookupOptions): Promise<DnsResponse & { resolver: DohResolverId }> {\n const chosen = resolverById(options.resolver);\n let first: DomainCheckError | undefined;\n for (const resolver of [chosen, ...DOH_RESOLVERS.filter(r => r !== chosen)]) {\n try { return { ...await askResolver(resolver, name, type, options), resolver: resolver.id }; }\n catch (e) {\n if (!(e instanceof ResolverUnavailable) || options.signal?.aborted) throw e;\n first ??= e;\n }\n }\n throw first!;\n}\n\n/**\n * Refuses a domain whose addresses are not all public before its web server is\n * contacted, so a proof cannot make a contact's app probe their own network.\n * The browser resolves again when it fetches; a name that changes its answer in\n * between (rebinding) is not fully excluded by this check — see the draft.\n */\nexport async function assertPublicHost(domain: string, options: DomainLookupOptions): Promise<void> {\n const [v4, v6] = await Promise.all([dohQuery(domain, 'A', options), dohQuery(domain, 'AAAA', options)]);\n const addresses = [...v4.answers, ...v6.answers].map(a => a.data);\n if (!addresses.length) throw new DomainCheckError(`${domain} has no address`);\n if (!addresses.every(isPublicAddress)) throw new DomainCheckError(`${domain} points to a private network address, so it was not contacted`);\n}\n\n/** A well-known file: https only, no redirects, bounded. `undefined` when the server says it is not there. */\nexport async function fetchWellKnown(domain: string, method: 'https' | 'nip05', options: DomainLookupOptions): Promise<string | undefined> {\n await assertPublicHost(domain, options);\n const url = wellKnownUrl(domain, method);\n let response: Awaited<ReturnType<DomainFetch>>;\n try {\n response = await options.fetch(url, { headers: { accept: 'application/json' }, maxBytes: DOMAIN_FILE_MAX_BYTES, signal: options.signal, redirect: 'error' });\n } catch (e) {\n throw new DomainCheckError(/too large/i.test(String(e)) ? `${url} is too large`\n : `${domain} could not be reached. The file must be served at exactly this address, without a redirect, with the header Access-Control-Allow-Origin: *`);\n }\n if (response.status === 404 || response.status === 410) return;\n if (response.status >= 300 && response.status < 400) throw new DomainCheckError(`${domain} answered with a redirect; the file must be served at exactly ${url}`);\n if (response.status !== 200) throw new DomainCheckError(`${domain} answered ${response.status}`);\n try { return new TextDecoder('utf-8', { fatal: true }).decode(response.bytes); } catch { throw new DomainCheckError(`${url} is not text`); }\n}\n\nexport interface DomainLookup {\n domain: string;\n method: DomainMethod;\n /** DNS / HTTPS: the Ghostly records found. */\n records: DomainRecord[];\n /** NIP-05: the Nostr key named for the domain itself. */\n nostrKey?: string;\n /** A record or file exists at all (as opposed to nothing there). */\n found: boolean;\n /** The resolver reported DNSSEC validation of the TXT answer. */\n dnssec: boolean;\n resolver: DohResolverId;\n}\n\nasync function lookup(domain: string, method: DomainMethod, options: DomainLookupOptions): Promise<DomainLookup & { ttlMs: number }> {\n const resolver = resolverById(options.resolver).id;\n if (method === 'dns') {\n const answer = await dohQuery(dnsProofName(domain), 'TXT', options);\n const values: string[] = [];\n for (const record of answer.answers.slice(0, 32)) { try { values.push(txtValue(record.data)); } catch { /* not text: not ours */ } }\n const ttlMs = answer.answers.length ? Math.min(...answer.answers.map(a => a.ttl)) * 1000 : 0;\n return { domain, method, resolver: answer.resolver, records: recordsFromTxt(values), found: answer.answers.length > 0, dnssec: answer.authenticated, ttlMs };\n }\n const text = await fetchWellKnown(domain, method, options);\n const base = { domain, method, resolver, dnssec: false, records: [] as DomainRecord[], ttlMs: CACHE_FILE_MS };\n if (text === undefined) return { ...base, found: false };\n try {\n return method === 'nip05' ? { ...base, found: true, nostrKey: nip05RootKey(text) } : { ...base, found: true, records: recordsFromWellKnown(text) };\n } catch { throw new DomainCheckError(`${wellKnownUrl(domain, method)} is not a valid ${method === 'nip05' ? 'NIP-05' : 'Ghostly'} file`); }\n}\n\nconst cache = new Map<string, { until: number; result: Promise<DomainLookup & { ttlMs: number }> }>();\n/** Tests only. */\nexport function clearDomainCache(): void { cache.clear(); }\n\n/** What a domain publishes by one method. Concurrent and back-to-back checks share one lookup. */\nexport async function lookupDomain(domainInput: string, method: DomainMethod, options: DomainLookupOptions): Promise<DomainLookup> {\n const domain = normalizeDomain(domainInput), now = options.now ?? Date.now, resolver = resolverById(options.resolver).id;\n const id = `${resolver}|${method}|${domain}`;\n const hit = cache.get(id);\n if (hit && hit.until > now()) { const { ttlMs: _ttl, ...result } = await hit.result; return result; }\n const started = now();\n const entry = { until: started + CACHE_MAX_MS, result: lookup(domain, method, { ...options, resolver }) };\n cache.set(id, entry);\n if (cache.size > 128) cache.delete(cache.keys().next().value!);\n try {\n const { ttlMs, ...result } = await entry.result;\n entry.until = started + Math.min(CACHE_MAX_MS, ttlMs);\n return result;\n } catch (e) {\n if (cache.get(id) === entry) cache.delete(id);\n throw e;\n }\n}\n\n/** Throws unless `domain` publishes `record` by `method` (DNS or HTTPS). Returns what was found. */\nexport async function assertDomainRecord(domain: string, method: 'dns' | 'https', record: DomainRecord, options: DomainLookupOptions): Promise<DomainLookup> {\n const found = await lookupDomain(domain, method, options);\n if (recordsName(found.records, record)) return found;\n const where = method === 'dns' ? `The TXT record at ${dnsProofName(domain)}` : wellKnownUrl(domain);\n if (found.records.some(r => r.key === record.key)) throw new DomainCheckError(`${where} is for another proof of this key`);\n if (found.records.length) throw new DomainCheckError(`${where} names another Ghostly key: this proof was replaced or removed`);\n throw new DomainCheckError(method === 'dns'\n ? `No Ghostly TXT record at ${dnsProofName(domain)}${found.found ? '' : ' (DNS changes can take a few minutes)'}`\n : found.found ? `${wellKnownUrl(domain)} names no Ghostly proof` : `${wellKnownUrl(domain)} was not found`);\n}\n\n/** Throws unless the domain's NIP-05 root name is `nostrKey`. */\nexport async function assertNip05(domain: string, nostrKey: string, options: DomainLookupOptions): Promise<DomainLookup> {\n const found = await lookupDomain(domain, 'nip05', options);\n if (found.nostrKey === nostrKey) return found;\n const url = wellKnownUrl(domain, 'nip05');\n throw new DomainCheckError(!found.found ? `${url} was not found` : found.nostrKey\n ? `${url} names another Nostr key for ${domain}` : `${url} has no root name \"_\" for ${domain}`);\n}\n","import { dnsProofName, domainTxtRecord, domainWellKnownFile, normalizeDomain, wellKnownUrl, type DomainRecord, type IdentityStatement } from \"@ghostly/core\";\nimport type { IdentityProofProvider, InAppSigner, PublishSigner, SignerSession } from \"../contract\";\nimport { assertDomainRecord, assertNip05, chosenResolver, resolverById } from \"../domain\";\nimport { extensionSigner, withNostrSigner } from \"../nostr\";\nimport { nostr, nostrProofTemplate, type NostrEvent } from \"./nostr\";\n\n/**\n * A domain the person controls (draft WISP 304, docs/wisps/304-domain.md).\n *\n * The statement (\"I control domain:example.com and authorize the Ghostly key K …\") is vouched for by\n * publishing, once, under the domain: a TXT record at `_ghostly.<domain>` or the file\n * `/.well-known/ghostly.json`, naming the proof key K and the statement id. The id commits the record to\n * the whole statement (dates and nonce too), so one published record vouches for exactly one proof. The\n * record names no contact and no conversation; sharing per contact is the contract's presentation.\n *\n * People who already run NIP-05 can instead sign the statement with the Nostr key their\n * `/.well-known/nostr.json` names for the domain itself (`_`): the proof holds only if both agree.\n */\nexport type DomainEvidence = { method: \"dns\" } | { method: \"https\" } | { method: \"nip05\"; event: NostrEvent };\n\nconst recordFor = (s: IdentityStatement): DomainRecord => ({ key: s.binding.key, proof: s.id });\nconst exactKeys = (raw: unknown, keys: string) => !!raw && typeof raw === \"object\" && !Array.isArray(raw) && Object.keys(raw).sort().join(\",\") === keys;\n\nconst dnsSigner: PublishSigner<DomainEvidence> = {\n id: \"dns\", kind: \"publish\", label: \"DNS TXT record\",\n description: \"Add a TXT record at your DNS host. Contacts' apps look it up through a DNS-over-HTTPS resolver.\",\n instructions(statement) {\n const domain = statement.binding.subject;\n return { steps: [\n { text: `Open the DNS settings of ${domain} at your registrar or DNS host and add a TXT record with this name. Some hosts add the domain themselves: then enter only the part before ${domain}.`, copy: dnsProofName(domain) },\n { text: \"Value (one string, exactly as shown):\", copy: domainTxtRecord(recordFor(statement)) },\n { text: \"Set the TTL to 300 seconds or less, so removing the record takes effect quickly. Then press Check: DNS changes can take a few minutes to appear.\" },\n { text: \"The record stays public while the proof is in use. Removing it withdraws this proof from every contact at their next check.\" },\n ] };\n },\n evidence: () => ({ method: \"dns\" }),\n};\n\nconst httpsSigner: PublishSigner<DomainEvidence> = {\n id: \"https\", kind: \"publish\", label: \"File on your website\",\n description: \"Upload a small file to your website. Contacts' apps download it from your server.\",\n instructions(statement) {\n const domain = statement.binding.subject;\n return { steps: [\n { text: `Create this file on the web server of ${domain}:`, copy: wellKnownUrl(domain) },\n { text: \"With exactly this content:\", copy: domainWellKnownFile([recordFor(statement)]) },\n { text: \"Serve it over HTTPS at exactly that address, without any redirect (not even to www), and with this header so browsers may read it:\", copy: \"Access-Control-Allow-Origin: *\" },\n { text: \"Then press Check. The file stays public while the proof is in use; deleting it withdraws this proof from every contact at their next check.\" },\n ] };\n },\n evidence: () => ({ method: \"https\" }),\n};\n\nconst domainField = { name: \"domain\", label: \"Domain\", kind: \"text\", placeholder: \"example.com\",\n help: \"Its /.well-known/nostr.json must name this Nostr key for \\\"_\\\" (the domain itself).\" } as const;\n\n/** NIP-05: the statement is signed by the Nostr key the domain's nostr.json names for itself. */\nfunction nip05Signer(id: \"nip05-nip07\" | \"nip05-nip46\"): InAppSigner<DomainEvidence> {\n const extension = id === \"nip05-nip07\";\n return {\n id, kind: \"in-app\",\n label: extension ? \"NIP-05 with a browser extension\" : \"NIP-05 with a remote signer\",\n description: \"Already run NIP-05? Sign with the Nostr key your domain's nostr.json names for itself. No new record needed.\",\n fields: extension ? [domainField] : [domainField, { name: \"bunker\", label: \"Signer connection link\", kind: \"secret\", placeholder: \"bunker://…\" }],\n platforms: extension ? [\"web\", \"desktop\"] : undefined,\n available: extension ? () => !!extensionSigner() : undefined,\n run: (ctx, work) => {\n const domain = normalizeDomain(ctx.values.domain ?? \"\");\n return withNostrSigner({ bunker: extension ? undefined : ctx.values.bunker, signal: ctx.signal, onAuth: ctx.onAuthUrl }, signer => {\n const session: SignerSession<DomainEvidence> = {\n subject: async () => domain,\n sign: async statement => {\n ctx.onProgress(\"Approve the Ghostly identity statement in your signer.\");\n return { method: \"nip05\", event: (await signer.signEvent(nostrProofTemplate(statement))) as NostrEvent };\n },\n };\n return work(session);\n });\n },\n };\n}\n\nexport const domain: IdentityProofProvider<DomainEvidence> = {\n id: \"domain\",\n label: \"Domain\",\n category: \"self-custodied\",\n summary: \"A DNS record or a file on your site\",\n description: \"Proves you control a domain name: publish a DNS TXT record or a file on its website, once. Contacts' apps look it up when you share it and can check again later.\",\n limits: \"Does not prove who runs the domain, only that whoever added the proof could publish under it.\",\n platforms: [\"web\", \"extension\", \"desktop\"],\n subject: {\n label: \"Domain\", placeholder: \"example.com\",\n help: \"A domain or subdomain you control. A proof for alice.example.com is published under alice.example.com.\",\n normalize: normalizeDomain,\n },\n publicUri: domain => `https://${domain}`,\n validity: { defaultDays: 90, maxDays: 365 },\n recheck: { afterSeconds: 86_400 },\n privacy: \"DNS: the contact's app asks the DNS-over-HTTPS resolver chosen in its settings (Quad9 by default; the next of Quad9, Cloudflare and Google when it cannot be reached) for the record, \" +\n \"so that resolver learns the domain was checked. Website or NIP-05: the resolver learns the domain, and the domain's web server sees the contact's IP address.\",\n signers: [dnsSigner, httpsSigner, nip05Signer(\"nip05-nip07\"), nip05Signer(\"nip05-nip46\")],\n parseEvidence(raw) {\n const e = raw as DomainEvidence;\n if (exactKeys(raw, \"method\") && (e.method === \"dns\" || e.method === \"https\")) return { method: e.method };\n if (exactKeys(raw, \"event,method\") && e.method === \"nip05\") return { method: \"nip05\", event: nostr.parseEvidence((e as { event: unknown }).event) };\n throw new Error(\"That is not domain proof evidence\");\n },\n async verify(statement, evidence, ctx) {\n const subject = statement.binding.subject;\n if (normalizeDomain(subject) !== subject) throw new Error(\"The domain is not in canonical form\");\n const resolver = chosenResolver();\n const options = { fetch: ctx.fetch, signal: ctx.signal, resolver: resolver.id, now: () => ctx.now * 1000 };\n if (evidence.method === \"nip05\") {\n // The same checks as a Nostr proof, for the key that signed; then the domain must name that key.\n await nostr.verify({ ...statement, binding: { ...statement.binding, subject: evidence.event.pubkey } }, evidence.event, ctx);\n await assertNip05(subject, evidence.event.pubkey, options);\n return { subject, source: `Nostr signature by the key ${wellKnownUrl(subject, \"nip05\")} names for ${subject}` };\n }\n const found = await assertDomainRecord(subject, evidence.method, recordFor(statement), options);\n return { subject, source: evidence.method === \"dns\"\n ? `DNS TXT record at ${dnsProofName(subject)}, via ${resolverById(found.resolver).name}${found.dnssec ? \", DNSSEC validated\" : \"\"}`\n : `File at ${wellKnownUrl(subject)}` };\n },\n};\n","import { fromBase64Url, toBase64Url, utf8Encode } from '@ghostly/core';\nimport type * as OpenPGP from 'openpgp';\n\n/**\n * OpenPGP identity proofs (RFC 9580, and the RFC 4880 v4 keys people actually have).\n *\n * The person signs a Ghostly statement once — the binding by which their\n * OpenPGP key authorizes a Ghostly proof key (see PROOFS.md); each chat\n * then gets a presentation the app signs itself. They sign with their own tooling —\n * `gpg --clearsign`, `gpg --detach-sign --armor`, a YubiKey or another OpenPGP\n * card behind gpg — and pastes the result with their public key. No secret key\n * ever enters Ghostly: a pasted private key is refused before it is parsed.\n *\n * What is shared with a contact is small and self-contained: one signature\n * packet and a minimal copy of the public key (the primary key, its user IDs\n * with their self-signatures, the subkey that signed). The contact verifies it\n * locally with the statement their own app builds; nothing is fetched.\n *\n * A key's user IDs are claims its holder wrote. Possession of the key proves\n * control of the key, not that a name or an email in a user ID belongs to the\n * person — the UI must say so wherever user IDs are shown.\n *\n * OpenPGP.js is loaded lazily (the \"lightweight\" build: ~233 KB minified,\n * ~64 KB gzipped, plus a ~46 KB curve chunk only for ECDSA keys), so nobody\n * pays for it until an OpenPGP proof is opened.\n */\n\nexport const PGP_SCHEME = 'openpgp/1';\n\n/** Strict size limits. Pasted input is the person's own; shared input comes from a peer. */\nexport const PGP_LIMITS = {\n /** A clearsigned statement or an armored detached signature. RSA-4096 with the statement is ~1.5 KB. */\n pastedSignature: 16 * 1024,\n /** An armored public key as exported, before it is minimized. Flooded keys are refused, not trimmed. */\n pastedKey: 256 * 1024,\n /** One signature packet, binary. RSA-4096 with the usual subpackets is ~600 bytes. */\n sharedSignature: 1536,\n /** The minimal key, binary. Both fit, base64url, in the contract's 16 KiB of evidence JSON. */\n sharedKey: 9 * 1024,\n /** User IDs kept in the minimal key and shown. */\n userIds: 16,\n /** Bytes per user ID. */\n userIdLength: 256,\n /** A keyserver response. */\n keyserverResponse: 256 * 1024,\n} as const;\n\n/** Clock difference tolerated between the signer's machine and the verifier. */\nexport const PGP_CLOCK_SKEW = 300;\n\nexport interface PgpEvidence {\n scheme: typeof PGP_SCHEME;\n /** base64url of the minimal transferable public key (binary packets). */\n key: string;\n /** base64url of exactly one signature packet over the statement. */\n signature: string;\n}\n\nexport interface PgpKeyInfo {\n /** Primary key fingerprint, uppercase hex (40 characters for v4, 64 for v6). */\n fingerprint: string;\n /** The key that signs: the primary key or a signing subkey. */\n signingFingerprint: string;\n version: 4 | 6;\n /** \"Ed25519\", \"RSA 3072\", \"ECDSA P-256\", … for the signing key. */\n algorithm: string;\n createdAt: number;\n /** Seconds, or null when the key does not expire. */\n expiresAt: number | null;\n /** Self-certified, unrevoked user IDs: claims by the key holder, not verified facts. */\n userIds: string[];\n}\n\nexport interface PgpVerified extends PgpKeyInfo {\n /** Signature creation time, seconds. */\n signedAt: number;\n}\n\nexport interface PgpVerifyOptions {\n /** Verification time, seconds. The key and its signing subkey must be valid now. */\n now?: number;\n /** Refuse signatures made before this time (seconds), e.g. the start of the binding's validity. */\n signedAfter?: number;\n /** The primary key fingerprint the statement names: any other key is refused, even with a valid signature. */\n expectedFingerprint?: string;\n}\n\ntype Pgp = typeof OpenPGP;\nlet loading: Promise<Pgp> | undefined;\n/** The only place OpenPGP.js is imported: a separate chunk, fetched on first use. */\nexport function loadOpenPgp(): Promise<Pgp> {\n loading ??= import('openpgp/lightweight').then(m => m as unknown as Pgp).catch(e => { loading = undefined; throw e; });\n return loading;\n}\n\nfunction config(pgp: Pgp): OpenPGP.PartialConfig {\n const { hash, publicKey, curve } = pgp.enums;\n return {\n minRSABits: 2048,\n maxUserIDLength: PGP_LIMITS.userIdLength,\n enableParsingV5Entities: false,\n ignoreUnsupportedPackets: true,\n ignoreMalformedPackets: false,\n // Self-signatures from older keys may still use SHA-1; the proof signature may not (below).\n rejectHashAlgorithms: new Set([hash.md5, hash.ripemd]),\n rejectMessageHashAlgorithms: new Set([hash.md5, hash.ripemd, hash.sha1]),\n rejectPublicKeyAlgorithms: new Set([publicKey.elgamal, publicKey.dsa]),\n rejectCurves: new Set([curve.secp256k1]),\n };\n}\n\n/** The allowlist, checked on the primary key and on the key that signs. */\nfunction allowedAlgorithm(packet: OpenPGP.PublicKeyPacket | OpenPGP.SecretKeyPacket | OpenPGP.PublicSubkeyPacket | OpenPGP.SecretSubkeyPacket, signing: boolean): string {\n const { algorithm, bits, curve } = packet.getAlgorithmInfo();\n const names: Record<string, string> = { nistP256: 'P-256', nistP384: 'P-384', nistP521: 'P-521',\n brainpoolP256r1: 'brainpoolP256r1', brainpoolP384r1: 'brainpoolP384r1', brainpoolP512r1: 'brainpoolP512r1' };\n if ((algorithm === 'rsaEncryptSign' || (algorithm === 'rsaSign' && signing)) && bits && bits >= 2048) return `RSA ${bits}`;\n if (algorithm === 'ed25519' || (algorithm === 'eddsaLegacy' && curve === 'ed25519Legacy')) return 'Ed25519';\n if (algorithm === 'ed448') return 'Ed448';\n if (algorithm === 'ecdsa' && curve && names[curve]) return `ECDSA ${names[curve]}`;\n const shown = algorithm.startsWith('rsa') ? `RSA ${bits ?? '?'}` : curve ? `${algorithm} ${curve}` : algorithm;\n throw new PgpProofError(`This key uses ${shown}, which Ghostly does not accept. Use Ed25519, ECDSA (P-256, P-384, P-521, brainpool) or RSA with at least 2048 bits.`);\n}\nconst ALLOWED_HASHES = new Set([8, 9, 10, 11, 12, 14]); // SHA-256, -384, -512, -224, SHA3-256, SHA3-512\n\nexport class PgpProofError extends Error { override name = 'PgpProofError'; }\n\n/** Armored blocks in pasted text; more BEGIN lines than any real paste has are refused before the regex scans. */\nfunction armorBlocks(text: string): RegExpExecArray[] {\n if ((text.match(/-----BEGIN PGP /g) ?? []).length > 4) throw new PgpProofError('Paste only what gpg printed: one signature and your public key.');\n return [...text.matchAll(ARMOR)];\n}\n// A clearsigned message has no END line of its own: it ends with its signature block.\nconst ARMOR = /-----BEGIN PGP SIGNED MESSAGE-----[\\s\\S]*?-----END PGP SIGNATURE-----|-----BEGIN PGP ([A-Z ,/0-9]+)-----[\\s\\S]*?-----END PGP \\1-----/g;\n/** The single armored block in pasted text (terminal output may surround it). */\nfunction armoredBlock(text: string, what: 'key' | 'signature'): { type: string; block: string } {\n if (/PRIVATE KEY BLOCK/.test(text))\n throw new PgpProofError('This is a PRIVATE key. Never paste it anywhere: Ghostly only needs your public key (gpg --armor --export).');\n const blocks = armorBlocks(text);\n if (blocks.length === 0) throw new PgpProofError(what === 'key' ? 'Paste an armored public key: it starts with -----BEGIN PGP PUBLIC KEY BLOCK-----.' : 'Paste the whole signed text: it starts with -----BEGIN PGP SIGNED MESSAGE----- (or -----BEGIN PGP SIGNATURE----- for a detached signature).');\n if (blocks.length > 1) throw new PgpProofError(`Paste only one ${what === 'key' ? 'public key' : 'signature'}.`);\n return { type: blocks[0][1] ?? 'SIGNED MESSAGE', block: blocks[0][0] };\n}\n\nfunction secondsOf(date: Date | number | null | typeof Infinity): number | null {\n if (date === null || date === Infinity) return null;\n return Math.floor((date instanceof Date ? date.getTime() : date) / 1000);\n}\nconst hexOf = (bytes: Uint8Array) => Array.from(bytes, b => b.toString(16).padStart(2, '0')).join('').toUpperCase();\nconst nowSeconds = () => Math.floor(Date.now() / 1000);\n/** Clearsigned text as it is hashed: trailing whitespace dropped, no final newline. */\nexport function canonicalStatement(statement: string): string {\n return statement.replace(/\\r\\n?/g, '\\n').split('\\n').map(l => l.replace(/[ \\t]+$/, '')).join('\\n').replace(/\\n+$/, '');\n}\n\n/**\n * One paste holding what gpg printed: the signed statement (clearsigned, or a detached signature) and,\n * optionally, the public key. Anything around the blocks (prompts, blank lines) is ignored.\n */\nexport function splitPgpPaste(pasted: string): { signature: string; publicKey?: string } {\n if (typeof pasted !== 'string' || pasted.length > PGP_LIMITS.pastedKey + PGP_LIMITS.pastedSignature) throw new PgpProofError('This is too large. Paste only what gpg printed.');\n if (/PRIVATE KEY BLOCK/.test(pasted)) throw new PgpProofError('This is a PRIVATE key. Never paste it anywhere: Ghostly only needs your public key (gpg --armor --export).');\n const blocks = armorBlocks(pasted).map(m => ({ type: m[1] ?? 'SIGNED MESSAGE', block: m[0] }));\n const keys = blocks.filter(b => b.type === 'PUBLIC KEY BLOCK'), others = blocks.filter(b => b.type !== 'PUBLIC KEY BLOCK');\n if (keys.length > 1) throw new PgpProofError('Paste only one public key.');\n if (others.length === 0) throw new PgpProofError('Paste the signed statement too: it starts with -----BEGIN PGP SIGNED MESSAGE-----.');\n if (others.length > 1) throw new PgpProofError('Paste only one signature.');\n return { signature: others[0].block, publicKey: keys[0]?.block };\n}\n\n/** Refuses a statement that `gpg --clearsign` would alter (dash-escaping, trailing spaces) or that is not plain text. */\nexport function assertSignableStatement(statement: string): void {\n if (!statement || statement.length > 4096 || statement !== canonicalStatement(statement) || /^-/m.test(statement) || /[^\\x20-\\x7e\\n]/.test(statement))\n throw new PgpProofError('Statement is not signable as plain text');\n}\n\n/** Why a key or a subkey is not usable now, in words the person can act on. */\nasync function explainInvalidKey(key: OpenPGP.PublicKey, keyId: OpenPGP.KeyID | undefined, at: Date, cfg: OpenPGP.PartialConfig, when: 'now' | 'signing'): Promise<never> {\n const past = when === 'signing' ? ' when it signed' : '';\n if (await key.isRevoked(undefined, undefined, at, cfg as OpenPGP.Config).catch(() => false))\n throw new PgpProofError(`This key has been revoked${past}. A revoked key proves nothing.`);\n try { await key.verifyPrimaryKey(at, undefined, cfg as OpenPGP.Config); }\n catch (e) {\n const expiry = await key.getExpirationTime(undefined, cfg as OpenPGP.Config).catch(() => null);\n if (expiry instanceof Date && expiry <= at) throw new PgpProofError(`This key expired on ${expiry.toISOString().slice(0, 10)}${past}. Extend its expiry (gpg --quick-set-expire) and export it again.`);\n throw new PgpProofError(`This key is not valid${past}: ${message(e)}`);\n }\n const sub = keyId ? key.getSubkeys(keyId)[0] : undefined;\n if (sub) {\n if (sub.revocationSignatures.length && await sub.verify(at, cfg as OpenPGP.Config).then(() => false, e => /revoked/i.test(message(e))))\n throw new PgpProofError(`The subkey that signed (${sub.getFingerprint().toUpperCase()}) has been revoked${past}.`);\n const expiry = await sub.getExpirationTime(at, cfg as OpenPGP.Config).catch(() => null);\n if (expiry instanceof Date && expiry <= at) throw new PgpProofError(`The subkey that signed expired on ${expiry.toISOString().slice(0, 10)}${past}.`);\n throw new PgpProofError(`The subkey that signed is not a valid signing subkey${past}.`);\n }\n throw new PgpProofError(`The key that signed is not a valid signing key${past}.`);\n}\nconst message = (e: unknown) => e instanceof Error ? e.message : String(e);\n\n/** Parses exactly one public key; refuses private keys, v3/v5 keys and key rings. */\nasync function readOneKey(pgp: Pgp, input: { armored: string } | { binary: Uint8Array }): Promise<OpenPGP.PublicKey> {\n const cfg = config(pgp);\n let keys: OpenPGP.Key[];\n try {\n keys = 'armored' in input ? await pgp.readKeys({ armoredKeys: input.armored, config: cfg }) : await pgp.readKeys({ binaryKeys: input.binary, config: cfg });\n } catch (e) { throw new PgpProofError(`Could not read this public key: ${message(e)}`); }\n if (keys.length !== 1) throw new PgpProofError('Paste one public key, not a key ring.');\n const [key] = keys;\n if (key.isPrivate()) throw new PgpProofError('This is a PRIVATE key. Ghostly only needs your public key (gpg --armor --export).');\n const version = key.keyPacket.version;\n if (version !== 4 && version !== 6) throw new PgpProofError(`OpenPGP v${version} keys are not accepted; use a v4 (RFC 4880) or v6 (RFC 9580) key.`);\n return key as OpenPGP.PublicKey;\n}\n\n/** Valid, unrevoked, self-certified user IDs at `at`. */\nasync function validUserIds(key: OpenPGP.PublicKey, at: Date, cfg: OpenPGP.PartialConfig): Promise<OpenPGP.User[]> {\n const valid: OpenPGP.User[] = [];\n for (const user of key.users) {\n if (!user.userID || valid.length >= PGP_LIMITS.userIds) continue;\n if (await user.verify(at, cfg as OpenPGP.Config).then(() => true, () => false)) valid.push(user);\n }\n return valid;\n}\n\n/**\n * The transferable key a contact receives: primary key and its direct/revocation\n * signatures, valid user IDs with self-certifications only (third-party\n * certifications and photos dropped), and the one subkey that signed.\n */\nasync function minimalKey(pgp: Pgp, key: OpenPGP.PublicKey, signer: OpenPGP.PublicKey | OpenPGP.Subkey, at: Date, cfg: OpenPGP.PartialConfig): Promise<Uint8Array> {\n const packets = new pgp.PacketList<OpenPGP.AnyPacket>();\n packets.push(key.keyPacket, ...key.revocationSignatures);\n // Direct-key self-signatures carry v6 key properties; OpenPGP.js keeps them here.\n const direct = (key as unknown as { directSignatures?: OpenPGP.SignaturePacket[] }).directSignatures ?? [];\n packets.push(...direct);\n for (const user of await validUserIds(key, at, cfg)) packets.push(user.userID!, ...user.selfCertifications, ...user.revocationSignatures);\n if (signer !== key) {\n const sub = signer as OpenPGP.Subkey;\n packets.push(sub.keyPacket, ...sub.bindingSignatures, ...sub.revocationSignatures);\n }\n const bytes = new pgp.PublicKey(packets).write();\n if (bytes.length > PGP_LIMITS.sharedKey) throw new PgpProofError('This key is too large to share, even without third-party certifications. Remove old user IDs or use a smaller key.');\n return bytes;\n}\n\nasync function keyInfo(key: OpenPGP.PublicKey, signer: OpenPGP.PublicKey | OpenPGP.Subkey, algorithm: string, at: Date, cfg: OpenPGP.PartialConfig): Promise<PgpKeyInfo> {\n const expiry = await key.getExpirationTime(undefined, cfg as OpenPGP.Config).catch(() => null);\n const subExpiry = signer !== key ? await (signer as OpenPGP.Subkey).getExpirationTime(at, cfg as OpenPGP.Config).catch(() => null) : null;\n const expires = [secondsOf(expiry), secondsOf(subExpiry)].filter((x): x is number => x !== null);\n return {\n fingerprint: key.getFingerprint().toUpperCase(),\n signingFingerprint: signer.getFingerprint().toUpperCase(),\n version: key.keyPacket.version as 4 | 6,\n algorithm,\n createdAt: secondsOf(key.getCreationTime())!,\n expiresAt: expires.length ? Math.min(...expires) : null,\n userIds: (await validUserIds(key, at, cfg)).map(u => u.userID!.userID),\n };\n}\n\n/** What a pasted public key is, before anything is signed: fingerprint and user IDs to show. */\nexport async function readPgpPublicKey(armored: string, options: { now?: number } = {}): Promise<PgpKeyInfo> {\n if (typeof armored !== 'string' || armored.length > PGP_LIMITS.pastedKey) throw new PgpProofError('This public key is too large. Export it with --export-options export-minimal.');\n const { type, block } = armoredBlock(armored, 'key');\n if (type !== 'PUBLIC KEY BLOCK') throw new PgpProofError('Paste a public key block (gpg --armor --export).');\n const pgp = await loadOpenPgp();\n const cfg = config(pgp);\n const key = await readOneKey(pgp, { armored: block });\n const at = new Date((options.now ?? nowSeconds()) * 1000);\n allowedAlgorithm(key.keyPacket, false);\n let signer: OpenPGP.PublicKey | OpenPGP.Subkey;\n try { signer = await key.getSigningKey(undefined, at, undefined, cfg as OpenPGP.Config) as OpenPGP.PublicKey | OpenPGP.Subkey; }\n catch { return explainInvalidKey(key, undefined, at, cfg, 'now'); }\n return keyInfo(key, signer, allowedAlgorithm(signer.keyPacket, true), at, cfg);\n}\n\nasync function readSignaturePacket(pgp: Pgp, input: { armored: string } | { binary: Uint8Array }): Promise<OpenPGP.SignaturePacket> {\n let sig: OpenPGP.Signature;\n try {\n sig = 'armored' in input ? await pgp.readSignature({ armoredSignature: input.armored, config: config(pgp) }) : await pgp.readSignature({ binarySignature: input.binary, config: config(pgp) });\n } catch (e) { throw new PgpProofError(`Could not read this signature: ${message(e)}`); }\n const packets = sig.packets.filterByTag(pgp.enums.packet.signature) as unknown as OpenPGP.SignaturePacket[];\n if (sig.packets.length !== 1 || packets.length !== 1) throw new PgpProofError('Sign with one key only: this signature has several signers.');\n return packets[0];\n}\n\n/**\n * The core check, shared by the person (on paste) and the contact (on receipt):\n * one signature over the statement, by a key or signing subkey of `key` that is\n * valid both when it signed and now, with allowed algorithms and hash.\n */\nasync function verifyPacket(pgp: Pgp, key: OpenPGP.PublicKey, sig: OpenPGP.SignaturePacket, statement: string, options: PgpVerifyOptions): Promise<{ info: PgpVerified; signer: OpenPGP.PublicKey | OpenPGP.Subkey }> {\n const cfg = config(pgp);\n const now = options.now ?? nowSeconds();\n const at = new Date(now * 1000);\n const { signature } = pgp.enums;\n if (sig.signatureType !== signature.text && sig.signatureType !== signature.binary) throw new PgpProofError('This is not a signature over a text or a file.');\n if (sig.hashAlgorithm === null || !ALLOWED_HASHES.has(sig.hashAlgorithm)) throw new PgpProofError('This signature uses a weak hash (SHA-1 or older). Sign again with gpg\\'s default (SHA-256 or stronger): --digest-algo SHA256.');\n if (!sig.created) throw new PgpProofError('This signature has no creation time.');\n const signedAt = secondsOf(sig.created)!;\n if (signedAt > now + PGP_CLOCK_SKEW) throw new PgpProofError('This signature is dated in the future. Check the clock of the machine that signed.');\n if (options.expectedFingerprint !== undefined && key.getFingerprint().toUpperCase() !== options.expectedFingerprint.toUpperCase())\n throw new PgpProofError('This key is not the one the statement names.');\n const issuer = key.getKeys(sig.issuerKeyID)[0];\n if (!issuer) throw new PgpProofError(`This signature was made by another key (${sig.issuerKeyID.toHex().toUpperCase()}), not the public key given.`);\n if (sig.issuerFingerprint && hexOf(sig.issuerFingerprint) !== issuer.getFingerprint().toUpperCase())\n throw new PgpProofError('This signature was made by another key, not the public key given.');\n allowedAlgorithm(key.keyPacket, false);\n // Valid now (not expired, not revoked, signing-capable with its back-signature)…\n let signer: OpenPGP.PublicKey | OpenPGP.Subkey;\n try { signer = await key.getSigningKey(sig.issuerKeyID, at, undefined, cfg as OpenPGP.Config) as OpenPGP.PublicKey | OpenPGP.Subkey; }\n catch { return explainInvalidKey(key, sig.issuerKeyID, at, cfg, 'now'); }\n const algorithm = allowedAlgorithm(signer.keyPacket, true);\n // …and when it signed.\n const created = new Date(Math.min(signedAt, now) * 1000);\n try { await key.getSigningKey(sig.issuerKeyID, created, undefined, cfg as OpenPGP.Config); }\n catch { return explainInvalidKey(key, sig.issuerKeyID, created, cfg, 'signing'); }\n // After the key checks, so an expired or revoked key is reported as such.\n if (options.signedAfter !== undefined && signedAt < options.signedAfter - PGP_CLOCK_SKEW) throw new PgpProofError('This signature is older than the statement it should sign.');\n // A detached signature over a file holds its final newline; clearsigned text does not.\n const text = canonicalStatement(statement);\n const packets = new pgp.PacketList<OpenPGP.AnyPacket>(); packets.push(sig);\n const detached = new pgp.Signature(packets as OpenPGP.PacketList<OpenPGP.SignaturePacket>);\n let verified = false;\n for (const candidate of [text, `${text}\\n`, `${text}\\r\\n`]) {\n const message = await pgp.createMessage({ binary: utf8Encode(candidate) });\n const result = await pgp.verify({ message, signature: detached, verificationKeys: key, date: new Date(Math.max(now, signedAt) * 1000), config: cfg });\n if (result.signatures.length === 1 && await result.signatures[0].verified.then(() => true, () => false)) { verified = true; break; }\n }\n if (!verified) throw new PgpProofError('This signature is not over the Ghostly statement (it signs other text, or it was changed).');\n return { info: { ...await keyInfo(key, signer, algorithm, at, cfg), signedAt }, signer };\n}\n\n/**\n * The person's side: checks what they pasted against the statement and\n * produces the evidence to share. Either a clearsigned statement or an\n * armored detached signature is accepted.\n */\nexport async function preparePgpEvidence(input: { statement: string; signature: string; publicKey: string }, options: PgpVerifyOptions = {}): Promise<{ evidence: PgpEvidence; verified: PgpVerified }> {\n if (typeof input.signature !== 'string' || input.signature.length > PGP_LIMITS.pastedSignature) throw new PgpProofError('This signature is too large. Paste only the output of gpg --clearsign.');\n if (typeof input.publicKey !== 'string' || input.publicKey.length > PGP_LIMITS.pastedKey) throw new PgpProofError('This public key is too large. Export it with --export-options export-minimal.');\n const { type, block } = armoredBlock(input.signature, 'signature');\n const keyBlock = armoredBlock(input.publicKey, 'key');\n if (keyBlock.type !== 'PUBLIC KEY BLOCK') throw new PgpProofError('Paste a public key block (gpg --armor --export).');\n if (type === 'MESSAGE') throw new PgpProofError('This is a signed message (gpg --sign). Use gpg --clearsign, or --detach-sign --armor.');\n if (type === 'PUBLIC KEY BLOCK') throw new PgpProofError('This is a public key; paste it in the public key field and the signature here.');\n const pgp = await loadOpenPgp();\n const key = await readOneKey(pgp, { armored: keyBlock.block });\n let sig: OpenPGP.SignaturePacket;\n if (type === 'SIGNED MESSAGE') {\n let cleartext: OpenPGP.CleartextMessage;\n try { cleartext = await pgp.readCleartextMessage({ cleartextMessage: block, config: config(pgp) }); }\n catch (e) { throw new PgpProofError(`Could not read this signed text: ${message(e)}`); }\n if (canonicalStatement(cleartext.getText()) !== canonicalStatement(input.statement))\n throw new PgpProofError('This signs different text. Sign exactly the statement shown, without editing it.');\n // Typed as private in OpenPGP.js's declarations, public at runtime.\n const packets = (cleartext as unknown as { signature: OpenPGP.Signature }).signature.packets;\n if (packets.length !== 1 || !(packets[0] instanceof pgp.SignaturePacket)) throw new PgpProofError('Sign with one key only: this signature has several signers.');\n sig = packets[0];\n } else if (type === 'SIGNATURE') sig = await readSignaturePacket(pgp, { armored: block });\n else throw new PgpProofError('Paste the output of gpg --clearsign (or gpg --detach-sign --armor).');\n const { info, signer } = await verifyPacket(pgp, key, sig, input.statement, options);\n const single = new pgp.PacketList<OpenPGP.AnyPacket>(); single.push(sig);\n const signature = new pgp.Signature(single as OpenPGP.PacketList<OpenPGP.SignaturePacket>).write();\n if (signature.length > PGP_LIMITS.sharedSignature) throw new PgpProofError('This signature is too large to share.');\n const at = new Date((options.now ?? nowSeconds()) * 1000);\n const evidence: PgpEvidence = { scheme: PGP_SCHEME, key: toBase64Url(await minimalKey(pgp, key, signer, at, config(pgp))), signature: toBase64Url(signature) };\n return { evidence, verified: info };\n}\n\nconst b64 = /^[A-Za-z0-9_-]+$/;\n/** Untrusted evidence off the wire: exact shape, bounded, canonical base64url. Checked before any OpenPGP parsing. */\nexport function parsePgpEvidence(raw: unknown): PgpEvidence {\n const e = raw as PgpEvidence;\n if (!e || typeof e !== 'object' || Array.isArray(e) || e.scheme !== PGP_SCHEME || Object.keys(e).sort().join(',') !== 'key,scheme,signature' ||\n typeof e.key !== 'string' || typeof e.signature !== 'string' || !b64.test(e.key) || !b64.test(e.signature) ||\n e.key.length > Math.ceil(PGP_LIMITS.sharedKey * 4 / 3) || e.signature.length > Math.ceil(PGP_LIMITS.sharedSignature * 4 / 3))\n throw new PgpProofError('Invalid OpenPGP proof');\n let canonical: boolean;\n try { canonical = toBase64Url(fromBase64Url(e.key)) === e.key && toBase64Url(fromBase64Url(e.signature)) === e.signature; } catch { canonical = false; }\n if (!canonical) throw new PgpProofError('Invalid OpenPGP proof encoding');\n return { scheme: PGP_SCHEME, key: e.key, signature: e.signature };\n}\n\n/** The contact's side: verifies shared evidence against the statement their own app built. */\nexport async function verifyPgpEvidence(raw: PgpEvidence, statement: string, options: PgpVerifyOptions = {}): Promise<PgpVerified> {\n const evidence = parsePgpEvidence(raw);\n const keyBytes = fromBase64Url(evidence.key), sigBytes = fromBase64Url(evidence.signature);\n const pgp = await loadOpenPgp();\n const key = await readOneKey(pgp, { binary: keyBytes });\n const sig = await readSignaturePacket(pgp, { binary: sigBytes });\n return (await verifyPacket(pgp, key, sig, statement, options)).info;\n}\n\n/** Groups of four, as gpg prints fingerprints. */\nexport function formatFingerprint(fingerprint: string): string {\n return fingerprint.replace(/(.{4})/g, '$1 ').trim();\n}\n\n/** keys.openpgp.org publishes a user ID only after its owner confirms the email. */\nexport const KEYSERVER = 'https://keys.openpgp.org';\nexport interface KeyserverKey { armored: string; source: typeof KEYSERVER; fetchedAt: number }\n/**\n * Fetches a public key from keys.openpgp.org. Only ever called when the person\n * asks for it, after the UI said this contacts that server.\n */\nexport async function fetchKeyFromKeyserver(query: string, options: { signal?: AbortSignal; fetch?: typeof fetch; now?: number } = {}): Promise<KeyserverKey> {\n const q = query.trim().replace(/\\s+/g, '').replace(/^0x/i, '');\n let path: string;\n if (/^[A-Fa-f0-9]{40}$/.test(q) || /^[A-Fa-f0-9]{64}$/.test(q)) path = `/vks/v1/by-fingerprint/${q.toUpperCase()}`;\n else if (/^[^\\s@<>]{1,64}@[^\\s@<>]{1,190}$/.test(query.trim())) path = `/vks/v1/by-email/${encodeURIComponent(query.trim())}`;\n else throw new PgpProofError('Enter a full fingerprint (40 hex characters) or an email address.');\n const response = await (options.fetch ?? fetch)(`${KEYSERVER}${path}`, { signal: options.signal, credentials: 'omit', referrerPolicy: 'no-referrer', redirect: 'error' });\n if (response.status === 404) throw new PgpProofError('keys.openpgp.org has no key for this. Paste your public key instead.');\n if (!response.ok) throw new PgpProofError(`keys.openpgp.org answered ${response.status}. Paste your public key instead.`);\n const length = Number(response.headers.get('content-length') ?? 0);\n if (length > PGP_LIMITS.keyserverResponse) throw new PgpProofError('The key from keys.openpgp.org is too large.');\n const armored = await response.text();\n if (armored.length > PGP_LIMITS.keyserverResponse) throw new PgpProofError('The key from keys.openpgp.org is too large.');\n return { armored, source: KEYSERVER, fetchedAt: options.now ?? nowSeconds() };\n}\n\n/**\n * Emails keys.openpgp.org vouches for on this key: it publishes a user ID only\n * once the email's owner confirmed it, so every user ID in its copy is one the\n * keyserver checked. That is the keyserver's check, and says nothing about a name.\n */\nexport async function keyserverVerifiedEmails(keyserverArmored: string, fingerprint: string, options: { now?: number } = {}): Promise<string[]> {\n const info = await readPgpPublicKey(keyserverArmored, options);\n if (info.fingerprint !== fingerprint.toUpperCase()) throw new PgpProofError('keys.openpgp.org returned a different key.');\n const emails = info.userIds.map(u => /<([^<>\\s]+@[^<>\\s]+)>\\s*$/.exec(u)?.[1] ?? (/^[^<>\\s]+@[^<>\\s]+$/.test(u) ? u : undefined));\n return [...new Set(emails.filter((e): e is string => !!e).map(e => e.toLowerCase()))];\n}\n","import type { IdentityStatement } from \"@ghostly/core\";\nimport type { ExternalToolSigner, IdentityProofProvider, SignerInstructions } from \"../contract\";\nimport {\n KEYSERVER, PgpProofError, assertSignableStatement, fetchKeyFromKeyserver, formatFingerprint, keyserverVerifiedEmails, parsePgpEvidence,\n preparePgpEvidence, splitPgpPaste, verifyPgpEvidence, type PgpEvidence,\n} from \"../openpgp\";\n\n/**\n * OpenPGP keys, signed with the person's own gpg — a YubiKey or another OpenPGP card works unchanged,\n * since gpg drives it. The subject is the primary key's fingerprint. See docs/wisps/305-openpgp.md.\n */\n\n/** A v4 (40 hex) or v6 (64 hex) fingerprint, as gpg prints it or not: spaces and 0x are dropped. */\nexport function normalizeFingerprint(input: string): string {\n const value = input.trim().replace(/^0x/i, \"\").replace(/\\s+/g, \"\").toUpperCase();\n if (/^[A-F0-9]{40}$/.test(value) || /^[A-F0-9]{64}$/.test(value)) return value;\n if (/^[A-F0-9]{8}$|^[A-F0-9]{16}$/.test(value)) throw new PgpProofError(\"Use the full fingerprint, not a key ID: gpg --fingerprint shows it.\");\n throw new PgpProofError(\"Enter your key's fingerprint: 40 hex characters (gpg --fingerprint).\");\n}\n\n/** The user ID is its holder's own words: shown with that caveat, never as a checked name. */\nconst USER_ID_SOURCE = \"User ID on the key, written by its holder: not proof that the name or email is theirs\";\nconst KEYSERVER_SOURCE = \"Email confirmed to keys.openpgp.org by its owner: the keyserver's check, not Ghostly's\";\n\nfunction steps(statement: IdentityStatement, exportKey: boolean): SignerInstructions {\n assertSignableStatement(statement.text);\n // The statement is one line of space-separated fields with no quote in it, so it is safe in single quotes.\n if (statement.text.includes(\"'\")) throw new PgpProofError(\"Statement is not signable as plain text\");\n const fpr = normalizeFingerprint(statement.binding.subject);\n const sign = `gpg --local-user ${fpr} --clearsign --output - ghostly-identity.txt`;\n return {\n steps: [\n { text: \"Save the statement to a file (it is one line, with no newline at the end):\", copy: `printf '%s' '${statement.text}' > ghostly-identity.txt` },\n exportKey\n ? { text: \"Sign it and export your public key, then copy everything this prints:\", copy: `${sign} && gpg --armor --export --export-options export-minimal ${fpr}` }\n : { text: \"Sign it, then copy everything this prints:\", copy: sign },\n { text: \"On a YubiKey or another OpenPGP card, gpg asks for the card's PIN, and for a touch if the card requires one. Without a shell, save the statement below in a text file and sign that file.\", copy: statement.text },\n ],\n paste: { label: exportKey ? \"Signed statement and public key\" : \"Signed statement\", placeholder: \"-----BEGIN PGP SIGNED MESSAGE-----\", multiline: true },\n };\n}\n\nasync function evidenceFrom(statement: IdentityStatement, signature: string, publicKey: string): Promise<PgpEvidence> {\n const { evidence } = await preparePgpEvidence({ statement: statement.text, signature, publicKey },\n { expectedFingerprint: normalizeFingerprint(statement.binding.subject), signedAfter: statement.binding.issuedAt });\n return evidence;\n}\n\nconst gpg: ExternalToolSigner<PgpEvidence> = {\n id: \"gpg\", kind: \"external-tool\", label: \"gpg\",\n description: \"Sign with gpg and paste what it prints, public key included. Nothing is looked up.\",\n instructions: statement => steps(statement, true),\n async parse(pasted, statement) {\n const { signature, publicKey } = splitPgpPaste(pasted);\n if (!publicKey) throw new PgpProofError(\"Paste your public key too: the second command prints it (-----BEGIN PGP PUBLIC KEY BLOCK-----).\");\n return evidenceFrom(statement, signature, publicKey);\n },\n};\n\nconst gpgKeyserver: ExternalToolSigner<PgpEvidence> = {\n id: \"gpg-keys-openpgp-org\", kind: \"external-tool\", label: \"gpg, key from keys.openpgp.org\",\n description: \"Sign with gpg and paste the signature; Ghostly then fetches your public key from keys.openpgp.org, which learns which key was looked up.\",\n instructions: statement => steps(statement, false),\n async parse(pasted, statement) {\n const { signature, publicKey } = splitPgpPaste(pasted);\n // Only here, on the person's choice of this signer, is the keyserver contacted.\n const key = publicKey ?? (await fetchKeyFromKeyserver(statement.binding.subject)).armored;\n return evidenceFrom(statement, signature, key);\n },\n};\n\nexport const openpgp: IdentityProofProvider<PgpEvidence> = {\n id: \"openpgp\",\n label: \"OpenPGP key\",\n category: \"self-custodied\",\n summary: \"Sign with gpg and paste the result\",\n description: \"Proves you hold an OpenPGP key: sign the statement with gpg (a YubiKey works too) and paste the result.\",\n limits: \"Holding a key does not prove the names or emails in its user IDs.\",\n platforms: [\"web\", \"extension\", \"desktop\"],\n subject: {\n label: \"Key fingerprint\", placeholder: \"40 hex characters, from gpg --fingerprint\",\n help: \"The primary key's full fingerprint. Holding a key does not prove the names or emails in its user IDs.\",\n normalize: normalizeFingerprint,\n short: fpr => `…${formatFingerprint(fpr.slice(-16))}`,\n },\n // The fingerprint URI scheme OpenPGP apps share keys with.\n publicUri: fpr => `openpgp4fpr:${fpr}`,\n validity: { defaultDays: 90, maxDays: 365 },\n privacy: `Nothing: the signature is checked on this device. ${KEYSERVER.replace(\"https://\", \"\")} is contacted only if you choose it for your key, or ask it to confirm emails.`,\n signers: [gpg, gpgKeyserver],\n lookupLabel: \"Check emails with keys.openpgp.org\",\n parseEvidence: raw => parsePgpEvidence(raw),\n async verify(statement, evidence, ctx) {\n ctx.signal.throwIfAborted();\n const { binding } = statement;\n const verified = await verifyPgpEvidence(evidence, statement.text, { now: ctx.now, signedAfter: binding.issuedAt, expectedFingerprint: normalizeFingerprint(binding.subject) });\n const subkey = verified.signingFingerprint !== verified.fingerprint;\n const [userId] = verified.userIds;\n return {\n subject: verified.fingerprint,\n source: `OpenPGP signature (${verified.algorithm}${subkey ? \" signing subkey\" : \"\"}, v${verified.version} key)`,\n ...(verified.expiresAt !== null && verified.expiresAt < binding.expiresAt ? { expiresAt: verified.expiresAt } : {}),\n ...(userId ? { display: { name: userId, source: USER_ID_SOURCE, fetchedAt: ctx.now } } : {}),\n };\n },\n /** On request only: which emails on this key keys.openpgp.org confirmed, and whether it holds a revocation. */\n async lookupDisplay(subject, { signal }) {\n const fpr = normalizeFingerprint(subject);\n const found = await fetchKeyFromKeyserver(fpr, { signal });\n const emails = await keyserverVerifiedEmails(found.armored, fpr);\n if (!emails.length) return undefined;\n return { name: emails[0], url: `${KEYSERVER}/search?q=${fpr}`, source: KEYSERVER_SOURCE, fetchedAt: found.fetchedAt };\n },\n};\n","import { parseSshPublicKey, sameSshKey, type SshPublicKey } from '@ghostly/core';\nimport type { IdentityFetch } from './contract';\n\n/** \"This account publishes that key\", without OAuth: GitHub and GitLab publish every account's SSH\n * public keys, and only the account holder can add one. The verifier's own lookup, through the\n * engine's bounded `ctx.fetch`; it tells the forge (and the network) which account was checked. */\n\nexport type SshForge = 'github' | 'gitlab';\nexport const SSH_FORGE_LABEL: Record<SshForge, string> = { github: 'GitHub', gitlab: 'GitLab' };\n/** The only host each forge lookup contacts, for the privacy line. */\nexport const SSH_FORGE_HOST: Record<SshForge, string> = { github: 'api.github.com', gitlab: 'gitlab.com' };\n\nexport type SshForgeStatus = 'linked' | 'not-listed' | 'no-account';\n\nconst MAX_BODY = 256 * 1024;\nconst MAX_KEYS = 200;\n\nexport function validForgeLogin(forge: SshForge, login: string): boolean {\n if (typeof login !== 'string') return false;\n // GitHub: 1-39 alphanumerics or single inner hyphens. GitLab: letters, digits, _ . - ;\n // not starting with - and not ending in .git/.atom. Capped at 64 so it fits every label.\n return forge === 'github'\n ? /^[A-Za-z0-9](?:[A-Za-z0-9]|-(?=[A-Za-z0-9])){0,38}$/.test(login)\n : /^[A-Za-z0-9_.][A-Za-z0-9_.-]{0,63}$/.test(login) && !/\\.(?:git|atom)$/i.test(login);\n}\n\nasync function get(fetcher: IdentityFetch, url: string, signal?: AbortSignal): Promise<{ status: 404 } | { status: 200; body: unknown }> {\n const response = await fetcher(url, { maxBytes: MAX_BODY, signal });\n if (response.status === 404) return { status: 404 };\n if (response.status === 403 || response.status === 429) throw new Error(`${new URL(url).host} is limiting lookups; check again later`);\n if (response.status !== 200) throw new Error(`${new URL(url).host} answered HTTP ${response.status}`);\n try { return { status: 200, body: JSON.parse(response.text) }; } catch { throw new Error(`Unexpected answer from ${new URL(url).host}`); }\n}\n\nfunction keysFrom(body: unknown): SshPublicKey[] {\n if (!Array.isArray(body)) throw new Error('Unexpected key list');\n const keys: SshPublicKey[] = [];\n for (const entry of body.slice(0, MAX_KEYS)) {\n const line = (entry as { key?: unknown } | null)?.key;\n if (typeof line !== 'string') continue;\n try { keys.push(parseSshPublicKey(line)); } catch { /* A key type Ghostly cannot verify never matches. */ }\n }\n return keys;\n}\n\n/** The account's published keys, or null when there is no such account. */\nasync function listKeys(forge: SshForge, login: string, fetcher: IdentityFetch, signal?: AbortSignal): Promise<SshPublicKey[] | null> {\n if (forge === 'github') {\n const r = await get(fetcher, `https://api.github.com/users/${encodeURIComponent(login)}/keys?per_page=100`, signal);\n return r.status === 404 ? null : keysFrom(r.body);\n }\n const users = await get(fetcher, `https://gitlab.com/api/v4/users?username=${encodeURIComponent(login)}`, signal);\n const list = users.status === 200 && Array.isArray(users.body) ? users.body as { id?: unknown; username?: unknown }[] : [];\n const user = list.find(u => typeof u?.username === 'string' && u.username.toLowerCase() === login.toLowerCase());\n if (!user) return null;\n if (!Number.isSafeInteger(user.id) || (user.id as number) <= 0) throw new Error('Unexpected answer from gitlab.com');\n const r = await get(fetcher, `https://gitlab.com/api/v4/users/${user.id as number}/keys?per_page=100`, signal);\n return r.status === 404 ? null : keysFrom(r.body);\n}\n\n// Only concurrent identical lookups share a request. What stays checked is the verifier's stored\n// result, which the app offers to re-check once the provider's `recheck` time has passed.\nconst inFlight = new WeakMap<IdentityFetch, Map<string, Promise<SshPublicKey[] | null>>>();\n\n/** Whether `key` is one of `login`'s published SSH keys. Throws when the forge cannot be asked. */\nexport async function checkSshForge(forge: SshForge, login: string, key: Pick<SshPublicKey, 'blob'>, fetcher: IdentityFetch, signal?: AbortSignal): Promise<SshForgeStatus> {\n if (!validForgeLogin(forge, login)) throw new Error(`Not a ${SSH_FORGE_LABEL[forge]} username`);\n let pending = inFlight.get(fetcher);\n if (!pending) inFlight.set(fetcher, pending = new Map());\n const map = pending, id = `${forge}:${login.toLowerCase()}`;\n let lookup = map.get(id);\n if (!lookup) {\n lookup = listKeys(forge, login, fetcher, signal).finally(() => map.delete(id));\n map.set(id, lookup);\n }\n const keys = await lookup;\n if (!keys) return 'no-account';\n return keys.some(k => sameSshKey(k, key)) ? 'linked' : 'not-listed';\n}\n","import {\n parseSshPublicKey, parseSshSignature, sshSignCommand, SshSigError, SSHSIG_MAX_ARMOR, SSHSIG_NAMESPACE, utf8Encode, verifySshSignature,\n type IdentityStatement, type SshSignatureCheck,\n} from \"@ghostly/core\";\nimport type { ExternalToolSigner, IdentityProofProvider, VerifyContext } from \"../contract\";\nimport { checkSshForge, SSH_FORGE_HOST, SSH_FORGE_LABEL, validForgeLogin, type SshForge } from \"../sshForges\";\n\n/**\n * SSH keys (draft WISP 307-ssh). The person signs the binding statement once with their own\n * `ssh-keygen -Y sign -n ghostly` and pastes the armored SSHSIG; the private key never enters Ghostly.\n *\n * - `ssh` the subject is the key's SHA-256 fingerprint; checked on this device, no network.\n * - `ssh-github` the subject is a GitHub account: the signing key must be one of the SSH keys GitHub\n * - `ssh-gitlab` publishes for it (GitLab alike). No OAuth: only the account holder can add a key, so\n * \"this account lists the key\" links the two. Re-checked, since keys get removed.\n */\n\nexport interface SshEvidence { signature: string }\n\nconst FINGERPRINT = /^SHA256:[A-Za-z0-9+/]{43}$/;\n\nfunction normalizeKeySubject(input: string): string {\n const value = input.trim();\n if (FINGERPRINT.test(value)) return value;\n try { return parseSshPublicKey(value).fingerprint; }\n catch (e) {\n // Say why a real key is refused (a DSA key, a small RSA key); anything else gets the instruction.\n if (e instanceof SshSigError && /^(Unsupported SSH key type|RSA keys must)/.test(e.message)) throw Object.assign(new Error(e.message), { cause: e });\n throw Object.assign(new Error(\"Paste your public key (the one line in ~/.ssh/id_ed25519.pub) or its SHA256: fingerprint\"), { cause: e });\n }\n}\n\nfunction normalizeLogin(forge: SshForge, input: string): string {\n const login = input.trim().replace(/^@/, \"\").toLowerCase();\n if (!validForgeLogin(forge, login)) throw new Error(`Enter a ${SSH_FORGE_LABEL[forge]} username`);\n return login;\n}\n\n/** The armor, re-wrapped canonically, so evidence is the same whatever the clipboard did to it. */\nfunction canonicalArmor(pasted: string): string {\n const parsed = parseSshSignature(pasted);\n if (parsed.namespace !== SSHSIG_NAMESPACE) throw new Error(`That signature is for \"${parsed.namespace}\", not \"${SSHSIG_NAMESPACE}\": run the command with -n ${SSHSIG_NAMESPACE}`);\n const body = pasted.replace(/\\r\\n/g, \"\\n\").trim().split(\"\\n\").map(l => l.trim()).slice(1, -1).join(\"\");\n return `-----BEGIN SSH SIGNATURE-----\\n${body.match(/.{1,70}/g)!.join(\"\\n\")}\\n-----END SSH SIGNATURE-----\\n`;\n}\n\n/** Exactly the statement; or with one trailing newline, which an editor adds when the statement is\n * saved to a file and signed there (the Windows route). Nothing else is accepted. */\nasync function verifyStatementSignature(statement: IdentityStatement, evidence: SshEvidence): Promise<SshSignatureCheck> {\n try { return await verifySshSignature(evidence.signature, statement.bytes); }\n catch (e) {\n if (!(e instanceof SshSigError) || !/does not match this statement/.test(e.message)) throw e;\n try { return await verifySshSignature(evidence.signature, utf8Encode(`${statement.text}\\n`)); }\n catch (again) { throw Object.assign(new Error(\"The signature is not over this statement: copy the command again and sign exactly what it shows\"), { cause: again }); }\n }\n}\n\nconst keyKind = (c: SshSignatureCheck) => c.securityKey\n ? `security key, touch${c.securityKey.userVerified ? \" and PIN\" : \"\"} confirmed`\n : c.key.type === \"ssh-rsa\" ? `RSA ${c.key.bits}` : c.key.type.replace(/^ssh-|^ecdsa-sha2-/, \"\");\n\nfunction sshKeygenSigner(forAccount?: SshForge): ExternalToolSigner<SshEvidence> {\n return {\n id: \"ssh-keygen\", kind: \"external-tool\", label: \"ssh-keygen\",\n description: forAccount\n ? `Sign with an SSH key that your ${SSH_FORGE_LABEL[forAccount]} account lists (Settings → SSH keys).`\n : \"Sign with OpenSSH's ssh-keygen (8.1 or later): a key file, ssh-agent, or a FIDO security key.\",\n instructions: statement => {\n let command: string | undefined;\n try { command = sshSignCommand(statement.text); } catch { command = undefined; }\n return {\n steps: [\n ...(command ? [{ text: \"Run this in a terminal (macOS, Linux, or Git Bash/WSL on Windows). If your key is not ~/.ssh/id_ed25519, change the path after -f; a .pub path works when the private key is in ssh-agent or on a security key.\", copy: command }] : []),\n { text: `${command ? \"Or save\" : \"Save\"} this statement to a file, run ssh-keygen -Y sign -n ${SSHSIG_NAMESPACE} -f <your key> <file>, and paste the <file>.sig it writes.`, copy: statement.text },\n { text: \"Paste the whole signature it prints, from -----BEGIN SSH SIGNATURE----- to -----END SSH SIGNATURE-----. Your private key never leaves your computer.\" },\n ],\n paste: { label: \"SSH signature\", placeholder: \"-----BEGIN SSH SIGNATURE-----\", multiline: true },\n };\n },\n parse(pasted, statement) {\n const signature = canonicalArmor(pasted);\n if (!forAccount) {\n const { key } = parseSshSignature(signature);\n if (key.fingerprint !== statement.binding.subject) throw new Error(`That signature was made by ${key.fingerprint}, not the key you entered (${statement.binding.subject})`);\n }\n return { signature };\n },\n };\n}\n\nfunction parseSshEvidence(raw: unknown): SshEvidence {\n const e = raw as SshEvidence;\n if (!e || typeof e !== \"object\" || Array.isArray(e) || Object.keys(e).join(\",\") !== \"signature\" ||\n typeof e.signature !== \"string\" || e.signature.length > SSHSIG_MAX_ARMOR) throw new Error(\"That is not an SSH signature\");\n return { signature: e.signature };\n}\n\nconst shortFingerprint = (f: string) => `${f.slice(0, 15)}…${f.slice(-4)}`;\n\nexport const ssh: IdentityProofProvider<SshEvidence> = {\n id: \"ssh\",\n label: \"SSH key\",\n category: \"self-custodied\",\n summary: \"Sign once with ssh-keygen\",\n description: \"Proves you hold an SSH key, including FIDO security keys: sign the statement once with ssh-keygen and paste the signature.\",\n platforms: [\"web\", \"extension\", \"desktop\"],\n subject: {\n label: \"Public key\", placeholder: \"ssh-ed25519 AAAA… (or SHA256:…)\",\n help: \"Your public key, the one line in the .pub file. Contacts see its fingerprint.\",\n normalize: normalizeKeySubject, short: shortFingerprint,\n },\n validity: { defaultDays: 90, maxDays: 365 },\n privacy: \"Nothing: the signature is checked on this device. The key's fingerprint links every chat you share it in.\",\n signers: [sshKeygenSigner()],\n parseEvidence: parseSshEvidence,\n async verify(statement, evidence) {\n const check = await verifyStatementSignature(statement, evidence);\n if (check.key.fingerprint !== statement.binding.subject) throw new Error(\"Signed by another SSH key\");\n return { subject: statement.binding.subject, source: `SSH signature (${keyKind(check)})` };\n },\n};\n\nfunction forgeProvider(forge: SshForge): IdentityProofProvider<SshEvidence> {\n const label = SSH_FORGE_LABEL[forge], host = SSH_FORGE_HOST[forge];\n return {\n id: `ssh-${forge}`,\n label: `${label} (SSH key)`,\n category: \"self-custodied\",\n summary: `A key your ${label} account lists`,\n description: `Proves you hold an SSH key your ${label} account publishes: sign the statement once with ssh-keygen. No ${label} login.`,\n limits: `Does not prove a ${label} login: only that a key the account lists is yours, for as long as it stays listed.`,\n platforms: [\"web\", \"extension\", \"desktop\"],\n subject: { label: `${label} username`, placeholder: forge === \"github\" ? \"octocat\" : \"username\", normalize: input => normalizeLogin(forge, input) },\n publicUri: login => `https://${forge === \"github\" ? \"github.com\" : \"gitlab.com\"}/${login}`,\n validity: { defaultDays: 90, maxDays: 365 },\n // Keys get removed from accounts; the contact's app offers \"Check again\" after this.\n recheck: { afterSeconds: 600 },\n privacy: `Your contact's app asks ${host} for this account's public SSH keys, when you share it and on each re-check: ${label} learns the account was looked up, and from which IP address. So does this app when you add it.`,\n signers: [sshKeygenSigner(forge)],\n parseEvidence: parseSshEvidence,\n async verify(statement, evidence, ctx: VerifyContext) {\n const login = statement.binding.subject;\n if (normalizeLogin(forge, login) !== login) throw new Error(`Not a ${label} username`);\n const check = await verifyStatementSignature(statement, evidence);\n const status = await checkSshForge(forge, login, check.key, ctx.fetch, ctx.signal);\n if (status === \"no-account\") throw new Error(`${label} has no account ${login}`);\n if (status === \"not-listed\") throw new Error(`${label}: ${login} does not list the key that signed (${check.key.fingerprint})`);\n return { subject: login, source: `${label}: ${login} (via published SSH key)` };\n },\n };\n}\n\nexport const sshGithub = forgeProvider(\"github\");\nexport const sshGitlab = forgeProvider(\"gitlab\");\n","import type { FoundUpdate } from \"../../../apps/ui/src/lib/updates\";\nimport type { EngineEvent, RpcRequest, RpcResponse } from \"./shared/rpc\";\nimport type { OidcPlatform } from \"./proofs/oidc/providers\";\nimport type { OidcWindow } from \"./proofs/oidc/flow\";\nimport type { AtprotoHost } from \"./proofs/atproto/oauth\";\nimport type { CallMedia } from \"@ghostly/core\";\n\n/**\n * What differs between the places this peer runs. The extension keeps the\n * peer in an offscreen document and reaches it through extension messaging;\n * the web app runs it in the page. Everything else is shared.\n */\nexport interface EngineConnection {\n send(request: RpcRequest): void;\n}\n\n/**\n * How a client finds a new version of itself and puts it in place. The web\n * app reloads the tab; an extension from the store reloads itself; an unpacked\n * extension can only point at the download. A host that leaves this out has no\n * updater, and the UI never mentions one.\n */\nexport interface UpdateSource {\n /** Where someone installing by hand should go. */\n downloadUrl: string;\n /** The newest published version, or null when this client already runs it. */\n check(): Promise<FoundUpdate | null>;\n /** Never called for `apply: \"manual\"`. `onProgress` is used where the platform reports it. */\n install(update: FoundUpdate, onProgress?: (fraction: number) => void): Promise<void>;\n}\n\nexport interface BrowserHost {\n version: string;\n /** Something the user should know about this client, shown in the sidebar. */\n notice?: string;\n /** Left out where this client has no way to learn about new versions. */\n updates?: UpdateSource;\n /**\n * Where this host frames mini-apps (WISP 1200): its runner page, served with the runner's policy as a header. Left\n * out where it runs none (the extension, until it gets its own runner).\n */\n appRunner?: string;\n /** The runner for apps granted `internet` (WISP 1200 · Permissions), under a policy that allows HTTPS and WSS. */\n appNetRunner?: string;\n /**\n * The host serves its runner itself, always with the runner's policy (Desktop: Rust's `ghostly-app` scheme, to app\n * windows only), so the page does not ask for its header. Left out where a web server serves it (it may be one\n * without the header).\n */\n appRunnerServed?: boolean;\n features: {\n /** Can this host reach web apps on the user's machine? A web page only can if they allow it with CORS. */\n shareLocalServices: boolean;\n /** Can this host show a contact's web app on an origin of its own? */\n openServices: boolean;\n /** Can this host restart as another local profile (WISP 04)? The page's own peer, or the extension's offscreen one. */\n profiles?: boolean;\n };\n /**\n * Starts a new peer in place of one that stopped for good (\"Clear all data\"). Left out where the peer lives in the\n * page: reloading the page starts it again. The extension's peer outlives its pages, so it is restarted on purpose.\n */\n restartEngine?(): Promise<void>;\n /** Reaches the peer. `onDisconnect` fires when it goes away; the client then connects again. */\n connect(onMessage: (message: EngineEvent | RpcResponse) => void, onDisconnect: () => void): Promise<EngineConnection>;\n /**\n * Asks the user for access to a local origin, where the platform has such a thing: `originPattern` is the\n * extension's host pattern (`http://localhost/*`), `origin` the exact address with its port (`http://localhost:3400`),\n * which the desktop app has Rust ask about in a native dialog.\n */\n requestLocalAccess(originPattern: string, origin: string): Promise<boolean>;\n /** The profile no longer shares any app at `origin` (desktop: off Rust's list of addresses it may reach). */\n forgetLocalAccess?(origin: string): Promise<void>;\n openService(peerPubKeyZ32: string, serviceId: string): Promise<void>;\n /**\n * Opens a `lightning:` or `bitcoin:` link in a wallet on this device. Left out where a plain link\n * already does it (a web page); the desktop app and the extension have to hand it to the system.\n */\n openPaymentLink?(uri: string): Promise<void>;\n /**\n * Puts the app's window in or out of full screen. The desktop app on Linux has it: WebKitGTK's element full screen\n * aborts the app there, so the page fills the window with the video instead (apps/desktop/src/fullscreen.rs).\n */\n fullscreenWindow?(on: boolean): Promise<void>;\n /**\n * Hands a link to the system's share sheet, pointing at `anchor` (the button, in CSS pixels).\n * Resolves to false where the platform has no sheet to show; left out where the page's own Web\n * Share API is the way (a web page).\n */\n shareText?(text: string, anchor?: ShareAnchor): Promise<boolean>;\n /**\n * The clipboard's text, read by the platform itself, for a paste button the user just clicked.\n * Left out where the page's `navigator.clipboard.readText()` is the way (a web page, the\n * extension); the desktop app reads natively because WKWebView asks for a second click.\n */\n readClipboardText?(): Promise<string>;\n /**\n * Files copied in a file manager, or a picture, read by the platform itself for a paste whose event\n * carried neither files nor text (a webview that hides them from the page). Left out where the page\n * sees them in the paste event (a browser, WebKit on macOS).\n */\n readClipboardFiles?(): Promise<ClipboardFile[]>;\n /**\n * Where calls get their media, when not from the page's own WebRTC. Ghostly Desktop on Linux: WebKitGTK has\n * no WebRTC there, and GStreamer runs the calls. Left out where the page has it.\n */\n callMedia?: CallMedia;\n /** Signing in with an OpenID Connect provider for an identity proof. Left out where the platform cannot. */\n oidc?: OidcHost;\n /** Signing in to the person's AT Protocol server (Bluesky or another PDS) for an identity proof. Left out where the platform cannot. */\n atproto?: AtprotoHost;\n /**\n * Opens Pubky Passport's authorize page (`https://passport.pubky.app/authorize#d=…`, the request in its fragment)\n * outside the app. The desktop app hands it to the system browser; left out where the page opens a popup itself.\n */\n openPubkyPassport?(url: string): Promise<void>;\n /**\n * A Pubky cookie session's requests to the homeserver, made outside the page with a cookie jar of their own: the\n * desktop app, whose WKWebView drops the homeserver's session cookie (a third-party one there). Left out where\n * the page's own fetch keeps it.\n */\n pubkyCookieSession?(): PubkyCookieSession;\n /**\n * Keeps the computer from sleeping while idle, or lets it sleep again (\"Keep this computer awake\", WISP 06 § User\n * experience): the desktop app, so a phone can take the profile over while the person is out. Resolves to whether\n * it is held now. Left out where the platform cannot (a web page, the extension).\n */\n keepAwake?(on: boolean): Promise<boolean>;\n}\n\n/** One approval's cookie session, outside the page: its cookies never reach the page, and `close` forgets them. */\nexport interface PubkyCookieSession {\n fetch(request: { url: string; method: string; headers: [string, string][]; body: Uint8Array | null }): Promise<{ status: number; headers: [string, string][]; body: Uint8Array }>;\n close(): void;\n}\n\n/** Where a share sheet points: the button that opened it, in CSS pixels from the page's top left. */\nexport interface ShareAnchor { x: number; y: number; width: number; height: number }\n\nexport interface OidcHost {\n platform: OidcPlatform;\n /**\n * Call it straight from the click: the web app opens its popup and the\n * extension asks for the `identity` permission before anything is awaited,\n * or the browser blocks them.\n */\n open(): Promise<OidcWindow>;\n}\n\nlet current: BrowserHost | null = null;\n\n/** Must be called before anything in `platform/` is used. */\nexport function setBrowserHost(host: BrowserHost): void {\n current = host;\n}\n\nexport function getBrowserHost(): BrowserHost {\n if (!current) throw new Error(\"No browser host configured\");\n return current;\n}\n\n/**\n * One thing a paste brought, held by the platform: a file (its name) or a picture (no name: the page\n * names it). `read` hands its bytes a step at a time; `done` says the page has what it wants of\n * it (or gave up), so the platform lets its copy go: nothing is read after it.\n */\nexport interface ClipboardFile {\n name: string | null;\n size: number;\n mime: string | null;\n read(offset: number, length: number): Promise<Uint8Array>;\n done?(): void;\n}\n","/**\n * The OpenID Connect providers Ghostly can ask for a signed ID token. A contact\n * verifies that token against the provider's published keys, with no Ghostly\n * server in between, so a provider is only listed when its ID tokens are signed\n * and its keys and sign-in page are reachable from a browser without a client\n * secret. See docs/wisps/3xx-oidc-proofs.md for the providers that are not\n * (Facebook, X, LinkedIn, GitHub) and why.\n *\n * Client IDs are public configuration, identical in every build, because the\n * contact's app checks `aud` against them. They stay empty until the OAuth\n * clients are registered (docs/OIDC-PROVIDERS.md); a provider without a client\n * ID is neither offered nor accepted.\n */\n\nexport type OidcProviderId = 'google' | 'microsoft' | 'apple' | 'gitlab' | 'twitch' | 'test';\nexport type OidcAlg = 'RS256' | 'ES256';\nexport type OidcPlatform = 'web' | 'extension' | 'desktop';\n/** What the person chooses to put in the token beyond the account identifier. */\nexport type OidcReveal = 'email' | 'profile';\n\nexport interface OidcProvider {\n id: OidcProviderId;\n name: string;\n /** Exact `iss`. Microsoft's contains the tenant: see `issuerFor`. */\n issuer: string;\n /** Other spellings the provider documents for the same issuer. */\n issuerAliases?: string[];\n discovery: string;\n authorizationEndpoint: string;\n /** Pinned rather than discovered, so a changed discovery document cannot redirect key lookup. */\n jwksUri: string;\n /** Only for `code`: the token endpoint, reached with PKCE and no secret. */\n tokenEndpoint?: string;\n /** Best effort after a `code` exchange: the access token Ghostly never wanted is revoked at once. */\n revocationEndpoint?: string;\n /** Pinned per provider; the token header's `alg` must be one of these. */\n algs: OidcAlg[];\n /**\n * `id_token` returns only the signed ID token in the fragment: no access\n * token, no code, nothing to exchange, so no secret. `code id_token` is\n * Apple's shape (its code needs a secret; Ghostly drops it). `code` is for\n * providers without an ID-token-only response, exchanged with PKCE.\n */\n responseType: 'id_token' | 'code id_token' | 'code';\n /** Which extra claims this provider can add, and the scope (or claims request) for each. */\n reveal: Partial<Record<OidcReveal, { scope?: string; claims?: string[] }>>;\n /** What the provider puts in the token whatever the person picks. Shown before sign-in. */\n alwaysIncluded: string;\n /** Extra authorization parameters, e.g. an account chooser. */\n extraParams?: Record<string, string>;\n /** Client IDs per platform. Desktop signs in through the web callback page, so it uses the web client. */\n clientIds: Partial<Record<OidcPlatform, string>>;\n}\n\nexport const TEST_ISSUER = /^https:\\/\\/[a-z0-9-]+(\\.[a-z0-9-]+)*\\.test$/;\nconst MICROSOFT_TENANT = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;\n\nexport const OIDC_PROVIDERS: Record<Exclude<OidcProviderId, 'test'>, OidcProvider> = {\n google: {\n id: 'google', name: 'Google',\n issuer: 'https://accounts.google.com', issuerAliases: ['accounts.google.com'],\n discovery: 'https://accounts.google.com/.well-known/openid-configuration',\n authorizationEndpoint: 'https://accounts.google.com/o/oauth2/v2/auth',\n jwksUri: 'https://www.googleapis.com/oauth2/v3/certs',\n algs: ['RS256'], responseType: 'id_token',\n reveal: { email: { scope: 'email' }, profile: { scope: 'profile' } },\n alwaysIncluded: 'A Google account identifier.',\n extraParams: { prompt: 'select_account' },\n clientIds: { web: '', extension: '' },\n },\n microsoft: {\n // `common` takes work, school and personal accounts; the issuer names the account's tenant.\n id: 'microsoft', name: 'Microsoft',\n issuer: 'https://login.microsoftonline.com/{tenantid}/v2.0',\n discovery: 'https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration',\n authorizationEndpoint: 'https://login.microsoftonline.com/common/oauth2/v2.0/authorize',\n jwksUri: 'https://login.microsoftonline.com/common/discovery/v2.0/keys',\n algs: ['RS256'], responseType: 'id_token',\n reveal: { email: { scope: 'email' }, profile: { scope: 'profile' } },\n alwaysIncluded: 'A Microsoft account identifier and the directory (tenant) the account belongs to.',\n extraParams: { prompt: 'select_account', response_mode: 'fragment' },\n clientIds: { web: '', extension: '' },\n },\n apple: {\n // Apple only returns the ID token in the fragment when no scope is asked\n // for; name and email need `form_post`, which a static page cannot read.\n id: 'apple', name: 'Apple',\n issuer: 'https://appleid.apple.com',\n discovery: 'https://appleid.apple.com/.well-known/openid-configuration',\n authorizationEndpoint: 'https://appleid.apple.com/auth/authorize',\n jwksUri: 'https://appleid.apple.com/auth/keys',\n algs: ['RS256'], responseType: 'code id_token',\n reveal: {},\n alwaysIncluded: 'An Apple ID identifier that only Ghostly sees. Apple cannot add your email here without a Ghostly server.',\n extraParams: { response_mode: 'fragment' },\n clientIds: { web: '', extension: '' },\n },\n gitlab: {\n id: 'gitlab', name: 'GitLab',\n issuer: 'https://gitlab.com',\n discovery: 'https://gitlab.com/.well-known/openid-configuration',\n authorizationEndpoint: 'https://gitlab.com/oauth/authorize',\n tokenEndpoint: 'https://gitlab.com/oauth/token',\n revocationEndpoint: 'https://gitlab.com/oauth/revoke',\n jwksUri: 'https://gitlab.com/oauth/discovery/keys',\n algs: ['RS256'], responseType: 'code',\n reveal: { email: { scope: 'email' }, profile: { scope: 'profile' } },\n alwaysIncluded: 'A GitLab.com user ID and the groups you belong to directly: GitLab adds them to every ID token.',\n clientIds: { web: '', extension: '' },\n },\n twitch: {\n // Twitch's email claim needs `user:read:email`, an API scope: not asked for.\n id: 'twitch', name: 'Twitch',\n issuer: 'https://id.twitch.tv/oauth2',\n discovery: 'https://id.twitch.tv/oauth2/.well-known/openid-configuration',\n authorizationEndpoint: 'https://id.twitch.tv/oauth2/authorize',\n jwksUri: 'https://id.twitch.tv/oauth2/keys',\n algs: ['RS256'], responseType: 'id_token',\n reveal: { profile: { claims: ['preferred_username'] } },\n alwaysIncluded: 'A Twitch user ID.',\n extraParams: { force_verify: 'true' },\n clientIds: { web: '', extension: '' },\n },\n};\n\n/**\n * The local issuer the e2e suite runs (e2e/support/oidcIssuer.ts). It exists\n * only in a build made with VITE_OIDC_TEST_ISSUER, and only on a `.test` host\n * (a name reserved for testing, never a real issuer); a release build has no\n * such provider, so a token from it is refused like any unknown issuer.\n */\nexport function testProvider(issuer: string): OidcProvider {\n if (!TEST_ISSUER.test(issuer)) throw new Error('A test issuer lives on a .test host');\n const base = issuer;\n return {\n id: 'test', name: 'Test issuer', issuer: base,\n discovery: `${base}/.well-known/openid-configuration`,\n authorizationEndpoint: `${base}/authorize`,\n jwksUri: `${base}/jwks`,\n algs: ['RS256', 'ES256'], responseType: 'id_token',\n reveal: { email: { scope: 'email' }, profile: { scope: 'profile' } },\n alwaysIncluded: 'A test account identifier.',\n clientIds: { web: 'ghostly-e2e', extension: 'ghostly-e2e', desktop: 'ghostly-e2e' },\n };\n}\n\nfunction configuredTestIssuer(): string | undefined {\n try {\n const value = import.meta.env?.VITE_OIDC_TEST_ISSUER as string | undefined;\n return value && TEST_ISSUER.test(value) ? value : undefined;\n } catch { return undefined; }\n}\n\n/** Every provider this build knows, configured or not. */\nexport function oidcProviders(testIssuer = configuredTestIssuer()): OidcProvider[] {\n return [...Object.values(OIDC_PROVIDERS), ...(testIssuer ? [testProvider(testIssuer)] : [])];\n}\n\nexport function oidcProvider(id: string, testIssuer = configuredTestIssuer()): OidcProvider | undefined {\n return oidcProviders(testIssuer).find(p => p.id === id);\n}\n\n/** Every client ID this provider has, on any platform: a contact may use another platform than the prover. */\nexport function audiences(provider: OidcProvider): string[] {\n return [...new Set(Object.values(provider.clientIds).filter((id): id is string => !!id))];\n}\n\nexport function clientIdFor(provider: OidcProvider, platform: OidcPlatform): string {\n return provider.clientIds[platform] || provider.clientIds.web || '';\n}\n\n/** A provider is offered only once its client ID for this platform exists. */\nexport function isConfigured(provider: OidcProvider, platform: OidcPlatform): boolean {\n return !!clientIdFor(provider, platform);\n}\n\n/** The exact issuer a token from this provider must carry, or undefined when its claims do not name one. */\nexport function issuerFor(provider: OidcProvider, claims: { tid?: unknown }): string[] {\n if (provider.id !== 'microsoft') return [provider.issuer, ...(provider.issuerAliases ?? [])];\n return typeof claims.tid === 'string' && MICROSOFT_TENANT.test(claims.tid) ? [provider.issuer.replace('{tenantid}', claims.tid)] : [];\n}\n\n/** The provider a proof's subject (an issuer, as `OidcProvider.issuer` spells it) names. */\nexport function providerForIssuer(issuer: string, list = oidcProviders()): OidcProvider | undefined {\n return list.find(p => p.issuer === issuer);\n}\n","/**\n * A provider's signing keys, fetched from its pinned JWKS URL with limits and\n * cached. A key ID that is not in the cached set triggers one early refetch\n * (providers rotate keys), rate-limited so that a stream of made-up key IDs\n * cannot turn a contact's app into a request generator.\n */\n\nexport interface Jwk {\n kty: string; kid?: string; use?: string; alg?: string; key_ops?: string[];\n n?: string; e?: string; crv?: string; x?: string; y?: string;\n /** Microsoft adds the issuer each key signs for. */\n issuer?: string;\n}\n/**\n * How keys are fetched. In `verify` this is the engine's `ctx.fetch` (HTTPS GET, no credentials, no\n * redirects, time-out, size cap); `cacheControl` is used when the fetcher reports it.\n */\nexport type JwksFetch = (url: string, options: { maxBytes: number }) => Promise<{ status: number; text: string; cacheControl?: string | null }>;\n\nexport const JWKS_MAX_BYTES = 64 * 1024;\nexport const JWKS_MAX_KEYS = 32;\nconst MIN_TTL_MS = 5 * 60_000;\nconst MAX_TTL_MS = 24 * 60 * 60_000;\nconst DEFAULT_TTL_MS = 60 * 60_000;\n/** A missing `kid` refetches at most this often per URL. */\nexport const REFETCH_INTERVAL_MS = 60_000;\n\ninterface Entry { keys: Jwk[]; expires: number; fetched: number }\n\nfunction allowedUrl(url: string): boolean {\n try {\n const u = new URL(url);\n return u.protocol === 'https:' && !u.username && !u.password && !u.hash;\n } catch { return false; }\n}\n\nfunction ttl(cacheControl: string | null | undefined): number {\n const maxAge = /(?:^|[,\\s])max-age=(\\d+)/i.exec(cacheControl ?? '')?.[1];\n const ms = maxAge ? Number(maxAge) * 1000 : DEFAULT_TTL_MS;\n return Math.min(MAX_TTL_MS, Math.max(MIN_TTL_MS, ms));\n}\n\nfunction parseKeys(text: string): Jwk[] {\n if (text.length > JWKS_MAX_BYTES) throw new Error('Provider key set too large');\n let doc: { keys?: unknown };\n try { doc = JSON.parse(text) as { keys?: unknown }; } catch { throw new Error('Invalid provider key set'); }\n if (!doc || !Array.isArray(doc.keys) || doc.keys.length > JWKS_MAX_KEYS) throw new Error('Invalid provider key set');\n return doc.keys.filter((k): k is Jwk => !!k && typeof k === 'object' && typeof (k as Jwk).kty === 'string');\n}\n\nexport class JwksCache {\n private entries = new Map<string, Entry>();\n private inflight = new Map<string, Promise<Entry>>();\n constructor(private options: { now?: () => number } = {}) {}\n private now() { return (this.options.now ?? Date.now)(); }\n\n private load(url: string, fetcher: JwksFetch): Promise<Entry> {\n const running = this.inflight.get(url);\n if (running) return running;\n const task = (async () => {\n if (!allowedUrl(url)) throw new Error('Provider key URL refused');\n const response = await fetcher(url, { maxBytes: JWKS_MAX_BYTES });\n if (response.status !== 200) throw new Error(`Provider keys unavailable (${response.status})`);\n const keys = parseKeys(response.text);\n const fetched = this.now();\n const entry = { keys, fetched, expires: fetched + ttl(response.cacheControl) };\n this.entries.set(url, entry);\n return entry;\n })();\n this.inflight.set(url, task);\n return task.finally(() => this.inflight.delete(url));\n }\n\n /** The key with this `kid`, refetching once when it is unknown (rotation) or the cache expired. */\n async key(url: string, kid: string, fetcher: JwksFetch): Promise<Jwk | undefined> {\n let entry = this.entries.get(url);\n if (!entry || entry.expires <= this.now()) entry = await this.load(url, fetcher);\n let found = entry.keys.find(k => k.kid === kid);\n if (!found && this.now() - entry.fetched >= REFETCH_INTERVAL_MS) {\n entry = await this.load(url, fetcher);\n found = entry.keys.find(k => k.kid === kid);\n }\n return found;\n }\n\n clear(): void { this.entries.clear(); }\n}\n\n/** One cache per app: every contact's proofs share the providers' keys. */\nexport const sharedJwksCache = new JwksCache();\n","import { fromBase64Url, toBase64Url, utf8Decode, utf8Encode } from '@ghostly/core';\nimport { audiences, issuerFor, type OidcAlg, type OidcProvider, type OidcProviderId } from './providers';\nimport { sharedJwksCache, type Jwk, type JwksCache, type JwksFetch } from './jwks';\n\n/**\n * Checks an ID token the way a contact must: the provider's signature under\n * a key from its own JWKS, the pinned `alg`, the issuer, `aud` = one of\n * Ghostly's client IDs for that provider, the time window, and `nonce` = the\n * hash of the exact Ghostly proof statement. Nothing here talks to a Ghostly\n * server, and nothing here trusts what the token says about its own key.\n */\n\n/** Seconds of clock difference accepted on `iat`, `nbf` and `exp`. */\nexport const CLOCK_TOLERANCE = 60;\n/** An ID token that claims to live longer than a day is not a sign-in result. */\nconst MAX_LIFETIME = 24 * 60 * 60;\n/** Room for Microsoft's larger tokens, well inside a peer frame. */\nexport const MAX_ID_TOKEN_LENGTH = 6144;\nconst SEGMENT = /^[A-Za-z0-9_-]+$/;\nconst REFUSED_HEADERS = ['crit', 'jku', 'jwk', 'x5u', 'x5c', 'zip', 'enc'];\n\nexport class OidcVerificationError extends Error {\n constructor(message: string) { super(message); this.name = 'OidcVerificationError'; }\n}\nconst refuse = (message: string): never => { throw new OidcVerificationError(message); };\n\n/** What a contact is shown: only claims the provider signed, bounded. */\nexport interface OidcIdentity {\n provider: OidcProviderId;\n issuer: string;\n subject: string;\n audience: string;\n issuedAt: number;\n expiresAt: number;\n email?: string;\n emailVerified?: boolean;\n name?: string;\n username?: string;\n /** Microsoft's directory (tenant) ID. */\n tenant?: string;\n}\n\nfunction segment(value: string): Uint8Array {\n if (!SEGMENT.test(value)) refuse('Malformed ID token');\n let bytes: Uint8Array;\n try { bytes = fromBase64Url(value); } catch { return refuse('Malformed ID token'); }\n if (toBase64Url(bytes) !== value) refuse('Malformed ID token');\n return bytes;\n}\n\nfunction json(value: string): Record<string, unknown> {\n let parsed: unknown;\n try { parsed = JSON.parse(utf8Decode(segment(value))); } catch (e) { if (e instanceof OidcVerificationError) throw e; return refuse('Malformed ID token'); }\n if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) refuse('Malformed ID token');\n return parsed as Record<string, unknown>;\n}\n\nconst text = (value: unknown, max: number): string | undefined =>\n typeof value === 'string' && value.length > 0 && value.length <= max ? value : undefined;\n\n/** Splits and decodes without verifying anything. Only for display of a token already verified. */\nexport function decodeIdToken(token: string): { header: Record<string, unknown>; claims: Record<string, unknown>; signature: Uint8Array; signed: Uint8Array } {\n if (typeof token !== 'string' || token.length > MAX_ID_TOKEN_LENGTH) refuse('ID token missing or too large');\n const parts = token.split('.');\n if (parts.length !== 3) refuse('Not a signed ID token');\n return { header: json(parts[0]), claims: json(parts[1]), signature: segment(parts[2]), signed: utf8Encode(`${parts[0]}.${parts[1]}`) };\n}\n\nasync function verifySignature(alg: OidcAlg, jwk: Jwk, signed: Uint8Array, signature: Uint8Array): Promise<boolean> {\n if (jwk.use !== undefined && jwk.use !== 'sig') refuse('Provider key is not a signing key');\n if (jwk.alg !== undefined && jwk.alg !== alg) refuse('Provider key is for another algorithm');\n if (jwk.key_ops !== undefined && !(Array.isArray(jwk.key_ops) && jwk.key_ops.includes('verify'))) refuse('Provider key is not a signing key');\n let key: CryptoKey;\n if (alg === 'RS256') {\n if (jwk.kty !== 'RSA' || typeof jwk.n !== 'string' || typeof jwk.e !== 'string') return refuse('Provider key does not match the token algorithm');\n const modulus = segment(jwk.n);\n const bits = (modulus.length - (modulus[0] === 0 ? 1 : 0)) * 8;\n if (bits < 2048 || bits > 8192) refuse('Provider key size refused');\n key = await crypto.subtle.importKey('jwk', { kty: 'RSA', n: jwk.n, e: jwk.e, ext: true }, { name: 'RSASSA-PKCS1-v1_5', hash: 'SHA-256' }, false, ['verify']);\n return crypto.subtle.verify('RSASSA-PKCS1-v1_5', key, signature as BufferSource, signed as BufferSource);\n }\n if (jwk.kty !== 'EC' || jwk.crv !== 'P-256' || typeof jwk.x !== 'string' || typeof jwk.y !== 'string') return refuse('Provider key does not match the token algorithm');\n if (signature.length !== 64) refuse('Invalid ID token signature');\n key = await crypto.subtle.importKey('jwk', { kty: 'EC', crv: 'P-256', x: jwk.x, y: jwk.y, ext: true }, { name: 'ECDSA', namedCurve: 'P-256' }, false, ['verify']);\n return crypto.subtle.verify({ name: 'ECDSA', hash: 'SHA-256' }, key, signature as BufferSource, signed as BufferSource);\n}\n\n/** Whether `iss` is this provider's, as it words it (Microsoft: with the token's own tenant). */\nexport function issuerMatches(provider: OidcProvider, claims: Record<string, unknown>): boolean {\n const iss = text(claims.iss, 512);\n return !!iss && issuerFor(provider, claims).includes(iss);\n}\n\nexport interface VerifyOptions {\n provider: OidcProvider;\n /** The nonce the sign-in was asked for: an identity proof's `statement.id`. */\n nonce: string;\n /** How the provider's keys are fetched: `ctx.fetch` in a proof's `verify`. */\n fetch: JwksFetch;\n /** Unix seconds. */\n now?: number;\n jwks?: JwksCache;\n /**\n * `[from, to]` in Unix seconds: the token must have been issued in this window. For a proof, the\n * sign-in that followed the statement; the token's own `exp` (an hour) then does not matter, the\n * statement's validity does. Without it, the token must still be unexpired.\n */\n issuedWithin?: [number, number];\n}\n\nexport async function verifyIdToken(token: string, options: VerifyOptions): Promise<OidcIdentity> {\n const { provider, nonce } = options;\n const now = options.now ?? Math.floor(Date.now() / 1000);\n const { header, claims, signature, signed } = decodeIdToken(token);\n\n // The algorithm is the provider's, never the token's choice: `none`, HS256\n // (a public key used as an HMAC secret) and anything unpinned stop here.\n const alg = header.alg;\n if (typeof alg !== 'string' || !provider.algs.includes(alg as OidcAlg)) refuse(`Algorithm ${typeof alg === 'string' ? alg : 'missing'} is not accepted for ${provider.name}`);\n if (header.typ !== undefined && (typeof header.typ !== 'string' || header.typ.toUpperCase() !== 'JWT')) refuse('Not an ID token');\n for (const name of REFUSED_HEADERS) if (name in header) refuse('ID token header refused');\n const kid = text(header.kid, 256) ?? refuse('ID token names no provider key');\n\n if (!issuerMatches(provider, claims)) refuse(`Token was not issued by ${provider.name}`);\n const iss = claims.iss as string;\n\n const expected = audiences(provider);\n if (!expected.length) refuse(`${provider.name} is not configured in this Ghostly build`);\n const aud = typeof claims.aud === 'string' ? [claims.aud] : Array.isArray(claims.aud) && claims.aud.every(a => typeof a === 'string') ? claims.aud as string[] : [];\n const audience = aud.find(a => expected.includes(a)) ?? refuse('Token was issued to another app');\n if (aud.length > 1 && claims.azp !== audience) refuse('Token was issued to another app');\n if (claims.azp !== undefined && claims.azp !== audience) refuse('Token was issued to another app');\n\n const iat = claims.iat, exp = claims.exp, nbf = claims.nbf;\n if (typeof iat !== 'number' || typeof exp !== 'number' || !Number.isFinite(iat) || !Number.isFinite(exp)) return refuse('Token has no validity window');\n if (exp <= iat || exp - iat > MAX_LIFETIME) refuse('Token validity window refused');\n if (iat > now + CLOCK_TOLERANCE) refuse('Token is not valid yet');\n if (options.issuedWithin) {\n const [from, to] = options.issuedWithin;\n if (iat < from || iat > to) refuse('The sign-in does not belong to this proof: it happened at another time');\n } else {\n if (typeof nbf === 'number' && nbf > now + CLOCK_TOLERANCE) refuse('Token is not valid yet');\n if (exp + CLOCK_TOLERANCE < now) refuse('Token expired');\n }\n\n if (typeof claims.nonce !== 'string' || claims.nonce !== nonce) refuse('Token answers another proof request');\n const subject = text(claims.sub, 255) ?? refuse('Token names no account');\n\n const jwk = await (options.jwks ?? sharedJwksCache).key(provider.jwksUri, kid, options.fetch) ?? refuse(`${provider.name} does not publish the key that signed this token`);\n if (provider.id === 'microsoft' && jwk.issuer !== undefined && jwk.issuer !== provider.issuer && jwk.issuer !== iss) refuse('Provider key belongs to another issuer');\n let valid = false;\n try { valid = await verifySignature(alg as OidcAlg, jwk, signed, signature); }\n catch (e) { if (e instanceof OidcVerificationError) throw e; refuse('Invalid ID token signature'); }\n if (!valid) refuse('Invalid ID token signature');\n\n const verified = claims.email_verified === true || claims.email_verified === 'true' ? true : claims.email_verified === false || claims.email_verified === 'false' ? false : undefined;\n return {\n provider: provider.id, issuer: iss, subject, audience, issuedAt: Math.floor(iat), expiresAt: Math.floor(exp),\n email: text(claims.email, 320), emailVerified: text(claims.email, 320) ? verified : undefined,\n name: text(claims.name, 200), username: text(claims.preferred_username, 200) ?? text(claims.nickname, 200),\n tenant: provider.id === 'microsoft' ? text(claims.tid, 64) : undefined,\n };\n}\n","import { randomBytes, toBase64Url, utf8Encode } from '@ghostly/core';\nimport { clientIdFor, type OidcPlatform, type OidcProvider, type OidcReveal } from './providers';\nimport { decodeIdToken, issuerMatches } from './verify';\n\n/**\n * The sign-in half: send the person to the provider with the identity proof's\n * statement id as `nonce`, and take back only the ID token. The token's\n * signature is checked by the proof's `verify`, before saving and on every\n * contact's device; here only that it answers this request. Access and refresh tokens are never requested\n * where the provider allows it, and dropped (and revoked) where it does not.\n */\n\n/** How a platform shows the provider's page and hands back where it redirected. */\nexport interface OidcWindow {\n /** The redirect URI registered for this platform. */\n redirectUri: string;\n /** Extra state prefix the platform needs to route the answer back (desktop: its loopback port). */\n statePrefix?: string;\n /** Opens `url` and resolves with the full URL the provider redirected to. */\n authorize(url: string, signal: AbortSignal): Promise<string>;\n /** Releases the window or listener, answered or not. */\n close(): void;\n}\n\nexport interface OidcRequest {\n url: string;\n state: string;\n nonce: string;\n redirectUri: string;\n clientId: string;\n /** PKCE verifier, only for `code`. Held in memory for the one exchange. */\n verifier?: string;\n}\n\n/** Optional platform prefix (desktop `d.<port>.`, Android deep link `a.`), then 32 random bytes. */\nconst STATE = /^(?:d\\.\\d{1,5}\\.|a\\.)?[A-Za-z0-9_-]{43}$/;\n\nexport function scopeFor(provider: OidcProvider, reveal: OidcReveal[]): { scope: string; claims?: string } {\n const scopes = provider.id === 'apple' ? [] : ['openid'];\n const claims: string[] = [];\n for (const r of reveal) {\n const option = provider.reveal[r];\n if (!option) continue;\n if (option.scope) scopes.push(option.scope);\n if (option.claims) claims.push(...option.claims);\n }\n return { scope: scopes.join(' '), claims: claims.length ? JSON.stringify({ id_token: Object.fromEntries(claims.map(c => [c, null])) }) : undefined };\n}\n\nexport async function authorizationRequest(options: { provider: OidcProvider; platform: OidcPlatform; nonce: string; reveal: OidcReveal[]; redirectUri: string; statePrefix?: string }): Promise<OidcRequest> {\n const { provider } = options;\n const clientId = clientIdFor(provider, options.platform);\n if (!clientId) throw new Error(`${provider.name} sign-in is not set up in this Ghostly build yet.`);\n const { nonce } = options;\n if (!/^[A-Za-z0-9_-]{16,128}$/.test(nonce)) throw new Error('Invalid sign-in nonce');\n const state = `${options.statePrefix ?? ''}${toBase64Url(randomBytes(32))}`;\n const url = new URL(provider.authorizationEndpoint);\n const { scope, claims } = scopeFor(provider, options.reveal);\n const params: Record<string, string> = {\n client_id: clientId, redirect_uri: options.redirectUri, response_type: provider.responseType,\n nonce, state, ...(scope ? { scope } : {}), ...(claims ? { claims } : {}), ...provider.extraParams,\n };\n let verifier: string | undefined;\n if (provider.responseType === 'code') {\n verifier = toBase64Url(randomBytes(32));\n params.code_challenge = toBase64Url(new Uint8Array(await crypto.subtle.digest('SHA-256', utf8Encode(verifier) as BufferSource)));\n params.code_challenge_method = 'S256';\n }\n for (const [k, v] of Object.entries(params)) url.searchParams.set(k, v);\n return { url: url.toString(), state, nonce, redirectUri: options.redirectUri, clientId, verifier };\n}\n\n/** Parameters from the redirect: the fragment for implicit responses, the query for `code`. */\nexport function callbackParams(redirected: string, redirectUri: string): URLSearchParams {\n const url = new URL(redirected), expected = new URL(redirectUri);\n if (url.origin !== expected.origin || url.pathname !== expected.pathname) throw new Error('Sign-in returned to an unexpected address.');\n const params = new URLSearchParams(url.hash.slice(1));\n for (const [k, v] of new URLSearchParams(url.search)) if (!params.has(k)) params.set(k, v);\n return params;\n}\n\nconst PROVIDER_ERRORS: Record<string, string> = {\n access_denied: 'Sign-in was cancelled.',\n login_required: 'Sign in to your account and try again.',\n consent_required: 'Consent was not given.',\n interaction_required: 'The provider needs you to finish signing in.',\n unauthorized_client: 'This provider has not accepted Ghostly’s sign-in yet.',\n invalid_request: 'The provider refused the sign-in request.',\n};\n\nasync function exchangeCode(provider: OidcProvider, request: OidcRequest, code: string, doFetch: typeof fetch): Promise<string> {\n if (!provider.tokenEndpoint || !request.verifier) throw new Error('This provider cannot finish sign-in here.');\n const response = await doFetch(provider.tokenEndpoint, {\n method: 'POST', redirect: 'error', credentials: 'omit', referrerPolicy: 'no-referrer', cache: 'no-store',\n headers: { 'content-type': 'application/x-www-form-urlencoded', accept: 'application/json' },\n body: new URLSearchParams({ grant_type: 'authorization_code', code, redirect_uri: request.redirectUri, client_id: request.clientId, code_verifier: request.verifier }),\n });\n const body = await response.json().catch(() => ({})) as { id_token?: unknown; access_token?: unknown; refresh_token?: unknown };\n // Only the ID token leaves this function. The others were never wanted:\n // revoke them without waiting, and let them go out of scope.\n for (const token of [body.refresh_token, body.access_token]) {\n if (typeof token === 'string' && provider.revocationEndpoint)\n void doFetch(provider.revocationEndpoint, { method: 'POST', redirect: 'error', credentials: 'omit', referrerPolicy: 'no-referrer',\n headers: { 'content-type': 'application/x-www-form-urlencoded' }, body: new URLSearchParams({ token, client_id: request.clientId }) }).catch(() => {});\n }\n if (!response.ok || typeof body.id_token !== 'string') throw new Error(`${provider.name} did not return an ID token.`);\n return body.id_token;\n}\n\n/** Turns the redirect into the ID token: state, provider error, code exchange if needed, and that it answers this request. */\nexport async function completeSignIn(provider: OidcProvider, request: OidcRequest, redirected: string, options: { fetch?: typeof fetch } = {}): Promise<string> {\n const params = callbackParams(redirected, request.redirectUri);\n const state = params.get('state');\n if (!state || !STATE.test(state) || state !== request.state) throw new Error('Sign-in answer does not belong to this request.');\n const error = params.get('error');\n if (error) throw new Error(PROVIDER_ERRORS[error] ?? 'The provider refused the sign-in.');\n let idToken: string | null;\n if (provider.responseType === 'code') {\n const code = params.get('code');\n if (!code || code.length > 2048) throw new Error(`${provider.name} did not return a sign-in code.`);\n idToken = await exchangeCode(provider, request, code, options.fetch ?? fetch);\n } else idToken = params.get('id_token');\n // Apple's code needs a client secret to redeem, which Ghostly does not have: it is dropped with the rest.\n if (!idToken) throw new Error(`${provider.name} did not return an ID token.`);\n const { claims } = decodeIdToken(idToken);\n if (claims.nonce !== request.nonce || !issuerMatches(provider, claims)) throw new Error(`${provider.name} answered another sign-in request.`);\n return idToken;\n}\n\n/** Everything the prover does: sign in on this platform and return the ID token for this nonce. */\nexport async function signInForProof(options: {\n provider: OidcProvider; platform: OidcPlatform; nonce: string; reveal: OidcReveal[];\n window: OidcWindow; signal: AbortSignal; fetch?: typeof fetch;\n}): Promise<string> {\n const { window: w } = options;\n try {\n options.signal.throwIfAborted();\n const request = await authorizationRequest({ ...options, redirectUri: w.redirectUri, statePrefix: w.statePrefix });\n const redirected = await w.authorize(request.url, options.signal);\n options.signal.throwIfAborted();\n return await completeSignIn(options.provider, request, redirected, options);\n } finally { w.close(); }\n}\n","import { IDENTITY_CLOCK_SKEW, type IdentityStatement } from \"@ghostly/core\";\nimport type { IdentityProofProvider, IdentityPlatform, RedirectSigner } from \"../contract\";\nimport { getBrowserHost, type OidcHost } from \"../../host\";\nimport { audiences, isConfigured, oidcProviders, providerForIssuer, type OidcProvider, type OidcReveal } from \"../oidc/providers\";\nimport { signInForProof } from \"../oidc/flow\";\nimport { MAX_ID_TOKEN_LENGTH, verifyIdToken } from \"../oidc/verify\";\nimport { JwksCache, sharedJwksCache } from \"../oidc/jwks\";\n\n/**\n * Identity proofs through OpenID Connect: the person signs in with a provider (Google, Microsoft,\n * Apple, GitLab, Twitch) whose ID token carries the statement id as `nonce`. The token is the evidence;\n * a contact's app checks the provider's signature against its published keys, the issuer, Ghostly's\n * client ID, the nonce and when the sign-in happened. No Ghostly server is involved, and the proof is\n * only as good as the provider's word: it is attested, not a key the person holds.\n *\n * The statement's subject is the issuer the person picked; the account comes out of `verify`.\n * Configuration and flows per provider: ../oidc/, docs/OIDC-PROVIDERS.md, docs/wisps/3xx-oidc-proofs.md.\n */\nexport interface OidcEvidence { token: string }\n\n/** How long after the statement the sign-in may have happened: time to pick an account and approve. */\nconst SIGN_IN_WINDOW = 15 * 60;\nconst TOKEN = /^[A-Za-z0-9_-]+\\.[A-Za-z0-9_-]+\\.[A-Za-z0-9_-]+$/;\nconst SUB = /^[\\x21-\\x7e]{1,255}$/;\n\nfunction currentHost(): OidcHost | undefined {\n try { return getBrowserHost().oidc; } catch { return undefined; }\n}\n\nconst hostOf = (issuer: string) => { try { return new URL(issuer).host; } catch { return issuer; } };\n\nexport interface OidcIdentityOptions {\n /** The OIDC providers this build knows (tests pass configured ones). */\n providers?: () => OidcProvider[];\n /** The platform's sign-in window. */\n host?: () => OidcHost | undefined;\n /** Provider keys, cached across proofs. */\n jwks?: JwksCache;\n /** The UI-side fetch for GitLab's code exchange. */\n fetch?: typeof fetch;\n}\n\nexport function createOidcIdentityProvider(options: OidcIdentityOptions = {}): IdentityProofProvider<OidcEvidence> {\n const providers = options.providers ?? (() => oidcProviders());\n const host = options.host ?? currentHost;\n const configured = () => providers().filter(p => audiences(p).length > 0);\n\n const signer = (id: string, label: string, description: string, reveal: OidcReveal[]): RedirectSigner<OidcEvidence> => ({\n id, kind: \"redirect\", label, description,\n available: () => { const h = host(); return !!h && providers().some(p => isConfigured(p, h.platform)); },\n start(statement: IdentityStatement, ctx) {\n // Before any await: the popup and the extension's permission prompt need the click's user activation.\n const h = host();\n const opening = h?.open();\n return (async () => {\n if (!h || !opening) throw new Error(\"Signing in with a provider is not available here.\");\n const provider = providerForIssuer(statement.binding.subject, providers());\n if (!provider || !isConfigured(provider, h.platform)) { void opening.then(w => w.close(), () => {}); throw new Error(\"This provider is not set up in this Ghostly build.\"); }\n ctx.onProgress(`Sign in with ${provider.name} in the window that opened.`);\n const token = await signInForProof({\n provider, platform: h.platform, nonce: statement.id, window: await opening, signal: ctx.signal, fetch: options.fetch,\n // What the provider cannot put in its token is not asked for.\n reveal: reveal.filter(r => provider.reveal[r]),\n });\n return { token };\n })();\n },\n });\n\n return {\n id: \"oidc\",\n label: \"Account at a provider\",\n category: \"provider-attested\",\n summary: \"Sign in with Google, Microsoft, Apple…\",\n description: \"Sign in with Google, Microsoft, Apple, GitLab or Twitch: the provider vouches to Ghostly, with a signed token, that the account is yours.\",\n limits: \"Attested by the provider, not by a key you hold: your contact trusts that company.\",\n platforms: [\"web\", \"extension\", \"desktop\"] satisfies IdentityPlatform[],\n subject: {\n label: \"Provider\",\n help: \"Your contact sees your account’s identifier at that provider, plus what you choose to add. GitLab also adds the groups you belong to directly; Apple never gives Ghostly your email.\",\n get options() { return configured().map(p => ({ value: p.issuer, label: p.name })); },\n normalize(input: string) {\n const provider = providerForIssuer(input.trim(), configured());\n if (!provider) throw new Error(\"Choose a provider\");\n return provider.issuer;\n },\n short: subject => providerForIssuer(subject, providers())?.name ?? hostOf(subject),\n },\n // Providers rotate their signing keys within weeks (Google about every two); a contact can only\n // check a token while its key is still published, so a proof is short-lived.\n validity: { defaultDays: 7, maxDays: 14 },\n privacy: \"Your contact’s app downloads the provider’s public keys (for example from www.googleapis.com), so the provider sees that some app checked a sign-in, not whose. Your contact sees the account identifier and what you chose to share.\",\n signers: [\n signer(\"oidc-account\", \"Sign in: share the account only\", \"The token carries the provider’s identifier for your account, nothing else.\", []),\n signer(\"oidc-email\", \"Sign in: share the account and email\", \"Adds your email address, where the provider offers it.\", [\"email\"]),\n signer(\"oidc-profile\", \"Sign in: share the account, email and name\", \"Adds your email address and name or username, where the provider offers them.\", [\"email\", \"profile\"]),\n ],\n parseEvidence(raw) {\n const e = raw as OidcEvidence;\n if (!raw || typeof raw !== \"object\" || Array.isArray(raw) || Object.keys(raw).join(\",\") !== \"token\" ||\n typeof e.token !== \"string\" || e.token.length > MAX_ID_TOKEN_LENGTH || !TOKEN.test(e.token)) throw new Error(\"Invalid sign-in token\");\n return { token: e.token };\n },\n async verify(statement, { token }, ctx) {\n const provider = providerForIssuer(statement.binding.subject, providers());\n if (!provider) throw new Error(\"This app does not know this provider\");\n const id = await verifyIdToken(token, {\n provider, nonce: statement.id, now: ctx.now, jwks: options.jwks ?? sharedJwksCache,\n fetch: async (url, { maxBytes }) => { const r = await ctx.fetch(url, { maxBytes, signal: ctx.signal }); return { status: r.status, text: r.text }; },\n issuedWithin: [statement.binding.issuedAt - IDENTITY_CLOCK_SKEW, statement.binding.issuedAt + SIGN_IN_WINDOW],\n });\n if (!SUB.test(id.subject)) throw new Error(\"The provider’s account identifier cannot be shown\");\n const attester = hostOf(provider.issuer);\n const person = id.name ?? id.username;\n const name = id.email ? (person && `${person} · ${id.email}`.length <= 64 ? `${person} · ${id.email}` : id.email) : person;\n const url = id.username && /^[A-Za-z0-9_.-]{1,64}$/.test(id.username)\n ? provider.id === \"gitlab\" ? `https://gitlab.com/${id.username}` : provider.id === \"twitch\" ? `https://www.twitch.tv/${id.username}` : undefined\n : undefined;\n return {\n // Microsoft's issuer names the account's tenant; the others are fixed.\n subject: `${provider.id === \"microsoft\" ? id.issuer : provider.issuer}#${id.subject}`,\n attester,\n source: `ID token signed by ${attester}`,\n ...(name || url ? { display: {\n ...(name ? { name } : {}), ...(url ? { url } : {}), fetchedAt: id.issuedAt,\n source: `Signed by ${provider.name}${id.email ? id.emailVerified ? `; email verified by ${provider.name}` : `; email not verified by ${provider.name}` : \"\"}`,\n } } : {}),\n };\n },\n };\n}\n\nexport const oidc = createOidcIdentityProvider();\n","import {\n DidError, DID_DOCUMENT_MAX_BYTES, resolveDidDht, DID_FILE_MAX_BYTES, didWebDocumentUrl, didWebFileUrl, didWebHost, parseDid, parseDidDocument, staticDidDocument,\n type DidDocumentView, type DidMethod,\n} from \"@ghostly/core\";\nimport type { IdentityFetch } from \"./contract\";\nimport { assertPublicHost, chosenResolver, DomainCheckError, type DohResolverId } from \"./domain\";\n\n/**\n * Network side of DID proofs: resolving a DID to the keys its document lists, under the same limits as the\n * other providers. did:key and did:jwk need no network. did:web is one HTTPS GET of its did.json (bounded,\n * no redirect, after checking that the domain's addresses are public, like the domain proof). did:dht is a\n * signed Pkarr record read from a relay (`resolveDidDht`, WISP 310-did-dht).\n *\n * What a check reveals, and to whom:\n * - did:web: the DNS-over-HTTPS resolver learns the domain; the domain's web server, run by the person\n * being checked, sees this device's IP address and when.\n * - did:dht: the Pkarr relay learns which DID was looked up, from which IP address.\n */\n\nexport interface DidResolution {\n method: DidMethod;\n document: DidDocumentView;\n /** did:web: the host, and where its did.json was read. */\n host?: string;\n documentUrl?: string;\n /** did:dht: the relay that answered. */\n relay?: string;\n}\n\nexport interface DidResolveOptions {\n fetch: IdentityFetch;\n signal?: AbortSignal;\n /** Milliseconds. */\n now?: () => number;\n resolver?: DohResolverId;\n /** did:dht resolution; tests pass their own. */\n dht?: typeof resolveDidDht;\n}\n\n/** Resolutions are shared for this long: back-to-back checks and the add dialog's preview, never a stale key for long. */\nconst CACHE_MS = 30_000;\nconst cache = new Map<string, { until: number; result: Promise<DidResolution> }>();\n/** The last document seen per DID, without expiry, for the signer's instructions (which cannot wait). */\nconst seen = new Map<string, DidResolution>();\n\n/** Tests only. */\nexport function clearDidCache(): void { cache.clear(); seen.clear(); }\n\n/** What this app last resolved for `did`, if anything: the add dialog's instructions name its keys. */\nexport const lastDidResolution = (did: string): DidResolution | undefined => seen.get(did);\n\nasync function fetchDidWeb(did: string, url: string, maxBytes: number, options: DidResolveOptions): Promise<string | undefined> {\n const host = didWebHost(did);\n try {\n await assertPublicHost(host, { fetch: options.fetch, signal: options.signal, resolver: options.resolver ?? chosenResolver().id, now: options.now });\n } catch (e) { throw e instanceof DomainCheckError ? new DidError(e.message) : e; }\n let response: Awaited<ReturnType<IdentityFetch>>;\n try {\n response = await options.fetch(url, { headers: { accept: \"application/did+json, application/json\" }, maxBytes, signal: options.signal, redirect: \"error\" });\n } catch (e) {\n if (options.signal?.aborted) throw e;\n throw new DidError(/too large/i.test(String(e)) ? `${url} is too large`\n : `${host} could not be reached. It must serve ${url} over HTTPS, without a redirect, with the header Access-Control-Allow-Origin: *`);\n }\n if (response.status === 404 || response.status === 410) return undefined;\n if (response.status >= 300 && response.status < 400) throw new DidError(`${host} answered with a redirect; it must serve exactly ${url}`);\n if (response.status !== 200) throw new DidError(`${host} answered ${response.status} for ${url}`);\n try { return new TextDecoder(\"utf-8\", { fatal: true }).decode(response.bytes); } catch { throw new DidError(`${url} is not text`); }\n}\n\nasync function resolve(did: string, method: DidMethod, options: DidResolveOptions): Promise<DidResolution> {\n if (method === \"key\" || method === \"jwk\") return { method, document: staticDidDocument(did) };\n if (method === \"web\") {\n const documentUrl = didWebDocumentUrl(did);\n const text = await fetchDidWeb(did, documentUrl, DID_DOCUMENT_MAX_BYTES, options);\n if (text === undefined) throw new DidError(`No DID document at ${documentUrl}`);\n let raw: unknown;\n try { raw = JSON.parse(text); } catch { throw new DidError(`${documentUrl} is not JSON`); }\n return { method, document: parseDidDocument(did, raw), host: didWebHost(did), documentUrl };\n }\n const found = await (options.dht ?? resolveDidDht)(did, { fetch: options.fetch, signal: options.signal });\n if (found.metadata.deactivated) throw new DidError(\"This DID is deactivated: its owner retired it\");\n return { method, document: parseDidDocument(did, found.document), relay: found.relay };\n}\n\n/** A DID's document as far as Ghostly uses it. Concurrent and back-to-back resolutions share one lookup. */\nexport async function resolveDid(input: string, options: DidResolveOptions): Promise<DidResolution> {\n const { did, method } = parseDid(input);\n const now = options.now ?? Date.now;\n const hit = cache.get(did);\n if (hit && hit.until > now()) return hit.result;\n const entry = { until: now() + CACHE_MS, result: resolve(did, method, options) };\n cache.set(did, entry);\n if (cache.size > 64) cache.delete(cache.keys().next().value!);\n try {\n const result = await entry.result;\n seen.delete(did);\n seen.set(did, result);\n if (seen.size > 16) seen.delete(seen.keys().next().value!);\n return result;\n } catch (e) {\n if (cache.get(did) === entry) cache.delete(did);\n throw e;\n }\n}\n\n/** The statement file a did:web publishes for proof `proofId`; undefined when the server says it is not there. */\nexport async function fetchDidWebFile(did: string, proofId: string, options: DidResolveOptions): Promise<string | undefined> {\n return fetchDidWeb(did, didWebFileUrl(did, proofId), DID_FILE_MAX_BYTES, options);\n}\n","import {\n checkDidJws, checkDidSignature, DidError, DID_JWS_MAX_LENGTH, didGhostlyServiceEntry, didJwsAlg, didSignedMessages, didWebFile, didWebFileNames,\n didWebFileUrl, normalizeDid, parseDid, parseDidSignaturePaste, signingKeys, staticDidDocument, strictBase64Url, toBase64Url,\n type DidCurve, type DidKey, type IdentityStatement,\n} from \"@ghostly/core\";\nimport type { ExternalToolSigner, IdentityProofProvider, PublishSigner, SignerInstructions, SubjectPreview } from \"../contract\";\nimport { fetchDidWebFile, lastDidResolution, resolveDid, type DidResolution } from \"../did\";\nimport { boundedIdentityFetch } from \"../verify\";\n\n/**\n * A decentralized identifier (W3C DID Core), draft WISP 311-did. The subject is the DID; the statement is\n * vouched for by a key its document lists under `authentication` or `assertionMethod` (a JWS or a raw\n * signature pasted back, like SSH and PGP), or, for did:web, by publishing it where the DID document lives\n * (a file beside did.json, or a service entry in it), like the domain proof.\n *\n * Methods: did:key and did:jwk (the key is the identifier: checked offline), did:dht (a signed Pkarr\n * record), did:web (HTTPS). Every check resolves the DID again, so a key removed from a did:web or did:dht\n * document stops counting at the contact's next check.\n */\n\nexport type DidEvidence =\n | { method: \"jws\"; vm: string; jws: string }\n | { method: \"sig\"; vm: string; sig: string }\n | { method: \"file\" }\n | { method: \"service\" };\n\nconst VM = /^did:[a-z0-9]+:[\\x21-\\x7e]+#[\\x21-\\x7e]+$/;\nconst SIG = /^[A-Za-z0-9_-]{86}$/;\nconst exactKeys = (raw: unknown, keys: string) => !!raw && typeof raw === \"object\" && !Array.isArray(raw) && Object.keys(raw).sort().join(\",\") === keys;\nconst validVm = (vm: unknown): vm is string => typeof vm === \"string\" && vm.length <= 640 && VM.test(vm);\n\n/** The profile's own Ghostly did:dht, refused as an external identity. The UI and the engine each set where to read it. */\nlet ownDid: () => string | undefined = () => undefined;\nexport function setOwnDidSource(source: () => string | undefined): void { ownDid = source; }\n\n/** The add dialog's checks run in the UI, with the same bounded fetch the engine uses. */\nconst uiFetch = boundedIdentityFetch({ online: () => globalThis.navigator?.onLine !== false });\n\n/** `#key-1`, shortened when a multibase fragment is long. */\nconst fragment = (id: string) => {\n const f = id.slice(id.indexOf(\"#\"));\n return f.length > 20 ? `${f.slice(0, 11)}…${f.slice(-4)}` : f;\n};\n\nfunction shortDid(did: string): string {\n if (did.length <= 30) return did;\n const { method, id } = parseDidSafe(did);\n if (method === \"web\") return `did:web:${id.split(\":\")[0]}${id.includes(\":\") ? \":…\" : \"\"}`;\n return `did:${method}:${id.slice(0, 8)}…${id.slice(-4)}`;\n}\nconst parseDidSafe = (did: string) => { try { return parseDid(did); } catch { return { did, method: did.split(\":\")[1] ?? \"\", id: did.split(\":\").slice(2).join(\":\") }; } };\n\n/** Keys the instructions can name before anything is fetched: the identifier's own, or what the preview resolved. */\nfunction knownKeys(did: string): DidKey[] {\n const { method } = parseDid(did);\n if (method === \"key\" || method === \"jwk\") return signingKeys(staticDidDocument(did));\n const seen = lastDidResolution(did);\n return seen ? signingKeys(seen.document) : [];\n}\n\nconst NOBLE: Record<DidCurve, { name: string; module: string }> = {\n Ed25519: { name: \"ed25519\", module: \"ed25519.js\" },\n secp256k1: { name: \"secp256k1\", module: \"secp256k1.js\" },\n \"P-256\": { name: \"p256\", module: \"nist.js\" },\n};\n\nfunction joseScript(statement: string, kid?: string): string {\n return [\n \"// npm install jose; then: node sign.mjs your-private-key.jwk.json\",\n \"import { readFileSync } from \\\"node:fs\\\";\",\n \"import { CompactSign, importJWK } from \\\"jose\\\";\",\n `const statement = ${JSON.stringify(statement)};`,\n \"const jwk = JSON.parse(readFileSync(process.argv[2], \\\"utf8\\\"));\",\n \"const alg = { Ed25519: \\\"EdDSA\\\", \\\"P-256\\\": \\\"ES256\\\" }[jwk.crv];\",\n \"const jws = await new CompactSign(new TextEncoder().encode(statement))\",\n ` .setProtectedHeader({ alg${kid ? `, kid: ${JSON.stringify(kid)}` : \"\"} })`,\n \" .sign(await importJWK(jwk, alg));\",\n \"console.log(jws);\",\n ].join(\"\\n\");\n}\n\nfunction nobleScript(statement: string, curve: DidCurve): string {\n const lib = NOBLE[curve];\n return [\n \"// npm install @noble/curves; then: node sign.mjs your-private-key.hex\",\n \"import { readFileSync } from \\\"node:fs\\\";\",\n `import { ${lib.name} } from \"@noble/curves/${lib.module}\";`,\n `const statement = new TextEncoder().encode(${JSON.stringify(statement)});`,\n \"const secret = Uint8Array.from(Buffer.from(readFileSync(process.argv[2], \\\"utf8\\\").trim(), \\\"hex\\\"));\",\n `console.log(Buffer.from(${lib.name}.sign(statement, secret)).toString(\"hex\"));`,\n ].join(\"\\n\");\n}\n\nfunction opensslCommand(statement: string, curve: DidCurve): string {\n const save = `printf '%s' '${statement}' > statement.txt`;\n return curve === \"Ed25519\"\n ? `${save} && openssl pkeyutl -sign -rawin -inkey key.pem -in statement.txt | xxd -p | tr -d '\\\\n'`\n : `${save} && openssl dgst -sha256 -sign key.pem statement.txt | xxd -p | tr -d '\\\\n'`;\n}\n\nconst signer: ExternalToolSigner<DidEvidence> = {\n id: \"sign\", kind: \"external-tool\", label: \"Sign with a key of the DID\",\n description: \"Sign the statement with a key your DID document lists for authentication or assertionMethod, and paste a JWS or the raw signature. The private key never enters Ghostly.\",\n instructions(statement): SignerInstructions {\n const did = statement.binding.subject;\n if (statement.text.includes(\"'\")) throw new DidError(\"Statement is not signable as plain text\");\n const keys = knownKeys(did);\n const key = keys.length === 1 ? keys[0] : undefined;\n const curve = key?.curve ?? keys[0]?.curve ?? \"Ed25519\";\n const which = keys.length ? keys.map(k => `${fragment(k.id)} (${k.curve})`).join(\", \") : \"a key listed under authentication or assertionMethod\";\n return {\n steps: [\n { text: `Sign this statement with the private key of ${which}. It is one line, with no newline at the end:`, copy: statement.text },\n ...(curve === \"secp256k1\" ? [] : [{ text: `With jose (Node.js 20 or later), a JWS: save this as sign.mjs and run it with your ${curve} private key as a JWK file.`, copy: joseScript(statement.text, key?.id) }]),\n { text: `With @noble/curves (Node.js), a raw signature: save this as sign.mjs and run it with your ${curve} private key in hex.`, copy: nobleScript(statement.text, curve) },\n { text: `Or with OpenSSL 3 and your ${curve} key in PEM:`, copy: opensslCommand(statement.text, curve) },\n { text: `Paste what it prints: a compact JWS (${didJwsAlg(curve)}, attached or detached), or the signature in hex or base64. If your DID lists several keys, you may write the key id first: #key-1 <signature>.` },\n ],\n paste: { label: \"JWS or signature\", placeholder: \"eyJhbGciOi… or the signature in hex\", multiline: true },\n };\n },\n async parse(pasted, statement) {\n const did = statement.binding.subject;\n const paste = parseDidSignaturePaste(pasted);\n const { document } = await resolveDid(did, { fetch: uiFetch });\n const messages = didSignedMessages(statement.text);\n if (paste.kind === \"jws\") return { method: \"jws\", vm: checkDidJws(paste.jws, messages, document).key.id, jws: paste.jws };\n const check = checkDidSignature(paste.signature, messages, document, paste.vm);\n return { method: \"sig\", vm: check.key.id, sig: toBase64Url(check.signature) };\n },\n};\n\nconst webOnly = (statement: IdentityStatement) => {\n const did = statement.binding.subject;\n if (parseDid(did).method !== \"web\") throw new DidError(\"Publishing works for did:web only: sign the statement instead\");\n return did;\n};\n\nconst fileSigner: PublishSigner<DidEvidence> = {\n id: \"file\", kind: \"publish\", label: \"Publish a file beside did.json\",\n description: \"did:web only: upload a small file next to your did.json. Contacts' apps download both from your server.\",\n instructions(statement) {\n const did = webOnly(statement);\n return { steps: [\n { text: \"Create this file on your web server, beside your did.json:\", copy: didWebFileUrl(did, statement.id) },\n { text: \"With exactly this content:\", copy: didWebFile(did, statement.text) },\n { text: \"Serve it over HTTPS at exactly that address, without any redirect, and with this header so browsers may read it (your did.json needs it too):\", copy: \"Access-Control-Allow-Origin: *\" },\n { text: \"Then press Check. The file stays public while the proof is in use; deleting it withdraws this proof from every contact at their next check.\" },\n ] };\n },\n evidence: () => ({ method: \"file\" }),\n};\n\nconst serviceSigner: PublishSigner<DidEvidence> = {\n id: \"service\", kind: \"publish\", label: \"Add a service to did.json\",\n description: \"did:web only: add one entry to the service list of your did.json. Contacts' apps read it there.\",\n instructions(statement) {\n const did = webOnly(statement);\n const seen = lastDidResolution(did);\n return { steps: [\n { text: `Add this entry to the \"service\" list of your did.json${seen?.documentUrl ? ` (${seen.documentUrl})` : \"\"}:`, copy: JSON.stringify(didGhostlyServiceEntry(did, statement.binding.key, statement.id), null, 2) },\n { text: \"Then press Check. Removing the entry withdraws this proof from every contact at their next check.\" },\n ] };\n },\n evidence: () => ({ method: \"service\" }),\n};\n\nfunction previewOf(r: DidResolution): SubjectPreview {\n const keys = signingKeys(r.document);\n const unusable = r.document.keys.length - keys.length + r.document.skipped;\n if (!keys.length && r.method !== \"web\") throw new DidError(\"This DID lists no Ed25519, secp256k1 or P-256 key for authentication or assertionMethod, so it cannot sign\");\n const facts: { label: string; value: string }[] = [{ label: \"Method\", value: `did:${r.method}${r.method === \"key\" || r.method === \"jwk\" ? \", the key is the identifier\" : \"\"}` }];\n if (r.host) facts.push({ label: \"Domain\", value: r.host });\n if (r.documentUrl) facts.push({ label: \"Document\", value: r.documentUrl });\n if (r.relay) facts.push({ label: \"Relay\", value: r.relay.replace(/^https:\\/\\//, \"\") });\n facts.push({ label: keys.length === 1 ? \"Key\" : \"Keys\", value: keys.length ? keys.map(k => `${fragment(k.id)} (${k.curve})`).join(\", \") : \"none that can sign: publish instead\" });\n if (unusable > 0) facts.push({ label: \"Not usable\", value: `${unusable} ${unusable === 1 ? \"key\" : \"keys\"}: another curve, or not for authentication or assertionMethod` });\n return { facts, signers: r.method === \"web\" ? [...(keys.length ? [\"sign\"] : []), \"file\", \"service\"] : [\"sign\"] };\n}\n\nexport const did: IdentityProofProvider<DidEvidence> = {\n id: \"did\",\n label: \"DID\",\n category: \"self-custodied\",\n summary: \"did:key, did:jwk, did:dht or did:web\",\n description: \"Proves you control a decentralized identifier (W3C DID): sign the statement with a key its DID document lists, or, for did:web, publish it beside your did.json.\",\n limits: \"Does not prove who controls the DID, only that whoever added the proof held one of its keys (or its website).\",\n platforms: [\"web\", \"extension\", \"desktop\"],\n advanced: true,\n experimental: true,\n subject: {\n label: \"DID\", placeholder: \"did:key:z6Mk… or did:web:example.com\",\n help: \"did:key, did:jwk, did:dht or did:web. Contacts see the whole DID, and the key that signed.\",\n normalize(input) {\n const canonical = normalizeDid(input);\n if (canonical === ownDid()) throw new DidError(\"That is this profile's own Ghostly DID: your contacts already see your Ghostly identity. Add another DID.\");\n return canonical;\n },\n short: shortDid,\n preview: async (subject, { signal }) => previewOf(await resolveDid(subject, { fetch: uiFetch, signal })),\n },\n // A proven DID is already a URI: the profile's own did:dht may list it under alsoKnownAs.\n publicUri: subject => subject,\n validity: { defaultDays: 90, maxDays: 365 },\n // A did:web or did:dht document can drop a key, or the file be deleted: contacts can check again.\n recheck: { afterSeconds: 86_400 },\n privacy: \"did:key and did:jwk: nothing, checked on this device. did:web: the contact's app asks its DNS-over-HTTPS resolver for your domain and downloads your did.json, \" +\n \"so your web server sees the contact's IP address. did:dht: a Pkarr relay learns which DID was looked up.\",\n signers: [signer, fileSigner, serviceSigner],\n parseEvidence(raw) {\n const e = raw as Record<string, unknown>;\n if (exactKeys(raw, \"jws,method,vm\") && e.method === \"jws\" && typeof e.jws === \"string\" && e.jws.length <= DID_JWS_MAX_LENGTH && validVm(e.vm)) return { method: \"jws\", vm: e.vm, jws: e.jws };\n if (exactKeys(raw, \"method,sig,vm\") && e.method === \"sig\" && typeof e.sig === \"string\" && SIG.test(e.sig) && validVm(e.vm)) return { method: \"sig\", vm: e.vm, sig: e.sig };\n if (exactKeys(raw, \"method\") && (e.method === \"file\" || e.method === \"service\")) return { method: e.method };\n throw new Error(\"That is not DID proof evidence\");\n },\n async verify(statement, evidence, ctx) {\n const subject = statement.binding.subject;\n const { method } = parseDid(subject);\n if (normalizeDid(subject) !== subject) throw new DidError(\"The DID is not in canonical form\");\n if ((evidence.method === \"file\" || evidence.method === \"service\") && method !== \"web\") throw new DidError(\"Only a did:web can publish its proof\");\n const options = { fetch: ctx.fetch, signal: ctx.signal, now: () => ctx.now * 1000 };\n const { document, host } = await resolveDid(subject, options);\n const messages = didSignedMessages(statement.text);\n if (evidence.method === \"jws\" || evidence.method === \"sig\") {\n const check = evidence.method === \"jws\"\n ? checkDidJws(evidence.jws, messages, document)\n : checkDidSignature(strictBase64Url(evidence.sig), messages, document, evidence.vm);\n if (check.key.id !== evidence.vm) throw new DidError(\"Signed by another key than the proof names\");\n return { subject, source: `did:${method} · ${evidence.method === \"jws\" ? `${check.alg} JWS` : `${check.alg} signature`} by ${fragment(check.key.id)}` };\n }\n if (evidence.method === \"service\") {\n if (!document.services.some(s => s.proof === statement.id && s.key === statement.binding.key)) throw new DidError(\"The did.json has no Ghostly service for this proof: it was removed, or not added yet\");\n return { subject, source: `did:web · service in the did.json of ${host}` };\n }\n const text = await fetchDidWebFile(subject, statement.id, options);\n if (text === undefined) throw new DidError(`${didWebFileUrl(subject, statement.id)} was not found: it was deleted, or not uploaded yet`);\n if (!didWebFileNames(text, subject, statement.text)) throw new DidError(`${didWebFileUrl(subject, statement.id)} is not the statement of this proof`);\n return { subject, source: `did:web · statement file beside the did.json of ${host}` };\n },\n};\n","import { atprotoDidMethod, isAtprotoDid, parseAtprotoDidDocument, txtValue, type AtprotoDidDocument } from \"@ghostly/core\";\nimport type { IdentityFetch } from \"../contract\";\nimport { assertPublicHost, dohQuery, type DohResolverId } from \"../domain\";\n\n/**\n * AT Protocol identity resolution, the network half: a DID to its document (the PLC directory for\n * did:plc, the host's `/.well-known/did.json` for did:web), and a handle to a DID (DNS TXT\n * `_atproto.<handle>` through the person's DNS-over-HTTPS resolver, then\n * `https://<handle>/.well-known/atproto-did`). Every request goes through the contract's bounded\n * fetch: HTTPS only, no redirects, a size cap, a time-out. URLs are built here from the DID or the\n * handle, never taken from a proof.\n */\n\nexport const PLC_DIRECTORY = \"https://plc.directory\";\nconst TEST_HOST = /^https:\\/\\/[a-z0-9-]+(\\.[a-z0-9-]+)*\\.test$/;\n/** DID documents are small; a large answer is not one. */\nexport const DID_DOCUMENT_MAX_BYTES = 32 * 1024;\nconst HANDLE_DID_MAX_BYTES = 2048;\n\n/**\n * The PLC directory. A build made for the e2e suite with VITE_ATPROTO_TEST_PLC may point it at a\n * `.test` host (a name reserved for testing); a release build always uses plc.directory.\n */\nexport function plcDirectory(): string {\n try {\n const value = import.meta.env?.VITE_ATPROTO_TEST_PLC as string | undefined;\n return value && TEST_HOST.test(value) ? value : PLC_DIRECTORY;\n } catch { return PLC_DIRECTORY; }\n}\n\nexport interface AtprotoResolveOptions {\n fetch: IdentityFetch;\n signal?: AbortSignal;\n /** The DoH resolver for handles (the person's choice, as for domains). */\n resolver?: DohResolverId;\n /** The PLC directory; plcDirectory() by default. */\n plc?: string;\n}\n\n/** Where a DID's document lives. */\nexport function didDocumentUrl(did: string, plc = plcDirectory()): string {\n const method = atprotoDidMethod(did);\n if (method === \"plc\") return `${plc}/${did}`;\n if (method === \"web\") return `https://${did.slice(\"did:web:\".length)}/.well-known/did.json`;\n throw new Error(\"Not an AT Protocol DID\");\n}\n\n/**\n * Refuses a host the account chose (a did:web host, the PDS its document names) that points to a private\n * network address: a contact's app never knocks on its own local network for someone else's proof.\n */\nexport async function assertPublicServer(host: string, options: AtprotoResolveOptions): Promise<void> {\n try { await assertPublicHost(host, { fetch: options.fetch, signal: options.signal, resolver: options.resolver }); }\n catch (e) { throw new Error(`The account's server ${host} was not contacted: ${e instanceof Error ? e.message : String(e)}`); } // eslint-disable-line preserve-caught-error -- The domain check's message says why; ES2020 has no Error.cause.\n}\n\n/** A DID's document, checked (parseAtprotoDidDocument). Throws a message people can read. */\nexport async function resolveAtprotoDid(did: string, options: AtprotoResolveOptions): Promise<AtprotoDidDocument> {\n const url = didDocumentUrl(did, options.plc);\n if (atprotoDidMethod(did) === \"web\") await assertPublicServer(did.slice(\"did:web:\".length), options);\n let response: Awaited<ReturnType<IdentityFetch>>;\n try {\n response = await options.fetch(url, { headers: { accept: \"application/did+ld+json, application/json\" }, maxBytes: DID_DOCUMENT_MAX_BYTES, signal: options.signal, redirect: \"error\" });\n } catch (e) {\n // eslint-disable-next-line preserve-caught-error -- A message people can act on; the library's error stays out of the UI (ES2020 has no Error.cause).\n throw new Error(/too large/i.test(String(e)) ? \"The account's DID document is too large\" : \"The account's DID could not be resolved\");\n }\n if (response.status === 404 || response.status === 410) throw new Error(\"The account's DID is not registered, or was deactivated\");\n if (response.status !== 200) throw new Error(\"The account's DID could not be resolved\");\n let doc: unknown;\n try { doc = JSON.parse(response.text); } catch { throw new Error(\"The account's DID document is malformed\"); }\n return parseAtprotoDidDocument(doc, did);\n}\n\n/**\n * The DID a handle points to, or null when it points nowhere (or to two different DIDs). DNS first,\n * then HTTPS, as the handle spec says. The HTTPS lookup first checks the handle's addresses are public,\n * so a handle cannot make a contact's app knock on its local network.\n */\nexport async function resolveAtprotoHandle(handle: string, options: AtprotoResolveOptions): Promise<string | null> {\n const lookup = { fetch: options.fetch, signal: options.signal, resolver: options.resolver };\n try {\n const answer = await dohQuery(`_atproto.${handle}`, \"TXT\", lookup);\n const dids = new Set<string>();\n for (const a of answer.answers) {\n if (a.type !== 16) continue;\n let text: string;\n try { text = txtValue(a.data); } catch { continue; }\n if (text.startsWith(\"did=\")) dids.add(text.slice(4));\n }\n if (dids.size > 1) return null;\n const [did] = dids;\n if (did) return isAtprotoDid(did) ? did : null;\n } catch { /* The resolver failed: try HTTPS. */ }\n try {\n await assertPublicHost(handle, lookup);\n const response = await options.fetch(`https://${handle}/.well-known/atproto-did`, { headers: { accept: \"text/plain\" }, maxBytes: HANDLE_DID_MAX_BYTES, signal: options.signal, redirect: \"error\" });\n if (response.status !== 200) return null;\n const did = response.text.trim();\n return isAtprotoDid(did) ? did : null;\n } catch { return null; }\n}\n\n/**\n * The handle to show for a DID: the one its document claims, and only if that handle resolves back to\n * the same DID. Otherwise nothing: the card shows the DID. Never throws.\n */\nexport async function verifiedAtprotoHandle(doc: AtprotoDidDocument, options: AtprotoResolveOptions): Promise<string | undefined> {\n if (!doc.handle) return undefined;\n try { return (await resolveAtprotoHandle(doc.handle, options)) === doc.did ? doc.handle : undefined; } catch { return undefined; }\n}\n","import {\n ATPROTO_DECLARED_SCOPE, ATPROTO_FULL_SCOPE, ATPROTO_PROOF_COLLECTION, ATPROTO_PROOF_SCOPE, atprotoProofRecord, atprotoProofRkey,\n type AtprotoDidDocument, type IdentityStatement,\n} from \"@ghostly/core\";\nimport type { IdentityFetch, IdentityPlatform } from \"../contract\";\nimport { resolveAtprotoDid, resolveAtprotoHandle, type AtprotoResolveOptions } from \"./resolve\";\n\n/**\n * The prover's half of AT Protocol proofs: AT Protocol OAuth (PAR + PKCE + DPoP, through the official\n * `@atproto/oauth-client`) against the person's own server, then one record written or deleted. Every\n * flow gets a fresh client whose stores live in memory only: the DPoP key (a non-extractable WebCrypto\n * key), the PKCE verifier and the tokens exist for this one flow, are never logged or persisted, and the\n * session is revoked (signOut) as soon as the record is written. Nothing is kept for later: removing\n * the proof asks the person again.\n *\n * The client is identified by the URL of its metadata document (no registration with anyone):\n * https://ghostly.tools/oauth/client-metadata.json, which is ATPROTO_CLIENT_METADATA verbatim\n * (apps/website/public/oauth/client-metadata.json; a unit test keeps the two equal). A web app served from\n * a loopback address (development, the e2e suite) uses AT Protocol's `http://localhost` development\n * client instead, which needs no document.\n */\n\nexport const ATPROTO_CLIENT_ID = \"https://ghostly.tools/oauth/client-metadata.json\";\n/** The extension's id in the Chrome Web Store: its chromiumapp.org redirect is registered. Unpacked builds have another id. */\nexport const EXTENSION_ID = \"nbedaagicniejlmfcncndfjcejaidbcf\";\nexport const ATPROTO_REDIRECTS = {\n /** The web app's static callback page (shared with OpenID Connect): the answer stays in the fragment. */\n web: \"https://app.ghostly.tools/oidc-callback.html\",\n /** chrome.identity.launchWebAuthFlow catches it without loading it. */\n extension: `https://${EXTENSION_ID}.chromiumapp.org/atproto`,\n /** The desktop app's one-shot listener on 127.0.0.1; the port is not part of the match. */\n desktop: \"http://127.0.0.1/oidc-callback\",\n} as const;\n\n/**\n * `native` because the desktop app's loopback redirect is only allowed for native clients; HTTPS\n * redirects (web, extension) are allowed for them too. No `refresh_token` grant: Ghostly never keeps\n * a session, so it never gets a refresh token to lose.\n */\nexport const ATPROTO_CLIENT_METADATA = {\n client_id: ATPROTO_CLIENT_ID,\n client_name: \"Ghostly\",\n client_uri: \"https://ghostly.tools\",\n logo_uri: \"https://ghostly.tools/icon-192.png\",\n application_type: \"native\",\n grant_types: [\"authorization_code\"],\n response_types: [\"code\"],\n scope: ATPROTO_DECLARED_SCOPE,\n token_endpoint_auth_method: \"none\",\n dpop_bound_access_tokens: true,\n redirect_uris: [ATPROTO_REDIRECTS.web, ATPROTO_REDIRECTS.extension, ATPROTO_REDIRECTS.desktop],\n} as const;\n\n/** AT Protocol's development client: no document, loopback redirects only, any port. */\nexport function loopbackClientMetadata(redirectUri: string) {\n const registered = new URL(redirectUri);\n registered.port = \"\";\n const id = `http://localhost?${new URLSearchParams({ redirect_uri: registered.href, scope: ATPROTO_DECLARED_SCOPE })}`;\n return {\n client_id: id, application_type: \"native\", grant_types: [\"authorization_code\"], response_types: [\"code\"], scope: ATPROTO_DECLARED_SCOPE,\n token_endpoint_auth_method: \"none\", dpop_bound_access_tokens: true, redirect_uris: [registered.href],\n } as const;\n}\n\nconst isLoopback = (url: string) => { try { const u = new URL(url); return u.protocol === \"http:\" && (u.hostname === \"127.0.0.1\" || u.hostname === \"[::1]\"); } catch { return false; } };\nconst withoutPort = (url: string) => { const u = new URL(url); u.port = \"\"; return u.href; };\n\n/**\n * The client for a redirect: the published one, or the development client on a loopback web app. A\n * loopback redirect is registered without its port (servers match any port), but the library checks\n * the exact address locally, so the one in use is added to the copy it gets; the server only ever sees\n * the client id and reads the registered addresses itself.\n */\nexport function clientMetadataFor(platform: IdentityPlatform, redirectUri: string) {\n const base = platform === \"web\" && isLoopback(redirectUri) ? loopbackClientMetadata(redirectUri) : ATPROTO_CLIENT_METADATA;\n const registered = base.redirect_uris as readonly string[];\n const matches = registered.includes(redirectUri) || (isLoopback(redirectUri) && registered.includes(withoutPort(redirectUri)));\n if (!matches) throw new Error(\"This Ghostly build cannot sign in to an AT Protocol server here (its return address is not registered).\");\n return { ...base, redirect_uris: [...new Set([redirectUri, ...registered])] };\n}\n\n/** How a platform shows the server's page and hands back where it redirected. */\nexport interface AtprotoWindow {\n /** The redirect URI for this platform, as used in the request (the desktop's carries its port). */\n redirectUri: string;\n /** Opens `url`; resolves with the full address the server redirected to, carrying `state`. */\n authorize(url: string, state: string, signal: AbortSignal): Promise<string>;\n /** Releases the window or listener, answered or not. */\n close(): void;\n}\n\nexport interface AtprotoHost {\n platform: IdentityPlatform;\n /** Call it straight from the click: the web app opens its popup before anything is awaited. */\n open(): Promise<AtprotoWindow>;\n}\n\n/** Which permission to ask for: only Ghostly's records, or the whole repository (older servers). */\nexport type AtprotoAccess = \"proof-records\" | \"full\";\nexport const scopeFor = (access: AtprotoAccess) => (access === \"full\" ? ATPROTO_FULL_SCOPE : ATPROTO_PROOF_SCOPE);\n\n/** The server refused the narrow permission: it predates granular scopes. */\nexport class AtprotoScopeError extends Error {}\n\n/** The account a handle (or a DID typed as is) names, with its document. */\nexport async function lookupAtprotoAccount(input: string, options: AtprotoResolveOptions & { normalize(input: string): string | null }): Promise<AtprotoDidDocument> {\n const value = input.trim();\n if (value.startsWith(\"did:\")) return resolveAtprotoDid(value, options);\n const handle = options.normalize(value);\n if (!handle) throw new Error(\"That is not a handle (like alice.bsky.social)\");\n const did = await resolveAtprotoHandle(handle, options);\n if (!did) throw new Error(`${handle} does not point to an AT Protocol account`);\n const doc = await resolveAtprotoDid(did, options);\n if (doc.handle !== handle) throw new Error(`${handle} points to an account that names another handle`);\n return doc;\n}\n\nexport interface AtprotoFlowOptions {\n platform: IdentityPlatform;\n window: AtprotoWindow;\n account: AtprotoDidDocument;\n access: AtprotoAccess;\n signal: AbortSignal;\n /** The resolution the library uses: the same bounded lookups as the verifier. */\n resolve: AtprotoResolveOptions;\n onProgress(message: string): void;\n /** The HTTP client for the OAuth requests and the record (the page's fetch). */\n fetch?: typeof fetch;\n}\n\nconst MESSAGES: Record<string, string> = {\n access_denied: \"You declined on your server. Nothing was published.\",\n login_required: \"Log in on your server and try again.\",\n consent_required: \"Your server needs your approval to continue.\",\n};\n\n/** The redirect's parameters: the fragment (Ghostly asks for fragment responses), then the query. */\nexport function callbackParams(redirected: string, redirectUri: string): URLSearchParams {\n const url = new URL(redirected), expected = new URL(redirectUri);\n if (url.origin !== expected.origin || url.pathname !== expected.pathname) throw new Error(\"Sign-in returned to an unexpected address.\");\n const params = new URLSearchParams(url.hash.slice(1));\n for (const [k, v] of new URLSearchParams(url.search)) if (!params.has(k)) params.set(k, v);\n return params;\n}\n\nconst pdsHost = (pds: string) => new URL(pds).host;\n\n/** Runs OAuth for `account`, hands `work` a session scoped to this flow, and always revokes it afterwards. */\nasync function withSession<T>(options: AtprotoFlowOptions, work: (session: { did: string; fetchHandler(path: string, init?: RequestInit): Promise<Response> }) => Promise<T>): Promise<T> {\n const { window: w, account, signal } = options;\n signal.throwIfAborted();\n const [{ OAuthClient }, { WebcryptoKey }] = await Promise.all([import(\"@atproto/oauth-client\"), import(\"@atproto/jwk-webcrypto\")]);\n const states = new Map<string, unknown>(), sessions = new Map<string, unknown>();\n let state: string | undefined;\n const memory = <V>(map: Map<string, V>, onSet?: (key: string) => void) => ({\n async get(key: string) { return map.get(key); },\n async set(key: string, value: V) { onSet?.(key); map.set(key, value); },\n async del(key: string) { map.delete(key); },\n });\n const client = new OAuthClient({\n responseMode: \"fragment\",\n clientMetadata: clientMetadataFor(options.platform, w.redirectUri) as never,\n stateStore: memory(states, key => { state = key; }) as never,\n sessionStore: memory(sessions) as never,\n runtimeImplementation: {\n createKey: algs => WebcryptoKey.generate(algs),\n getRandomValues: length => crypto.getRandomValues(new Uint8Array(length)),\n digest: async (data, { name }) => new Uint8Array(await crypto.subtle.digest(`SHA-${name.slice(3)}`, data)),\n },\n // The same lookups the verifier makes, bounded, through the person's DoH resolver: no third-party handle resolver.\n identityResolver: {\n async resolve(input: string) {\n const did = input.startsWith(\"did:\") ? input : await resolveAtprotoHandle(input, options.resolve);\n if (!did) throw new Error(\"The account could not be resolved\");\n const doc = await fetchDidJson(did, options.resolve);\n return { did, didDoc: doc.json, handle: doc.parsed.handle ?? \"handle.invalid\" } as never;\n },\n },\n fetch: options.fetch ?? globalThis.fetch.bind(globalThis),\n allowHttp: false,\n });\n let url: URL;\n try {\n url = await client.authorize(account.did, { scope: scopeFor(options.access), redirect_uri: w.redirectUri as never, signal });\n } catch (e) {\n const text = String((e as { error?: string })?.error ?? (e as Error)?.message ?? e);\n if (/invalid_scope/i.test(text) || /scope/i.test(String((e as { errorDescription?: string })?.errorDescription ?? \"\"))) throw new AtprotoScopeError(\n \"Your server does not offer a permission limited to Ghostly's records: it only offers full access to your account. Choose \\\"Your server, full access\\\" to continue anyway; Ghostly still only writes this one record.\");\n // eslint-disable-next-line preserve-caught-error -- A message people can act on; the library's error stays out of the UI (ES2020 has no Error.cause).\n throw new Error(`Your server (${pdsHost(account.pds)}) refused to start the sign-in.`);\n }\n if (!state) throw new Error(\"The sign-in could not start.\");\n options.onProgress(`Approve on ${pdsHost(account.pds)} in the window that opened…`);\n const redirected = await w.authorize(url.href, state, signal);\n signal.throwIfAborted();\n const params = callbackParams(redirected, w.redirectUri);\n const error = params.get(\"error\");\n if (error) throw new Error(MESSAGES[error] ?? \"Your server refused the sign-in.\");\n let session: Awaited<ReturnType<typeof client.callback>>[\"session\"];\n try { ({ session } = await client.callback(params, { redirect_uri: w.redirectUri as never })); }\n catch { throw new Error(\"The sign-in could not be completed. Nothing was published.\"); }\n try {\n if (session.did !== account.did) throw new Error(\"You logged in to another account than the one you typed. Nothing was published.\");\n return await work(session);\n } finally {\n // Revokes the tokens on the server; the DPoP key and verifier go out of scope with the client.\n await session.signOut().catch(() => {});\n sessions.clear(); states.clear();\n }\n}\n\n/** A DID's document, checked, and the JSON the library reads its services from. */\nasync function fetchDidJson(did: string, options: AtprotoResolveOptions): Promise<{ parsed: AtprotoDidDocument; json: unknown }> {\n let json: unknown;\n const recording: IdentityFetch = async (url, init) => {\n const response = await options.fetch(url, init);\n try { json = JSON.parse(response.text); } catch { /* resolveAtprotoDid says so */ }\n return response;\n };\n const parsed = await resolveAtprotoDid(did, { ...options, fetch: recording });\n return { parsed, json };\n}\n\nasync function xrpcError(response: Response): Promise<string> {\n const body = await response.json().catch(() => ({})) as { error?: string; message?: string };\n return `${body.error ?? response.status}${body.message ? `: ${body.message}` : \"\"}`;\n}\n\n/** Writes the proof record (rkey = the proof key) and returns once the server has it. */\nexport async function publishAtprotoProof(statement: IdentityStatement, options: AtprotoFlowOptions): Promise<void> {\n await withSession(options, async session => {\n options.onProgress(`Publishing the proof on ${pdsHost(options.account.pds)}…`);\n const response = await session.fetchHandler(\"/xrpc/com.atproto.repo.createRecord\", {\n method: \"POST\", headers: { \"content-type\": \"application/json\" },\n body: JSON.stringify({ repo: session.did, collection: ATPROTO_PROOF_COLLECTION, rkey: atprotoProofRkey(statement.binding.key), record: atprotoProofRecord(statement, new Date()) }),\n });\n if (response.status === 403) throw new AtprotoScopeError(\"Your server did not let Ghostly write its record. Choose \\\"Your server, full access\\\" to try again.\");\n if (!response.ok) throw new Error(`Your server did not publish the record (${await xrpcError(response)}).`);\n });\n}\n\n/** Deletes the proof record. Deleting a record that is not there succeeds. */\nexport async function unpublishAtprotoProof(proofKey: string, options: AtprotoFlowOptions): Promise<void> {\n await withSession(options, async session => {\n options.onProgress(`Deleting the record on ${pdsHost(options.account.pds)}…`);\n const response = await session.fetchHandler(\"/xrpc/com.atproto.repo.deleteRecord\", {\n method: \"POST\", headers: { \"content-type\": \"application/json\" },\n body: JSON.stringify({ repo: session.did, collection: ATPROTO_PROOF_COLLECTION, rkey: atprotoProofRkey(proofKey) }),\n });\n if (response.status === 403) throw new AtprotoScopeError(\"Your server did not let Ghostly delete its record.\");\n if (!response.ok) throw new Error(`Your server did not delete the record (${await xrpcError(response)}).`);\n });\n}\n","import {\n ATPROTO_PROOF_COLLECTION, AtprotoProofError, atprotoProofRkey, checkAtprotoProofRecord, isAtprotoDid, normalizeAtprotoHandle, shortAtprotoDid,\n verifyAtprotoRecordProof,\n} from \"@ghostly/core\";\nimport type { IdentityFetch, IdentityProofProvider, InAppSigner, SignerContext } from \"../contract\";\nimport { getBrowserHost } from \"../../host\";\nimport { boundedIdentityFetch } from \"../verify\";\nimport { chosenResolver } from \"../domain\";\nimport { lookupAtprotoAccount, publishAtprotoProof, unpublishAtprotoProof, type AtprotoAccess, type AtprotoHost, AtprotoScopeError } from \"../atproto/oauth\";\nimport { assertPublicServer, resolveAtprotoDid, verifiedAtprotoHandle, type AtprotoResolveOptions } from \"../atproto/resolve\";\n\n/**\n * Identity proofs for an AT Protocol account (Bluesky, or any PDS): the account publishes, in its own\n * repository, one record of the collection `tools.ghostly.proof` carrying the statement, keyed by the\n * proof key. Publishing takes an OAuth approval on the person's server that lets Ghostly create and\n * delete records of that collection only. A contact's app checks it without logging in: the DID\n * document (PLC directory or did:web) names the signing key and the server; the server's\n * `com.atproto.sync.getRecord` answer is a CAR whose commit must be signed by that key and whose tree\n * must lead to the record. The handle is shown only when it resolves back to the DID.\n *\n * The subject is the DID (it outlives handles). Evidence is empty: everything is fetched from the\n * account's own repository, so deleting the record (removing the proof here does) makes re-checks fail.\n * See docs/wisps/312-atproto.md and docs/lexicons/tools.ghostly.proof.json.\n */\nexport type AtprotoEvidence = Record<string, never>;\n\n/** The proof CAR: a commit, the tree path and one small record. Real repositories' paths are a few KiB. */\nexport const RECORD_PROOF_MAX_BYTES = 256 * 1024;\n\nfunction currentHost(): AtprotoHost | undefined {\n try { return getBrowserHost().atproto; } catch { return undefined; }\n}\n\nconst hostOf = (url: string) => new URL(url).host;\n\nexport interface AtprotoIdentityOptions {\n /** The platform's sign-in window. */\n host?: () => AtprotoHost | undefined;\n /** The UI side's network for looking the account up before signing in. */\n fetch?: IdentityFetch;\n /** The page's fetch for OAuth and the record (tests pass a fake server). */\n oauthFetch?: typeof fetch;\n /** The PLC directory (tests). */\n plc?: string;\n}\n\nexport function createAtprotoIdentityProvider(options: AtprotoIdentityOptions = {}): IdentityProofProvider<AtprotoEvidence> {\n const host = options.host ?? currentHost;\n const uiFetch = () => options.fetch ?? boundedIdentityFetch({ online: () => true });\n const resolveOptions = (fetch: IdentityFetch, signal?: AbortSignal): AtprotoResolveOptions =>\n ({ fetch, signal, resolver: chosenResolver().id, ...(options.plc ? { plc: options.plc } : {}) });\n\n /** Opens the window from the click, looks the account up, then runs OAuth for `work`; always closes the window. */\n function signIn<T>(input: string, access: AtprotoAccess, ctx: SignerContext, work: (flow: Parameters<typeof publishAtprotoProof>[1]) => Promise<T>): Promise<T> {\n // Before any await: the web app's popup needs the click's user activation.\n const h = host();\n const opening = h?.open();\n return (async () => {\n if (!h || !opening) throw new Error(\"Signing in to an AT Protocol server is not available here.\");\n const w = await opening;\n try {\n const resolve = resolveOptions(uiFetch(), ctx.signal);\n ctx.onProgress(`Looking up ${input.trim()}…`);\n const account = await lookupAtprotoAccount(input, { ...resolve, normalize: normalizeAtprotoHandle });\n ctx.signal.throwIfAborted();\n return await work({ platform: h.platform, window: w, account, access, signal: ctx.signal, resolve, onProgress: ctx.onProgress, fetch: options.oauthFetch });\n } finally { w.close(); }\n })();\n }\n\n const signer = (id: string, access: AtprotoAccess, label: string, description: string): InAppSigner<AtprotoEvidence> => ({\n id, kind: \"in-app\", label, description,\n action: access === \"full\" ? \"Continue on your server (full access)\" : \"Continue on your server\",\n available: () => !!host(),\n fields: [{\n name: \"handle\", label: \"Handle\", kind: \"text\", placeholder: \"alice.bsky.social\",\n help: \"Your Bluesky handle, your own domain if you use one, or your DID.\",\n }],\n run(ctx, work) {\n return signIn(ctx.values.handle ?? \"\", access, ctx, flow => work({\n subject: async () => flow.account.did,\n sign: async statement => { await publishAtprotoProof(statement, flow); return {}; },\n }));\n },\n });\n\n // Removing asks for a fresh approval; the server that refused the narrow permission once is asked for full access.\n const fullAccessServers = new Set<string>();\n\n return {\n id: \"atproto\",\n label: \"Bluesky / AT Protocol\",\n category: \"self-custodied\",\n summary: \"Publish a record on your Bluesky server\",\n description: \"Proves you control a Bluesky (AT Protocol) account: approve on your server, and Ghostly publishes one record in your account's repository. It is public, like the rest of your repository: anyone can see this account made a Ghostly proof, not who you share it with. Contacts check it without logging in.\",\n limits: \"Signed with your account's key, which your server usually holds. It proves control of the account, not who you are; the handle is shown only while it points back to the account.\",\n platforms: [\"web\", \"extension\", \"desktop\"],\n subject: {\n label: \"Account (DID)\",\n placeholder: \"did:plc:…\",\n normalize(input: string) {\n const did = input.trim();\n if (!isAtprotoDid(did)) throw new Error(\"Not an AT Protocol account (did:plc or did:web)\");\n return did;\n },\n short: shortAtprotoDid,\n },\n // The account's AT URI, for a public DID document's alsoKnownAs (#247), when the person lists it there.\n publicUri: did => `at://${did}`,\n validity: { defaultDays: 90, maxDays: 365 },\n // The record can be deleted any time: contacts look again after an hour.\n recheck: { afterSeconds: 3600 },\n privacy: \"Your contact's app asks the PLC directory (or your did:web host) for your account's document, your server for the record, and its DNS-over-HTTPS resolver or your handle's website for your handle, so they learn this account was checked.\",\n signers: [\n signer(\"atproto-oauth\", \"proof-records\", \"Your server (Bluesky or another PDS)\",\n \"Opens your server's login page. Ghostly asks only to create and delete its own proof records: never to post, read your messages or edit your profile.\"),\n signer(\"atproto-oauth-full\", \"full\", \"Your server, full access (older servers)\",\n \"Only if your server says it cannot limit Ghostly's permission: it then asks for access to your whole account. Ghostly still only writes this one record, and gives the access back right away.\"),\n ],\n parseEvidence(raw) {\n if (!raw || typeof raw !== \"object\" || Array.isArray(raw) || Object.keys(raw).length) throw new Error(\"Invalid AT Protocol evidence\");\n return {};\n },\n async verify(statement, _evidence, ctx) {\n const did = statement.binding.subject;\n if (!isAtprotoDid(did)) throw new Error(\"Not an AT Protocol account\");\n const resolve = resolveOptions(ctx.fetch, ctx.signal);\n // The server comes from the DID document, never from the proof.\n const doc = await resolveAtprotoDid(did, resolve);\n await assertPublicServer(hostOf(doc.pds).replace(/:\\d+$/, \"\"), resolve);\n const rkey = atprotoProofRkey(statement.binding.key);\n const url = `${doc.pds}/xrpc/com.atproto.sync.getRecord?${new URLSearchParams({ did, collection: ATPROTO_PROOF_COLLECTION, rkey })}`;\n let response: Awaited<ReturnType<IdentityFetch>>;\n try { response = await ctx.fetch(url, { headers: { accept: \"application/vnd.ipld.car\" }, maxBytes: RECORD_PROOF_MAX_BYTES, signal: ctx.signal, redirect: \"error\" }); }\n // eslint-disable-next-line preserve-caught-error -- A message people can act on; the library's error stays out of the UI (ES2020 has no Error.cause).\n catch (e) { throw new Error(/too large/i.test(String(e)) ? \"The account's server sent a proof that is too large\" : `The account's server (${hostOf(doc.pds)}) could not be reached`); }\n if (response.status !== 200) {\n let code = \"\";\n try { code = String((JSON.parse(response.text) as { error?: unknown }).error ?? \"\"); } catch { /* not JSON */ }\n if (code === \"RecordNotFound\") throw new Error(\"The record is no longer in the account's repository\");\n if (/^Repo(NotFound|Deactivated|Takendown|Suspended)$/.test(code)) throw new Error(\"The account is no longer on its server\");\n throw new Error(`The account's server (${hostOf(doc.pds)}) did not send the record`);\n }\n let proof: ReturnType<typeof verifyAtprotoRecordProof>;\n try { proof = verifyAtprotoRecordProof(response.bytes, { did, key: doc.key, collection: ATPROTO_PROOF_COLLECTION, rkey }); }\n // eslint-disable-next-line preserve-caught-error -- A message people can act on; the library's error stays out of the UI (ES2020 has no Error.cause).\n catch (e) { throw new Error(e instanceof AtprotoProofError ? `The account's repository does not prove the record: ${e.message}` : \"The account's repository could not be read\"); }\n if (!proof.record) throw new Error(\"The record is no longer in the account's repository\");\n checkAtprotoProofRecord(proof.record.value, statement);\n const handle = await verifiedAtprotoHandle(doc, resolve);\n return {\n subject: did,\n source: `Record signed with the account's key, from ${hostOf(doc.pds)}`,\n display: {\n ...(handle ? { name: `@${handle}` } : {}),\n url: `https://bsky.app/profile/${did}`,\n source: handle ? \"Handle checked both ways: it points to this account\" : \"The account's handle does not point back to it, so it is not shown\",\n fetchedAt: ctx.now,\n },\n };\n },\n unpublish: {\n description: \"Also deletes the record on your server: your server asks you to approve.\",\n run(proof, ctx) {\n return signIn(proof.subject, \"proof-records\", ctx, async flow => {\n const access: AtprotoAccess = fullAccessServers.has(flow.account.pds) ? \"full\" : \"proof-records\";\n try { await unpublishAtprotoProof(proof.key, { ...flow, access }); }\n catch (e) {\n if (e instanceof AtprotoScopeError && access !== \"full\") {\n fullAccessServers.add(flow.account.pds);\n // eslint-disable-next-line preserve-caught-error -- A message people can act on; the library's error stays out of the UI (ES2020 has no Error.cause).\n throw new Error(\"Your server only offers full access to your account. Try again to approve that; Ghostly only deletes this one record.\");\n }\n throw e;\n }\n });\n },\n },\n };\n}\n\nexport const atproto = createAtprotoIdentityProvider();\n","import {\n homeserverWebEndpoint, IdentityCheckUnavailable, openRelayPayload, pubkyHomeserverOf, PUBKY_PROOF_MAX_BYTES, RELAY_PAYLOAD_MAX_BYTES,\n type HomeserverEndpoint,\n} from \"@ghostly/core\";\nimport type { AuthFlow, GrantAuthFlow, Session } from \"@synonymdev/pubky\";\nimport type { ApprovalRequest, IdentityFetch } from \"./contract\";\nimport { assertPublicHost, chosenResolver, type DohResolverId } from \"./domain\";\nimport { getBrowserHost, type PubkyCookieSession } from \"../host\";\n\n/**\n * Pubky, browser side: reading a key's records and files the way a contact's app does (through the proof\n * contract's bounded `ctx.fetch` only), and asking the person to approve one Pubky auth request in Pubky Ring or\n * Pubky Passport (https://github.com/pubky/pubky-passport/blob/main/docs/integration.md). See WISP 302.\n */\n\n// -- reading ---------------------------------------------------------------------------------------\n\n/** Pkarr relays the records are read from: Pubky's own, the ones the Pubky SDK uses. */\nexport const PUBKY_RELAYS: readonly string[] = [\"https://pkarr.pubky.org\", \"https://pkarr.pubky.app\"];\n\n/**\n * The newest DNS packet the relays hold for `key`, its signature checked against `key`. Every relay is asked; one\n * that is down, answers garbage or holds an older packet does not count. Throws when none has a valid one.\n */\nexport async function pubkyRecords(key: string, fetch: IdentityFetch, signal?: AbortSignal, relays: readonly string[] = PUBKY_RELAYS): Promise<Uint8Array> {\n const failed: unknown[] = [];\n const answers = await Promise.all(relays.map(async relay => {\n let r;\n try { r = await fetch(`${relay.replace(/\\/+$/, \"\")}/${key}`, { maxBytes: RELAY_PAYLOAD_MAX_BYTES, signal }); }\n catch (e) { failed.push(e); return undefined; }\n try { return r.status === 200 ? openRelayPayload(key, r.bytes) : undefined; } catch { return undefined; }\n }));\n const newest = answers.filter(a => !!a).sort((a, b) => (b.seq > a.seq ? 1 : b.seq < a.seq ? -1 : 0))[0];\n // No relay answered at all (offline, a time-out): say that, not that the key has no records.\n if (!newest && failed.length === relays.length && failed[0] instanceof Error) throw failed[0];\n if (!newest) throw new Error(\"This Pubky key has no records on the Pkarr relays\");\n return newest.dnsPacket;\n}\n\n/** Where `key`'s homeserver answers browsers: its `_pubky` record, then the homeserver's own HTTPS record. */\nexport async function pubkyHomeserver(key: string, fetch: IdentityFetch, signal?: AbortSignal, relays?: readonly string[]): Promise<{ homeserver: string; endpoint: HomeserverEndpoint }> {\n const homeserver = pubkyHomeserverOf(await pubkyRecords(key, fetch, signal, relays), key);\n if (!homeserver) throw new Error(\"This Pubky key names no homeserver\");\n const endpoint = homeserverWebEndpoint(await pubkyRecords(homeserver, fetch, signal, relays), homeserver);\n if (!endpoint) throw new Error(\"The homeserver of this Pubky key publishes no address a browser can reach\");\n return { homeserver, endpoint };\n}\n\n/**\n * A public file of `key` at `path`, from the homeserver its own records name: bounded, no redirects, the owner in the\n * `pubky-host` header the way the SDK addresses it. `undefined` when the homeserver says it is not there.\n *\n * The key's owner chooses that host, so it is contacted only on port 443 and only once its addresses are all public\n * (the name is resolved first through the chosen DNS-over-HTTPS resolver): a proof never makes a contact's app knock\n * on its own network. Failures on the way there are IdentityCheckUnavailable, which the contact is not told about.\n */\nexport async function readPubkyFile(key: string, path: `/pub/${string}`, fetch: IdentityFetch, signal?: AbortSignal, relays?: readonly string[], resolver: DohResolverId = chosenResolver().id): Promise<{ text: string | undefined; host: string }> {\n const { endpoint: { host, port } } = await pubkyHomeserver(key, fetch, signal, relays);\n if (port !== undefined) throw new IdentityCheckUnavailable(`The homeserver ${host} answers on port ${port}; only port 443 is contacted`);\n try { await assertPublicHost(host, { fetch, signal, resolver }); }\n catch (e) { throw new IdentityCheckUnavailable(`The homeserver ${host} was not contacted: ${e instanceof Error ? e.message : String(e)}`); }\n let r: Awaited<ReturnType<IdentityFetch>>;\n try { r = await fetch(`https://${host}${path}`, { headers: { \"pubky-host\": key }, maxBytes: PUBKY_PROOF_MAX_BYTES, signal }); }\n catch (e) {\n // A file over the cap is the owner's doing, and says nothing about this side's network.\n if (/too large/i.test(String(e))) throw e;\n throw new IdentityCheckUnavailable(`The homeserver ${host} could not be reached`);\n }\n if (r.status === 404) return { text: undefined, host };\n if (r.status !== 200) throw new IdentityCheckUnavailable(`The homeserver ${host} answered ${r.status}`);\n return { text: r.text, host };\n}\n\n// -- approving -------------------------------------------------------------------------------------\n\n/** The app identifier Pubky Ring and Passport list the grant under. */\nexport const PUBKY_CLIENT_ID = \"ghostly.tools\";\nexport const PASSPORT_ORIGIN = \"https://passport.pubky.app\";\n/** How long one request waits: Pubky's relay keeps a message about five minutes. */\nconst APPROVAL_TIMEOUT = 5 * 60_000;\nconst POLL_EVERY = 1_000;\n\n/**\n * Passport's authorize page for a request. The authorization URL carries the relay secret: it goes in the fragment\n * (never sent to a server), encoded exactly once, and is never logged.\n */\nexport const passportUrl = (authorizationUrl: string) => `${PASSPORT_ORIGIN}/authorize#d=${encodeURIComponent(authorizationUrl)}`;\n\n/** Messages from the SDK may quote the request: nothing that looks like one reaches the UI or a log. */\nconst redact = (text: string) => text.replace(/pubkyauth:\\/\\/\\S*/gi, \"(request)\").replace(/#d=\\S*/g, \"#d=(request)\");\nconst failure = (what: string, e: unknown) => new Error(`${what}${e instanceof Error && e.message ? `: ${redact(e.message)}` : \"\"}`);\n\n/** An approved Pubky session, only as long as `work` runs: the key, and writes inside the one folder granted. */\nexport interface PubkyApprovedSession {\n key: string;\n put(path: `/pub/${string}`, text: string): Promise<void>;\n delete(path: `/pub/${string}`): Promise<void>;\n}\n\nexport interface PubkyApprovalOptions {\n /** The one capability asked for, `/pub/…/:w`. A session granted anything else is refused. */\n capability: `/pub/${string}/:w`;\n signal: AbortSignal;\n onApproval(request: ApprovalRequest | null): void;\n onProgress(message: string): void;\n /** Where Passport opens. The desktop app hands it to the system browser; elsewhere a popup from the click. */\n openPassport?: (url: string) => Window | null | void;\n /** Tests: the SDK's relay, and a shorter wait. */\n relay?: string;\n timeoutMs?: number;\n}\n\n/**\n * Opens a Passport window from the click that calls it. It must run synchronously in the click handler, before\n * anything is awaited, or the browser blocks the popup; the desktop app opens the system browser instead.\n */\nexport function openPassportWindow(url: string): Window | null | void {\n let host: ReturnType<typeof getBrowserHost> | undefined;\n try { host = getBrowserHost(); } catch { host = undefined; }\n if (host?.openPubkyPassport) { void host.openPubkyPassport(url).catch(() => {}); return; }\n // Not `noopener`: the reference lets Ghostly close the window once either signer approved.\n return window.open(url, \"pubky-passport\", \"popup,width=520,height=760\");\n}\n\n/** What a request of either kind offers while it waits: one poll at a time, freed once no poll is in flight. */\ninterface PendingRequest { tryPollOnce(): Promise<Session | undefined>; free(): void }\n\n/** A 401 or 403 from the homeserver: the session it was given is not one it accepts. */\nconst refused = (e: unknown) => {\n const status = (e as { data?: { statusCode?: unknown } } | null)?.data?.statusCode;\n return status === 401 || status === 403;\n};\n\n/**\n * Polls every request until one of them is approved and resolves with that session and its request; a cancel, the\n * deadline or a failed poll ends the wait for all of them. Each request is freed once its last poll has settled\n * (never while one is in flight), and a session that lands after the end is signed out at once; `onSettled` runs\n * once all of that is done.\n */\nfunction firstApproval(requests: readonly PendingRequest[], signal: AbortSignal, timeoutMs: number, onSettled: () => void): Promise<{ session: Session; request: PendingRequest }> {\n return new Promise((resolve, reject) => {\n let over = false;\n let polling = requests.length;\n const wakers = new Set<() => void>();\n const end = (settle: () => void) => {\n if (over) return;\n over = true;\n clearTimeout(timer);\n signal.removeEventListener(\"abort\", cancel);\n for (const wake of wakers) wake();\n settle();\n };\n const cancel = () => end(() => reject(signal.reason));\n const timer = setTimeout(() => end(() => reject(new Error(\"Nobody approved the request in time. Start again for a new one.\"))), timeoutMs);\n if (signal.aborted) cancel(); else signal.addEventListener(\"abort\", cancel, { once: true });\n const pause = () => new Promise<void>(done => {\n const wake = () => { clearTimeout(t); wakers.delete(wake); done(); };\n const t = setTimeout(wake, POLL_EVERY);\n wakers.add(wake);\n });\n\n // The end does not wait for a poll in flight (one that got an approval is still redeeming it at the homeserver):\n // its request is freed when it settles, and a session it brings is signed out.\n const poll = async (request: PendingRequest) => {\n try {\n while (!over) {\n let session: Session | undefined;\n try { session = await request.tryPollOnce(); }\n catch (e) { end(() => reject(failure(\"The Pubky request failed\", e))); return; }\n if (session && over) { try { await session.signout(); } catch { /* best effort */ } finally { session.free(); } return; }\n if (session) { const approved = session; end(() => resolve({ session: approved, request })); return; }\n if (!over) await pause();\n }\n } finally {\n request.free();\n if (--polling === 0) onSettled();\n }\n };\n for (const request of requests) void poll(request);\n });\n}\n\n/**\n * A request of a cookie session (Ring's approval) to its homeserver: its `/session` or a file of its storage\n * (`/storage/<key>/pub/…`, or `/pub/…` where the homeserver does not address storage by path), sent with the\n * browser's credentials. A grant session's requests carry an `Authorization` header instead, and the relay's\n * and the Pkarr relays' are elsewhere: those stay the page's.\n */\nconst cookieSessionRequest = (request: Request) =>\n request.credentials === \"include\" && !request.headers.has(\"authorization\") && /^\\/(?:session$|storage\\/|pub\\/)/.test(new URL(request.url).pathname);\n\n/** Statuses a Response carries no body with. */\nconst NO_BODY = new Set([204, 205, 304]);\n\n/**\n * Sends the SDK's cookie-session requests through `transport` until the returned function runs. The SDK fetches with\n * the global fetch, so this wraps it; everything else goes on to the page's fetch as it was. The cookie the\n * homeserver sets stays in the transport: the page, and so the SDK, never sees it.\n */\nfunction routeCookieSession(transport: PubkyCookieSession): () => void {\n const pageFetch = globalThis.fetch;\n let closed = false;\n const routed = async (input: RequestInfo | URL, init?: RequestInit): Promise<Response> => {\n // A Request made from another takes its body over: whatever is made here is what goes on.\n const request = input instanceof Request && init === undefined ? input : new Request(input, init);\n // Once closed, a wrapper something else has wrapped since stays in place: it only passes requests on.\n if (closed || !cookieSessionRequest(request)) return pageFetch(request);\n request.signal.throwIfAborted();\n const body = request.body ? new Uint8Array(await request.arrayBuffer()) : null;\n let answer;\n try { answer = await transport.fetch({ url: request.url, method: request.method, headers: [...request.headers], body }); }\n catch (e) { throw Object.assign(new TypeError(e instanceof Error ? e.message : String(e)), { cause: e }); }\n const response = new Response(NO_BODY.has(answer.status) ? null : answer.body as BodyInit, { status: answer.status, headers: answer.headers });\n // The SDK parses the answer's URL, which a Response made here does not have: it is the request's (no redirects).\n Object.defineProperty(response, \"url\", { value: request.url });\n return response;\n };\n globalThis.fetch = routed;\n return () => {\n if (closed) return;\n closed = true;\n if (globalThis.fetch === routed) globalThis.fetch = pageFetch;\n transport.close();\n };\n}\n\n/** The host's way around a page that drops the homeserver's cookie, if it has one. */\nfunction cookieSessionTransport(): PubkyCookieSession | undefined {\n try { return getBrowserHost().pubkyCookieSession?.(); } catch { return undefined; }\n}\n\n/**\n * Starts one request for `capability` in each of the two forms Pubky signers read (a grant request for Passport, a\n * cookie request for Ring's QR code: see below), shows both on one screen, and waits for whichever is approved\n * first, then runs `work` with that session. The session lives only in this renderer's memory while `work` runs,\n * and is signed out afterwards; nothing about it is stored. An approval that arrives after the end is signed out.\n */\nexport async function withPubkyApproval<T>(options: PubkyApprovalOptions, work: (session: PubkyApprovedSession) => Promise<T>): Promise<T> {\n const { signal } = options;\n signal.throwIfAborted();\n const { AuthFlow, GrantAuthFlow, AuthFlowKind } = await import(\"@synonymdev/pubky\");\n signal.throwIfAborted();\n const relay = options.relay ?? null;\n let grant: GrantAuthFlow, cookie: AuthFlow;\n try {\n // The flow's proof-of-possession key stays in this flow's memory (not the SDK's delegated IndexedDB key).\n grant = GrantAuthFlow.start(options.capability, AuthFlowKind.signin(), { clientId: PUBKY_CLIENT_ID, relay, xCallback: { xSource: \"Ghostly\" } });\n } catch (e) { throw failure(\"Could not start a Pubky request\", e); }\n try {\n // Same capability and relay, its own secret. The SDK deprecates this flow, but it is the one Ring's store build reads.\n cookie = AuthFlow.start(options.capability, AuthFlowKind.signin(), relay, { xSource: \"Ghostly\" });\n } catch (e) { grant.free(); throw failure(\"Could not start a Pubky request\", e); }\n\n const opened: { window?: Window | null } = {};\n options.onApproval({\n open: {\n label: \"Approve in your browser (Pubky Passport)\",\n description: \"Opens Pubky Passport. No Passport identity yet? It offers “Continue with Google” and makes one.\",\n run: () => {\n const window = (options.openPassport ?? openPassportWindow)(passportUrl(grant.authorizationUrl));\n opened.window = window ?? null;\n // null: the browser refused the popup (undefined: it opened elsewhere, the system browser).\n if (window === null) options.onProgress(\"Your browser blocked the Passport window: allow pop-ups for Ghostly and press the button again, or scan the code with Pubky Ring.\");\n },\n },\n // Ring gets the cookie request (`pubkyauth://signin?…`), not the grant one: the Ring in the app stores (1.19)\n // predates pubky/pubky-ring#360 and answers \"Unrecognized format\" to a `signin_grant` link. Move this to\n // grant.authorizationUrl, and drop the cookie flow, once Ring's store build parses `signin_grant`.\n qr: { value: cookie.authorizationUrl, label: \"Or scan with Pubky Ring\" },\n notes: [\n \"An identity Passport makes with Google is recovered with Google plus Passport: both are needed, neither alone can.\",\n \"The code is your request: scan it yourself, and do not share it.\",\n ],\n });\n\n // Where the page would drop the cookie session's cookie (the desktop app), its requests go through the host, from\n // the first poll (an approval is redeemed inside one) until the last poll has settled and the work is over.\n const transport = cookieSessionTransport();\n const unroute = transport ? routeCookieSession(transport) : () => {};\n let settled!: () => void;\n const polled = new Promise<void>(done => { settled = done; });\n try {\n return await approveAndWork(options, work, { grant, cookie, opened, settled, cookieKept: !!transport });\n } finally {\n void polled.then(unroute);\n }\n}\n\n/**\n * The rest of `withPubkyApproval`, once both requests are shown: the wait, the check, and the work. `cookieKept`: the\n * host keeps a cookie session's cookie, so a refusal is not the page's cookie policy.\n */\nasync function approveAndWork<T>(options: PubkyApprovalOptions, work: (session: PubkyApprovedSession) => Promise<T>, { grant, cookie, opened, settled, cookieKept }: {\n grant: GrantAuthFlow; cookie: AuthFlow; opened: { window?: Window | null }; settled: () => void; cookieKept: boolean;\n}): Promise<T> {\n const { signal } = options;\n let session: Session, viaCookie: boolean;\n try {\n const first = await firstApproval([grant, cookie], signal, options.timeoutMs ?? APPROVAL_TIMEOUT, settled);\n session = first.session;\n viaCookie = first.request === cookie;\n } finally {\n options.onApproval(null);\n try { if (opened.window && !opened.window.closed) opened.window.close(); } catch { /* another origin's window: best effort */ }\n }\n\n options.onProgress(\"Approved. Checking what Pubky granted…\");\n let key: string;\n try {\n const info = session.info;\n const publicKey = info.publicKey;\n try {\n key = publicKey.z32();\n const granted = info.capabilities;\n if (granted.length !== 1 || granted[0] !== options.capability)\n throw new Error(\"The signer granted different permissions than Ghostly asked for (one proof folder). Nothing was written.\");\n } finally { publicKey.free(); info.free(); }\n } catch (e) {\n await session.signout().catch(() => {});\n session.free();\n throw e;\n }\n\n // A cookie session lives in the homeserver's cookie, a third-party cookie in a page. WebKit (Safari) and Vanadium drop it, so the\n // homeserver then refuses the session's writes: say that, not \"401\". The desktop app keeps it outside its WebView.\n const fail = (what: string) => (e: unknown) => {\n if (viaCookie && !cookieKept && refused(e))\n throw new Error(\"Pubky Ring approved, but this browser blocks your homeserver's sign-in cookie (Vanadium and Safari block cookies from other sites), so nothing was written. Approve with Pubky Passport instead, or use Firefox or the Ghostly desktop app.\");\n throw failure(what, e);\n };\n const approved = session;\n try {\n return await work({\n key,\n put: (path, text) => approved.storage.putText(path, text).catch(fail(\"Could not write to your homeserver\")),\n delete: path => approved.storage.delete(path).catch(fail(\"Could not delete from your homeserver\")),\n });\n } finally {\n // The grant is only needed for this: end it, and forget the session.\n try { await approved.signout(); } catch { /* the grant expires on its own */ }\n approved.free();\n }\n}\n","import {\n isPubkyKey, isPubkyProofFolder, newPubkyProofFolder, normalizePubkyKey, pubkyProofCapability, pubkyProofFile, pubkyProofPath,\n} from \"@ghostly/core\";\nimport type { IdentityPlatform, IdentityProofProvider, InAppSigner } from \"../contract\";\nimport { readPubkyFile, withPubkyApproval, type PubkyApprovalOptions } from \"../pubky\";\n\n/**\n * A Pubky identity (WISP 302, docs/wisps/302-pubky.md), proven by publication: Pubky Ring and Pubky Passport sign\n * only standard Pubky auth requests, so Ghostly asks, in ONE request either of them can approve, for write access to\n * one fresh folder of the key's homeserver (`/pub/ghostly.app/proofs/<random>/`), and writes the identity statement\n * there. Contacts' apps read the file back from the homeserver the key's own signed Pkarr records name. The session\n * is used once, in memory, and signed out; removing the proof needs a new approval to delete the file.\n */\nexport interface PubkyEvidence { folder: string }\n\nconst short = (key: string) => `pubky${key.slice(0, 4)}…${key.slice(-4)}`;\n\n/** Strict: exactly `{ folder }`, 64 hex characters. Nothing else, so no evidence can name a host or another path. */\nfunction parseEvidence(raw: unknown): PubkyEvidence {\n const e = raw as PubkyEvidence;\n if (!raw || typeof raw !== \"object\" || Array.isArray(raw) || Object.keys(raw).join(\",\") !== \"folder\" || !isPubkyProofFolder(e.folder))\n throw new Error(\"That is not Pubky proof evidence\");\n return { folder: e.folder };\n}\n\nexport interface PubkyIdentityOptions {\n /** Pkarr relays the verifier reads (tests). */\n relays?: readonly string[];\n /** The approval flow's knobs (tests: a local relay, a Passport stand-in). */\n approval?: Partial<Pick<PubkyApprovalOptions, \"relay\" | \"timeoutMs\" | \"openPassport\">>;\n}\n\nexport function createPubkyIdentityProvider(options: PubkyIdentityOptions = {}): IdentityProofProvider<PubkyEvidence> {\n const approve = <T>(capability: `/pub/${string}/:w`, ctx: Parameters<InAppSigner<PubkyEvidence>[\"run\"]>[0], work: Parameters<typeof withPubkyApproval<T>>[1]) => {\n if (!ctx.onApproval) throw new Error(\"This screen cannot show a Pubky request\");\n return withPubkyApproval({ ...options.approval, capability, signal: ctx.signal, onApproval: ctx.onApproval, onProgress: ctx.onProgress }, work);\n };\n\n const signer: InAppSigner<PubkyEvidence> = {\n id: \"pubky-auth\", kind: \"in-app\",\n label: \"Pubky Ring or Pubky Passport\",\n action: \"Continue\",\n description: \"One request: approve it in Pubky Passport in your browser, or scan it with Pubky Ring. Ghostly only asks to write one small file to a new folder of your homeserver.\",\n run(ctx, work) {\n const folder = newPubkyProofFolder();\n return approve(pubkyProofCapability(folder), ctx, async session => {\n let written: `/pub/${string}` | undefined;\n try {\n return await work({\n subject: async () => session.key,\n async sign(statement) {\n if (statement.binding.subject !== session.key) throw new Error(\"That is another Pubky identity\");\n ctx.onProgress(\"Writing the proof to your homeserver…\");\n const path = pubkyProofPath(folder, statement.id);\n await session.put(path, pubkyProofFile(statement));\n written = path;\n return { folder };\n },\n });\n } catch (e) {\n // Not saved (a failed check, a cancel): the file does not stay behind.\n if (written) await session.delete(written).catch(() => {});\n throw e;\n }\n });\n },\n };\n\n return {\n id: \"pubky\",\n label: \"Pubky\",\n category: \"self-custodied\",\n summary: \"Approve in Pubky Ring or Passport\",\n description: \"Proves you hold a Pubky key: approve one request in Pubky Ring or Pubky Passport, and Ghostly writes a small proof file to your homeserver. Contacts’ apps read it back through your key’s own records.\",\n limits: \"The file is on your homeserver: whoever runs it could write there too. It does not prove who holds the key.\",\n platforms: [\"web\", \"extension\", \"desktop\"] satisfies IdentityPlatform[],\n subject: {\n label: \"Pubky key\",\n placeholder: \"pubky…\",\n help: \"Comes from Pubky Ring or Passport when you approve.\",\n normalize: normalizePubkyKey,\n short,\n },\n validity: { defaultDays: 90, maxDays: 365 },\n // The file can be deleted, or the key can move to another homeserver, before the proof expires.\n recheck: { afterSeconds: 86_400 },\n privacy: \"Your contact’s app reads your key’s records from Pubky’s Pkarr relays (pkarr.pubky.org, pkarr.pubky.app), looks up your homeserver’s address through its DNS-over-HTTPS resolver (or the next one when that cannot be reached; it only contacts a public address, on port 443), and reads the proof file from your homeserver, which sees the contact’s IP address.\",\n signers: [signer],\n // Pubky's own URI form for a key, as its SDK writes resources (`pubky://<key>/…`).\n publicUri: key => `pubky://${key}`,\n parseEvidence,\n async verify(statement, { folder }, ctx) {\n const key = statement.binding.subject;\n if (!isPubkyKey(key)) throw new Error(\"That is not a Pubky key\");\n const { text, host } = await readPubkyFile(key, pubkyProofPath(folder, statement.id), ctx.fetch, ctx.signal, options.relays);\n if (text === undefined) throw new Error(\"The proof file is not on the homeserver\");\n if (text !== pubkyProofFile(statement)) throw new Error(\"The file on the homeserver is not this proof\");\n return { subject: key, source: `File on the homeserver ${host}, found through the key’s own Pkarr records` };\n },\n unpublish: {\n description: \"Also deletes the proof file on your homeserver: that takes one more approval in Pubky Ring or Passport, since Ghostly kept no access to it.\",\n async run({ id, subject, evidence }, ctx) {\n // Stored when the proof was made: parsed like any evidence, so a bad record never names another path.\n const { folder } = parseEvidence(evidence);\n const path = pubkyProofPath(folder, id);\n return approve(pubkyProofCapability(folder), ctx, async session => {\n if (session.key !== subject) throw new Error(\"That is another Pubky identity: approve with the one this proof is for\");\n ctx.onProgress(\"Deleting the proof file from your homeserver…\");\n await session.delete(path);\n });\n },\n },\n };\n}\n\nexport const pubky = createPubkyIdentityProvider();\n","import { ed25519 } from \"@noble/curves/ed25519.js\";\nimport { fromBase64Url, toBase64Url, utf8Decode, utf8Encode, type IdentityStatement } from \"@ghostly/core\";\nimport type { IdentityProofProvider } from \"./contract\";\n\n/**\n * Fake identity providers, for unit tests and e2e. No network, no real accounts. They are in the\n * registry only when `localStorage[\"ghostly-test-identities\"] === \"1\"` (or a test passes them itself).\n *\n * - fake-key self-custodied: an Ed25519 key held by a fake in-app signer, or signed \"outside\" and pasted.\n * - fake-account provider-attested: a fake issuer signs a token whose nonce is the statement id (like OIDC).\n * - fake-record self-custodied, published: a record the verifier fetches through `ctx.fetch` (like DNS).\n */\nexport const TEST_IDENTITIES_FLAG = \"ghostly-test-identities\";\nexport const testIdentitiesEnabled = () => { try { return globalThis.localStorage?.getItem(TEST_IDENTITIES_FLAG) === \"1\"; } catch { return false; } };\n\nconst HEX64 = /^[a-f0-9]{64}$/;\nconst hex = (b: Uint8Array) => Array.from(b, x => x.toString(16).padStart(2, \"0\")).join(\"\");\nconst unhex = (s: string) => Uint8Array.from(s.match(/../g)!, x => parseInt(x, 16));\nconst normalizeHexKey = (input: string) => { const v = input.trim().toLowerCase(); if (!HEX64.test(v)) throw new Error(\"Enter 64 hex characters\"); return v; };\nconst exactKeys = (raw: unknown, keys: string) => !!raw && typeof raw === \"object\" && !Array.isArray(raw) && Object.keys(raw).sort().join(\",\") === keys;\n\n/** The key behind the fake in-app signer. Tests may replace it. */\nexport const fakeKeyring = { seed: ed25519.utils.randomSecretKey() };\nexport const fakeKeySubject = (seed = fakeKeyring.seed) => hex(ed25519.getPublicKey(seed));\nexport const fakeKeySign = (statement: IdentityStatement, seed = fakeKeyring.seed) => ({ sig: toBase64Url(ed25519.sign(statement.bytes, seed)) });\n\nexport interface FakeKeyEvidence { sig: string }\nexport const fakeKey: IdentityProofProvider<FakeKeyEvidence> = {\n id: \"fake-key\", label: \"Test key\", category: \"self-custodied\",\n summary: \"Sign with the test key\", description: \"A test identity: an Ed25519 key held by this page. For tests only.\",\n platforms: [\"web\", \"extension\", \"desktop\"],\n subject: { label: \"Public key\", placeholder: \"64 hex characters\", normalize: normalizeHexKey, short: k => `${k.slice(0, 8)}…${k.slice(-4)}` },\n publicUri: k => `urn:ghostly-test:${k}`,\n validity: { defaultDays: 30, maxDays: 365 },\n privacy: \"Nothing: checked on this device.\",\n signers: [\n { id: \"fake-signer\", kind: \"in-app\", label: \"Test signer\", fields: [{ name: \"refuse\", label: \"Refuse\", kind: \"text\", optional: true }],\n run: async (ctx, work) => {\n ctx.signal.throwIfAborted();\n if (ctx.values.refuse) throw new Error(\"The test signer refused\");\n return work({ subject: async () => fakeKeySubject(), sign: async statement => fakeKeySign(statement) });\n } },\n { id: \"fake-tool\", kind: \"external-tool\", label: \"Sign outside Ghostly\",\n instructions: statement => ({ steps: [{ text: \"Sign this statement with your test key:\", copy: statement.text }], paste: { label: \"Signature (base64url)\" } }),\n parse: pasted => { const sig = pasted.trim(); if (!/^[A-Za-z0-9_-]{86}$/.test(sig)) throw new Error(\"That is not a test signature\"); return { sig }; } },\n ],\n parseEvidence(raw) {\n const e = raw as FakeKeyEvidence;\n if (!exactKeys(raw, \"sig\") || typeof e.sig !== \"string\" || !/^[A-Za-z0-9_-]{86}$/.test(e.sig)) throw new Error(\"Invalid test evidence\");\n return { sig: e.sig };\n },\n async verify(statement, evidence) {\n let ok: boolean;\n try { ok = ed25519.verify(fromBase64Url(evidence.sig), statement.bytes, unhex(statement.binding.subject)); } catch { ok = false; }\n if (!ok) throw new Error(\"Invalid test signature\");\n return { subject: statement.binding.subject, source: \"Test signature (Ed25519)\" };\n },\n};\n\n/** The fake issuer's key. Its public half is what fake-account trusts, like a provider's JWKS. */\nconst ISSUER_SEED = unhex(\"7a\".repeat(32));\nexport const FAKE_ISSUER = \"https://issuer.ghostly.test\";\nexport interface FakeAccountEvidence { token: string }\n/** What the fake login page signs: iss, sub, nonce (= statement id), iat. */\nexport function fakeAccountToken(statement: IdentityStatement, account = \"alice\", seed = ISSUER_SEED, iat = statement.binding.issuedAt): FakeAccountEvidence {\n const payload = toBase64Url(utf8Encode(JSON.stringify({ iss: FAKE_ISSUER, sub: account, nonce: statement.id, iat })));\n return { token: `${payload}.${toBase64Url(ed25519.sign(utf8Encode(payload), seed))}` };\n}\nexport const fakeAccount: IdentityProofProvider<FakeAccountEvidence> = {\n id: \"fake-account\", label: \"Test account\", category: \"provider-attested\",\n summary: \"Log in to the test issuer\", description: \"A test login provider that vouches for an account. For tests only.\",\n platforms: [\"web\", \"extension\", \"desktop\"],\n subject: { label: \"Provider\", options: [{ value: FAKE_ISSUER, label: \"Test issuer\" }],\n normalize: input => { if (input.trim() !== FAKE_ISSUER) throw new Error(\"Unknown test issuer\"); return FAKE_ISSUER; } },\n validity: { defaultDays: 30, maxDays: 90 },\n privacy: \"Nothing: the issuer's signature is checked on this device.\",\n signers: [{ id: \"fake-login\", kind: \"redirect\", label: \"Log in to the test issuer\",\n start: async (statement, ctx) => { ctx.signal.throwIfAborted(); return fakeAccountToken(statement, ctx.values.account || \"alice\"); } }],\n parseEvidence(raw) {\n const e = raw as FakeAccountEvidence;\n if (!exactKeys(raw, \"token\") || typeof e.token !== \"string\" || e.token.length > 2048 || !/^[A-Za-z0-9_-]+\\.[A-Za-z0-9_-]{86}$/.test(e.token)) throw new Error(\"Invalid test token\");\n return { token: e.token };\n },\n async verify(statement, { token }) {\n const [payload, sig] = token.split(\".\");\n if (!ed25519.verify(fromBase64Url(sig), utf8Encode(payload), ed25519.getPublicKey(ISSUER_SEED))) throw new Error(\"Not signed by the test issuer\");\n const claims = JSON.parse(utf8Decode(fromBase64Url(payload))) as { iss: string; sub: string; nonce: string };\n if (claims.iss !== statement.binding.subject) throw new Error(\"Token from another issuer\");\n if (claims.nonce !== statement.id) throw new Error(\"Token for another statement\");\n if (typeof claims.sub !== \"string\" || !/^[a-z0-9]{1,64}$/.test(claims.sub)) throw new Error(\"Invalid account\");\n return { subject: `${claims.iss}#${claims.sub}`, attester: \"issuer.ghostly.test\", source: \"Token signed by the test issuer\",\n display: { name: claims.sub, source: \"Test issuer profile\", fetchedAt: statement.binding.issuedAt } };\n },\n};\n\n/** fake-record's records: subject → published text. The verifier reads `https://records.ghostly.test/<subject>`. */\nexport const FAKE_RECORD_HOST = \"https://records.ghostly.test/\";\nexport const fakeRecordText = (statement: IdentityStatement) => `ghostly-proof=${statement.id}`;\nexport const fakeRecord: IdentityProofProvider<Record<string, never>> = {\n id: \"fake-record\", label: \"Test record\", category: \"self-custodied\",\n summary: \"Publish a test record\", description: \"A test record published where the verifier looks it up, like DNS. For tests only.\",\n platforms: [\"web\", \"extension\", \"desktop\"],\n subject: { label: \"Name\", placeholder: \"alice\", normalize: input => { const v = input.trim().toLowerCase(); if (!/^[a-z0-9-]{1,63}$/.test(v)) throw new Error(\"Letters, digits and dashes\"); return v; } },\n validity: { defaultDays: 30, maxDays: 365 },\n recheck: { afterSeconds: 3600 },\n privacy: \"The verifier asks records.ghostly.test for the record: that server learns the name was checked.\",\n signers: [{ id: \"fake-publish\", kind: \"publish\", label: \"Publish a record\",\n instructions: statement => ({ steps: [{ text: `Publish this record for ${statement.binding.subject}:`, copy: fakeRecordText(statement) }] }),\n evidence: () => ({}) }],\n parseEvidence(raw) { if (!exactKeys(raw, \"\")) throw new Error(\"Invalid test evidence\"); return {}; },\n async verify(statement, _evidence, ctx) {\n const response = await ctx.fetch(`${FAKE_RECORD_HOST}${statement.binding.subject}`, { maxBytes: 4096, signal: ctx.signal });\n if (response.status !== 200 || !response.text.split(\"\\n\").includes(fakeRecordText(statement))) throw new Error(\"The record is not published\");\n return { subject: statement.binding.subject, source: \"Record published at records.ghostly.test\" };\n },\n};\n\nexport const FAKE_IDENTITY_PROVIDERS: readonly IdentityProofProvider[] = [fakeKey, fakeAccount, fakeRecord];\n","import type { IdentityProofProvider } from \"./contract\";\nimport { bitcoin } from \"./providers/bitcoin\";\nimport { nostr } from \"./providers/nostr\";\nimport { domain } from \"./providers/domain\";\nimport { openpgp } from \"./providers/openpgp\";\nimport { ssh, sshGithub, sshGitlab } from \"./providers/ssh\";\nimport { oidc } from \"./providers/oidc\";\nimport { did } from \"./providers/did\";\nimport { atproto } from \"./providers/atproto\";\nimport { pubky } from \"./providers/pubky\";\nimport { FAKE_IDENTITY_PROVIDERS, testIdentitiesEnabled } from \"./testing\";\nimport { registeredIdentityProviders, reserveAdapterIds } from \"../plugins/registry\";\n\n/**\n * Every identity-proof provider built into Ghostly. Adding one is adding its module under `providers/`\n * and ONE line here; see PROOFS.md. The order is the order of the picker on the Identities page. A\n * provider written outside the app registers through `plugins/registry.ts` (the SDK) and comes after.\n */\nexport const IDENTITY_PROVIDERS: readonly IdentityProofProvider[] = [\n nostr,\n pubky,\n domain,\n openpgp,\n bitcoin,\n ssh, sshGithub, sshGitlab,\n oidc,\n did,\n atproto,\n];\n\nreserveAdapterIds(\"identity\", IDENTITY_PROVIDERS.map((p) => p.id));\n\nconst warned = new Set<string>();\n/** The built-in providers, then the ones plugins registered (a built-in's id wins), then the fakes when this browser asked for them (e2e). */\nexport function identityProviders(): readonly IdentityProofProvider[] {\n const external = registeredIdentityProviders().filter((p) => {\n const taken = IDENTITY_PROVIDERS.some((b) => b.id === p.id);\n if (taken && !warned.has(p.id)) { warned.add(p.id); console.error(`[ghostly] identity provider ${p.id} from a plugin is ignored: a built-in has that id`); }\n return !taken;\n });\n return [...IDENTITY_PROVIDERS, ...external, ...(testIdentitiesEnabled() ? FAKE_IDENTITY_PROVIDERS : [])];\n}\n\nexport function identityProvider(id: string, providers = identityProviders()): IdentityProofProvider | undefined {\n return providers.find(p => p.id === id);\n}\n","import { FILE_BYTES_STEP, fileBytesOf, type FileBytes } from \"./fileBytes\";\nimport { fileStore, type StoredFile } from \"./idb\";\nimport type { FileTransferView } from \"./types\";\n\n/** The bytes a file holds: its metadata says, and a file stored before that says it with its Blob. */\nexport function storedSize(file: StoredFile): number {\n return file.metadata?.size ?? file.blob?.size ?? 0;\n}\n\n/**\n * A group's file (WISP 503) that is not here, read from its record: when the engine shows no transfer for it, it waits\n * for its person's Download. The engine keeps no view of such a file in the states it builds (one for every file a\n * group ever had would go into each of them), so the app's bubble and the CLI read it here. Null for any other file.\n */\nexport function waitingGroupFile(file: StoredFile | undefined): FileTransferView | null {\n if (!file?.group || file.direction !== \"in\" || file.transfer?.state !== \"transferring\") return null;\n return { state: \"transferring\", stage: \"asking\", transferred: 0, size: file.metadata?.size ?? file.transfer.size, direction: \"in\" };\n}\n\nasync function backend(file: StoredFile): Promise<FileBytes | null> {\n return file.bytes ? fileBytesOf(file.bytes) : null;\n}\n\n/** The file as a Blob backed by storage, or null where it cannot be had as one (see `FileBytes.blob`). */\nexport async function storedBlob(file: StoredFile, type: string): Promise<Blob | null> {\n if (file.blob) return file.blob.slice(0, file.blob.size, type);\n return (await backend(file))?.blob(file.id, type) ?? null;\n}\n\n/** At most `length` bytes from `offset`. */\nexport async function readStored(file: StoredFile, offset: number, length: number): Promise<Uint8Array> {\n if (file.blob) return new Uint8Array(await file.blob.slice(offset, offset + length).arrayBuffer());\n const bytes = await backend(file);\n if (!bytes) throw new Error(\"The file is gone\");\n return bytes.read(file.id, offset, length);\n}\n\n/** The file, a step at a time, from `offset`. */\nexport async function* streamStored(file: StoredFile, offset = 0): AsyncGenerator<Uint8Array> {\n const size = storedSize(file);\n while (offset < size) {\n const part = await readStored(file, offset, Math.min(FILE_BYTES_STEP, size - offset));\n if (!part.length) throw new Error(\"The file is shorter than it says\");\n offset += part.length;\n yield part;\n }\n}\n\n/** A file's record and its bytes, wherever they are. */\nexport async function removeStored(id: string): Promise<void> {\n const file = await fileStore.get(id);\n await fileStore.delete(id);\n if (file?.bytes) await (await backend(file))?.remove(id);\n}\n\n/** Writes are gathered to this size before they go to storage: a chunk from the wire is 16 KiB. */\nconst GATHER = 256 * 1024;\n\n/**\n * Writes one file in order, gathering small writes into larger ones. `written` counts what was handed over,\n * gathered or not; `flush` makes all of it durable.\n */\nexport class FileAppender {\n private buffer = new Uint8Array(GATHER);\n private buffered = 0;\n private stored: number;\n\n constructor(readonly bytes: FileBytes, readonly id: string, offset = 0) { this.stored = offset; }\n\n get written(): number { return this.stored + this.buffered; }\n\n async append(chunk: Uint8Array): Promise<void> {\n if (this.buffered + chunk.length > GATHER) await this.drain();\n if (chunk.length >= GATHER) {\n await this.bytes.append(this.id, this.stored, chunk);\n this.stored += chunk.length;\n return;\n }\n this.buffer.set(chunk, this.buffered);\n this.buffered += chunk.length;\n }\n\n private async drain(): Promise<void> {\n if (!this.buffered) return;\n const part = this.buffer.slice(0, this.buffered);\n await this.bytes.append(this.id, this.stored, part);\n this.stored += part.length;\n this.buffered = 0;\n }\n\n async flush(): Promise<void> {\n await this.drain();\n await this.bytes.flush(this.id);\n }\n\n async close(): Promise<void> {\n await this.drain();\n await this.bytes.close(this.id);\n }\n}\n","import type { DiscoveryStatus, GroupMention, GroupMessageFrame, ImageMeta, LinkPreview, PairingProgress, PaymentMethodName, RoutineCard, StatusCard, TaskCard, TypingKind, UsageCard, VideoMeta, VoiceMeta, WirePin, WireReaction } from \"@ghostly/core\";\nimport type { UsdtWalletView } from \"../engine/paymentAdapters/usdtWallet\";\nimport type { ArkWalletView } from \"../engine/paymentAdapters/arkWallet\";\nimport type { BarkWalletView } from \"../engine/paymentAdapters/barkWallet\";\nimport type { FedimintWalletView } from \"../engine/paymentAdapters/fedimintWallet\";\nimport type { SparkWalletView } from \"../engine/paymentAdapters/sparkWallet\";\nimport type { LightningView } from \"../engine/paymentAdapters/providers/lightningService\";\nimport type { LightningCardView } from \"../engine/paymentAdapters/providers/lightningCards\";\nimport type { BitcoinView } from \"../engine/paymentAdapters/providers/bitcoinService\";\nimport type { PaymentReview, PaymentTarget, VapidKeys, WakeTarget, WalletNetwork } from \"@ghostly/core\";\nimport type { ProviderDescriptorView } from \"../engine/paymentAdapters/providers/types\";\nimport type { CapsState, DeliveryMode, DhtDeliveryState, DhtDeliveryView, HoldKind } from \"@ghostly/core\";\nimport type { S3Config } from \"../backup/s3\";\nimport type { BackupReminders } from \"./backupReminder\";\nimport type { TransportCause, TransportEntry, TransportEvent } from \"../engine/transportLog\";\nimport type { PublicProfile, ProfileChoice } from '../profiles/public';\nimport type { NostrContactCache, NostrContactView, NostrSocialSettings, NostrSocialState } from \"../nostr/types\";\nimport type { ProofLedger, ProofAdapter } from \"@ghostly/core\";\nimport type { IdentityDisplay, IdentityLedger, IdentityStatus, IdentityTimelineEntry, SharedIdentity, VerifiedIdentity } from \"@ghostly/core\";\nimport type { DataLinkState, LinkStatus, LiveAttempt, ServiceAd, PairingState, NativeTransport, PairedTransport, TransportDescriptors, TransportWait } from \"@ghostly/core\";\nimport type { CommunityState, GroupByeFrame, GroupCommit, GroupRole, GroupState, GroupStatus } from \"@ghostly/core\";\n\n/** A link as stored in IndexedDB. Same fields Desktop keeps in its ChatSession. */\nexport interface StoredLink {\n /**\n * Paired 1:1 chats: how to wake the contact's closed web app, as it shared it (`wake/1`, WISP 401 § Wake-up push).\n * Absent until it does, and again once it stops sharing or its push service says the subscription is gone.\n */\n peerWake?: WakeTarget;\n /** Paired 1:1 chats: this side's token for this chat in its own wake-ups; a new one whenever the subscription changes. */\n wakeToken?: string;\n /** Paired 1:1 chats: muted here, so the contact is told to forget this side's subscription until it is unmuted. */\n wakeMuted?: boolean;\n deliveryMode?: DeliveryMode;\n dhtDeliveryState?: DhtDeliveryState;\n /** This side's layer-0 capability record and the contact's last good one (WISP 03). */\n capsState?: CapsState;\n publicProfiles?: PublicProfile[];\n profileChoice?: ProfileChoice;\n peerProofs?: ProofLedger;\n /** Identity proofs shared in this chat, both ways (WISP 300). */\n identities?: IdentityLedger;\n /** The contact's Nostr profile, follows and notes, cached under their proven key, loaded only on request. */\n nostrSocial?: Record<string, NostrContactCache>;\n profile?: \"paired-chat/1\";\n participationSeed?: string;\n transportSeeds?: Partial<Record<NativeTransport, string>>;\n peerDescriptors?: TransportDescriptors;\n peerTransports?: PairedTransport[];\n peerFallback?: boolean;\n /** Absent: no choice for this chat, the app's rule applies (\"Automatic\"). */\n preferredTransport?: PairedTransport;\n transportFallback?: boolean;\n /** The chat's connection story, newest last (see engine/transportLog.ts). Local only. */\n transportLog?: TransportEntry[];\n /** Every connection event, for the connection panel, newest last (see engine/transportLog.ts). Local only. */\n transportHistory?: TransportEvent[];\n pairedPeerKey?: string;\n /** The inviter's participation key, from a `ghostly1` code (the joiner's side): the only key that may be pinned. */\n peerParticipationKeyZ32?: string;\n /** Absent only in old releases, where first pin required an explicit code comparison. */\n peerTrust?: { version: 1; verifiedKey?: string; verifiedAt?: number };\n requireSignedSignals?: boolean;\n id: string;\n seedB64: string;\n peerPubKeyZ32: string;\n encKeyB64: string;\n createdAt: number;\n label?: string;\n /**\n * The name the contact asked to be shown by, as they last said it. `\"\"` means a paired contact said they\n * have none (removed, or not shared); absent means nothing was heard yet.\n */\n peerNick?: string;\n /** The contact's profile picture, as they last sent it (checked, small JPEG data URL). */\n peerAvatar?: string;\n /** Present on the side that created the link, until the peer shows up. */\n inviteCode?: string;\n /** Messages deleted on this device, by id, so a republished one is not stored again. */\n deletedIds?: string[];\n /**\n * Files of mine whose offer went on the DHT floor (WISP 403 § Files) and that were deleted here before they went live,\n * by wire id: the next session that carries files/3 says each is cancelled (`pf-abort`), so the contact's bubble does too.\n */\n withdrawnFiles?: string[];\n /** This side's reactions the contact has not confirmed yet (WISP 401 § Reactions), in the order of their numbers. */\n reactionsOut?: WireReaction[];\n /** The chat's pinned message (WISP 400 § Pinned message): the latest pin, either side's; `id` \"\" once unpinned. */\n pin?: StoredPin;\n /** This side's pin the contact has not confirmed yet (WISP 401 § Pinned message): said on each live session until it does. */\n pinOut?: WirePin;\n /** Ways of paying this device allows in this chat. Absent or true: allowed. */\n paymentMethods?: Partial<Record<PaymentMethodName, boolean>>;\n /**\n * For a way of paying, the networks this chat accepts it on (its cards on the Accept side). Absent for a method:\n * every network. A request, a payment or an ask of a network off here is refused, and the contact is told.\n */\n paymentNetworks?: Partial<Record<PaymentMethodName, WalletNetwork[]>>;\n /** Store-and-forward for this contact (WISP 404, `hold/1`). Absent: off, as for every chat from before it. */\n hold?: HoldState;\n /** An edge of a private group (WISP 900): the group, and the member at the other end. Not a chat. */\n group?: string;\n groupPeer?: string;\n /**\n * Not an edge but an entry session of the group's link (`group-entry/1`): `host` on the admin's\n * side, toward a joiner's member key; `guest` on the joiner's, toward the link's entry key.\n */\n groupEntry?: \"host\" | \"guest\";\n /**\n * An edge whose session is up: set when it opens, cleared when it drops while this app runs (not when the app quits).\n * Still set when the app starts, it was live when the app last ran, and is dialled at once (`resume`). Local only.\n */\n edgeLive?: boolean;\n /** When that session opened: a member's offer from before it is not answered after a restart (`resumeFloor`). Local only. */\n edgeLiveSince?: number;\n}\n\n/** What happened to a group's membership, as a line in its history. */\nexport type GroupEvent = \"created\" | \"joined\" | \"gone\" | \"admin\" | \"rotated\" | \"removed\" | \"left\" | \"forked\" | \"picture\" | \"renamed\";\n\n/** A private group as stored: an invitation not yet answered, or a group I am (or was) in. */\nexport interface StoredGroup {\n id: string;\n createdAt: number;\n /** Pending (or accepted, awaiting the welcome) invitation, on the invitee's side. */\n invitation?: {\n name: string;\n /** The inviter's member key, learned over the contact chat: the admin who must have admitted me. */\n admin: string;\n /** The contact chat it came over. */\n linkId: string;\n e: number;\n n: number;\n /** My member seed, once accepted. */\n seedB64?: string;\n /** Chain pieces that precede the welcome of a long chain. */\n pieces: { t: \"group-chain\"; g: string; commits: GroupCommit[] }[];\n /** Joining through the group's link: its entry key, the admin's side of `linkId` (an entry session, not a contact chat). */\n entry?: string;\n /**\n * Removed and invited again over a contact chat: the hash of the genesis of the chain I held. The id alone names no\n * group (anyone may sign a genesis under it): only a welcome whose chain starts there joins the history I kept.\n */\n genesis?: string;\n /**\n * Taken back by who sent it (`group-decline` over its chat), and when: kept a week so the list says so where the\n * invitation was, instead of it going with no word. Nothing is accepted or owed for it; another invitation replaces it.\n */\n takenBack?: number;\n };\n /**\n * On the admin's side: contact chats invited and not answered yet, and when. Kept so that an accept after my app\n * started again still lets them in.\n */\n invited?: Record<string, number>;\n /** On the admin's side: the group's link is on, with this entry key seed (`group-entry/1`). */\n entry?: { seedB64: string; createdAt: number };\n state?: GroupState;\n /** Member key → the contact chat that invited them (or me): the path for courtesy notices. */\n contacts?: Record<string, string>;\n /**\n * Members whose apps said (`paired-groups` version 3, on their edge or the link that admitted them) that they take\n * rosters past eight: an admin grows the group past eight only when every other member is here (WISP 902 § Bounds).\n */\n large?: string[];\n /**\n * On the admin's side: members I let in over a contact chat or the group's link whose edge has not come up with me\n * yet, so whose app may never have had the group's keys (its welcome lost on the way). A `group-decline` from that\n * chat takes such a member out again, and one that knocks on the link again gets its welcome again; one seen on an\n * edge once is a member, whatever its chat says after.\n */\n unseen?: string[];\n /**\n * Removed, then invited again over a contact chat: the names my app knew members by (member key → name). The history\n * stays while the invitation waits, and still names who wrote it; once in again, the names of members who are gone\n * since go on naming what they wrote.\n */\n formerNames?: Record<string, string>;\n /** Removed, then invited again: the removed group as it was, which a Decline puts back (with the history it kept). */\n previous?: StoredGroup;\n /**\n * Past 16 members, with hubs (WISP 902 · Group Mesh § Hubs): the hubs I kept edges with (as a hub, the other hubs),\n * where my edges go when the app starts again, before the beacon is read.\n */\n hubs?: string[];\n /** Members whose edge came up without `paired-groups` version 4: their apps take no hubs, so hubs keep edges with them. */\n legacy?: string[];\n /**\n * I left: the group is gone from the list and its history from the device. What is kept is the\n * edge to the admin, until the admin's commit removing me arrives or `at` is a week old, so\n * a leave said while the admin was away still reaches it. With hubs, the edges to my hubs too, and my\n * signed leave (`bye`) that they carry to the admin.\n */\n left?: { at: number; admin: string; hubs?: string[]; bye?: GroupByeFrame };\n /**\n * I declined an invitation while its inviter's chat (`linkId`) was down: the answer is said when that chat is up, or\n * dropped once `at` is a week old. Alone when nothing else of the group is kept; beside `state` when the invitation\n * was to a group I was removed from, which stays.\n */\n declined?: { at: number; linkId: string };\n /**\n * On the admin's side: members I removed while they could not be told (their edge was down, and no contact chat\n * carried the notice), by member key. `e`: the epoch of the commit that removed them; `at`: when. The edge to each\n * stays until it opens and that commit goes over it (`told`, the first time), or `at` is a week old: a member removed\n * while its app was closed otherwise never learns it, since every member closes the edge to someone out of the\n * roster. `opened`: when the edge's connection first opened; it closes a while after, told or not.\n */\n farewells?: Record<string, { at: number; e: number; told?: number; answered?: number; opened?: number }>;\n /** The group's pinned message (WISP 400 § Pinned message): the latest pin; `id` \"\" once unpinned. */\n pin?: StoredPin;\n /**\n * When the latest message of another member (`peer`) and the latest one that names me (`mention`) reached this\n * device, where that is later than the message's own time (`cameAt`): what makes the group unread survives a restart.\n */\n came?: { peer?: number; mention?: number };\n /** A community group (`group-community/1`) I am in: its session state. Mesh groups use `state`. */\n community?: CommunityState;\n /**\n * Joining a community group through its link (`group2/…`), or again after my admission lost a\n * race: my member seed, the entry session, and what arrived of the welcome.\n */\n joining?: { g: string; host: string; seedB64: string; linkId: string; name: string; inviter: string; invitedAt?: number; pieces: unknown[]; since: number; check?: boolean };\n}\n\nexport interface GroupMemberView {\n key: string;\n role: GroupRole;\n me: boolean;\n nick?: string;\n /** The pairwise edge to this member is open, or a hub I am connected to reaches them (always true for me). */\n online: boolean;\n /** Reached through a hub, not an edge of mine (WISP 902 · Group Mesh § Hubs). */\n viaHub?: boolean;\n /** A hub of the group now (listed in its beacon), and what the admin said: pinned as a hub, or never one. */\n hub?: boolean;\n hubRole?: \"pin\" | \"exclude\";\n /** Messages of the current epoch known to be missing from this member. */\n missing: number;\n /** The pairwise edge to this member as the engine sees it: absent for me, and until the edge exists. */\n edge?: GroupEdgeView;\n}\n\n/** One edge of a group's mesh (WISP 902): a paired link toward one member, which carries the group to and from them. */\nexport interface GroupEdgeView {\n linkId: string;\n /**\n * `open`: frames flow (the member is reachable). `connecting`: the member answered and the channel is being set up.\n * `waiting`: nothing heard from the member's app yet. `error`: the last attempt failed (`error` says why).\n */\n state: \"open\" | \"connecting\" | \"waiting\" | \"error\";\n /** The transport carrying the edge while it is open: WebRTC, or Iroh or HyperDHT where one side has none. */\n transport?: PairedTransport;\n /**\n * Waiting because this device has no free native slot for it (a member reached over Iroh or HyperDHT, and the\n * device's native listeners held by chats and other group links): it is tried again as one frees up.\n */\n noSlot?: true;\n /** When this device last heard from the member on this edge, in ms (0: never). */\n lastSeenAt: number;\n /** The engine's own words for why the last attempt failed (English, for logs and details); `cause` sums it up. */\n error?: string;\n /**\n * What `error` comes down to, for a short status: `relays`, its packets could not go out or be read (the Pkarr relays\n * failed); `session`, a connection opened and dropped in a way that says nothing about the member (it is dialled\n * again); `other`, anything else (a refused key, no common transport...).\n */\n cause?: GroupEdgeCause;\n}\n\nexport type GroupEdgeCause = \"relays\" | \"session\" | \"other\";\n\n/**\n * How far a join through a group's link got, as the joiner can know it: the knock is being left,\n * it is there for the admin's app to read, that app opened the entry session, it let me in. Or a member's app\n * answered that there is no room yet (`group-full`, WISP 902 § A full group): `full`, or `blocked` while the group\n * waits for every member's app to take more than eight; the joiner goes on knocking.\n */\nexport type GroupJoinStage = \"knocking\" | \"knocked\" | \"answered\" | \"admitted\" | \"full\" | \"blocked\";\n\nexport interface GroupView {\n id: string;\n name: string;\n createdAt: number;\n /** Which kind of group: a private mesh of up to 32 (`group-mesh/1`) or a community (`group-community/1`). */\n profile: \"mesh\" | \"community\";\n /** Absent while it is only an invitation. */\n status?: GroupStatus | \"lost\";\n statusReason?: string;\n epoch?: number;\n myKey?: string;\n isAdmin: boolean;\n /**\n * Admin work is off for this group on this device (WISP 06 § Forced takeover): no commit is signed and no door duty\n * taken until the person turns on \"Manage groups from this device\" (`setGroupManage`).\n */\n adminOff?: true;\n members: GroupMemberView[];\n /** On the invitee's side, until the welcome arrives. */\n /**\n * `waiting`: joining a community through its link, how many others were knocking with me when I last knocked.\n * `since`: joining a community through its link, when the knock started (a group kept here, knocking to get back in,\n * was created long before).\n * `full`: with stage `full` or `blocked`, the members the group has and the most it holds, as the answer said.\n * `unanswered`: accepted over a contact chat, said again as often as it is, and the inviter's app answered nothing\n * while that chat was up: the answer is offered again.\n * `takenBack`: who sent it took it back (the group is gone from its app, or the admin role from it): nothing to answer.\n */\n invitation?: { linkId: string; contact: string; admin: string; members: number; accepted: boolean; unanswered?: boolean; takenBack?: boolean; viaLink?: boolean; stage?: GroupJoinStage; waiting?: number; since?: number; full?: { n: number; max: number } };\n /** The group's link while it is on (`group1/<id>/<entry key>`); only the admin who made it sees it. */\n entryLink?: string;\n /** Contacts (by chat id) invited by me and not yet in. */\n invited: string[];\n /** Contact chats that are members, by chat id → member key. */\n memberLinks: Record<string, string>;\n lastMessageAt: number;\n /** When the latest message from another member arrived (what makes the group unread; mine and membership lines do not); absent for none. */\n lastPeerMessageAt?: number;\n /** When the latest message that names me arrived; absent for none. */\n lastMentionAt?: number;\n /** The latest reaction in the group, for the chat list. */\n lastReaction?: ReactionNote;\n /** The group's pin (WISP 400 § Pinned message): `id` \"\" once unpinned; absent when nothing was ever pinned. */\n pin?: PinView;\n canSend: boolean;\n /** The group's picture (a JPEG data URL the engine checked), set by its admin; absent for none. */\n picture?: string;\n /**\n * A private group past 16 members that runs on hubs (WISP 902 · Group Mesh § Hubs): `hub` when I am one. Absent for\n * the full mesh.\n */\n hubs?: { hub: boolean };\n /** Community groups: how this device is connected (a hub for others, or through hubs). */\n community?: { hub: boolean; hubs: number; connected: number };\n /** Private groups: the members typing now, in the order they started (WISP 902 · Group Mesh § Typing). Never stored. */\n typing?: GroupTypingView[];\n /** Private groups: this side's wake-up tokens, one per member it shared its subscription with (the push worker's table). */\n wakeTokens?: string[];\n /** Private groups: its members were told not to wake this side while the group is muted (`setWakeMuted`). */\n wakeMuted?: boolean;\n /**\n * The names of authors no longer in the roster (removed, left, or back with a new member key), by\n * member key, so their earlier messages still say who wrote them. Absent when there are none.\n */\n formerNames?: Record<string, string>;\n}\n\n/** A member typing in a group: `kind` when it is not plain typing, and a bot's status line, sanitized as in a 1:1 chat. */\nexport interface GroupTypingView { key: string; kind?: Exclude<TypingKind, \"typing\">; status?: string }\n\n/** One item held in this device's storage for the contact, or on its way there. */\nexport interface HeldEntry {\n seq: number;\n /** The id on the wire: the text's wire id, the file's wire id or the payment id. */\n id: string;\n /** The chat message this item is, for its delivery state. */\n messageId: string;\n kind: Exclude<HoldKind, \"manifest\">;\n /** What to rebuild the bundle from: the file's local id or the payment id; text is on the message. */\n ref?: string;\n /** The object's name in storage, chosen before the first upload and kept across retries. */\n name: string;\n bytes: number;\n ts: number;\n expires: number;\n state: \"queued\" | \"held\" | \"failed\";\n error?: string;\n}\n\n/** Everything store-and-forward keeps per chat: the switch, what the contact said, sequences and the outbox. */\nexport interface HoldState {\n /** This device offers `hold/1` in this chat: it accepts held items, and holds items for the contact when it can. */\n enabled: boolean;\n /** The contact's app accepts held items, as it last said (handshake or session). */\n peerAllows?: boolean;\n /** This device's mailbox folder for the contact in its storage, chosen once. */\n mailbox?: string;\n /** Sequence of the last item held for the contact. */\n outSeq: number;\n /** Highest sequence received from the contact and stored. */\n inSeq: number;\n /** Highest sequence the contact said it received of what was held for it. */\n peerAck: number;\n /** This device's pointer revision, and the contact's last seen one. */\n pointerRev: number;\n peerPointerRev: number;\n /** When the manifest and its addresses were last signed (they live seven days). */\n manifestSignedAt?: number;\n outbox: HeldEntry[];\n /** Items from the contact refused on the way in (changed, oversized, not for this chat). */\n refused: number;\n /** Their sequences, the last 32, told to the contact on this device's pointer. */\n refusedSeqs?: number[];\n /** Ways of paying the contact allowed at the last session, for requests held while it is away. */\n peerPaymentMethods?: PaymentMethodName[];\n}\n\n/** A file attached to a message. The bytes live in the `files` store under `id`. */\nexport interface MessageFile {\n id: string;\n name: string;\n size: number;\n mime: string;\n /** A voice message: its length and the shape of its sound. */\n voice?: VoiceMeta;\n /** A video: its length, size and a small first frame, when the sender sent them. */\n video?: VideoMeta;\n /** A picture: its size as it is shown, when the sender sent it. */\n image?: ImageMeta;\n}\n\nexport interface FileTransferView {\n state: \"transferring\" | \"done\" | \"failed\";\n transferred: number;\n size: number;\n error?: string;\n /**\n * files/3 (WISP 501 rev 0.3), while `transferring`: where it stands when it is not moving. `waiting`: no live\n * connection (it goes on by itself); `asking`: the receiver's person has not decided (on the receiving side,\n * this person); `queued`: accepted, waiting its turn; `paused`; `verifying`: every byte there, checking the digest.\n */\n stage?: \"preparing\" | \"waiting\" | \"asking\" | \"queued\" | \"paused\" | \"verifying\";\n /** files/3: the transfer can be paused, resumed and cancelled from here. */\n direction?: \"in\" | \"out\";\n pausedBy?: \"me\" | \"peer\";\n /** Bytes per second lately, while moving. */\n rate?: number;\n /** Receiving, `asking`: bytes this device can still take for files, when it says. */\n room?: number | null;\n /** Failed but the sender can offer it again (files/3). */\n retry?: boolean; /**\n * files/3, unfinished and not moving for a while (or not since the app started): the sender can send it again\n * (`resend`), the receiver ask for it again (`request`). Either goes on from what the receiver holds.\n */\n stalled?: boolean;\n /**\n * A group file (WISP 503) waiting, `waiting`: why. `nobody`: nobody this device is connected to has it; `busy`: its\n * holders answered busy; `damaged`: what came failed the author's digest (`stalled`: its person can ask again).\n */\n wait?: \"nobody\" | \"busy\" | \"damaged\";\n /** The same, in a few words (\"Nobody you are connected to has this file yet\"). */\n note?: string;\n}\n\n/** Ecash held by this peer. One row per proof; `reserved` while an operation is using it. */\nexport interface StoredProof {\n mint: string;\n id: string;\n amount: number;\n secret: string;\n C: string;\n dleq?: unknown;\n /** Handed to the mint for a Lightning payment that has not settled: not spendable, not yet gone. */\n reserved?: boolean;\n /**\n * Brought back by a profile restore: a copy of ecash as it was when the backup was made, which the profile it was\n * copied from (or this one, before the backup was restored) may have spent since. The mint is asked once, and a\n * proof it reads spent is dropped (see `CashuWallet.checkRestored`).\n */\n unchecked?: boolean;\n}\n\n/** A Lightning payment the mint has not settled yet. Its proofs stay in the wallet, reserved, until it does. */\nexport interface PendingMelt {\n /** The melt quote id. */\n quote: string;\n mint: string;\n /** The invoice, so it is not paid a second time while this one is in flight. */\n request: string;\n /** What the invoice pays. */\n amount: number;\n /** The reserved proofs handed to the mint. */\n secrets: string[];\n /** Everything that left the balance for this payment: the proofs above plus any swap fee. */\n outlay: number;\n /** NUT-08 blank outputs (`OutputData.serialize`), to unblind the fee change once the mint returns it. */\n outputs: unknown[];\n note?: string;\n /** Set when the invoice pays a contact's payment request. */\n paymentId?: string;\n createdAt: number;\n}\n\n/**\n * A swap at a mint, written down before the mint is asked, with everything that finishes it: the mint signs the\n * outputs of `swap` and nothing else, so its signatures can be asked for again (NUT-09) for as long as this is kept.\n * Deleted in the transaction that stores what the swap brought, or once the mint says it never happened.\n */\nexport interface PendingSwap {\n id: string;\n mint: string;\n /** `receive`: a token redeemed into this wallet. `send`: this wallet's ecash split, for a token or a Lightning payment. */\n kind: \"receive\" | \"send\";\n /** cashu-ts' `SerializedSwapPreview`: the inputs and the blinded outputs with their secrets. As secret as the ecash itself. */\n swap: unknown;\n createdAt: number;\n /** When the request to the mint ended with no answer. The mint may still act on it for a moment after that. */\n attemptEndedAt?: number;\n /**\n * The inputs were given back (the swap was overdue with nothing signed), and it holds nothing any more. Still asked\n * about for a while: a request that reached the mint late is recovered from what is written here.\n */\n released?: boolean;\n /** What it brought is stored; only the chat of its payment is still to be told (the app closed in between). */\n finished?: boolean;\n /**\n * Its inputs read spent at a mint that cannot say what it signed (no NUT-09): how many times that was the answer.\n * Kept, since the outputs are the only way back to that ecash, and asked about less and less often (`askAt`).\n */\n stuck?: number;\n /** Not asked about before this time. */\n askAt?: number;\n /** Came with a restored copy of the profile: what it brings is as old as the copy, and is checked like the copy's ecash. */\n restored?: boolean;\n /** `receive`: the token being redeemed. Redeeming it again while this is kept finishes this swap, and makes no other. */\n token?: string;\n /** `receive`: the history line of the ecash, written with it. */\n tx?: WalletTx;\n /** `receive`: the payment record written with the ecash (a contact's payment received, or one of ours taken back). */\n payment?: StoredPayment;\n}\n\n/** A Lightning invoice the mint issued for us; paid invoices turn into ecash. */\nexport interface StoredQuote {\n quote: string;\n mint: string;\n amount: number;\n invoice: string;\n createdAt: number;\n expiresAt: number | null;\n /** Set when the invoice belongs to a payment request sent to a peer. */\n paymentId?: string;\n /**\n * The mint says it issued this quote's ecash, but the wallet never stored it. Kept, not polled: it is the\n * only record of sats the user paid for.\n */\n issuedUnclaimed?: boolean;\n /** The mint said this invoice is paid, and its ecash is not claimed yet (the claim is tried again every round). */\n paid?: boolean;\n /**\n * The mint marks its invoices paid by itself (a test mint, `paysItsOwnInvoices`): its \"paid\" says nothing about a\n * payer. Not polled, never minted by itself: a payer saying it paid (`CashuWallet.vouch`) lets it be minted.\n */\n held?: boolean;\n /** Asked for on purpose, as test coins from a test mint (\"Get test coins\"): minted as soon as the mint says paid. */\n testCoins?: boolean;\n /**\n * The blinded outputs the mint is asked to sign for this invoice (`OutputData.serialize`), written down before it\n * is asked: the ecash it issues is for these, and its signatures for them can be asked for again (NUT-09).\n */\n outputs?: unknown[];\n}\n\nexport type PaymentState =\n | \"pending\" // sent, no word from the peer yet / request waiting to be paid\n | \"settled\"\n | \"failed\"\n | \"reclaimed\"; // the peer never took the ecash and it is back in the wallet\n\n/** A payment or payment request in a chat, by id. */\nexport interface StoredPayment {\n /** A request made because the contact asked to pay (its `pay-ask` id), or, on the payer's side, the ask it answers. */\n ask?: string;\n target?: PaymentTarget;\n txid?: string;\n id: string;\n linkId: string;\n kind: \"payment\" | \"request\";\n direction: \"in\" | \"out\";\n amount: number;\n unit: string;\n memo?: string;\n state: PaymentState;\n error?: string;\n createdAt: number;\n mint?: string;\n /** Outgoing ecash, kept until the peer confirms so it can be reclaimed. */\n token?: string;\n /** Requests: how the payer can pay. */\n invoice?: string;\n mints?: string[];\n /**\n * Real money or test coins: only a wallet of this network pays a request, or made a payment. Absent on records\n * from before wallets had their own network: its target, mints or invoice say (see `paymentNetwork`).\n */\n network?: WalletNetwork;\n /** Fedimint requests: the federations the payee takes ecash of (their ids). */\n federations?: string[];\n /** Fedimint payments: the federation of the notes, and the client operation that spent (or redeems) them. */\n federation?: string;\n fedimintOp?: string;\n requestId?: string;\n /** Requests we pay: a Lightning payment is in flight at the mint, so paying again would pay twice. */\n lightningPending?: boolean;\n /** Requests we pay: this device paid it over Lightning (the payee's wallet cannot say who paid an invoice). */\n paidHere?: boolean;\n /**\n * A request to a whole group (WISP 902 § Payments): the group's id. Its `linkId` is `group:<id>`, it went to\n * every member over their edges, and the first member whose payment settles it pays it; later ones are refused.\n */\n group?: string;\n /**\n * A request closed by the one who made it (their wallet for it was removed): it can no longer be paid, and a\n * payment still made to what it named is lost. Set on both sides, with `state` \"failed\".\n */\n closed?: boolean;\n}\n\n/** What pages see of a payment: everything but the token. */\nexport type PaymentView = Omit<StoredPayment, \"token\">;\n\nexport interface MintView {\n url: string;\n name: string;\n balance: number;\n /** Null until the mint answered once. */\n info: MintInfoView | null;\n /** What it still waits for (see `mintAwaiting`), when it does: removing it asks about these first. */\n awaiting?: WalletAwaitingView[];\n}\n\n/** What a mint says about itself and what it charges. */\n/** What a pasted piece of ecash says about itself. Mirrors `CashuInspection` in the UI's platform contract. */\nexport type CashuInspection =\n | { kind: \"token\"; amount: number; unit: string; mint: string; memo?: string; accepted: boolean }\n | { kind: \"request\"; amount: number | null; unit: string; mints: string[]; description?: string };\n\nexport interface MintInfoView {\n version?: string;\n motd?: string;\n /**\n * Fee for spending ecash, in sats per thousand proofs (NUT-02). A swap,\n * a redeem or a Lightning payment spends a handful of proofs and is charged\n * the sum, rounded up. 0 means spending ecash is free.\n */\n inputFeePpk: number;\n /** Lightning in and out: allowed amounts in sats, null when the mint sets no bound. */\n receive: { min: number | null; max: number | null } | null;\n send: { min: number | null; max: number | null } | null;\n}\n\nexport type WalletTxKind =\n | \"lightning-in\"\n | \"lightning-out\"\n | \"ecash-in\" // from a contact, or a pasted token\n | \"ecash-out\"\n | \"reclaimed\"\n | \"fee\"; // a Lightning payment failed, and the mint kept its fee for preparing it (`amount` 0)\n\n/** One movement of the wallet. `fee` is exact: what left the balance beyond `amount`, or what a redeem cost. */\nexport interface WalletTx {\n id: string;\n timestamp: number;\n mint: string;\n kind: WalletTxKind;\n /** Sats that moved, always positive; the kind gives the direction. */\n amount: number;\n fee: number;\n note?: string;\n}\n\n/** The kinds of wallet a profile can have: each is one card of the deck, on one network. */\nexport type WalletType = \"cashu\" | \"lightning\" | \"arkade\" | \"bark\" | \"spark\" | \"bitcoin\" | \"fedimint\" | \"usdt\";\nexport const WALLET_TYPES: readonly WalletType[] = [\"cashu\", \"lightning\", \"arkade\", \"bark\", \"spark\", \"bitcoin\", \"fedimint\", \"usdt\"];\n\n/** One network's wallets, whole: what `WalletView` shows of the network in use, for either network. */\nexport interface NetworkWalletsView {\n ark?: ArkWalletView;\n bark?: BarkWalletView;\n fedimint?: FedimintWalletView;\n spark?: SparkWalletView;\n usdt?: UsdtWalletView;\n /** The network's default Lightning card for receiving (what a caller from before cards reads as its Lightning). */\n lightning?: LightningView;\n /** Every Lightning card of the network, in the order they were added: each its own source, balance and history. */\n lightnings?: LightningCardView[];\n bitcoin?: BitcoinView;\n /** This network's Cashu mints (test mints and mints on this machine are Testnet's). */\n mints: MintView[];\n balance: number;\n /** Cashu: sats held for a payment or a swap the mints have not settled yet. Not in `balance`, and not gone. */\n setAside?: number;\n /** Cashu: swaps at these mints that are not finished yet, and the sats in them. Removing the wallet names them. */\n openSwaps?: number;\n swapsAmount?: number;\n /** Cashu: sats in swaps whose inputs read spent at a mint that cannot say what it gave for them. Not in `setAside`. */\n unconfirmed?: number;\n /** This network's history, newest first. */\n history: WalletTx[];\n feesPaid: number;\n /** What this network's wallets still wait for: removing one reads it (see `walletRemoval`). Always set by the engine. */\n awaiting?: WalletAwaitingView[];\n}\n\n/**\n * Something one wallet still waits for. `request`: a request of ours still open in a chat that only this wallet can\n * be paid through; `invoice`: an invoice of its own, not paid and not expired; `paid`: an invoice the mint says is\n * paid whose ecash is not claimed yet; `unclaimed`: ecash the mint says it issued for an invoice of ours that this\n * wallet never received; `sent`: ecash sent from it that the contact has not taken yet. Removing the wallet loses\n * what is paid to the first four; `sent` Cashu comes back once its mint is added again.\n */\nexport interface WalletAwaitingView {\n /** The wallet it goes through (Lightning through the Cashu mints is the Cashu wallet's). */\n type: WalletType;\n /** `swap`: sats in an exchange with a Cashu mint that the mint has not settled yet (`PendingSwap`). */\n kind: \"request\" | \"invoice\" | \"paid\" | \"unclaimed\" | \"sent\" | \"swap\";\n /** In the wallet's base unit: sats, or the token's smallest unit for USDT. */\n amount: number;\n /** The chat payment it belongs to, when it does. */\n paymentId?: string;\n /** Lightning: the card it goes through. */\n card?: string;\n}\n\n/**\n * A wallet the profile has: one type on one network, one of each but Lightning, which a network can have several\n * cards of. `id` is `<type>:<network>`, and `lightning:<network>:<card>` for a Lightning card. Its seed and secrets\n * stay in the engine; `config` holds only what can be shown (a server, a source).\n */\nexport interface WalletInstanceView {\n id: string;\n type: WalletType;\n network: WalletNetwork;\n config: Record<string, string>;\n /** Lightning: the card's id on its network, its name, and whether it is the network's default for receiving. */\n card?: string;\n name?: string;\n receive?: boolean;\n /**\n * At home on another device (WISP 06 § Wallets that stay home): that device's name (\"\" when this device does not know\n * it), and when its coins expire (Ark, Bark). It is never opened here, and takes no payment here.\n */\n home?: { device: string; expiresAt?: number };\n}\n\n/**\n * What New can make: one type on one network. `available` false: `reason` says why (Mainnet not validated yet,\n * one already there). `needs`: the one thing it asks for, when it cannot be made in one click.\n */\nexport interface WalletOffer {\n type: WalletType;\n network: WalletNetwork;\n available: boolean;\n reason?: string;\n /** Already made: its card is in the deck. */\n exists?: boolean;\n /** A network can have several of it (Lightning): New adds one more, whatever `exists` says. */\n several?: boolean;\n needs?: \"invite\" | \"provider\" | \"apiKey\";\n /** Lightning and on-chain: the sources that can be picked on this network. */\n providers?: ProviderDescriptorView[];\n}\n\n/** What New asks the engine to make (see `walletCreate`). */\nexport interface WalletCreate {\n type: WalletType;\n network: WalletNetwork;\n /** Lightning and on-chain: the source, and the values of its form (blank ones take the network's default). */\n providerId?: string;\n values?: Record<string, string>;\n /** Fedimint: the federation's invite code. */\n invite?: string;\n /** Spark on Mainnet: the person's Breez API key. */\n apiKey?: string;\n}\n\n/** What Remove asks the engine to take away (see `walletRemove`). */\nexport interface WalletRemove {\n type: WalletType;\n network: WalletNetwork;\n /** Lightning: the card to remove; absent, the network's default for receiving. */\n card?: string;\n /** The person confirmed that what it holds on this device becomes unreachable without its backup. */\n acceptLoss?: boolean;\n}\n\n/** \"Get test coins\" on a Testnet wallet: which one asks its faucet. */\nexport interface WalletTestCoins {\n type: WalletType;\n network: WalletNetwork;\n /** Lightning: the card asking (only the Cashu mints' card has a faucet); absent, the default one. */\n card?: string;\n}\n\n/** What the faucet gave: `amount` in the wallet's own unit (\"test sats\", \"TEST-USDT\"). */\nexport interface TestCoinsResult {\n amount: number;\n unit: string;\n /** Asked, and on its way (a transaction the chain has not confirmed): it shows up in a few seconds. */\n pending?: boolean;\n}\n\n/**\n * The first-run wallet setup of a new profile: the Mainnet kinds still to make, and why the last try at each failed.\n * Kept in the settings. Absent on a profile from before it, which is never set up by itself.\n */\nexport interface WalletSetupRecord {\n /** Kinds not made yet, tried again at each start. A kind with no Mainnet wallet here yet (on-chain) waits quietly. */\n left: WalletType[];\n /** Why the last try failed, per kind still left. */\n failed?: Partial<Record<WalletType, string>>;\n}\n\n/** What the Wallet page shows of the first-run setup: whether it runs now, and each kind it could not make. */\nexport interface WalletSetupView {\n running: boolean;\n failed: { type: WalletType; network: WalletNetwork; reason: string }[];\n}\n\nexport interface WalletView {\n /** The first-run setup, while it runs or has a kind it could not make; absent otherwise. */\n setup?: WalletSetupView;\n /** Both networks' wallets, open side by side. The flat fields below are Mainnet's, for a caller naming no network. */\n networks?: Record<WalletNetwork, NetworkWalletsView>;\n /** The wallets this profile has, in the deck's order. */\n wallets?: WalletInstanceView[];\n /** What New can make on each network. */\n offers?: WalletOffer[];\n ark?: ArkWalletView;\n /** Second's Ark (Bark): a different Ark server from `ark`, not interchangeable with it. */\n bark?: BarkWalletView;\n /** Federations joined with an invite code, and their ecash. */\n fedimint?: FedimintWalletView;\n /** Spark, wallet to wallet (the Breez SDK's Spark wallet; it can also be the Breez Lightning source). */\n spark?: SparkWalletView;\n usdt?: UsdtWalletView;\n /** The Lightning source of this mode (the Cashu mints by default) and its latest operations. */\n lightning?: LightningView;\n lightnings?: LightningCardView[];\n /** The on-chain Bitcoin source of this mode, if one is set up. */\n bitcoin?: BitcoinView;\n intents?: PaymentReview[];\n mints: MintView[];\n balance: number;\n /** Cashu: see `NetworkWalletsView`. */\n setAside?: number;\n openSwaps?: number;\n swapsAmount?: number;\n unconfirmed?: number;\n /** Newest first. */\n history: WalletTx[];\n feesPaid: number;\n /** Each Mainnet wallet's backup reminder, by wallet id: the page works out which asks now (`backupDue`). */\n backupReminders?: BackupReminders;\n}\n\nexport interface StoredMessage {\n wireId?: string;\n /**\n * `held`: in this device's storage, waiting for the contact to come back (WISP 404).\n * `queued`: unconfirmed, and sent again by itself under the same id once the chat can carry it.\n * `waiting`: not sent yet; it goes by itself when the chat can carry it (live, or the DHT text before it\n * confirmed), and can be cancelled meanwhile (\"Sends when live\", WISP 400).\n */\n delivery?: \"sending\" | \"sent\" | \"queued\" | \"waiting\" | \"held\" | \"delivered\" | \"failed\";\n deliveryError?: string;\n /** Until when a `queued` message is sent again by itself; after that it waits for Retry. */\n resendUntil?: number;\n /**\n * A file of mine that waits for the live link, said on the DHT floor meanwhile (WISP 403 § Files): `sent` its offer\n * went, `seen` the contact's app took it (its bubble is in place there), `unfit` it cannot go there (it waits as before).\n */\n fileOnFloor?: \"sent\" | \"seen\" | \"unfit\";\n linkId: string;\n id: string;\n text: string;\n sender: \"me\" | \"peer\";\n /**\n * The row's place in its history, which is sorted by it (WISP 400, requirement 10). Mine: when I sent it. Received:\n * when it was first stored on this device (`arrivalKey`), never the sender's clock, so a conversation reads in the\n * order things happened here. A row received before `sentAt` existed keeps the sender's time it was stored under.\n */\n timestamp: number;\n /** Received: when the sender says it sent it, by its own clock. Shown (`shownTime`), never sorted by. */\n sentAt?: number;\n /** `hold`: through the sender's storage while the other side was away (WISP 404). */\n via: \"pkarr\" | \"datalink\" | \"hold\";\n nick?: string;\n file?: MessageFile;\n paymentId?: string;\n /** Group messages: the member key of the sender. */\n member?: string;\n /** Group history lines that are not messages. */\n event?: GroupEvent;\n /** Group history: a payment between members, as the group knows it (WISP 902 § Payments). */\n groupPay?: GroupPayNote;\n /** Group messages: the places of the text that name members, by member key (WISP 902 § Mentions). */\n mentions?: GroupMention[];\n /** A group message someone else sent that names me (or everyone). */\n mentioned?: true;\n /**\n * A private group's message from another member: the frame it came in (sealed, signed by its author), kept on the\n * group's newest rows to hand on to members who were away (`GROUP_LIMITS.relay`, WISP 902 § Catch-up). The engine's\n * own: a row goes to a page without it (`withoutFrame`).\n */\n frame?: GroupMessageFrame;\n /** How this message travelled, as the engine saw it go or come (the message's details view). */\n details?: MessageDetails;\n /** A link preview that came with the text (WISP 401 § Link previews): made by the sender's app, never fetched here. */\n preview?: LinkPreview;\n /** The message this one answers (WISP 400 § Replies). */\n replyTo?: MessageReply;\n /**\n * Reactions to it (WISP 400 § Reactions), one per person: `me`, `peer` (a 1:1 chat's contact) or a group member's\n * key. One taken back stays with an empty emoji, so an older reaction arriving late does not bring it back.\n */\n reactions?: Record<string, MessageReaction>;\n /** A text edited after it was sent (WISP 400 § Edits): `text` is the latest version. */\n edit?: MessageEdit;\n /**\n * A forwarded text or file (WISP 400 § Forwards): how many times it has been forwarded, this hop included. Only the\n * count: never who wrote it first, nor where it came from.\n */\n forwarded?: number;\n /**\n * A bot's status card (WISP 405 · Status Cards): a task or a routine shown instead of `text`, which is its fallback.\n * It belongs to the version it came with: an edit brings its own, or leaves the message a text.\n */\n card?: StatusCard;\n /**\n * A reply that presses a button of this side's own message (WISP 406 · Message Buttons), as the author's engine took\n * it: set only when the button was open for this person. What `button.pressed` reports.\n */\n press?: MessagePress;\n /**\n * A card of mine (WISP 405 · Status Cards, WISP 406 · Message Buttons) whose text went on the DHT floor or into a hold,\n * which carry text alone: `due` until the card goes again live, as an edit of the card alone; `sent` once it did, or\n * once it was given up (too old).\n */\n cardRestore?: \"due\" | \"sent\";\n /** What `cardRestore` was named before it covered every kind of card (buttons only): read for rows kept since. */\n buttonsRestore?: \"due\" | \"sent\";\n}\n\n/**\n * A message that carries a task, a routine or a usage card, as the Tasks board and the chat list read it across every\n * chat and group (`statusCardIndex`): where it is (`linkId`: a chat's id, or `group:<id>`), its message, who sent it (`member`: a group\n * member's key, absent for my own), when it was sent and when its last edit was made. Nothing else of the message.\n */\nexport interface CardIndexRow {\n linkId: string;\n id: string;\n card: TaskCard | RoutineCard | UsageCard;\n sender: \"me\" | \"peer\";\n member?: string;\n timestamp: number;\n editedAt?: number;\n}\n\n/** A page of a chat's history, oldest first, and whether older messages remain (`messagePage`). */\nexport interface MessagePage { messages: StoredMessage[]; more: boolean }\n\n/**\n * A chat's pin as kept (WISP 400 § Pinned message): the message's id both sides know (`id`, \"\" unpinned), the pinner's\n * number (the highest wins), who pinned (`me`, `peer`, or a member's key) and when it came here. `messageId`: the row\n * here, when it was here then. A private group's keeps the pinner's signature (`k`, `sig`), to pass it on.\n */\nexport interface StoredPin extends WirePin { by: string; at: number; messageId?: string; k?: string; sig?: string }\n\n/** A chat's pin, as the pages and the CLI see it. `id`: the id both sides know the message by (\"\" once unpinned); `messageId`: the row here. */\nexport interface PinView { id: string; by: string; at: number; messageId?: string }\n\n/** One person's reaction to a message: the emoji (\"\" once taken back), their number (the highest wins), when it came. */\nexport interface MessageReaction { e: string; n: number; at: number }\n\n/** The latest reaction in a chat, as the chat list says it: \"Ana reacted ❤️ to \"…\"\". */\nexport interface ReactionNote {\n at: number;\n /** Who reacted: `me`, `peer`, or a group member's key. */\n by: string;\n emoji: string;\n /** A line of the message it went to. */\n snippet: string;\n /** The message was mine. */\n mine: boolean;\n /** The message's id here: an edit of it changes `snippet`. */\n message?: string;\n}\n\n/** How a message was edited (WISP 400 § Edits). */\nexport interface MessageEdit {\n /** The edit shown: its number (1 for the first; the highest one wins) and when its author made it. */\n seq: number;\n at: number;\n /** Earlier versions, oldest first (the newest `EDIT_HISTORY_KEEP`): when each was written, and its text. */\n history: MessageVersion[];\n /** Mine: the contact has not confirmed this edit yet. It goes by itself once the chat is live and both sides offer edit/1. */\n pending?: true;\n /**\n * Mine: the engine's own edit that sends a question's buttons again, the text unchanged (WISP 406 · Message Buttons),\n * never one a person or a bot made. The bot's event stream reports none for it; a later edit makes a new one without it.\n */\n restore?: true;\n}\n\nexport interface MessageVersion { at: number; text: string }\n\n/**\n * The message a reply answers, as kept with the reply (WISP 400 § Replies). What the replier's app saw of it goes\n * with the reply; the receiver keeps its own view of the original when it has it (`messageId`), and the line and the\n * author then come from there, not from the wire.\n */\n/** A button press on this side's message (WISP 406 · Message Buttons). */\nexport interface MessagePress {\n /** The message with the buttons, its id here. */\n messageId: string;\n button: string;\n label: string;\n /** Named by its label or id in the text, not by the reply (the DHT floor, or an app without buttons). */\n inferred?: true;\n}\n\nexport interface MessageReply {\n /** The original's id in this chat as both sides know it: a paired chat's wire, file or payment id; a group message id. */\n id: string;\n /** A line of the original, plain and short (`replySnippet`); empty when only its id came (over the DHT). */\n snippet: string;\n /** Who wrote the original; absent when nothing says (only its id came, and it is not here). */\n from?: \"me\" | \"peer\";\n /** A group's original: its author's member key. */\n member?: string;\n /** A button press (WISP 406 · Message Buttons): the original's button this reply presses, its text the label. */\n button?: string;\n /**\n * The original here, when it was found in this chat as the reply was kept (always, for a reply sent here). Without\n * it the line is only what the replier's app said: shown, but marked as not checked.\n */\n messageId?: string;\n}\n\n/**\n * The way a message went, as the engine saw it at that moment: a live transport, the DHT floor (a paired chat's\n * `dht-text/1` envelope), the sender's storage (`hold/1`), or a chat from before pairing (`legacy-*`).\n */\nexport type MessagePath = PairedTransport | \"dht\" | \"hold\" | \"legacy-datalink\" | \"legacy-dht\";\n\n/** One send of a message: when, over what, and how it ended. */\nexport interface MessageSend {\n at: number;\n path: MessagePath;\n /** The live session crossed a relay (WISP 100, \"Relayed\"); the relays' hosts. */\n relayed?: boolean;\n relays?: string[];\n /** The round trip measured on the session at the time. */\n rttMs?: number;\n /** Absent while the send is under way. */\n result?: \"sent\" | \"failed\";\n error?: string;\n}\n\n/**\n * What the engine remembers about one message's travels, for its details view (WISP 400 § Message details): facts\n * as they were when the message went or came, that nothing else keeps. Kept on the message row, bounded by\n * `MESSAGE_DETAILS_MAX_BYTES`. Never a key, a seed or anything else that opens the chat.\n */\nexport interface MessageDetails {\n /** Sends of this message, oldest first: the first and the last ones (`MESSAGE_DETAILS_MAX_SENDS`). */\n sends?: MessageSend[];\n /** Sends there were in all, when more than `sends` keeps. */\n attempts?: number;\n /** A received message: when it came and over what. */\n received?: Omit<MessageSend, \"result\" | \"error\">;\n /** The last successful send was handed to the transport. */\n sentAt?: number;\n /** The contact's receipt came (the message is `delivered`). */\n receiptAt?: number;\n /** Stored for an away contact, sealed for them. */\n heldAt?: number;\n /** A file: its last byte arrived here, or the contact confirmed it. */\n completedAt?: number;\n /** The frame the message travelled in. `wireBytes`: as sent on the channel (a JSON frame, a sealed record, a bundle). */\n wire?: { frame: string; protocol: string; plaintextBytes?: number; wireBytes?: number; chunks?: number; chunkBytes?: number };\n /** The DHT floor: the envelope's sequence and times, the sealed packet's size and nonce, the record read. */\n dht?: { seq?: number; issued?: number; expires?: number; packetBytes?: number; nonce?: string; recordKey?: string; records?: string[] };\n /** Store-and-forward: the item's sequence in the mailbox, its sealed size and expiry. */\n hold?: { seq?: number; bytes?: number; expires?: number; mailbox?: string };\n}\n\n/** A details record larger than this (as JSON) is trimmed: its oldest sends go first. */\nexport const MESSAGE_DETAILS_MAX_BYTES = 2048;\n/** Sends kept on a record: the first, and the last ones. */\nexport const MESSAGE_DETAILS_MAX_SENDS = 6;\n\n/** What the details view is made of: the row, its record, and what the engine knows around it right now. */\nexport interface MessageDetailsView {\n message: Pick<StoredMessage, \"id\" | \"wireId\" | \"linkId\" | \"sender\" | \"timestamp\" | \"sentAt\" | \"via\" | \"delivery\" | \"deliveryError\" | \"resendUntil\" | \"member\" | \"nick\"> & {\n kind: \"text\" | \"file\" | \"voice\" | \"payment\" | \"event\" | \"note\";\n /** UTF-8 bytes of the text. */\n textBytes: number;\n };\n details?: MessageDetails;\n /** The chat the message is in. Keys are the public participation keys (z-base-32), never a seed. */\n link?: {\n id: string;\n profile?: \"paired-chat/1\";\n deliveryMode?: DeliveryMode;\n /** Our own participation key on this link. */\n myKey: string;\n /** The contact's, as pinned; absent before the first contact. */\n peerKey?: string;\n /** The pinned key was compared by the two people (WISP 401 § verification). */\n verified: boolean;\n /** The session carrying the chat right now, if any. */\n transportNow?: PairedTransport;\n relayedNow?: boolean;\n rttNowMs?: number;\n };\n /** The file the message carries (files/2, files/3 or a held item), as stored. */\n file?: {\n id: string; name: string; size: number; mime: string;\n /** SHA-256 of the bytes, hex, once known (the sender's own, or checked on arrival). */\n digest?: string;\n protocol?: \"files/2\" | \"files/3\" | \"hold/1\";\n state?: string;\n stage?: string;\n transferred?: number;\n /** Why a transfer that ended without the file did. */\n error?: string;\n /** files/3: bytes confirmed durably, where a restart resumes. */\n confirmed?: number;\n /** files/3: when the offer was made. */\n since?: number;\n consented?: boolean;\n /** Where the bytes are kept on this device. */\n storage?: string;\n voice?: { duration: number; peaks: number };\n };\n /** The payment or request the message stands for: everything but the money itself. */\n payment?: {\n id: string; kind: \"payment\" | \"request\"; direction: \"in\" | \"out\"; amount: number; unit: string; state: PaymentState;\n method?: string; network?: string; provider?: string; asset?: string; mint?: string; txid?: string; ask?: string;\n createdAt: number; error?: string; memo?: string; group?: string;\n /** SHA-256 of the invoice, hex, when there is one (the invoice itself carries the amount and a payment hash). */\n invoiceDigest?: string;\n lightningPending?: boolean; paidHere?: boolean;\n };\n /** A group message: which group, and the member key of the sender when it is not this device. */\n group?: { id: string; member?: string; profile?: string };\n}\n\n/** The ways of paying a group note can name. */\nexport type GroupPayRail = \"cashu\" | \"lightning\" | \"arkade\" | \"bark\" | \"bitcoin\" | \"spark\" | \"usdt\" | \"fedimint\";\n\n/**\n * A payment between two members as the whole group sees it (WISP 902 § Payments): who pays whom, how much, over\n * what, and how it stands. No money and nothing to pay with: that travels only on the edge between the two.\n */\nexport interface GroupPayNote {\n id: string;\n /** `request`: a member asks to be paid; `payment`: a member sends without a request. */\n kind: \"request\" | \"payment\";\n /** Who pays: a member key, or `*` for a request any member may pay once. */\n from: string;\n /** Who is paid. */\n to: string;\n /** In the unit's smallest denomination (sats, token base units). */\n amount: string;\n unit: string;\n decimals: number;\n rail: GroupPayRail;\n /** Test money (test mints, test networks): worth nothing. */\n test?: boolean;\n memo?: string;\n /** The request answers the payer's own ask (Ark, Bark, on-chain, USDT): the payer started it. */\n ask?: boolean;\n createdAt: number;\n /** `sent`: the payer says it paid, the payee has not confirmed. `closed`: failed, refused or taken back. */\n state: \"open\" | \"sent\" | \"paid\" | \"closed\";\n /** Who paid it: the payee's word, or, when its wallet cannot tell (an invoice), the only member who said so. */\n by?: string;\n /** Members who said they paid it. */\n claims?: string[];\n /** The last note this device said about it, sent again when an edge opens. */\n mine?: GroupPayFrame;\n}\n\n/** `group-pay` on an edge (WISP 902 § Payments). Old apps drop it. */\nexport interface GroupPayFrame {\n t: \"group-pay\";\n g: string;\n id: string;\n k: \"req\" | \"pay\";\n f: string;\n to: string;\n v: string;\n u: string;\n d: number;\n r: GroupPayRail;\n x?: 1;\n m?: string;\n a?: 1;\n ts: number;\n st: GroupPayNote[\"state\"];\n by?: string;\n}\n\n/** A local web application the user chose to share. */\nexport interface StoredService {\n id: string;\n name: string;\n /** Normalized loopback URL, e.g. `http://localhost:3400` */\n target: string;\n enabled: boolean;\n createdAt: number;\n /**\n * The contacts allowed to reach this service, by participation key. Absent or\n * empty means nobody: a service is granted per contact, never to everyone at\n * once, and a service from before this existed starts closed rather than\n * keeping an exposure its owner never chose.\n */\n sharedWith?: string[];\n}\n\nexport interface IceServerSetting {\n urls: string;\n username?: string;\n credential?: string;\n}\n\nexport interface Settings {\n online: boolean;\n nick: string;\n /** This profile's picture, shown to paired contacts: a small square JPEG data URL. */\n avatar?: string;\n /**\n * Whether contacts are told this profile's name and picture (WISP 401 § name and picture). Absent means\n * yes; off, every chat and group member is told there is none, and the join notice names nobody.\n */\n shareProfile?: boolean;\n /** The Pkarr relays. Browsers read and write through them; the Desktop writes to them (so browser contacts see its packets). */\n relays: string[];\n /**\n * Where the DHT is reached directly (the Desktop): whether reads may use the relays too, \"Also use Pkarr relays\"\n * in Settings, Network. Absent means no: reads go to the DHT alone.\n */\n readRelays?: boolean;\n /** Extra ICE servers (typically TURN) on top of the built-in STUN set. */\n iceServers: IceServerSetting[];\n /**\n * The Iroh relays a browser build homes on (WISP 102, relay only): a page cannot send UDP, so every Iroh\n * packet goes through one. Absent or empty means the defaults (n0's public relays, as the desktop app's).\n */\n irohRelays?: string[];\n /**\n * The HyperDHT relay (wss://) this browser reaches the HyperDHT through, so paired chats can use HyperDHT\n * (WISP 103) beside WebRTC. Empty: none. Absent means the default (shared/hyperdhtRelay.ts). The Desktop\n * runs HyperDHT itself and ignores it.\n */\n hyperdhtRelay?: string;\n /** Cashu mints this peer holds ecash at and accepts ecash from. The first is where invoices are created. */\n mints: string[];\n /** False until the default mints were put in place once; after that the list is the user's. */\n mintsInitialized: boolean;\n /** The Nostr social layer: relays, automatic profile loading, publication. Absent means the defaults, everything off. */\n nostr?: NostrSocialSettings;\n /**\n * Identity cards show the public profile (picture, name, bio, counts) of a verified identity, read from its\n * network's public host when the card is on screen (PUBLIC-PROFILES.md). Absent means on; off drops what was kept.\n */\n publicProfiles?: boolean;\n /**\n * Whether paired contacts are told when this profile is typing (WISP 401 § Typing). Absent means yes; off,\n * nothing is said, and a contact's typing is still shown.\n */\n sendTyping?: boolean;\n /**\n * Whether files are downloaded without asking where the rules allow it (WISP 503: a group's voice messages, and its\n * files up to 8 MiB within 256 MiB a group). Absent means on; off, each waits for a Download.\n */\n autoDownloads?: boolean;\n /**\n * Whether this device sends the group files it keeps to members who ask (WISP 503 § Serving limits). Absent means\n * on; off, it answers that it is busy and does not say it holds them.\n */\n serveFiles?: boolean;\n /**\n * Where items are held for away contacts (WISP 404): the profile's S3 storage and its random space\n * (WISP 1000/1002), as set up under Profile → Backups. Kept here for the peer, which may run outside the\n * page; never copied into a backup.\n */\n holdStorage?: { s3: S3Config; space: string } | null;\n /** The first-run wallet setup (see `WalletSetupRecord`). Only the engine writes it: a settings patch cannot. */\n walletSetup?: WalletSetupRecord;\n /**\n * This profile's push subscription (the installed web app, WISP 401 § Wake-up push): shared with each paired\n * contact whose app offers `wake/1`, so it can wake this app while it is closed. Absent: not woken.\n */\n wake?: WakeSubscription;\n /**\n * A contact who held `wake` can no longer be told to forget it (its chat was deleted, or muted: a contact may ignore\n * `w: null`). The app replaces the subscription (a new endpoint and VAPID key pair, new tokens for the others) and\n * `setWakeSubscription` clears this. Set by the engine only.\n */\n wakeRotate?: boolean;\n /**\n * Private groups muted here (WISP 902 · Group Mesh § Wake-up push): their members are told to forget this profile's\n * subscription, as a muted chat's contact is. Only through `setWakeMuted` with the group's `group:<id>`.\n */\n wakeMutedGroups?: string[];\n /**\n * Private groups → members that were given a token on an edge a group on hubs later dropped: they still hold the\n * subscription with no edge to close, so their leaving the group (or this profile leaving it, or muting it) replaces\n * it. Cleared with every new subscription. Set by the engine only.\n */\n wakeHeldBy?: Record<string, string[]>;\n /**\n * In a profile on several devices (WISP 06 § Push and the phone): devices asked for a new subscription over their\n * device link, by signing key, with the endpoint they are to replace. A device that shares another endpoint (or none)\n * has done it. Set by the engine only.\n */\n wakeRenew?: Record<string, string>;\n /**\n * A push relay (https) this app hands a finished wake-up to when it may not post to the contact's push service\n * itself (a browser page: the services answer without CORS). Empty or absent: none; nobody runs one by default.\n */\n pushRelay?: string;\n /** The backup reminder of each Mainnet wallet (see `shared/backupReminder`). Only the engine writes it: a settings patch cannot. */\n backupReminders?: BackupReminders;\n}\n\n/** A browser push subscription and the VAPID key pair it was made with. */\nexport interface WakeSubscription {\n endpoint: string;\n p256dh: string;\n auth: string;\n vapid: VapidKeys;\n /**\n * In a profile on several devices (WISP 06 § Push and the phone): the signing key of the device whose subscription\n * this is. It moves with the profile, so a device with none of its own (a desktop) goes on giving contacts the\n * phone's. Set by the engine only; absent in a profile on one device, and for a subscription made before.\n */\n device?: string;\n}\n\n/**\n * What `updateSettings` takes: any settings, and of the Nostr ones only those that change. The rest of the\n * Nostr settings stay as stored, so a switch flipped from a page that has not seen the last save yet does not\n * put back what that save replaced.\n */\nexport type SettingsPatch = Partial<Omit<Settings, \"nostr\">> & { nostr?: Partial<NostrSocialSettings> };\n\n/** What pages see of store-and-forward in one chat. */\nexport interface LinkHoldView {\n /** The switch of this chat on this device. */\n enabled: boolean;\n /** The contact's app accepts held items, as it last said; undefined until it said anything. */\n peerAllows?: boolean;\n /** This device has storage to hold items in (Profile → Backups → S3 storage). */\n storage: boolean;\n /** Items can be held for the contact right now: switch on, contact allows it, storage set up, contact pinned. */\n canHold: boolean;\n /** Items held for the contact and not yet picked up. */\n outstanding: number;\n bytes: number;\n maxBytes: number;\n maxItems: number;\n ttlMs: number;\n /** Items from the contact refused on the way in. */\n refused: number;\n /** The last problem holding or picking up, for people. */\n error?: string;\n}\n\n/** A proof of this profile (Identities). */\nexport interface IdentityProofView {\n id: string;\n provider: string;\n subject: string;\n /** The proof key the statement authorizes. */\n key: string;\n verified: VerifiedIdentity;\n issuedAt: number;\n /** Seconds: the earlier of the statement's and the evidence's expiry. */\n expiresAt: number;\n createdAt: number;\n /** Chats it is currently shared in (or queued for). */\n sharedWith: number;\n /** The identity as a URI the profile's public DID can list (`alsoKnownAs`); absent when it has none. */\n publicUri?: string;\n /** What the provider checked (public: contacts receive it too). Removal uses it to take down a published proof. */\n evidence?: unknown;\n /** The identity's public profile on its network, once asked for (PUBLIC-PROFILES.md); absent while not asked or turned off. */\n publicProfile?: PublicProfileView;\n}\n\n/**\n * An identity's public profile, as its network publishes it (docs/wisps/PUBLIC-PROFILES.md): self-described by the\n * account, never part of the proof. Only for an identity with a current verified proof, with Settings → Load public\n * profiles on; kept on this device and asked again after a day.\n */\nexport interface PublicProfileView {\n /** False when the network has no profile for this identity (or none was read yet). */\n found: boolean;\n name?: string;\n /** How the network writes the account: \"@alice.bsky.social\", a Nostr `name`. */\n handle?: string;\n /** A short bio, plain text. */\n about?: string;\n /** The website the account names: an https address, shown as a plain link. */\n website?: string;\n /** A sanitized `data:image/jpeg;base64,…` URL, never a remote one. */\n avatar?: string;\n /** The profile names a picture that is not shown: which rule refused it, in words (\"it is a GIF; …\"). */\n avatarMiss?: string;\n followers?: number;\n following?: number;\n /** The hosts it was read from, which saw this device's IP address: \"nexus.pubky.app\". */\n hosts: string[];\n /** Seconds; 0 when it was never read. */\n fetchedAt: number;\n loading?: boolean;\n /** The last attempt failed (a copy read before, if any, is still shown). */\n error?: string;\n}\n\n/**\n * A verified identity's recent posts (docs/wisps/PUBLIC-PROFILES.md, \"Posts and follows\"): `loadPublicPosts`, asked\n * when its card is chosen in a contact's identities panel. Plain text, bounded, never markup.\n */\nexport interface PublicPostView {\n id: string;\n /** Seconds. */\n createdAt: number;\n text: string;\n /** Opens the post in its network's app or site. */\n url?: string;\n reply?: boolean;\n /** Its pictures, loaded only on a tap (`loadPublicPostImage` with the index here). `host`: who is asked. */\n images: { host?: string; alt?: string }[];\n}\n\nexport interface PublicPostsView {\n posts: PublicPostView[];\n /** More can be asked for (`more: true`). */\n more: boolean;\n /** The hosts asked, which saw this device's IP address. */\n hosts: string[];\n /** Seconds. */\n fetchedAt: number;\n /** Notes the person's own Nostr mute list hides. */\n hidden?: number;\n /** The identity's profile in its network's app or site. */\n profileUrl?: string;\n}\n\n/**\n * Who a verified identity follows and who follows it, only as far as it touches people the reader knows: the reader's\n * own identities and the identities contacts shared. Computed on this device from lists read from the same hosts as\n * the profile; the lists themselves are never shown or stored.\n */\nexport interface PublicGraphView {\n /** False when there was nobody to compare with (no identity of the reader's, no contact, on this network): nothing was read. */\n compared: boolean;\n /** Its follow list names one of the reader's identities. */\n followsYou: boolean;\n /** One of the reader's identities follows it (their followers list, or the reader's own follow list). */\n youFollow: boolean;\n /** Contacts whose shared identity it follows (`follows`) or who follow it (`followedBy`). */\n contacts: { linkId: string; follows: boolean; followedBy: boolean }[];\n /** A list was longer than this app reads: someone may be missing, never added. */\n partial?: boolean;\n hosts: string[];\n /** Seconds; 0 when nothing was read. */\n fetchedAt: number;\n}\n\n/** A post's picture, on the reader's tap: a re-encoded `data:image/jpeg` URL, or why there is none. */\nexport interface PublicPostImageView { src?: string; miss?: string; hosts: string[]; width?: number; height?: number }\n\n/** The profile's did:dht (WISP 310-did-dht): its own key, public to everyone, never tied to a chat. */\nexport interface ProfileDidView {\n /** `did:dht:…` */\n id: string;\n /** Identity proof ids the person listed as public, in order (some may not be publishable right now). */\n listed: string[];\n /** What the document says the subject is also known as: the listed identities that are verified and current. */\n alsoKnownAs: string[];\n /** The last packet put on Pkarr: when, and its version (the BEP44 sequence number). */\n published?: { at: number; versionId: string };\n /** The document out there is the current one. */\n upToDate: boolean;\n /** Why the last publish failed. */\n error?: string;\n}\n\n/** A contact's proof, as this app checked it. */\nexport interface ReceivedIdentityView {\n id: string;\n provider: string;\n subject: string;\n verified: VerifiedIdentity;\n /** A public name/picture looked up on request. */\n display?: IdentityDisplay;\n status: IdentityStatus;\n verifiedAt: number;\n checkedAt: number;\n expiresAt: number;\n error?: string;\n /** The provider's checks can go stale and the last one is old enough to repeat. */\n recheckDue: boolean;\n /** Its public profile, while the proof is verified and current (PUBLIC-PROFILES.md). */\n publicProfile?: PublicProfileView;\n}\n\nexport interface LinkIdentitiesView {\n /** Both sides offer identity proofs and the channel is open now. */\n support: boolean;\n /** Providers the contact's app said it can verify. */\n contactProviders?: string[];\n shared: SharedIdentity[];\n received: ReceivedIdentityView[];\n error?: string;\n}\n\nexport interface LinkView {\n /** Identity proofs in this chat; absent for chats that are not paired. */\n identities?: LinkIdentitiesView;\n /** The contact's Nostr data, one entry per key they proved in this chat. */\n nostr?: NostrContactView[];\n discoveryError?: string;\n publicProfiles?: PublicProfile[];\n profileChoice?: ProfileChoice;\n peerProofs?: Pick<ProofLedger, \"local\" | \"remote\">;\n peerProofSupport?: boolean;\n peerProofAdapters?: ProofAdapter[];\n participationKey?: string;\n peerParticipationKey?: string;\n proofError?: string;\n profile?: \"paired-chat/1\";\n pairing?: PairingState;\n /**\n * How far this chat's first pairing got (`@ghostly/core` `PairingProgress`): from the invite being\n * published to `live`. Absent for chats paired before this app start, DHT-only invites and legacy chats.\n */\n pairingProgress?: PairingProgress;\n peerVerified?: boolean;\n /**\n * `methods`: ways of paying both sides allow in this chat right now. `calls` / `services`: both sides offer\n * `calls/1` / `services/1` on the open session (paired chats only; they need a live connection).\n */\n capabilities?: {\n files: boolean; payments: boolean; methods?: Record<PaymentMethodName, boolean>; calls?: boolean; services?: boolean; largeFiles?: boolean;\n /**\n * The networks the contact has a wallet on, per way of paying, as it said on the open session. Absent: it said\n * none (an older app): any network may meet. A way of paying it has no wallet of is not in the map (one it has\n * but turned off here lists no network). A card is offered only where its network is in its list.\n */\n networks?: Partial<Record<PaymentMethodName, readonly WalletNetwork[]>>;\n };\n /** files/3 live in this chat: bytes the contact's device said it can still take for files, when it said. */\n peerFileRoom?: number | null;\n /** Ways of paying this device allows in this chat. */\n paymentMethods?: Record<PaymentMethodName, boolean>;\n /** For each way of paying, the networks this chat accepts it on. */\n paymentNetworks?: Partial<Record<PaymentMethodName, readonly WalletNetwork[]>>;\n /** Both sides announced private groups on the open session: this contact can be invited. */\n groups?: boolean;\n /** Paired chats: what each side offers after the handshake (`paired-capabilities`); `peer` is null until it says. */\n sessionOffers?: { mine: string[]; peer: string[] | null };\n /** Paired chats: why a call cannot be placed right now, or null when it can. */\n callsUnavailable?: string | null;\n /** Paired 1:1 chats: the contact is typing now (`typing/1` on the live session). Absent otherwise. Never stored. */\n peerTyping?: boolean;\n /** Paired 1:1 chats: this side's wake-up token for this chat (the push worker's table), when it shared its subscription. */\n wakeToken?: string;\n /** Paired 1:1 chats: the contact was told not to wake this side while the chat is muted (`setWakeMuted`). */\n wakeMuted?: boolean;\n /** Paired 1:1 chats: the contact shared how to wake its closed app, so a message sent while it is away wakes it. */\n peerWakes?: boolean;\n /** While `peerTyping`: what the contact is doing when it is not plain typing (recording a voice note, thinking). */\n peerTypingKind?: Exclude<TypingKind, \"typing\">;\n /** While `peerTyping`: the contact's (a bot's) status line, sanitized: one line, at most 40 characters, no links. */\n peerTypingStatus?: string;\n availableTransports?: PairedTransport[];\n /**\n * Every transport this app runs for the chat, started or not: DHT only releases the native ones, and an app with no\n * WebRTC (the Linux Desktop) then has none started, yet can still leave DHT only for any of these.\n */\n runnableTransports?: PairedTransport[];\n /** Transports a session with this contact would cross a relay on (a browser's HyperDHT or Iroh): a fallback, and shown as relayed. */\n relayedTransports?: PairedTransport[];\n deliveryMode?: DeliveryMode;\n dhtDelivery?: DhtDeliveryView;\n canSendText?: boolean;\n /** `hold`: the contact is away and what is sent now waits in this device's storage for it. */\n textDelivery?: \"stream\" | \"dht\" | \"hold\" | \"unavailable\";\n hold?: LinkHoldView;\n transportErrors?: Partial<Record<PairedTransport, string>>;\n preferredTransport?: PairedTransport;\n transportFallback?: boolean;\n /** No transport chosen for this chat: the app's rule applies. */\n transportAutomatic?: boolean;\n /** What the contact's app can use on this link, as it last said (a session, or its capability record); unknown before either. */\n peerTransports?: PairedTransport[];\n /**\n * The transport this chat is set to reach and is not on yet, and why (WISP 100, \"A chosen transport not reached\n * yet\"): waited for, never a failure. Absent when the chat is on it, or nothing limits where it goes.\n */\n transportWait?: TransportWait;\n /**\n * The last attempt to go live that did not, as this side saw it (WISP 100, \"Why a chat is not live\"): what it tried\n * and why each did not connect, whether this side dialled or answered, and when it dials again. Absent while live.\n */\n liveAttempt?: LiveAttempt;\n /** Which side dials this chat to go live (the other answers): paired chats. */\n liveDialer?: \"you\" | \"contact\";\n /** Round trip on the live session, once measured. */\n transportRttMs?: number;\n /**\n * The live session goes through relays, never directly (WISP 100, \"Relayed\"): a browser's Iroh. The relays'\n * hosts, this app's first. They see who talks to whom and when, not what is said.\n */\n transportRelayed?: { relays: string[] };\n /** While live: since when the current transport carries the chat, and why it was chosen (see engine/transportLog.ts). */\n transportLive?: { since: number; cause?: TransportCause };\n /** The chat's connection story, newest last; only for the chat on screen (`setActiveLink`). */\n transportLog?: TransportEntry[];\n /** Every connection event of the chat on screen, for its connection panel, newest last. */\n transportHistory?: TransportEvent[];\n /** Identities shared in this chat, both ways, as its timeline shows them, newest last; only for the chat on screen. Local only. */\n identityTimeline?: IdentityTimelineEntry[];\n /** When the contact last shared an identity here (milliseconds): the chat list's preview and order. Paired chats. */\n identitySharedAt?: number;\n /** The latest reaction in this chat, for the chat list; paired chats. */\n lastReaction?: ReactionNote;\n /** The chat's pin (WISP 400 § Pinned message): `id` \"\" once unpinned; absent when nothing was ever pinned. */\n pin?: PinView;\n id: string;\n myPubKeyZ32: string;\n peerPubKeyZ32: string;\n label?: string;\n /**\n * The name the contact asked to be shown by, as they last said it. `\"\"` means a paired contact said they\n * have none (removed, or not shared); absent means nothing was heard yet.\n */\n peerNick?: string;\n /** The contact's profile picture, as they last sent it (checked, small JPEG data URL). */\n peerAvatar?: string;\n inviteCode?: string;\n createdAt: number;\n status: LinkStatus;\n dataLink: DataLinkState;\n peerOnline: boolean;\n peerLastSeenAt: number;\n /** `null` when the peer does not advertise (offline, or a legacy client). */\n peerServices: ServiceAd[] | null;\n lastMessageAt: number;\n /** Newest of my messages the peer acknowledged through Pkarr. */\n peerAck: number;\n lastSyncAt: number;\n poll: { polling: boolean; nextAt: number; interval: number };\n}\n\nexport interface ServiceView extends StoredService {\n /** Requests served since the engine started. */\n requests: number;\n}\n\nexport interface EngineState {\n settings: Settings;\n /**\n * Limited mode (WISP 06 § When a device checks): the device could not read which device is active and was started\n * anyway. The profile is offline whatever `settings.online` says, and no wallet is open. Absent otherwise.\n */\n limited?: true;\n /**\n * Why limited mode is on, when it is a restored copy (WISP 06 § A backup restored where a device set exists): `checking`,\n * the turn is read every 30 seconds to learn whether another device runs the profile; `removed`, the read found the\n * device set moved after a removal, and only the person can start the copy as a profile of its own.\n */\n restoreCheck?: \"checking\" | \"removed\";\n /**\n * Whose push subscription `settings.wake` is, in a profile on several devices (WISP 06 § Push and the phone): `here`,\n * this device's own; `away`, another device's, given to contacts because this device has none of its own (the page\n * neither replaces nor turns off what is not its own). Absent in a profile on one device, or when nobody said.\n */\n wakeOwner?: \"here\" | \"away\";\n transport: {\n protocol: string; relays: string[];\n /** Present where Iroh runs in the browser (web app, extension): the relays it uses and the defaults. */\n iroh?: { relays: string[]; defaults: string[] };\n /** This client reaches the Mainline DHT itself (the Desktop): relays are for writes, and reads only when chosen. */\n direct?: boolean;\n /** How reads go and how each relay is doing, for the connection panel's Details. */\n discovery?: DiscoveryStatus;\n /** False where this client has no WebRTC (Ghostly Desktop on Linux: WebKitGTK has none). Absent where it has. */\n webrtc?: false;\n /**\n * This device's own WebRTC attempts say direct connections do not get through its network (a VPN, a firewall, a\n * carrier's NAT: `DirectPathWatch` in packages/core). Chats still go live through relays. Absent otherwise.\n */\n directBlocked?: true;\n /**\n * This device's clock seems to be off: this clock minus what the relays and several contacts say, in ms (positive\n * when it is ahead). From several sources agreeing, never one contact (`ClockWatch` in packages/core). Absent otherwise.\n */\n clockOffMs?: number;\n /**\n * False where a group's links have no transport at all here: no WebRTC and no native transport (WISP 902 §\n * Transports), so no member of a group can be reached from it. Absent where they have one (the Linux Desktop runs\n * Iroh and HyperDHT).\n */\n groupLinks?: false;\n };\n links: LinkView[];\n services: ServiceView[];\n /** Transfers since the peer started, by file id. */\n transfers: Record<string, FileTransferView>;\n /**\n * The kept transfers are back in `transfers`. False for the first moments after a start: a file with no transfer\n * yet may still be moving, not finished.\n */\n transfersRestored?: boolean;\n /**\n * The kept groups are in `groups`. Unset while the peer starts: until then a group missing from the list may only not\n * be loaded yet, not gone (the app forgets a gone group's draft and read mark only between two states that have it).\n */\n groupsLoaded?: true;\n wallet: WalletView;\n payments: Record<string, PaymentView>;\n /** This profile's identity proofs. */\n identityProofs: IdentityProofView[];\n /** This profile's public DID. Absent until the engine has loaded it. */\n did?: ProfileDidView;\n /** The Nostr social layer: the person's own keys' data and the effective settings. */\n nostr: NostrSocialState;\n /** Private groups and pending invitations (WISP 900). */\n groups: GroupView[];\n /** The links of groups' edges: not chats, but payments with a member travel on them. */\n edges?: LinkView[];\n}\n","import { WALLET_NETWORKS, decodeBolt11, engineText, walletNetworkOf, type PaymentMethodName, type PaymentNetworks, type WalletNetwork } from \"@ghostly/core\";\nimport { mintNetwork } from \"../../shared/mints\";\nimport { WALLET_TYPES, type NetworkWalletsView, type StoredPayment, type WalletInstanceView, type WalletType } from \"../../shared/types\";\nimport { CASHU_MINT_SOURCE } from \"./providers/cashuMint\";\nimport { CASHU_CARD, LEGACY_CARD } from \"./providers/lightningCards\";\nimport { networkLabel } from \"./modeGate\";\nimport { networkReason } from \"./networkReason\";\n\nexport const WALLET_NAMES: Record<WalletType, string> = {\n cashu: \"Cashu\", lightning: \"Lightning\", arkade: \"Ark\", bark: \"Bark\", spark: \"Spark\", bitcoin: \"Bitcoin\", fedimint: \"Fedimint\", usdt: \"USDT\",\n};\n\n/** How long a one-click creation waits on its server before it gives up, saving nothing (tests shorten it). */\nexport const createTiming = { timeoutMs: 60_000 };\n\n/**\n * The wallets a profile has, read from both networks' views: one per type and network, in the deck's order, and one\n * per Lightning card. A wallet exists once it holds something to open (a seed, a source, a mint); Lightning through\n * the Cashu mints comes with that network's Cashu wallet.\n */\nexport function walletInstances(networks: Record<WalletNetwork, NetworkWalletsView>): WalletInstanceView[] {\n const list: WalletInstanceView[] = [];\n for (const type of WALLET_TYPES) for (const network of WALLET_NETWORKS) {\n if (type === \"lightning\") { list.push(...lightningInstances(network, networks[network])); continue; }\n const config = instanceConfig(type, networks[network]);\n if (config) list.push({ id: `${type}:${network}`, type, network, config });\n }\n return list;\n}\n\n/** A network's Lightning cards (a view from before cards: its one source, as one card). */\nfunction lightningInstances(network: WalletNetwork, view: NetworkWalletsView): WalletInstanceView[] {\n const cards = view.lightnings ?? (view.lightning ? [{ ...view.lightning, card: view.lightning.providerId === CASHU_MINT_SOURCE || !view.lightning.providerId ? CASHU_CARD : LEGACY_CARD, name: \"\", receive: true }] : []);\n return cards.flatMap((ln) => {\n if (!ln.providerId || (ln.providerId === CASHU_MINT_SOURCE && !view.mints.length)) return [];\n return [{ id: `lightning:${network}:${ln.card}`, type: \"lightning\" as const, network, config: { providerId: ln.providerId, label: ln.label ?? \"\" }, card: ln.card, name: ln.name || ln.label || \"Lightning\", receive: ln.receive }];\n });\n}\n\nfunction instanceConfig(type: WalletType, view: NetworkWalletsView): Record<string, string> | undefined {\n const text = (value: unknown) => typeof value === \"string\" ? value : \"\";\n switch (type) {\n case \"cashu\": return view.mints.length ? { mint: view.mints[0].url, mints: String(view.mints.length) } : undefined;\n case \"lightning\": return undefined;\n case \"arkade\": return view.ark?.configured ? { chain: text(view.ark.network), provider: text(view.ark.provider) } : undefined;\n case \"bark\": return view.bark?.configured ? { chain: text(view.bark.network), provider: text(view.bark.provider) } : undefined;\n case \"spark\": return view.spark?.configured ? { chain: text(view.spark.network) } : undefined;\n case \"bitcoin\": return view.bitcoin?.providerId ? { providerId: view.bitcoin.providerId, label: text(view.bitcoin.label) } : undefined;\n case \"fedimint\": return view.fedimint?.federations.length ? { federations: String(view.fedimint.federations.length) } : undefined;\n case \"usdt\": return view.usdt?.configured ? { chain: text(view.usdt.network), provider: text(view.usdt.provider) } : undefined;\n }\n}\n\n/** For each way of paying, the networks this profile has a wallet on: what every chat tells its contact. */\nexport function paymentNetworksOf(wallets: readonly WalletInstanceView[]): PaymentNetworks {\n const networks: PaymentNetworks = {};\n for (const wallet of wallets) {\n const method = wallet.type as PaymentMethodName;\n networks[method] = [...new Set([...(networks[method] ?? []), wallet.network])];\n }\n return networks;\n}\n\n/**\n * The network of a payment or a request: written on it since wallets have their own, else read from what it\n * carries (a target's chain, a mint, an invoice's chain). A test mint's invoice looks like a Bitcoin one; its\n * mints say Testnet.\n */\nexport function paymentNetwork(payment: Pick<StoredPayment, \"network\" | \"target\" | \"mints\" | \"mint\" | \"invoice\">): WalletNetwork {\n if (payment.network) return payment.network;\n if (payment.target) return payment.target.method === \"cashu\" ? mintNetwork(payment.target.provider) : walletNetworkOf(payment.target.network);\n const mint = payment.mints?.[0] ?? payment.mint;\n if (mint) return mintNetwork(mint);\n const decoded = payment.invoice ? decodeBolt11(payment.invoice) : null;\n return decoded && decoded.network !== \"bitcoin\" ? \"testnet\" : \"mainnet\";\n}\n\n/** The refusal when a card of one network would pay for the other: test coins and real money never meet. */\nexport const crossNetwork = (card: WalletNetwork, asked: WalletNetwork) =>\n card === asked ? `This is a ${networkLabel(asked)} payment (${asked === \"mainnet\" ? \"real money\" : \"test coins\"}): a ${networkLabel(card)} wallet never pays it. Use a ${networkLabel(asked)} wallet.`\n : engineText(asked === \"mainnet\" ? \"mainnetPaymentOnTestnet\" : \"testnetPaymentOnMainnet\");\n\n/**\n * Real money goes out only once the person confirmed it as real money (\"Send real money\"), whatever screen asked:\n * a spend RPC on Mainnet without `confirmedReal` is refused before anything is spent. Test coins need no second step.\n */\nexport function assertConfirmedReal(network: WalletNetwork, confirmedReal: unknown): void {\n if (network === \"mainnet\" && confirmedReal !== true) throw new Error(REAL_MONEY_UNCONFIRMED);\n}\nexport const REAL_MONEY_UNCONFIRMED = engineText(\"realMoneyUnconfirmed\");\n\n/** One clear message for a creation that failed: nothing was saved, and trying again is safe. */\nexport function createFailure(label: string, error: unknown): string {\n const reason = (networkReason(error) ?? (error instanceof Error ? error.message : String(error))).replace(/\\.$/, \"\");\n return `Could not create the ${label} wallet: ${reason}. Nothing was saved; try again.`;\n}\n","/**\n * Payment ids are chosen by whoever sends them, and every member of a group sees the requests the others make to the\n * group. So a contact's request is kept under the id it came with, unless a record of another chat already holds that\n * id: then under `<id>.<chat>`. A `.` is never in a payment id on the wire (8 to 64 of `A-Z a-z 0-9 _ -`), so no\n * contact can name another's record, and two contacts' requests never share one.\n */\nexport const paymentAlias = (wireId: string, linkId: string): string => `${wireId}.${linkId}`;\n\n/** The id a payment record goes by on the wire, which is also the one its chat and its group's notes know it by. */\nexport const paymentWireId = (key: string): string => key.split(\".\", 1)[0];\n","import { replySnippet, type WireReply } from \"@ghostly/core\";\nimport { paymentWireId } from \"./paymentIds\";\nimport type { MessageReply, StoredMessage } from \"./types\";\n\n/*\n * Replies (WISP 400 § Replies) as the engine and the UI both need them: which id a message goes by on both sides of\n * its chat, and a reply as it is kept and as it goes on the wire.\n */\n\n/** A paired chat's text (`me_`/`peer_` + its 22-character wire id), or a held file (`peer_` + its 16-character one). */\nconst PAIRED_ROW = /^(?:me|peer)_([A-Za-z0-9_-]{22}|[A-Za-z0-9_-]{16})$/;\nconst WIRE_ID = /^[A-Za-z0-9_-]{8,64}$/;\nconst GROUP_LINK = \"group:\";\n\n/** The parts of a message, as the engine or the UI keeps it, that say which id both sides know it by. */\nexport interface ReplyTarget {\n id: string;\n linkId?: string;\n wireId?: string;\n paymentId?: string;\n file?: { id: string };\n event?: unknown;\n groupPay?: unknown;\n /** Set by the UI from the engine's row (`replyRef`), for what the UI's own copy no longer says. */\n ref?: string;\n}\n\n/**\n * The id a message of a chat goes by on both sides, which a reply names; none for what only this side has (a notice,\n * a group payment line, a refused payment). A paired chat: the text's wire id, a payment's id, a file's wire id. A\n * group: the message id, which is the same for every member.\n */\nexport function replyRef(message: ReplyTarget, group = message.linkId?.startsWith(GROUP_LINK) ?? false): string | undefined {\n if (message.ref) return message.ref;\n if (group) return message.paymentId || message.event || message.groupPay || !message.id.includes(\":\") ? undefined : message.id;\n if (message.paymentId) return paymentWireId(message.paymentId);\n if (message.wireId && WIRE_ID.test(message.wireId)) return message.wireId;\n const out = message.file?.id.match(/-out-([A-Za-z0-9_-]{8,64})$/);\n if (out) return out[1];\n const row = message.id.match(PAIRED_ROW);\n return row && !/^\\d+$/.test(row[1]) ? row[1] : undefined;\n}\n\n/** A reply to `original` as this side keeps it: always found, since it is here. The engine's row, or the UI's copy. */\nexport function replyTo(original: Pick<StoredMessage, \"id\" | \"text\" | \"member\"> & { sender: string }, ref: string): MessageReply {\n return { id: ref, snippet: replySnippet(original.text), from: original.sender === \"me\" ? \"me\" : \"peer\",\n ...(original.member && { member: original.member }), messageId: original.id };\n}\n\n/**\n * A reply as it goes on a paired chat's wire, from the side that sends it (`f`: who wrote the original, from the\n * sender's side). For a group, `f` is the author's member key, which the reply keeps.\n */\nexport function pairedWireReply(reply: MessageReply, sender: \"me\" | \"peer\" = \"me\"): WireReply {\n return { i: reply.id, s: reply.snippet, f: reply.from === sender ? \"sender\" : \"recipient\", ...(reply.button && { b: reply.button }) };\n}\n\n/** A paired chat's reply as it arrived (`f` from the sender's side), as this side keeps it. */\nexport function receivedPairedReply(reply: WireReply | { i: string }): MessageReply {\n if (!(\"f\" in reply)) return { id: reply.i, snippet: \"\" };\n return { id: reply.i, snippet: reply.s, from: reply.f === \"sender\" ? \"peer\" : \"me\", ...(reply.b && { button: reply.b }) };\n}\n\n/** A group's reply as it arrived or was sent (`f` the author's member key), as this member keeps it. */\nexport function groupReply(reply: WireReply, myKey: string): MessageReply {\n return { id: reply.i, snippet: reply.s, from: reply.f === myKey ? \"me\" : \"peer\", member: reply.f, ...(reply.b && { button: reply.b }) };\n}\n","import {\n ChatFiles,\n GROUP_FILE_LIMITS,\n GROUP_HAVE_FRAME,\n GROUP_WANT_FRAME,\n GROUP_WANT_NO_FRAME,\n RateWindow,\n fetchesByItself,\n fileMessageText,\n groupFileFallback,\n isGroupMessageId,\n randomBytes,\n readGroupHave,\n readGroupWant,\n readGroupWantNo,\n toBase64Url,\n transferEnded,\n type FileInfo,\n type FileTransferRecord,\n type GroupFileMeta,\n type GroupWantRefusal,\n type IncomingTarget,\n type OfferDecision,\n type OutgoingSource,\n} from \"@ghostly/core\";\nimport { fileBytes, fileBytesOf } from \"../shared/fileBytes\";\nimport { fileStore, type StoredFile } from \"../shared/idb\";\nimport { FileAppender, readStored, removeStored } from \"../shared/storedFiles\";\nimport type { FileTransferView, MessageFile } from \"../shared/types\";\n\n/*\n * Files and voice messages in groups (WISP 503 · Group Files), the engine's side. A file is announced as a group message\n * carrying its description (`fl`); each member's app then fetches the bytes from a member that holds them, over\n * `files/3` on the edge between the two (`ChatFiles`, WISP 501): it sends `group-want`, the holder offers the file with\n * `gm` naming the message, and what arrives is checked against the author's digest. Whoever stored it holds it, and says\n * so (`group-have`), so a member away during the announcement gets it later from whoever is there.\n */\n\n/** A group file's local id: its group's and a random part, never anything a member sends. */\nexport const groupFileId = (groupId: string, direction: \"in\" | \"out\", random = toBase64Url(randomBytes(12))) => `group-${groupId}-${direction}-${random}`;\n/** The group a local file id names, among `groupIds`. */\nexport const groupOfFile = (fileId: string, groupIds: Iterable<string>): string | undefined =>\n [...groupIds].find(g => fileId.startsWith(`group-${g}-in-`) || fileId.startsWith(`group-${g}-out-`));\n\n/** What a file's message says: its caption, or (when the author wrote none) the file itself, not the line older apps show. */\nexport const groupFileText = (text: string, meta: GroupFileMeta): string => text === groupFileFallback(meta) ? fileMessageText(meta) : text;\n\n/** Where a group's files are kept: the profile's file store and file storage (`storedGroupFiles`), or a test's memory. */\nexport interface GroupFileStore {\n /** The records of a group's files (`linkId` `group:<id>`). */\n list(groupId: string): Promise<StoredFile[]>;\n /** Every group a stored file is of, whether or not this device still has the group. */\n groups(): Promise<string[]>;\n get(id: string): Promise<StoredFile | undefined>;\n put(file: StoredFile): Promise<void>;\n patch(id: string, fields: Pick<StoredFile, \"transfer\" | \"group\">): Promise<void>;\n /** The record and its bytes. */\n remove(id: string): Promise<void>;\n /**\n * Writes a file in order: from its first byte (whatever was there goes), or with `resume` after what an earlier\n * transfer stored (the writer's `offset`).\n */\n write(id: string, resume?: boolean): Promise<GroupFileWriter>;\n /** At most `length` bytes of a kept file from `offset`. */\n read(file: StoredFile, offset: number, length: number): Promise<Uint8Array>;\n /** Bytes this device can still take for files, or null when the platform does not say. */\n room(): Promise<number | null>;\n}\n\nexport interface GroupFileWriter {\n /** Where it writes from: the bytes already stored. */\n offset: number;\n append(bytes: Uint8Array): Promise<void>;\n flush(): Promise<void>;\n /** SHA-256 of what was stored, read back from storage, base64url. */\n digest(): Promise<string>;\n /** Done writing for now: what is stored stays. */\n close(): Promise<void>;\n discard(): Promise<void>;\n}\n\n/** The profile's files store and file storage, as a 1:1 chat's files use them. */\nexport const storedGroupFiles: GroupFileStore = {\n list: groupId => fileStore.listForLink(`group:${groupId}`),\n groups: () => fileStore.groupIds(),\n get: id => fileStore.get(id),\n put: file => fileStore.put(file),\n patch: (id, fields) => fileStore.patch(id, fields),\n remove: id => removeStored(id),\n async write(id, resume = false) {\n const stored = await fileStore.get(id);\n const bytes = (stored?.bytes && await fileBytesOf(stored.bytes)) || await fileBytes();\n const size = await bytes.size(id);\n const offset = resume && size !== null ? size : 0;\n if (size !== null && size !== offset) await bytes.truncate(id, offset);\n if (stored && stored.bytes !== bytes.kind) await fileStore.patch(id, { bytes: bytes.kind });\n const appender = new FileAppender(bytes, id, offset);\n return {\n offset,\n append: chunk => appender.append(chunk),\n flush: () => appender.flush(),\n digest: async () => { await appender.close(); return bytes.digest(id); },\n close: () => appender.close(),\n discard: async () => { await appender.close().catch(() => {}); await bytes.remove(id); },\n };\n },\n read: (file, offset, length) => readStored(file, offset, length),\n room: async () => (await fileBytes()).room(),\n};\n\n/** What the engine gives the group files: where they are kept, their views, the profile's settings, an edge's files/3. */\nexport interface GroupFilesHost {\n store: GroupFileStore;\n transfers: Map<string, FileTransferView>;\n /** The file's view goes in the next state the engine sends, and then away: the file's record says the rest. */\n brief(fileId: string): void;\n settings(): { autoDownloads: boolean; serveFiles: boolean };\n /** A files/3 frame on an edge's open session; false when it could not go. */\n sendFiles(linkId: string, frame: Record<string, unknown>): boolean;\n /** A file's bytes on an edge, in the form its session agreed (`GhostLink.sendFileData`, files/4). Absent: `pf-data` frames. */\n sendData?(linkId: string, id: string, offset: number, data: Uint8Array): boolean;\n /** Resolves once the edge's session can take another data frame. */\n writable?(linkId: string): Promise<void> | undefined;\n}\n\n/** Me in a group I am active in, and who could read one of its messages. */\nexport interface GroupFileMembership {\n me: string;\n community: boolean;\n inRoster(key: string): boolean;\n /** In the roster of the message's epoch and in the roster now (WISP 503 § Asking). */\n couldRead(key: string, messageId: string): boolean;\n}\n\nexport interface GroupFileDeps extends GroupFilesHost {\n /** A group frame on an edge; false when it could not go. */\n send(linkId: string, frame: Record<string, unknown>): boolean;\n /** Member key → edge link id, for the group's edges. */\n edges(groupId: string): Map<string, string>;\n /** The edge is up, and the app at its other end announced group files (`GROUP_VERSION_FILES`). */\n ready(linkId: string): boolean;\n membership(groupId: string): GroupFileMembership | undefined;\n /** An application frame to everyone in a community. */\n sendApp(groupId: string, frame: Record<string, unknown>): Promise<void>;\n changed(): void;\n now(): number;\n}\n\n/** A group file this device knows of: one it announced, or one a member announced. */\ninterface Kept {\n id: string;\n groupId: string;\n message: string;\n author: string;\n meta: GroupFileMeta;\n /** I announced it: my copy is its first. */\n mine: boolean;\n /** Fetched without asking: it counts toward the group's automatic limit. */\n auto: boolean;\n /** Its person asked for it (Download). */\n asked: boolean;\n /** Its person paused it: what came stays, and it reads paused after a restart too, until Resume. */\n paused?: boolean;\n /** Every byte is here, checked against the author's digest: this device holds it. */\n held: boolean;\n /** Members said to hold it (`group-have`), or that offered it, newest first. The author is asked whether or not it is here. */\n holders: string[];\n /** A discard of what an earlier transfer stored, which the next one waits for. */\n idle?: Promise<void>;\n /** Bytes a transfer stored here that the next one goes on from, shown while it waits for a holder. */\n stored?: number;\n /** The transfer (its id on the edge) its person cancelled: what it stored is deleted, not kept for the next one. */\n dropped?: string;\n /**\n * The bytes kept from an earlier holder did not check out with the next one's tail: who served the bad part is not\n * known, so the next transfer starts from 0 (`fresh`) and the holder whose tail failed is not counted as damaged\n * (`unclear`, read once by `transferChanged`).\n */\n fresh?: boolean;\n unclear?: boolean;\n /**\n * Holders whose bytes failed the author's digest: not asked again for it, whatever the round, until its person asks\n * for it again (Download). Once every holder known is here, it is fetched no more (`DAMAGED`).\n */\n damaged: Set<string>;\n}\n\n/** A file being asked for: of one holder at a time. */\ninterface Want {\n /** The holder asked now and the edge it was asked on; none while nobody reachable has it. */\n holder?: string;\n linkId?: string;\n at: number;\n /** Holders asked in this round that had nothing to give: the next ask goes to another. */\n tried: Set<string>;\n /** The holder's transfer, once it offered: its id on the edge, and when its bytes last moved. */\n wire?: string;\n moved: number;\n movedAt: number;\n /** What the last holder asked in this round answered, when it was not the file: shown while the want waits. */\n why?: \"busy\" | \"damaged\";\n /**\n * Holders that answered `busy` in this round: one of them in reach tells this device when its place frees (its\n * `group-have`), so members never heard of as holders are not asked meanwhile.\n */\n busy?: Set<string>;\n}\n\n/** One edge's files/3, both ways. */\ninterface Edge {\n linkId: string;\n groupId: string;\n peer: string;\n files: ChatFiles;\n /** Transfers this side serves on the edge (their wire id) → the local file. */\n serving: Map<string, string>;\n /** Of each transfer served: the bytes the asker confirmed, and when that last changed. */\n servedMoved: Map<string, { confirmed: number; at: number }>;\n /** Transfers this side takes on the edge (the holder's wire id) → the local file. */\n taking: Map<string, string>;\n /** Transfers that ended on the edge, oldest first: the last `ENDED_KEPT` keep their record, the rest are forgotten. */\n ended: [\"in\" | \"out\", string][];\n /** Message ids to say in the next `group-have`, and the frames of it that went lately (`haveFrames` in `haveWindowMs`). */\n have: Set<string>;\n haveSent: RateWindow;\n /** Message ids its member asked for while this device served all it serves at once: said in a `group-have` once a slot frees. */\n busy: Set<string>;\n /** Message ids its member said it does not hold (`gone`): not asked of it again until it says it does, or the edge's session closes or opens. */\n gone: Set<string>;\n}\n\n/**\n * Ended transfers an edge keeps the record of. A late frame for one of them is still answered as files/3 says (an abort\n * to an asker going on with a transfer cancelled here); older ones are forgotten, so an edge's records stay few\n * however many transfers it has seen.\n */\nconst ENDED_KEPT = 8;\n\n/** Why a want waits, as the person reads it (the app shows its own words for each `wait`). */\nconst WAITING_NOTES = {\n nobody: \"Nobody you are connected to has this file yet\",\n busy: \"Its holders are busy; asking again soon\",\n damaged: \"It arrived damaged; ask again\",\n} as const;\n/** Why a want stopped: every holder served bytes that are not the author's file. */\nconst DAMAGED = \"Every copy of this file arrived damaged and was deleted. Ask for it again later.\";\n\nexport class GroupFileDesk {\n private readonly kept = new Map<string, Kept>();\n /** `<group>\\n<message id>` → local id. */\n private readonly byMessage = new Map<string, string>();\n private readonly wants = new Map<string, Want>();\n /**\n * The want last asked on each edge (by link id), and its file: whether a file is asked of an edge's member is read here,\n * not by going through every want each time (`askedOf`). An entry the want has moved on from is dropped when read.\n */\n private readonly askedOn = new Map<string, { id: string; want: Want }>();\n private readonly edgesById = new Map<string, Edge>();\n /** My file about to be announced, by `<group>\\n<digest>`: the message that announces it takes this local id. */\n private readonly own = new Map<string, string>();\n /** Announcements sent per group, and taken per member of a group (WISP 503 § Rules for both, Pace). */\n private readonly sentPace = new Map<string, RateWindow>();\n private readonly sentTaken = new Map<string, RateWindow>();\n private readonly takenPace = new Map<string, RateWindow>();\n /** `<group>\\n<message id>` a `group-have` named before its message came → its holders, newest first. */\n private readonly ahead = new Map<string, string[]>();\n /** A community's ids for its next `group-have`, and when one last went. */\n private readonly communityHave = new Map<string, { ids: Set<string>; at: number }>();\n /** Bytes served today, and the day (UTC days since the epoch). */\n private served = { day: 0, bytes: 0 };\n\n constructor(private readonly deps: GroupFileDeps) {}\n\n // -- what the groups hand over -------------------------------------------------------------------------------\n\n /**\n * At start: each group's files as they were kept. One fetched by itself, or asked for, and not here yet is asked for\n * again; with automatic downloads off, one chosen by itself before waits for a Download instead.\n */\n async load(groupIds: string[]): Promise<void> {\n for (const groupId of groupIds) {\n for (const file of await this.deps.store.list(groupId).catch(() => [])) {\n if (!file.group || !file.metadata || !file.digest || this.kept.has(file.id)) continue;\n const { name, size, mime, voice, video, image } = file.metadata;\n const kept: Kept = { id: file.id, groupId, message: file.group.message, author: file.group.author,\n meta: { name, size, mime, d: file.digest, ...(voice && { voice }), ...(video && { video }), ...(image && { image }) },\n mine: file.direction === \"out\", auto: !!file.group.auto, asked: !!file.group.asked, held: file.transfer?.state === \"done\", holders: [], damaged: new Set() };\n this.add(kept);\n // All here: it shows no transfer (a file with none reads as finished; its record says done). A view for each\n // would go into every state the engine builds, so each state would cost more with every file the groups ever had.\n if (kept.held) continue;\n // Every holder served it damaged: it waits for its person to ask again, not fetched again by itself.\n if (file.transfer?.state === \"failed\") this.show(kept, { state: \"failed\", transferred: 0, size, direction: \"in\", retry: true, error: file.transfer.error ?? DAMAGED });\n // Paused by its person: still paused, with the bytes that came (what the next transfer goes on from).\n else if (file.group.paused) {\n kept.paused = true;\n kept.stored = await this.keptBytes(kept.id);\n this.show(kept, { state: \"transferring\", stage: \"paused\", pausedBy: \"me\", transferred: kept.stored, size, direction: \"in\" });\n }\n else if (kept.asked || (kept.auto && this.deps.settings().autoDownloads)) this.wants.set(kept.id, this.newWant());\n else if (kept.auto) await this.stop(kept, false);\n // Waiting for a Download: no transfer either. Its record says it is not here (`waitingGroupFile`).\n }\n }\n for (const [id] of this.wants) this.ask(this.kept.get(id)!);\n this.deps.changed();\n }\n\n /**\n * At start, once every group was read: the files of a group this device no longer has (`groupIds` are the groups it\n * has, a tombstone or a join under way among them) go, with their bytes. A group left before Leave took its files\n * left them behind, where nothing lists them.\n */\n async sweep(groupIds: string[]): Promise<void> {\n const here = new Set(groupIds);\n for (const groupId of await this.deps.store.groups().catch(() => [])) {\n if (here.has(groupId)) continue;\n for (const file of await this.deps.store.list(groupId).catch(() => [])) await this.deps.store.remove(file.id).catch(() => {});\n }\n }\n\n /**\n * The person stops a file that is coming, or that this device would fetch (Cancel, Decline): nobody is asked for it\n * any more, the transfer under way ends, what came of it is deleted, and it waits for a Download. Paused (`keep`),\n * what came stays, and Resume (`download`) goes on from there.\n */\n async halt(fileId: string, keep = false): Promise<void> {\n const kept = this.kept.get(fileId);\n if (!kept) throw new Error(\"No such file\");\n // Only a file that is coming pauses: one waiting for a Download has nothing to go on with.\n if (keep && !this.wants.has(fileId)) return;\n await this.stop(kept, keep);\n }\n\n /** Automatic downloads were turned off: files chosen by themselves and not here yet stop coming, and wait for a Download. */\n async autoDownloadsOff(): Promise<void> {\n for (const kept of [...this.kept.values()]) if (kept.auto && !kept.asked && this.wants.has(kept.id)) await this.stop(kept, false);\n }\n\n private async stop(kept: Kept, keep: boolean): Promise<void> {\n if (kept.held || kept.mine) return;\n const want = this.wants.get(kept.id);\n // Gone first: the transfer's end below asks nobody else (`transferChanged`), and a late offer is refused.\n this.wants.delete(kept.id);\n const edge = want?.wire && want.linkId ? this.edgesById.get(want.linkId) : undefined;\n const record = edge?.files.get(\"in\", want!.wire!);\n const { size } = kept.meta;\n // One every holder served damaged waits for a Download as any other: its record no longer says it failed.\n const failed = !keep && this.deps.transfers.get(kept.id)?.state === \"failed\";\n if (!keep) { kept.stored = 0; kept.fresh = true; }\n if (edge && record && !transferEnded(record)) {\n // The transfer's own writer deletes what it stored (`openTarget`), or keeps it when paused.\n if (!keep) kept.dropped = record.id;\n edge.files.cancel(\"in\", record.id);\n edge.taking.delete(record.id);\n } else if (!keep) {\n // No transfer under way: what an earlier one stored goes, before the next one writes.\n kept.idle = (kept.idle ?? Promise.resolve()).then(() => this.deps.store.write(kept.id)).then(writer => writer.discard()).catch(() => {});\n }\n if (keep) this.show(kept, { state: \"transferring\", stage: \"paused\", pausedBy: \"me\", transferred: kept.stored ?? 0, size, direction: \"in\" });\n else this.waiting(kept);\n // Not fetched by itself any more, nor asked for: it stays stopped (or paused) after a restart, and counts toward no limit.\n const wasPaused = !!kept.paused;\n kept.paused = keep;\n if (kept.auto || kept.asked || wasPaused !== keep || failed) {\n kept.auto = kept.asked = false;\n await this.deps.store.patch(kept.id, { group: this.groupField(kept), ...(failed && { transfer: { state: \"transferring\", transferred: 0, size } }) }).catch(() => {});\n }\n }\n\n /**\n * An error when this group took as many announcements of mine as it takes in a minute, or as its members take from\n * one member in `announceWindowMs` (past that they would not fetch the file by themselves); null when one may go now.\n */\n mayAnnounce(groupId: string): string | null {\n const wait = Math.max(...this.sentPaces(groupId).map(pace => pace.wait()));\n if (!wait) return null;\n const minutes = Math.ceil(wait / 60_000);\n return `You sent many files to this group just now. Wait ${minutes > 1 ? `${minutes} minutes` : \"a minute\"}.`;\n }\n\n /** My file, stored under `fileId` with the author's digest, is about to be announced in the group: its message takes it. */\n outgoing(groupId: string, fileId: string, meta: GroupFileMeta): void {\n this.own.set(`${groupId}\\n${meta.d}`, fileId);\n }\n /** The announcement did not go: the file is not this group's after all. */\n notSent(groupId: string, meta: GroupFileMeta): void { this.own.delete(`${groupId}\\n${meta.d}`); }\n\n /**\n * A message of the group announces a file (mine, or a member's): the file its row shows. A member's is kept here\n * unfetched, and fetched now when it is one this device takes by itself and the member announced no more than the\n * group fetches that way. The same message again (a catch-up, a whole copy) is the same file.\n */\n async announced(groupId: string, m: { id: string; sender: string; file: GroupFileMeta }, me: string): Promise<MessageFile | undefined> {\n const known = this.byMessage.get(`${groupId}\\n${m.id}`);\n if (known) return messageFile(this.kept.get(known)!);\n const ownId = m.sender === me ? this.own.get(`${groupId}\\n${m.file.d}`) : undefined;\n if (ownId) {\n this.own.delete(`${groupId}\\n${m.file.d}`);\n for (const pace of this.sentPaces(groupId)) pace.take();\n const kept: Kept = { id: ownId, groupId, message: m.id, author: me, meta: m.file, mine: true, auto: false, asked: false, held: true, holders: [], damaged: new Set() };\n this.add(kept);\n const transfer = { state: \"done\" as const, transferred: m.file.size, size: m.file.size };\n await this.deps.store.patch(ownId, { group: { message: m.id, author: me }, transfer }).catch(() => {});\n this.finished(kept, \"out\");\n this.queueHave(kept);\n return messageFile(kept);\n }\n // More from one member than a group fetches by itself: past it, the file is kept and waits for a Download (WISP 503,\n // Pace). Counted as they come, so a catch-up of files sent over hours is one burst: none of them is lost to it.\n const paced = this.pace(this.takenPace, `${groupId}\\n${m.sender}`, GROUP_FILE_LIMITS.announceTaken, GROUP_FILE_LIMITS.announceWindowMs).take();\n const id = groupFileId(groupId, \"in\");\n const auto = paced && this.deps.settings().autoDownloads && fetchesByItself(m.file, this.keptAutomatically(groupId));\n const holders = this.ahead.get(`${groupId}\\n${m.id}`) ?? [];\n this.ahead.delete(`${groupId}\\n${m.id}`);\n const kept: Kept = { id, groupId, message: m.id, author: m.sender, meta: m.file, mine: false, auto, asked: false, held: false, holders, damaged: new Set() };\n const { name, size, mime, voice, video, image } = m.file;\n // Known at once: the same message coming again meanwhile (another copy of it) is this file.\n this.add(kept);\n // No bytes yet: where they go is decided when they come (`GroupFileStore.write`).\n await this.deps.store.put({ id, linkId: `group:${groupId}`, direction: \"in\", digest: m.file.d, createdAt: this.deps.now(),\n metadata: { name, size, mime, timestamp: this.deps.now(), voice, video, image },\n transfer: { state: \"transferring\", transferred: 0, size }, group: { message: m.id, author: m.sender, ...(auto && { auto: true }) } });\n if (auto) { this.wants.set(id, this.newWant()); this.ask(kept); }\n else this.waiting(kept);\n return messageFile(kept);\n }\n\n /** The person asks for a file this device did not fetch by itself (Download), or for one that stopped coming. */\n async download(fileId: string): Promise<void> {\n const kept = this.kept.get(fileId);\n if (!kept) throw new Error(\"No such file\");\n if (kept.held) return;\n const room = await this.deps.store.room().catch(() => null);\n if (room !== null && kept.meta.size > room) throw new Error(\"Not enough space on this device for this file\");\n if (!kept.asked || kept.paused) {\n kept.asked = true; kept.paused = false;\n await this.deps.store.patch(kept.id, { group: this.groupField(kept) }).catch(() => {});\n }\n // Asked again: every holder is worth asking again, one that served it damaged too.\n kept.damaged.clear();\n if (this.deps.transfers.get(kept.id)?.state === \"failed\") {\n await this.deps.store.patch(kept.id, { transfer: { state: \"transferring\", transferred: 0, size: kept.meta.size } }).catch(() => {});\n }\n const want = this.wants.get(kept.id);\n if (!want) this.wants.set(kept.id, this.newWant());\n else if (!want.wire) { want.tried.clear(); want.why = want.busy = undefined; }\n this.ask(kept);\n }\n\n /** The group is gone from this device: its files go too. */\n async drop(groupId: string): Promise<void> {\n for (const kept of [...this.kept.values()].filter(k => k.groupId === groupId)) {\n this.kept.delete(kept.id);\n this.byMessage.delete(`${groupId}\\n${kept.message}`);\n this.wants.delete(kept.id);\n this.deps.transfers.delete(kept.id);\n await this.deps.store.remove(kept.id).catch(() => {});\n }\n for (const [linkId, edge] of this.edgesById) if (edge.groupId === groupId) { edge.files.detach(); this.edgesById.delete(linkId); }\n for (const key of [...this.ahead.keys()]) if (key.startsWith(`${groupId}\\n`)) this.ahead.delete(key);\n this.communityHave.delete(groupId);\n this.deps.changed();\n }\n\n // -- edges ---------------------------------------------------------------------------------------------------\n\n /** An edge's files/3 session opened (both sides agreed it) or closed. */\n session(groupId: string, peer: string, linkId: string, open: boolean): void {\n const edge = this.edge(groupId, peer, linkId);\n if (open) edge.files.attach(); else edge.files.detach();\n // Another session, or none: its member may have fetched since what it did not hold, and is asked again.\n edge.gone.clear();\n if (open) this.retry(groupId);\n }\n\n /** An edge came up and both sides said where they are: what this device holds goes to it, and wants waiting for a holder go. */\n edgeReady(groupId: string, peer: string, linkId: string): void {\n if (!this.deps.ready(linkId)) return;\n const edge = this.edge(groupId, peer, linkId);\n if (this.deps.settings().serveFiles) {\n const held = [...this.kept.values()].filter(k => k.groupId === groupId && k.held).map(k => k.message).slice(-GROUP_FILE_LIMITS.haveIds);\n if (held.length) this.sendHave(edge, held);\n }\n this.retry(groupId);\n }\n\n /** A `group-want`, `group-want-no` or `group-have` on an edge, from the member it is pinned to. */\n async frame(groupId: string, peer: string, linkId: string, frame: Record<string, unknown>): Promise<void> {\n if (frame.t === GROUP_WANT_FRAME) {\n const want = readGroupWant(frame, groupId);\n if (want) await this.serve(this.edge(groupId, peer, linkId), want.id);\n } else if (frame.t === GROUP_WANT_NO_FRAME) {\n const no = readGroupWantNo(frame, groupId);\n if (no) this.refused(groupId, peer, linkId, no.id, no.why);\n } else if (frame.t === GROUP_HAVE_FRAME) {\n const ids = readGroupHave(frame, groupId);\n if (ids) this.heard(groupId, peer, ids);\n }\n }\n\n /** A community's `group-have`, an application frame its signer sent the group. */\n communityApp(groupId: string, sender: string, frame: Record<string, unknown>): void {\n const ids = readGroupHave(frame);\n if (ids) this.heard(groupId, sender, ids);\n }\n\n /**\n * A files/3 frame on an edge. An offer is taken only for a file this device asked that member for (`gm` naming the\n * message): nobody can push a file into another's storage. What it brings belongs to the group's message, not to the\n * edge as a chat.\n */\n filesFrame(groupId: string, peer: string, linkId: string, frame: Record<string, unknown>): Promise<void> {\n const edge = this.edge(groupId, peer, linkId);\n if (frame.t === \"pf-offer\" && typeof frame.id === \"string\" && /^[A-Za-z0-9_-]{8,64}$/.test(frame.id) && !edge.taking.has(frame.id)) {\n const kept = isGroupMessageId(frame.gm) ? this.kept.get(this.byMessage.get(`${groupId}\\n${frame.gm}`) ?? \"\") : undefined;\n const want = kept && this.wants.get(kept.id);\n if (!kept || kept.held || !want || want.linkId !== linkId || want.wire) {\n this.deps.sendFiles(linkId, { t: \"pf-refuse\", id: frame.id, why: \"invalid\" });\n return Promise.resolve();\n }\n edge.taking.set(frame.id, kept.id);\n // It offers the file: it holds it, whether or not it ever said so.\n kept.holders = newestHolder(peer, kept.holders);\n want.wire = frame.id;\n want.why = undefined;\n want.movedAt = this.deps.now();\n }\n return edge.files.handle(frame);\n }\n\n /** Wants that waited too long for an offer, or for bytes, go to the next holder; `group-have`s gathered go. */\n tick(): void {\n const now = this.deps.now();\n for (const [id, want] of this.wants) {\n const kept = this.kept.get(id);\n if (!kept) { this.wants.delete(id); continue; }\n if (now - (want.wire ? want.movedAt : want.at) < GROUP_FILE_LIMITS.wantWaitMs) continue;\n if (want.wire) {\n // A holder that stalls is left: the transfer ends here, and the next holder is asked (`transferChanged`).\n const edge = want.linkId ? this.edgesById.get(want.linkId) : undefined;\n if (edge?.files.get(\"in\", want.wire) && !transferEnded(edge.files.get(\"in\", want.wire)!)) { edge.files.cancel(\"in\", want.wire); continue; }\n want.wire = undefined;\n }\n if (want.holder) want.tried.add(want.holder);\n // Everyone asked in turn: a new round, as holders come and go.\n else { want.tried.clear(); want.why = want.busy = undefined; }\n this.ask(kept);\n }\n this.letGo();\n for (const edge of this.edgesById.values()) {\n if (edge.have.size && this.deps.ready(edge.linkId)) this.sendHave(edge, [...edge.have]);\n }\n for (const [groupId, have] of this.communityHave) {\n if (!have.ids.size || now - have.at < GROUP_FILE_LIMITS.communityHaveEveryMs) continue;\n const ids = [...have.ids].slice(-GROUP_FILE_LIMITS.haveIds);\n have.ids.clear();\n have.at = now;\n void this.deps.sendApp(groupId, { t: GROUP_HAVE_FRAME, ids }).catch(() => {});\n }\n }\n\n // -- asking --------------------------------------------------------------------------------------------------\n\n private newWant(): Want { return { at: this.deps.now(), tried: new Set(), moved: 0, movedAt: 0 }; }\n\n /**\n * Wants of this group that wait for a holder: asked now (an edge came up or freed, or a member said it holds\n * something). One queued behind an edge's file is asked only while an edge of the group is free to take it, and free\n * for it (`freeFor`): once every edge has a file asked of it, or the free ones are of members it already asked, it would\n * only queue again. So a file that ends asks the few wants it frees an edge for, not every want waiting (a member back\n * from away with hundreds of files to fetch asked each of them once per file, more so with a member in the group that\n * holds none of them, or serves none).\n */\n private retry(groupId: string): void {\n let free: [string, string][] | undefined;\n for (const [id, want] of this.wants) {\n const kept = this.kept.get(id);\n if (kept?.groupId !== groupId || want.holder) continue;\n if (this.deps.transfers.get(id)?.stage === \"queued\" && !this.freeFor(kept, want, free ??= this.freeEdges(groupId))) continue;\n this.ask(kept);\n // That ask may have taken a free edge.\n free = undefined;\n }\n }\n\n /** The group's edges that are up, with files/3 live and no file asked of their member: a want could be asked on them. */\n private freeEdges(groupId: string): [string, string][] {\n return [...this.deps.edges(groupId)].filter(([, linkId]) => this.deps.ready(linkId) && this.edgesById.get(linkId)?.files.live && !this.askedOf(linkId));\n }\n\n /** One of these free edges (member key, link id) is one `ask` would ask this want on: asked now, it would not only queue again. */\n private freeFor(kept: Kept, want: Want, free: [string, string][]): boolean {\n const membership = this.deps.membership(kept.groupId);\n if (!membership) return false;\n const known = this.knownHolders(kept, membership);\n // As in `ask`: the others only while no known holder is worth waiting for.\n const others = !this.waitForKnown(kept, want, known, this.deps.edges(kept.groupId));\n return free.some(([key, linkId]) => this.mayAsk(kept, want, key, linkId)\n && (known.includes(key) || (others && key !== membership.me && membership.couldRead(key, kept.message))));\n }\n\n /** The members known to hold the file (its author first), in the group's roster, but this device. */\n private knownHolders(kept: Kept, membership: GroupFileMembership | undefined): string[] {\n return [kept.author, ...kept.holders.filter(key => key !== kept.author)]\n .filter(key => membership && key !== membership.me && membership.inRoster(key));\n }\n\n /**\n * A known holder that answered busy, or the author whose edge another file of this device's is asked of, its edge up,\n * serves when its place frees: the others are asked only when no known holder is worth waiting for (none in reach, or\n * every one gone), as after a restart here. Only the author's edge holds them back while it is asked of: any member\n * can say it holds files, and one that serves the file asked of it slowly would keep every other file it named here.\n */\n private waitForKnown(kept: Kept, want: Want, known: string[], edges: Map<string, string>): boolean {\n return known.some(key => {\n const linkId = edges.get(key) ?? \"\";\n return this.deps.ready(linkId) && (want.busy?.has(key) || (key === kept.author && this.askedOf(linkId, want)));\n });\n }\n\n /** The want may ask this member in this round: not asked yet, it did not serve it damaged, and did not say it is gone. */\n private mayAsk(kept: Kept, want: Want, key: string, linkId: string | undefined): boolean {\n return !want.tried.has(key) && !kept.damaged.has(key) && !this.edgesById.get(linkId ?? \"\")?.gone.has(kept.message);\n }\n\n /**\n * Asks the next holder for a file: the author first while its edge is up, then the holders whose edge is up, then every\n * other member whose edge is up and that could read its message. Who holds what is heard, not kept: a holder names few\n * of its files to an edge that opens, and none it was told of is known after a restart here, so a member in reach is\n * asked whether or not it said so (one that does not hold it answers `gone`, and is not asked again). Only over an\n * edge this device has. (A private group past 16 members, or a community, reaches a holder it has no edge with\n * over a file session of the two members, WISP 503 § The file session; that session is not built yet, so such a\n * holder is not asked, and the file waits for one that is reachable.)\n */\n private ask(kept: Kept): void {\n const want = this.wants.get(kept.id);\n if (!want || kept.held || want.wire) return;\n const membership = this.deps.membership(kept.groupId);\n const edges = this.deps.edges(kept.groupId);\n const known = this.knownHolders(kept, membership);\n // Every holder known served it damaged: asking again would fetch the same bytes, all of them, round after round.\n if (known.length && known.every(key => kept.damaged.has(key))) { this.stopDamaged(kept); return; }\n const others = this.waitForKnown(kept, want, known, edges) ? [] : [...edges.keys()].filter(key => !known.includes(key) && membership && key !== membership.me && membership.couldRead(key, kept.message));\n const order = [...known, ...others].filter(key => this.mayAsk(kept, want, key, edges.get(key)));\n let behind = false;\n for (const key of order) {\n const linkId = edges.get(key);\n if (!linkId || !this.deps.ready(linkId) || !this.edgesById.get(linkId)?.files.live) continue;\n // A holder serves this device one file at a time (WISP 503 § Serving limits): asked now, it would answer `busy`.\n // This one waits its turn, and is asked when the file before it ends (`transferChanged`).\n if (this.askedOf(linkId, want)) { behind = true; continue; }\n // Asked of it before the frame goes: its answer is this want's, however soon it comes.\n Object.assign(want, { holder: key, linkId, at: this.deps.now() });\n this.askedOn.set(linkId, { id: kept.id, want });\n if (!this.deps.send(linkId, { t: GROUP_WANT_FRAME, g: kept.groupId, id: kept.message })) { want.tried.add(key); continue; }\n if (want.holder === key) this.show(kept, { state: \"transferring\", stage: \"queued\", transferred: kept.stored ?? 0, size: kept.meta.size, direction: \"in\" });\n return;\n }\n Object.assign(want, { holder: undefined, linkId: undefined, at: this.deps.now() });\n if (behind) { this.show(kept, { state: \"transferring\", stage: \"queued\", transferred: kept.stored ?? 0, size: kept.meta.size, direction: \"in\" }); return; }\n // Why it waits: a holder busy, bytes that came damaged (its person can ask again), or nobody in reach has it.\n const wait = want.why ?? \"nobody\";\n this.show(kept, { state: \"transferring\", stage: \"waiting\", transferred: kept.stored ?? 0, size: kept.meta.size, direction: \"in\",\n wait, note: WAITING_NOTES[wait], ...(wait === \"damaged\" && { stalled: true }) });\n }\n\n /**\n * Another file is asked of, or coming from, the member at the other end of this edge. A want gets its edge only in\n * `ask`, which notes it in `askedOn`, and one edge has one want at a time: the want noted there is it, while it still\n * waits and is still on that edge.\n */\n private askedOf(linkId: string, except?: Want): boolean {\n const on = this.askedOn.get(linkId);\n if (!on || on.want === except) return false;\n if (on.want.linkId === linkId && this.wants.get(on.id) === on.want) return true;\n this.askedOn.delete(linkId);\n return false;\n }\n\n /** No holder serves the author's bytes: the want stops, and stays stopped after a restart, until Download. */\n private stopDamaged(kept: Kept): void {\n this.wants.delete(kept.id);\n const { size } = kept.meta;\n void this.deps.store.patch(kept.id, { transfer: { state: \"failed\", transferred: 0, size, error: DAMAGED } }).catch(() => {});\n this.show(kept, { state: \"failed\", transferred: 0, size, direction: \"in\", retry: true, error: DAMAGED });\n }\n\n /** A holder's no: the next one is asked. One that deleted the file is no holder any more. */\n private refused(groupId: string, peer: string, linkId: string, messageId: string, why: GroupWantRefusal): void {\n const kept = this.kept.get(this.byMessage.get(`${groupId}\\n${messageId}`) ?? \"\");\n const want = kept && this.wants.get(kept.id);\n if (!kept || !want || want.linkId !== linkId || want.wire) return;\n if (why === \"gone\") {\n kept.holders = kept.holders.filter(key => key !== peer);\n this.edgesById.get(linkId)?.gone.add(messageId);\n }\n if (why === \"busy\") { want.why = \"busy\"; (want.busy ??= new Set()).add(peer); }\n want.tried.add(peer);\n want.holder = want.linkId = undefined;\n this.ask(kept);\n // The edge is free for a file waiting its turn there.\n this.retry(groupId);\n }\n\n /** A member says it holds these files: remembered as a holder of each (newest first), and asked when a want waits. */\n private heard(groupId: string, from: string, ids: string[]): void {\n const membership = this.deps.membership(groupId);\n if (!membership || from === membership.me || !membership.inRoster(from)) return;\n const newest = (holders: string[]) => newestHolder(from, holders);\n const edge = this.edgesById.get(this.deps.edges(groupId).get(from) ?? \"\");\n // Its edge, while free: a want queued behind other edges' files is asked again only if it can be asked there.\n let free: [string, string][] | undefined;\n for (const message of ids) {\n const key = `${groupId}\\n${message}`, kept = this.kept.get(this.byMessage.get(key) ?? \"\");\n if (!kept) {\n // Its message has not come yet (it is on its way in a catch-up): who holds it is kept for when it does.\n this.ahead.set(key, newest(this.ahead.get(key) ?? []));\n if (this.ahead.size > GROUP_FILE_LIMITS.heardAhead) this.ahead.delete(this.ahead.keys().next().value!);\n continue;\n }\n if (kept.mine) continue;\n kept.holders = newest(kept.holders);\n edge?.gone.delete(message);\n const want = this.wants.get(kept.id);\n if (!want) continue;\n want.tried.delete(from);\n want.busy?.delete(from);\n if (want.holder) continue;\n if (this.deps.transfers.get(kept.id)?.stage === \"queued\" && !this.freeFor(kept, want, free ??= this.freeEdges(groupId).filter(([key]) => key === from))) continue;\n this.ask(kept);\n free = undefined;\n }\n }\n\n // -- serving -------------------------------------------------------------------------------------------------\n\n /** A member asks for a file: offered over files/3 on its edge, or a `group-want-no` saying why not. */\n private async serve(edge: Edge, messageId: string): Promise<void> {\n const kept = this.kept.get(this.byMessage.get(`${edge.groupId}\\n${messageId}`) ?? \"\");\n const here = !!kept?.held && !!(await this.deps.store.get(kept.id).catch(() => undefined));\n // Decided and offered in one step: two wants at once never both find the last free place.\n const why = this.mayServe(edge, messageId, here ? kept : undefined);\n if (why || !kept) { this.deps.send(edge.linkId, { t: GROUP_WANT_NO_FRAME, g: edge.groupId, id: messageId, why: why ?? \"gone\" }); return; }\n const wire = toBase64Url(randomBytes(12));\n edge.serving.set(wire, kept.id);\n edge.servedMoved.set(wire, { confirmed: 0, at: this.deps.now() });\n const { name, mime, size, voice, video, image } = kept.meta;\n const file: FileInfo = { id: wire, name, mime, size, timestamp: this.deps.now(), ...(voice && { voice }), ...(video && { video }), ...(image && { image }) };\n edge.files.offer(file, kept.meta.d);\n }\n\n /**\n * Why this device does not serve a file now, or null when it does (WISP 503 § Asking, Serving limits): `refused` to\n * someone who could not read the announcement, `gone` when it is not here (`kept` undefined), `busy` when serving is\n * off or full.\n */\n private mayServe(edge: Edge, messageId: string, kept: Kept | undefined): GroupWantRefusal | null {\n const membership = this.deps.membership(edge.groupId);\n if (!membership || !membership.couldRead(edge.peer, messageId)) return \"refused\";\n if (!kept) return \"gone\";\n if (!this.deps.settings().serveFiles || !edge.files.live) return \"busy\";\n this.letGo();\n const active = this.servingNow();\n if (active.length >= GROUP_FILE_LIMITS.serveAtOnce) { edge.busy.add(messageId); return \"busy\"; }\n // One that has all its bytes waits only for the member's word that its check passed, which may come after this want.\n if (active.some(a => a.peer === edge.peer && a.sending)) return \"busy\";\n const today = Math.floor(this.deps.now() / 86_400_000);\n if (this.served.day !== today) this.served = { day: today, bytes: 0 };\n const reserved = active.reduce((sum, a) => sum + a.size, 0);\n return this.served.bytes + reserved + kept.meta.size > GROUP_FILE_LIMITS.serveBytesPerDay ? \"busy\" : null;\n }\n\n /**\n * Transfers served now, all groups together: to whom, what each takes of the day (its size, or what it sent when that\n * is more), and whether bytes are still to go.\n */\n private servingNow(): { peer: string; size: number; sending: boolean }[] {\n const active: { peer: string; size: number; sending: boolean }[] = [];\n for (const edge of this.edgesById.values()) for (const wire of edge.serving.keys()) {\n const record = edge.files.get(\"out\", wire);\n if (record && !transferEnded(record)) {\n active.push({ peer: edge.peer, size: Math.max(record.file.size, edge.files.sent(wire)), sending: record.confirmed < record.file.size });\n }\n }\n return active;\n }\n\n /**\n * An asker has a served transfer send parts again (WISP 503 § Serving limits): every byte sent counts toward the day,\n * so it goes on only while the day has room for it.\n */\n private mayResend(): boolean {\n const today = Math.floor(this.deps.now() / 86_400_000);\n if (this.served.day !== today) this.served = { day: today, bytes: 0 };\n return this.served.bytes + this.servingNow().reduce((sum, a) => sum + a.size, 0) < GROUP_FILE_LIMITS.serveBytesPerDay;\n }\n\n /**\n * Served transfers that no longer count end, their places free for others (WISP 503 § Serving limits): the asker's\n * session closed, it is not in the roster any more, or its bytes have not moved for `wantWaitMs` (the asker has moved\n * on by then).\n */\n private letGo(): void {\n const now = this.deps.now();\n for (const edge of this.edgesById.values()) for (const wire of [...edge.serving.keys()]) {\n const record = edge.files.get(\"out\", wire);\n if (!record || transferEnded(record)) continue;\n let moved = edge.servedMoved.get(wire);\n if (!moved || moved.confirmed !== record.confirmed) edge.servedMoved.set(wire, (moved = { confirmed: record.confirmed, at: now }));\n const gone = !edge.files.live || !this.deps.membership(edge.groupId)?.inRoster(edge.peer);\n if (gone || now - moved.at >= GROUP_FILE_LIMITS.wantWaitMs) edge.files.cancel(\"out\", wire);\n }\n }\n\n /**\n * Says on an edge which files this device holds, as many frames as the edge takes now (`haveFrames` in\n * `haveWindowMs`); the ids past that, and those of a frame that could not go, wait for the next tick with room.\n */\n private sendHave(edge: Edge, ids: string[]): void {\n for (const id of ids) edge.have.add(id);\n for (let i = 0; i < ids.length; i += GROUP_FILE_LIMITS.haveIds) {\n const frame = ids.slice(i, i + GROUP_FILE_LIMITS.haveIds);\n if (edge.haveSent.wait() || !this.deps.send(edge.linkId, { t: GROUP_HAVE_FRAME, g: edge.groupId, ids: frame })) return;\n edge.haveSent.take();\n for (const id of frame) edge.have.delete(id);\n }\n }\n\n /**\n * This device holds a new file: the group hears it with the next `group-have`, which gathers what there is to say\n * until the next tick.\n */\n private queueHave(kept: Kept): void {\n if (!this.deps.settings().serveFiles) return;\n if (this.deps.membership(kept.groupId)?.community) {\n let have = this.communityHave.get(kept.groupId);\n if (!have) this.communityHave.set(kept.groupId, (have = { ids: new Set(), at: 0 }));\n have.ids.add(kept.message);\n return;\n }\n for (const [key, linkId] of this.deps.edges(kept.groupId)) this.edge(kept.groupId, key, linkId).have.add(kept.message);\n }\n\n // -- files/3 on an edge --------------------------------------------------------------------------------------\n\n private edge(groupId: string, peer: string, linkId: string): Edge {\n const known = this.edgesById.get(linkId);\n if (known) return known;\n const edge: Edge = { linkId, groupId, peer, files: undefined as unknown as ChatFiles, serving: new Map(), servedMoved: new Map(), taking: new Map(), ended: [], have: new Set(),\n haveSent: new RateWindow(GROUP_FILE_LIMITS.haveFrames, GROUP_FILE_LIMITS.haveWindowMs, () => this.deps.now()), busy: new Set(), gone: new Set() };\n edge.files = new ChatFiles({\n // An offer names the group message it serves (`gm`), which a 1:1 chat never carries.\n send: frame => {\n const kept = frame.t === \"pf-offer\" ? this.kept.get(edge.serving.get(frame.id as string) ?? \"\") : undefined;\n return this.deps.sendFiles(linkId, kept ? { ...frame, gm: kept.message } : frame);\n },\n decide: file => this.decide(edge, file),\n openTarget: record => this.openTarget(edge, record),\n openSource: record => this.openSource(edge, record),\n changed: (record, transferred) => this.transferChanged(edge, record, transferred),\n room: () => this.deps.store.room(),\n ...(this.deps.sendData && { sendData: (id: string, offset: number, data: Uint8Array) => this.deps.sendData!(linkId, id, offset, data) }),\n writable: () => this.deps.writable?.(linkId),\n resend: record => !edge.serving.has(record.id) || this.mayResend(),\n });\n this.edgesById.set(linkId, edge);\n return edge;\n }\n\n /** An offer that answers my want (`filesFrame` checked that): taken, unless it is not the file announced or there is no room. */\n private async decide(edge: Edge, file: FileInfo): Promise<OfferDecision> {\n const kept = this.kept.get(edge.taking.get(file.id) ?? \"\"), want = kept && this.wants.get(kept.id);\n if (!kept || kept.held || want?.linkId !== edge.linkId || want.wire !== file.id || file.size !== kept.meta.size) return { refuse: \"invalid\" };\n const room = await this.deps.store.room().catch(() => null);\n return room !== null && file.size > room ? { refuse: \"no-room\", room } : \"accept\";\n }\n\n private async openTarget(edge: Edge, record: FileTransferRecord): Promise<IncomingTarget> {\n const kept = this.kept.get(edge.taking.get(record.id) ?? \"\");\n if (!kept) throw new Error(\"No such file\");\n // What an earlier holder's transfer stored stays, and this one goes on from there (WISP 503: the asker accepts from\n // the offset it stored): every holder serves the author's bytes, and the whole file is checked at the end.\n await kept.idle;\n const writer = await this.deps.store.write(kept.id, !kept.fresh);\n kept.fresh = false;\n const resumed = writer.offset > 0;\n let damaged = false;\n return {\n offset: writer.offset,\n append: bytes => writer.append(bytes),\n flush: () => writer.flush(),\n // Against the author's digest, read back from storage, not the holder's word: a holder can withhold a file, never\n // serve another one.\n verify: async () => {\n damaged = (await writer.digest()) !== kept.meta.d;\n const want = this.wants.get(kept.id);\n if (damaged && want) want.why = \"damaged\";\n return !damaged;\n },\n // Only damaged bytes go, and those of a file its person cancelled. Left with a holder that stalled or stopped, what\n // came is kept for the next holder.\n discard: () => {\n // Whole from this holder and damaged: its bytes are not the author's, and it is not asked for them again.\n if (damaged) { kept.stored = 0; if (resumed) kept.fresh = kept.unclear = true; else kept.damaged.add(edge.peer); }\n return (kept.idle = (damaged || kept.dropped === record.id ? writer.discard() : writer.close()).catch(() => {}));\n },\n };\n }\n\n private async openSource(edge: Edge, record: FileTransferRecord): Promise<OutgoingSource> {\n const kept = this.kept.get(edge.serving.get(record.id) ?? \"\");\n const stored = kept && await this.deps.store.get(kept.id);\n if (!kept || !stored) throw new Error(\"The file is gone\");\n return { read: (offset, length) => this.deps.store.read(stored, offset, length), digest: async () => kept.meta.d };\n }\n\n /** A transfer on the edge ended: its record stays among the last `ENDED_KEPT`, and the oldest past that is forgotten. */\n private transferOver(edge: Edge, record: FileTransferRecord): void {\n if (edge.ended.some(([direction, id]) => direction === record.direction && id === record.id)) return;\n edge.ended.push([record.direction, record.id]);\n while (edge.ended.length > ENDED_KEPT) edge.files.forget(...edge.ended.shift()!);\n }\n\n private transferChanged(edge: Edge, record: FileTransferRecord, transferred: number): void {\n if (transferEnded(record)) this.transferOver(edge, record);\n if (record.direction === \"out\") {\n if (transferEnded(record) && edge.serving.has(record.id)) {\n // What was sent to a member counts toward the day's serving, done or not, and whatever it confirmed.\n const today = Math.floor(this.deps.now() / 86_400_000);\n if (this.served.day !== today) this.served = { day: today, bytes: 0 };\n this.served.bytes += edge.files.sent(record.id);\n edge.serving.delete(record.id);\n edge.servedMoved.delete(record.id);\n // A slot is free: those turned away as busy hear this device holds their file (with the next `group-have`), and\n // ask it again rather than wait out their round.\n const serving = this.deps.settings().serveFiles;\n for (const other of this.edgesById.values()) {\n if (serving) for (const id of other.busy) other.have.add(id);\n other.busy.clear();\n }\n }\n return;\n }\n const kept = this.kept.get(edge.taking.get(record.id) ?? \"\"), want = kept && this.wants.get(kept.id);\n // A transfer this device moved on from (the holder stalled, then went on) changes nothing.\n if (!kept || !want || want.wire !== record.id || want.linkId !== edge.linkId) return;\n if (transferred !== want.moved) { want.moved = transferred; want.movedAt = this.deps.now(); }\n if (record.state !== \"failed\" && record.state !== \"declined\" && record.state !== \"cancelled\") kept.stored = transferred;\n const { size } = kept.meta;\n switch (record.state) {\n case \"done\": {\n kept.held = true;\n this.wants.delete(kept.id);\n edge.taking.delete(record.id);\n void this.deps.store.patch(kept.id, { transfer: { state: \"done\", transferred: size, size } }).catch(() => {});\n this.finished(kept, \"in\");\n this.queueHave(kept);\n // The holder is free for the next file this device waits for.\n this.retry(kept.groupId);\n return;\n }\n case \"failed\": case \"declined\": case \"cancelled\":\n // Damaged (refused, its bytes deleted), stopped by the holder or left here: the next holder is asked.\n edge.taking.delete(record.id);\n // A resumed file that failed its check may owe its bad bytes to the earlier holder: this one is asked again, from 0.\n if (want.holder && !kept.unclear) want.tried.add(want.holder);\n kept.unclear = false;\n Object.assign(want, { wire: undefined, holder: undefined, linkId: undefined, moved: 0 });\n this.ask(kept);\n this.retry(kept.groupId);\n return;\n case \"verifying\":\n this.show(kept, { state: \"transferring\", stage: \"verifying\", transferred, size, direction: \"in\" });\n return;\n default:\n this.show(kept, { state: \"transferring\", transferred, size, direction: \"in\" });\n }\n }\n\n // -- bookkeeping ---------------------------------------------------------------------------------------------\n\n private add(kept: Kept): void {\n this.kept.set(kept.id, kept);\n this.byMessage.set(`${kept.groupId}\\n${kept.message}`, kept.id);\n }\n\n /** Bytes this device fetched by itself from the group and keeps (or is fetching): what `autoBytesPerGroup` bounds. */\n private keptAutomatically(groupId: string): number {\n let sum = 0;\n for (const kept of this.kept.values()) if (kept.groupId === groupId && kept.auto && !kept.mine) sum += kept.meta.size;\n return sum;\n }\n\n private groupField(kept: Kept): NonNullable<StoredFile[\"group\"]> {\n return { message: kept.message, author: kept.author, ...(kept.auto && { auto: true }), ...(kept.asked && { asked: true }), ...(kept.paused && { paused: true }) };\n }\n\n /** The bytes of a file kept here from transfers that did not finish. */\n private async keptBytes(id: string): Promise<number> {\n const writer = await this.deps.store.write(id, true).catch(() => undefined);\n if (!writer) return 0;\n await writer.close().catch(() => {});\n return writer.offset;\n }\n\n private pace(windows: Map<string, RateWindow>, key: string, limit: number, ms: number): RateWindow {\n let window = windows.get(key);\n if (!window) windows.set(key, (window = new RateWindow(limit, ms, () => this.deps.now())));\n return window;\n }\n\n /** My announcements in a group, in a minute and in the window the members count them over. */\n private sentPaces(groupId: string): RateWindow[] {\n return [this.pace(this.sentPace, groupId, GROUP_FILE_LIMITS.announcePerMinute, 60_000),\n this.pace(this.sentTaken, groupId, GROUP_FILE_LIMITS.announceTaken, GROUP_FILE_LIMITS.announceWindowMs)];\n }\n\n /**\n * The file waits for its person's Download: said in one state (the CLI's `file.offered`), then left to its record,\n * which says it is not here (`waitingGroupFile`). A view kept for each such file would go into every state the engine\n * builds, so each would cost more with every file the groups ever had.\n */\n private waiting(kept: Kept): void {\n this.show(kept, { state: \"transferring\", stage: \"asking\", transferred: 0, size: kept.meta.size, direction: \"in\" });\n this.deps.brief(kept.id);\n }\n\n /**\n * All here: said in one state (the CLI's `file.done`), then left to its record, which says it is done. A view kept for\n * each file fetched or sent would go into every state the engine builds until the next start.\n */\n private finished(kept: Kept, direction: \"in\" | \"out\"): void {\n this.show(kept, { state: \"done\", transferred: kept.meta.size, size: kept.meta.size, direction });\n this.deps.brief(kept.id);\n }\n\n /** A file's row shows `view`; the engine hears of it only when that is not what the row shows already. */\n private show(kept: Kept, view: FileTransferView): void {\n if (sameView(this.deps.transfers.get(kept.id), view)) return;\n this.deps.transfers.set(kept.id, view);\n this.deps.changed();\n }\n}\n\n/** Two views of a transfer that say the same: their fields are all plain values. */\nfunction sameView(a: FileTransferView | undefined, b: FileTransferView): boolean {\n if (!a) return false;\n const keys = new Set([...Object.keys(a), ...Object.keys(b)] as (keyof FileTransferView)[]);\n for (const key of keys) if (a[key] !== b[key]) return false;\n return true;\n}\n\n/** A file's holders with `key` the newest of them, the `holders` newest kept. */\nconst newestHolder = (key: string, holders: string[]): string[] => [key, ...holders.filter(other => other !== key)].slice(0, GROUP_FILE_LIMITS.holders);\n\nfunction messageFile(kept: Kept): MessageFile {\n const { name, size, mime, voice, video, image } = kept.meta;\n return { id: kept.id, name, size, mime, ...(voice && { voice }), ...(video && { video }), ...(image && { image }) };\n}\n"],"mappings":"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"}
|