@ghostlytools/cli 1.0.3 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/README.md +1 -1
  2. package/dist/assets/bark_ffi_wasm_bg-EY2uc0Bx.js.map +1 -1
  3. package/dist/assets/bitcoindevkit_bg-B-iLWrmX.js.map +1 -1
  4. package/dist/assets/{fileBytes-CpP9Vj7s.js → fileBytes-BQ84L9Fe.js} +2 -2
  5. package/dist/assets/{fileBytes-CpP9Vj7s.js.map → fileBytes-BQ84L9Fe.js.map} +1 -1
  6. package/dist/assets/{fileBytes-Br_MpeNT.js → fileBytes-DxiJHmTk.js} +3 -3
  7. package/dist/assets/fileBytes-DxiJHmTk.js.map +1 -0
  8. package/dist/assets/{fileBytesIdb-vubSXPoO.js → fileBytesIdb-DQkQ10PS.js} +3 -3
  9. package/dist/assets/{fileBytesIdb-vubSXPoO.js.map → fileBytesIdb-DQkQ10PS.js.map} +1 -1
  10. package/dist/assets/{fileBytesOpfs-6iSa6u2K.js → fileBytesOpfs-BFfR_68b.js} +3 -3
  11. package/dist/assets/{fileBytesOpfs-6iSa6u2K.js.map → fileBytesOpfs-BFfR_68b.js.map} +1 -1
  12. package/dist/assets/{ghostly_iroh_web-CPa_YEfb.js → ghostly_iroh_web-Dvzh3Y6E.js} +32 -32
  13. package/dist/assets/{ghostly_iroh_web-CPa_YEfb.js.map → ghostly_iroh_web-Dvzh3Y6E.js.map} +1 -1
  14. package/dist/assets/ghostly_iroh_web_bg.wasm +0 -0
  15. package/dist/assets/{hyperdhtRelay-VM9STKLd.js → hyperdhtRelay-CVLCuqVr.js} +3 -3
  16. package/dist/assets/{hyperdhtRelay-VM9STKLd.js.map → hyperdhtRelay-CVLCuqVr.js.map} +1 -1
  17. package/dist/assets/{idb-DIN_Miwb.js → idb-Co2lObpU.js} +4 -4
  18. package/dist/assets/idb-Co2lObpU.js.map +1 -0
  19. package/dist/assets/{mainline-CcO2f8jy.js → mainline-CKFpwXcB.js} +2 -2
  20. package/dist/assets/{mainline-CcO2f8jy.js.map → mainline-CKFpwXcB.js.map} +1 -1
  21. package/dist/assets/{restoredRows-tVxgyG23.js → restoredRows-C1O9FfA4.js} +38 -3
  22. package/dist/assets/restoredRows-C1O9FfA4.js.map +1 -0
  23. package/dist/assets/{server-BnNXYzR9.js → server-vGoe9MET.js} +9460 -784
  24. package/dist/assets/server-vGoe9MET.js.map +1 -0
  25. package/dist/assets/{src-7sdQpCoi.js → src-CJdPTl7Q.js} +4216 -1305
  26. package/dist/assets/src-CJdPTl7Q.js.map +1 -0
  27. package/dist/ghostly.mjs +374 -45
  28. package/dist/ghostly.mjs.map +1 -1
  29. package/package.json +2 -1
  30. package/dist/assets/fileBytes-Br_MpeNT.js.map +0 -1
  31. package/dist/assets/idb-DIN_Miwb.js.map +0 -1
  32. package/dist/assets/restoredRows-tVxgyG23.js.map +0 -1
  33. package/dist/assets/server-BnNXYzR9.js.map +0 -1
  34. package/dist/assets/src-7sdQpCoi.js.map +0 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ghostlytools/cli",
3
- "version": "1.0.3",
3
+ "version": "1.1.0",
4
4
  "type": "module",
5
5
  "description": "Ghostly for bots and terminals: the app's own engine, headless on Node, behind a daemon and a `ghostly` command with JSON output.",
6
6
  "license": "MIT",
@@ -50,6 +50,7 @@
50
50
  "@scure/bip39": "2.4.0",
51
51
  "@scure/btc-signer": "2.4.1",
52
52
  "@secondts/bark": "0.25.0",
53
+ "@serenity-kit/opaque": "1.1.0",
53
54
  "@synonymdev/pubky": "0.14.0",
54
55
  "@tetherto/wdk-wallet-evm": "1.0.0-beta.19",
55
56
  "b4a": "1.9.0",
@@ -1 +0,0 @@
1
- {"version":3,"file":"fileBytes-Br_MpeNT.js","names":[],"sources":["../../../browser/src/shared/fileBytesCommon.ts","../../../browser/src/shared/fileBytes.ts"],"sourcesContent":["/** What `fileBytes.ts` and its worker share: the worker is built alone, so this imports nothing. */\n\n/** Reads and copies move this much at a time. */\nexport const FILE_BYTES_STEP = 1024 * 1024;\n\nconst BASE64URL = \"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_\";\n\n/** base64url without padding (the worker has no @ghostly/core). */\nexport function digestText(bytes: Uint8Array): string {\n let out = \"\";\n for (let i = 0; i < bytes.length; i += 3) {\n const n = (bytes[i] << 16) | ((bytes[i + 1] ?? 0) << 8) | (bytes[i + 2] ?? 0);\n out += BASE64URL[(n >> 18) & 63] + BASE64URL[(n >> 12) & 63];\n if (i + 1 < bytes.length) out += BASE64URL[(n >> 6) & 63];\n if (i + 2 < bytes.length) out += BASE64URL[n & 63];\n }\n return out;\n}\n","import { sha256 } from \"@noble/hashes/sha2.js\";\nimport { databaseName } from \"./idb\";\nimport { digestText, FILE_BYTES_STEP } from \"./fileBytesCommon\";\n\n/**\n * Where the bytes of a file live when they may be more than memory holds: a file sent or received in a chat.\n * Every implementation keeps one file per id under the profile's space (`databaseName()`), is written in\n * order (`append` at the stored size, `truncate` to go back), and is read in ranges, so no caller ever needs\n * the whole file at once.\n *\n * - `opfs`: the origin-private file system, written by a worker with sync access handles (web, extension).\n * - `native`: a real file in the app's data folder, through Rust (Desktop).\n * - `idb`: 1 MiB records in IndexedDB, where neither of those exists (and in tests).\n */\nexport interface FileBytes {\n readonly kind: FileBytesKind;\n /** Copies a picked file in, hashing it on the way. Resolves to its SHA-256, base64url. */\n stage(id: string, source: Blob, onProgress?: (copied: number) => void): Promise<string>;\n /** Writes at `offset`, which must be the stored size. Resolves once written; `flush` makes it durable. */\n append(id: string, offset: number, bytes: Uint8Array): Promise<void>;\n /** What was appended so far survives a crash. */\n flush(id: string): Promise<void>;\n /** Writing is over for now: flushed, and the file let go so it can be read elsewhere. */\n close(id: string): Promise<void>;\n /** Bytes stored, or null when there is no such file. */\n size(id: string): Promise<number | null>;\n /** Cuts the file to `size` bytes (a transfer going back to its last durable point). */\n truncate(id: string, size: number): Promise<void>;\n /** At most `length` bytes from `offset`; fewer only at the end of the file. */\n read(id: string, offset: number, length: number): Promise<Uint8Array>;\n /** SHA-256 of what is stored, base64url: read back from storage, so it checks what was written. */\n digest(id: string): Promise<string>;\n /**\n * The file as a Blob to show or save, backed by storage rather than memory, or null where this platform\n * cannot hand one out (Desktop, above `NATIVE_BLOB_MAX`: saved with `save` instead).\n */\n blob(id: string, type: string): Promise<Blob | null>;\n remove(id: string): Promise<void>;\n /** Removes every file whose id starts with `prefix` (\"\" for all of this space). */\n removeWhere(prefix: string): Promise<void>;\n /** Bytes this device can still take for files, or null when the platform does not say. */\n room(): Promise<number | null>;\n /** Saves a copy where the person chooses, where the platform has a dialog for it. False when they cancelled. */\n save?(id: string, name: string): Promise<boolean>;\n /**\n * A URL a `<video>` or `<audio>` plays the stored file from, served in ranges by the platform (Desktop, for a file\n * too large for `blob`), or null when there is no such file. The URL works until `release`.\n */\n stream?(id: string, type: string): Promise<FileStream | null>;\n /** Removes another profile's folder (a deleted profile). IndexedDB pieces go with the profile's database. */\n dropSpace?(space: string): Promise<void>;\n /**\n * The same storage for another profile's space: a backup of a profile that is not the active one reads its files\n * through it, and a restore writes a new profile's files through it before that profile exists. Absent where files\n * are pieces in the profile's own database (`idb`): those are read and written with that database.\n */\n forSpace?(space: string): FileBytes;\n}\n\nexport type FileBytesKind = \"opfs\" | \"native\" | \"idb\";\n\n/** A stored file being served to a media element: its URL, and how to stop serving it. */\nexport interface FileStream {\n url: string;\n release(): void;\n}\n\n/** Files sent from memory-sized sources stay whole in IndexedDB; anything larger goes through a `FileBytes`. */\nexport const SMALL_FILE_BYTES = 16 * 1024 * 1024;\n\nexport { FILE_BYTES_STEP, digestText } from \"./fileBytesCommon\";\n\nconst ID = /^[A-Za-z0-9_-]{1,200}$/;\nconst SPACE = /^[A-Za-z0-9_.-]{1,100}$/;\n\n/** A file id every backend accepts as a file name as it is: never a path. */\nexport function checkFileId(id: string): string {\n if (!ID.test(id)) throw new Error(\"Invalid file id\");\n return id;\n}\n\n/** This profile's folder name. */\nexport function fileSpace(): string {\n const space = databaseName();\n if (!SPACE.test(space)) throw new Error(\"Invalid profile space\");\n return space;\n}\n\n/**\n * SHA-256 of a Blob, read a step at a time: memory stays at one step. Read with `slice().arrayBuffer()`, not\n * `stream()`: in WebKit a Blob kept in IndexedDB that reads fine in slices failed as a stream (\"The object can not be\n * found here\"), which failed a voice note after all its bytes had arrived.\n */\nexport async function blobDigest(blob: Blob, onProgress?: (done: number) => void): Promise<string> {\n const hash = sha256.create();\n for (let done = 0; done < blob.size;) {\n const part = new Uint8Array(await blob.slice(done, Math.min(blob.size, done + FILE_BYTES_STEP)).arrayBuffer());\n if (!part.length) throw new Error(\"The file is shorter than it says\");\n hash.update(part);\n done += part.length;\n onProgress?.(done);\n }\n return digestText(hash.digest());\n}\n\ntype Maker = () => Promise<FileBytes | null>;\nconst makers = new Map<FileBytesKind, Maker>([\n [\"opfs\", () => import(\"./fileBytesOpfs\").then((m) => m.OpfsFileBytes.open())],\n [\"idb\", () => import(\"./fileBytesIdb\").then((m) => new m.IdbFileBytes())],\n]);\nconst made = new Map<FileBytesKind, Promise<FileBytes | null>>();\nlet preferred: FileBytesKind[] = [\"opfs\", \"idb\"];\n\n/** How a backend is made. The platform registers its own (Desktop: `native`) before anything uses it. */\nexport function registerFileBytes(kind: FileBytesKind, make: Maker, prefer = false): void {\n makers.set(kind, make);\n made.delete(kind);\n if (prefer) preferred = [kind, ...preferred.filter((k) => k !== kind)];\n}\n\n/** The backend of this kind, or null where this platform has none. */\nexport function fileBytesOf(kind: FileBytesKind): Promise<FileBytes | null> {\n let backend = made.get(kind);\n if (!backend) {\n const make = makers.get(kind);\n backend = make ? make().catch(() => null) : Promise.resolve(null);\n made.set(kind, backend);\n }\n return backend;\n}\n\n/** Where new files go: the first backend this platform has. */\nexport async function fileBytes(): Promise<FileBytes> {\n for (const kind of preferred) {\n const backend = await fileBytesOf(kind);\n if (backend) return backend;\n }\n throw new Error(\"This device has no storage for files\");\n}\n\n/** \"Clear all data\" and a deleted chat: the bytes of its files go with it, wherever they are. */\nexport async function removeFileBytes(prefix = \"\"): Promise<void> {\n await Promise.all([...makers.keys()].map(async (kind) => (await fileBytesOf(kind))?.removeWhere(prefix)));\n}\n\n/** A deleted profile's files, wherever they are. */\nexport async function dropFileSpace(space: string): Promise<void> {\n if (!SPACE.test(space)) throw new Error(\"Invalid profile space\");\n await Promise.all([...makers.keys()].map(async (kind) => (await fileBytesOf(kind))?.dropSpace?.(space)));\n}\n\n/** Tests: forget what was made and which backend comes first. */\nexport function resetFileBytes(order: FileBytesKind[] = [\"opfs\", \"idb\"]): void {\n made.clear();\n preferred = order;\n}\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"idb-DIN_Miwb.js","names":[],"sources":["../../../browser/src/shared/idb.ts"],"sourcesContent":["/**\n * The extension's one IndexedDB database. The peer (offscreen document) and\n * the pages share an origin, so both open it: that is also how file contents\n * move between them without squeezing through runtime messages.\n */\nlet dbName = \"ghostly\";\n\n/** Before first use: keeps profiles that share a browser storage area apart (Desktop's GHOSTLY_PROFILE). */\nexport function setDatabaseName(name: string): void {\n dbName = name;\n}\n/** The peer database this page uses: its profile's (WISP 04). */\nexport function databaseName(): string {\n return dbName;\n}\n/** The schema this build reads and writes. A database stored at a higher one is a newer build's: it is never opened. */\nexport const DB_VERSION = 12;\n\n/**\n * Why the profile's database did not open. `newer`: a newer Ghostly stored it (IndexedDB never opens a database below\n * its stored version). `blocked`: another window still holds it open at an older version. `full`: no storage space.\n * `denied`: the browser gives this page no storage (a private window, storage blocked for the site). `failed`:\n * anything else, a damaged database included (browsers do not tell that one apart).\n */\nexport type ProfileOpenReason = \"newer\" | \"blocked\" | \"full\" | \"denied\" | \"failed\";\n\n/** What the pages are told (a plain object: it crosses the extension's port), and what a bot reads in `details`. */\nexport interface ProfileOpenFailure {\n reason: ProfileOpenReason;\n /** The schema version this build reads (`DB_VERSION`). */\n supportedVersion: number;\n /** The version the database is stored at, when it could be read (`newer`). */\n storedVersion?: number;\n /** What the browser said, in its own words. */\n detail?: string;\n}\n\n/** The profile's database did not open. Nothing was changed, and nothing is ever deleted or reset because of it. */\nexport class ProfileOpenError extends Error {\n constructor(readonly failure: ProfileOpenFailure) {\n super(profileOpenMessage(failure));\n this.name = \"ProfileOpenError\";\n }\n}\n\n/** The failure in plain English, for logs, the CLI and anything that shows an engine error as it came. */\nexport function profileOpenMessage(failure: ProfileOpenFailure): string {\n const could = \"Ghostly could not open this profile's data\";\n switch (failure.reason) {\n case \"newer\":\n return `This profile was last used by a newer version of Ghostly (its data is version ${failure.storedVersion ?? \"above \" + failure.supportedVersion}, this version reads up to ${failure.supportedVersion}). Update Ghostly to open it.`;\n case \"blocked\": return `${could}: another Ghostly window or tab still has it open with an older version. Close it and try again.`;\n case \"full\": return `${could}: this device is out of storage space. Free some space and try again.`;\n case \"denied\": return `${could}: storage is not allowed here (a private window, or storage blocked for this site).`;\n default: return `${could}${failure.detail ? `: ${failure.detail}` : \"\"}`;\n }\n}\n\n/** What the start of the engine failed with, as the failure the pages show: a database that did not open, or anything else. */\nexport function profileOpenFailure(error: unknown): ProfileOpenFailure {\n if (error instanceof ProfileOpenError) return error.failure;\n const detail = error instanceof Error ? error.message : String(error);\n return { reason: \"failed\", supportedVersion: DB_VERSION, ...(detail ? { detail } : {}) };\n}\n\n/** How long an open that another window blocks may wait for that window to let go before it is said to be blocked. */\nexport const OPEN_TIMINGS = { blockedMs: 10_000 };\n\nfunction failureOf(error: unknown): ProfileOpenFailure {\n const name = (error as { name?: unknown } | null)?.name;\n const message = (error as { message?: unknown } | null)?.message;\n const detail = [typeof name === \"string\" ? name : \"\", typeof message === \"string\" ? message : \"\"].filter(Boolean).join(\": \");\n const reason: ProfileOpenReason = name === \"VersionError\" ? \"newer\"\n : name === \"QuotaExceededError\" ? \"full\"\n // Firefox's private windows of old, storage blocked for the site, or no IndexedDB at all.\n : name === \"BlockedError\" ? \"blocked\"\n : name === \"SecurityError\" || name === \"InvalidStateError\" || name === \"ReferenceError\" || name === \"TypeError\" ? \"denied\"\n : \"failed\";\n return { reason, supportedVersion: DB_VERSION, ...(detail ? { detail } : {}) };\n}\n\n/**\n * The version a database is stored at, read without changing it: an open that names no version opens what is there.\n * Only for a database known to exist (an open without a version would make a missing one).\n */\nfunction storedVersion(name: string): Promise<number | undefined> {\n return new Promise((resolve) => {\n try {\n const request = indexedDB.open(name);\n // It did not exist after all: leave nothing behind.\n request.onupgradeneeded = () => { try { request.transaction?.abort(); } catch { /* already over */ } };\n request.onsuccess = () => { const { version } = request.result; request.result.close(); resolve(version); };\n request.onerror = request.onblocked = () => resolve(undefined);\n } catch { resolve(undefined); }\n });\n}\n/** The messages store's index of card messages (`card.kind`): what `db.getCardMessages` reads. */\nexport const CARD_INDEX = \"byCardKind\";\n\nexport const STORES = {\n links: \"links\",\n messages: \"messages\",\n services: \"services\",\n settings: \"settings\",\n files: \"files\",\n proofs: \"proofs\",\n payments: \"payments\",\n quotes: \"quotes\",\n walletTx: \"walletTx\",\n melts: \"melts\",\n /** Swaps at a mint that have not finished (`PendingSwap`): what gets their outputs back when an answer is missed. */\n swaps: \"swaps\",\n intents: \"paymentIntents\",\n /** Private groups (WISP 900): membership chain, epoch secrets and my own recent messages, by group id. */\n groups: \"groups\",\n /** Files kept in 1 MiB pieces where the platform has no file system for them (see `fileBytesIdb.ts`). */\n fileChunks: \"fileChunks\",\n /**\n * What changes about a stored file after it is stored, by file id. A `files` record is written once and never\n * again: WebKit loses the Blob of a record written back with the Blob it read (see `fileStore`).\n */\n fileState: \"fileState\",\n} as const;\n\n/**\n * A file's contents. Metadata travels with the chat message; this is only the bytes: in `blob` for a file\n * small enough to hold whole, or in the platform's file storage (`bytes`, see `fileBytes.ts`) under the same id.\n * Ids are `<link id>-out-<random>` for files we send and `<link id>-in-<random>` for\n * files we receive, both chosen here. Files stored before that are `<link id>-<wire id>`\n * without `direction` and are still read under their old id.\n */\nexport interface StoredFile {\n id: string;\n linkId: string;\n blob?: Blob;\n /** Where the bytes are when there is no `blob`. */\n bytes?: import(\"./fileBytes\").FileBytesKind;\n createdAt: number;\n direction?: \"in\" | \"out\";\n /** The id the file had on the data link. */\n wireId?: string;\n digest?: string;\n metadata?: { name: string; size: number; mime: string; timestamp: number; voice?: import(\"@ghostly/core\").VoiceMeta; video?: import(\"@ghostly/core\").VideoMeta; image?: import(\"@ghostly/core\").ImageMeta };\n transfer?: { state: \"transferring\" | \"done\" | \"failed\"; transferred: number; size: number; error?: string };\n /** files/3: the transfer's own record (`@ghostly/core` `FileTransferRecord`), kept so it resumes after a restart. */\n wire3?: import(\"@ghostly/core\").FileTransferRecord;\n}\n\n/** The fields of a stored file that change after it is stored: kept in `STORES.fileState`, read over the record's own. */\ntype FileStateFields = Partial<Pick<StoredFile, \"bytes\" | \"digest\" | \"transfer\" | \"wire3\">>;\ntype FileState = { id: string } & FileStateFields;\n\nlet dbPromise: Promise<IDBDatabase> | null = null;\n\nexport function openDb(): Promise<IDBDatabase> {\n dbPromise ??= new Promise((resolve, reject) => {\n let settled = false;\n let blocked: ReturnType<typeof setTimeout> | undefined;\n // Said once, as what it is (`ProfileOpenError`): the engine does not start, and the pages show why.\n const fail = (error: unknown) => {\n if (settled) return;\n settled = true;\n clearTimeout(blocked);\n const failure = failureOf(error);\n if (failure.reason !== \"newer\") { reject(new ProfileOpenError(failure)); return; }\n void storedVersion(dbName).then((version) => reject(new ProfileOpenError({ ...failure, ...(version !== undefined ? { storedVersion: version } : {}) })));\n };\n let request: IDBOpenDBRequest;\n try {\n request = indexedDB.open(dbName, DB_VERSION);\n } catch (error) {\n fail(error);\n return;\n }\n request.onupgradeneeded = () => {\n const db = request.result;\n const has = (name: string) => db.objectStoreNames.contains(name);\n if (!has(STORES.links)) db.createObjectStore(STORES.links, { keyPath: \"id\" });\n if (!has(STORES.services)) db.createObjectStore(STORES.services, { keyPath: \"id\" });\n if (!has(STORES.settings)) db.createObjectStore(STORES.settings);\n if (!has(STORES.messages)) {\n db.createObjectStore(STORES.messages, { keyPath: [\"linkId\", \"id\"] }).createIndex(\"byLink\", \"linkId\");\n }\n if (!has(STORES.proofs)) db.createObjectStore(STORES.proofs, { keyPath: \"secret\" });\n if (!has(STORES.payments)) db.createObjectStore(STORES.payments, { keyPath: \"id\" });\n if (!has(STORES.quotes)) db.createObjectStore(STORES.quotes, { keyPath: \"quote\" });\n if (!has(STORES.walletTx)) db.createObjectStore(STORES.walletTx, { keyPath: \"id\" });\n // v5: Lightning payments the mint has not settled yet, and the proofs they hold.\n if (!has(STORES.melts)) db.createObjectStore(STORES.melts, { keyPath: \"quote\" });\n if (!has(STORES.intents)) db.createObjectStore(STORES.intents, { keyPath: \"review.id\" });\n if (!has(STORES.files)) {\n db.createObjectStore(STORES.files, { keyPath: \"id\" }).createIndex(\"byLink\", \"linkId\");\n }\n // v7: private groups.\n if (!has(STORES.groups)) db.createObjectStore(STORES.groups, { keyPath: \"id\" });\n // v8: files in pieces, for platforms without the origin-private file system.\n if (!has(STORES.fileChunks)) db.createObjectStore(STORES.fileChunks, { keyPath: [\"id\", \"index\"] });\n // v9: what changes about a stored file, apart from its record (see `fileStore`).\n if (!has(STORES.fileState)) db.createObjectStore(STORES.fileState, { keyPath: \"id\" });\n // v10: a chat's messages in time order, so its latest page is read without the rest.\n const messages = request.transaction!.objectStore(STORES.messages);\n if (!messages.indexNames.contains(\"byLinkTime\")) messages.createIndex(\"byLinkTime\", [\"linkId\", \"timestamp\"]);\n // v11: the messages that carry a status card, by the card's kind, across every chat and group (the Tasks board).\n // A message without a card has no such key and is not in it, so the index is as small as the cards are few.\n if (!messages.indexNames.contains(CARD_INDEX)) messages.createIndex(CARD_INDEX, \"card.kind\");\n // v12: swaps at a mint, written down before the mint is asked.\n if (!has(STORES.swaps)) db.createObjectStore(STORES.swaps, { keyPath: \"id\" });\n };\n request.onsuccess = () => {\n // Opened after it was said to be blocked: nobody uses this connection, and it must not block the next open.\n if (settled) { request.result.close(); return; }\n settled = true;\n clearTimeout(blocked);\n // Let the other context upgrade the schema instead of blocking it.\n request.result.onversionchange = () => {\n request.result.close();\n dbPromise = null;\n };\n resolve(request.result);\n };\n // The event's default aborts nothing here, but an unhandled one is logged as an uncaught error in some browsers.\n request.onerror = (event) => { event.preventDefault?.(); fail(request.error); };\n // Another window holds the database at an older version and has not let go (ours do, on `versionchange`). The\n // open stays pending for as long as that lasts: before, the engine waited with it, silently and for good.\n request.onblocked = () => {\n blocked ??= setTimeout(() => fail({ name: \"BlockedError\", message: \"another connection holds an older version open\" }), OPEN_TIMINGS.blockedMs);\n };\n });\n return dbPromise;\n}\n\n/** The stores \"Clear all data\" empties: chats and their keys, messages, files, shared apps, groups. */\nexport const PROFILE_STORES = [STORES.links, STORES.messages, STORES.files, STORES.fileState, STORES.fileChunks, STORES.services, STORES.groups] as const;\n\n/**\n * Records of the settings store that belong to the profile, not to a wallet: its DID and its key, its identity proofs\n * with their keys and revocations, its own Nostr data, the public profiles it read. A list of what goes rather than of\n * what stays, on purpose: every other record of that store is a wallet's (seeds, configuration, pending operations),\n * and a wallet record a newer version adds must never be deleted by mistake.\n */\nexport const PROFILE_SETTINGS_KEYS = [\"profileDid\", \"identityProofs\", \"identityRevocations\", \"nostrSocial\", \"publicProfiles\"] as const;\n\n/**\n * The fields of the peer's `settings` record that are a wallet's, kept by \"Clear all data\": the mint list, the first-run\n * wallet setup and the backup reminders. Everything else in it is the profile's (name, picture, network settings,\n * storage credentials for held items, the push subscription and who holds it) and goes.\n */\nexport const WALLET_SETTINGS_FIELDS = [\"mints\", \"mintsInitialized\", \"walletSetup\", \"backupReminders\"] as const;\n\n/**\n * \"Clear all data\" (WISP 04): everything of the profile in its peer database except its wallets. The chats' stores are\n * emptied, the profile's records of the settings store are deleted and the `settings` record keeps only its wallet\n * fields. The wallets (proofs, payments, quotes, history, payment journal, wallet records) stay: ecash is money, and\n * nothing else holds a copy of it. The caller stops the peer first, or it may write back what was just deleted.\n */\nexport async function clearProfileStores(): Promise<void> {\n if (typeof indexedDB === \"undefined\") return;\n const db = await openDb();\n const names = [...PROFILE_STORES, STORES.settings].filter((name) => db.objectStoreNames.contains(name));\n const tx = db.transaction(names, \"readwrite\");\n for (const name of names) if (name !== STORES.settings) tx.objectStore(name).clear();\n if (names.includes(STORES.settings)) {\n const settings = tx.objectStore(STORES.settings);\n for (const key of PROFILE_SETTINGS_KEYS) settings.delete(key);\n const read = settings.get(\"settings\");\n read.onsuccess = () => {\n const record = read.result as Record<string, unknown> | undefined;\n if (!record || typeof record !== \"object\") return;\n const kept = Object.fromEntries(WALLET_SETTINGS_FIELDS.filter((field) => field in record).map((field) => [field, record[field]]));\n settings.put(kept, \"settings\");\n };\n }\n await new Promise<void>((resolve, reject) => {\n tx.oncomplete = () => resolve();\n tx.onerror = tx.onabort = () => reject(tx.error);\n });\n}\n\n/** Deletes a database of the origin by name (an SDK's own), as far as the browser lets it: blocked or failed, it gives up quietly. */\nexport function deleteDatabase(name: string): Promise<void> {\n return new Promise<void>((resolve) => {\n try { const request = indexedDB.deleteDatabase(name); request.onsuccess = request.onerror = request.onblocked = () => resolve(); } catch { resolve(); }\n });\n}\n\nexport function wrap<T>(request: IDBRequest<T>): Promise<T> {\n return new Promise((resolve, reject) => {\n request.onsuccess = () => resolve(request.result);\n request.onerror = () => reject(request.error);\n });\n}\n\nexport async function store(name: string, mode: IDBTransactionMode): Promise<IDBObjectStore> {\n return (await openDb()).transaction(name, mode).objectStore(name);\n}\n\n/**\n * Several writes across stores in one transaction: they all land, or none does.\n * `work` only queues requests; the promise settles when the transaction does.\n */\nexport async function transact(names: string[], work: (stores: Record<string, IDBObjectStore>) => void): Promise<void> {\n const tx = (await openDb()).transaction(names, \"readwrite\");\n const done = new Promise<void>((resolve, reject) => {\n tx.oncomplete = () => resolve();\n tx.onabort = () => reject(tx.error ?? new Error(\"The wallet could not save\"));\n tx.onerror = () => reject(tx.error ?? new Error(\"The wallet could not save\"));\n });\n try {\n work(Object.fromEntries(names.map((name) => [name, tx.objectStore(name)])));\n } catch (error) {\n tx.abort();\n await done.catch(() => {});\n throw error;\n }\n return done;\n}\n\n/** A stored file as it stands: its record, with what changed since it was stored read over it. */\nfunction withState(file: StoredFile, state: FileState | undefined): StoredFile {\n if (!state) return file;\n const { id: _id, ...fields } = state;\n return { ...file, ...fields };\n}\n\n/**\n * The files store. A record is written once, by `put`, and never written back: what changes later (`patch`,\n * `updateTransfer`) goes to `STORES.fileState` and is read over it. WebKit (Safari, the macOS app) keeps a stored\n * Blob as a file the record points to, and a record written back with the Blob it read can lose that file: from\n * then on every read of it fails with \"The object can not be found here.\" For the same reason a Blob read from\n * here is never put here again, under any id (a forward copies the bytes into a Blob of its own).\n */\nexport const fileStore = {\n /** Stores a file anew: whatever changed about an earlier file under this id is forgotten. */\n async put(file: StoredFile): Promise<void> {\n await transact([STORES.files, STORES.fileState], stores => {\n stores[STORES.files].put(file);\n stores[STORES.fileState].delete(file.id);\n });\n },\n async updateTransfer(id: string, transfer: NonNullable<StoredFile[\"transfer\"]>): Promise<void> {\n await fileStore.patch(id, { transfer });\n },\n /** Changes some fields of a stored file (never its record, see above); nothing happens to one that was deleted. */\n async patch(id: string, fields: FileStateFields): Promise<void> {\n await transact([STORES.files, STORES.fileState], stores => {\n const exists = stores[STORES.files].getKey(id);\n exists.onsuccess = () => {\n if (exists.result === undefined) return;\n const state = stores[STORES.fileState].get(id);\n state.onsuccess = () => { stores[STORES.fileState].put({ ...(state.result as FileState | undefined), ...fields, id } satisfies FileState); };\n };\n });\n },\n async get(id: string): Promise<StoredFile | undefined> {\n const tx = (await openDb()).transaction([STORES.files, STORES.fileState], \"readonly\");\n const [file, state] = await Promise.all([\n wrap<StoredFile | undefined>(tx.objectStore(STORES.files).get(id)),\n wrap<FileState | undefined>(tx.objectStore(STORES.fileState).get(id)),\n ]);\n return file && withState(file, state);\n },\n async listForLink(linkId: string): Promise<StoredFile[]> {\n const tx = (await openDb()).transaction([STORES.files, STORES.fileState], \"readonly\");\n const files = await wrap<StoredFile[]>(tx.objectStore(STORES.files).index(\"byLink\").getAll(linkId));\n const states = await Promise.all(files.map((file) => wrap<FileState | undefined>(tx.objectStore(STORES.fileState).get(file.id))));\n return files.map((file, i) => withState(file, states[i]));\n },\n async delete(id: string): Promise<void> {\n await transact([STORES.files, STORES.fileState], stores => {\n stores[STORES.files].delete(id);\n stores[STORES.fileState].delete(id);\n });\n },\n async deleteForLink(linkId: string): Promise<void> {\n const tx = (await openDb()).transaction([STORES.files, STORES.fileState], \"readwrite\");\n const files = tx.objectStore(STORES.files), state = tx.objectStore(STORES.fileState);\n const keys = await wrap(files.index(\"byLink\").getAllKeys(linkId));\n await Promise.all(keys.flatMap((key) => [wrap(files.delete(key)), wrap(state.delete(key))]));\n },\n};\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"restoredRows-tVxgyG23.js","names":[],"sources":["../../../browser/src/shared/restoredRows.ts"],"sourcesContent":["import { STORES, openDb } from \"./idb\";\n\n/**\n * What a restore changes in the money a backup carries (WISP 05 § Restoring). A bundle holds the wallet as it stood\n * when the backup was made, and the profile it came from may have gone on since: none of it is taken at its word.\n */\nexport const RESTORED_WALLET_STORES: readonly string[] = [STORES.proofs, STORES.swaps, STORES.intents];\n\n/** One row of `store` as a restore writes it. A row of any other store, or one that is not what it should be, is kept as it is. */\nexport function restoredWalletRow(store: string, value: unknown): unknown {\n if (!value || typeof value !== \"object\") return value;\n if (store === STORES.proofs) {\n // Ecash as it was when the backup was made: what was spent since is found by asking the mint, once the wallet runs.\n const proof = value as { secret?: unknown; reserved?: boolean };\n return typeof proof.secret === \"string\" && !proof.reserved ? { ...proof, unchecked: true } : value;\n }\n // A swap as it stood when the backup was made: what it brings is checked like the copy's ecash.\n if (store === STORES.swaps) return { ...value, restored: true };\n if (store === STORES.intents) {\n // An older copy cannot prove an unfinished attempt was never sent; it may not authorize a new one.\n const intent = value as { review?: { state?: string; method?: string } };\n // An on-chain review never approved stays unapproved: only Approve on this profile could send it, and a copy has none.\n if (intent.review?.method === \"bitcoin\" && intent.review.state === \"pending\") return { ...intent, review: { ...intent.review, state: \"cancelled\" } };\n return intent.review && [\"pending\", \"submitted\", \"unknown\"].includes(intent.review.state ?? \"\") ? { ...intent, review: { ...intent.review, state: \"unknown\" } } : value;\n }\n return value;\n}\n\n/**\n * The same, for a restore that brought the profile's database back whole (the headless CLI's): every wallet row of\n * this profile's database is rewritten as `restoredWalletRow` says, in one transaction, before the engine reads any.\n * Doing it twice changes nothing more.\n */\nexport async function markRestoredWallet(): Promise<void> {\n const db = await openDb();\n const names = RESTORED_WALLET_STORES.filter((name) => db.objectStoreNames.contains(name));\n if (!names.length) return;\n const tx = db.transaction(names, \"readwrite\");\n const done = new Promise<void>((resolve, reject) => {\n tx.oncomplete = () => resolve();\n tx.onabort = () => reject(tx.error ?? new Error(\"The restored wallet could not be marked\"));\n tx.onerror = () => reject(tx.error ?? new Error(\"The restored wallet could not be marked\"));\n });\n for (const name of names) {\n const walk = tx.objectStore(name).openCursor();\n walk.onsuccess = () => {\n const cursor = walk.result;\n if (!cursor) return;\n const next = restoredWalletRow(name, cursor.value);\n if (next !== cursor.value) cursor.update(next);\n cursor.continue();\n };\n }\n return done;\n}\n"],"mappings":";;;;;;AAMA,IAAa,yBAA4C;CAAC,OAAO;CAAQ,OAAO;CAAO,OAAO;AAAO;;AAGrG,SAAgB,kBAAkB,OAAe,OAAyB;CACxE,IAAI,CAAC,SAAS,OAAO,UAAU,UAAU,OAAO;CAChD,IAAI,UAAU,OAAO,QAAQ;EAE3B,MAAM,QAAQ;EACd,OAAO,OAAO,MAAM,WAAW,YAAY,CAAC,MAAM,WAAW;GAAE,GAAG;GAAO,WAAW;EAAK,IAAI;CAC/F;CAEA,IAAI,UAAU,OAAO,OAAO,OAAO;EAAE,GAAG;EAAO,UAAU;CAAK;CAC9D,IAAI,UAAU,OAAO,SAAS;EAE5B,MAAM,SAAS;EAEf,IAAI,OAAO,QAAQ,WAAW,aAAa,OAAO,OAAO,UAAU,WAAW,OAAO;GAAE,GAAG;GAAQ,QAAQ;IAAE,GAAG,OAAO;IAAQ,OAAO;GAAY;EAAE;EACnJ,OAAO,OAAO,UAAU;GAAC;GAAW;GAAa;EAAS,CAAC,CAAC,SAAS,OAAO,OAAO,SAAS,EAAE,IAAI;GAAE,GAAG;GAAQ,QAAQ;IAAE,GAAG,OAAO;IAAQ,OAAO;GAAU;EAAE,IAAI;CACpK;CACA,OAAO;AACT;;;;;;AAOA,eAAsB,qBAAoC;CACxD,MAAM,KAAK,MAAM,OAAO;CACxB,MAAM,QAAQ,uBAAuB,QAAQ,SAAS,GAAG,iBAAiB,SAAS,IAAI,CAAC;CACxF,IAAI,CAAC,MAAM,QAAQ;CACnB,MAAM,KAAK,GAAG,YAAY,OAAO,WAAW;CAC5C,MAAM,OAAO,IAAI,SAAe,SAAS,WAAW;EAClD,GAAG,mBAAmB,QAAQ;EAC9B,GAAG,gBAAgB,OAAO,GAAG,yBAAS,IAAI,MAAM,yCAAyC,CAAC;EAC1F,GAAG,gBAAgB,OAAO,GAAG,yBAAS,IAAI,MAAM,yCAAyC,CAAC;CAC5F,CAAC;CACD,KAAK,MAAM,QAAQ,OAAO;EACxB,MAAM,OAAO,GAAG,YAAY,IAAI,CAAC,CAAC,WAAW;EAC7C,KAAK,kBAAkB;GACrB,MAAM,SAAS,KAAK;GACpB,IAAI,CAAC,QAAQ;GACb,MAAM,OAAO,kBAAkB,MAAM,OAAO,KAAK;GACjD,IAAI,SAAS,OAAO,OAAO,OAAO,OAAO,IAAI;GAC7C,OAAO,SAAS;EAClB;CACF;CACA,OAAO;AACT"}