@ghostlytools/cli 1.3.7 → 1.3.9

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 (58) hide show
  1. package/README.md +8 -5
  2. package/SKILL.md +2 -0
  3. package/dist/assets/{api-DlxlF4jP.js → api-BCrwJ57L.js} +50 -24
  4. package/dist/assets/api-BCrwJ57L.js.map +1 -0
  5. package/dist/assets/{api-BL0vpWGN.js → api-Cp9GxjUO.js} +2 -2
  6. package/dist/assets/{apiKit-BnpVuZrZ.js → apiKit-B4qhnLQT.js} +2 -2
  7. package/dist/assets/{apiKit-BnpVuZrZ.js.map → apiKit-B4qhnLQT.js.map} +1 -1
  8. package/dist/assets/{apps-B_6Q-Mq-.js → apps-CpxENMgl.js} +1 -1
  9. package/dist/assets/{apps-Bs6QYQjm.js → apps-DOH1P_S0.js} +2 -2
  10. package/dist/assets/{apps-Bs6QYQjm.js.map → apps-DOH1P_S0.js.map} +1 -1
  11. package/dist/assets/{arkade-DwgUNqCz.js → arkade-Cyf5MGxG.js} +2 -2
  12. package/dist/assets/{arkade-DwgUNqCz.js.map → arkade-Cyf5MGxG.js.map} +1 -1
  13. package/dist/assets/{backup-DEe1NqKb.js → backup-CTpFFQ-4.js} +4 -4
  14. package/dist/assets/{backup-DEe1NqKb.js.map → backup-CTpFFQ-4.js.map} +1 -1
  15. package/dist/assets/{backup-BqtsbjOT.js → backup-CqM-0-2v.js} +1 -1
  16. package/dist/assets/{buttons-D9NQ3kV0.js → buttons-BtxwyM2l.js} +3 -3
  17. package/dist/assets/{buttons-D9NQ3kV0.js.map → buttons-BtxwyM2l.js.map} +1 -1
  18. package/dist/assets/buttons-CJqNkG-Q.js +2 -0
  19. package/dist/assets/{envelope-WC8QWYIL.js → envelope-DW8TWKPD.js} +2 -2
  20. package/dist/assets/{envelope-WC8QWYIL.js.map → envelope-DW8TWKPD.js.map} +1 -1
  21. package/dist/assets/{groupFiles-BrCMSXxg.js → groupFiles-uxa0U9_n.js} +3 -3
  22. package/dist/assets/{groupFiles-BrCMSXxg.js.map → groupFiles-uxa0U9_n.js.map} +1 -1
  23. package/dist/assets/{host-H0IYKNoK.js → host-DPy0eaH9.js} +29 -9
  24. package/dist/assets/host-DPy0eaH9.js.map +1 -0
  25. package/dist/assets/{hyperdhtRelay-CEfwbaCO.js → hyperdhtRelay-De5FT7Am.js} +2 -2
  26. package/dist/assets/{hyperdhtRelay-CEfwbaCO.js.map → hyperdhtRelay-De5FT7Am.js.map} +1 -1
  27. package/dist/assets/{irohWeb-Bn-JA2kp.js → irohWeb-z5QDDWa-.js} +2 -2
  28. package/dist/assets/{irohWeb-Bn-JA2kp.js.map → irohWeb-z5QDDWa-.js.map} +1 -1
  29. package/dist/assets/{main-DiCC-Vjq.js → main-Dt7xrqtb.js} +52 -27
  30. package/dist/assets/main-Dt7xrqtb.js.map +1 -0
  31. package/dist/assets/{mainline-gU7pE7s2.js → mainline-Cld-bKeC.js} +2 -2
  32. package/dist/assets/{mainline-gU7pE7s2.js.map → mainline-Cld-bKeC.js.map} +1 -1
  33. package/dist/assets/{networkReason-Dl9JThBQ.js → networkReason-vUHo6A_C.js} +2 -2
  34. package/dist/assets/{networkReason-Dl9JThBQ.js.map → networkReason-vUHo6A_C.js.map} +1 -1
  35. package/dist/assets/{nwc-CKjZjg-q.js → nwc-zXpmd2FN.js} +3 -3
  36. package/dist/assets/{nwc-CKjZjg-q.js.map → nwc-zXpmd2FN.js.map} +1 -1
  37. package/dist/assets/{nwcProvider-GDrc0HM4.js → nwcProvider-D482SQ1a.js} +2 -2
  38. package/dist/assets/{nwcProvider-GDrc0HM4.js.map → nwcProvider-D482SQ1a.js.map} +1 -1
  39. package/dist/assets/{registry-Wk7ZHuwm.js → registry-CxsDR3c6.js} +1 -1
  40. package/dist/assets/{registry-JGOFtJDE.js → registry-OVx2kY3d.js} +3 -3
  41. package/dist/assets/{registry-JGOFtJDE.js.map → registry-OVx2kY3d.js.map} +1 -1
  42. package/dist/assets/{resolve-CVpEbud3.js → resolve-CPTAnu-9.js} +2 -2
  43. package/dist/assets/{resolve-CVpEbud3.js.map → resolve-CPTAnu-9.js.map} +1 -1
  44. package/dist/assets/{server-CkYscxE3.js → server-p5DJnN7T.js} +17 -13
  45. package/dist/assets/{server-CkYscxE3.js.map → server-p5DJnN7T.js.map} +1 -1
  46. package/dist/assets/{src-gJqfoRKI.js → src-Dg1bvMNv.js} +155 -30
  47. package/dist/assets/src-Dg1bvMNv.js.map +1 -0
  48. package/dist/assets/{turnRefs-zUQbFkl3.js → turnRefs-WnggXm2T.js} +2 -2
  49. package/dist/assets/{turnRefs-zUQbFkl3.js.map → turnRefs-WnggXm2T.js.map} +1 -1
  50. package/dist/assets/{usdt-BkDMGqZa.js → usdt-BY5OIIvG.js} +3 -3
  51. package/dist/assets/{usdt-BkDMGqZa.js.map → usdt-BY5OIIvG.js.map} +1 -1
  52. package/dist/ghostly.mjs +1 -1
  53. package/package.json +1 -1
  54. package/dist/assets/api-DlxlF4jP.js.map +0 -1
  55. package/dist/assets/buttons-CB1opfZP.js +0 -2
  56. package/dist/assets/host-H0IYKNoK.js.map +0 -1
  57. package/dist/assets/main-DiCC-Vjq.js.map +0 -1
  58. package/dist/assets/src-gJqfoRKI.js.map +0 -1
@@ -1,4 +1,4 @@
1
- import { xl as fromBase64Url } from "./src-gJqfoRKI.js";
1
+ import { xl as fromBase64Url } from "./src-Dg1bvMNv.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-CEfwbaCO.js.map
357
+ //# sourceMappingURL=hyperdhtRelay-De5FT7Am.js.map
@@ -1 +1 @@
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
+ {"version":3,"file":"hyperdhtRelay-De5FT7Am.js","names":[],"sources":["../../../browser/src/platform/hyperdhtRelay.ts"],"sourcesContent":["import { ed25519 } from \"@noble/curves/ed25519.js\";\nimport { fromBase64Url, type BoundChannel, type FrameChannel, type NativeEndpoint } from \"@ghostly/core\";\nimport { hyperdhtRelayProblem } from \"../shared/hyperdhtRelay\";\n\nexport { hyperdhtRelayProblem as relayUrlProblem };\n\n/**\n * HyperDHT (WISP 103) for a browser, through a dht-relay: a server that runs the UDP half of HyperDHT and\n * talks to the browser over one WebSocket. It runs non-custodial: the browser keeps its keys, runs the\n * Noise handshake and the encrypted stream itself, and the relay moves handshake messages and ciphertext.\n * The binding (the handshake hash and both keys) is computed here, at the endpoint, as the Desktop does.\n *\n * What the relay sees: this browser's address, the HyperDHT keys it listens on and dials, and when and how\n * much it sends. Not the frames, not the keys' secrets, not the handshake hash.\n *\n * The wire is the Desktop's (native/transports/hyperdht/endpoint.mjs): the same preface, u32 length-prefixed\n * UTF-8 frames of at most 60 KiB, so a browser and a Desktop talk HyperDHT to each other.\n */\n\nconst PREFACE = \"ghostly/paired-chat/1\";\nconst MAX_FRAME = 60 * 1024;\n/** The top bit of a frame's length word: the frame is a binary message, not UTF-8 text (WISP 103). */\nconst BINARY = 0x80000000;\nconst SEND_BUDGET = 120 * 1024;\nconst HANDSHAKE_MS = 20_000;\nconst RELAY_OPEN_MS = 10_000;\nconst LISTEN_MS = 20_000;\nconst MAX_CHANNELS = 2;\n/**\n * How the relay client is always made: never custodial. dht-relay's default is custodial, which sends every\n * secret key to the relay so it can run the handshake and the stream itself. Nothing here takes this from\n * settings or options; test/hyperdhtRelay.test.ts fails if a secret key ever reaches the relay's wire.\n */\nconst NON_CUSTODIAL = Object.freeze({ custodial: false as const });\n\n/**\n * The descriptor a browser's HyperDHT endpoint gives out: its key, that it is reached only through a relay, and\n * which one (the connection panel names its host; a Desktop dialling it ignores both).\n */\nexport interface RelayedHyperDescriptor { publicKey: string; relayed: true; relay: string }\n\n/** The parts of dht-relay and its streams this adapter uses. */\ninterface SecretSocket {\n handshakeHash: Uint8Array | null;\n publicKey: Uint8Array;\n remotePublicKey: Uint8Array;\n opened?: Promise<boolean>;\n destroyed?: boolean;\n write(data: Uint8Array): boolean;\n destroy(error?: Error): void;\n on(event: string, listener: (...args: never[]) => void): unknown;\n once(event: string, listener: (...args: never[]) => void): unknown;\n off(event: string, listener: (...args: never[]) => void): unknown;\n}\ninterface RelayedServer {\n listen(keyPair: KeyPair): Promise<void>;\n close(): Promise<void>;\n}\ninterface RelayedNode {\n ready(): Promise<void>;\n connect(remotePublicKey: Uint8Array, options: { keyPair: KeyPair }): SecretSocket;\n createServer(options: object, onconnection: (socket: SecretSocket) => void): RelayedServer;\n destroy(): Promise<void>;\n}\ninterface KeyPair { publicKey: Uint8Array; secretKey: Uint8Array }\n\nconst hex = (bytes: Uint8Array) => Array.from(bytes, b => b.toString(16).padStart(2, \"0\")).join(\"\");\nconst fromHex = (text: string) => Uint8Array.from(text.match(/../g)!, byte => parseInt(byte, 16));\n\n/** HyperDHT's key pair for a seed: libsodium's layout (the seed, then the public key). */\nexport function hyperKeyPair(seed: Uint8Array): KeyPair {\n if (seed.length !== 32) throw new Error(\"Invalid HyperDHT seed\");\n const publicKey = ed25519.getPublicKey(seed);\n const secretKey = new Uint8Array(64);\n secretKey.set(seed); secretKey.set(publicKey, 32);\n return { publicKey, secretKey };\n}\n\n/**\n * One WebSocket per relay for the whole app, shared by every chat's endpoint (each listens on its own\n * key). It closes when the last endpoint does. When the relay goes away, every endpoint on it says it is\n * unavailable, and the next one to start opens a new connection.\n */\ninterface RelayClient {\n node: RelayedNode;\n endpoints: Set<{ unavailable(): void }>;\n gone: boolean;\n /** Settles when the relay connection closes. */\n closed: Promise<void>;\n}\nconst clients = new Map<string, Promise<RelayClient>>();\n\nasync function relayClient(url: string, createSocket: (url: string) => WebSocket): Promise<RelayClient> {\n const existing = clients.get(url);\n if (existing) {\n const client = await existing.catch(() => null);\n if (client && !client.gone) return client;\n if (clients.get(url) === existing) clients.delete(url);\n }\n const opening = (async () => {\n const [{ default: RelayedDHT }, { default: WebSocketStream }] = await Promise.all([\n // @ts-expect-error dht-relay ships no types: RelayedNode and SecretSocket above describe what this uses.\n import(\"@hyperswarm/dht-relay\"),\n // @ts-expect-error The same package's WebSocket stream, untyped too.\n import(\"@hyperswarm/dht-relay/ws\"),\n ]);\n const socket = createSocket(url);\n socket.binaryType = \"arraybuffer\";\n await new Promise<void>((resolve, reject) => {\n const timer = setTimeout(() => { socket.close(); reject(new Error(\"The HyperDHT relay did not answer\")); }, RELAY_OPEN_MS);\n socket.addEventListener(\"open\", () => { clearTimeout(timer); resolve(); }, { once: true });\n socket.addEventListener(\"error\", () => { clearTimeout(timer); reject(new Error(\"Could not reach the HyperDHT relay\")); }, { once: true });\n socket.addEventListener(\"close\", () => { clearTimeout(timer); reject(new Error(\"The HyperDHT relay closed the connection\")); }, { once: true });\n });\n // Non-custodial: the relay is told public keys only, never a secret key (dht-relay's default sends them).\n // The node's own key is never used to listen or dial: a random one says nothing about this app.\n const node = new RelayedDHT(new WebSocketStream(true, socket), { ...NON_CUSTODIAL, keyPair: hyperKeyPair(crypto.getRandomValues(new Uint8Array(32))) }) as RelayedNode;\n let closed!: () => void;\n const client: RelayClient = { node, endpoints: new Set(), gone: false, closed: new Promise(resolve => { closed = resolve; }) };\n socket.addEventListener(\"close\", () => {\n client.gone = true; closed();\n if (clients.get(url) === opening) clients.delete(url);\n for (const endpoint of [...client.endpoints]) endpoint.unavailable();\n });\n return client;\n })();\n clients.set(url, opening);\n opening.catch(() => { if (clients.get(url) === opening) clients.delete(url); });\n return opening;\n}\n\nfunction release(url: string, client: RelayClient, endpoint: { unavailable(): void }): void {\n client.endpoints.delete(endpoint);\n if (client.endpoints.size || client.gone) return;\n client.gone = true;\n void clients.get(url)?.then(current => { if (current === client) clients.delete(url); }).catch(() => {});\n void client.node.destroy().catch(() => {});\n}\n\n/** One authenticated stream as a frame channel, with a bounded queue before the session installs its reader. */\nclass RelayedChannel implements FrameChannel {\n private reader: FrameChannel[\"onMessage\"] = null;\n private buffered: (string | Uint8Array)[] = [];\n private closed = false;\n private pendingBytes = 0;\n /** It carries binary messages (WISP 100 § Text and binary messages), as the Desktop's own HyperDHT does. */\n readonly binary = true;\n onClose: FrameChannel[\"onClose\"] = null;\n constructor(private readonly socket: SecretSocket) {\n socket.on(\"drain\", () => { this.pendingBytes = 0; });\n }\n get bufferedAmount(): number { return this.pendingBytes; }\n readonly sendBudget = SEND_BUDGET;\n get onMessage() { return this.reader; }\n set onMessage(reader: FrameChannel[\"onMessage\"]) {\n this.reader = reader;\n if (reader) for (const text of this.buffered.splice(0)) reader(text);\n }\n receive(text: string | Uint8Array): boolean {\n if (this.closed) return true;\n if (this.reader) this.reader(text);\n else if (this.buffered.length < 64) this.buffered.push(text);\n else return false;\n return true;\n }\n send(data: string | Uint8Array): void {\n if (this.closed || !data.length) throw new Error(\"Native channel is closed or exceeds its frame budget\");\n const binary = typeof data !== \"string\";\n const payload = binary ? data : new TextEncoder().encode(data);\n if (payload.length > MAX_FRAME || this.pendingBytes + payload.length > SEND_BUDGET) throw new Error(\"Native channel is closed or exceeds its frame budget\");\n // The stream takes every write; past its high-water mark it says so until it drains.\n if (!writeFrame(this.socket, payload, binary) || this.pendingBytes) this.pendingBytes += payload.length + 4;\n }\n drained(): Promise<void> {\n if (!this.pendingBytes) return Promise.resolve();\n return new Promise((resolve, reject) => {\n const done = () => { this.socket.off(\"close\", gone); resolve(); };\n const gone = () => { this.socket.off(\"drain\", done); reject(new Error(\"Native channel closed\")); };\n this.socket.once(\"drain\", done); this.socket.once(\"close\", gone);\n });\n }\n closedByPeer(): void {\n if (this.closed) return;\n this.closed = true; this.buffered = [];\n this.onClose?.();\n }\n close(): void {\n if (this.closed) return;\n this.socket.destroy();\n this.closedByPeer();\n }\n}\n\nfunction writeFrame(socket: SecretSocket, payload: Uint8Array, binary = false): boolean {\n const frame = new Uint8Array(4 + payload.length);\n new DataView(frame.buffer).setUint32(0, (binary ? BINARY : 0) + payload.length);\n frame.set(payload, 4);\n return socket.write(frame);\n}\n\n/**\n * Waits for the peer's preface on an authenticated stream (sending ours as soon as the handshake is\n * done), then hands the channel over with the binding the stream itself gives. A frame of the wrong size,\n * bytes that are not UTF-8 or another protocol close the stream.\n */\nfunction attach(socket: SecretSocket, channels: Set<RelayedChannel>): Promise<BoundChannel> {\n if (channels.size >= MAX_CHANNELS) { socket.destroy(); return Promise.reject(new Error(\"Native connection limit reached\")); }\n const channel = new RelayedChannel(socket);\n channels.add(channel);\n return new Promise<BoundChannel>((resolve, reject) => {\n let buffer = new Uint8Array(0);\n let prefaced = false;\n const decoder = new TextDecoder(\"utf-8\", { fatal: true });\n const timer = setTimeout(() => socket.destroy(new Error(\"HyperDHT handshake timed out\")), HANDSHAKE_MS);\n const fail = (error: Error) => { socket.destroy(error); };\n socket.on(\"error\", (error: Error) => reject(error));\n socket.on(\"close\", () => {\n clearTimeout(timer); channels.delete(channel);\n reject(new Error(\"Native channel closed\"));\n channel.closedByPeer();\n });\n const opened = () => {\n if (socket.handshakeHash?.length !== 64 || socket.publicKey?.length !== 32 || socket.remotePublicKey?.length !== 32) {\n fail(new Error(\"Noise session binding unavailable\")); return;\n }\n writeFrame(socket, new TextEncoder().encode(PREFACE));\n };\n if (socket.opened) void socket.opened.then(ok => { if (ok) opened(); else reject(new Error(\"Noise handshake failed\")); });\n else socket.once(\"open\", opened);\n socket.on(\"data\", (chunk: Uint8Array) => {\n const joined = new Uint8Array(buffer.length + chunk.length);\n joined.set(buffer); joined.set(chunk, buffer.length);\n buffer = joined;\n while (buffer.length >= 4) {\n const word = new DataView(buffer.buffer, buffer.byteOffset, 4).getUint32(0);\n const binary = word >= BINARY, length = word % BINARY;\n // A binary frame before the preface is another protocol.\n if (length === 0 || length > MAX_FRAME || (binary && !prefaced)) { fail(new Error(\"Invalid frame length\")); return; }\n if (buffer.length < 4 + length) break;\n if (binary) {\n const message = buffer.slice(4, 4 + length);\n buffer = buffer.slice(4 + length);\n if (!channel.receive(message)) { fail(new Error(\"Receive queue exceeded\")); return; }\n continue;\n }\n let text: string;\n try { text = decoder.decode(buffer.subarray(4, 4 + length)); }\n catch { fail(new Error(\"Invalid UTF-8 frame\")); return; }\n buffer = buffer.slice(4 + length);\n if (!prefaced) {\n if (text !== PREFACE) { fail(new Error(\"Unexpected application protocol\")); return; }\n prefaced = true; clearTimeout(timer);\n resolve({ channel, binding: { transport: \"hyperdht/1\", context: hex(socket.handshakeHash!),\n identities: [hex(socket.publicKey), hex(socket.remotePublicKey)].sort() as [string, string] } });\n } else if (!channel.receive(text)) { fail(new Error(\"Receive queue exceeded\")); return; }\n }\n });\n });\n}\n\nexport interface RelayedHyperOptions {\n /** How to open the WebSocket (tests pass Node's `ws`). Default: the browser's. */\n createSocket?: (url: string) => WebSocket;\n}\n\n/**\n * A chat's HyperDHT endpoint through the relay at `url`: it listens on the key of `seedB64` (the chat's\n * own transport seed, as on the Desktop) and dials a contact's descriptor. Fails, and so is not offered,\n * when the relay cannot be reached or does not let it listen.\n */\nexport async function createRelayedHyperEndpoint(seedB64: string, url: string, options: RelayedHyperOptions = {}): Promise<NativeEndpoint> {\n const problem = hyperdhtRelayProblem(url);\n if (problem) throw new Error(problem);\n const keyPair = hyperKeyPair(fromBase64Url(seedB64));\n const client = await relayClient(url, options.createSocket ?? (address => new WebSocket(address)));\n const channels = new Set<RelayedChannel>();\n let stopped = false;\n let handler: NativeEndpoint[\"onConnection\"] = null;\n const waiting: BoundChannel[] = [];\n let endpoint: NativeEndpoint | null = null;\n const member = {\n unavailable() {\n if (stopped) return;\n stopped = true;\n for (const channel of channels) channel.close();\n endpoint?.onUnavailable?.();\n },\n };\n client.endpoints.add(member);\n const server = client.node.createServer({}, socket => {\n void attach(socket, channels).then(bound => {\n if (stopped) bound.channel.close();\n else if (handler) handler(bound);\n else if (waiting.length < MAX_CHANNELS) waiting.push(bound);\n else bound.channel.close();\n }).catch(() => {});\n });\n try {\n await Promise.race([\n server.listen(keyPair),\n client.closed.then(() => { throw new Error(\"The HyperDHT relay closed the connection\"); }),\n new Promise((_, reject) => setTimeout(() => reject(new Error(\"The HyperDHT relay did not let this chat listen\")), LISTEN_MS)),\n ]);\n if (client.gone) throw new Error(\"The HyperDHT relay closed the connection\");\n } catch (error) {\n stopped = true;\n void server.close().catch(() => {});\n release(url, client, member);\n throw error;\n }\n const descriptor: RelayedHyperDescriptor = { publicKey: hex(keyPair.publicKey), relayed: true, relay: url };\n endpoint = {\n transport: \"hyperdht/1\", descriptor, onDescriptor: null, onUnavailable: null,\n get onConnection() { return handler; },\n set onConnection(value) { handler = value; if (value) for (const bound of waiting.splice(0)) value(bound); },\n async connect(address) {\n const publicKey = (address as { publicKey?: unknown } | null)?.publicKey;\n if (stopped || typeof publicKey !== \"string\" || !/^[a-f0-9]{64}$/.test(publicKey)) throw new Error(\"Invalid HyperDHT endpoint\");\n return attach(client.node.connect(fromHex(publicKey), { keyPair }), channels);\n },\n async close() {\n stopped = true;\n for (const channel of channels) channel.close();\n waiting.length = 0;\n await server.close().catch(() => {});\n release(url, client, member);\n },\n };\n return endpoint;\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAmBA,IAAM,UAAU;AAChB,IAAM,YAAY;;AAElB,IAAM,SAAS;AACf,IAAM,cAAc;AACpB,IAAM,eAAe;AACrB,IAAM,gBAAgB;AACtB,IAAM,YAAY;AAClB,IAAM,eAAe;;;;;;AAMrB,IAAM,gBAAgB,OAAO,OAAO,EAAE,WAAW,MAAe,CAAC;AAiCjE,IAAM,OAAO,UAAsB,MAAM,KAAK,QAAO,MAAK,EAAE,SAAS,EAAE,CAAC,CAAC,SAAS,GAAG,GAAG,CAAC,CAAC,CAAC,KAAK,EAAE;AAClG,IAAM,WAAW,SAAiB,WAAW,KAAK,KAAK,MAAM,KAAK,IAAI,SAAQ,SAAS,MAAM,EAAE,CAAC;;AAGhG,SAAgB,aAAa,MAA2B;CACtD,IAAI,KAAK,WAAW,IAAI,MAAM,IAAI,MAAM,uBAAuB;CAC/D,MAAM,YAAY,QAAQ,aAAa,IAAI;CAC3C,MAAM,4BAAY,IAAI,WAAW,EAAE;CACnC,UAAU,IAAI,IAAI;CAAG,UAAU,IAAI,WAAW,EAAE;CAChD,OAAO;EAAE;EAAW;CAAU;AAChC;AAcA,IAAM,0BAAU,IAAI,IAAkC;AAEtD,eAAe,YAAY,KAAa,cAAgE;CACtG,MAAM,WAAW,QAAQ,IAAI,GAAG;CAChC,IAAI,UAAU;EACZ,MAAM,SAAS,MAAM,SAAS,YAAY,IAAI;EAC9C,IAAI,UAAU,CAAC,OAAO,MAAM,OAAO;EACnC,IAAI,QAAQ,IAAI,GAAG,MAAM,UAAU,QAAQ,OAAO,GAAG;CACvD;CACA,MAAM,WAAW,YAAY;EAC3B,MAAM,CAAC,EAAE,SAAS,cAAc,EAAE,SAAS,qBAAqB,MAAM,QAAQ,IAAI,CAEhF,OAAO,0BAEP,OAAO,2BACT,CAAC;EACD,MAAM,SAAS,aAAa,GAAG;EAC/B,OAAO,aAAa;EACpB,MAAM,IAAI,SAAe,SAAS,WAAW;GAC3C,MAAM,QAAQ,iBAAiB;IAAE,OAAO,MAAM;IAAG,uBAAO,IAAI,MAAM,mCAAmC,CAAC;GAAG,GAAG,aAAa;GACzH,OAAO,iBAAiB,cAAc;IAAE,aAAa,KAAK;IAAG,QAAQ;GAAG,GAAG,EAAE,MAAM,KAAK,CAAC;GACzF,OAAO,iBAAiB,eAAe;IAAE,aAAa,KAAK;IAAG,uBAAO,IAAI,MAAM,oCAAoC,CAAC;GAAG,GAAG,EAAE,MAAM,KAAK,CAAC;GACxI,OAAO,iBAAiB,eAAe;IAAE,aAAa,KAAK;IAAG,uBAAO,IAAI,MAAM,0CAA0C,CAAC;GAAG,GAAG,EAAE,MAAM,KAAK,CAAC;EAChJ,CAAC;EAGD,MAAM,OAAO,IAAI,WAAW,IAAI,gBAAgB,MAAM,MAAM,GAAG;GAAE,GAAG;GAAe,SAAS,aAAa,OAAO,gCAAgB,IAAI,WAAW,EAAE,CAAC,CAAC;EAAE,CAAC;EACtJ,IAAI;EACJ,MAAM,SAAsB;GAAE;GAAM,2BAAW,IAAI,IAAI;GAAG,MAAM;GAAO,QAAQ,IAAI,SAAQ,YAAW;IAAE,SAAS;GAAS,CAAC;EAAE;EAC7H,OAAO,iBAAiB,eAAe;GACrC,OAAO,OAAO;GAAM,OAAO;GAC3B,IAAI,QAAQ,IAAI,GAAG,MAAM,SAAS,QAAQ,OAAO,GAAG;GACpD,KAAK,MAAM,YAAY,CAAC,GAAG,OAAO,SAAS,GAAG,SAAS,YAAY;EACrE,CAAC;EACD,OAAO;CACT,EAAA,CAAG;CACH,QAAQ,IAAI,KAAK,OAAO;CACxB,QAAQ,YAAY;EAAE,IAAI,QAAQ,IAAI,GAAG,MAAM,SAAS,QAAQ,OAAO,GAAG;CAAG,CAAC;CAC9E,OAAO;AACT;AAEA,SAAS,QAAQ,KAAa,QAAqB,UAAyC;CAC1F,OAAO,UAAU,OAAO,QAAQ;CAChC,IAAI,OAAO,UAAU,QAAQ,OAAO,MAAM;CAC1C,OAAO,OAAO;CACd,QAAa,IAAI,GAAG,CAAC,EAAE,MAAK,YAAW;EAAE,IAAI,YAAY,QAAQ,QAAQ,OAAO,GAAG;CAAG,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC;CACvG,OAAY,KAAK,QAAQ,CAAC,CAAC,YAAY,CAAC,CAAC;AAC3C;;AAGA,IAAM,iBAAN,MAA6C;CAQd;CAP7B,SAA4C;CAC5C,WAA4C,CAAC;CAC7C,SAAiB;CACjB,eAAuB;;CAEvB,SAAkB;CAClB,UAAmC;CACnC,YAAY,QAAuC;EAAtB,KAAA,SAAA;EAC3B,OAAO,GAAG,eAAe;GAAE,KAAK,eAAe;EAAG,CAAC;CACrD;CACA,IAAI,iBAAyB;EAAE,OAAO,KAAK;CAAc;CACzD,aAAsB;CACtB,IAAI,YAAY;EAAE,OAAO,KAAK;CAAQ;CACtC,IAAI,UAAU,QAAmC;EAC/C,KAAK,SAAS;EACd,IAAI,QAAQ,KAAK,MAAM,QAAQ,KAAK,SAAS,OAAO,CAAC,GAAG,OAAO,IAAI;CACrE;CACA,QAAQ,MAAoC;EAC1C,IAAI,KAAK,QAAQ,OAAO;EACxB,IAAI,KAAK,QAAQ,KAAK,OAAO,IAAI;OAC5B,IAAI,KAAK,SAAS,SAAS,IAAI,KAAK,SAAS,KAAK,IAAI;OACtD,OAAO;EACZ,OAAO;CACT;CACA,KAAK,MAAiC;EACpC,IAAI,KAAK,UAAU,CAAC,KAAK,QAAQ,MAAM,IAAI,MAAM,sDAAsD;EACvG,MAAM,SAAS,OAAO,SAAS;EAC/B,MAAM,UAAU,SAAS,OAAO,IAAI,YAAY,CAAC,CAAC,OAAO,IAAI;EAC7D,IAAI,QAAQ,SAAS,aAAa,KAAK,eAAe,QAAQ,SAAS,aAAa,MAAM,IAAI,MAAM,sDAAsD;EAE1J,IAAI,CAAC,WAAW,KAAK,QAAQ,SAAS,MAAM,KAAK,KAAK,cAAc,KAAK,gBAAgB,QAAQ,SAAS;CAC5G;CACA,UAAyB;EACvB,IAAI,CAAC,KAAK,cAAc,OAAO,QAAQ,QAAQ;EAC/C,OAAO,IAAI,SAAS,SAAS,WAAW;GACtC,MAAM,aAAa;IAAE,KAAK,OAAO,IAAI,SAAS,IAAI;IAAG,QAAQ;GAAG;GAChE,MAAM,aAAa;IAAE,KAAK,OAAO,IAAI,SAAS,IAAI;IAAG,uBAAO,IAAI,MAAM,uBAAuB,CAAC;GAAG;GACjG,KAAK,OAAO,KAAK,SAAS,IAAI;GAAG,KAAK,OAAO,KAAK,SAAS,IAAI;EACjE,CAAC;CACH;CACA,eAAqB;EACnB,IAAI,KAAK,QAAQ;EACjB,KAAK,SAAS;EAAM,KAAK,WAAW,CAAC;EACrC,KAAK,UAAU;CACjB;CACA,QAAc;EACZ,IAAI,KAAK,QAAQ;EACjB,KAAK,OAAO,QAAQ;EACpB,KAAK,aAAa;CACpB;AACF;AAEA,SAAS,WAAW,QAAsB,SAAqB,SAAS,OAAgB;CACtF,MAAM,QAAQ,IAAI,WAAW,IAAI,QAAQ,MAAM;CAC/C,IAAI,SAAS,MAAM,MAAM,CAAC,CAAC,UAAU,IAAI,SAAS,SAAS,KAAK,QAAQ,MAAM;CAC9E,MAAM,IAAI,SAAS,CAAC;CACpB,OAAO,OAAO,MAAM,KAAK;AAC3B;;;;;;AAOA,SAAS,OAAO,QAAsB,UAAsD;CAC1F,IAAI,SAAS,QAAQ,cAAc;EAAE,OAAO,QAAQ;EAAG,OAAO,QAAQ,uBAAO,IAAI,MAAM,iCAAiC,CAAC;CAAG;CAC5H,MAAM,UAAU,IAAI,eAAe,MAAM;CACzC,SAAS,IAAI,OAAO;CACpB,OAAO,IAAI,SAAuB,SAAS,WAAW;EACpD,IAAI,yBAAS,IAAI,WAAW,CAAC;EAC7B,IAAI,WAAW;EACf,MAAM,UAAU,IAAI,YAAY,SAAS,EAAE,OAAO,KAAK,CAAC;EACxD,MAAM,QAAQ,iBAAiB,OAAO,wBAAQ,IAAI,MAAM,8BAA8B,CAAC,GAAG,YAAY;EACtG,MAAM,QAAQ,UAAiB;GAAE,OAAO,QAAQ,KAAK;EAAG;EACxD,OAAO,GAAG,UAAU,UAAiB,OAAO,KAAK,CAAC;EAClD,OAAO,GAAG,eAAe;GACvB,aAAa,KAAK;GAAG,SAAS,OAAO,OAAO;GAC5C,uBAAO,IAAI,MAAM,uBAAuB,CAAC;GACzC,QAAQ,aAAa;EACvB,CAAC;EACD,MAAM,eAAe;GACnB,IAAI,OAAO,eAAe,WAAW,MAAM,OAAO,WAAW,WAAW,MAAM,OAAO,iBAAiB,WAAW,IAAI;IACnH,qBAAK,IAAI,MAAM,mCAAmC,CAAC;IAAG;GACxD;GACA,WAAW,QAAQ,IAAI,YAAY,CAAC,CAAC,OAAO,OAAO,CAAC;EACtD;EACA,IAAI,OAAO,QAAQ,OAAY,OAAO,MAAK,OAAM;GAAE,IAAI,IAAI,OAAO;QAAQ,uBAAO,IAAI,MAAM,wBAAwB,CAAC;EAAG,CAAC;OACnH,OAAO,KAAK,QAAQ,MAAM;EAC/B,OAAO,GAAG,SAAS,UAAsB;GACvC,MAAM,SAAS,IAAI,WAAW,OAAO,SAAS,MAAM,MAAM;GAC1D,OAAO,IAAI,MAAM;GAAG,OAAO,IAAI,OAAO,OAAO,MAAM;GACnD,SAAS;GACT,OAAO,OAAO,UAAU,GAAG;IACzB,MAAM,OAAO,IAAI,SAAS,OAAO,QAAQ,OAAO,YAAY,CAAC,CAAC,CAAC,UAAU,CAAC;IAC1E,MAAM,SAAS,QAAQ,QAAQ,SAAS,OAAO;IAE/C,IAAI,WAAW,KAAK,SAAS,aAAc,UAAU,CAAC,UAAW;KAAE,qBAAK,IAAI,MAAM,sBAAsB,CAAC;KAAG;IAAQ;IACpH,IAAI,OAAO,SAAS,IAAI,QAAQ;IAChC,IAAI,QAAQ;KACV,MAAM,UAAU,OAAO,MAAM,GAAG,IAAI,MAAM;KAC1C,SAAS,OAAO,MAAM,IAAI,MAAM;KAChC,IAAI,CAAC,QAAQ,QAAQ,OAAO,GAAG;MAAE,qBAAK,IAAI,MAAM,wBAAwB,CAAC;MAAG;KAAQ;KACpF;IACF;IACA,IAAI;IACJ,IAAI;KAAE,OAAO,QAAQ,OAAO,OAAO,SAAS,GAAG,IAAI,MAAM,CAAC;IAAG,QACvD;KAAE,qBAAK,IAAI,MAAM,qBAAqB,CAAC;KAAG;IAAQ;IACxD,SAAS,OAAO,MAAM,IAAI,MAAM;IAChC,IAAI,CAAC,UAAU;KACb,IAAI,SAAS,SAAS;MAAE,qBAAK,IAAI,MAAM,iCAAiC,CAAC;MAAG;KAAQ;KACpF,WAAW;KAAM,aAAa,KAAK;KACnC,QAAQ;MAAE;MAAS,SAAS;OAAE,WAAW;OAAc,SAAS,IAAI,OAAO,aAAc;OACvF,YAAY,CAAC,IAAI,OAAO,SAAS,GAAG,IAAI,OAAO,eAAe,CAAC,CAAC,CAAC,KAAK;MAAsB;KAAE,CAAC;IACnG,OAAO,IAAI,CAAC,QAAQ,QAAQ,IAAI,GAAG;KAAE,qBAAK,IAAI,MAAM,wBAAwB,CAAC;KAAG;IAAQ;GAC1F;EACF,CAAC;CACH,CAAC;AACH;;;;;;AAYA,eAAsB,2BAA2B,SAAiB,KAAa,UAA+B,CAAC,GAA4B;CACzI,MAAM,UAAU,qBAAqB,GAAG;CACxC,IAAI,SAAS,MAAM,IAAI,MAAM,OAAO;CACpC,MAAM,UAAU,aAAa,cAAc,OAAO,CAAC;CACnD,MAAM,SAAS,MAAM,YAAY,KAAK,QAAQ,kBAAiB,YAAW,IAAI,UAAU,OAAO,EAAE;CACjG,MAAM,2BAAW,IAAI,IAAoB;CACzC,IAAI,UAAU;CACd,IAAI,UAA0C;CAC9C,MAAM,UAA0B,CAAC;CACjC,IAAI,WAAkC;CACtC,MAAM,SAAS,EACb,cAAc;EACZ,IAAI,SAAS;EACb,UAAU;EACV,KAAK,MAAM,WAAW,UAAU,QAAQ,MAAM;EAC9C,UAAU,gBAAgB;CAC5B,EACF;CACA,OAAO,UAAU,IAAI,MAAM;CAC3B,MAAM,SAAS,OAAO,KAAK,aAAa,CAAC,IAAG,WAAU;EACpD,OAAY,QAAQ,QAAQ,CAAC,CAAC,MAAK,UAAS;GAC1C,IAAI,SAAS,MAAM,QAAQ,MAAM;QAC5B,IAAI,SAAS,QAAQ,KAAK;QAC1B,IAAI,QAAQ,SAAS,cAAc,QAAQ,KAAK,KAAK;QACrD,MAAM,QAAQ,MAAM;EAC3B,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC;CACnB,CAAC;CACD,IAAI;EACF,MAAM,QAAQ,KAAK;GACjB,OAAO,OAAO,OAAO;GACrB,OAAO,OAAO,WAAW;IAAE,MAAM,IAAI,MAAM,0CAA0C;GAAG,CAAC;GACzF,IAAI,SAAS,GAAG,WAAW,iBAAiB,uBAAO,IAAI,MAAM,iDAAiD,CAAC,GAAG,SAAS,CAAC;EAC9H,CAAC;EACD,IAAI,OAAO,MAAM,MAAM,IAAI,MAAM,0CAA0C;CAC7E,SAAS,OAAO;EACd,UAAU;EACV,OAAY,MAAM,CAAC,CAAC,YAAY,CAAC,CAAC;EAClC,QAAQ,KAAK,QAAQ,MAAM;EAC3B,MAAM;CACR;CAEA,WAAW;EACT,WAAW;EAAc,YAAA;GAFkB,WAAW,IAAI,QAAQ,SAAS;GAAG,SAAS;GAAM,OAAO;EAE3E;EAAY,cAAc;EAAM,eAAe;EACxE,IAAI,eAAe;GAAE,OAAO;EAAS;EACrC,IAAI,aAAa,OAAO;GAAE,UAAU;GAAO,IAAI,OAAO,KAAK,MAAM,SAAS,QAAQ,OAAO,CAAC,GAAG,MAAM,KAAK;EAAG;EAC3G,MAAM,QAAQ,SAAS;GACrB,MAAM,YAAa,SAA4C;GAC/D,IAAI,WAAW,OAAO,cAAc,YAAY,CAAC,iBAAiB,KAAK,SAAS,GAAG,MAAM,IAAI,MAAM,2BAA2B;GAC9H,OAAO,OAAO,OAAO,KAAK,QAAQ,QAAQ,SAAS,GAAG,EAAE,QAAQ,CAAC,GAAG,QAAQ;EAC9E;EACA,MAAM,QAAQ;GACZ,UAAU;GACV,KAAK,MAAM,WAAW,UAAU,QAAQ,MAAM;GAC9C,QAAQ,SAAS;GACjB,MAAM,OAAO,MAAM,CAAC,CAAC,YAAY,CAAC,CAAC;GACnC,QAAQ,KAAK,QAAQ,MAAM;EAC7B;CACF;CACA,OAAO;AACT"}
@@ -1,4 +1,4 @@
1
- import { oa as irohDescriptor, sa as irohRelayUrl, xl as fromBase64Url } from "./src-gJqfoRKI.js";
1
+ import { oa as irohDescriptor, sa as irohRelayUrl, xl as fromBase64Url } from "./src-Dg1bvMNv.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-Bn-JA2kp.js.map
544
+ //# sourceMappingURL=irohWeb-z5QDDWa-.js.map
@@ -1 +1 @@
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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"}
1
+ {"version":3,"file":"irohWeb-z5QDDWa-.js","names":[],"sources":["../../../browser/src/engine/paymentAdapters/fedimintSdk.ts","../../../browser/src/platform/irohWeb.ts"],"sourcesContent":["import type { BitcoinNetwork } from \"@ghostly/core\";\n\n/**\n * The part of the Fedimint web SDK (`@fedimint/core`, WebAssembly client in a worker) Ghostly uses, behind an\n * interface small enough to fake in tests. The client runs in a worker, on a file it reads and writes through a sync\n * access handle: in a browser, a module worker and the origin-private file system; on Node, the CLI's\n * `worker_threads` worker and a file of the profile (`FedimintPlatform`).\n *\n * One client database per joined federation (`ghostly-fedimint-<id>.db`, one file): the SDK's services are bound to one client per database, and each database is held by one worker.\n * Every database of a profile and mode is given the same mnemonic; the client derives a different secret per\n * federation from it. Amounts are millisatoshis, as the SDK counts them.\n */\nexport interface FederationGuardian { name: string; url: string }\nexport interface FederationInfo {\n federationId: string;\n /** `meta.federation_name`, when the guardians set one. */\n name?: string;\n guardians: FederationGuardian[];\n /** Core consensus version, `major.minor`. */\n consensusVersion: string;\n /** The Bitcoin network of its wallet module. Unknown when the federation has no v1 wallet module. */\n network?: BitcoinNetwork;\n /** Module kinds (`mint`, `ln`, `wallet`, `meta`…). The SDK uses `mint`, `ln` and `wallet` (v1). */\n modules: string[];\n /** `meta.welcome_message`, shortened. */\n welcome?: string;\n}\n\n/** Out-of-band notes, parsed offline (signatures and prefix only: whether they are spent is the federation's to say). */\nexport interface ParsedNotes { amountMsats: number; federationIdPrefix: string; federationId?: string }\n\n/** Where a spend of notes stands. `canceled` = taken back (reissued to us); `taken` = the recipient redeemed them. */\nexport type SpendState = \"created\" | \"canceling\" | \"canceled\" | \"taken\" | \"refunded\";\n/** A redeem (reissue) of notes we were given. */\nexport type RedeemState = \"created\" | \"issuing\" | \"done\" | \"failed\";\nexport type ReceiveState = \"open\" | \"funded\" | \"claimed\" | \"canceled\";\nexport interface PayOutcome { state: \"pending\" | \"paid\" | \"failed\"; preimage?: string; error?: string }\n\nexport interface FedimintOperation {\n id: string;\n /** Module: `mint`, `ln`, `wallet`. */\n kind: string;\n /** What it was: `spend_oob`, `reissuance`, `receive`, `pay`… as the SDK names it. */\n variant?: string;\n createdAt: number;\n amountMsats?: number;\n /** The SDK's final outcome, once there is one. */\n outcome?: unknown;\n /** What Ghostly asked to be kept with it (`extra_meta`): the payment id it belongs to. */\n ghostly?: string;\n /** Lightning: the invoice it paid or was paid on, and the fee it paid (msats). */\n invoice?: string;\n feeMsats?: number;\n}\n\nexport interface FedimintClient {\n readonly federationId: string;\n info(): Promise<FederationInfo>;\n balance(): Promise<number>;\n /** Called with every new balance, until the returned function is called. */\n onBalance(listener: (msats: number) => void): () => void;\n parseNotes(notes: string): Promise<ParsedNotes>;\n /** Takes notes out of the wallet, as a bearer string. Unredeemed after `cancelAfterSecs`, they come back by themselves. */\n spend(amountMsats: number, options: { cancelAfterSecs: number; ghostly: string }): Promise<{ notes: string; operationId: string }>;\n /** Asks to take notes back: they are reissued to us unless the recipient redeemed them first. */\n cancelSpend(operationId: string): Promise<void>;\n /** The latest state within `waitMs` (it returns early on a final one). Undefined: the client knows no such operation. */\n spendState(operationId: string, waitMs: number): Promise<SpendState | undefined>;\n redeem(notes: string, ghostly: string): Promise<string>;\n redeemState(operationId: string, waitMs: number): Promise<RedeemState | undefined>;\n createInvoice(amountMsats: number, description: string, expirySecs: number, ghostly: string): Promise<{ operationId: string; invoice: string }>;\n receiveState(operationId: string, waitMs: number): Promise<ReceiveState | undefined>;\n /** What the federation's gateway would charge to pay this amount, in msats. Undefined: no gateway. */\n gatewayFee(amountMsats: number): Promise<number | undefined>;\n payInvoice(invoice: string, ghostly: string): Promise<{ operationId: string; feeMsats: number; internal: boolean }>;\n payState(operationId: string, internal: boolean, waitMs: number): Promise<PayOutcome>;\n operations(limit: number): Promise<FedimintOperation[]>;\n /** Ends the worker; the database stays. */\n close(): Promise<void>;\n}\n\nexport interface FedimintSdk {\n preview(invite: string): Promise<FederationInfo>;\n /** Joins (or, `recover`, restores from the federation's backup of this mnemonic) into a new database. */\n join(params: { database: string; mnemonic: string; invite: string; recover: boolean }): Promise<FedimintClient>;\n /** Opens a database joined before. */\n open(params: { database: string; mnemonic: string }): Promise<FedimintClient>;\n /** Deletes a database: only one that never held money (a failed join). */\n remove(database: string): Promise<void>;\n /** Whether this device has the database (a profile restored from a backup has the records, not the files). */\n exists(database: string): Promise<boolean>;\n}\n\nexport const fedimintDatabase = (id: string) => `ghostly-fedimint-${id}.db`;\n\n// ── The real SDK ───────────────────────────────────────────────────────────────────────────────────────────\n\ntype Json = null | boolean | number | string | Json[] | { [key: string]: Json };\nconst MODULE_WASM = () => import(\"@fedimint/fedimint-client-wasm-bundler/fedimint_client_wasm_bg.wasm?url\");\n/** The SDK checks it with an assert: any other length kills the worker. One client per database, always this name. */\nconst CLIENT_NAME = \"dd5135b2-c228-41b7-a4f9-3b6e7afe3088\";\nconst NETWORKS: Record<string, BitcoinNetwork> = { bitcoin: \"bitcoin\", signet: \"signet\", testnet: \"testnet\", testnet4: \"testnet\", regtest: \"regtest\", mutinynet: \"mutinynet\" };\n/**\n * A v1 wallet (or Lightning) module's client config comes consensus-encoded as hex (`config` from a joined client,\n * `unknown_module_hex` in a preview); its network is the chain's magic, a u32 as a compact-size integer (`fe` +\n * four bytes, little-endian). Exactly one network must be found, or it stays unknown.\n */\nconst MAGIC: [string, BitcoinNetwork][] = [[\"fed9b4bef9\", \"bitcoin\"], [\"fe0709110b\", \"testnet\"], [\"fe283f161c\", \"testnet\"], [\"fe40cf030a\", \"signet\"], [\"fecb2ddfa5\", \"mutinynet\"], [\"fedab5bffa\", \"regtest\"]];\nfunction moduleNetwork(module: { network?: unknown; config?: unknown; unknown_module_hex?: unknown }): BitcoinNetwork | undefined {\n if (typeof module.network === \"string\") return NETWORKS[module.network];\n const encoded = typeof module.config === \"string\" ? module.config : module.unknown_module_hex;\n if (typeof encoded !== \"string\" || !/^[0-9a-f]*$/i.test(encoded)) return undefined;\n const hex = encoded.toLowerCase();\n const found = [...new Set(MAGIC.filter(([magic]) => hex.includes(magic)).map(([, network]) => network))];\n return found.length === 1 ? found[0] : undefined;\n}\nconst hex = (value: unknown) => typeof value === \"string\" ? value : Array.isArray(value) ? value.map((b) => Number(b).toString(16).padStart(2, \"0\")).join(\"\") : undefined;\n\nlet compiling: Promise<WebAssembly.Module> | undefined;\n/** Compiled once per page (11 MB): each worker is handed the module, not the bytes. */\nfunction compiled(): Promise<WebAssembly.Module> {\n return compiling ??= (async () => {\n const url = (await MODULE_WASM()).default;\n const response = await fetch(url);\n // A server that sends the wrong MIME type cannot stream: compile from the bytes instead.\n try { return await WebAssembly.compileStreaming(response.clone()); } catch { return WebAssembly.compile(await response.arrayBuffer()); }\n })().catch((error) => { compiling = undefined; throw error; });\n}\n\n/** A transport of `@fedimint/core` onto our own worker (fedimintWorker.ts). */\nasync function browserTransport() {\n const [{ Transport }, module] = await Promise.all([import(\"@fedimint/types\"), compiled()]);\n class WorkerTransport extends Transport {\n readonly logger = { debug() {}, info() {}, warn() {}, error() {} };\n private readonly worker = new Worker(new URL(\"./fedimintWorker.ts\", import.meta.url), { type: \"module\" });\n constructor() {\n super();\n this.worker.onmessage = (event) => this.messageHandler(event.data);\n this.worker.onerror = (event) => { event.preventDefault(); this.messageHandler({ type: \"error\", error: `The Fedimint client stopped: ${event.message || \"unknown error\"}` }); };\n }\n postMessage(message: { type: string; payload?: Json; requestId?: number }) {\n // The worker gets the compiled module with its init, and nothing to log.\n this.worker.postMessage(message.type === \"init\" ? { ...message, payload: { ...(message.payload as object), module } } : message);\n }\n terminate() { this.worker.terminate(); }\n }\n return new WorkerTransport();\n}\n\nconst text = (value: unknown, max = 200) => typeof value === \"string\" ? value.slice(0, max) : undefined;\nconst errorText = (error: unknown) => error instanceof Error ? error.message : typeof error === \"string\" ? error : JSON.stringify(error);\n\nexport function federationInfo(federationId: string, config: unknown): FederationInfo {\n const c = config as { global?: { api_endpoints?: Record<string, { url?: unknown; name?: unknown }>; consensus_version?: { major?: unknown; minor?: unknown }; meta?: Record<string, unknown> }; modules?: Record<string, { kind?: unknown; network?: unknown; config?: unknown; unknown_module_hex?: unknown }> };\n const endpoints = Object.entries(c?.global?.api_endpoints ?? {}).sort(([a], [b]) => Number(a) - Number(b));\n const modules = Object.values(c?.modules ?? {});\n // The wallet module says it; a federation without one (Lightning and ecash only) says it through its Lightning module.\n const network = modules.filter((m) => m?.kind === \"wallet\").map(moduleNetwork).find(Boolean) ?? modules.filter((m) => m?.kind === \"ln\").map(moduleNetwork).find(Boolean);\n const version = c?.global?.consensus_version;\n return {\n federationId,\n name: text(c?.global?.meta?.federation_name, 80),\n guardians: endpoints.slice(0, 64).map(([peer, e]) => ({ name: text(e?.name, 80) || `Guardian ${peer}`, url: text(e?.url, 300) ?? \"\" })),\n consensusVersion: `${Number(version?.major ?? 0)}.${Number(version?.minor ?? 0)}`,\n network,\n modules: [...new Set(modules.map((m) => (typeof m?.kind === \"string\" ? m.kind.slice(0, 32) : \"\")).filter(Boolean))],\n welcome: text(c?.global?.meta?.welcome_message, 280),\n };\n}\n\n/** The first value a subscription gives that is final, or the last one within `waitMs`. */\nfunction watch<S>(subscribe: (onState: (state: S) => void, onError: (error: string) => void) => () => void, final: (state: S) => boolean, waitMs: number): Promise<S | undefined> {\n return new Promise((resolve) => {\n let last: S | undefined, done = false, cancel: () => void = () => {};\n const finish = () => { if (done) return; done = true; clearTimeout(timer); cancel(); resolve(last); };\n const timer = setTimeout(finish, waitMs);\n cancel = subscribe((state) => { last = state; if (final(state)) finish(); }, () => finish());\n });\n}\n\nconst SPEND: Record<string, SpendState> = { Created: \"created\", UserCanceledProcessing: \"canceling\", UserCanceledSuccess: \"canceled\", UserCanceledFailure: \"taken\", Success: \"taken\", Refunded: \"refunded\" };\nconst spendFinal = (s: SpendState) => s === \"canceled\" || s === \"taken\" || s === \"refunded\";\nconst REDEEM: Record<string, RedeemState> = { Created: \"created\", Issuing: \"issuing\", Done: \"done\" };\nconst receiveOf = (s: unknown): ReceiveState => s === \"claimed\" ? \"claimed\" : s === \"funded\" || s === \"awaiting_funds\" ? \"funded\" : typeof s === \"object\" && s && \"canceled\" in s ? \"canceled\" : \"open\";\n\n/* eslint-disable @typescript-eslint/no-explicit-any -- the SDK's own types are loose unions of JSON; this is where they are narrowed. */\nclass RealClient implements FedimintClient {\n constructor(readonly federationId: string, private wallet: any, private director: any, private end: () => void) {}\n async info() { return federationInfo(this.federationId, await this.wallet.federation.getConfig()); }\n balance(): Promise<number> { return this.wallet.balance.getBalance(); }\n onBalance(listener: (msats: number) => void) { return this.wallet.balance.subscribeBalance(listener, () => {}); }\n async parseNotes(notes: string): Promise<ParsedNotes> {\n const parsed = await this.director.parseOobNotes(notes);\n return { amountMsats: Number(parsed.total_amount), federationIdPrefix: String(parsed.federation_id_prefix), federationId: parsed.federation_id ?? undefined };\n }\n async spend(amountMsats: number, { cancelAfterSecs, ghostly }: { cancelAfterSecs: number; ghostly: string }) {\n const { notes, operation_id } = await this.wallet.mint.spendNotes(amountMsats, cancelAfterSecs, false, { ghostly });\n return { notes: String(notes), operationId: String(operation_id) };\n }\n cancelSpend(operationId: string): Promise<void> { return this.wallet.mint.tryCancelSpendNotes(operationId); }\n spendState(operationId: string, waitMs: number) {\n return watch<SpendState>((ok, fail) => this.wallet.mint.subscribeSpendNotes(operationId, (s: string) => ok(SPEND[s] ?? \"created\"), fail), spendFinal, waitMs);\n }\n redeem(notes: string, ghostly: string): Promise<string> { return this.wallet.mint.reissueExternalNotes(notes, { ghostly }); }\n redeemState(operationId: string, waitMs: number) {\n return watch<RedeemState>((ok, fail) => this.wallet.mint.subscribeReissueExternalNotes(operationId, (s: unknown) => ok(typeof s === \"string\" ? REDEEM[s] ?? \"created\" : s && typeof s === \"object\" && \"Failed\" in s ? \"failed\" : \"created\"), (error: string) => { ok(\"failed\"); fail(error); }), (s) => s === \"done\" || s === \"failed\", waitMs);\n }\n async createInvoice(amountMsats: number, description: string, expirySecs: number, ghostly: string) {\n const { operation_id, invoice } = await this.wallet.lightning.createInvoice(amountMsats, description, expirySecs, undefined, { ghostly });\n return { operationId: String(operation_id), invoice: String(invoice) };\n }\n receiveState(operationId: string, waitMs: number) {\n return watch<ReceiveState>((ok, fail) => this.wallet.lightning.subscribeLnReceive(operationId, (s: unknown) => ok(receiveOf(s)), fail), (s) => s === \"claimed\" || s === \"canceled\", waitMs);\n }\n async gatewayFee(amountMsats: number) {\n await this.wallet.lightning.updateGatewayCache().catch(() => {});\n const gateway = await this.wallet.lightning.getAvailableGateway().catch(() => null);\n const fees = gateway?.fees;\n if (!fees) return undefined;\n return Number(fees.base_msat ?? 0) + Math.ceil(amountMsats * Number(fees.proportional_millionths ?? 0) / 1_000_000);\n }\n async payInvoice(invoice: string, ghostly: string) {\n const paid = await this.wallet.lightning.payInvoice(invoice, undefined, { ghostly });\n const internal = \"internal\" in paid.payment_type;\n return { operationId: String(internal ? paid.payment_type.internal : paid.payment_type.lightning), feeMsats: Number(paid.fee ?? 0), internal };\n }\n async payState(operationId: string, internal: boolean, waitMs: number): Promise<PayOutcome> {\n const state = await watch<unknown>((ok, fail) => internal ? this.wallet.lightning.subscribeInternalPayment(operationId, ok, fail) : this.wallet.lightning.subscribeLnPay(operationId, ok, fail),\n (s) => typeof s === \"object\" && !!s && (\"success\" in s || \"preimage\" in s || \"refunded\" in s || \"refund_success\" in s || \"unexpected_error\" in s || \"funding_failed\" in s) || s === \"canceled\", waitMs);\n if (state && typeof state === \"object\") {\n const s = state as Record<string, any>;\n // A payment out through the gateway ends in `success`; one to the same federation (internal) in `preimage` (bytes).\n if (s.success?.preimage || s.preimage) return { state: \"paid\", preimage: hex(s.success?.preimage ?? s.preimage) };\n if (\"refunded\" in s || \"refund_success\" in s || \"funding_failed\" in s) return { state: \"failed\", error: errorText(s.refunded?.gateway_error ?? s.funding_failed?.error ?? \"The gateway gave the payment back\") };\n if (\"unexpected_error\" in s) return { state: \"pending\", error: errorText(s.unexpected_error) };\n }\n if (state === \"canceled\") return { state: \"failed\", error: \"Canceled before it was funded\" };\n return { state: \"pending\" };\n }\n async operations(limit: number): Promise<FedimintOperation[]> {\n const list: [{ creation_time?: { secs_since_epoch?: number }; operation_id?: string }, { operation_module_kind?: string; meta?: any; outcome?: any }][] = await this.wallet.federation.listOperations(limit);\n return list.map(([key, log]) => {\n const variant = log.meta?.variant && typeof log.meta.variant === \"object\" ? Object.keys(log.meta.variant)[0] : undefined;\n const detail = variant ? log.meta.variant[variant] : undefined;\n return {\n id: String(key.operation_id ?? \"\"), kind: String(log.operation_module_kind ?? \"\"), variant,\n createdAt: Number(key.creation_time?.secs_since_epoch ?? 0) * 1000,\n amountMsats: typeof log.meta?.amount === \"number\" ? log.meta.amount : typeof detail?.requested_amount === \"number\" ? detail.requested_amount : undefined,\n outcome: log.outcome?.outcome, ghostly: typeof log.meta?.extra_meta?.ghostly === \"string\" ? log.meta.extra_meta.ghostly : undefined,\n invoice: typeof detail?.invoice === \"string\" ? detail.invoice : undefined,\n feeMsats: typeof detail?.fee === \"number\" ? detail.fee : undefined,\n };\n });\n }\n async close() { try { await this.wallet.cleanup(); } catch { /* already gone */ } this.end(); }\n}\n\n/**\n * Where the client runs: a worker per database, and the files the databases are. The browser's is a module worker on\n * the origin-private file system (below); a headless Ghostly on Node brings its own (packages/cli/src/runtime/fedimint.ts).\n */\nexport interface FedimintPlatform {\n /** A `@fedimint/types` Transport onto a new worker of its own, which one database will be opened in. */\n transport(): Promise<{ terminate(): void }>;\n exists(database: string): Promise<boolean>;\n /** Deletes a database, and is done when there is none. */\n remove(database: string): Promise<void>;\n}\n\nconst BROWSER: FedimintPlatform = {\n transport: browserTransport,\n async exists(database) {\n const root = await navigator.storage.getDirectory();\n try { const file = await (await root.getFileHandle(database)).getFile(); return file.size > 0; } catch { return false; }\n },\n async remove(database) {\n const root = await navigator.storage.getDirectory();\n await root.removeEntry(database).catch((error: unknown) => { if ((error as { name?: string })?.name !== \"NotFoundError\") throw error; });\n },\n};\n\nlet loading: Promise<FedimintSdk> | undefined;\n/** Loads the SDK on first use: profiles that never join a federation never fetch its 11 MB. */\nexport function loadFedimintSdk(): Promise<FedimintSdk> {\n return loading ??= fedimintSdkOn(BROWSER).catch((error) => { loading = undefined; throw error; });\n}\n\n/** The SDK on a platform's workers and files. */\nexport async function fedimintSdkOn(platform: FedimintPlatform): Promise<FedimintSdk> {\n const { WalletDirector } = await import(\"@fedimint/core\");\n /** A director on its own worker and database, with the mnemonic in place. */\n const director = async (database: string, mnemonic?: string) => {\n const t = await platform.transport();\n const d = new WalletDirector(t as any, database, true);\n try {\n await d.initialize(database);\n if (mnemonic && !(await d.hasMnemonicSet())) await d.setMnemonic(mnemonic.trim().split(/\\s+/));\n return { director: d, end: () => t.terminate() };\n } catch (error) { t.terminate(); throw error; }\n };\n const client = async (database: string, mnemonic: string, start: (wallet: any) => Promise<void>) => {\n const { director: d, end } = await director(database, mnemonic);\n try {\n const wallet = await d.createWallet();\n await start(wallet);\n return new RealClient(await wallet.federation.getFederationId(), wallet, d, end);\n } catch (error) { end(); throw error; }\n };\n const sdk: FedimintSdk = {\n async preview(invite) {\n // A scratch database of its own: previewing joins nothing.\n const database = fedimintDatabase(`preview-${crypto.randomUUID()}`);\n const { director: d, end } = await director(database);\n try { const preview = await d.previewFederation(invite.trim()); return federationInfo(preview.federation_id, preview.config); }\n finally { end(); await sdk.remove(database).catch(() => {}); }\n },\n join: ({ database, mnemonic, invite, recover }) => client(database, mnemonic, async (wallet) => {\n // joinFederation says false instead of throwing; its reason goes to the (silenced) logger.\n if (!(await wallet.joinFederation(invite.trim(), { clientName: CLIENT_NAME, forceRecover: recover }))) throw new Error(\"Could not join the federation: its guardians did not answer, or the invite code is not valid\");\n }),\n open: ({ database, mnemonic }) => client(database, mnemonic, (wallet) => wallet.open(CLIENT_NAME)),\n exists: (database) => platform.exists(database),\n remove: (database) => platform.remove(database),\n };\n return sdk;\n}\n/* eslint-enable @typescript-eslint/no-explicit-any */\n","import { fromBase64Url, irohDescriptor, irohRelayUrl, type BoundChannel, type FrameChannel, type NativeBinding, type NativeEndpoint } from \"@ghostly/core\";\n\n/**\n * Iroh (WISP 102) in a browser: the same QUIC/TLS session and binding as the\n * Desktop's native Iroh, but relay only. A page cannot send UDP, so every\n * packet goes through an Iroh relay over a WebSocket; the relay sees who talks\n * to whom and when, never the frames.\n *\n * The wasm (about 1 MB gzip) loads on the first chat that starts an endpoint,\n * never on the app's first load.\n */\n\n/**\n * n0's public relays, the same ones the Desktop's Iroh homes on by default. Iroh itself names them with the trailing\n * dot of a full domain name (`use1-1.relay.n0.iroh.link.`), and compares relay URLs as text; here they have none,\n * because WebKit opens no host name that ends in a dot. `BROWSER_SPELLING` is what keeps the two one relay.\n */\nexport const DEFAULT_IROH_RELAYS = [\n \"https://use1-1.relay.n0.iroh.link/\",\n \"https://euc1-1.relay.n0.iroh.link/\",\n \"https://aps1-1.relay.n0.iroh.link/\",\n \"https://usw1-1.relay.n0.iroh.link/\",\n] as const;\n\n/**\n * The spelling of every relay URL handed to the wasm Iroh (`irohRelayUrl`): the relays it homes on and a contact's\n * relay before a dial. One spelling, so a contact on this endpoint's relay is reached over the connection already\n * open to it; without the dot, so Safari can open it (a Desktop's record names its relay with the dot).\n */\nconst BROWSER_SPELLING = \"plain\";\n\n/**\n * Why a relay URL cannot be used, or null. TLS, except on this machine (a local `iroh-relay --dev`): a page\n * may not open plain WebSockets elsewhere, and the relay's operator would see traffic metadata in the clear.\n */\nexport function irohRelayProblem(value: string): string | null {\n let url: URL;\n try { url = new URL(value); } catch { return `Not a relay address: ${value}`; }\n const local = [\"localhost\", \"127.0.0.1\", \"[::1]\"].includes(url.hostname);\n if (url.protocol !== \"https:\" && !(url.protocol === \"http:\" && local)) return `Use an https:// relay address: ${value}`;\n if (url.username || url.password || url.search || url.hash || value.length > 256) return `Not a relay address: ${value}`;\n return null;\n}\n\n/** A browser endpoint's descriptor: no direct addresses, only its relay. */\nexport interface IrohWebDescriptor { id: string; relay: string | null; addresses: string[] }\n\ninterface WasmConn {\n binding(): NativeBinding;\n send(text: string): Promise<void>;\n recv(): Promise<string | undefined>;\n rtt(): number;\n close(): void;\n free(): void;\n}\ninterface WasmNode {\n address(): IrohWebDescriptor;\n connect(descriptor: unknown, timeoutMs: number): Promise<WasmConn>;\n accept(): Promise<WasmConn | undefined>;\n close(): Promise<void>;\n free(): void;\n}\nexport interface IrohWasm {\n IrohNode: { start(seed: Uint8Array, relays: string[], onlineMs: number): Promise<WasmNode> };\n}\n\nlet loaded: Promise<IrohWasm> | null = null;\n/** Loads the wasm once per page. Tests pass their own `load` (Node reads the file). */\nexport function loadIrohWasm(): Promise<IrohWasm> {\n loaded ??= import(\"@ghostly/iroh-web\").then(async module => {\n await module.default();\n return module as unknown as IrohWasm;\n }).catch(error => { loaded = null; throw error; });\n return loaded;\n}\n\nexport interface IrohWebOptions {\n /** Relay URLs, first preferred. Default: n0's public relays. */\n relays?: readonly string[];\n /** How long to wait for the first relay to answer. */\n onlineMs?: number;\n connectMs?: number;\n load?: () => Promise<IrohWasm>;\n}\n\nconst SEND_BUDGET = 120 * 1024;\nconst MAX_FRAME = 60 * 1024;\n\n/** One paired channel over one QUIC stream. Sends are queued in order. */\nclass IrohWebChannel implements FrameChannel {\n private reader: FrameChannel[\"onMessage\"] = null;\n private buffered: string[] = [];\n private queue = Promise.resolve();\n private closed = false;\n bufferedAmount = 0;\n readonly sendBudget = SEND_BUDGET;\n onClose: FrameChannel[\"onClose\"] = null;\n constructor(private conn: WasmConn, private forget: () => void) { void this.read(); }\n get onMessage() { return this.reader; }\n set onMessage(reader: FrameChannel[\"onMessage\"]) {\n this.reader = reader;\n if (reader) for (const text of this.buffered.splice(0)) reader(text);\n }\n private async read() {\n try {\n for (;;) {\n const text = await this.conn.recv();\n if (text === undefined || this.closed) break;\n if (this.reader) this.reader(text);\n else if (this.buffered.length < 64) this.buffered.push(text);\n else break;\n }\n } catch { /* A broken stream ends the channel. */ }\n this.close();\n }\n send(data: string | Uint8Array) {\n if (typeof data !== \"string\" || this.closed || !data.length || data.length > MAX_FRAME || this.bufferedAmount + data.length > SEND_BUDGET)\n throw new Error(\"Iroh channel is closed or its send budget is full\");\n this.bufferedAmount += data.length;\n this.queue = this.queue.then(() => this.closed ? undefined : this.conn.send(data))\n .catch(() => this.close()).finally(() => { this.bufferedAmount -= data.length; });\n }\n drained() { return this.queue; }\n get rttMs() { const rtt = this.conn.rtt(); return rtt >= 0 ? rtt : undefined; }\n close() {\n if (this.closed) return;\n this.closed = true; this.buffered = [];\n try { this.conn.close(); } catch { /* Already gone. */ }\n this.forget();\n this.onClose?.();\n }\n}\n\n/** A NativeEndpoint for `iroh/1`, as the Desktop's is, for one chat's transport seed. */\nexport async function createIrohWebEndpoint(seedB64: string, options: IrohWebOptions = {}): Promise<NativeEndpoint> {\n const seed = fromBase64Url(seedB64);\n if (seed.length !== 32) throw new Error(\"Invalid transport seed\");\n // Either spelling of a relay (with or without the trailing dot) homes on it under the one a browser opens.\n const relays = [...new Set((options.relays?.length ? options.relays : DEFAULT_IROH_RELAYS).map(relay => irohRelayUrl(relay, BROWSER_SPELLING)))];\n const wasm = await (options.load ?? loadIrohWasm)();\n const node = await wasm.IrohNode.start(seed, relays, options.onlineMs ?? 10_000);\n const channels = new Set<IrohWebChannel>();\n let stopped = false;\n let handler: NativeEndpoint[\"onConnection\"] = null;\n const early: BoundChannel[] = [];\n const bind = (conn: WasmConn): BoundChannel => {\n const channel: IrohWebChannel = new IrohWebChannel(conn, () => channels.delete(channel));\n channels.add(channel);\n return { channel, binding: conn.binding() };\n };\n const endpoint: NativeEndpoint = {\n transport: \"iroh/1\",\n // Relay only: the rank puts it after every direct path (WISP 100, \"Relayed\").\n descriptor: { ...node.address(), relayed: true },\n onDescriptor: null,\n get onConnection() { return handler; },\n set onConnection(value) { handler = value; if (value) for (const bound of early.splice(0)) value(bound); },\n async connect(descriptor) {\n if (stopped) throw new Error(\"Iroh endpoint is stopped\");\n const conn = await node.connect(irohDescriptor(descriptor, BROWSER_SPELLING), options.connectMs ?? 20_000);\n if (stopped) { conn.close(); throw new Error(\"Iroh endpoint is stopped\"); }\n return bind(conn);\n },\n async close() {\n if (stopped) return;\n stopped = true;\n for (const channel of [...channels]) channel.close();\n for (const bound of early.splice(0)) bound.channel.close();\n await node.close().catch(() => {});\n },\n };\n void (async () => {\n while (!stopped) {\n let conn: WasmConn | undefined;\n try { conn = await node.accept(); } catch { break; }\n if (!conn) break;\n if (stopped) { conn.close(); break; }\n const bound = bind(conn);\n const accept = endpoint.onConnection;\n if (accept) accept(bound); else if (early.length < 4) early.push(bound); else bound.channel.close();\n }\n // The relay went away for good (or the endpoint was closed): stop offering Iroh.\n if (!stopped) { endpoint.onUnavailable?.(); await endpoint.close(); }\n })();\n return endpoint;\n}\n"],"mappings":"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"}
@@ -1,7 +1,7 @@
1
- import { $n as GROUP_FILE_LIMITS, Ja as PR_CHECKS, Xa as RUN_RESULTS, Ya as PR_STATES, Za as STATUS_CARD_LIMITS, qa as ITEM_STATES, to as cardUrl } from "./src-gJqfoRKI.js";
2
- import { a as SECRET_RESULTS, i as ENGINE_READS, r as ENGINE_METHODS, t as turnRef } from "./turnRefs-zUQbFkl3.js";
1
+ import { $n as GROUP_FILE_LIMITS, Ja as PR_CHECKS, Xa as RUN_RESULTS, Ya as PR_STATES, Za as STATUS_CARD_LIMITS, qa as ITEM_STATES, to as cardUrl } from "./src-Dg1bvMNv.js";
2
+ import { a as SECRET_RESULTS, i as ENGINE_READS, r as ENGINE_METHODS, t as turnRef } from "./turnRefs-WnggXm2T.js";
3
3
  import { i as localFileError, n as EXIT, r as asCliError, t as CliError } from "./errors-B-zMxbcd.js";
4
- import { t as CONTACT_KEY } from "./apiKit-BnpVuZrZ.js";
4
+ import { t as CONTACT_KEY } from "./apiKit-B4qhnLQT.js";
5
5
  import { a as createProfile, c as listProfiles, d as profilePaths, f as selectProfile, i as checkProfileName, l as lockOwner, o as currentProfile, s as ghostlyHome, u as profileExists } from "./profiles-gPCjLOQq.js";
6
6
  import { a as provesDaemon, c as ownSocket, o as readDaemonSecret, r as newNonce, t as HELLO } from "./daemonProof-B53VnZ1Z.js";
7
7
  import { accessSync, constants, existsSync, openSync, readFileSync, renameSync, rmSync, statSync, writeFileSync } from "node:fs";
@@ -12,7 +12,7 @@ import { createInterface } from "node:readline";
12
12
  import { format } from "node:util";
13
13
  var package_default = {
14
14
  name: "@ghostlytools/cli",
15
- version: "1.3.7",
15
+ version: "1.3.9",
16
16
  type: "module",
17
17
  description: "Ghostly for bots and terminals: the app's own engine, headless on Node, behind a daemon and a `ghostly` command with JSON output.",
18
18
  license: "MIT",
@@ -942,7 +942,7 @@ var commands$8 = {
942
942
  description: "Replace the three files when they are there"
943
943
  }
944
944
  },
945
- run: async ({ options }, a) => (await import("./apps-B_6Q-Mq-.js")).initApp(a.dir, {
945
+ run: async ({ options }, a) => (await import("./apps-CpxENMgl.js")).initApp(a.dir, {
946
946
  name: options.name,
947
947
  title: options.title,
948
948
  force: options.force === true
@@ -982,7 +982,7 @@ var commands$8 = {
982
982
  description: "This version's sequence, higher than the bundle at --out's (default: one more than it)"
983
983
  }
984
984
  },
985
- run: async ({ options }, a) => (await import("./apps-B_6Q-Mq-.js")).publishApp(a.dir, keyFile(options.key, "app publish <dir> --key <file> [--out <file>] [--sequence n]"), here(options.out), options.sequence)
985
+ run: async ({ options }, a) => (await import("./apps-CpxENMgl.js")).publishApp(a.dir, keyFile(options.key, "app publish <dir> --key <file> [--out <file>] [--sequence n]"), here(options.out), options.sequence)
986
986
  },
987
987
  "app revoke": {
988
988
  method: "app.revoke",
@@ -1015,7 +1015,7 @@ var commands$8 = {
1015
1015
  description: "Allow --up-to past the bundle's own sequence (versions published from elsewhere)"
1016
1016
  }
1017
1017
  },
1018
- run: async ({ options }, a) => (await import("./apps-B_6Q-Mq-.js")).revokeApp(a.dir, keyFile(options.key, "app revoke <dir> --key <file> (--digest <digest>... | --up-to <sequence>) [--reason <text>]"), {
1018
+ run: async ({ options }, a) => (await import("./apps-CpxENMgl.js")).revokeApp(a.dir, keyFile(options.key, "app revoke <dir> --key <file> (--digest <digest>... | --up-to <sequence>) [--reason <text>]"), {
1019
1019
  digests: options.digest,
1020
1020
  upTo: options["up-to"],
1021
1021
  reason: options.reason,
@@ -1076,7 +1076,7 @@ var commands$8 = {
1076
1076
  usage: "app verify <bundle|url>",
1077
1077
  summary: "Check a .ghostlyapp as a client would, from a file or an https URL: its manifest and digest, or refused (exit 1) with the reason, revoked when the ghostly-revoke.json beside it revokes this version",
1078
1078
  args: ["bundle"],
1079
- run: async (_, a) => (await import("./apps-B_6Q-Mq-.js")).verifyApp(a.bundle)
1079
+ run: async (_, a) => (await import("./apps-CpxENMgl.js")).verifyApp(a.bundle)
1080
1080
  },
1081
1081
  "store sign": {
1082
1082
  method: "store.sign",
@@ -1093,7 +1093,7 @@ var commands$8 = {
1093
1093
  description: "The folder to write ghostly-store.json and ghostly-store.sig in (default: the index's)"
1094
1094
  }
1095
1095
  },
1096
- run: async ({ options }, a) => (await import("./apps-B_6Q-Mq-.js")).signStore(a.index, keyFile(options.key, "store sign <index> --key <file> [--out <dir>]"), here(options.out))
1096
+ run: async ({ options }, a) => (await import("./apps-CpxENMgl.js")).signStore(a.index, keyFile(options.key, "store sign <index> --key <file> [--out <dir>]"), here(options.out))
1097
1097
  }
1098
1098
  };
1099
1099
  //#endregion
@@ -2283,20 +2283,22 @@ var commands$1 = {
2283
2283
  },
2284
2284
  "chat pay": {
2285
2285
  method: "chat.pay",
2286
- usage: "chat pay <chat> <sats> [--memo t] [--network testnet] [--confirm-real]",
2286
+ usage: "chat pay <chat> <sats> [--memo t] [--network testnet] [--confirm-real] [--force]",
2287
2287
  summary: "Send ecash to a contact (Mainnet needs --confirm-real)",
2288
2288
  args: ["chat", "amount"],
2289
2289
  options: {
2290
2290
  memo,
2291
2291
  network: net,
2292
- "confirm-real": confirmReal
2292
+ "confirm-real": confirmReal,
2293
+ force
2293
2294
  },
2294
2295
  params: ({ options }, a) => ({
2295
2296
  chat: a.chat,
2296
2297
  amount: sats(a.amount),
2297
2298
  memo: options.memo,
2298
2299
  network: options.network,
2299
- confirmReal: options["confirm-real"] === true
2300
+ confirmReal: options["confirm-real"] === true,
2301
+ force: options.force === true
2300
2302
  })
2301
2303
  },
2302
2304
  "chat pay-request": {
@@ -2327,7 +2329,7 @@ var commands$1 = {
2327
2329
  },
2328
2330
  "chat request": {
2329
2331
  method: "chat.request",
2330
- usage: "chat request <chat> <sats> [--memo t] [--method m] [--rail cashu|lightning] [--network testnet] [--card <id>]",
2332
+ usage: "chat request <chat> <sats> [--memo t] [--method m] [--rail cashu|lightning] [--network testnet] [--card <id>] [--force]",
2331
2333
  summary: "Ask a contact to pay",
2332
2334
  args: ["chat", "amount"],
2333
2335
  options: {
@@ -2341,7 +2343,8 @@ var commands$1 = {
2341
2343
  description: "cashu or lightning"
2342
2344
  },
2343
2345
  network: net,
2344
- card
2346
+ card,
2347
+ force
2345
2348
  },
2346
2349
  params: ({ options }, a) => ({
2347
2350
  chat: a.chat,
@@ -2350,7 +2353,8 @@ var commands$1 = {
2350
2353
  method: options.method,
2351
2354
  rail: options.rail,
2352
2355
  network: options.network,
2353
- card: options.card
2356
+ card: options.card,
2357
+ force: options.force === true
2354
2358
  })
2355
2359
  },
2356
2360
  "lightning default": {
@@ -3121,10 +3125,20 @@ function runExec(command, event) {
3121
3125
  child.stdin.end(JSON.stringify(event) + "\n");
3122
3126
  });
3123
3127
  }
3124
- async function post(url, event) {
3125
- for (let attempt = 0; attempt < 3; attempt++) {
3128
+ /** The longest wait between two tries of a webhook that is not answering. */
3129
+ var RETRY_MAX_MS = 6e4;
3130
+ /**
3131
+ * Posts one event; resolves true once the bridge took it, false when it refused it (a 4xx or a redirect, three times).
3132
+ * A bridge that is not there (refused connection, timeout, 5xx, 408, 429) has not handled the event: it is tried again,
3133
+ * waiting up to a minute between tries, for as long as it takes, so the cursor never moves past it. Before, three tries
3134
+ * in 3.5 s and the cursor moved on: a bridge restarting for longer lost every event of the time it was down, for good.
3135
+ */
3136
+ async function post(url, event, retryMs) {
3137
+ let wait = retryMs, holding = false;
3138
+ for (let attempt = 0;; attempt++) {
3139
+ let down = true;
3126
3140
  try {
3127
- if ((await fetch(url, {
3141
+ const response = await fetch(url, {
3128
3142
  method: "POST",
3129
3143
  redirect: "manual",
3130
3144
  headers: {
@@ -3134,11 +3148,22 @@ async function post(url, event) {
3134
3148
  },
3135
3149
  body: JSON.stringify(event),
3136
3150
  signal: AbortSignal.timeout(1e4)
3137
- })).ok) return true;
3151
+ });
3152
+ response.body?.cancel().catch(() => {});
3153
+ if (response.ok) {
3154
+ if (holding) process.stderr.write(`ghostly: the webhook at ${url.href} answers again: event ${event.seq} taken\n`);
3155
+ return true;
3156
+ }
3157
+ down = response.status >= 500 || response.status === 408 || response.status === 429;
3138
3158
  } catch {}
3139
- await new Promise((resolve) => setTimeout(resolve, 500 * 2 ** attempt));
3159
+ if (!down && attempt >= 2) return false;
3160
+ if (down && attempt >= 2 && !holding) {
3161
+ holding = true;
3162
+ process.stderr.write(`ghostly: the webhook at ${url.href} is not answering: holding event ${event.seq} (${event.type}) until it does\n`);
3163
+ }
3164
+ await new Promise((resolve) => setTimeout(resolve, wait));
3165
+ wait = Math.min(wait * 2, RETRY_MAX_MS);
3140
3166
  }
3141
- return false;
3142
3167
  }
3143
3168
  /**
3144
3169
  * A handler that takes events in order, one at a time. A step that throws (the output closed, the allowlist's lookup,
@@ -3167,7 +3192,7 @@ function eventHandler(options) {
3167
3192
  const code = await runExec(options.exec, event);
3168
3193
  if (code !== 0) process.stderr.write(`ghostly: --exec exited ${code} on event ${event.seq} (${event.type})\n`);
3169
3194
  }
3170
- if (webhook && !await post(webhook, event)) process.stderr.write(`ghostly: the webhook did not take event ${event.seq} (${event.type})\n`);
3195
+ if (webhook && !await post(webhook, event, options.retryMs ?? 500)) process.stderr.write(`ghostly: the webhook did not take event ${event.seq} (${event.type})\n`);
3171
3196
  if (full) await options.drained?.();
3172
3197
  if (options.cursor) writeCursor(options.cursor, event.seq);
3173
3198
  }).catch((error) => {
@@ -3301,8 +3326,8 @@ var VERSION = package_default.version;
3301
3326
  * command a running daemon answers loads none of them, and starts about as fast as Node does. Statically imported, they
3302
3327
  * cost every command ~100 ms before its socket was opened.
3303
3328
  */
3304
- var engineApi = () => import("./api-BL0vpWGN.js");
3305
- var engineHost = () => import("./host-H0IYKNoK.js");
3329
+ var engineApi = () => import("./api-Cp9GxjUO.js");
3330
+ var engineHost = () => import("./host-DPy0eaH9.js");
3306
3331
  var pretty = false;
3307
3332
  function print(value) {
3308
3333
  process.stdout.write(JSON.stringify(value, null, pretty ? 2 : void 0) + "\n");
@@ -3766,7 +3791,7 @@ async function profileCommand(sub, argv) {
3766
3791
  const [file, name] = parsed.positionals;
3767
3792
  if (!file || !name) throw new CliError("usage", "ghostly profile restore <file> <new profile> [--passphrase-file f] [--use]");
3768
3793
  noMoreThan(2, parsed.positionals, "profile restore <file> <new profile> [--passphrase-file f] [--use]");
3769
- const { backupFileProtection, restoreProfile } = await import("./backup-BqtsbjOT.js");
3794
+ const { backupFileProtection, restoreProfile } = await import("./backup-CqM-0-2v.js");
3770
3795
  const protection = await backupFileProtection(file);
3771
3796
  const paths = await restoreProfile(g.home, checkProfileName(name), file, protection === "none" ? void 0 : backupPassphrase(parsed.options["passphrase-file"], RESTORE_PASSPHRASE));
3772
3797
  if (parsed.options.use) selectProfile(g.home, name);
@@ -4305,7 +4330,7 @@ async function textCommand(name, argv) {
4305
4330
  };
4306
4331
  if (spec.target === "group") params.mentions = parsed.options.mention ?? [];
4307
4332
  if (parsed.options.reply !== void 0) params.reply = parsed.options.reply;
4308
- if (!spec.message) Object.assign(params, (await import("./buttons-CB1opfZP.js")).buttonsOf(parsed.options));
4333
+ if (!spec.message) Object.assign(params, (await import("./buttons-CJqNkG-Q.js")).buttonsOf(parsed.options));
4309
4334
  print(await withSession(g, (s) => {
4310
4335
  if (spec.target === "chat") {
4311
4336
  params.wait = parsed.options.wait ?? (s.mode === "one-shot" ? spec.message ? "confirmed" : "sent" : "none");
@@ -4589,4 +4614,4 @@ async function run(argv) {
4589
4614
  //#endregion
4590
4615
  export { run };
4591
4616
 
4592
- //# sourceMappingURL=main-DiCC-Vjq.js.map
4617
+ //# sourceMappingURL=main-Dt7xrqtb.js.map