@ghostlytools/cli 1.3.5 → 1.3.7

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.
Files changed (62) hide show
  1. package/README.md +7 -1
  2. package/dist/assets/{api-CgPyYfjH.js → api-BL0vpWGN.js} +2 -2
  3. package/dist/assets/{api-Do7NjRg2.js → api-DlxlF4jP.js} +10 -10
  4. package/dist/assets/api-DlxlF4jP.js.map +1 -0
  5. package/dist/assets/{apiKit-eh5qBKIl.js → apiKit-BnpVuZrZ.js} +2 -2
  6. package/dist/assets/{apiKit-eh5qBKIl.js.map → apiKit-BnpVuZrZ.js.map} +1 -1
  7. package/dist/assets/{apps-B83dRFga.js → apps-B_6Q-Mq-.js} +1 -1
  8. package/dist/assets/{apps-Cl9MtELY.js → apps-Bs6QYQjm.js} +2 -2
  9. package/dist/assets/{apps-Cl9MtELY.js.map → apps-Bs6QYQjm.js.map} +1 -1
  10. package/dist/assets/{arkade-B5Xn40bC.js → arkade-DwgUNqCz.js} +2 -2
  11. package/dist/assets/{arkade-B5Xn40bC.js.map → arkade-DwgUNqCz.js.map} +1 -1
  12. package/dist/assets/{backup-CDOh2tuw.js → backup-BqtsbjOT.js} +1 -1
  13. package/dist/assets/{backup-BjZcDFjL.js → backup-DEe1NqKb.js} +4 -4
  14. package/dist/assets/{backup-BjZcDFjL.js.map → backup-DEe1NqKb.js.map} +1 -1
  15. package/dist/assets/buttons-CB1opfZP.js +2 -0
  16. package/dist/assets/{buttons-CUKHafux.js → buttons-D9NQ3kV0.js} +3 -3
  17. package/dist/assets/{buttons-CUKHafux.js.map → buttons-D9NQ3kV0.js.map} +1 -1
  18. package/dist/assets/daemonProof-B53VnZ1Z.js +100 -0
  19. package/dist/assets/daemonProof-B53VnZ1Z.js.map +1 -0
  20. package/dist/assets/{envelope-wspXCjle.js → envelope-WC8QWYIL.js} +2 -2
  21. package/dist/assets/{envelope-wspXCjle.js.map → envelope-WC8QWYIL.js.map} +1 -1
  22. package/dist/assets/{groupFiles-Cfrjo3YS.js → groupFiles-BrCMSXxg.js} +7 -4
  23. package/dist/assets/groupFiles-BrCMSXxg.js.map +1 -0
  24. package/dist/assets/{host-BdS-7nRW.js → host-H0IYKNoK.js} +29 -11
  25. package/dist/assets/{host-BdS-7nRW.js.map → host-H0IYKNoK.js.map} +1 -1
  26. package/dist/assets/{hyperdhtRelay-H97Vz73W.js → hyperdhtRelay-CEfwbaCO.js} +2 -2
  27. package/dist/assets/{hyperdhtRelay-H97Vz73W.js.map → hyperdhtRelay-CEfwbaCO.js.map} +1 -1
  28. package/dist/assets/{irohWeb-jErC-V0J.js → irohWeb-Bn-JA2kp.js} +2 -2
  29. package/dist/assets/{irohWeb-jErC-V0J.js.map → irohWeb-Bn-JA2kp.js.map} +1 -1
  30. package/dist/assets/{main-C14lA5aK.js → main-DiCC-Vjq.js} +111 -39
  31. package/dist/assets/main-DiCC-Vjq.js.map +1 -0
  32. package/dist/assets/{mainline-CYI1tZT7.js → mainline-gU7pE7s2.js} +2 -2
  33. package/dist/assets/{mainline-CYI1tZT7.js.map → mainline-gU7pE7s2.js.map} +1 -1
  34. package/dist/assets/{networkReason-CiHrd-kG.js → networkReason-Dl9JThBQ.js} +2 -2
  35. package/dist/assets/{networkReason-CiHrd-kG.js.map → networkReason-Dl9JThBQ.js.map} +1 -1
  36. package/dist/assets/{nwc-0JYm8Vys.js → nwc-CKjZjg-q.js} +3 -3
  37. package/dist/assets/{nwc-0JYm8Vys.js.map → nwc-CKjZjg-q.js.map} +1 -1
  38. package/dist/assets/{nwcProvider-2coJAsqr.js → nwcProvider-GDrc0HM4.js} +2 -2
  39. package/dist/assets/{nwcProvider-2coJAsqr.js.map → nwcProvider-GDrc0HM4.js.map} +1 -1
  40. package/dist/assets/{registry-BptCF3tL.js → registry-JGOFtJDE.js} +3 -3
  41. package/dist/assets/{registry-BptCF3tL.js.map → registry-JGOFtJDE.js.map} +1 -1
  42. package/dist/assets/{registry-sML83UvL.js → registry-Wk7ZHuwm.js} +1 -1
  43. package/dist/assets/{resolve-2hGrvTen.js → resolve-CVpEbud3.js} +2 -2
  44. package/dist/assets/{resolve-2hGrvTen.js.map → resolve-CVpEbud3.js.map} +1 -1
  45. package/dist/assets/{server-BbPiCghe.js → server-CkYscxE3.js} +55 -21
  46. package/dist/assets/server-CkYscxE3.js.map +1 -0
  47. package/dist/assets/{src-BLzG4Sfv.js → src-gJqfoRKI.js} +32 -3
  48. package/dist/assets/src-gJqfoRKI.js.map +1 -0
  49. package/dist/assets/{turnRefs-ClWSgjb0.js → turnRefs-zUQbFkl3.js} +2 -2
  50. package/dist/assets/{turnRefs-ClWSgjb0.js.map → turnRefs-zUQbFkl3.js.map} +1 -1
  51. package/dist/assets/{usdt-zwyn_AQK.js → usdt-BkDMGqZa.js} +3 -3
  52. package/dist/assets/{usdt-zwyn_AQK.js.map → usdt-BkDMGqZa.js.map} +1 -1
  53. package/dist/ghostly.mjs +1 -1
  54. package/package.json +1 -1
  55. package/dist/assets/api-Do7NjRg2.js.map +0 -1
  56. package/dist/assets/buttons-CFabREDv.js +0 -2
  57. package/dist/assets/groupFiles-Cfrjo3YS.js.map +0 -1
  58. package/dist/assets/main-C14lA5aK.js.map +0 -1
  59. package/dist/assets/privateFolder-Bk9C9oFv.js +0 -38
  60. package/dist/assets/privateFolder-Bk9C9oFv.js.map +0 -1
  61. package/dist/assets/server-BbPiCghe.js.map +0 -1
  62. package/dist/assets/src-BLzG4Sfv.js.map +0 -1
@@ -1,4 +1,4 @@
1
- import { xl as fromBase64Url } from "./src-BLzG4Sfv.js";
1
+ import { xl as fromBase64Url } from "./src-gJqfoRKI.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-H97Vz73W.js.map
357
+ //# sourceMappingURL=hyperdhtRelay-CEfwbaCO.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"hyperdhtRelay-H97Vz73W.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-CEfwbaCO.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,4 +1,4 @@
1
- import { oa as irohDescriptor, sa as irohRelayUrl, xl as fromBase64Url } from "./src-BLzG4Sfv.js";
1
+ import { oa as irohDescriptor, sa as irohRelayUrl, xl as fromBase64Url } from "./src-gJqfoRKI.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-jErC-V0J.js.map
544
+ //# sourceMappingURL=irohWeb-Bn-JA2kp.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"irohWeb-jErC-V0J.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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"}
1
+ {"version":3,"file":"irohWeb-Bn-JA2kp.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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"}