@ghostlytools/cli 1.3.9 → 1.4.0
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 +13 -4
- package/SKILL.md +7 -4
- package/dist/assets/{api-Cp9GxjUO.js → api-BqLfti32.js} +2 -2
- package/dist/assets/{api-BCrwJ57L.js → api-JMkEO0RR.js} +130 -50
- package/dist/assets/api-JMkEO0RR.js.map +1 -0
- package/dist/assets/{apiKit-B4qhnLQT.js → apiKit-BpIUCwa-.js} +3 -3
- package/dist/assets/{apiKit-B4qhnLQT.js.map → apiKit-BpIUCwa-.js.map} +1 -1
- package/dist/assets/{apps-CpxENMgl.js → apps-BdfyGVZ1.js} +1 -1
- package/dist/assets/{apps-DOH1P_S0.js → apps-Bettj7WQ.js} +6 -6
- package/dist/assets/apps-Bettj7WQ.js.map +1 -0
- package/dist/assets/{arkade-Cyf5MGxG.js → arkade-CP4HvKaa.js} +2 -2
- package/dist/assets/{arkade-Cyf5MGxG.js.map → arkade-CP4HvKaa.js.map} +1 -1
- package/dist/assets/{backup-CqM-0-2v.js → backup-BbO2zLjx.js} +1 -1
- package/dist/assets/{backup-CTpFFQ-4.js → backup-BmBtta66.js} +7 -7
- package/dist/assets/{backup-CTpFFQ-4.js.map → backup-BmBtta66.js.map} +1 -1
- package/dist/assets/blobFiles-B0rNnHlO.js +35 -0
- package/dist/assets/blobFiles-B0rNnHlO.js.map +1 -0
- package/dist/assets/{buttons-BtxwyM2l.js → buttons-YgijrprJ.js} +4 -4
- package/dist/assets/{buttons-BtxwyM2l.js.map → buttons-YgijrprJ.js.map} +1 -1
- package/dist/assets/buttons-_GBlxAti.js +2 -0
- package/dist/assets/{endpoint-BOTFMpfS.js → endpoint-Cl3cgAYl.js} +3 -2
- package/dist/assets/endpoint-Cl3cgAYl.js.map +1 -0
- package/dist/assets/{envelope-DW8TWKPD.js → envelope-C7vnmsJN.js} +2 -2
- package/dist/assets/{envelope-DW8TWKPD.js.map → envelope-C7vnmsJN.js.map} +1 -1
- package/dist/assets/{errors-B-zMxbcd.js → errors-BLSPp0tK.js} +3 -1
- package/dist/assets/errors-BLSPp0tK.js.map +1 -0
- package/dist/assets/{fileBytes-Di7VZtmU.js → fileBytes-B-fqGdEd.js} +3 -3
- package/dist/assets/{fileBytes-Di7VZtmU.js.map → fileBytes-B-fqGdEd.js.map} +1 -1
- package/dist/assets/{fileBytes-CgbibpbV.js → fileBytes-C2jycN_7.js} +1 -1
- package/dist/assets/{fileBytes-CfSXjTFa.js → fileBytes-D6fqFQEX.js} +6 -3
- package/dist/assets/fileBytes-D6fqFQEX.js.map +1 -0
- package/dist/assets/fileBytesIdb-DOxm_BKt.js +2 -0
- package/dist/assets/{fileBytesIdb-D5u6H8X_.js → fileBytesIdb-_HnxdacI.js} +3 -3
- package/dist/assets/{fileBytesIdb-D5u6H8X_.js.map → fileBytesIdb-_HnxdacI.js.map} +1 -1
- package/dist/assets/{fileBytesOpfs-Dd1N4ERH.js → fileBytesOpfs-p_CANncD.js} +3 -3
- package/dist/assets/{fileBytesOpfs-Dd1N4ERH.js.map → fileBytesOpfs-p_CANncD.js.map} +1 -1
- package/dist/assets/{groupFiles-uxa0U9_n.js → groupFiles-sIdGq4AV.js} +5 -5
- package/dist/assets/groupFiles-sIdGq4AV.js.map +1 -0
- package/dist/assets/{host-DPy0eaH9.js → host-Drd-c4VO.js} +147 -25
- package/dist/assets/host-Drd-c4VO.js.map +1 -0
- package/dist/assets/{hyperdhtRelay-De5FT7Am.js → hyperdhtRelay-13aQ4KOf.js} +2 -2
- package/dist/assets/{hyperdhtRelay-De5FT7Am.js.map → hyperdhtRelay-13aQ4KOf.js.map} +1 -1
- package/dist/assets/{idb-B5L8wuOm.js → idb-CMVgtn2k.js} +59 -6
- package/dist/assets/idb-CMVgtn2k.js.map +1 -0
- package/dist/assets/{irohWeb-z5QDDWa-.js → irohWeb-DHCxSvHF.js} +2 -2
- package/dist/assets/{irohWeb-z5QDDWa-.js.map → irohWeb-DHCxSvHF.js.map} +1 -1
- package/dist/assets/{main-Dt7xrqtb.js → main-BWAwKY4f.js} +476 -141
- package/dist/assets/main-BWAwKY4f.js.map +1 -0
- package/dist/assets/{mainline-Cld-bKeC.js → mainline-CsG-DdJ5.js} +2 -2
- package/dist/assets/{mainline-Cld-bKeC.js.map → mainline-CsG-DdJ5.js.map} +1 -1
- package/dist/assets/{networkReason-vUHo6A_C.js → networkReason-B91upMCb.js} +3 -3
- package/dist/assets/{networkReason-vUHo6A_C.js.map → networkReason-B91upMCb.js.map} +1 -1
- package/dist/assets/{nwc-zXpmd2FN.js → nwc-BFv67iSz.js} +3 -3
- package/dist/assets/{nwc-zXpmd2FN.js.map → nwc-BFv67iSz.js.map} +1 -1
- package/dist/assets/{nwcProvider-D482SQ1a.js → nwcProvider-BKNFdH9v.js} +2 -2
- package/dist/assets/{nwcProvider-D482SQ1a.js.map → nwcProvider-BKNFdH9v.js.map} +1 -1
- package/dist/assets/{profiles-gPCjLOQq.js → profiles-DSxPYdlU.js} +13 -3
- package/dist/assets/profiles-DSxPYdlU.js.map +1 -0
- package/dist/assets/{registry-CxsDR3c6.js → registry-CHPd_hf7.js} +1 -1
- package/dist/assets/{registry-OVx2kY3d.js → registry-qbnWmwCE.js} +3 -3
- package/dist/assets/registry-qbnWmwCE.js.map +1 -0
- package/dist/assets/{resolve-CPTAnu-9.js → resolve-B63_uSu7.js} +2 -2
- package/dist/assets/{resolve-CPTAnu-9.js.map → resolve-B63_uSu7.js.map} +1 -1
- package/dist/assets/{restoredRows-Db3oUTko.js → restoredRows-DeBImGRd.js} +2 -2
- package/dist/assets/{restoredRows-Db3oUTko.js.map → restoredRows-DeBImGRd.js.map} +1 -1
- package/dist/assets/{server-p5DJnN7T.js → server-Cd1dLOB8.js} +1485 -240
- package/dist/assets/server-Cd1dLOB8.js.map +1 -0
- package/dist/assets/{src-Dg1bvMNv.js → src-DzXL-GYu.js} +7277 -5975
- package/dist/assets/src-DzXL-GYu.js.map +1 -0
- package/dist/assets/turnRefs-BCtM10d3.js +357 -0
- package/dist/assets/turnRefs-BCtM10d3.js.map +1 -0
- package/dist/assets/{usdt-BY5OIIvG.js → usdt-B7JWDhQV.js} +3 -3
- package/dist/assets/{usdt-BY5OIIvG.js.map → usdt-B7JWDhQV.js.map} +1 -1
- package/dist/ghostly.mjs +1 -1
- package/package.json +7 -6
- package/skills/ghostly/README.md +41 -0
- package/skills/ghostly/SKILL.md +79 -0
- package/skills/ghostly/scripts/listen +67 -0
- package/skills/ghostly/scripts/react +25 -0
- package/skills/ghostly/scripts/say +44 -0
- package/skills/ghostly/scripts/status +27 -0
- package/dist/assets/api-BCrwJ57L.js.map +0 -1
- package/dist/assets/apps-DOH1P_S0.js.map +0 -1
- package/dist/assets/buttons-CJqNkG-Q.js +0 -2
- package/dist/assets/endpoint-BOTFMpfS.js.map +0 -1
- package/dist/assets/errors-B-zMxbcd.js.map +0 -1
- package/dist/assets/fileBytes-CfSXjTFa.js.map +0 -1
- package/dist/assets/fileBytesIdb-CFP-DSkZ.js +0 -2
- package/dist/assets/groupFiles-uxa0U9_n.js.map +0 -1
- package/dist/assets/host-DPy0eaH9.js.map +0 -1
- package/dist/assets/idb-B5L8wuOm.js.map +0 -1
- package/dist/assets/main-Dt7xrqtb.js.map +0 -1
- package/dist/assets/profiles-gPCjLOQq.js.map +0 -1
- package/dist/assets/registry-OVx2kY3d.js.map +0 -1
- package/dist/assets/server-p5DJnN7T.js.map +0 -1
- package/dist/assets/src-Dg1bvMNv.js.map +0 -1
- package/dist/assets/turnRefs-WnggXm2T.js +0 -323
- package/dist/assets/turnRefs-WnggXm2T.js.map +0 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Zl as fromBase64Url } from "./src-DzXL-GYu.js";
|
|
2
2
|
import { t as hyperdhtRelayProblem } from "./hyperdhtRelay-BT_wRxab.js";
|
|
3
3
|
import { ed25519 } from "@noble/curves/ed25519.js";
|
|
4
4
|
//#region ../browser/src/platform/hyperdhtRelay.ts
|
|
@@ -354,4 +354,4 @@ async function createRelayedHyperEndpoint(seedB64, url, options = {}) {
|
|
|
354
354
|
//#endregion
|
|
355
355
|
export { createRelayedHyperEndpoint };
|
|
356
356
|
|
|
357
|
-
//# sourceMappingURL=hyperdhtRelay-
|
|
357
|
+
//# sourceMappingURL=hyperdhtRelay-13aQ4KOf.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hyperdhtRelay-De5FT7Am.js","names":[],"sources":["../../../browser/src/platform/hyperdhtRelay.ts"],"sourcesContent":["import { ed25519 } from \"@noble/curves/ed25519.js\";\nimport { fromBase64Url, type BoundChannel, type FrameChannel, type NativeEndpoint } from \"@ghostly/core\";\nimport { hyperdhtRelayProblem } from \"../shared/hyperdhtRelay\";\n\nexport { hyperdhtRelayProblem as relayUrlProblem };\n\n/**\n * HyperDHT (WISP 103) for a browser, through a dht-relay: a server that runs the UDP half of HyperDHT and\n * talks to the browser over one WebSocket. It runs non-custodial: the browser keeps its keys, runs the\n * Noise handshake and the encrypted stream itself, and the relay moves handshake messages and ciphertext.\n * The binding (the handshake hash and both keys) is computed here, at the endpoint, as the Desktop does.\n *\n * What the relay sees: this browser's address, the HyperDHT keys it listens on and dials, and when and how\n * much it sends. Not the frames, not the keys' secrets, not the handshake hash.\n *\n * The wire is the Desktop's (native/transports/hyperdht/endpoint.mjs): the same preface, u32 length-prefixed\n * UTF-8 frames of at most 60 KiB, so a browser and a Desktop talk HyperDHT to each other.\n */\n\nconst PREFACE = \"ghostly/paired-chat/1\";\nconst MAX_FRAME = 60 * 1024;\n/** The top bit of a frame's length word: the frame is a binary message, not UTF-8 text (WISP 103). */\nconst BINARY = 0x80000000;\nconst SEND_BUDGET = 120 * 1024;\nconst HANDSHAKE_MS = 20_000;\nconst RELAY_OPEN_MS = 10_000;\nconst LISTEN_MS = 20_000;\nconst MAX_CHANNELS = 2;\n/**\n * How the relay client is always made: never custodial. dht-relay's default is custodial, which sends every\n * secret key to the relay so it can run the handshake and the stream itself. Nothing here takes this from\n * settings or options; test/hyperdhtRelay.test.ts fails if a secret key ever reaches the relay's wire.\n */\nconst NON_CUSTODIAL = Object.freeze({ custodial: false as const });\n\n/**\n * The descriptor a browser's HyperDHT endpoint gives out: its key, that it is reached only through a relay, and\n * which one (the connection panel names its host; a Desktop dialling it ignores both).\n */\nexport interface RelayedHyperDescriptor { publicKey: string; relayed: true; relay: string }\n\n/** The parts of dht-relay and its streams this adapter uses. */\ninterface SecretSocket {\n handshakeHash: Uint8Array | null;\n publicKey: Uint8Array;\n remotePublicKey: Uint8Array;\n opened?: Promise<boolean>;\n destroyed?: boolean;\n write(data: Uint8Array): boolean;\n destroy(error?: Error): void;\n on(event: string, listener: (...args: never[]) => void): unknown;\n once(event: string, listener: (...args: never[]) => void): unknown;\n off(event: string, listener: (...args: never[]) => void): unknown;\n}\ninterface RelayedServer {\n listen(keyPair: KeyPair): Promise<void>;\n close(): Promise<void>;\n}\ninterface RelayedNode {\n ready(): Promise<void>;\n connect(remotePublicKey: Uint8Array, options: { keyPair: KeyPair }): SecretSocket;\n createServer(options: object, onconnection: (socket: SecretSocket) => void): RelayedServer;\n destroy(): Promise<void>;\n}\ninterface KeyPair { publicKey: Uint8Array; secretKey: Uint8Array }\n\nconst hex = (bytes: Uint8Array) => Array.from(bytes, b => b.toString(16).padStart(2, \"0\")).join(\"\");\nconst fromHex = (text: string) => Uint8Array.from(text.match(/../g)!, byte => parseInt(byte, 16));\n\n/** HyperDHT's key pair for a seed: libsodium's layout (the seed, then the public key). */\nexport function hyperKeyPair(seed: Uint8Array): KeyPair {\n if (seed.length !== 32) throw new Error(\"Invalid HyperDHT seed\");\n const publicKey = ed25519.getPublicKey(seed);\n const secretKey = new Uint8Array(64);\n secretKey.set(seed); secretKey.set(publicKey, 32);\n return { publicKey, secretKey };\n}\n\n/**\n * One WebSocket per relay for the whole app, shared by every chat's endpoint (each listens on its own\n * key). It closes when the last endpoint does. When the relay goes away, every endpoint on it says it is\n * unavailable, and the next one to start opens a new connection.\n */\ninterface RelayClient {\n node: RelayedNode;\n endpoints: Set<{ unavailable(): void }>;\n gone: boolean;\n /** Settles when the relay connection closes. */\n closed: Promise<void>;\n}\nconst clients = new Map<string, Promise<RelayClient>>();\n\nasync function relayClient(url: string, createSocket: (url: string) => WebSocket): Promise<RelayClient> {\n const existing = clients.get(url);\n if (existing) {\n const client = await existing.catch(() => null);\n if (client && !client.gone) return client;\n if (clients.get(url) === existing) clients.delete(url);\n }\n const opening = (async () => {\n const [{ default: RelayedDHT }, { default: WebSocketStream }] = await Promise.all([\n // @ts-expect-error dht-relay ships no types: RelayedNode and SecretSocket above describe what this uses.\n import(\"@hyperswarm/dht-relay\"),\n // @ts-expect-error The same package's WebSocket stream, untyped too.\n import(\"@hyperswarm/dht-relay/ws\"),\n ]);\n const socket = createSocket(url);\n socket.binaryType = \"arraybuffer\";\n await new Promise<void>((resolve, reject) => {\n const timer = setTimeout(() => { socket.close(); reject(new Error(\"The HyperDHT relay did not answer\")); }, RELAY_OPEN_MS);\n socket.addEventListener(\"open\", () => { clearTimeout(timer); resolve(); }, { once: true });\n socket.addEventListener(\"error\", () => { clearTimeout(timer); reject(new Error(\"Could not reach the HyperDHT relay\")); }, { once: true });\n socket.addEventListener(\"close\", () => { clearTimeout(timer); reject(new Error(\"The HyperDHT relay closed the connection\")); }, { once: true });\n });\n // Non-custodial: the relay is told public keys only, never a secret key (dht-relay's default sends them).\n // The node's own key is never used to listen or dial: a random one says nothing about this app.\n const node = new RelayedDHT(new WebSocketStream(true, socket), { ...NON_CUSTODIAL, keyPair: hyperKeyPair(crypto.getRandomValues(new Uint8Array(32))) }) as RelayedNode;\n let closed!: () => void;\n const client: RelayClient = { node, endpoints: new Set(), gone: false, closed: new Promise(resolve => { closed = resolve; }) };\n socket.addEventListener(\"close\", () => {\n client.gone = true; closed();\n if (clients.get(url) === opening) clients.delete(url);\n for (const endpoint of [...client.endpoints]) endpoint.unavailable();\n });\n return client;\n })();\n clients.set(url, opening);\n opening.catch(() => { if (clients.get(url) === opening) clients.delete(url); });\n return opening;\n}\n\nfunction release(url: string, client: RelayClient, endpoint: { unavailable(): void }): void {\n client.endpoints.delete(endpoint);\n if (client.endpoints.size || client.gone) return;\n client.gone = true;\n void clients.get(url)?.then(current => { if (current === client) clients.delete(url); }).catch(() => {});\n void client.node.destroy().catch(() => {});\n}\n\n/** One authenticated stream as a frame channel, with a bounded queue before the session installs its reader. */\nclass RelayedChannel implements FrameChannel {\n private reader: FrameChannel[\"onMessage\"] = null;\n private buffered: (string | Uint8Array)[] = [];\n private closed = false;\n private pendingBytes = 0;\n /** It carries binary messages (WISP 100 § Text and binary messages), as the Desktop's own HyperDHT does. */\n readonly binary = true;\n onClose: FrameChannel[\"onClose\"] = null;\n constructor(private readonly socket: SecretSocket) {\n socket.on(\"drain\", () => { this.pendingBytes = 0; });\n }\n get bufferedAmount(): number { return this.pendingBytes; }\n readonly sendBudget = SEND_BUDGET;\n get onMessage() { return this.reader; }\n set onMessage(reader: FrameChannel[\"onMessage\"]) {\n this.reader = reader;\n if (reader) for (const text of this.buffered.splice(0)) reader(text);\n }\n receive(text: string | Uint8Array): boolean {\n if (this.closed) return true;\n if (this.reader) this.reader(text);\n else if (this.buffered.length < 64) this.buffered.push(text);\n else return false;\n return true;\n }\n send(data: string | Uint8Array): void {\n if (this.closed || !data.length) throw new Error(\"Native channel is closed or exceeds its frame budget\");\n const binary = typeof data !== \"string\";\n const payload = binary ? data : new TextEncoder().encode(data);\n if (payload.length > MAX_FRAME || this.pendingBytes + payload.length > SEND_BUDGET) throw new Error(\"Native channel is closed or exceeds its frame budget\");\n // The stream takes every write; past its high-water mark it says so until it drains.\n if (!writeFrame(this.socket, payload, binary) || this.pendingBytes) this.pendingBytes += payload.length + 4;\n }\n drained(): Promise<void> {\n if (!this.pendingBytes) return Promise.resolve();\n return new Promise((resolve, reject) => {\n const done = () => { this.socket.off(\"close\", gone); resolve(); };\n const gone = () => { this.socket.off(\"drain\", done); reject(new Error(\"Native channel closed\")); };\n this.socket.once(\"drain\", done); this.socket.once(\"close\", gone);\n });\n }\n closedByPeer(): void {\n if (this.closed) return;\n this.closed = true; this.buffered = [];\n this.onClose?.();\n }\n close(): void {\n if (this.closed) return;\n this.socket.destroy();\n this.closedByPeer();\n }\n}\n\nfunction writeFrame(socket: SecretSocket, payload: Uint8Array, binary = false): boolean {\n const frame = new Uint8Array(4 + payload.length);\n new DataView(frame.buffer).setUint32(0, (binary ? BINARY : 0) + payload.length);\n frame.set(payload, 4);\n return socket.write(frame);\n}\n\n/**\n * Waits for the peer's preface on an authenticated stream (sending ours as soon as the handshake is\n * done), then hands the channel over with the binding the stream itself gives. A frame of the wrong size,\n * bytes that are not UTF-8 or another protocol close the stream.\n */\nfunction attach(socket: SecretSocket, channels: Set<RelayedChannel>): Promise<BoundChannel> {\n if (channels.size >= MAX_CHANNELS) { socket.destroy(); return Promise.reject(new Error(\"Native connection limit reached\")); }\n const channel = new RelayedChannel(socket);\n channels.add(channel);\n return new Promise<BoundChannel>((resolve, reject) => {\n let buffer = new Uint8Array(0);\n let prefaced = false;\n const decoder = new TextDecoder(\"utf-8\", { fatal: true });\n const timer = setTimeout(() => socket.destroy(new Error(\"HyperDHT handshake timed out\")), HANDSHAKE_MS);\n const fail = (error: Error) => { socket.destroy(error); };\n socket.on(\"error\", (error: Error) => reject(error));\n socket.on(\"close\", () => {\n clearTimeout(timer); channels.delete(channel);\n reject(new Error(\"Native channel closed\"));\n channel.closedByPeer();\n });\n const opened = () => {\n if (socket.handshakeHash?.length !== 64 || socket.publicKey?.length !== 32 || socket.remotePublicKey?.length !== 32) {\n fail(new Error(\"Noise session binding unavailable\")); return;\n }\n writeFrame(socket, new TextEncoder().encode(PREFACE));\n };\n if (socket.opened) void socket.opened.then(ok => { if (ok) opened(); else reject(new Error(\"Noise handshake failed\")); });\n else socket.once(\"open\", opened);\n socket.on(\"data\", (chunk: Uint8Array) => {\n const joined = new Uint8Array(buffer.length + chunk.length);\n joined.set(buffer); joined.set(chunk, buffer.length);\n buffer = joined;\n while (buffer.length >= 4) {\n const word = new DataView(buffer.buffer, buffer.byteOffset, 4).getUint32(0);\n const binary = word >= BINARY, length = word % BINARY;\n // A binary frame before the preface is another protocol.\n if (length === 0 || length > MAX_FRAME || (binary && !prefaced)) { fail(new Error(\"Invalid frame length\")); return; }\n if (buffer.length < 4 + length) break;\n if (binary) {\n const message = buffer.slice(4, 4 + length);\n buffer = buffer.slice(4 + length);\n if (!channel.receive(message)) { fail(new Error(\"Receive queue exceeded\")); return; }\n continue;\n }\n let text: string;\n try { text = decoder.decode(buffer.subarray(4, 4 + length)); }\n catch { fail(new Error(\"Invalid UTF-8 frame\")); return; }\n buffer = buffer.slice(4 + length);\n if (!prefaced) {\n if (text !== PREFACE) { fail(new Error(\"Unexpected application protocol\")); return; }\n prefaced = true; clearTimeout(timer);\n resolve({ channel, binding: { transport: \"hyperdht/1\", context: hex(socket.handshakeHash!),\n identities: [hex(socket.publicKey), hex(socket.remotePublicKey)].sort() as [string, string] } });\n } else if (!channel.receive(text)) { fail(new Error(\"Receive queue exceeded\")); return; }\n }\n });\n });\n}\n\nexport interface RelayedHyperOptions {\n /** How to open the WebSocket (tests pass Node's `ws`). Default: the browser's. */\n createSocket?: (url: string) => WebSocket;\n}\n\n/**\n * A chat's HyperDHT endpoint through the relay at `url`: it listens on the key of `seedB64` (the chat's\n * own transport seed, as on the Desktop) and dials a contact's descriptor. Fails, and so is not offered,\n * when the relay cannot be reached or does not let it listen.\n */\nexport async function createRelayedHyperEndpoint(seedB64: string, url: string, options: RelayedHyperOptions = {}): Promise<NativeEndpoint> {\n const problem = hyperdhtRelayProblem(url);\n if (problem) throw new Error(problem);\n const keyPair = hyperKeyPair(fromBase64Url(seedB64));\n const client = await relayClient(url, options.createSocket ?? (address => new WebSocket(address)));\n const channels = new Set<RelayedChannel>();\n let stopped = false;\n let handler: NativeEndpoint[\"onConnection\"] = null;\n const waiting: BoundChannel[] = [];\n let endpoint: NativeEndpoint | null = null;\n const member = {\n unavailable() {\n if (stopped) return;\n stopped = true;\n for (const channel of channels) channel.close();\n endpoint?.onUnavailable?.();\n },\n };\n client.endpoints.add(member);\n const server = client.node.createServer({}, socket => {\n void attach(socket, channels).then(bound => {\n if (stopped) bound.channel.close();\n else if (handler) handler(bound);\n else if (waiting.length < MAX_CHANNELS) waiting.push(bound);\n else bound.channel.close();\n }).catch(() => {});\n });\n try {\n await Promise.race([\n server.listen(keyPair),\n client.closed.then(() => { throw new Error(\"The HyperDHT relay closed the connection\"); }),\n new Promise((_, reject) => setTimeout(() => reject(new Error(\"The HyperDHT relay did not let this chat listen\")), LISTEN_MS)),\n ]);\n if (client.gone) throw new Error(\"The HyperDHT relay closed the connection\");\n } catch (error) {\n stopped = true;\n void server.close().catch(() => {});\n release(url, client, member);\n throw error;\n }\n const descriptor: RelayedHyperDescriptor = { publicKey: hex(keyPair.publicKey), relayed: true, relay: url };\n endpoint = {\n transport: \"hyperdht/1\", descriptor, onDescriptor: null, onUnavailable: null,\n get onConnection() { return handler; },\n set onConnection(value) { handler = value; if (value) for (const bound of waiting.splice(0)) value(bound); },\n async connect(address) {\n const publicKey = (address as { publicKey?: unknown } | null)?.publicKey;\n if (stopped || typeof publicKey !== \"string\" || !/^[a-f0-9]{64}$/.test(publicKey)) throw new Error(\"Invalid HyperDHT endpoint\");\n return attach(client.node.connect(fromHex(publicKey), { keyPair }), channels);\n },\n async close() {\n stopped = true;\n for (const channel of channels) channel.close();\n waiting.length = 0;\n await server.close().catch(() => {});\n release(url, client, member);\n },\n };\n return endpoint;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"hyperdhtRelay-13aQ4KOf.js","names":[],"sources":["../../../browser/src/platform/hyperdhtRelay.ts"],"sourcesContent":["import { ed25519 } from \"@noble/curves/ed25519.js\";\nimport { fromBase64Url, type BoundChannel, type FrameChannel, type NativeEndpoint } from \"@ghostly/core\";\nimport { hyperdhtRelayProblem } from \"../shared/hyperdhtRelay\";\n\nexport { hyperdhtRelayProblem as relayUrlProblem };\n\n/**\n * HyperDHT (WISP 103) for a browser, through a dht-relay: a server that runs the UDP half of HyperDHT and\n * talks to the browser over one WebSocket. It runs non-custodial: the browser keeps its keys, runs the\n * Noise handshake and the encrypted stream itself, and the relay moves handshake messages and ciphertext.\n * The binding (the handshake hash and both keys) is computed here, at the endpoint, as the Desktop does.\n *\n * What the relay sees: this browser's address, the HyperDHT keys it listens on and dials, and when and how\n * much it sends. Not the frames, not the keys' secrets, not the handshake hash.\n *\n * The wire is the Desktop's (native/transports/hyperdht/endpoint.mjs): the same preface, u32 length-prefixed\n * UTF-8 frames of at most 60 KiB, so a browser and a Desktop talk HyperDHT to each other.\n */\n\nconst PREFACE = \"ghostly/paired-chat/1\";\nconst MAX_FRAME = 60 * 1024;\n/** The top bit of a frame's length word: the frame is a binary message, not UTF-8 text (WISP 103). */\nconst BINARY = 0x80000000;\nconst SEND_BUDGET = 120 * 1024;\nconst HANDSHAKE_MS = 20_000;\nconst RELAY_OPEN_MS = 10_000;\nconst LISTEN_MS = 20_000;\nconst MAX_CHANNELS = 2;\n/**\n * How the relay client is always made: never custodial. dht-relay's default is custodial, which sends every\n * secret key to the relay so it can run the handshake and the stream itself. Nothing here takes this from\n * settings or options; test/hyperdhtRelay.test.ts fails if a secret key ever reaches the relay's wire.\n */\nconst NON_CUSTODIAL = Object.freeze({ custodial: false as const });\n\n/**\n * The descriptor a browser's HyperDHT endpoint gives out: its key, that it is reached only through a relay, and\n * which one (the connection panel names its host; a Desktop dialling it ignores both).\n */\nexport interface RelayedHyperDescriptor { publicKey: string; relayed: true; relay: string }\n\n/** The parts of dht-relay and its streams this adapter uses. */\ninterface SecretSocket {\n handshakeHash: Uint8Array | null;\n publicKey: Uint8Array;\n remotePublicKey: Uint8Array;\n opened?: Promise<boolean>;\n destroyed?: boolean;\n write(data: Uint8Array): boolean;\n destroy(error?: Error): void;\n on(event: string, listener: (...args: never[]) => void): unknown;\n once(event: string, listener: (...args: never[]) => void): unknown;\n off(event: string, listener: (...args: never[]) => void): unknown;\n}\ninterface RelayedServer {\n listen(keyPair: KeyPair): Promise<void>;\n close(): Promise<void>;\n}\ninterface RelayedNode {\n ready(): Promise<void>;\n connect(remotePublicKey: Uint8Array, options: { keyPair: KeyPair }): SecretSocket;\n createServer(options: object, onconnection: (socket: SecretSocket) => void): RelayedServer;\n destroy(): Promise<void>;\n}\ninterface KeyPair { publicKey: Uint8Array; secretKey: Uint8Array }\n\nconst hex = (bytes: Uint8Array) => Array.from(bytes, b => b.toString(16).padStart(2, \"0\")).join(\"\");\nconst fromHex = (text: string) => Uint8Array.from(text.match(/../g)!, byte => parseInt(byte, 16));\n\n/** HyperDHT's key pair for a seed: libsodium's layout (the seed, then the public key). */\nexport function hyperKeyPair(seed: Uint8Array): KeyPair {\n if (seed.length !== 32) throw new Error(\"Invalid HyperDHT seed\");\n const publicKey = ed25519.getPublicKey(seed);\n const secretKey = new Uint8Array(64);\n secretKey.set(seed); secretKey.set(publicKey, 32);\n return { publicKey, secretKey };\n}\n\n/**\n * One WebSocket per relay for the whole app, shared by every chat's endpoint (each listens on its own\n * key). It closes when the last endpoint does. When the relay goes away, every endpoint on it says it is\n * unavailable, and the next one to start opens a new connection.\n */\ninterface RelayClient {\n node: RelayedNode;\n endpoints: Set<{ unavailable(): void }>;\n gone: boolean;\n /** Settles when the relay connection closes. */\n closed: Promise<void>;\n}\nconst clients = new Map<string, Promise<RelayClient>>();\n\nasync function relayClient(url: string, createSocket: (url: string) => WebSocket): Promise<RelayClient> {\n const existing = clients.get(url);\n if (existing) {\n const client = await existing.catch(() => null);\n if (client && !client.gone) return client;\n if (clients.get(url) === existing) clients.delete(url);\n }\n const opening = (async () => {\n const [{ default: RelayedDHT }, { default: WebSocketStream }] = await Promise.all([\n // @ts-expect-error dht-relay ships no types: RelayedNode and SecretSocket above describe what this uses.\n import(\"@hyperswarm/dht-relay\"),\n // @ts-expect-error The same package's WebSocket stream, untyped too.\n import(\"@hyperswarm/dht-relay/ws\"),\n ]);\n const socket = createSocket(url);\n socket.binaryType = \"arraybuffer\";\n await new Promise<void>((resolve, reject) => {\n const timer = setTimeout(() => { socket.close(); reject(new Error(\"The HyperDHT relay did not answer\")); }, RELAY_OPEN_MS);\n socket.addEventListener(\"open\", () => { clearTimeout(timer); resolve(); }, { once: true });\n socket.addEventListener(\"error\", () => { clearTimeout(timer); reject(new Error(\"Could not reach the HyperDHT relay\")); }, { once: true });\n socket.addEventListener(\"close\", () => { clearTimeout(timer); reject(new Error(\"The HyperDHT relay closed the connection\")); }, { once: true });\n });\n // Non-custodial: the relay is told public keys only, never a secret key (dht-relay's default sends them).\n // The node's own key is never used to listen or dial: a random one says nothing about this app.\n const node = new RelayedDHT(new WebSocketStream(true, socket), { ...NON_CUSTODIAL, keyPair: hyperKeyPair(crypto.getRandomValues(new Uint8Array(32))) }) as RelayedNode;\n let closed!: () => void;\n const client: RelayClient = { node, endpoints: new Set(), gone: false, closed: new Promise(resolve => { closed = resolve; }) };\n socket.addEventListener(\"close\", () => {\n client.gone = true; closed();\n if (clients.get(url) === opening) clients.delete(url);\n for (const endpoint of [...client.endpoints]) endpoint.unavailable();\n });\n return client;\n })();\n clients.set(url, opening);\n opening.catch(() => { if (clients.get(url) === opening) clients.delete(url); });\n return opening;\n}\n\nfunction release(url: string, client: RelayClient, endpoint: { unavailable(): void }): void {\n client.endpoints.delete(endpoint);\n if (client.endpoints.size || client.gone) return;\n client.gone = true;\n void clients.get(url)?.then(current => { if (current === client) clients.delete(url); }).catch(() => {});\n void client.node.destroy().catch(() => {});\n}\n\n/** One authenticated stream as a frame channel, with a bounded queue before the session installs its reader. */\nclass RelayedChannel implements FrameChannel {\n private reader: FrameChannel[\"onMessage\"] = null;\n private buffered: (string | Uint8Array)[] = [];\n private closed = false;\n private pendingBytes = 0;\n /** It carries binary messages (WISP 100 § Text and binary messages), as the Desktop's own HyperDHT does. */\n readonly binary = true;\n onClose: FrameChannel[\"onClose\"] = null;\n constructor(private readonly socket: SecretSocket) {\n socket.on(\"drain\", () => { this.pendingBytes = 0; });\n }\n get bufferedAmount(): number { return this.pendingBytes; }\n readonly sendBudget = SEND_BUDGET;\n get onMessage() { return this.reader; }\n set onMessage(reader: FrameChannel[\"onMessage\"]) {\n this.reader = reader;\n if (reader) for (const text of this.buffered.splice(0)) reader(text);\n }\n receive(text: string | Uint8Array): boolean {\n if (this.closed) return true;\n if (this.reader) this.reader(text);\n else if (this.buffered.length < 64) this.buffered.push(text);\n else return false;\n return true;\n }\n send(data: string | Uint8Array): void {\n if (this.closed || !data.length) throw new Error(\"Native channel is closed or exceeds its frame budget\");\n const binary = typeof data !== \"string\";\n const payload = binary ? data : new TextEncoder().encode(data);\n if (payload.length > MAX_FRAME || this.pendingBytes + payload.length > SEND_BUDGET) throw new Error(\"Native channel is closed or exceeds its frame budget\");\n // The stream takes every write; past its high-water mark it says so until it drains.\n if (!writeFrame(this.socket, payload, binary) || this.pendingBytes) this.pendingBytes += payload.length + 4;\n }\n drained(): Promise<void> {\n if (!this.pendingBytes) return Promise.resolve();\n return new Promise((resolve, reject) => {\n const done = () => { this.socket.off(\"close\", gone); resolve(); };\n const gone = () => { this.socket.off(\"drain\", done); reject(new Error(\"Native channel closed\")); };\n this.socket.once(\"drain\", done); this.socket.once(\"close\", gone);\n });\n }\n closedByPeer(): void {\n if (this.closed) return;\n this.closed = true; this.buffered = [];\n this.onClose?.();\n }\n close(): void {\n if (this.closed) return;\n this.socket.destroy();\n this.closedByPeer();\n }\n}\n\nfunction writeFrame(socket: SecretSocket, payload: Uint8Array, binary = false): boolean {\n const frame = new Uint8Array(4 + payload.length);\n new DataView(frame.buffer).setUint32(0, (binary ? BINARY : 0) + payload.length);\n frame.set(payload, 4);\n return socket.write(frame);\n}\n\n/**\n * Waits for the peer's preface on an authenticated stream (sending ours as soon as the handshake is\n * done), then hands the channel over with the binding the stream itself gives. A frame of the wrong size,\n * bytes that are not UTF-8 or another protocol close the stream.\n */\nfunction attach(socket: SecretSocket, channels: Set<RelayedChannel>): Promise<BoundChannel> {\n if (channels.size >= MAX_CHANNELS) { socket.destroy(); return Promise.reject(new Error(\"Native connection limit reached\")); }\n const channel = new RelayedChannel(socket);\n channels.add(channel);\n return new Promise<BoundChannel>((resolve, reject) => {\n let buffer = new Uint8Array(0);\n let prefaced = false;\n const decoder = new TextDecoder(\"utf-8\", { fatal: true });\n const timer = setTimeout(() => socket.destroy(new Error(\"HyperDHT handshake timed out\")), HANDSHAKE_MS);\n const fail = (error: Error) => { socket.destroy(error); };\n socket.on(\"error\", (error: Error) => reject(error));\n socket.on(\"close\", () => {\n clearTimeout(timer); channels.delete(channel);\n reject(new Error(\"Native channel closed\"));\n channel.closedByPeer();\n });\n const opened = () => {\n if (socket.handshakeHash?.length !== 64 || socket.publicKey?.length !== 32 || socket.remotePublicKey?.length !== 32) {\n fail(new Error(\"Noise session binding unavailable\")); return;\n }\n writeFrame(socket, new TextEncoder().encode(PREFACE));\n };\n if (socket.opened) void socket.opened.then(ok => { if (ok) opened(); else reject(new Error(\"Noise handshake failed\")); });\n else socket.once(\"open\", opened);\n socket.on(\"data\", (chunk: Uint8Array) => {\n const joined = new Uint8Array(buffer.length + chunk.length);\n joined.set(buffer); joined.set(chunk, buffer.length);\n buffer = joined;\n while (buffer.length >= 4) {\n const word = new DataView(buffer.buffer, buffer.byteOffset, 4).getUint32(0);\n const binary = word >= BINARY, length = word % BINARY;\n // A binary frame before the preface is another protocol.\n if (length === 0 || length > MAX_FRAME || (binary && !prefaced)) { fail(new Error(\"Invalid frame length\")); return; }\n if (buffer.length < 4 + length) break;\n if (binary) {\n const message = buffer.slice(4, 4 + length);\n buffer = buffer.slice(4 + length);\n if (!channel.receive(message)) { fail(new Error(\"Receive queue exceeded\")); return; }\n continue;\n }\n let text: string;\n try { text = decoder.decode(buffer.subarray(4, 4 + length)); }\n catch { fail(new Error(\"Invalid UTF-8 frame\")); return; }\n buffer = buffer.slice(4 + length);\n if (!prefaced) {\n if (text !== PREFACE) { fail(new Error(\"Unexpected application protocol\")); return; }\n prefaced = true; clearTimeout(timer);\n resolve({ channel, binding: { transport: \"hyperdht/1\", context: hex(socket.handshakeHash!),\n identities: [hex(socket.publicKey), hex(socket.remotePublicKey)].sort() as [string, string] } });\n } else if (!channel.receive(text)) { fail(new Error(\"Receive queue exceeded\")); return; }\n }\n });\n });\n}\n\nexport interface RelayedHyperOptions {\n /** How to open the WebSocket (tests pass Node's `ws`). Default: the browser's. */\n createSocket?: (url: string) => WebSocket;\n}\n\n/**\n * A chat's HyperDHT endpoint through the relay at `url`: it listens on the key of `seedB64` (the chat's\n * own transport seed, as on the Desktop) and dials a contact's descriptor. Fails, and so is not offered,\n * when the relay cannot be reached or does not let it listen.\n */\nexport async function createRelayedHyperEndpoint(seedB64: string, url: string, options: RelayedHyperOptions = {}): Promise<NativeEndpoint> {\n const problem = hyperdhtRelayProblem(url);\n if (problem) throw new Error(problem);\n const keyPair = hyperKeyPair(fromBase64Url(seedB64));\n const client = await relayClient(url, options.createSocket ?? (address => new WebSocket(address)));\n const channels = new Set<RelayedChannel>();\n let stopped = false;\n let handler: NativeEndpoint[\"onConnection\"] = null;\n const waiting: BoundChannel[] = [];\n let endpoint: NativeEndpoint | null = null;\n const member = {\n unavailable() {\n if (stopped) return;\n stopped = true;\n for (const channel of channels) channel.close();\n endpoint?.onUnavailable?.();\n },\n };\n client.endpoints.add(member);\n const server = client.node.createServer({}, socket => {\n void attach(socket, channels).then(bound => {\n if (stopped) bound.channel.close();\n else if (handler) handler(bound);\n else if (waiting.length < MAX_CHANNELS) waiting.push(bound);\n else bound.channel.close();\n }).catch(() => {});\n });\n try {\n await Promise.race([\n server.listen(keyPair),\n client.closed.then(() => { throw new Error(\"The HyperDHT relay closed the connection\"); }),\n new Promise((_, reject) => setTimeout(() => reject(new Error(\"The HyperDHT relay did not let this chat listen\")), LISTEN_MS)),\n ]);\n if (client.gone) throw new Error(\"The HyperDHT relay closed the connection\");\n } catch (error) {\n stopped = true;\n void server.close().catch(() => {});\n release(url, client, member);\n throw error;\n }\n const descriptor: RelayedHyperDescriptor = { publicKey: hex(keyPair.publicKey), relayed: true, relay: url };\n endpoint = {\n transport: \"hyperdht/1\", descriptor, onDescriptor: null, onUnavailable: null,\n get onConnection() { return handler; },\n set onConnection(value) { handler = value; if (value) for (const bound of waiting.splice(0)) value(bound); },\n async connect(address) {\n const publicKey = (address as { publicKey?: unknown } | null)?.publicKey;\n if (stopped || typeof publicKey !== \"string\" || !/^[a-f0-9]{64}$/.test(publicKey)) throw new Error(\"Invalid HyperDHT endpoint\");\n return attach(client.node.connect(fromHex(publicKey), { keyPair }), channels);\n },\n async close() {\n stopped = true;\n for (const channel of channels) channel.close();\n waiting.length = 0;\n await server.close().catch(() => {});\n release(url, client, member);\n },\n };\n return endpoint;\n}\n"],"mappings":"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"}
|
|
@@ -39,6 +39,18 @@ function profileOpenFailure(error) {
|
|
|
39
39
|
...detail ? { detail } : {}
|
|
40
40
|
};
|
|
41
41
|
}
|
|
42
|
+
/**
|
|
43
|
+
* A failed call as the client hears it (engine/server.ts): the error's message. Only the message travels, so a storage
|
|
44
|
+
* write the browser refused for want of space (Chromium's QuotaExceededError has an empty message) says so, its name
|
|
45
|
+
* kept for the ⓘ.
|
|
46
|
+
*/
|
|
47
|
+
function errorLine(error) {
|
|
48
|
+
if (error?.name === "QuotaExceededError") {
|
|
49
|
+
const message = error instanceof Error ? error.message.trim() : "";
|
|
50
|
+
return `Not enough space on this device (QuotaExceededError${message ? `: ${message}` : ""})`;
|
|
51
|
+
}
|
|
52
|
+
return error instanceof Error ? error.message : String(error);
|
|
53
|
+
}
|
|
42
54
|
/** How long an open that another window blocks may wait for that window to let go before it is said to be blocked. */
|
|
43
55
|
var OPEN_TIMINGS = { blockedMs: 1e4 };
|
|
44
56
|
function failureOf(error) {
|
|
@@ -216,9 +228,10 @@ var PROFILE_STORES = [
|
|
|
216
228
|
/**
|
|
217
229
|
* Records of the settings store that belong to the profile, not to a wallet: its DID and its key, its identity proofs
|
|
218
230
|
* with their keys and revocations, its own Nostr data, the public profiles it read, the built-in mini-apps it removed
|
|
219
|
-
* (`BUILT_IN_APPS_KEY` of `engine/apps.ts`)
|
|
220
|
-
*
|
|
221
|
-
*
|
|
231
|
+
* (`BUILT_IN_APPS_KEY` of `engine/apps.ts`), its x401 credentials and their keys (`engine/x401.ts`), its contact links
|
|
232
|
+
* (`engine/contactLinks.ts`) and the requests it sent through others' (`engine/contactAsks.ts`). A list of what goes
|
|
233
|
+
* rather than of what stays, on purpose: every other record of that store is a wallet's (seeds, configuration, pending
|
|
234
|
+
* operations), and a wallet record a newer version adds must never be deleted by mistake.
|
|
222
235
|
*/
|
|
223
236
|
var PROFILE_SETTINGS_KEYS = [
|
|
224
237
|
"profileDid",
|
|
@@ -226,7 +239,10 @@ var PROFILE_SETTINGS_KEYS = [
|
|
|
226
239
|
"identityRevocations",
|
|
227
240
|
"nostrSocial",
|
|
228
241
|
"publicProfiles",
|
|
229
|
-
"appBuiltIns"
|
|
242
|
+
"appBuiltIns",
|
|
243
|
+
"x401Credentials",
|
|
244
|
+
"contactLinks",
|
|
245
|
+
"contactAsks"
|
|
230
246
|
];
|
|
231
247
|
/**
|
|
232
248
|
* The fields of the peer's `settings` record that are a wallet's, kept by "Clear all data": the mint list, the first-run
|
|
@@ -414,6 +430,43 @@ var fileStore = {
|
|
|
414
430
|
});
|
|
415
431
|
return ids;
|
|
416
432
|
},
|
|
433
|
+
/** The id of every stored file whose bytes are a Blob in its record. */
|
|
434
|
+
async blobIds() {
|
|
435
|
+
const files = await store(STORES.files, "readonly"), ids = [];
|
|
436
|
+
await new Promise((resolve, reject) => {
|
|
437
|
+
const request = files.openCursor();
|
|
438
|
+
request.onerror = () => reject(request.error);
|
|
439
|
+
request.onsuccess = () => {
|
|
440
|
+
const cursor = request.result;
|
|
441
|
+
if (!cursor) return resolve();
|
|
442
|
+
if (cursor.value.blob) ids.push(String(cursor.key));
|
|
443
|
+
cursor.continue();
|
|
444
|
+
};
|
|
445
|
+
});
|
|
446
|
+
return ids;
|
|
447
|
+
},
|
|
448
|
+
/**
|
|
449
|
+
* A file whose Blob was copied to file storage (`kind`, under its id): its record without the Blob from now on. Written
|
|
450
|
+
* back without the Blob it read, so not what WebKit loses (see above); what changed about it since (`fileState`) stays.
|
|
451
|
+
* False when the record is gone or holds no Blob any more: the copy is the caller's to remove.
|
|
452
|
+
*/
|
|
453
|
+
async movedToBytes(id, kind) {
|
|
454
|
+
let moved = false;
|
|
455
|
+
await transact([STORES.files], (stores) => {
|
|
456
|
+
const read = stores[STORES.files].get(id);
|
|
457
|
+
read.onsuccess = () => {
|
|
458
|
+
const record = read.result;
|
|
459
|
+
if (!record?.blob) return;
|
|
460
|
+
const { blob: _blob, ...rest } = record;
|
|
461
|
+
stores[STORES.files].put({
|
|
462
|
+
...rest,
|
|
463
|
+
bytes: kind
|
|
464
|
+
});
|
|
465
|
+
moved = true;
|
|
466
|
+
};
|
|
467
|
+
});
|
|
468
|
+
return moved;
|
|
469
|
+
},
|
|
417
470
|
async delete(id) {
|
|
418
471
|
await transact([STORES.files, STORES.fileState], (stores) => {
|
|
419
472
|
stores[STORES.files].delete(id);
|
|
@@ -428,6 +481,6 @@ var fileStore = {
|
|
|
428
481
|
}
|
|
429
482
|
};
|
|
430
483
|
//#endregion
|
|
431
|
-
export { clearProfileStores as a,
|
|
484
|
+
export { clearProfileStores as a, errorLine as c, openDb as d, profileOpenFailure as f, wrap as h, STORES as i, fileStore as l, transact as m, APP_STORAGE_SCOPE_INDEX as n, databaseName as o, store as p, CARD_INDEX as r, deleteDatabase as s, APP_STORAGE_CHAT_INDEX as t, newestWalletTx as u };
|
|
432
485
|
|
|
433
|
-
//# sourceMappingURL=idb-
|
|
486
|
+
//# sourceMappingURL=idb-CMVgtn2k.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"idb-CMVgtn2k.js","names":[],"sources":["../../../browser/src/shared/idb.ts"],"sourcesContent":["/**\n * The extension's one IndexedDB database. The peer (offscreen document) and\n * the pages share an origin, so both open it: that is also how file contents\n * move between them without squeezing through runtime messages.\n */\nlet dbName = \"ghostly\";\n\n/** Before first use: keeps profiles that share a browser storage area apart (Desktop's GHOSTLY_PROFILE). */\nexport function setDatabaseName(name: string): void {\n dbName = name;\n}\n/** The peer database this page uses: its profile's (WISP 04). */\nexport function databaseName(): string {\n return dbName;\n}\n/** The schema this build reads and writes. A database stored at a higher one is a newer build's: it is never opened. */\nexport const DB_VERSION = 15;\n\n/**\n * Why the profile's database did not open. `newer`: a newer Ghostly stored it (IndexedDB never opens a database below\n * its stored version). `blocked`: another window still holds it open at an older version. `full`: no storage space.\n * `denied`: the browser gives this page no storage (a private window, storage blocked for the site). `failed`:\n * anything else, a damaged database included (browsers do not tell that one apart).\n */\nexport type ProfileOpenReason = \"newer\" | \"blocked\" | \"full\" | \"denied\" | \"failed\";\n\n/** What the pages are told (a plain object: it crosses the extension's port), and what a bot reads in `details`. */\nexport interface ProfileOpenFailure {\n reason: ProfileOpenReason;\n /** The schema version this build reads (`DB_VERSION`). */\n supportedVersion: number;\n /** The version the database is stored at, when it could be read (`newer`). */\n storedVersion?: number;\n /** What the browser said, in its own words. */\n detail?: string;\n}\n\n/** The profile's database did not open. Nothing was changed, and nothing is ever deleted or reset because of it. */\nexport class ProfileOpenError extends Error {\n constructor(readonly failure: ProfileOpenFailure) {\n super(profileOpenMessage(failure));\n this.name = \"ProfileOpenError\";\n }\n}\n\n/** The failure in plain English, for logs, the CLI and anything that shows an engine error as it came. */\nexport function profileOpenMessage(failure: ProfileOpenFailure): string {\n const could = \"Ghostly could not open this profile's data\";\n switch (failure.reason) {\n case \"newer\":\n return `This profile was last used by a newer version of Ghostly (its data is version ${failure.storedVersion ?? \"above \" + failure.supportedVersion}, this version reads up to ${failure.supportedVersion}). Update Ghostly to open it.`;\n case \"blocked\": return `${could}: another Ghostly window or tab still has it open with an older version. Close it and try again.`;\n case \"full\": return `${could}: this device is out of storage space. Free some space and try again.`;\n case \"denied\": return `${could}: storage is not allowed here (a private window, or storage blocked for this site).`;\n default: return `${could}${failure.detail ? `: ${failure.detail}` : \"\"}`;\n }\n}\n\n/** What the start of the engine failed with, as the failure the pages show: a database that did not open, or anything else. */\nexport function profileOpenFailure(error: unknown): ProfileOpenFailure {\n if (error instanceof ProfileOpenError) return error.failure;\n const detail = error instanceof Error ? error.message : String(error);\n return { reason: \"failed\", supportedVersion: DB_VERSION, ...(detail ? { detail } : {}) };\n}\n\n/**\n * A failed call as the client hears it (engine/server.ts): the error's message. Only the message travels, so a storage\n * write the browser refused for want of space (Chromium's QuotaExceededError has an empty message) says so, its name\n * kept for the ⓘ.\n */\nexport function errorLine(error: unknown): string {\n if ((error as { name?: unknown } | null)?.name === \"QuotaExceededError\") {\n const message = error instanceof Error ? error.message.trim() : \"\";\n return `Not enough space on this device (QuotaExceededError${message ? `: ${message}` : \"\"})`;\n }\n return error instanceof Error ? error.message : String(error);\n}\n\n/** How long an open that another window blocks may wait for that window to let go before it is said to be blocked. */\nexport const OPEN_TIMINGS = { blockedMs: 10_000 };\n\nfunction failureOf(error: unknown): ProfileOpenFailure {\n const name = (error as { name?: unknown } | null)?.name;\n const message = (error as { message?: unknown } | null)?.message;\n const detail = [typeof name === \"string\" ? name : \"\", typeof message === \"string\" ? message : \"\"].filter(Boolean).join(\": \");\n const reason: ProfileOpenReason = name === \"VersionError\" ? \"newer\"\n : name === \"QuotaExceededError\" ? \"full\"\n // Firefox's private windows of old, storage blocked for the site, or no IndexedDB at all.\n : name === \"BlockedError\" ? \"blocked\"\n : name === \"SecurityError\" || name === \"InvalidStateError\" || name === \"ReferenceError\" || name === \"TypeError\" ? \"denied\"\n : \"failed\";\n return { reason, supportedVersion: DB_VERSION, ...(detail ? { detail } : {}) };\n}\n\n/**\n * The version a database is stored at, read without changing it: an open that names no version opens what is there.\n * Only for a database known to exist (an open without a version would make a missing one).\n */\nfunction storedVersion(name: string): Promise<number | undefined> {\n return new Promise((resolve) => {\n try {\n const request = indexedDB.open(name);\n // It did not exist after all: leave nothing behind.\n request.onupgradeneeded = () => { try { request.transaction?.abort(); } catch { /* already over */ } };\n request.onsuccess = () => { const { version } = request.result; request.result.close(); resolve(version); };\n request.onerror = request.onblocked = () => resolve(undefined);\n } catch { resolve(undefined); }\n });\n}\n/** The messages store's index of card messages (`card.kind`): what `db.getCardMessages` reads. */\nexport const CARD_INDEX = \"byCardKind\";\n/** The wallet history's index by (mint, time): what `newestWalletTx` reads. */\nexport const WALLET_TX_INDEX = \"byMintTime\";\n\nexport const STORES = {\n links: \"links\",\n messages: \"messages\",\n services: \"services\",\n settings: \"settings\",\n files: \"files\",\n proofs: \"proofs\",\n payments: \"payments\",\n quotes: \"quotes\",\n walletTx: \"walletTx\",\n melts: \"melts\",\n /** Swaps at a mint that have not finished (`PendingSwap`): what gets their outputs back when an answer is missed. */\n swaps: \"swaps\",\n intents: \"paymentIntents\",\n /** Private groups (WISP 900): membership chain, epoch secrets and my own recent messages, by group id. */\n groups: \"groups\",\n /** Files kept in 1 MiB pieces where the platform has no file system for them (see `fileBytesIdb.ts`). */\n fileChunks: \"fileChunks\",\n /**\n * What changes about a stored file after it is stored, by file id. A `files` record is written once and never\n * again: WebKit loses the Blob of a record written back with the Blob it read (see `fileStore`).\n */\n fileState: \"fileState\",\n /** Installed mini-apps (WISP 1200 § Updates and rollback), by app reference: `InstalledApp` (`engine/apps.ts`). */\n apps: \"apps\",\n /** The app stores the person added, by store key: `AddedAppStore`. A store is its key; its URL is kept on the record. */\n appStores: \"appStores\",\n /** Each installed app's own storage, by (app reference, scope, key): `AppStorageRow`. Scope: a chat's link id, or `alone`. */\n appStorage: \"appStorage\",\n} as const;\n\n/** The app storage store's index by (app reference, scope): one scope's rows, for its 5 MiB cap and its keys. */\nexport const APP_STORAGE_SCOPE_INDEX = \"byAppScope\";\n/** The app storage store's index by scope: every app's rows of one chat, which go with the chat. */\nexport const APP_STORAGE_CHAT_INDEX = \"byScope\";\n\n/**\n * A file's contents. Metadata travels with the chat message; this is only the bytes: in `blob` for a file\n * small enough to hold whole, or in the platform's file storage (`bytes`, see `fileBytes.ts`) under the same id.\n * Ids are `<link id>-out-<random>` for files we send and `<link id>-in-<random>` for\n * files we receive, both chosen here. Files stored before that are `<link id>-<wire id>`\n * without `direction` and are still read under their old id.\n */\nexport interface StoredFile {\n id: string;\n linkId: string;\n blob?: Blob;\n /** Where the bytes are when there is no `blob`. */\n bytes?: import(\"./fileBytes\").FileBytesKind;\n createdAt: number;\n direction?: \"in\" | \"out\";\n /** The id the file had on the data link. */\n wireId?: string;\n digest?: string;\n metadata?: { name: string; size: number; mime: string; timestamp: number; voice?: import(\"@ghostly/core\").VoiceMeta; video?: import(\"@ghostly/core\").VideoMeta; image?: import(\"@ghostly/core\").ImageMeta };\n transfer?: { state: \"transferring\" | \"done\" | \"failed\"; transferred: number; size: number; error?: string };\n /** files/3: the transfer's own record (`@ghostly/core` `FileTransferRecord`), kept so it resumes after a restart. */\n wire3?: import(\"@ghostly/core\").FileTransferRecord;\n /** Its bytes were left out of the light backup this profile was restored from (WISP 05 § Light backups). */\n leftOut?: boolean;\n /**\n * A file the contact said on the DHT floor (WISP 403 § Files), by its wire id: its bubble waits for its offer on the\n * live session, and no byte is here yet. Gone once the offer came (`wireId` and `wire3` then).\n */\n announced?: string;\n /**\n * A file of a group (WISP 503), its `linkId` `group:<id>`: the message that announced it and its author, whether this\n * device fetched it without asking (it counts toward the group's automatic limit), and whether its person asked for it.\n * `digest` is the author's, which the bytes are checked against.\n */\n group?: { message: string; author: string; auto?: boolean; asked?: boolean; paused?: boolean };\n}\n\n/** The fields of a stored file that change after it is stored: kept in `STORES.fileState`, read over the record's own. */\ntype FileStateFields = Partial<Pick<StoredFile, \"bytes\" | \"digest\" | \"transfer\" | \"wire3\" | \"group\">>;\ntype FileState = { id: string } & FileStateFields;\n\nlet dbPromise: Promise<IDBDatabase> | null = null;\n\nexport function openDb(): Promise<IDBDatabase> {\n dbPromise ??= new Promise((resolve, reject) => {\n let settled = false;\n let blocked: ReturnType<typeof setTimeout> | undefined;\n // Said once, as what it is (`ProfileOpenError`): the engine does not start, and the pages show why.\n const fail = (error: unknown) => {\n if (settled) return;\n settled = true;\n clearTimeout(blocked);\n const failure = failureOf(error);\n if (failure.reason !== \"newer\") { reject(new ProfileOpenError(failure)); return; }\n void storedVersion(dbName).then((version) => reject(new ProfileOpenError({ ...failure, ...(version !== undefined ? { storedVersion: version } : {}) })));\n };\n let request: IDBOpenDBRequest;\n try {\n request = indexedDB.open(dbName, DB_VERSION);\n } catch (error) {\n fail(error);\n return;\n }\n request.onupgradeneeded = () => {\n const db = request.result;\n const has = (name: string) => db.objectStoreNames.contains(name);\n if (!has(STORES.links)) db.createObjectStore(STORES.links, { keyPath: \"id\" });\n if (!has(STORES.services)) db.createObjectStore(STORES.services, { keyPath: \"id\" });\n if (!has(STORES.settings)) db.createObjectStore(STORES.settings);\n if (!has(STORES.messages)) {\n db.createObjectStore(STORES.messages, { keyPath: [\"linkId\", \"id\"] }).createIndex(\"byLink\", \"linkId\");\n }\n if (!has(STORES.proofs)) db.createObjectStore(STORES.proofs, { keyPath: \"secret\" });\n if (!has(STORES.payments)) db.createObjectStore(STORES.payments, { keyPath: \"id\" });\n if (!has(STORES.quotes)) db.createObjectStore(STORES.quotes, { keyPath: \"quote\" });\n if (!has(STORES.walletTx)) db.createObjectStore(STORES.walletTx, { keyPath: \"id\" });\n // v5: Lightning payments the mint has not settled yet, and the proofs they hold.\n if (!has(STORES.melts)) db.createObjectStore(STORES.melts, { keyPath: \"quote\" });\n if (!has(STORES.intents)) db.createObjectStore(STORES.intents, { keyPath: \"review.id\" });\n if (!has(STORES.files)) {\n db.createObjectStore(STORES.files, { keyPath: \"id\" }).createIndex(\"byLink\", \"linkId\");\n }\n // v7: private groups.\n if (!has(STORES.groups)) db.createObjectStore(STORES.groups, { keyPath: \"id\" });\n // v8: files in pieces, for platforms without the origin-private file system.\n if (!has(STORES.fileChunks)) db.createObjectStore(STORES.fileChunks, { keyPath: [\"id\", \"index\"] });\n // v9: what changes about a stored file, apart from its record (see `fileStore`).\n if (!has(STORES.fileState)) db.createObjectStore(STORES.fileState, { keyPath: \"id\" });\n // v10: a chat's messages in time order, so its latest page is read without the rest.\n const messages = request.transaction!.objectStore(STORES.messages);\n if (!messages.indexNames.contains(\"byLinkTime\")) messages.createIndex(\"byLinkTime\", [\"linkId\", \"timestamp\"]);\n // v11: the messages that carry a status card, by the card's kind, across every chat and group (the Tasks board).\n // A message without a card has no such key and is not in it, so the index is as small as the cards are few.\n if (!messages.indexNames.contains(CARD_INDEX)) messages.createIndex(CARD_INDEX, \"card.kind\");\n // v12: swaps at a mint, written down before the mint is asked.\n if (!has(STORES.swaps)) db.createObjectStore(STORES.swaps, { keyPath: \"id\" });\n // v13: no change of the schema. A build from here can hold a frozen copy of a profile on a standby device (WISP 06\n // § Compatibility and rollout): the step keeps an older build (1.0.3 reads 12) from opening and starting one.\n // v14: mini-apps (WISP 1200): the installed apps, the stores added and each app's storage per scope. Empty until the\n // apps feature is on; the bundles themselves are in file storage under `app-<digest>`, outside every store here.\n if (!has(STORES.apps)) db.createObjectStore(STORES.apps, { keyPath: \"ref\" });\n if (!has(STORES.appStores)) db.createObjectStore(STORES.appStores, { keyPath: \"key\" });\n if (!has(STORES.appStorage)) {\n const storage = db.createObjectStore(STORES.appStorage, { keyPath: [\"ref\", \"scope\", \"key\"] });\n storage.createIndex(APP_STORAGE_SCOPE_INDEX, [\"ref\", \"scope\"]);\n storage.createIndex(APP_STORAGE_CHAT_INDEX, \"scope\");\n }\n // v15: the wallet history by mint and time, so a refresh reads each network's newest movements without the rest.\n const walletTx = request.transaction!.objectStore(STORES.walletTx);\n if (!walletTx.indexNames.contains(WALLET_TX_INDEX)) walletTx.createIndex(WALLET_TX_INDEX, [\"mint\", \"timestamp\"]);\n };\n request.onsuccess = () => {\n // Opened after it was said to be blocked: nobody uses this connection, and it must not block the next open.\n if (settled) { request.result.close(); return; }\n settled = true;\n clearTimeout(blocked);\n // Let the other context upgrade the schema instead of blocking it.\n request.result.onversionchange = () => {\n request.result.close();\n dbPromise = null;\n };\n resolve(request.result);\n };\n // The event's default aborts nothing here, but an unhandled one is logged as an uncaught error in some browsers.\n request.onerror = (event) => { event.preventDefault?.(); fail(request.error); };\n // Another window holds the database at an older version and has not let go (ours do, on `versionchange`). The\n // open stays pending for as long as that lasts: before, the engine waited with it, silently and for good.\n request.onblocked = () => {\n blocked ??= setTimeout(() => fail({ name: \"BlockedError\", message: \"another connection holds an older version open\" }), OPEN_TIMINGS.blockedMs);\n };\n });\n return dbPromise;\n}\n\n/** The stores \"Clear all data\" empties: chats and their keys, messages, files, shared services, groups, mini-apps and their data. */\nexport const PROFILE_STORES = [STORES.links, STORES.messages, STORES.files, STORES.fileState, STORES.fileChunks, STORES.services, STORES.groups, STORES.apps, STORES.appStores, STORES.appStorage] as const;\n\n/**\n * Records of the settings store that belong to the profile, not to a wallet: its DID and its key, its identity proofs\n * with their keys and revocations, its own Nostr data, the public profiles it read, the built-in mini-apps it removed\n * (`BUILT_IN_APPS_KEY` of `engine/apps.ts`), its x401 credentials and their keys (`engine/x401.ts`), its contact links\n * (`engine/contactLinks.ts`) and the requests it sent through others' (`engine/contactAsks.ts`). A list of what goes\n * rather than of what stays, on purpose: every other record of that store is a wallet's (seeds, configuration, pending\n * operations), and a wallet record a newer version adds must never be deleted by mistake.\n */\nexport const PROFILE_SETTINGS_KEYS = [\"profileDid\", \"identityProofs\", \"identityRevocations\", \"nostrSocial\", \"publicProfiles\", \"appBuiltIns\", \"x401Credentials\", \"contactLinks\", \"contactAsks\"] as const;\n\n/**\n * The fields of the peer's `settings` record that are a wallet's, kept by \"Clear all data\": the mint list, the first-run\n * wallet setup and the backup reminders. Everything else in it is the profile's (name, picture, network settings,\n * storage credentials for held items, the push subscription and who holds it) and goes.\n */\nexport const WALLET_SETTINGS_FIELDS = [\"mints\", \"mintsInitialized\", \"walletSetup\", \"backupReminders\"] as const;\n\n/**\n * \"Clear all data\" (WISP 04): everything of the profile in its peer database except its wallets. The chats' stores are\n * emptied, the profile's records of the settings store are deleted and the `settings` record keeps only its wallet\n * fields. The wallets (proofs, payments, quotes, history, payment journal, wallet records) stay: ecash is money, and\n * nothing else holds a copy of it. The caller stops the peer first, or it may write back what was just deleted.\n */\nexport async function clearProfileStores(): Promise<void> {\n if (typeof indexedDB === \"undefined\") return;\n const db = await openDb();\n const names = [...PROFILE_STORES, STORES.settings].filter((name) => db.objectStoreNames.contains(name));\n const tx = db.transaction(names, \"readwrite\");\n for (const name of names) if (name !== STORES.settings) tx.objectStore(name).clear();\n if (names.includes(STORES.settings)) {\n const settings = tx.objectStore(STORES.settings);\n for (const key of PROFILE_SETTINGS_KEYS) settings.delete(key);\n const read = settings.get(\"settings\");\n read.onsuccess = () => {\n const record = read.result as Record<string, unknown> | undefined;\n if (!record || typeof record !== \"object\") return;\n const kept = Object.fromEntries(WALLET_SETTINGS_FIELDS.filter((field) => field in record).map((field) => [field, record[field]]));\n settings.put(kept, \"settings\");\n };\n }\n await new Promise<void>((resolve, reject) => {\n tx.oncomplete = () => resolve();\n tx.onerror = tx.onabort = () => reject(tx.error);\n });\n}\n\n/** Deletes a database of the origin by name (an SDK's own), as far as the browser lets it: blocked or failed, it gives up quietly. */\nexport function deleteDatabase(name: string): Promise<void> {\n return new Promise<void>((resolve) => {\n try { const request = indexedDB.deleteDatabase(name); request.onsuccess = request.onerror = request.onblocked = () => resolve(); } catch { resolve(); }\n });\n}\n\nexport function wrap<T>(request: IDBRequest<T>): Promise<T> {\n return new Promise((resolve, reject) => {\n request.onsuccess = () => resolve(request.result);\n request.onerror = () => reject(request.error);\n });\n}\n\nexport async function store(name: string, mode: IDBTransactionMode): Promise<IDBObjectStore> {\n return (await openDb()).transaction(name, mode).objectStore(name);\n}\n\n/**\n * Several writes across stores in one transaction: they all land, or none does.\n * `work` only queues requests; the promise settles when the transaction does.\n */\nexport async function transact(names: string[], work: (stores: Record<string, IDBObjectStore>) => void): Promise<void> {\n const tx = (await openDb()).transaction(names, \"readwrite\");\n const done = new Promise<void>((resolve, reject) => {\n tx.oncomplete = () => resolve();\n tx.onabort = () => reject(tx.error ?? new Error(\"The wallet could not save\"));\n tx.onerror = () => reject(tx.error ?? new Error(\"The wallet could not save\"));\n });\n try {\n work(Object.fromEntries(names.map((name) => [name, tx.objectStore(name)])));\n } catch (error) {\n tx.abort();\n await done.catch(() => {});\n throw error;\n }\n return done;\n}\n\n/**\n * The `count` newest wallet movements of the mints `wanted` picks, newest first. Read backwards on the history index, a\n * mint at a time: a mint not wanted, or one whose `count` newest are read, is stepped over at once. What it reads grows\n * with the number of mints, never with the history's length.\n */\nexport async function newestWalletTx<T extends { timestamp: number }>(wanted: (mint: string) => boolean, count: number): Promise<T[]> {\n const index = (await store(STORES.walletTx, \"readonly\")).index(WALLET_TX_INDEX);\n const rows: T[] = [];\n await new Promise<void>((resolve, reject) => {\n let mint: string | undefined, taken = 0;\n const read = (below: IDBKeyRange | null) => {\n const request = index.openCursor(below, \"prev\");\n request.onerror = () => reject(request.error);\n request.onsuccess = () => {\n const cursor = request.result;\n if (!cursor) { resolve(); return; }\n const [at] = cursor.key as [string, number];\n if (at !== mint) { mint = at; taken = 0; }\n // A new cursor below every key of this mint starts at the next mint's newest movement. Not `continue(key)`:\n // fake-indexeddb (the CLI's and Desktop-headless' IndexedDB) walks every key that skips.\n if (taken >= count || !wanted(at)) { read(IDBKeyRange.upperBound([at, -Infinity], true)); return; }\n rows.push(cursor.value as T);\n taken++;\n cursor.continue();\n };\n };\n read(null);\n });\n return rows.sort((a, b) => b.timestamp - a.timestamp).slice(0, count);\n}\n\n/** A stored file as it stands: its record, with what changed since it was stored read over it. */\nfunction withState(file: StoredFile, state: FileState | undefined): StoredFile {\n if (!state) return file;\n const { id: _id, ...fields } = state;\n return { ...file, ...fields };\n}\n\n/**\n * The files store. A record is written once, by `put`, and never written back: what changes later (`patch`,\n * `updateTransfer`) goes to `STORES.fileState` and is read over it. WebKit (Safari, the macOS app) keeps a stored\n * Blob as a file the record points to, and a record written back with the Blob it read can lose that file: from\n * then on every read of it fails with \"The object can not be found here.\" For the same reason a Blob read from\n * here is never put here again, under any id (a forward copies the bytes into a Blob of its own).\n */\nexport const fileStore = {\n /** Stores a file anew: whatever changed about an earlier file under this id is forgotten. */\n async put(file: StoredFile): Promise<void> {\n await transact([STORES.files, STORES.fileState], stores => {\n stores[STORES.files].put(file);\n stores[STORES.fileState].delete(file.id);\n });\n },\n async updateTransfer(id: string, transfer: NonNullable<StoredFile[\"transfer\"]>): Promise<void> {\n await fileStore.patch(id, { transfer });\n },\n /** Changes some fields of a stored file (never its record, see above); nothing happens to one that was deleted. */\n async patch(id: string, fields: FileStateFields): Promise<void> {\n await transact([STORES.files, STORES.fileState], stores => {\n const exists = stores[STORES.files].getKey(id);\n exists.onsuccess = () => {\n if (exists.result === undefined) return;\n const state = stores[STORES.fileState].get(id);\n state.onsuccess = () => { stores[STORES.fileState].put({ ...(state.result as FileState | undefined), ...fields, id } satisfies FileState); };\n };\n });\n },\n async get(id: string): Promise<StoredFile | undefined> {\n const tx = (await openDb()).transaction([STORES.files, STORES.fileState], \"readonly\");\n const [file, state] = await Promise.all([\n wrap<StoredFile | undefined>(tx.objectStore(STORES.files).get(id)),\n wrap<FileState | undefined>(tx.objectStore(STORES.fileState).get(id)),\n ]);\n return file && withState(file, state);\n },\n async listForLink(linkId: string): Promise<StoredFile[]> {\n const tx = (await openDb()).transaction([STORES.files, STORES.fileState], \"readonly\");\n const files = await wrap<StoredFile[]>(tx.objectStore(STORES.files).index(\"byLink\").getAll(linkId));\n const states = await Promise.all(files.map((file) => wrap<FileState | undefined>(tx.objectStore(STORES.fileState).get(file.id))));\n return files.map((file, i) => withState(file, states[i]));\n },\n /** The id of every group a stored file is of (`linkId` `group:<id>`), read off the index: no record is loaded. */\n async groupIds(): Promise<string[]> {\n const index = (await store(STORES.files, \"readonly\")).index(\"byLink\"), ids: string[] = [];\n await new Promise<void>((resolve, reject) => {\n const request = index.openKeyCursor(IDBKeyRange.bound(\"group:\", \"group:\\uffff\"), \"nextunique\");\n request.onerror = () => reject(request.error);\n request.onsuccess = () => {\n const cursor = request.result;\n if (!cursor) return resolve();\n ids.push(String(cursor.key).slice(\"group:\".length));\n cursor.continue();\n };\n });\n return ids;\n },\n /** The id of every stored file whose bytes are a Blob in its record. */\n async blobIds(): Promise<string[]> {\n const files = await store(STORES.files, \"readonly\"), ids: string[] = [];\n await new Promise<void>((resolve, reject) => {\n const request = files.openCursor();\n request.onerror = () => reject(request.error);\n request.onsuccess = () => {\n const cursor = request.result;\n if (!cursor) return resolve();\n if ((cursor.value as StoredFile).blob) ids.push(String(cursor.key));\n cursor.continue();\n };\n });\n return ids;\n },\n /**\n * A file whose Blob was copied to file storage (`kind`, under its id): its record without the Blob from now on. Written\n * back without the Blob it read, so not what WebKit loses (see above); what changed about it since (`fileState`) stays.\n * False when the record is gone or holds no Blob any more: the copy is the caller's to remove.\n */\n async movedToBytes(id: string, kind: NonNullable<StoredFile[\"bytes\"]>): Promise<boolean> {\n let moved = false;\n await transact([STORES.files], stores => {\n const read = stores[STORES.files].get(id);\n read.onsuccess = () => {\n const record = read.result as StoredFile | undefined;\n if (!record?.blob) return;\n const { blob: _blob, ...rest } = record;\n stores[STORES.files].put({ ...rest, bytes: kind } satisfies StoredFile);\n moved = true;\n };\n });\n return moved;\n },\n async delete(id: string): Promise<void> {\n await transact([STORES.files, STORES.fileState], stores => {\n stores[STORES.files].delete(id);\n stores[STORES.fileState].delete(id);\n });\n },\n async deleteForLink(linkId: string): Promise<void> {\n const tx = (await openDb()).transaction([STORES.files, STORES.fileState], \"readwrite\");\n const files = tx.objectStore(STORES.files), state = tx.objectStore(STORES.fileState);\n const keys = await wrap(files.index(\"byLink\").getAllKeys(linkId));\n await Promise.all(keys.flatMap((key) => [wrap(files.delete(key)), wrap(state.delete(key))]));\n },\n};\n"],"mappings":";;;;;;AAKA,IAAI,SAAS;;AAOb,SAAgB,eAAuB;CACrC,OAAO;AACT;;AAwBA,IAAa,mBAAb,cAAsC,MAAM;CACrB;CAArB,YAAY,SAAsC;EAChD,MAAM,mBAAmB,OAAO,CAAC;EADd,KAAA,UAAA;EAEnB,KAAK,OAAO;CACd;AACF;;AAGA,SAAgB,mBAAmB,SAAqC;CACtE,MAAM,QAAQ;CACd,QAAQ,QAAQ,QAAhB;EACE,KAAK,SACH,OAAO,iFAAiF,QAAQ,iBAAiB,WAAW,QAAQ,iBAAiB,6BAA6B,QAAQ,iBAAiB;EAC7M,KAAK,WAAW,OAAO,GAAG,MAAM;EAChC,KAAK,QAAQ,OAAO,GAAG,MAAM;EAC7B,KAAK,UAAU,OAAO,GAAG,MAAM;EAC/B,SAAS,OAAO,GAAG,QAAQ,QAAQ,SAAS,KAAK,QAAQ,WAAW;CACtE;AACF;;AAGA,SAAgB,mBAAmB,OAAoC;CACrE,IAAI,iBAAiB,kBAAkB,OAAO,MAAM;CACpD,MAAM,SAAS,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;CACpE,OAAO;EAAE,QAAQ;EAAU,kBAAA;EAA8B,GAAI,SAAS,EAAE,OAAO,IAAI,CAAC;CAAG;AACzF;;;;;;AAOA,SAAgB,UAAU,OAAwB;CAChD,IAAK,OAAqC,SAAS,sBAAsB;EACvE,MAAM,UAAU,iBAAiB,QAAQ,MAAM,QAAQ,KAAK,IAAI;EAChE,OAAO,sDAAsD,UAAU,KAAK,YAAY,GAAG;CAC7F;CACA,OAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AAC9D;;AAGA,IAAa,eAAe,EAAE,WAAW,IAAO;AAEhD,SAAS,UAAU,OAAoC;CACrD,MAAM,OAAQ,OAAqC;CACnD,MAAM,UAAW,OAAwC;CACzD,MAAM,SAAS,CAAC,OAAO,SAAS,WAAW,OAAO,IAAI,OAAO,YAAY,WAAW,UAAU,EAAE,CAAC,CAAC,OAAO,OAAO,CAAC,CAAC,KAAK,IAAI;CAO3H,OAAO;EAAE,QANyB,SAAS,iBAAiB,UACxD,SAAS,uBAAuB,SAE9B,SAAS,iBAAiB,YAC1B,SAAS,mBAAmB,SAAS,uBAAuB,SAAS,oBAAoB,SAAS,cAAc,WAC9G;EACS,kBAAA;EAA8B,GAAI,SAAS,EAAE,OAAO,IAAI,CAAC;CAAG;AAC/E;;;;;AAMA,SAAS,cAAc,MAA2C;CAChE,OAAO,IAAI,SAAS,YAAY;EAC9B,IAAI;GACF,MAAM,UAAU,UAAU,KAAK,IAAI;GAEnC,QAAQ,wBAAwB;IAAE,IAAI;KAAE,QAAQ,aAAa,MAAM;IAAG,QAAQ,CAAqB;GAAE;GACrG,QAAQ,kBAAkB;IAAE,MAAM,EAAE,YAAY,QAAQ;IAAQ,QAAQ,OAAO,MAAM;IAAG,QAAQ,OAAO;GAAG;GAC1G,QAAQ,UAAU,QAAQ,kBAAkB,QAAQ,KAAA,CAAS;EAC/D,QAAQ;GAAE,QAAQ,KAAA,CAAS;EAAG;CAChC,CAAC;AACH;;AAEA,IAAa,aAAa;;AAE1B,IAAa,kBAAkB;AAE/B,IAAa,SAAS;CACpB,OAAO;CACP,UAAU;CACV,UAAU;CACV,UAAU;CACV,OAAO;CACP,QAAQ;CACR,UAAU;CACV,QAAQ;CACR,UAAU;CACV,OAAO;;CAEP,OAAO;CACP,SAAS;;CAET,QAAQ;;CAER,YAAY;;;;;CAKZ,WAAW;;CAEX,MAAM;;CAEN,WAAW;;CAEX,YAAY;AACd;;AAGA,IAAa,0BAA0B;;AAEvC,IAAa,yBAAyB;AA2CtC,IAAI,YAAyC;AAE7C,SAAgB,SAA+B;CAC7C,cAAc,IAAI,SAAS,SAAS,WAAW;EAC7C,IAAI,UAAU;EACd,IAAI;EAEJ,MAAM,QAAQ,UAAmB;GAC/B,IAAI,SAAS;GACb,UAAU;GACV,aAAa,OAAO;GACpB,MAAM,UAAU,UAAU,KAAK;GAC/B,IAAI,QAAQ,WAAW,SAAS;IAAE,OAAO,IAAI,iBAAiB,OAAO,CAAC;IAAG;GAAQ;GACjF,cAAmB,MAAM,CAAC,CAAC,MAAM,YAAY,OAAO,IAAI,iBAAiB;IAAE,GAAG;IAAS,GAAI,YAAY,KAAA,IAAY,EAAE,eAAe,QAAQ,IAAI,CAAC;GAAG,CAAC,CAAC,CAAC;EACzJ;EACA,IAAI;EACJ,IAAI;GACF,UAAU,UAAU,KAAK,QAAA,EAAkB;EAC7C,SAAS,OAAO;GACd,KAAK,KAAK;GACV;EACF;EACA,QAAQ,wBAAwB;GAC9B,MAAM,KAAK,QAAQ;GACnB,MAAM,OAAO,SAAiB,GAAG,iBAAiB,SAAS,IAAI;GAC/D,IAAI,CAAC,IAAI,OAAO,KAAK,GAAG,GAAG,kBAAkB,OAAO,OAAO,EAAE,SAAS,KAAK,CAAC;GAC5E,IAAI,CAAC,IAAI,OAAO,QAAQ,GAAG,GAAG,kBAAkB,OAAO,UAAU,EAAE,SAAS,KAAK,CAAC;GAClF,IAAI,CAAC,IAAI,OAAO,QAAQ,GAAG,GAAG,kBAAkB,OAAO,QAAQ;GAC/D,IAAI,CAAC,IAAI,OAAO,QAAQ,GACtB,GAAG,kBAAkB,OAAO,UAAU,EAAE,SAAS,CAAC,UAAU,IAAI,EAAE,CAAC,CAAC,CAAC,YAAY,UAAU,QAAQ;GAErG,IAAI,CAAC,IAAI,OAAO,MAAM,GAAG,GAAG,kBAAkB,OAAO,QAAQ,EAAE,SAAS,SAAS,CAAC;GAClF,IAAI,CAAC,IAAI,OAAO,QAAQ,GAAG,GAAG,kBAAkB,OAAO,UAAU,EAAE,SAAS,KAAK,CAAC;GAClF,IAAI,CAAC,IAAI,OAAO,MAAM,GAAG,GAAG,kBAAkB,OAAO,QAAQ,EAAE,SAAS,QAAQ,CAAC;GACjF,IAAI,CAAC,IAAI,OAAO,QAAQ,GAAG,GAAG,kBAAkB,OAAO,UAAU,EAAE,SAAS,KAAK,CAAC;GAElF,IAAI,CAAC,IAAI,OAAO,KAAK,GAAG,GAAG,kBAAkB,OAAO,OAAO,EAAE,SAAS,QAAQ,CAAC;GAC/E,IAAI,CAAC,IAAI,OAAO,OAAO,GAAG,GAAG,kBAAkB,OAAO,SAAS,EAAE,SAAS,YAAY,CAAC;GACvF,IAAI,CAAC,IAAI,OAAO,KAAK,GACnB,GAAG,kBAAkB,OAAO,OAAO,EAAE,SAAS,KAAK,CAAC,CAAC,CAAC,YAAY,UAAU,QAAQ;GAGtF,IAAI,CAAC,IAAI,OAAO,MAAM,GAAG,GAAG,kBAAkB,OAAO,QAAQ,EAAE,SAAS,KAAK,CAAC;GAE9E,IAAI,CAAC,IAAI,OAAO,UAAU,GAAG,GAAG,kBAAkB,OAAO,YAAY,EAAE,SAAS,CAAC,MAAM,OAAO,EAAE,CAAC;GAEjG,IAAI,CAAC,IAAI,OAAO,SAAS,GAAG,GAAG,kBAAkB,OAAO,WAAW,EAAE,SAAS,KAAK,CAAC;GAEpF,MAAM,WAAW,QAAQ,YAAa,YAAY,OAAO,QAAQ;GACjE,IAAI,CAAC,SAAS,WAAW,SAAS,YAAY,GAAG,SAAS,YAAY,cAAc,CAAC,UAAU,WAAW,CAAC;GAG3G,IAAI,CAAC,SAAS,WAAW,SAAA,YAAmB,GAAG,SAAS,YAAY,YAAY,WAAW;GAE3F,IAAI,CAAC,IAAI,OAAO,KAAK,GAAG,GAAG,kBAAkB,OAAO,OAAO,EAAE,SAAS,KAAK,CAAC;GAK5E,IAAI,CAAC,IAAI,OAAO,IAAI,GAAG,GAAG,kBAAkB,OAAO,MAAM,EAAE,SAAS,MAAM,CAAC;GAC3E,IAAI,CAAC,IAAI,OAAO,SAAS,GAAG,GAAG,kBAAkB,OAAO,WAAW,EAAE,SAAS,MAAM,CAAC;GACrF,IAAI,CAAC,IAAI,OAAO,UAAU,GAAG;IAC3B,MAAM,UAAU,GAAG,kBAAkB,OAAO,YAAY,EAAE,SAAS;KAAC;KAAO;KAAS;IAAK,EAAE,CAAC;IAC5F,QAAQ,YAAY,yBAAyB,CAAC,OAAO,OAAO,CAAC;IAC7D,QAAQ,YAAY,wBAAwB,OAAO;GACrD;GAEA,MAAM,WAAW,QAAQ,YAAa,YAAY,OAAO,QAAQ;GACjE,IAAI,CAAC,SAAS,WAAW,SAAA,YAAwB,GAAG,SAAS,YAAY,iBAAiB,CAAC,QAAQ,WAAW,CAAC;EACjH;EACA,QAAQ,kBAAkB;GAExB,IAAI,SAAS;IAAE,QAAQ,OAAO,MAAM;IAAG;GAAQ;GAC/C,UAAU;GACV,aAAa,OAAO;GAEpB,QAAQ,OAAO,wBAAwB;IACrC,QAAQ,OAAO,MAAM;IACrB,YAAY;GACd;GACA,QAAQ,QAAQ,MAAM;EACxB;EAEA,QAAQ,WAAW,UAAU;GAAE,MAAM,iBAAiB;GAAG,KAAK,QAAQ,KAAK;EAAG;EAG9E,QAAQ,kBAAkB;GACxB,YAAY,iBAAiB,KAAK;IAAE,MAAM;IAAgB,SAAS;GAAiD,CAAC,GAAG,aAAa,SAAS;EAChJ;CACF,CAAC;CACD,OAAO;AACT;;AAGA,IAAa,iBAAiB;CAAC,OAAO;CAAO,OAAO;CAAU,OAAO;CAAO,OAAO;CAAW,OAAO;CAAY,OAAO;CAAU,OAAO;CAAQ,OAAO;CAAM,OAAO;CAAW,OAAO;AAAU;;;;;;;;;AAUjM,IAAa,wBAAwB;CAAC;CAAc;CAAkB;CAAuB;CAAe;CAAkB;CAAe;CAAmB;CAAgB;AAAa;;;;;;AAO7L,IAAa,yBAAyB;CAAC;CAAS;CAAoB;CAAe;AAAiB;;;;;;;AAQpG,eAAsB,qBAAoC;CACxD,IAAI,OAAO,cAAc,aAAa;CACtC,MAAM,KAAK,MAAM,OAAO;CACxB,MAAM,QAAQ,CAAC,GAAG,gBAAgB,OAAO,QAAQ,CAAC,CAAC,QAAQ,SAAS,GAAG,iBAAiB,SAAS,IAAI,CAAC;CACtG,MAAM,KAAK,GAAG,YAAY,OAAO,WAAW;CAC5C,KAAK,MAAM,QAAQ,OAAO,IAAI,SAAS,OAAO,UAAU,GAAG,YAAY,IAAI,CAAC,CAAC,MAAM;CACnF,IAAI,MAAM,SAAS,OAAO,QAAQ,GAAG;EACnC,MAAM,WAAW,GAAG,YAAY,OAAO,QAAQ;EAC/C,KAAK,MAAM,OAAO,uBAAuB,SAAS,OAAO,GAAG;EAC5D,MAAM,OAAO,SAAS,IAAI,UAAU;EACpC,KAAK,kBAAkB;GACrB,MAAM,SAAS,KAAK;GACpB,IAAI,CAAC,UAAU,OAAO,WAAW,UAAU;GAC3C,MAAM,OAAO,OAAO,YAAY,uBAAuB,QAAQ,UAAU,SAAS,MAAM,CAAC,CAAC,KAAK,UAAU,CAAC,OAAO,OAAO,MAAM,CAAC,CAAC;GAChI,SAAS,IAAI,MAAM,UAAU;EAC/B;CACF;CACA,MAAM,IAAI,SAAe,SAAS,WAAW;EAC3C,GAAG,mBAAmB,QAAQ;EAC9B,GAAG,UAAU,GAAG,gBAAgB,OAAO,GAAG,KAAK;CACjD,CAAC;AACH;;AAGA,SAAgB,eAAe,MAA6B;CAC1D,OAAO,IAAI,SAAe,YAAY;EACpC,IAAI;GAAE,MAAM,UAAU,UAAU,eAAe,IAAI;GAAG,QAAQ,YAAY,QAAQ,UAAU,QAAQ,kBAAkB,QAAQ;EAAG,QAAQ;GAAE,QAAQ;EAAG;CACxJ,CAAC;AACH;AAEA,SAAgB,KAAQ,SAAoC;CAC1D,OAAO,IAAI,SAAS,SAAS,WAAW;EACtC,QAAQ,kBAAkB,QAAQ,QAAQ,MAAM;EAChD,QAAQ,gBAAgB,OAAO,QAAQ,KAAK;CAC9C,CAAC;AACH;AAEA,eAAsB,MAAM,MAAc,MAAmD;CAC3F,QAAQ,MAAM,OAAO,EAAA,CAAG,YAAY,MAAM,IAAI,CAAC,CAAC,YAAY,IAAI;AAClE;;;;;AAMA,eAAsB,SAAS,OAAiB,MAAuE;CACrH,MAAM,MAAM,MAAM,OAAO,EAAA,CAAG,YAAY,OAAO,WAAW;CAC1D,MAAM,OAAO,IAAI,SAAe,SAAS,WAAW;EAClD,GAAG,mBAAmB,QAAQ;EAC9B,GAAG,gBAAgB,OAAO,GAAG,yBAAS,IAAI,MAAM,2BAA2B,CAAC;EAC5E,GAAG,gBAAgB,OAAO,GAAG,yBAAS,IAAI,MAAM,2BAA2B,CAAC;CAC9E,CAAC;CACD,IAAI;EACF,KAAK,OAAO,YAAY,MAAM,KAAK,SAAS,CAAC,MAAM,GAAG,YAAY,IAAI,CAAC,CAAC,CAAC,CAAC;CAC5E,SAAS,OAAO;EACd,GAAG,MAAM;EACT,MAAM,KAAK,YAAY,CAAC,CAAC;EACzB,MAAM;CACR;CACA,OAAO;AACT;;;;;;AAOA,eAAsB,eAAgD,QAAmC,OAA6B;CACpI,MAAM,SAAS,MAAM,MAAM,OAAO,UAAU,UAAU,EAAA,CAAG,MAAM,eAAe;CAC9E,MAAM,OAAY,CAAC;CACnB,MAAM,IAAI,SAAe,SAAS,WAAW;EAC3C,IAAI,MAA0B,QAAQ;EACtC,MAAM,QAAQ,UAA8B;GAC1C,MAAM,UAAU,MAAM,WAAW,OAAO,MAAM;GAC9C,QAAQ,gBAAgB,OAAO,QAAQ,KAAK;GAC5C,QAAQ,kBAAkB;IACxB,MAAM,SAAS,QAAQ;IACvB,IAAI,CAAC,QAAQ;KAAE,QAAQ;KAAG;IAAQ;IAClC,MAAM,CAAC,MAAM,OAAO;IACpB,IAAI,OAAO,MAAM;KAAE,OAAO;KAAI,QAAQ;IAAG;IAGzC,IAAI,SAAS,SAAS,CAAC,OAAO,EAAE,GAAG;KAAE,KAAK,YAAY,WAAW,CAAC,IAAI,SAAS,GAAG,IAAI,CAAC;KAAG;IAAQ;IAClG,KAAK,KAAK,OAAO,KAAU;IAC3B;IACA,OAAO,SAAS;GAClB;EACF;EACA,KAAK,IAAI;CACX,CAAC;CACD,OAAO,KAAK,MAAM,GAAG,MAAM,EAAE,YAAY,EAAE,SAAS,CAAC,CAAC,MAAM,GAAG,KAAK;AACtE;;AAGA,SAAS,UAAU,MAAkB,OAA0C;CAC7E,IAAI,CAAC,OAAO,OAAO;CACnB,MAAM,EAAE,IAAI,KAAK,GAAG,WAAW;CAC/B,OAAO;EAAE,GAAG;EAAM,GAAG;CAAO;AAC9B;;;;;;;;AASA,IAAa,YAAY;;CAEvB,MAAM,IAAI,MAAiC;EACzC,MAAM,SAAS,CAAC,OAAO,OAAO,OAAO,SAAS,IAAG,WAAU;GACzD,OAAO,OAAO,MAAM,CAAC,IAAI,IAAI;GAC7B,OAAO,OAAO,UAAU,CAAC,OAAO,KAAK,EAAE;EACzC,CAAC;CACH;CACA,MAAM,eAAe,IAAY,UAA8D;EAC7F,MAAM,UAAU,MAAM,IAAI,EAAE,SAAS,CAAC;CACxC;;CAEA,MAAM,MAAM,IAAY,QAAwC;EAC9D,MAAM,SAAS,CAAC,OAAO,OAAO,OAAO,SAAS,IAAG,WAAU;GACzD,MAAM,SAAS,OAAO,OAAO,MAAM,CAAC,OAAO,EAAE;GAC7C,OAAO,kBAAkB;IACvB,IAAI,OAAO,WAAW,KAAA,GAAW;IACjC,MAAM,QAAQ,OAAO,OAAO,UAAU,CAAC,IAAI,EAAE;IAC7C,MAAM,kBAAkB;KAAE,OAAO,OAAO,UAAU,CAAC,IAAI;MAAE,GAAI,MAAM;MAAkC,GAAG;MAAQ;KAAG,CAAqB;IAAG;GAC7I;EACF,CAAC;CACH;CACA,MAAM,IAAI,IAA6C;EACrD,MAAM,MAAM,MAAM,OAAO,EAAA,CAAG,YAAY,CAAC,OAAO,OAAO,OAAO,SAAS,GAAG,UAAU;EACpF,MAAM,CAAC,MAAM,SAAS,MAAM,QAAQ,IAAI,CACtC,KAA6B,GAAG,YAAY,OAAO,KAAK,CAAC,CAAC,IAAI,EAAE,CAAC,GACjE,KAA4B,GAAG,YAAY,OAAO,SAAS,CAAC,CAAC,IAAI,EAAE,CAAC,CACtE,CAAC;EACD,OAAO,QAAQ,UAAU,MAAM,KAAK;CACtC;CACA,MAAM,YAAY,QAAuC;EACvD,MAAM,MAAM,MAAM,OAAO,EAAA,CAAG,YAAY,CAAC,OAAO,OAAO,OAAO,SAAS,GAAG,UAAU;EACpF,MAAM,QAAQ,MAAM,KAAmB,GAAG,YAAY,OAAO,KAAK,CAAC,CAAC,MAAM,QAAQ,CAAC,CAAC,OAAO,MAAM,CAAC;EAClG,MAAM,SAAS,MAAM,QAAQ,IAAI,MAAM,KAAK,SAAS,KAA4B,GAAG,YAAY,OAAO,SAAS,CAAC,CAAC,IAAI,KAAK,EAAE,CAAC,CAAC,CAAC;EAChI,OAAO,MAAM,KAAK,MAAM,MAAM,UAAU,MAAM,OAAO,EAAE,CAAC;CAC1D;;CAEA,MAAM,WAA8B;EAClC,MAAM,SAAS,MAAM,MAAM,OAAO,OAAO,UAAU,EAAA,CAAG,MAAM,QAAQ,GAAG,MAAgB,CAAC;EACxF,MAAM,IAAI,SAAe,SAAS,WAAW;GAC3C,MAAM,UAAU,MAAM,cAAc,YAAY,MAAM,UAAU,SAAc,GAAG,YAAY;GAC7F,QAAQ,gBAAgB,OAAO,QAAQ,KAAK;GAC5C,QAAQ,kBAAkB;IACxB,MAAM,SAAS,QAAQ;IACvB,IAAI,CAAC,QAAQ,OAAO,QAAQ;IAC5B,IAAI,KAAK,OAAO,OAAO,GAAG,CAAC,CAAC,MAAM,CAAe,CAAC;IAClD,OAAO,SAAS;GAClB;EACF,CAAC;EACD,OAAO;CACT;;CAEA,MAAM,UAA6B;EACjC,MAAM,QAAQ,MAAM,MAAM,OAAO,OAAO,UAAU,GAAG,MAAgB,CAAC;EACtE,MAAM,IAAI,SAAe,SAAS,WAAW;GAC3C,MAAM,UAAU,MAAM,WAAW;GACjC,QAAQ,gBAAgB,OAAO,QAAQ,KAAK;GAC5C,QAAQ,kBAAkB;IACxB,MAAM,SAAS,QAAQ;IACvB,IAAI,CAAC,QAAQ,OAAO,QAAQ;IAC5B,IAAK,OAAO,MAAqB,MAAM,IAAI,KAAK,OAAO,OAAO,GAAG,CAAC;IAClE,OAAO,SAAS;GAClB;EACF,CAAC;EACD,OAAO;CACT;;;;;;CAMA,MAAM,aAAa,IAAY,MAA0D;EACvF,IAAI,QAAQ;EACZ,MAAM,SAAS,CAAC,OAAO,KAAK,IAAG,WAAU;GACvC,MAAM,OAAO,OAAO,OAAO,MAAM,CAAC,IAAI,EAAE;GACxC,KAAK,kBAAkB;IACrB,MAAM,SAAS,KAAK;IACpB,IAAI,CAAC,QAAQ,MAAM;IACnB,MAAM,EAAE,MAAM,OAAO,GAAG,SAAS;IACjC,OAAO,OAAO,MAAM,CAAC,IAAI;KAAE,GAAG;KAAM,OAAO;IAAK,CAAsB;IACtE,QAAQ;GACV;EACF,CAAC;EACD,OAAO;CACT;CACA,MAAM,OAAO,IAA2B;EACtC,MAAM,SAAS,CAAC,OAAO,OAAO,OAAO,SAAS,IAAG,WAAU;GACzD,OAAO,OAAO,MAAM,CAAC,OAAO,EAAE;GAC9B,OAAO,OAAO,UAAU,CAAC,OAAO,EAAE;EACpC,CAAC;CACH;CACA,MAAM,cAAc,QAA+B;EACjD,MAAM,MAAM,MAAM,OAAO,EAAA,CAAG,YAAY,CAAC,OAAO,OAAO,OAAO,SAAS,GAAG,WAAW;EACrF,MAAM,QAAQ,GAAG,YAAY,OAAO,KAAK,GAAG,QAAQ,GAAG,YAAY,OAAO,SAAS;EACnF,MAAM,OAAO,MAAM,KAAK,MAAM,MAAM,QAAQ,CAAC,CAAC,WAAW,MAAM,CAAC;EAChE,MAAM,QAAQ,IAAI,KAAK,SAAS,QAAQ,CAAC,KAAK,MAAM,OAAO,GAAG,CAAC,GAAG,KAAK,MAAM,OAAO,GAAG,CAAC,CAAC,CAAC,CAAC;CAC7F;AACF"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Xi as irohDescriptor, Zi as irohRelayUrl, Zl as fromBase64Url } from "./src-DzXL-GYu.js";
|
|
2
2
|
//#region ../browser/src/engine/paymentAdapters/fedimintSdk.ts
|
|
3
3
|
var fedimintDatabase = (id) => `ghostly-fedimint-${id}.db`;
|
|
4
4
|
var MODULE_WASM = () => import("./fedimint_client_wasm_bg-BtHHlFpp.js");
|
|
@@ -541,4 +541,4 @@ async function createIrohWebEndpoint(seedB64, options = {}) {
|
|
|
541
541
|
//#endregion
|
|
542
542
|
export { fedimintSdkOn as a, fedimintDatabase as i, createIrohWebEndpoint as n, loadFedimintSdk as o, irohRelayProblem as r, DEFAULT_IROH_RELAYS as t };
|
|
543
543
|
|
|
544
|
-
//# sourceMappingURL=irohWeb-
|
|
544
|
+
//# sourceMappingURL=irohWeb-DHCxSvHF.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"irohWeb-z5QDDWa-.js","names":[],"sources":["../../../browser/src/engine/paymentAdapters/fedimintSdk.ts","../../../browser/src/platform/irohWeb.ts"],"sourcesContent":["import type { BitcoinNetwork } from \"@ghostly/core\";\n\n/**\n * The part of the Fedimint web SDK (`@fedimint/core`, WebAssembly client in a worker) Ghostly uses, behind an\n * interface small enough to fake in tests. The client runs in a worker, on a file it reads and writes through a sync\n * access handle: in a browser, a module worker and the origin-private file system; on Node, the CLI's\n * `worker_threads` worker and a file of the profile (`FedimintPlatform`).\n *\n * One client database per joined federation (`ghostly-fedimint-<id>.db`, one file): the SDK's services are bound to one client per database, and each database is held by one worker.\n * Every database of a profile and mode is given the same mnemonic; the client derives a different secret per\n * federation from it. Amounts are millisatoshis, as the SDK counts them.\n */\nexport interface FederationGuardian { name: string; url: string }\nexport interface FederationInfo {\n federationId: string;\n /** `meta.federation_name`, when the guardians set one. */\n name?: string;\n guardians: FederationGuardian[];\n /** Core consensus version, `major.minor`. */\n consensusVersion: string;\n /** The Bitcoin network of its wallet module. Unknown when the federation has no v1 wallet module. */\n network?: BitcoinNetwork;\n /** Module kinds (`mint`, `ln`, `wallet`, `meta`…). The SDK uses `mint`, `ln` and `wallet` (v1). */\n modules: string[];\n /** `meta.welcome_message`, shortened. */\n welcome?: string;\n}\n\n/** Out-of-band notes, parsed offline (signatures and prefix only: whether they are spent is the federation's to say). */\nexport interface ParsedNotes { amountMsats: number; federationIdPrefix: string; federationId?: string }\n\n/** Where a spend of notes stands. `canceled` = taken back (reissued to us); `taken` = the recipient redeemed them. */\nexport type SpendState = \"created\" | \"canceling\" | \"canceled\" | \"taken\" | \"refunded\";\n/** A redeem (reissue) of notes we were given. */\nexport type RedeemState = \"created\" | \"issuing\" | \"done\" | \"failed\";\nexport type ReceiveState = \"open\" | \"funded\" | \"claimed\" | \"canceled\";\nexport interface PayOutcome { state: \"pending\" | \"paid\" | \"failed\"; preimage?: string; error?: string }\n\nexport interface FedimintOperation {\n id: string;\n /** Module: `mint`, `ln`, `wallet`. */\n kind: string;\n /** What it was: `spend_oob`, `reissuance`, `receive`, `pay`… as the SDK names it. */\n variant?: string;\n createdAt: number;\n amountMsats?: number;\n /** The SDK's final outcome, once there is one. */\n outcome?: unknown;\n /** What Ghostly asked to be kept with it (`extra_meta`): the payment id it belongs to. */\n ghostly?: string;\n /** Lightning: the invoice it paid or was paid on, and the fee it paid (msats). */\n invoice?: string;\n feeMsats?: number;\n}\n\nexport interface FedimintClient {\n readonly federationId: string;\n info(): Promise<FederationInfo>;\n balance(): Promise<number>;\n /** Called with every new balance, until the returned function is called. */\n onBalance(listener: (msats: number) => void): () => void;\n parseNotes(notes: string): Promise<ParsedNotes>;\n /** Takes notes out of the wallet, as a bearer string. Unredeemed after `cancelAfterSecs`, they come back by themselves. */\n spend(amountMsats: number, options: { cancelAfterSecs: number; ghostly: string }): Promise<{ notes: string; operationId: string }>;\n /** Asks to take notes back: they are reissued to us unless the recipient redeemed them first. */\n cancelSpend(operationId: string): Promise<void>;\n /** The latest state within `waitMs` (it returns early on a final one). Undefined: the client knows no such operation. */\n spendState(operationId: string, waitMs: number): Promise<SpendState | undefined>;\n redeem(notes: string, ghostly: string): Promise<string>;\n redeemState(operationId: string, waitMs: number): Promise<RedeemState | undefined>;\n createInvoice(amountMsats: number, description: string, expirySecs: number, ghostly: string): Promise<{ operationId: string; invoice: string }>;\n receiveState(operationId: string, waitMs: number): Promise<ReceiveState | undefined>;\n /** What the federation's gateway would charge to pay this amount, in msats. Undefined: no gateway. */\n gatewayFee(amountMsats: number): Promise<number | undefined>;\n payInvoice(invoice: string, ghostly: string): Promise<{ operationId: string; feeMsats: number; internal: boolean }>;\n payState(operationId: string, internal: boolean, waitMs: number): Promise<PayOutcome>;\n operations(limit: number): Promise<FedimintOperation[]>;\n /** Ends the worker; the database stays. */\n close(): Promise<void>;\n}\n\nexport interface FedimintSdk {\n preview(invite: string): Promise<FederationInfo>;\n /** Joins (or, `recover`, restores from the federation's backup of this mnemonic) into a new database. */\n join(params: { database: string; mnemonic: string; invite: string; recover: boolean }): Promise<FedimintClient>;\n /** Opens a database joined before. */\n open(params: { database: string; mnemonic: string }): Promise<FedimintClient>;\n /** Deletes a database: only one that never held money (a failed join). */\n remove(database: string): Promise<void>;\n /** Whether this device has the database (a profile restored from a backup has the records, not the files). */\n exists(database: string): Promise<boolean>;\n}\n\nexport const fedimintDatabase = (id: string) => `ghostly-fedimint-${id}.db`;\n\n// ── The real SDK ───────────────────────────────────────────────────────────────────────────────────────────\n\ntype Json = null | boolean | number | string | Json[] | { [key: string]: Json };\nconst MODULE_WASM = () => import(\"@fedimint/fedimint-client-wasm-bundler/fedimint_client_wasm_bg.wasm?url\");\n/** The SDK checks it with an assert: any other length kills the worker. One client per database, always this name. */\nconst CLIENT_NAME = \"dd5135b2-c228-41b7-a4f9-3b6e7afe3088\";\nconst NETWORKS: Record<string, BitcoinNetwork> = { bitcoin: \"bitcoin\", signet: \"signet\", testnet: \"testnet\", testnet4: \"testnet\", regtest: \"regtest\", mutinynet: \"mutinynet\" };\n/**\n * A v1 wallet (or Lightning) module's client config comes consensus-encoded as hex (`config` from a joined client,\n * `unknown_module_hex` in a preview); its network is the chain's magic, a u32 as a compact-size integer (`fe` +\n * four bytes, little-endian). Exactly one network must be found, or it stays unknown.\n */\nconst MAGIC: [string, BitcoinNetwork][] = [[\"fed9b4bef9\", \"bitcoin\"], [\"fe0709110b\", \"testnet\"], [\"fe283f161c\", \"testnet\"], [\"fe40cf030a\", \"signet\"], [\"fecb2ddfa5\", \"mutinynet\"], [\"fedab5bffa\", \"regtest\"]];\nfunction moduleNetwork(module: { network?: unknown; config?: unknown; unknown_module_hex?: unknown }): BitcoinNetwork | undefined {\n if (typeof module.network === \"string\") return NETWORKS[module.network];\n const encoded = typeof module.config === \"string\" ? module.config : module.unknown_module_hex;\n if (typeof encoded !== \"string\" || !/^[0-9a-f]*$/i.test(encoded)) return undefined;\n const hex = encoded.toLowerCase();\n const found = [...new Set(MAGIC.filter(([magic]) => hex.includes(magic)).map(([, network]) => network))];\n return found.length === 1 ? found[0] : undefined;\n}\nconst hex = (value: unknown) => typeof value === \"string\" ? value : Array.isArray(value) ? value.map((b) => Number(b).toString(16).padStart(2, \"0\")).join(\"\") : undefined;\n\nlet compiling: Promise<WebAssembly.Module> | undefined;\n/** Compiled once per page (11 MB): each worker is handed the module, not the bytes. */\nfunction compiled(): Promise<WebAssembly.Module> {\n return compiling ??= (async () => {\n const url = (await MODULE_WASM()).default;\n const response = await fetch(url);\n // A server that sends the wrong MIME type cannot stream: compile from the bytes instead.\n try { return await WebAssembly.compileStreaming(response.clone()); } catch { return WebAssembly.compile(await response.arrayBuffer()); }\n })().catch((error) => { compiling = undefined; throw error; });\n}\n\n/** A transport of `@fedimint/core` onto our own worker (fedimintWorker.ts). */\nasync function browserTransport() {\n const [{ Transport }, module] = await Promise.all([import(\"@fedimint/types\"), compiled()]);\n class WorkerTransport extends Transport {\n readonly logger = { debug() {}, info() {}, warn() {}, error() {} };\n private readonly worker = new Worker(new URL(\"./fedimintWorker.ts\", import.meta.url), { type: \"module\" });\n constructor() {\n super();\n this.worker.onmessage = (event) => this.messageHandler(event.data);\n this.worker.onerror = (event) => { event.preventDefault(); this.messageHandler({ type: \"error\", error: `The Fedimint client stopped: ${event.message || \"unknown error\"}` }); };\n }\n postMessage(message: { type: string; payload?: Json; requestId?: number }) {\n // The worker gets the compiled module with its init, and nothing to log.\n this.worker.postMessage(message.type === \"init\" ? { ...message, payload: { ...(message.payload as object), module } } : message);\n }\n terminate() { this.worker.terminate(); }\n }\n return new WorkerTransport();\n}\n\nconst text = (value: unknown, max = 200) => typeof value === \"string\" ? value.slice(0, max) : undefined;\nconst errorText = (error: unknown) => error instanceof Error ? error.message : typeof error === \"string\" ? error : JSON.stringify(error);\n\nexport function federationInfo(federationId: string, config: unknown): FederationInfo {\n const c = config as { global?: { api_endpoints?: Record<string, { url?: unknown; name?: unknown }>; consensus_version?: { major?: unknown; minor?: unknown }; meta?: Record<string, unknown> }; modules?: Record<string, { kind?: unknown; network?: unknown; config?: unknown; unknown_module_hex?: unknown }> };\n const endpoints = Object.entries(c?.global?.api_endpoints ?? {}).sort(([a], [b]) => Number(a) - Number(b));\n const modules = Object.values(c?.modules ?? {});\n // The wallet module says it; a federation without one (Lightning and ecash only) says it through its Lightning module.\n const network = modules.filter((m) => m?.kind === \"wallet\").map(moduleNetwork).find(Boolean) ?? modules.filter((m) => m?.kind === \"ln\").map(moduleNetwork).find(Boolean);\n const version = c?.global?.consensus_version;\n return {\n federationId,\n name: text(c?.global?.meta?.federation_name, 80),\n guardians: endpoints.slice(0, 64).map(([peer, e]) => ({ name: text(e?.name, 80) || `Guardian ${peer}`, url: text(e?.url, 300) ?? \"\" })),\n consensusVersion: `${Number(version?.major ?? 0)}.${Number(version?.minor ?? 0)}`,\n network,\n modules: [...new Set(modules.map((m) => (typeof m?.kind === \"string\" ? m.kind.slice(0, 32) : \"\")).filter(Boolean))],\n welcome: text(c?.global?.meta?.welcome_message, 280),\n };\n}\n\n/** The first value a subscription gives that is final, or the last one within `waitMs`. */\nfunction watch<S>(subscribe: (onState: (state: S) => void, onError: (error: string) => void) => () => void, final: (state: S) => boolean, waitMs: number): Promise<S | undefined> {\n return new Promise((resolve) => {\n let last: S | undefined, done = false, cancel: () => void = () => {};\n const finish = () => { if (done) return; done = true; clearTimeout(timer); cancel(); resolve(last); };\n const timer = setTimeout(finish, waitMs);\n cancel = subscribe((state) => { last = state; if (final(state)) finish(); }, () => finish());\n });\n}\n\nconst SPEND: Record<string, SpendState> = { Created: \"created\", UserCanceledProcessing: \"canceling\", UserCanceledSuccess: \"canceled\", UserCanceledFailure: \"taken\", Success: \"taken\", Refunded: \"refunded\" };\nconst spendFinal = (s: SpendState) => s === \"canceled\" || s === \"taken\" || s === \"refunded\";\nconst REDEEM: Record<string, RedeemState> = { Created: \"created\", Issuing: \"issuing\", Done: \"done\" };\nconst receiveOf = (s: unknown): ReceiveState => s === \"claimed\" ? \"claimed\" : s === \"funded\" || s === \"awaiting_funds\" ? \"funded\" : typeof s === \"object\" && s && \"canceled\" in s ? \"canceled\" : \"open\";\n\n/* eslint-disable @typescript-eslint/no-explicit-any -- the SDK's own types are loose unions of JSON; this is where they are narrowed. */\nclass RealClient implements FedimintClient {\n constructor(readonly federationId: string, private wallet: any, private director: any, private end: () => void) {}\n async info() { return federationInfo(this.federationId, await this.wallet.federation.getConfig()); }\n balance(): Promise<number> { return this.wallet.balance.getBalance(); }\n onBalance(listener: (msats: number) => void) { return this.wallet.balance.subscribeBalance(listener, () => {}); }\n async parseNotes(notes: string): Promise<ParsedNotes> {\n const parsed = await this.director.parseOobNotes(notes);\n return { amountMsats: Number(parsed.total_amount), federationIdPrefix: String(parsed.federation_id_prefix), federationId: parsed.federation_id ?? undefined };\n }\n async spend(amountMsats: number, { cancelAfterSecs, ghostly }: { cancelAfterSecs: number; ghostly: string }) {\n const { notes, operation_id } = await this.wallet.mint.spendNotes(amountMsats, cancelAfterSecs, false, { ghostly });\n return { notes: String(notes), operationId: String(operation_id) };\n }\n cancelSpend(operationId: string): Promise<void> { return this.wallet.mint.tryCancelSpendNotes(operationId); }\n spendState(operationId: string, waitMs: number) {\n return watch<SpendState>((ok, fail) => this.wallet.mint.subscribeSpendNotes(operationId, (s: string) => ok(SPEND[s] ?? \"created\"), fail), spendFinal, waitMs);\n }\n redeem(notes: string, ghostly: string): Promise<string> { return this.wallet.mint.reissueExternalNotes(notes, { ghostly }); }\n redeemState(operationId: string, waitMs: number) {\n return watch<RedeemState>((ok, fail) => this.wallet.mint.subscribeReissueExternalNotes(operationId, (s: unknown) => ok(typeof s === \"string\" ? REDEEM[s] ?? \"created\" : s && typeof s === \"object\" && \"Failed\" in s ? \"failed\" : \"created\"), (error: string) => { ok(\"failed\"); fail(error); }), (s) => s === \"done\" || s === \"failed\", waitMs);\n }\n async createInvoice(amountMsats: number, description: string, expirySecs: number, ghostly: string) {\n const { operation_id, invoice } = await this.wallet.lightning.createInvoice(amountMsats, description, expirySecs, undefined, { ghostly });\n return { operationId: String(operation_id), invoice: String(invoice) };\n }\n receiveState(operationId: string, waitMs: number) {\n return watch<ReceiveState>((ok, fail) => this.wallet.lightning.subscribeLnReceive(operationId, (s: unknown) => ok(receiveOf(s)), fail), (s) => s === \"claimed\" || s === \"canceled\", waitMs);\n }\n async gatewayFee(amountMsats: number) {\n await this.wallet.lightning.updateGatewayCache().catch(() => {});\n const gateway = await this.wallet.lightning.getAvailableGateway().catch(() => null);\n const fees = gateway?.fees;\n if (!fees) return undefined;\n return Number(fees.base_msat ?? 0) + Math.ceil(amountMsats * Number(fees.proportional_millionths ?? 0) / 1_000_000);\n }\n async payInvoice(invoice: string, ghostly: string) {\n const paid = await this.wallet.lightning.payInvoice(invoice, undefined, { ghostly });\n const internal = \"internal\" in paid.payment_type;\n return { operationId: String(internal ? paid.payment_type.internal : paid.payment_type.lightning), feeMsats: Number(paid.fee ?? 0), internal };\n }\n async payState(operationId: string, internal: boolean, waitMs: number): Promise<PayOutcome> {\n const state = await watch<unknown>((ok, fail) => internal ? this.wallet.lightning.subscribeInternalPayment(operationId, ok, fail) : this.wallet.lightning.subscribeLnPay(operationId, ok, fail),\n (s) => typeof s === \"object\" && !!s && (\"success\" in s || \"preimage\" in s || \"refunded\" in s || \"refund_success\" in s || \"unexpected_error\" in s || \"funding_failed\" in s) || s === \"canceled\", waitMs);\n if (state && typeof state === \"object\") {\n const s = state as Record<string, any>;\n // A payment out through the gateway ends in `success`; one to the same federation (internal) in `preimage` (bytes).\n if (s.success?.preimage || s.preimage) return { state: \"paid\", preimage: hex(s.success?.preimage ?? s.preimage) };\n if (\"refunded\" in s || \"refund_success\" in s || \"funding_failed\" in s) return { state: \"failed\", error: errorText(s.refunded?.gateway_error ?? s.funding_failed?.error ?? \"The gateway gave the payment back\") };\n if (\"unexpected_error\" in s) return { state: \"pending\", error: errorText(s.unexpected_error) };\n }\n if (state === \"canceled\") return { state: \"failed\", error: \"Canceled before it was funded\" };\n return { state: \"pending\" };\n }\n async operations(limit: number): Promise<FedimintOperation[]> {\n const list: [{ creation_time?: { secs_since_epoch?: number }; operation_id?: string }, { operation_module_kind?: string; meta?: any; outcome?: any }][] = await this.wallet.federation.listOperations(limit);\n return list.map(([key, log]) => {\n const variant = log.meta?.variant && typeof log.meta.variant === \"object\" ? Object.keys(log.meta.variant)[0] : undefined;\n const detail = variant ? log.meta.variant[variant] : undefined;\n return {\n id: String(key.operation_id ?? \"\"), kind: String(log.operation_module_kind ?? \"\"), variant,\n createdAt: Number(key.creation_time?.secs_since_epoch ?? 0) * 1000,\n amountMsats: typeof log.meta?.amount === \"number\" ? log.meta.amount : typeof detail?.requested_amount === \"number\" ? detail.requested_amount : undefined,\n outcome: log.outcome?.outcome, ghostly: typeof log.meta?.extra_meta?.ghostly === \"string\" ? log.meta.extra_meta.ghostly : undefined,\n invoice: typeof detail?.invoice === \"string\" ? detail.invoice : undefined,\n feeMsats: typeof detail?.fee === \"number\" ? detail.fee : undefined,\n };\n });\n }\n async close() { try { await this.wallet.cleanup(); } catch { /* already gone */ } this.end(); }\n}\n\n/**\n * Where the client runs: a worker per database, and the files the databases are. The browser's is a module worker on\n * the origin-private file system (below); a headless Ghostly on Node brings its own (packages/cli/src/runtime/fedimint.ts).\n */\nexport interface FedimintPlatform {\n /** A `@fedimint/types` Transport onto a new worker of its own, which one database will be opened in. */\n transport(): Promise<{ terminate(): void }>;\n exists(database: string): Promise<boolean>;\n /** Deletes a database, and is done when there is none. */\n remove(database: string): Promise<void>;\n}\n\nconst BROWSER: FedimintPlatform = {\n transport: browserTransport,\n async exists(database) {\n const root = await navigator.storage.getDirectory();\n try { const file = await (await root.getFileHandle(database)).getFile(); return file.size > 0; } catch { return false; }\n },\n async remove(database) {\n const root = await navigator.storage.getDirectory();\n await root.removeEntry(database).catch((error: unknown) => { if ((error as { name?: string })?.name !== \"NotFoundError\") throw error; });\n },\n};\n\nlet loading: Promise<FedimintSdk> | undefined;\n/** Loads the SDK on first use: profiles that never join a federation never fetch its 11 MB. */\nexport function loadFedimintSdk(): Promise<FedimintSdk> {\n return loading ??= fedimintSdkOn(BROWSER).catch((error) => { loading = undefined; throw error; });\n}\n\n/** The SDK on a platform's workers and files. */\nexport async function fedimintSdkOn(platform: FedimintPlatform): Promise<FedimintSdk> {\n const { WalletDirector } = await import(\"@fedimint/core\");\n /** A director on its own worker and database, with the mnemonic in place. */\n const director = async (database: string, mnemonic?: string) => {\n const t = await platform.transport();\n const d = new WalletDirector(t as any, database, true);\n try {\n await d.initialize(database);\n if (mnemonic && !(await d.hasMnemonicSet())) await d.setMnemonic(mnemonic.trim().split(/\\s+/));\n return { director: d, end: () => t.terminate() };\n } catch (error) { t.terminate(); throw error; }\n };\n const client = async (database: string, mnemonic: string, start: (wallet: any) => Promise<void>) => {\n const { director: d, end } = await director(database, mnemonic);\n try {\n const wallet = await d.createWallet();\n await start(wallet);\n return new RealClient(await wallet.federation.getFederationId(), wallet, d, end);\n } catch (error) { end(); throw error; }\n };\n const sdk: FedimintSdk = {\n async preview(invite) {\n // A scratch database of its own: previewing joins nothing.\n const database = fedimintDatabase(`preview-${crypto.randomUUID()}`);\n const { director: d, end } = await director(database);\n try { const preview = await d.previewFederation(invite.trim()); return federationInfo(preview.federation_id, preview.config); }\n finally { end(); await sdk.remove(database).catch(() => {}); }\n },\n join: ({ database, mnemonic, invite, recover }) => client(database, mnemonic, async (wallet) => {\n // joinFederation says false instead of throwing; its reason goes to the (silenced) logger.\n if (!(await wallet.joinFederation(invite.trim(), { clientName: CLIENT_NAME, forceRecover: recover }))) throw new Error(\"Could not join the federation: its guardians did not answer, or the invite code is not valid\");\n }),\n open: ({ database, mnemonic }) => client(database, mnemonic, (wallet) => wallet.open(CLIENT_NAME)),\n exists: (database) => platform.exists(database),\n remove: (database) => platform.remove(database),\n };\n return sdk;\n}\n/* eslint-enable @typescript-eslint/no-explicit-any */\n","import { fromBase64Url, irohDescriptor, irohRelayUrl, type BoundChannel, type FrameChannel, type NativeBinding, type NativeEndpoint } from \"@ghostly/core\";\n\n/**\n * Iroh (WISP 102) in a browser: the same QUIC/TLS session and binding as the\n * Desktop's native Iroh, but relay only. A page cannot send UDP, so every\n * packet goes through an Iroh relay over a WebSocket; the relay sees who talks\n * to whom and when, never the frames.\n *\n * The wasm (about 1 MB gzip) loads on the first chat that starts an endpoint,\n * never on the app's first load.\n */\n\n/**\n * n0's public relays, the same ones the Desktop's Iroh homes on by default. Iroh itself names them with the trailing\n * dot of a full domain name (`use1-1.relay.n0.iroh.link.`), and compares relay URLs as text; here they have none,\n * because WebKit opens no host name that ends in a dot. `BROWSER_SPELLING` is what keeps the two one relay.\n */\nexport const DEFAULT_IROH_RELAYS = [\n \"https://use1-1.relay.n0.iroh.link/\",\n \"https://euc1-1.relay.n0.iroh.link/\",\n \"https://aps1-1.relay.n0.iroh.link/\",\n \"https://usw1-1.relay.n0.iroh.link/\",\n] as const;\n\n/**\n * The spelling of every relay URL handed to the wasm Iroh (`irohRelayUrl`): the relays it homes on and a contact's\n * relay before a dial. One spelling, so a contact on this endpoint's relay is reached over the connection already\n * open to it; without the dot, so Safari can open it (a Desktop's record names its relay with the dot).\n */\nconst BROWSER_SPELLING = \"plain\";\n\n/**\n * Why a relay URL cannot be used, or null. TLS, except on this machine (a local `iroh-relay --dev`): a page\n * may not open plain WebSockets elsewhere, and the relay's operator would see traffic metadata in the clear.\n */\nexport function irohRelayProblem(value: string): string | null {\n let url: URL;\n try { url = new URL(value); } catch { return `Not a relay address: ${value}`; }\n const local = [\"localhost\", \"127.0.0.1\", \"[::1]\"].includes(url.hostname);\n if (url.protocol !== \"https:\" && !(url.protocol === \"http:\" && local)) return `Use an https:// relay address: ${value}`;\n if (url.username || url.password || url.search || url.hash || value.length > 256) return `Not a relay address: ${value}`;\n return null;\n}\n\n/** A browser endpoint's descriptor: no direct addresses, only its relay. */\nexport interface IrohWebDescriptor { id: string; relay: string | null; addresses: string[] }\n\ninterface WasmConn {\n binding(): NativeBinding;\n send(text: string): Promise<void>;\n recv(): Promise<string | undefined>;\n rtt(): number;\n close(): void;\n free(): void;\n}\ninterface WasmNode {\n address(): IrohWebDescriptor;\n connect(descriptor: unknown, timeoutMs: number): Promise<WasmConn>;\n accept(): Promise<WasmConn | undefined>;\n close(): Promise<void>;\n free(): void;\n}\nexport interface IrohWasm {\n IrohNode: { start(seed: Uint8Array, relays: string[], onlineMs: number): Promise<WasmNode> };\n}\n\nlet loaded: Promise<IrohWasm> | null = null;\n/** Loads the wasm once per page. Tests pass their own `load` (Node reads the file). */\nexport function loadIrohWasm(): Promise<IrohWasm> {\n loaded ??= import(\"@ghostly/iroh-web\").then(async module => {\n await module.default();\n return module as unknown as IrohWasm;\n }).catch(error => { loaded = null; throw error; });\n return loaded;\n}\n\nexport interface IrohWebOptions {\n /** Relay URLs, first preferred. Default: n0's public relays. */\n relays?: readonly string[];\n /** How long to wait for the first relay to answer. */\n onlineMs?: number;\n connectMs?: number;\n load?: () => Promise<IrohWasm>;\n}\n\nconst SEND_BUDGET = 120 * 1024;\nconst MAX_FRAME = 60 * 1024;\n\n/** One paired channel over one QUIC stream. Sends are queued in order. */\nclass IrohWebChannel implements FrameChannel {\n private reader: FrameChannel[\"onMessage\"] = null;\n private buffered: string[] = [];\n private queue = Promise.resolve();\n private closed = false;\n bufferedAmount = 0;\n readonly sendBudget = SEND_BUDGET;\n onClose: FrameChannel[\"onClose\"] = null;\n constructor(private conn: WasmConn, private forget: () => void) { void this.read(); }\n get onMessage() { return this.reader; }\n set onMessage(reader: FrameChannel[\"onMessage\"]) {\n this.reader = reader;\n if (reader) for (const text of this.buffered.splice(0)) reader(text);\n }\n private async read() {\n try {\n for (;;) {\n const text = await this.conn.recv();\n if (text === undefined || this.closed) break;\n if (this.reader) this.reader(text);\n else if (this.buffered.length < 64) this.buffered.push(text);\n else break;\n }\n } catch { /* A broken stream ends the channel. */ }\n this.close();\n }\n send(data: string | Uint8Array) {\n if (typeof data !== \"string\" || this.closed || !data.length || data.length > MAX_FRAME || this.bufferedAmount + data.length > SEND_BUDGET)\n throw new Error(\"Iroh channel is closed or its send budget is full\");\n this.bufferedAmount += data.length;\n this.queue = this.queue.then(() => this.closed ? undefined : this.conn.send(data))\n .catch(() => this.close()).finally(() => { this.bufferedAmount -= data.length; });\n }\n drained() { return this.queue; }\n get rttMs() { const rtt = this.conn.rtt(); return rtt >= 0 ? rtt : undefined; }\n close() {\n if (this.closed) return;\n this.closed = true; this.buffered = [];\n try { this.conn.close(); } catch { /* Already gone. */ }\n this.forget();\n this.onClose?.();\n }\n}\n\n/** A NativeEndpoint for `iroh/1`, as the Desktop's is, for one chat's transport seed. */\nexport async function createIrohWebEndpoint(seedB64: string, options: IrohWebOptions = {}): Promise<NativeEndpoint> {\n const seed = fromBase64Url(seedB64);\n if (seed.length !== 32) throw new Error(\"Invalid transport seed\");\n // Either spelling of a relay (with or without the trailing dot) homes on it under the one a browser opens.\n const relays = [...new Set((options.relays?.length ? options.relays : DEFAULT_IROH_RELAYS).map(relay => irohRelayUrl(relay, BROWSER_SPELLING)))];\n const wasm = await (options.load ?? loadIrohWasm)();\n const node = await wasm.IrohNode.start(seed, relays, options.onlineMs ?? 10_000);\n const channels = new Set<IrohWebChannel>();\n let stopped = false;\n let handler: NativeEndpoint[\"onConnection\"] = null;\n const early: BoundChannel[] = [];\n const bind = (conn: WasmConn): BoundChannel => {\n const channel: IrohWebChannel = new IrohWebChannel(conn, () => channels.delete(channel));\n channels.add(channel);\n return { channel, binding: conn.binding() };\n };\n const endpoint: NativeEndpoint = {\n transport: \"iroh/1\",\n // Relay only: the rank puts it after every direct path (WISP 100, \"Relayed\").\n descriptor: { ...node.address(), relayed: true },\n onDescriptor: null,\n get onConnection() { return handler; },\n set onConnection(value) { handler = value; if (value) for (const bound of early.splice(0)) value(bound); },\n async connect(descriptor) {\n if (stopped) throw new Error(\"Iroh endpoint is stopped\");\n const conn = await node.connect(irohDescriptor(descriptor, BROWSER_SPELLING), options.connectMs ?? 20_000);\n if (stopped) { conn.close(); throw new Error(\"Iroh endpoint is stopped\"); }\n return bind(conn);\n },\n async close() {\n if (stopped) return;\n stopped = true;\n for (const channel of [...channels]) channel.close();\n for (const bound of early.splice(0)) bound.channel.close();\n await node.close().catch(() => {});\n },\n };\n void (async () => {\n while (!stopped) {\n let conn: WasmConn | undefined;\n try { conn = await node.accept(); } catch { break; }\n if (!conn) break;\n if (stopped) { conn.close(); break; }\n const bound = bind(conn);\n const accept = endpoint.onConnection;\n if (accept) accept(bound); else if (early.length < 4) early.push(bound); else bound.channel.close();\n }\n // The relay went away for good (or the endpoint was closed): stop offering Iroh.\n if (!stopped) { endpoint.onUnavailable?.(); await endpoint.close(); }\n })();\n return endpoint;\n}\n"],"mappings":";;AA6FA,IAAa,oBAAoB,OAAe,oBAAoB,GAAG;AAKvE,IAAM,oBAAoB,OAAO;;AAEjC,IAAM,cAAc;AACpB,IAAM,WAA2C;CAAE,SAAS;CAAW,QAAQ;CAAU,SAAS;CAAW,UAAU;CAAW,SAAS;CAAW,WAAW;AAAY;;;;;;AAM7K,IAAM,QAAoC;CAAC,CAAC,cAAc,SAAS;CAAG,CAAC,cAAc,SAAS;CAAG,CAAC,cAAc,SAAS;CAAG,CAAC,cAAc,QAAQ;CAAG,CAAC,cAAc,WAAW;CAAG,CAAC,cAAc,SAAS;AAAC;AAC5M,SAAS,cAAc,QAA2G;CAChI,IAAI,OAAO,OAAO,YAAY,UAAU,OAAO,SAAS,OAAO;CAC/D,MAAM,UAAU,OAAO,OAAO,WAAW,WAAW,OAAO,SAAS,OAAO;CAC3E,IAAI,OAAO,YAAY,YAAY,CAAC,eAAe,KAAK,OAAO,GAAG,OAAO,KAAA;CACzE,MAAM,MAAM,QAAQ,YAAY;CAChC,MAAM,QAAQ,CAAC,GAAG,IAAI,IAAI,MAAM,QAAQ,CAAC,WAAW,IAAI,SAAS,KAAK,CAAC,CAAC,CAAC,KAAK,GAAG,aAAa,OAAO,CAAC,CAAC;CACvG,OAAO,MAAM,WAAW,IAAI,MAAM,KAAK,KAAA;AACzC;AACA,IAAM,OAAO,UAAmB,OAAO,UAAU,WAAW,QAAQ,MAAM,QAAQ,KAAK,IAAI,MAAM,KAAK,MAAM,OAAO,CAAC,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC,SAAS,GAAG,GAAG,CAAC,CAAC,CAAC,KAAK,EAAE,IAAI,KAAA;AAEhK,IAAI;;AAEJ,SAAS,WAAwC;CAC/C,OAAO,eAAe,YAAY;EAChC,MAAM,OAAO,MAAM,YAAY,EAAA,CAAG;EAClC,MAAM,WAAW,MAAM,MAAM,GAAG;EAEhC,IAAI;GAAE,OAAO,MAAM,YAAY,iBAAiB,SAAS,MAAM,CAAC;EAAG,QAAQ;GAAE,OAAO,YAAY,QAAQ,MAAM,SAAS,YAAY,CAAC;EAAG;CACzI,EAAA,CAAG,CAAC,CAAC,OAAO,UAAU;EAAE,YAAY,KAAA;EAAW,MAAM;CAAO,CAAC;AAC/D;;AAGA,eAAe,mBAAmB;CAChC,MAAM,CAAC,EAAE,aAAa,UAAU,MAAM,QAAQ,IAAI,CAAC,OAAO,oBAAoB,SAAS,CAAC,CAAC;CACzF,MAAM,wBAAwB,UAAU;EACtC,SAAkB;GAAE,QAAQ,CAAC;GAAG,OAAO,CAAC;GAAG,OAAO,CAAC;GAAG,QAAQ,CAAC;EAAE;EACjE,SAA0B,IAAI,OAAO,IAAI,IAAI,uBAAuB,YAAY,GAAG,GAAG,EAAE,MAAM,SAAS,CAAC;EACxG,cAAc;GACZ,MAAM;GACN,KAAK,OAAO,aAAa,UAAU,KAAK,eAAe,MAAM,IAAI;GACjE,KAAK,OAAO,WAAW,UAAU;IAAE,MAAM,eAAe;IAAG,KAAK,eAAe;KAAE,MAAM;KAAS,OAAO,gCAAgC,MAAM,WAAW;IAAkB,CAAC;GAAG;EAChL;EACA,YAAY,SAA+D;GAEzE,KAAK,OAAO,YAAY,QAAQ,SAAS,SAAS;IAAE,GAAG;IAAS,SAAS;KAAE,GAAI,QAAQ;KAAoB;IAAO;GAAE,IAAI,OAAO;EACjI;EACA,YAAY;GAAE,KAAK,OAAO,UAAU;EAAG;CACzC;CACA,OAAO,IAAI,gBAAgB;AAC7B;AAEA,IAAM,QAAQ,OAAgB,MAAM,QAAQ,OAAO,UAAU,WAAW,MAAM,MAAM,GAAG,GAAG,IAAI,KAAA;AAC9F,IAAM,aAAa,UAAmB,iBAAiB,QAAQ,MAAM,UAAU,OAAO,UAAU,WAAW,QAAQ,KAAK,UAAU,KAAK;AAEvI,SAAgB,eAAe,cAAsB,QAAiC;CACpF,MAAM,IAAI;CACV,MAAM,YAAY,OAAO,QAAQ,GAAG,QAAQ,iBAAiB,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,OAAO,CAAC,IAAI,OAAO,CAAC,CAAC;CACzG,MAAM,UAAU,OAAO,OAAO,GAAG,WAAW,CAAC,CAAC;CAE9C,MAAM,UAAU,QAAQ,QAAQ,MAAM,GAAG,SAAS,QAAQ,CAAC,CAAC,IAAI,aAAa,CAAC,CAAC,KAAK,OAAO,KAAK,QAAQ,QAAQ,MAAM,GAAG,SAAS,IAAI,CAAC,CAAC,IAAI,aAAa,CAAC,CAAC,KAAK,OAAO;CACvK,MAAM,UAAU,GAAG,QAAQ;CAC3B,OAAO;EACL;EACA,MAAM,KAAK,GAAG,QAAQ,MAAM,iBAAiB,EAAE;EAC/C,WAAW,UAAU,MAAM,GAAG,EAAE,CAAC,CAAC,KAAK,CAAC,MAAM,QAAQ;GAAE,MAAM,KAAK,GAAG,MAAM,EAAE,KAAK,YAAY;GAAQ,KAAK,KAAK,GAAG,KAAK,GAAG,KAAK;EAAG,EAAE;EACtI,kBAAkB,GAAG,OAAO,SAAS,SAAS,CAAC,EAAE,GAAG,OAAO,SAAS,SAAS,CAAC;EAC9E;EACA,SAAS,CAAC,GAAG,IAAI,IAAI,QAAQ,KAAK,MAAO,OAAO,GAAG,SAAS,WAAW,EAAE,KAAK,MAAM,GAAG,EAAE,IAAI,EAAG,CAAC,CAAC,OAAO,OAAO,CAAC,CAAC;EAClH,SAAS,KAAK,GAAG,QAAQ,MAAM,iBAAiB,GAAG;CACrD;AACF;;AAGA,SAAS,MAAS,WAA0F,OAA8B,QAAwC;CAChL,OAAO,IAAI,SAAS,YAAY;EAC9B,IAAI,MAAqB,OAAO,OAAO,eAA2B,CAAC;EACnE,MAAM,eAAe;GAAE,IAAI,MAAM;GAAQ,OAAO;GAAM,aAAa,KAAK;GAAG,OAAO;GAAG,QAAQ,IAAI;EAAG;EACpG,MAAM,QAAQ,WAAW,QAAQ,MAAM;EACvC,SAAS,WAAW,UAAU;GAAE,OAAO;GAAO,IAAI,MAAM,KAAK,GAAG,OAAO;EAAG,SAAS,OAAO,CAAC;CAC7F,CAAC;AACH;AAEA,IAAM,QAAoC;CAAE,SAAS;CAAW,wBAAwB;CAAa,qBAAqB;CAAY,qBAAqB;CAAS,SAAS;CAAS,UAAU;AAAW;AAC3M,IAAM,cAAc,MAAkB,MAAM,cAAc,MAAM,WAAW,MAAM;AACjF,IAAM,SAAsC;CAAE,SAAS;CAAW,SAAS;CAAW,MAAM;AAAO;AACnG,IAAM,aAAa,MAA6B,MAAM,YAAY,YAAY,MAAM,YAAY,MAAM,mBAAmB,WAAW,OAAO,MAAM,YAAY,KAAK,cAAc,IAAI,aAAa;AAGjM,IAAM,aAAN,MAA2C;CACpB;CAA8B;CAAqB;CAAuB;CAA/F,YAAY,cAA+B,QAAqB,UAAuB,KAAyB;EAA3F,KAAA,eAAA;EAA8B,KAAA,SAAA;EAAqB,KAAA,WAAA;EAAuB,KAAA,MAAA;CAAkB;CACjH,MAAM,OAAO;EAAE,OAAO,eAAe,KAAK,cAAc,MAAM,KAAK,OAAO,WAAW,UAAU,CAAC;CAAG;CACnG,UAA2B;EAAE,OAAO,KAAK,OAAO,QAAQ,WAAW;CAAG;CACtE,UAAU,UAAmC;EAAE,OAAO,KAAK,OAAO,QAAQ,iBAAiB,gBAAgB,CAAC,CAAC;CAAG;CAChH,MAAM,WAAW,OAAqC;EACpD,MAAM,SAAS,MAAM,KAAK,SAAS,cAAc,KAAK;EACtD,OAAO;GAAE,aAAa,OAAO,OAAO,YAAY;GAAG,oBAAoB,OAAO,OAAO,oBAAoB;GAAG,cAAc,OAAO,iBAAiB,KAAA;EAAU;CAC9J;CACA,MAAM,MAAM,aAAqB,EAAE,iBAAiB,WAAyD;EAC3G,MAAM,EAAE,OAAO,iBAAiB,MAAM,KAAK,OAAO,KAAK,WAAW,aAAa,iBAAiB,OAAO,EAAE,QAAQ,CAAC;EAClH,OAAO;GAAE,OAAO,OAAO,KAAK;GAAG,aAAa,OAAO,YAAY;EAAE;CACnE;CACA,YAAY,aAAoC;EAAE,OAAO,KAAK,OAAO,KAAK,oBAAoB,WAAW;CAAG;CAC5G,WAAW,aAAqB,QAAgB;EAC9C,OAAO,OAAmB,IAAI,SAAS,KAAK,OAAO,KAAK,oBAAoB,cAAc,MAAc,GAAG,MAAM,MAAM,SAAS,GAAG,IAAI,GAAG,YAAY,MAAM;CAC9J;CACA,OAAO,OAAe,SAAkC;EAAE,OAAO,KAAK,OAAO,KAAK,qBAAqB,OAAO,EAAE,QAAQ,CAAC;CAAG;CAC5H,YAAY,aAAqB,QAAgB;EAC/C,OAAO,OAAoB,IAAI,SAAS,KAAK,OAAO,KAAK,8BAA8B,cAAc,MAAe,GAAG,OAAO,MAAM,WAAW,OAAO,MAAM,YAAY,KAAK,OAAO,MAAM,YAAY,YAAY,IAAI,WAAW,SAAS,IAAI,UAAkB;GAAE,GAAG,QAAQ;GAAG,KAAK,KAAK;EAAG,CAAC,IAAI,MAAM,MAAM,UAAU,MAAM,UAAU,MAAM;CAChV;CACA,MAAM,cAAc,aAAqB,aAAqB,YAAoB,SAAiB;EACjG,MAAM,EAAE,cAAc,YAAY,MAAM,KAAK,OAAO,UAAU,cAAc,aAAa,aAAa,YAAY,KAAA,GAAW,EAAE,QAAQ,CAAC;EACxI,OAAO;GAAE,aAAa,OAAO,YAAY;GAAG,SAAS,OAAO,OAAO;EAAE;CACvE;CACA,aAAa,aAAqB,QAAgB;EAChD,OAAO,OAAqB,IAAI,SAAS,KAAK,OAAO,UAAU,mBAAmB,cAAc,MAAe,GAAG,UAAU,CAAC,CAAC,GAAG,IAAI,IAAI,MAAM,MAAM,aAAa,MAAM,YAAY,MAAM;CAC5L;CACA,MAAM,WAAW,aAAqB;EACpC,MAAM,KAAK,OAAO,UAAU,mBAAmB,CAAC,CAAC,YAAY,CAAC,CAAC;EAE/D,MAAM,QAAO,MADS,KAAK,OAAO,UAAU,oBAAoB,CAAC,CAAC,YAAY,IAAI,EAAA,EAC5D;EACtB,IAAI,CAAC,MAAM,OAAO,KAAA;EAClB,OAAO,OAAO,KAAK,aAAa,CAAC,IAAI,KAAK,KAAK,cAAc,OAAO,KAAK,2BAA2B,CAAC,IAAI,GAAS;CACpH;CACA,MAAM,WAAW,SAAiB,SAAiB;EACjD,MAAM,OAAO,MAAM,KAAK,OAAO,UAAU,WAAW,SAAS,KAAA,GAAW,EAAE,QAAQ,CAAC;EACnF,MAAM,WAAW,cAAc,KAAK;EACpC,OAAO;GAAE,aAAa,OAAO,WAAW,KAAK,aAAa,WAAW,KAAK,aAAa,SAAS;GAAG,UAAU,OAAO,KAAK,OAAO,CAAC;GAAG;EAAS;CAC/I;CACA,MAAM,SAAS,aAAqB,UAAmB,QAAqC;EAC1F,MAAM,QAAQ,MAAM,OAAgB,IAAI,SAAS,WAAW,KAAK,OAAO,UAAU,yBAAyB,aAAa,IAAI,IAAI,IAAI,KAAK,OAAO,UAAU,eAAe,aAAa,IAAI,IAAI,IAC3L,MAAM,OAAO,MAAM,YAAY,CAAC,CAAC,MAAM,aAAa,KAAK,cAAc,KAAK,cAAc,KAAK,oBAAoB,KAAK,sBAAsB,KAAK,oBAAoB,MAAM,MAAM,YAAY,MAAM;EACxM,IAAI,SAAS,OAAO,UAAU,UAAU;GACtC,MAAM,IAAI;GAEV,IAAI,EAAE,SAAS,YAAY,EAAE,UAAU,OAAO;IAAE,OAAO;IAAQ,UAAU,IAAI,EAAE,SAAS,YAAY,EAAE,QAAQ;GAAE;GAChH,IAAI,cAAc,KAAK,oBAAoB,KAAK,oBAAoB,GAAG,OAAO;IAAE,OAAO;IAAU,OAAO,UAAU,EAAE,UAAU,iBAAiB,EAAE,gBAAgB,SAAS,mCAAmC;GAAE;GAC/M,IAAI,sBAAsB,GAAG,OAAO;IAAE,OAAO;IAAW,OAAO,UAAU,EAAE,gBAAgB;GAAE;EAC/F;EACA,IAAI,UAAU,YAAY,OAAO;GAAE,OAAO;GAAU,OAAO;EAAgC;EAC3F,OAAO,EAAE,OAAO,UAAU;CAC5B;CACA,MAAM,WAAW,OAA6C;EAE5D,QAAO,MADyJ,KAAK,OAAO,WAAW,eAAe,KAAK,EAAA,CAC/L,KAAK,CAAC,KAAK,SAAS;GAC9B,MAAM,UAAU,IAAI,MAAM,WAAW,OAAO,IAAI,KAAK,YAAY,WAAW,OAAO,KAAK,IAAI,KAAK,OAAO,CAAC,CAAC,KAAK,KAAA;GAC/G,MAAM,SAAS,UAAU,IAAI,KAAK,QAAQ,WAAW,KAAA;GACrD,OAAO;IACL,IAAI,OAAO,IAAI,gBAAgB,EAAE;IAAG,MAAM,OAAO,IAAI,yBAAyB,EAAE;IAAG;IACnF,WAAW,OAAO,IAAI,eAAe,oBAAoB,CAAC,IAAI;IAC9D,aAAa,OAAO,IAAI,MAAM,WAAW,WAAW,IAAI,KAAK,SAAS,OAAO,QAAQ,qBAAqB,WAAW,OAAO,mBAAmB,KAAA;IAC/I,SAAS,IAAI,SAAS;IAAS,SAAS,OAAO,IAAI,MAAM,YAAY,YAAY,WAAW,IAAI,KAAK,WAAW,UAAU,KAAA;IAC1H,SAAS,OAAO,QAAQ,YAAY,WAAW,OAAO,UAAU,KAAA;IAChE,UAAU,OAAO,QAAQ,QAAQ,WAAW,OAAO,MAAM,KAAA;GAC3D;EACF,CAAC;CACH;CACA,MAAM,QAAQ;EAAE,IAAI;GAAE,MAAM,KAAK,OAAO,QAAQ;EAAG,QAAQ,CAAqB;EAAE,KAAK,IAAI;CAAG;AAChG;AAcA,IAAM,UAA4B;CAChC,WAAW;CACX,MAAM,OAAO,UAAU;EACrB,MAAM,OAAO,MAAM,UAAU,QAAQ,aAAa;EAClD,IAAI;GAAqE,QAAO,OAAtD,MAAM,KAAK,cAAc,QAAQ,EAAA,CAAG,QAAQ,EAAA,CAAe,OAAO;EAAG,QAAQ;GAAE,OAAO;EAAO;CACzH;CACA,MAAM,OAAO,UAAU;EAErB,OAAM,MADa,UAAU,QAAQ,aAAa,EAAA,CACvC,YAAY,QAAQ,CAAC,CAAC,OAAO,UAAmB;GAAE,IAAK,OAA6B,SAAS,iBAAiB,MAAM;EAAO,CAAC;CACzI;AACF;AAEA,IAAI;;AAEJ,SAAgB,kBAAwC;CACtD,OAAO,YAAY,cAAc,OAAO,CAAC,CAAC,OAAO,UAAU;EAAE,UAAU,KAAA;EAAW,MAAM;CAAO,CAAC;AAClG;;AAGA,eAAsB,cAAc,UAAkD;CACpF,MAAM,EAAE,mBAAmB,MAAM,OAAO;;CAExC,MAAM,WAAW,OAAO,UAAkB,aAAsB;EAC9D,MAAM,IAAI,MAAM,SAAS,UAAU;EACnC,MAAM,IAAI,IAAI,eAAe,GAAU,UAAU,IAAI;EACrD,IAAI;GACF,MAAM,EAAE,WAAW,QAAQ;GAC3B,IAAI,YAAY,CAAE,MAAM,EAAE,eAAe,GAAI,MAAM,EAAE,YAAY,SAAS,KAAK,CAAC,CAAC,MAAM,KAAK,CAAC;GAC7F,OAAO;IAAE,UAAU;IAAG,WAAW,EAAE,UAAU;GAAE;EACjD,SAAS,OAAO;GAAE,EAAE,UAAU;GAAG,MAAM;EAAO;CAChD;CACA,MAAM,SAAS,OAAO,UAAkB,UAAkB,UAA0C;EAClG,MAAM,EAAE,UAAU,GAAG,QAAQ,MAAM,SAAS,UAAU,QAAQ;EAC9D,IAAI;GACF,MAAM,SAAS,MAAM,EAAE,aAAa;GACpC,MAAM,MAAM,MAAM;GAClB,OAAO,IAAI,WAAW,MAAM,OAAO,WAAW,gBAAgB,GAAG,QAAQ,GAAG,GAAG;EACjF,SAAS,OAAO;GAAE,IAAI;GAAG,MAAM;EAAO;CACxC;CACA,MAAM,MAAmB;EACvB,MAAM,QAAQ,QAAQ;GAEpB,MAAM,WAAW,iBAAiB,WAAW,OAAO,WAAW,GAAG;GAClE,MAAM,EAAE,UAAU,GAAG,QAAQ,MAAM,SAAS,QAAQ;GACpD,IAAI;IAAE,MAAM,UAAU,MAAM,EAAE,kBAAkB,OAAO,KAAK,CAAC;IAAG,OAAO,eAAe,QAAQ,eAAe,QAAQ,MAAM;GAAG,UACtH;IAAE,IAAI;IAAG,MAAM,IAAI,OAAO,QAAQ,CAAC,CAAC,YAAY,CAAC,CAAC;GAAG;EAC/D;EACA,OAAO,EAAE,UAAU,UAAU,QAAQ,cAAc,OAAO,UAAU,UAAU,OAAO,WAAW;GAE9F,IAAI,CAAE,MAAM,OAAO,eAAe,OAAO,KAAK,GAAG;IAAE,YAAY;IAAa,cAAc;GAAQ,CAAC,GAAI,MAAM,IAAI,MAAM,8FAA8F;EACvN,CAAC;EACD,OAAO,EAAE,UAAU,eAAe,OAAO,UAAU,WAAW,WAAW,OAAO,KAAK,WAAW,CAAC;EACjG,SAAS,aAAa,SAAS,OAAO,QAAQ;EAC9C,SAAS,aAAa,SAAS,OAAO,QAAQ;CAChD;CACA,OAAO;AACT;;;;;;;;;;;;;;;;;ACpTA,IAAa,sBAAsB;CACjC;CACA;CACA;CACA;AACF;;;;;;AAOA,IAAM,mBAAmB;;;;;AAMzB,SAAgB,iBAAiB,OAA8B;CAC7D,IAAI;CACJ,IAAI;EAAE,MAAM,IAAI,IAAI,KAAK;CAAG,QAAQ;EAAE,OAAO,wBAAwB;CAAS;CAC9E,MAAM,QAAQ;EAAC;EAAa;EAAa;CAAO,CAAC,CAAC,SAAS,IAAI,QAAQ;CACvE,IAAI,IAAI,aAAa,YAAY,EAAE,IAAI,aAAa,WAAW,QAAQ,OAAO,kCAAkC;CAChH,IAAI,IAAI,YAAY,IAAI,YAAY,IAAI,UAAU,IAAI,QAAQ,MAAM,SAAS,KAAK,OAAO,wBAAwB;CACjH,OAAO;AACT;AAwBA,IAAI,SAAmC;;AAEvC,SAAgB,eAAkC;CAChD,WAAW,OAAO,iCAAoB,CAAC,KAAK,OAAM,WAAU;EAC1D,MAAM,OAAO,QAAQ;EACrB,OAAO;CACT,CAAC,CAAC,CAAC,OAAM,UAAS;EAAE,SAAS;EAAM,MAAM;CAAO,CAAC;CACjD,OAAO;AACT;AAWA,IAAM,cAAc;AACpB,IAAM,YAAY;;AAGlB,IAAM,iBAAN,MAA6C;CAQvB;CAAwB;CAP5C,SAA4C;CAC5C,WAA6B,CAAC;CAC9B,QAAgB,QAAQ,QAAQ;CAChC,SAAiB;CACjB,iBAAiB;CACjB,aAAsB;CACtB,UAAmC;CACnC,YAAY,MAAwB,QAA4B;EAA5C,KAAA,OAAA;EAAwB,KAAA,SAAA;EAAsB,KAAU,KAAK;CAAG;CACpF,IAAI,YAAY;EAAE,OAAO,KAAK;CAAQ;CACtC,IAAI,UAAU,QAAmC;EAC/C,KAAK,SAAS;EACd,IAAI,QAAQ,KAAK,MAAM,QAAQ,KAAK,SAAS,OAAO,CAAC,GAAG,OAAO,IAAI;CACrE;CACA,MAAc,OAAO;EACnB,IAAI;GACF,SAAS;IACP,MAAM,OAAO,MAAM,KAAK,KAAK,KAAK;IAClC,IAAI,SAAS,KAAA,KAAa,KAAK,QAAQ;IACvC,IAAI,KAAK,QAAQ,KAAK,OAAO,IAAI;SAC5B,IAAI,KAAK,SAAS,SAAS,IAAI,KAAK,SAAS,KAAK,IAAI;SACtD;GACP;EACF,QAAQ,CAA0C;EAClD,KAAK,MAAM;CACb;CACA,KAAK,MAA2B;EAC9B,IAAI,OAAO,SAAS,YAAY,KAAK,UAAU,CAAC,KAAK,UAAU,KAAK,SAAS,aAAa,KAAK,iBAAiB,KAAK,SAAS,aAC5H,MAAM,IAAI,MAAM,mDAAmD;EACrE,KAAK,kBAAkB,KAAK;EAC5B,KAAK,QAAQ,KAAK,MAAM,WAAW,KAAK,SAAS,KAAA,IAAY,KAAK,KAAK,KAAK,IAAI,CAAC,CAAC,CAC/E,YAAY,KAAK,MAAM,CAAC,CAAC,CAAC,cAAc;GAAE,KAAK,kBAAkB,KAAK;EAAQ,CAAC;CACpF;CACA,UAAU;EAAE,OAAO,KAAK;CAAO;CAC/B,IAAI,QAAQ;EAAE,MAAM,MAAM,KAAK,KAAK,IAAI;EAAG,OAAO,OAAO,IAAI,MAAM,KAAA;CAAW;CAC9E,QAAQ;EACN,IAAI,KAAK,QAAQ;EACjB,KAAK,SAAS;EAAM,KAAK,WAAW,CAAC;EACrC,IAAI;GAAE,KAAK,KAAK,MAAM;EAAG,QAAQ,CAAsB;EACvD,KAAK,OAAO;EACZ,KAAK,UAAU;CACjB;AACF;;AAGA,eAAsB,sBAAsB,SAAiB,UAA0B,CAAC,GAA4B;CAClH,MAAM,OAAO,cAAc,OAAO;CAClC,IAAI,KAAK,WAAW,IAAI,MAAM,IAAI,MAAM,wBAAwB;CAEhE,MAAM,SAAS,CAAC,GAAG,IAAI,KAAK,QAAQ,QAAQ,SAAS,QAAQ,SAAS,oBAAA,CAAqB,KAAI,UAAS,aAAa,OAAO,gBAAgB,CAAC,CAAC,CAAC;CAE/I,MAAM,OAAO,OAAM,OADC,QAAQ,QAAQ,aAAA,CAAc,EAAA,CAC1B,SAAS,MAAM,MAAM,QAAQ,QAAQ,YAAY,GAAM;CAC/E,MAAM,2BAAW,IAAI,IAAoB;CACzC,IAAI,UAAU;CACd,IAAI,UAA0C;CAC9C,MAAM,QAAwB,CAAC;CAC/B,MAAM,QAAQ,SAAiC;EAC7C,MAAM,UAA0B,IAAI,eAAe,YAAY,SAAS,OAAO,OAAO,CAAC;EACvF,SAAS,IAAI,OAAO;EACpB,OAAO;GAAE;GAAS,SAAS,KAAK,QAAQ;EAAE;CAC5C;CACA,MAAM,WAA2B;EAC/B,WAAW;EAEX,YAAY;GAAE,GAAG,KAAK,QAAQ;GAAG,SAAS;EAAK;EAC/C,cAAc;EACd,IAAI,eAAe;GAAE,OAAO;EAAS;EACrC,IAAI,aAAa,OAAO;GAAE,UAAU;GAAO,IAAI,OAAO,KAAK,MAAM,SAAS,MAAM,OAAO,CAAC,GAAG,MAAM,KAAK;EAAG;EACzG,MAAM,QAAQ,YAAY;GACxB,IAAI,SAAS,MAAM,IAAI,MAAM,0BAA0B;GACvD,MAAM,OAAO,MAAM,KAAK,QAAQ,eAAe,YAAY,gBAAgB,GAAG,QAAQ,aAAa,GAAM;GACzG,IAAI,SAAS;IAAE,KAAK,MAAM;IAAG,MAAM,IAAI,MAAM,0BAA0B;GAAG;GAC1E,OAAO,KAAK,IAAI;EAClB;EACA,MAAM,QAAQ;GACZ,IAAI,SAAS;GACb,UAAU;GACV,KAAK,MAAM,WAAW,CAAC,GAAG,QAAQ,GAAG,QAAQ,MAAM;GACnD,KAAK,MAAM,SAAS,MAAM,OAAO,CAAC,GAAG,MAAM,QAAQ,MAAM;GACzD,MAAM,KAAK,MAAM,CAAC,CAAC,YAAY,CAAC,CAAC;EACnC;CACF;CACA,CAAM,YAAY;EAChB,OAAO,CAAC,SAAS;GACf,IAAI;GACJ,IAAI;IAAE,OAAO,MAAM,KAAK,OAAO;GAAG,QAAQ;IAAE;GAAO;GACnD,IAAI,CAAC,MAAM;GACX,IAAI,SAAS;IAAE,KAAK,MAAM;IAAG;GAAO;GACpC,MAAM,QAAQ,KAAK,IAAI;GACvB,MAAM,SAAS,SAAS;GACxB,IAAI,QAAQ,OAAO,KAAK;QAAQ,IAAI,MAAM,SAAS,GAAG,MAAM,KAAK,KAAK;QAAQ,MAAM,QAAQ,MAAM;EACpG;EAEA,IAAI,CAAC,SAAS;GAAE,SAAS,gBAAgB;GAAG,MAAM,SAAS,MAAM;EAAG;CACtE,EAAA,CAAG;CACH,OAAO;AACT"}
|
|
1
|
+
{"version":3,"file":"irohWeb-DHCxSvHF.js","names":[],"sources":["../../../browser/src/engine/paymentAdapters/fedimintSdk.ts","../../../browser/src/platform/irohWeb.ts"],"sourcesContent":["import type { BitcoinNetwork } from \"@ghostly/core\";\n\n/**\n * The part of the Fedimint web SDK (`@fedimint/core`, WebAssembly client in a worker) Ghostly uses, behind an\n * interface small enough to fake in tests. The client runs in a worker, on a file it reads and writes through a sync\n * access handle: in a browser, a module worker and the origin-private file system; on Node, the CLI's\n * `worker_threads` worker and a file of the profile (`FedimintPlatform`).\n *\n * One client database per joined federation (`ghostly-fedimint-<id>.db`, one file): the SDK's services are bound to one client per database, and each database is held by one worker.\n * Every database of a profile and mode is given the same mnemonic; the client derives a different secret per\n * federation from it. Amounts are millisatoshis, as the SDK counts them.\n */\nexport interface FederationGuardian { name: string; url: string }\nexport interface FederationInfo {\n federationId: string;\n /** `meta.federation_name`, when the guardians set one. */\n name?: string;\n guardians: FederationGuardian[];\n /** Core consensus version, `major.minor`. */\n consensusVersion: string;\n /** The Bitcoin network of its wallet module. Unknown when the federation has no v1 wallet module. */\n network?: BitcoinNetwork;\n /** Module kinds (`mint`, `ln`, `wallet`, `meta`…). The SDK uses `mint`, `ln` and `wallet` (v1). */\n modules: string[];\n /** `meta.welcome_message`, shortened. */\n welcome?: string;\n}\n\n/** Out-of-band notes, parsed offline (signatures and prefix only: whether they are spent is the federation's to say). */\nexport interface ParsedNotes { amountMsats: number; federationIdPrefix: string; federationId?: string }\n\n/** Where a spend of notes stands. `canceled` = taken back (reissued to us); `taken` = the recipient redeemed them. */\nexport type SpendState = \"created\" | \"canceling\" | \"canceled\" | \"taken\" | \"refunded\";\n/** A redeem (reissue) of notes we were given. */\nexport type RedeemState = \"created\" | \"issuing\" | \"done\" | \"failed\";\nexport type ReceiveState = \"open\" | \"funded\" | \"claimed\" | \"canceled\";\nexport interface PayOutcome { state: \"pending\" | \"paid\" | \"failed\"; preimage?: string; error?: string }\n\nexport interface FedimintOperation {\n id: string;\n /** Module: `mint`, `ln`, `wallet`. */\n kind: string;\n /** What it was: `spend_oob`, `reissuance`, `receive`, `pay`… as the SDK names it. */\n variant?: string;\n createdAt: number;\n amountMsats?: number;\n /** The SDK's final outcome, once there is one. */\n outcome?: unknown;\n /** What Ghostly asked to be kept with it (`extra_meta`): the payment id it belongs to. */\n ghostly?: string;\n /** Lightning: the invoice it paid or was paid on, and the fee it paid (msats). */\n invoice?: string;\n feeMsats?: number;\n}\n\nexport interface FedimintClient {\n readonly federationId: string;\n info(): Promise<FederationInfo>;\n balance(): Promise<number>;\n /** Called with every new balance, until the returned function is called. */\n onBalance(listener: (msats: number) => void): () => void;\n parseNotes(notes: string): Promise<ParsedNotes>;\n /** Takes notes out of the wallet, as a bearer string. Unredeemed after `cancelAfterSecs`, they come back by themselves. */\n spend(amountMsats: number, options: { cancelAfterSecs: number; ghostly: string }): Promise<{ notes: string; operationId: string }>;\n /** Asks to take notes back: they are reissued to us unless the recipient redeemed them first. */\n cancelSpend(operationId: string): Promise<void>;\n /** The latest state within `waitMs` (it returns early on a final one). Undefined: the client knows no such operation. */\n spendState(operationId: string, waitMs: number): Promise<SpendState | undefined>;\n redeem(notes: string, ghostly: string): Promise<string>;\n redeemState(operationId: string, waitMs: number): Promise<RedeemState | undefined>;\n createInvoice(amountMsats: number, description: string, expirySecs: number, ghostly: string): Promise<{ operationId: string; invoice: string }>;\n receiveState(operationId: string, waitMs: number): Promise<ReceiveState | undefined>;\n /** What the federation's gateway would charge to pay this amount, in msats. Undefined: no gateway. */\n gatewayFee(amountMsats: number): Promise<number | undefined>;\n payInvoice(invoice: string, ghostly: string): Promise<{ operationId: string; feeMsats: number; internal: boolean }>;\n payState(operationId: string, internal: boolean, waitMs: number): Promise<PayOutcome>;\n operations(limit: number): Promise<FedimintOperation[]>;\n /** Ends the worker; the database stays. */\n close(): Promise<void>;\n}\n\nexport interface FedimintSdk {\n preview(invite: string): Promise<FederationInfo>;\n /** Joins (or, `recover`, restores from the federation's backup of this mnemonic) into a new database. */\n join(params: { database: string; mnemonic: string; invite: string; recover: boolean }): Promise<FedimintClient>;\n /** Opens a database joined before. */\n open(params: { database: string; mnemonic: string }): Promise<FedimintClient>;\n /** Deletes a database: only one that never held money (a failed join). */\n remove(database: string): Promise<void>;\n /** Whether this device has the database (a profile restored from a backup has the records, not the files). */\n exists(database: string): Promise<boolean>;\n}\n\nexport const fedimintDatabase = (id: string) => `ghostly-fedimint-${id}.db`;\n\n// ── The real SDK ───────────────────────────────────────────────────────────────────────────────────────────\n\ntype Json = null | boolean | number | string | Json[] | { [key: string]: Json };\nconst MODULE_WASM = () => import(\"@fedimint/fedimint-client-wasm-bundler/fedimint_client_wasm_bg.wasm?url\");\n/** The SDK checks it with an assert: any other length kills the worker. One client per database, always this name. */\nconst CLIENT_NAME = \"dd5135b2-c228-41b7-a4f9-3b6e7afe3088\";\nconst NETWORKS: Record<string, BitcoinNetwork> = { bitcoin: \"bitcoin\", signet: \"signet\", testnet: \"testnet\", testnet4: \"testnet\", regtest: \"regtest\", mutinynet: \"mutinynet\" };\n/**\n * A v1 wallet (or Lightning) module's client config comes consensus-encoded as hex (`config` from a joined client,\n * `unknown_module_hex` in a preview); its network is the chain's magic, a u32 as a compact-size integer (`fe` +\n * four bytes, little-endian). Exactly one network must be found, or it stays unknown.\n */\nconst MAGIC: [string, BitcoinNetwork][] = [[\"fed9b4bef9\", \"bitcoin\"], [\"fe0709110b\", \"testnet\"], [\"fe283f161c\", \"testnet\"], [\"fe40cf030a\", \"signet\"], [\"fecb2ddfa5\", \"mutinynet\"], [\"fedab5bffa\", \"regtest\"]];\nfunction moduleNetwork(module: { network?: unknown; config?: unknown; unknown_module_hex?: unknown }): BitcoinNetwork | undefined {\n if (typeof module.network === \"string\") return NETWORKS[module.network];\n const encoded = typeof module.config === \"string\" ? module.config : module.unknown_module_hex;\n if (typeof encoded !== \"string\" || !/^[0-9a-f]*$/i.test(encoded)) return undefined;\n const hex = encoded.toLowerCase();\n const found = [...new Set(MAGIC.filter(([magic]) => hex.includes(magic)).map(([, network]) => network))];\n return found.length === 1 ? found[0] : undefined;\n}\nconst hex = (value: unknown) => typeof value === \"string\" ? value : Array.isArray(value) ? value.map((b) => Number(b).toString(16).padStart(2, \"0\")).join(\"\") : undefined;\n\nlet compiling: Promise<WebAssembly.Module> | undefined;\n/** Compiled once per page (11 MB): each worker is handed the module, not the bytes. */\nfunction compiled(): Promise<WebAssembly.Module> {\n return compiling ??= (async () => {\n const url = (await MODULE_WASM()).default;\n const response = await fetch(url);\n // A server that sends the wrong MIME type cannot stream: compile from the bytes instead.\n try { return await WebAssembly.compileStreaming(response.clone()); } catch { return WebAssembly.compile(await response.arrayBuffer()); }\n })().catch((error) => { compiling = undefined; throw error; });\n}\n\n/** A transport of `@fedimint/core` onto our own worker (fedimintWorker.ts). */\nasync function browserTransport() {\n const [{ Transport }, module] = await Promise.all([import(\"@fedimint/types\"), compiled()]);\n class WorkerTransport extends Transport {\n readonly logger = { debug() {}, info() {}, warn() {}, error() {} };\n private readonly worker = new Worker(new URL(\"./fedimintWorker.ts\", import.meta.url), { type: \"module\" });\n constructor() {\n super();\n this.worker.onmessage = (event) => this.messageHandler(event.data);\n this.worker.onerror = (event) => { event.preventDefault(); this.messageHandler({ type: \"error\", error: `The Fedimint client stopped: ${event.message || \"unknown error\"}` }); };\n }\n postMessage(message: { type: string; payload?: Json; requestId?: number }) {\n // The worker gets the compiled module with its init, and nothing to log.\n this.worker.postMessage(message.type === \"init\" ? { ...message, payload: { ...(message.payload as object), module } } : message);\n }\n terminate() { this.worker.terminate(); }\n }\n return new WorkerTransport();\n}\n\nconst text = (value: unknown, max = 200) => typeof value === \"string\" ? value.slice(0, max) : undefined;\nconst errorText = (error: unknown) => error instanceof Error ? error.message : typeof error === \"string\" ? error : JSON.stringify(error);\n\nexport function federationInfo(federationId: string, config: unknown): FederationInfo {\n const c = config as { global?: { api_endpoints?: Record<string, { url?: unknown; name?: unknown }>; consensus_version?: { major?: unknown; minor?: unknown }; meta?: Record<string, unknown> }; modules?: Record<string, { kind?: unknown; network?: unknown; config?: unknown; unknown_module_hex?: unknown }> };\n const endpoints = Object.entries(c?.global?.api_endpoints ?? {}).sort(([a], [b]) => Number(a) - Number(b));\n const modules = Object.values(c?.modules ?? {});\n // The wallet module says it; a federation without one (Lightning and ecash only) says it through its Lightning module.\n const network = modules.filter((m) => m?.kind === \"wallet\").map(moduleNetwork).find(Boolean) ?? modules.filter((m) => m?.kind === \"ln\").map(moduleNetwork).find(Boolean);\n const version = c?.global?.consensus_version;\n return {\n federationId,\n name: text(c?.global?.meta?.federation_name, 80),\n guardians: endpoints.slice(0, 64).map(([peer, e]) => ({ name: text(e?.name, 80) || `Guardian ${peer}`, url: text(e?.url, 300) ?? \"\" })),\n consensusVersion: `${Number(version?.major ?? 0)}.${Number(version?.minor ?? 0)}`,\n network,\n modules: [...new Set(modules.map((m) => (typeof m?.kind === \"string\" ? m.kind.slice(0, 32) : \"\")).filter(Boolean))],\n welcome: text(c?.global?.meta?.welcome_message, 280),\n };\n}\n\n/** The first value a subscription gives that is final, or the last one within `waitMs`. */\nfunction watch<S>(subscribe: (onState: (state: S) => void, onError: (error: string) => void) => () => void, final: (state: S) => boolean, waitMs: number): Promise<S | undefined> {\n return new Promise((resolve) => {\n let last: S | undefined, done = false, cancel: () => void = () => {};\n const finish = () => { if (done) return; done = true; clearTimeout(timer); cancel(); resolve(last); };\n const timer = setTimeout(finish, waitMs);\n cancel = subscribe((state) => { last = state; if (final(state)) finish(); }, () => finish());\n });\n}\n\nconst SPEND: Record<string, SpendState> = { Created: \"created\", UserCanceledProcessing: \"canceling\", UserCanceledSuccess: \"canceled\", UserCanceledFailure: \"taken\", Success: \"taken\", Refunded: \"refunded\" };\nconst spendFinal = (s: SpendState) => s === \"canceled\" || s === \"taken\" || s === \"refunded\";\nconst REDEEM: Record<string, RedeemState> = { Created: \"created\", Issuing: \"issuing\", Done: \"done\" };\nconst receiveOf = (s: unknown): ReceiveState => s === \"claimed\" ? \"claimed\" : s === \"funded\" || s === \"awaiting_funds\" ? \"funded\" : typeof s === \"object\" && s && \"canceled\" in s ? \"canceled\" : \"open\";\n\n/* eslint-disable @typescript-eslint/no-explicit-any -- the SDK's own types are loose unions of JSON; this is where they are narrowed. */\nclass RealClient implements FedimintClient {\n constructor(readonly federationId: string, private wallet: any, private director: any, private end: () => void) {}\n async info() { return federationInfo(this.federationId, await this.wallet.federation.getConfig()); }\n balance(): Promise<number> { return this.wallet.balance.getBalance(); }\n onBalance(listener: (msats: number) => void) { return this.wallet.balance.subscribeBalance(listener, () => {}); }\n async parseNotes(notes: string): Promise<ParsedNotes> {\n const parsed = await this.director.parseOobNotes(notes);\n return { amountMsats: Number(parsed.total_amount), federationIdPrefix: String(parsed.federation_id_prefix), federationId: parsed.federation_id ?? undefined };\n }\n async spend(amountMsats: number, { cancelAfterSecs, ghostly }: { cancelAfterSecs: number; ghostly: string }) {\n const { notes, operation_id } = await this.wallet.mint.spendNotes(amountMsats, cancelAfterSecs, false, { ghostly });\n return { notes: String(notes), operationId: String(operation_id) };\n }\n cancelSpend(operationId: string): Promise<void> { return this.wallet.mint.tryCancelSpendNotes(operationId); }\n spendState(operationId: string, waitMs: number) {\n return watch<SpendState>((ok, fail) => this.wallet.mint.subscribeSpendNotes(operationId, (s: string) => ok(SPEND[s] ?? \"created\"), fail), spendFinal, waitMs);\n }\n redeem(notes: string, ghostly: string): Promise<string> { return this.wallet.mint.reissueExternalNotes(notes, { ghostly }); }\n redeemState(operationId: string, waitMs: number) {\n return watch<RedeemState>((ok, fail) => this.wallet.mint.subscribeReissueExternalNotes(operationId, (s: unknown) => ok(typeof s === \"string\" ? REDEEM[s] ?? \"created\" : s && typeof s === \"object\" && \"Failed\" in s ? \"failed\" : \"created\"), (error: string) => { ok(\"failed\"); fail(error); }), (s) => s === \"done\" || s === \"failed\", waitMs);\n }\n async createInvoice(amountMsats: number, description: string, expirySecs: number, ghostly: string) {\n const { operation_id, invoice } = await this.wallet.lightning.createInvoice(amountMsats, description, expirySecs, undefined, { ghostly });\n return { operationId: String(operation_id), invoice: String(invoice) };\n }\n receiveState(operationId: string, waitMs: number) {\n return watch<ReceiveState>((ok, fail) => this.wallet.lightning.subscribeLnReceive(operationId, (s: unknown) => ok(receiveOf(s)), fail), (s) => s === \"claimed\" || s === \"canceled\", waitMs);\n }\n async gatewayFee(amountMsats: number) {\n await this.wallet.lightning.updateGatewayCache().catch(() => {});\n const gateway = await this.wallet.lightning.getAvailableGateway().catch(() => null);\n const fees = gateway?.fees;\n if (!fees) return undefined;\n return Number(fees.base_msat ?? 0) + Math.ceil(amountMsats * Number(fees.proportional_millionths ?? 0) / 1_000_000);\n }\n async payInvoice(invoice: string, ghostly: string) {\n const paid = await this.wallet.lightning.payInvoice(invoice, undefined, { ghostly });\n const internal = \"internal\" in paid.payment_type;\n return { operationId: String(internal ? paid.payment_type.internal : paid.payment_type.lightning), feeMsats: Number(paid.fee ?? 0), internal };\n }\n async payState(operationId: string, internal: boolean, waitMs: number): Promise<PayOutcome> {\n const state = await watch<unknown>((ok, fail) => internal ? this.wallet.lightning.subscribeInternalPayment(operationId, ok, fail) : this.wallet.lightning.subscribeLnPay(operationId, ok, fail),\n (s) => typeof s === \"object\" && !!s && (\"success\" in s || \"preimage\" in s || \"refunded\" in s || \"refund_success\" in s || \"unexpected_error\" in s || \"funding_failed\" in s) || s === \"canceled\", waitMs);\n if (state && typeof state === \"object\") {\n const s = state as Record<string, any>;\n // A payment out through the gateway ends in `success`; one to the same federation (internal) in `preimage` (bytes).\n if (s.success?.preimage || s.preimage) return { state: \"paid\", preimage: hex(s.success?.preimage ?? s.preimage) };\n if (\"refunded\" in s || \"refund_success\" in s || \"funding_failed\" in s) return { state: \"failed\", error: errorText(s.refunded?.gateway_error ?? s.funding_failed?.error ?? \"The gateway gave the payment back\") };\n if (\"unexpected_error\" in s) return { state: \"pending\", error: errorText(s.unexpected_error) };\n }\n if (state === \"canceled\") return { state: \"failed\", error: \"Canceled before it was funded\" };\n return { state: \"pending\" };\n }\n async operations(limit: number): Promise<FedimintOperation[]> {\n const list: [{ creation_time?: { secs_since_epoch?: number }; operation_id?: string }, { operation_module_kind?: string; meta?: any; outcome?: any }][] = await this.wallet.federation.listOperations(limit);\n return list.map(([key, log]) => {\n const variant = log.meta?.variant && typeof log.meta.variant === \"object\" ? Object.keys(log.meta.variant)[0] : undefined;\n const detail = variant ? log.meta.variant[variant] : undefined;\n return {\n id: String(key.operation_id ?? \"\"), kind: String(log.operation_module_kind ?? \"\"), variant,\n createdAt: Number(key.creation_time?.secs_since_epoch ?? 0) * 1000,\n amountMsats: typeof log.meta?.amount === \"number\" ? log.meta.amount : typeof detail?.requested_amount === \"number\" ? detail.requested_amount : undefined,\n outcome: log.outcome?.outcome, ghostly: typeof log.meta?.extra_meta?.ghostly === \"string\" ? log.meta.extra_meta.ghostly : undefined,\n invoice: typeof detail?.invoice === \"string\" ? detail.invoice : undefined,\n feeMsats: typeof detail?.fee === \"number\" ? detail.fee : undefined,\n };\n });\n }\n async close() { try { await this.wallet.cleanup(); } catch { /* already gone */ } this.end(); }\n}\n\n/**\n * Where the client runs: a worker per database, and the files the databases are. The browser's is a module worker on\n * the origin-private file system (below); a headless Ghostly on Node brings its own (packages/cli/src/runtime/fedimint.ts).\n */\nexport interface FedimintPlatform {\n /** A `@fedimint/types` Transport onto a new worker of its own, which one database will be opened in. */\n transport(): Promise<{ terminate(): void }>;\n exists(database: string): Promise<boolean>;\n /** Deletes a database, and is done when there is none. */\n remove(database: string): Promise<void>;\n}\n\nconst BROWSER: FedimintPlatform = {\n transport: browserTransport,\n async exists(database) {\n const root = await navigator.storage.getDirectory();\n try { const file = await (await root.getFileHandle(database)).getFile(); return file.size > 0; } catch { return false; }\n },\n async remove(database) {\n const root = await navigator.storage.getDirectory();\n await root.removeEntry(database).catch((error: unknown) => { if ((error as { name?: string })?.name !== \"NotFoundError\") throw error; });\n },\n};\n\nlet loading: Promise<FedimintSdk> | undefined;\n/** Loads the SDK on first use: profiles that never join a federation never fetch its 11 MB. */\nexport function loadFedimintSdk(): Promise<FedimintSdk> {\n return loading ??= fedimintSdkOn(BROWSER).catch((error) => { loading = undefined; throw error; });\n}\n\n/** The SDK on a platform's workers and files. */\nexport async function fedimintSdkOn(platform: FedimintPlatform): Promise<FedimintSdk> {\n const { WalletDirector } = await import(\"@fedimint/core\");\n /** A director on its own worker and database, with the mnemonic in place. */\n const director = async (database: string, mnemonic?: string) => {\n const t = await platform.transport();\n const d = new WalletDirector(t as any, database, true);\n try {\n await d.initialize(database);\n if (mnemonic && !(await d.hasMnemonicSet())) await d.setMnemonic(mnemonic.trim().split(/\\s+/));\n return { director: d, end: () => t.terminate() };\n } catch (error) { t.terminate(); throw error; }\n };\n const client = async (database: string, mnemonic: string, start: (wallet: any) => Promise<void>) => {\n const { director: d, end } = await director(database, mnemonic);\n try {\n const wallet = await d.createWallet();\n await start(wallet);\n return new RealClient(await wallet.federation.getFederationId(), wallet, d, end);\n } catch (error) { end(); throw error; }\n };\n const sdk: FedimintSdk = {\n async preview(invite) {\n // A scratch database of its own: previewing joins nothing.\n const database = fedimintDatabase(`preview-${crypto.randomUUID()}`);\n const { director: d, end } = await director(database);\n try { const preview = await d.previewFederation(invite.trim()); return federationInfo(preview.federation_id, preview.config); }\n finally { end(); await sdk.remove(database).catch(() => {}); }\n },\n join: ({ database, mnemonic, invite, recover }) => client(database, mnemonic, async (wallet) => {\n // joinFederation says false instead of throwing; its reason goes to the (silenced) logger.\n if (!(await wallet.joinFederation(invite.trim(), { clientName: CLIENT_NAME, forceRecover: recover }))) throw new Error(\"Could not join the federation: its guardians did not answer, or the invite code is not valid\");\n }),\n open: ({ database, mnemonic }) => client(database, mnemonic, (wallet) => wallet.open(CLIENT_NAME)),\n exists: (database) => platform.exists(database),\n remove: (database) => platform.remove(database),\n };\n return sdk;\n}\n/* eslint-enable @typescript-eslint/no-explicit-any */\n","import { fromBase64Url, irohDescriptor, irohRelayUrl, type BoundChannel, type FrameChannel, type NativeBinding, type NativeEndpoint } from \"@ghostly/core\";\n\n/**\n * Iroh (WISP 102) in a browser: the same QUIC/TLS session and binding as the\n * Desktop's native Iroh, but relay only. A page cannot send UDP, so every\n * packet goes through an Iroh relay over a WebSocket; the relay sees who talks\n * to whom and when, never the frames.\n *\n * The wasm (about 1 MB gzip) loads on the first chat that starts an endpoint,\n * never on the app's first load.\n */\n\n/**\n * n0's public relays, the same ones the Desktop's Iroh homes on by default. Iroh itself names them with the trailing\n * dot of a full domain name (`use1-1.relay.n0.iroh.link.`), and compares relay URLs as text; here they have none,\n * because WebKit opens no host name that ends in a dot. `BROWSER_SPELLING` is what keeps the two one relay.\n */\nexport const DEFAULT_IROH_RELAYS = [\n \"https://use1-1.relay.n0.iroh.link/\",\n \"https://euc1-1.relay.n0.iroh.link/\",\n \"https://aps1-1.relay.n0.iroh.link/\",\n \"https://usw1-1.relay.n0.iroh.link/\",\n] as const;\n\n/**\n * The spelling of every relay URL handed to the wasm Iroh (`irohRelayUrl`): the relays it homes on and a contact's\n * relay before a dial. One spelling, so a contact on this endpoint's relay is reached over the connection already\n * open to it; without the dot, so Safari can open it (a Desktop's record names its relay with the dot).\n */\nconst BROWSER_SPELLING = \"plain\";\n\n/**\n * Why a relay URL cannot be used, or null. TLS, except on this machine (a local `iroh-relay --dev`): a page\n * may not open plain WebSockets elsewhere, and the relay's operator would see traffic metadata in the clear.\n */\nexport function irohRelayProblem(value: string): string | null {\n let url: URL;\n try { url = new URL(value); } catch { return `Not a relay address: ${value}`; }\n const local = [\"localhost\", \"127.0.0.1\", \"[::1]\"].includes(url.hostname);\n if (url.protocol !== \"https:\" && !(url.protocol === \"http:\" && local)) return `Use an https:// relay address: ${value}`;\n if (url.username || url.password || url.search || url.hash || value.length > 256) return `Not a relay address: ${value}`;\n return null;\n}\n\n/** A browser endpoint's descriptor: no direct addresses, only its relay. */\nexport interface IrohWebDescriptor { id: string; relay: string | null; addresses: string[] }\n\ninterface WasmConn {\n binding(): NativeBinding;\n send(text: string): Promise<void>;\n recv(): Promise<string | undefined>;\n rtt(): number;\n close(): void;\n free(): void;\n}\ninterface WasmNode {\n address(): IrohWebDescriptor;\n connect(descriptor: unknown, timeoutMs: number): Promise<WasmConn>;\n accept(): Promise<WasmConn | undefined>;\n close(): Promise<void>;\n free(): void;\n}\nexport interface IrohWasm {\n IrohNode: { start(seed: Uint8Array, relays: string[], onlineMs: number): Promise<WasmNode> };\n}\n\nlet loaded: Promise<IrohWasm> | null = null;\n/** Loads the wasm once per page. Tests pass their own `load` (Node reads the file). */\nexport function loadIrohWasm(): Promise<IrohWasm> {\n loaded ??= import(\"@ghostly/iroh-web\").then(async module => {\n await module.default();\n return module as unknown as IrohWasm;\n }).catch(error => { loaded = null; throw error; });\n return loaded;\n}\n\nexport interface IrohWebOptions {\n /** Relay URLs, first preferred. Default: n0's public relays. */\n relays?: readonly string[];\n /** How long to wait for the first relay to answer. */\n onlineMs?: number;\n connectMs?: number;\n load?: () => Promise<IrohWasm>;\n}\n\nconst SEND_BUDGET = 120 * 1024;\nconst MAX_FRAME = 60 * 1024;\n\n/** One paired channel over one QUIC stream. Sends are queued in order. */\nclass IrohWebChannel implements FrameChannel {\n private reader: FrameChannel[\"onMessage\"] = null;\n private buffered: string[] = [];\n private queue = Promise.resolve();\n private closed = false;\n bufferedAmount = 0;\n readonly sendBudget = SEND_BUDGET;\n onClose: FrameChannel[\"onClose\"] = null;\n constructor(private conn: WasmConn, private forget: () => void) { void this.read(); }\n get onMessage() { return this.reader; }\n set onMessage(reader: FrameChannel[\"onMessage\"]) {\n this.reader = reader;\n if (reader) for (const text of this.buffered.splice(0)) reader(text);\n }\n private async read() {\n try {\n for (;;) {\n const text = await this.conn.recv();\n if (text === undefined || this.closed) break;\n if (this.reader) this.reader(text);\n else if (this.buffered.length < 64) this.buffered.push(text);\n else break;\n }\n } catch { /* A broken stream ends the channel. */ }\n this.close();\n }\n send(data: string | Uint8Array) {\n if (typeof data !== \"string\" || this.closed || !data.length || data.length > MAX_FRAME || this.bufferedAmount + data.length > SEND_BUDGET)\n throw new Error(\"Iroh channel is closed or its send budget is full\");\n this.bufferedAmount += data.length;\n this.queue = this.queue.then(() => this.closed ? undefined : this.conn.send(data))\n .catch(() => this.close()).finally(() => { this.bufferedAmount -= data.length; });\n }\n drained() { return this.queue; }\n get rttMs() { const rtt = this.conn.rtt(); return rtt >= 0 ? rtt : undefined; }\n close() {\n if (this.closed) return;\n this.closed = true; this.buffered = [];\n try { this.conn.close(); } catch { /* Already gone. */ }\n this.forget();\n this.onClose?.();\n }\n}\n\n/** A NativeEndpoint for `iroh/1`, as the Desktop's is, for one chat's transport seed. */\nexport async function createIrohWebEndpoint(seedB64: string, options: IrohWebOptions = {}): Promise<NativeEndpoint> {\n const seed = fromBase64Url(seedB64);\n if (seed.length !== 32) throw new Error(\"Invalid transport seed\");\n // Either spelling of a relay (with or without the trailing dot) homes on it under the one a browser opens.\n const relays = [...new Set((options.relays?.length ? options.relays : DEFAULT_IROH_RELAYS).map(relay => irohRelayUrl(relay, BROWSER_SPELLING)))];\n const wasm = await (options.load ?? loadIrohWasm)();\n const node = await wasm.IrohNode.start(seed, relays, options.onlineMs ?? 10_000);\n const channels = new Set<IrohWebChannel>();\n let stopped = false;\n let handler: NativeEndpoint[\"onConnection\"] = null;\n const early: BoundChannel[] = [];\n const bind = (conn: WasmConn): BoundChannel => {\n const channel: IrohWebChannel = new IrohWebChannel(conn, () => channels.delete(channel));\n channels.add(channel);\n return { channel, binding: conn.binding() };\n };\n const endpoint: NativeEndpoint = {\n transport: \"iroh/1\",\n // Relay only: the rank puts it after every direct path (WISP 100, \"Relayed\").\n descriptor: { ...node.address(), relayed: true },\n onDescriptor: null,\n get onConnection() { return handler; },\n set onConnection(value) { handler = value; if (value) for (const bound of early.splice(0)) value(bound); },\n async connect(descriptor) {\n if (stopped) throw new Error(\"Iroh endpoint is stopped\");\n const conn = await node.connect(irohDescriptor(descriptor, BROWSER_SPELLING), options.connectMs ?? 20_000);\n if (stopped) { conn.close(); throw new Error(\"Iroh endpoint is stopped\"); }\n return bind(conn);\n },\n async close() {\n if (stopped) return;\n stopped = true;\n for (const channel of [...channels]) channel.close();\n for (const bound of early.splice(0)) bound.channel.close();\n await node.close().catch(() => {});\n },\n };\n void (async () => {\n while (!stopped) {\n let conn: WasmConn | undefined;\n try { conn = await node.accept(); } catch { break; }\n if (!conn) break;\n if (stopped) { conn.close(); break; }\n const bound = bind(conn);\n const accept = endpoint.onConnection;\n if (accept) accept(bound); else if (early.length < 4) early.push(bound); else bound.channel.close();\n }\n // The relay went away for good (or the endpoint was closed): stop offering Iroh.\n if (!stopped) { endpoint.onUnavailable?.(); await endpoint.close(); }\n })();\n return endpoint;\n}\n"],"mappings":"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"}
|