selfstore 1.8.1 → 1.8.3

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 +5 -0
  2. package/dist/advanced.d.ts +10 -6
  3. package/dist/advanced.js +14 -13
  4. package/dist/advanced.js.map +1 -1
  5. package/dist/backups.d.ts +31 -179
  6. package/dist/backups.js +40 -3
  7. package/dist/backups.js.map +1 -1
  8. package/dist/{cache-lock-7D6N42ZP.js → cache-lock-KWJUORTS.js} +3 -4
  9. package/dist/cache-lock-KWJUORTS.js.map +1 -0
  10. package/dist/{chunk-CM3HSBCE.js → chunk-3OBJOMQ2.js} +28 -4
  11. package/dist/chunk-3OBJOMQ2.js.map +1 -0
  12. package/dist/{chunk-53WWPEML.js → chunk-DCEXWSM6.js} +3 -3
  13. package/dist/{chunk-53WWPEML.js.map → chunk-DCEXWSM6.js.map} +1 -1
  14. package/dist/{chunk-ZV2S3I76.js → chunk-ELDSPUTS.js} +30 -10
  15. package/dist/chunk-ELDSPUTS.js.map +1 -0
  16. package/dist/chunk-J5MFXJU6.js +310 -0
  17. package/dist/chunk-J5MFXJU6.js.map +1 -0
  18. package/dist/{chunk-ZDHTKFAL.js → chunk-MGDXR3RB.js} +5 -6
  19. package/dist/chunk-MGDXR3RB.js.map +1 -0
  20. package/dist/{chunk-CT2HTWA7.js → chunk-O3Y6FORN.js} +3 -4
  21. package/dist/chunk-O3Y6FORN.js.map +1 -0
  22. package/dist/{chunk-OZS7BSC2.js → chunk-PNX7AJYO.js} +26 -12
  23. package/dist/chunk-PNX7AJYO.js.map +1 -0
  24. package/dist/{chunk-XBPJRUA3.js → chunk-TKWU52LK.js} +41 -319
  25. package/dist/chunk-TKWU52LK.js.map +1 -0
  26. package/dist/{chunk-OIAXYI35.js → chunk-W22V5CRM.js} +3 -3
  27. package/dist/{chunk-OIAXYI35.js.map → chunk-W22V5CRM.js.map} +1 -1
  28. package/dist/{drive-DF08V8-L.d.ts → drive-CTYvGg_7.d.ts} +23 -110
  29. package/dist/{flows-dwgDgv6_.d.ts → flows-1EntUZ91.d.ts} +3 -2
  30. package/dist/flows.d.ts +4 -3
  31. package/dist/flows.js +16 -16
  32. package/dist/flows.js.map +1 -1
  33. package/dist/groups.js +3 -4
  34. package/dist/groups.js.map +1 -1
  35. package/dist/households.d.ts +1 -1
  36. package/dist/index.d.ts +5 -4
  37. package/dist/index.js +24 -18
  38. package/dist/index.js.map +1 -1
  39. package/dist/{local-ipQv_Pbv.d.ts → local-C52j3DeH.d.ts} +2 -2
  40. package/dist/local-DPS35F5C.js +6 -0
  41. package/dist/{local-5QN7V24I.js.map → local-DPS35F5C.js.map} +1 -1
  42. package/dist/manager-DZ1c_aSS.d.ts +181 -0
  43. package/dist/s3-DvsnA-FK.d.ts +112 -0
  44. package/dist/{store-CbGrlMME.d.ts → store-BiQ-BuVl.d.ts} +8 -2
  45. package/dist/widgets.d.ts +5 -4
  46. package/dist/widgets.js +11 -9
  47. package/dist/widgets.js.map +1 -1
  48. package/package.json +1 -1
  49. package/dist/cache-lock-7D6N42ZP.js.map +0 -1
  50. package/dist/chunk-CM3HSBCE.js.map +0 -1
  51. package/dist/chunk-CT2HTWA7.js.map +0 -1
  52. package/dist/chunk-OZS7BSC2.js.map +0 -1
  53. package/dist/chunk-SFF7KKM4.js +0 -29
  54. package/dist/chunk-SFF7KKM4.js.map +0 -1
  55. package/dist/chunk-XBPJRUA3.js.map +0 -1
  56. package/dist/chunk-ZDHTKFAL.js.map +0 -1
  57. package/dist/chunk-ZV2S3I76.js.map +0 -1
  58. package/dist/local-5QN7V24I.js +0 -7
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/backups/manager.ts"],"names":[],"mappings":"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","file":"backups.js","sourcesContent":["// Multi-backup manager: several backup files on one destination account, one\n// attached at a time, each an isolated snapshot - opening one replaces the\n// local data, never merges, never touches the other files.\n//\n// It owns the bookkeeping a backup panel needs: the self-healing registry of\n// files this device has connected to; the per-file memories a listing cannot\n// provide (encrypted? shared? - those bits live inside the files, learned as\n// they are opened or probed and remembered in the kv); and the open / create\n// / rename / delete gestures with their isolation invariants (fixed-id\n// targets, active-id committed only after a successful attach, wipe on every\n// switch so no sync-meta crosses files).\n//\n// Injectable and headless: a LocalStore + a kv + a BackupsHost port for the\n// destination I/O. Errors surface as stable codes, never prose; hosts wrap\n// subscribe in their own reactivity.\n\nimport type { KV } from '../persistence/cache';\nimport type { LocalStore } from '../persistence/store';\nimport type { BackupTarget } from '../persistence/target';\nimport { makeMachine, type FlowStore } from '../flows/machine';\n\n// --- Ports --------------------------------------------------------------------\n\n/** What the destination reports about a listed file (one cheap metadata call). */\nexport interface BackupFileInfo {\n\tid: string;\n\tname: string;\n\t/** RFC 3339 last-write timestamp, when the destination reports one. */\n\tmodifiedTime: string | null;\n}\n\n/** The destination I/O the manager drives - the host wires its own adapter\n * (e.g. over the Drive target ops). Every method may throw; the manager turns\n * failures into stable error codes. */\nexport interface BackupsHost {\n\t/** The target kind the managed files live on (e.g. 'drive'): the manager\n\t * only stamps an active file while the store is attached to this kind. */\n\tkind: string;\n\t/** The kv key the attached file id is persisted under (the same one the\n\t * target's connect path reads first). */\n\tactiveIdKey: string;\n\t/** List candidate backup files on the account. The manager applies the\n\t * naming rules on what comes back; internal files simply do not match. */\n\tlist(): Promise<BackupFileInfo[]>;\n\t/** A target over a FIXED file id - never re-resolved by name. The fixed id\n\t * is what keeps two backups from silently merging mid-switch. */\n\topen(fileId: string): BackupTarget;\n\t/** Create an empty file under this name and return its id. Refuse a name\n\t * the account already carries (a duplicate poisons find-by-name). */\n\tcreate(fileName: string): Promise<{ fileId: string }>;\n\t/** Delete a file for good. */\n\tremove(fileId: string): Promise<void>;\n\t/** Rename a file in place (metadata only: the id must not change). */\n\trename?(fileId: string, fileName: string): Promise<void>;\n\t/** Find (or create) the PERSONAL file by its canonical name, ignoring any\n\t * currently adopted id. Null when the user backs out of the session. */\n\tfindOrCreatePersonal(): Promise<{ fileId: string; created: boolean } | null>;\n\t/** Make sure a usable session exists before a remote call (re-consent when\n\t * it lapsed). False = the user refused; the gesture aborts as 'cancelled'.\n\t * Absent = sessions are the target's own business. */\n\tensureSession?(): Promise<boolean>;\n\t/** Resolve the owner label of a file this account does not own, for the\n\t * shared registry (\"shared by X\"). Best-effort. */\n\tfileOwner?(fileId: string): Promise<{ email: string | null; name: string | null } | null>;\n\t/** File ids that must never enter the registry even though the store may\n\t * attach them (e.g. a sharing engine's published copy file). */\n\texcludedFileIds?(): Promise<(string | null | undefined)[]>;\n}\n\n/** How backup files are named on the destination. */\nexport interface BackupsNaming {\n\t/** The personal, unnamed file (\"App.zip\"). Its label reads as null. */\n\tcanonicalName: string;\n\t/** label -> file name. Default: \"Stem (label).ext\" derived from\n\t * canonicalName (\"App.zip\" + \"family\" -> \"App (family).zip\"). */\n\tnamedFileFor?(label: string): string;\n\t/** file name -> parsed label; null = the personal file; undefined = not one\n\t * of ours (dropped from listings). Default matches the default namedFileFor. */\n\tparseLabel?(name: string): string | null | undefined;\n}\n\n/** The kv keys the manager persists under - override to keep a pre-existing\n * install's records. */\nexport interface BackupsKeys {\n\t/** The known-backups registry. */\n\tregistry?: string;\n\t/** fileId -> encrypted? map. */\n\tencrypted?: string;\n\t/** fileId -> shared? map. */\n\tshared?: string;\n\t/** Set while the ACTIVE file is not the personal one (another account's). */\n\tjoined?: string;\n\t/** Cached owner label of that file. */\n\towner?: string;\n}\n\n// --- Shapes -------------------------------------------------------------------\n\n/** Every backup this device has connected to: its own personal file and each\n * OTHER account's file it was let into. Isolated snapshots, all of them. */\nexport interface KnownBackups {\n\tpersonalFileId: string | null;\n\tshared: { fileId: string; ownerEmail: string | null; ownerName: string | null }[];\n}\n\n/** One row of the \"my backups\" list: a file this account owns. */\nexport interface BackupRow {\n\tfileId: string;\n\tname: string;\n\t/** Parsed display label; null for the personal, unnamed file. */\n\tlabel: string | null;\n\t/** Last-write time, epoch ms (null when the destination did not say). */\n\tmodifiedAt: number | null;\n\t/** true/false once known (opened, created or probed); null until then. */\n\tencrypted: boolean | null;\n\t/** true/false once learned (while this file was the active one); null\n\t * until then. A share link existing is the shared state. */\n\tshared: boolean | null;\n}\n\n/** The owner label of a joined (another account's) file. */\nexport interface BackupOwner {\n\temail: string | null;\n\tname: string | null;\n}\n\n/** Stable failure codes; the host maps them to its own copy. */\nexport type BackupsErrorCode = 'cancelled' | 'gone' | 'failed';\n\nexport interface BackupsSnapshot {\n\tregistry: KnownBackups;\n\t/** The file currently attached, when the store is on the managed kind. */\n\tactiveFileId: string | null;\n\t/** The active file is not the personal one (someone else's). */\n\tjoined: boolean;\n\t/** Who owns the joined file, when known. */\n\towner: BackupOwner | null;\n\t/** The last gesture's failure, or null. Reset at each gesture start. */\n\tlastError: BackupsErrorCode | null;\n}\n\nexport interface BackupsManager extends FlowStore<BackupsSnapshot> {\n\t/** Boot: load registry + joined/owner from kv and stamp the active file.\n\t * Kv-only, no destination call. */\n\thydrate(): Promise<void>;\n\t/** Re-read the registry and restamp the active file. Kv-only, cheap. */\n\trefresh(): Promise<void>;\n\t/** After an attach the manager did not make (a connect widget, a join):\n\t * record the now-active file in the registry and restamp joined/owner. */\n\tmarkActive(fileId: string): Promise<void>;\n\t/** Live list of this account's own files, per the naming rules, decorated\n\t * with the learned memories. One destination listing call. */\n\tlist(): Promise<BackupRow[]>;\n\t/** Learn a listed file's encrypted bit without opening it for real: one\n\t * download + header read, remembered for good. Null when unreachable. */\n\tprobeEncryption(fileId: string): Promise<boolean | null>;\n\t/** Record the ACTIVE file's live share state, keyed by file id, so its row\n\t * stays truthful after a switch away. */\n\tnoteShared(fileId: string, shared: boolean): Promise<void>;\n\t/** Open a known backup: an isolated snapshot replaces the local data.\n\t * 'encrypted' = collect its passphrase and call again with it. A file the\n\t * destination reports GONE is pruned from the registry (lastError 'gone'). */\n\topenBackup(fileId: string, passphrase?: string | null): Promise<'ok' | 'encrypted' | 'failed'>;\n\t/** Open (or create blank) the personal file. */\n\topenPersonal(passphrase?: string | null): Promise<'ok' | 'encrypted' | 'failed'>;\n\t/** Create a brand-new empty named backup and open it (starts blank - never\n\t * seeded from the currently loaded file). */\n\tcreateNamed(label: string): Promise<'ok' | 'failed'>;\n\t/** Record a file in the SHARED registry under the sharer's label, without\n\t * opening it. For a joined portfolio's dedicated wallet: the file may live\n\t * on this very account, yet it presents as \"shared by X\", never as mine. */\n\tregisterShared(fileId: string, owner: BackupOwner): Promise<void>;\n\t/** Create a joined portfolio's dedicated wallet: a file under the given\n\t * name (outside the own-files naming on purpose), attached BLANK - never\n\t * seeded from the previously loaded silo - and recorded in the shared\n\t * registry under the sharer's label. The host stages the shared content\n\t * afterwards (restore the projection, then save). */\n\tcreateShared(fileName: string, owner: BackupOwner): Promise<'ok' | 'failed'>;\n\t/** Rename a file in place (id unchanged, so renaming the ACTIVE file never\n\t * detaches it). The host target refuses a name already carried. */\n\trenameBackup(fileId: string, label: string): Promise<'ok' | 'failed'>;\n\t/** Delete a file for good. Deleting the ACTIVE one detaches first (the app\n\t * goes device-only, local data stays), so nothing writes mid-delete. */\n\tdeleteBackup(fileId: string): Promise<boolean>;\n\t/** Drop a KNOWN shared (another account's) file from this device's\n\t * registry. Forgetting only - the owner's file is never touched. */\n\tforgetShared(fileId: string): Promise<void>;\n\t/** The file name a label produces (the create form's preview line). */\n\tfileNameFor(label: string): string;\n}\n\n// --- Defaults -----------------------------------------------------------------\n\nconst DEFAULT_KEYS: Required<BackupsKeys> = {\n\tregistry: 'selfstore:backups:registry:v1',\n\tencrypted: 'selfstore:backups:encrypted:v1',\n\tshared: 'selfstore:backups:shared:v1',\n\tjoined: 'selfstore:backups:joined:v1',\n\towner: 'selfstore:backups:owner:v1'\n};\n\nconst escapeRe = (s: string): string => s.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n\n/** The default naming scheme: \"App.zip\" -> personal; \"App (label).zip\" ->\n * named. Derived from the canonical name's stem and extension. */\nfunction defaultNaming(\n\tnaming: BackupsNaming\n): Required<Pick<BackupsNaming, 'namedFileFor' | 'parseLabel'>> {\n\tconst dot = naming.canonicalName.lastIndexOf('.');\n\tconst stem = dot > 0 ? naming.canonicalName.slice(0, dot) : naming.canonicalName;\n\tconst ext = dot > 0 ? naming.canonicalName.slice(dot) : '';\n\tconst named = new RegExp(`^${escapeRe(stem)} \\\\((.+)\\\\)${escapeRe(ext)}$`);\n\treturn {\n\t\tnamedFileFor: (label: string) => `${stem} (${label.trim()})${ext}`,\n\t\tparseLabel: (name: string) => {\n\t\t\tif (name === naming.canonicalName) return null;\n\t\t\tconst m = named.exec(name);\n\t\t\treturn m ? m[1] : undefined;\n\t\t}\n\t};\n}\n\n// --- Factory ------------------------------------------------------------------\n\nexport function createBackupsManager(deps: {\n\tstore: LocalStore;\n\tkv: KV;\n\thost: BackupsHost;\n\tnaming: BackupsNaming;\n\tkeys?: BackupsKeys;\n}): BackupsManager {\n\tconst { store, kv, host } = deps;\n\tconst KEYS = { ...DEFAULT_KEYS, ...deps.keys };\n\tconst namingDefaults = defaultNaming(deps.naming);\n\tconst naming = {\n\t\tnamedFileFor: deps.naming.namedFileFor ?? namingDefaults.namedFileFor,\n\t\tparseLabel: deps.naming.parseLabel ?? namingDefaults.parseLabel\n\t};\n\n\tconst m = makeMachine<BackupsSnapshot>({\n\t\tregistry: { personalFileId: null, shared: [] },\n\t\tactiveFileId: null,\n\t\tjoined: false,\n\t\towner: null,\n\t\tlastError: null\n\t});\n\n\tconst fail = (code: BackupsErrorCode): void => m.set({ lastError: code });\n\n\t// Which file ids the destination lists as OURS, and which one carries the\n\t// canonical (personal) name. Filled by list(), refreshed lazily where a\n\t// truthful own/foreign call is needed. Null until first learned.\n\tlet ownIds: Set<string> | null = null;\n\tlet ownCanonicalId: string | null = null;\n\n\tfunction learnOwn(files: BackupFileInfo[]): void {\n\t\tconst ids = new Set<string>();\n\t\tlet canonical: string | null = null;\n\t\tfor (const f of files) {\n\t\t\tconst label = naming.parseLabel(f.name);\n\t\t\tif (label === undefined) continue;\n\t\t\tids.add(f.id);\n\t\t\tif (label === null) canonical = f.id;\n\t\t}\n\t\townIds = ids;\n\t\townCanonicalId = canonical;\n\t}\n\n\tasync function ensureOwnKnown(): Promise<void> {\n\t\tif (ownIds !== null) return;\n\t\ttry {\n\t\t\tlearnOwn(await host.list());\n\t\t} catch {\n\t\t\t// Offline: leave unknown - markActive falls back on the registry alone.\n\t\t}\n\t}\n\n\t/** Load the registry, self-healing: excluded ids (a sharing engine's copy\n\t * file) must never sit in it - recorded as \"personal\" they would mislabel\n\t * the chooser and let a later open treat a shared file as one's own. */\n\tasync function loadRegistry(): Promise<KnownBackups> {\n\t\tconst reg = (await kv.get<KnownBackups>(KEYS.registry)) ?? { personalFileId: null, shared: [] };\n\t\tconst excluded = (await host.excludedFileIds?.()) ?? [];\n\t\tconst bad = new Set(excluded.filter((x): x is string => typeof x === 'string'));\n\t\tif (bad.size === 0) return reg;\n\t\tconst healed: KnownBackups = {\n\t\t\tpersonalFileId: reg.personalFileId && bad.has(reg.personalFileId) ? null : reg.personalFileId,\n\t\t\tshared: reg.shared.filter((s) => !bad.has(s.fileId))\n\t\t};\n\t\tif (\n\t\t\thealed.personalFileId !== reg.personalFileId ||\n\t\t\thealed.shared.length !== reg.shared.length\n\t\t) {\n\t\t\tawait kv.set(KEYS.registry, healed);\n\t\t}\n\t\treturn healed;\n\t}\n\n\tasync function saveRegistry(next: KnownBackups): Promise<void> {\n\t\tm.set({ registry: next });\n\t\tawait kv.set(KEYS.registry, next);\n\t}\n\n\tasync function noteEncrypted(fileId: string, encrypted: boolean): Promise<void> {\n\t\tconst map = (await kv.get<Record<string, boolean>>(KEYS.encrypted)) ?? {};\n\t\tif (map[fileId] === encrypted) return;\n\t\tmap[fileId] = encrypted;\n\t\tawait kv.set(KEYS.encrypted, map);\n\t}\n\n\t/** Stamp the currently attached file (from the target's own persisted id)\n\t * into the snapshot and the registry. Kv-only. */\n\tasync function registerActive(): Promise<void> {\n\t\tif (store.state.targetKind !== host.kind) {\n\t\t\tm.set({ activeFileId: null });\n\t\t\treturn;\n\t\t}\n\t\tconst fileId = (await kv.get<string>(host.activeIdKey)) ?? null;\n\t\tm.set({ activeFileId: fileId });\n\t\tif (!fileId) return;\n\t\tconst excluded = (await host.excludedFileIds?.()) ?? [];\n\t\tif (excluded.includes(fileId)) return;\n\t\tconst reg = await loadRegistry();\n\t\tif (m.snapshot.joined) {\n\t\t\tconst owner = m.snapshot.owner;\n\t\t\tconst entry = { fileId, ownerEmail: owner?.email ?? null, ownerName: owner?.name ?? null };\n\t\t\tawait saveRegistry({\n\t\t\t\tpersonalFileId: reg.personalFileId === fileId ? null : reg.personalFileId,\n\t\t\t\tshared: [entry, ...reg.shared.filter((s) => s.fileId !== fileId)]\n\t\t\t});\n\t\t\treturn;\n\t\t}\n\t\t// Not joined. The personal slot belongs to the CANONICAL file alone: an\n\t\t// own NAMED backup lists live and must not clobber it (the slot is the\n\t\t// escape back to the personal file).\n\t\tconst canonical =\n\t\t\tfileId === ownCanonicalId ||\n\t\t\treg.personalFileId === fileId ||\n\t\t\t(ownIds === null && reg.personalFileId === null);\n\t\tif (canonical) {\n\t\t\tawait saveRegistry({\n\t\t\t\tpersonalFileId: fileId,\n\t\t\t\tshared: reg.shared.filter((s) => s.fileId !== fileId)\n\t\t\t});\n\t\t} else if (reg.shared.some((s) => s.fileId === fileId)) {\n\t\t\tawait saveRegistry({\n\t\t\t\tpersonalFileId: reg.personalFileId,\n\t\t\t\tshared: reg.shared.filter((s) => s.fileId !== fileId)\n\t\t\t});\n\t\t}\n\t}\n\n\t/** Session guard shared by the remote gestures: absent hook = no guard. */\n\tasync function ensureSession(): Promise<boolean> {\n\t\tif (!host.ensureSession) return true;\n\t\treturn host.ensureSession();\n\t}\n\n\tasync function markActive(fileId: string): Promise<void> {\n\t\tconst reg = await loadRegistry();\n\t\tconst knownShared = reg.shared.find((s) => s.fileId === fileId);\n\t\tlet mine = reg.personalFileId === fileId;\n\t\tif (!mine && !knownShared) {\n\t\t\t// Unknown file: ask the destination whose it is (one listing call,\n\t\t\t// cached). An own named backup must never read as joined - labeled\n\t\t\t// \"shared by me\", it would sit in the wrong section for good.\n\t\t\tawait ensureOwnKnown();\n\t\t\tmine = ownIds === null ? reg.personalFileId === null : ownIds.has(fileId);\n\t\t}\n\t\tif (mine || (!knownShared && ownIds?.has(fileId))) {\n\t\t\tawait kv.del(KEYS.joined);\n\t\t\tawait kv.del(KEYS.owner);\n\t\t\tm.set({ joined: false, owner: null });\n\t\t} else {\n\t\t\tconst owner: BackupOwner | null = knownShared\n\t\t\t\t? { email: knownShared.ownerEmail, name: knownShared.ownerName }\n\t\t\t\t: ((await host.fileOwner?.(fileId).catch(() => null)) ?? null);\n\t\t\tm.set({ joined: true, owner });\n\t\t\tawait kv.set(KEYS.joined, true);\n\t\t\tif (owner) await kv.set(KEYS.owner, owner);\n\t\t\telse await kv.del(KEYS.owner);\n\t\t}\n\t\tawait registerActive();\n\t}\n\n\t/** The one attach path every open shares: FIXED-id target, and the\n\t * remembered id committed only after the attach succeeds - committed up\n\t * front, the OUTGOING target (which resolves its file lazily from that\n\t * same key) would flush the old file's data into the one being opened:\n\t * two \"isolated\" backups silently merged. `wipe` keeps sync-meta from\n\t * crossing files (cross-file tombstones erase the other side). */\n\tasync function attachFixed(\n\t\tfileId: string,\n\t\tpassphrase: string | null,\n\t\tstrategy: 'replace-local' | 'replace-remote'\n\t): Promise<void> {\n\t\tconst target = host.open(fileId);\n\t\tawait store.attachTarget(target, {\n\t\t\tpassword: passphrase,\n\t\t\tstrategy,\n\t\t\tkeepSession: store.state.targetKind === host.kind,\n\t\t\twipe: true\n\t\t});\n\t\tawait kv.set(host.activeIdKey, fileId);\n\t}\n\n\treturn {\n\t\tget snapshot() {\n\t\t\treturn m.snapshot;\n\t\t},\n\t\tsubscribe: m.subscribe,\n\n\t\tfileNameFor(label: string): string {\n\t\t\treturn naming.namedFileFor(label.trim());\n\t\t},\n\n\t\tasync hydrate(): Promise<void> {\n\t\t\tconst joined = (await kv.get(KEYS.joined)) === true;\n\t\t\tconst owner = (await kv.get<BackupOwner>(KEYS.owner)) ?? null;\n\t\t\tm.set({ joined, owner, registry: await loadRegistry() });\n\t\t\tawait registerActive();\n\t\t},\n\n\t\tasync refresh(): Promise<void> {\n\t\t\tm.set({ registry: await loadRegistry() });\n\t\t\tawait registerActive();\n\t\t},\n\n\t\tmarkActive,\n\n\t\tasync list(): Promise<BackupRow[]> {\n\t\t\tconst files = await host.list();\n\t\t\tlearnOwn(files);\n\t\t\tconst enc = (await kv.get<Record<string, boolean>>(KEYS.encrypted)) ?? {};\n\t\t\tconst shr = (await kv.get<Record<string, boolean>>(KEYS.shared)) ?? {};\n\t\t\tconst rows: BackupRow[] = [];\n\t\t\tfor (const f of files) {\n\t\t\t\tconst label = naming.parseLabel(f.name);\n\t\t\t\tif (label === undefined) continue; // not one of ours (a copy, a bulletin, ...)\n\t\t\t\trows.push({\n\t\t\t\t\tfileId: f.id,\n\t\t\t\t\tname: f.name,\n\t\t\t\t\tlabel,\n\t\t\t\t\tmodifiedAt: f.modifiedTime ? Date.parse(f.modifiedTime) || null : null,\n\t\t\t\t\tencrypted: enc[f.id] ?? null,\n\t\t\t\t\tshared: shr[f.id] ?? null\n\t\t\t\t});\n\t\t\t}\n\t\t\treturn rows;\n\t\t},\n\n\t\tasync probeEncryption(fileId: string): Promise<boolean | null> {\n\t\t\ttry {\n\t\t\t\tconst info = await store.inspectTarget(host.open(fileId));\n\t\t\t\tif (!info.hasBackup) return null;\n\t\t\t\tawait noteEncrypted(fileId, info.encrypted);\n\t\t\t\treturn info.encrypted;\n\t\t\t} catch {\n\t\t\t\treturn null;\n\t\t\t}\n\t\t},\n\n\t\tasync noteShared(fileId: string, shared: boolean): Promise<void> {\n\t\t\tconst map = (await kv.get<Record<string, boolean>>(KEYS.shared)) ?? {};\n\t\t\tif (map[fileId] === shared) return;\n\t\t\tmap[fileId] = shared;\n\t\t\tawait kv.set(KEYS.shared, map);\n\t\t},\n\n\t\tasync openBackup(fileId, passphrase = null): Promise<'ok' | 'encrypted' | 'failed'> {\n\t\t\tm.set({ lastError: null });\n\t\t\ttry {\n\t\t\t\tif (!(await ensureSession())) {\n\t\t\t\t\tfail('cancelled');\n\t\t\t\t\treturn 'failed';\n\t\t\t\t}\n\t\t\t\tconst info = await store.inspectTarget(host.open(fileId));\n\t\t\t\tif (!info.hasBackup) {\n\t\t\t\t\t// Dead entry (deleted, or unshared from this account): prune it so\n\t\t\t\t\t// its row stops re-failing forever. A transient failure throws\n\t\t\t\t\t// instead and never prunes - dropping a good record on a blip\n\t\t\t\t\t// would be far worse than one more failed tap.\n\t\t\t\t\tconst reg = await loadRegistry();\n\t\t\t\t\tawait saveRegistry({\n\t\t\t\t\t\tpersonalFileId: reg.personalFileId === fileId ? null : reg.personalFileId,\n\t\t\t\t\t\tshared: reg.shared.filter((s) => s.fileId !== fileId)\n\t\t\t\t\t});\n\t\t\t\t\tfail('gone');\n\t\t\t\t\treturn 'failed';\n\t\t\t\t}\n\t\t\t\tawait noteEncrypted(fileId, info.encrypted); // the listing cannot tell\n\t\t\t\tif (info.encrypted && !passphrase) return 'encrypted';\n\t\t\t\tawait attachFixed(fileId, passphrase, 'replace-local');\n\t\t\t\tawait markActive(fileId);\n\t\t\t\treturn store.state.locked ? 'encrypted' : 'ok';\n\t\t\t} catch {\n\t\t\t\tfail('failed');\n\t\t\t\treturn 'failed';\n\t\t\t}\n\t\t},\n\n\t\tasync openPersonal(passphrase = null): Promise<'ok' | 'encrypted' | 'failed'> {\n\t\t\tm.set({ lastError: null });\n\t\t\ttry {\n\t\t\t\tconst own = await host.findOrCreatePersonal();\n\t\t\t\tif (!own) {\n\t\t\t\t\tfail('cancelled');\n\t\t\t\t\treturn 'failed';\n\t\t\t\t}\n\t\t\t\townCanonicalId = own.fileId;\n\t\t\t\townIds?.add(own.fileId);\n\t\t\t\tconst reg = await loadRegistry();\n\t\t\t\tawait saveRegistry({\n\t\t\t\t\tpersonalFileId: own.fileId,\n\t\t\t\t\tshared: reg.shared.filter((s) => s.fileId !== own.fileId)\n\t\t\t\t});\n\t\t\t\tif (!own.created) return await this.openBackup(own.fileId, passphrase);\n\t\t\t\tawait attachFixed(own.fileId, null, 'replace-remote');\n\t\t\t\tawait markActive(own.fileId);\n\t\t\t\treturn 'ok';\n\t\t\t} catch {\n\t\t\t\tfail('failed');\n\t\t\t\treturn 'failed';\n\t\t\t}\n\t\t},\n\n\t\tasync createNamed(label): Promise<'ok' | 'failed'> {\n\t\t\tm.set({ lastError: null });\n\t\t\tconst clean = label.trim();\n\t\t\tif (!clean) {\n\t\t\t\tfail('failed');\n\t\t\t\treturn 'failed';\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tif (!(await ensureSession())) {\n\t\t\t\t\tfail('cancelled');\n\t\t\t\t\treturn 'failed';\n\t\t\t\t}\n\t\t\t\tconst { fileId } = await host.create(naming.namedFileFor(clean));\n\t\t\t\townIds?.add(fileId);\n\t\t\t\tawait attachFixed(fileId, null, 'replace-remote');\n\t\t\t\tawait noteEncrypted(fileId, false); // a fresh backup starts unprotected\n\t\t\t\tawait markActive(fileId);\n\t\t\t\treturn 'ok';\n\t\t\t} catch {\n\t\t\t\tfail('failed');\n\t\t\t\treturn 'failed';\n\t\t\t}\n\t\t},\n\n\t\tasync registerShared(fileId, owner): Promise<void> {\n\t\t\tconst reg = await loadRegistry();\n\t\t\tawait saveRegistry({\n\t\t\t\tpersonalFileId: reg.personalFileId === fileId ? null : reg.personalFileId,\n\t\t\t\tshared: [\n\t\t\t\t\t{ fileId, ownerEmail: owner.email, ownerName: owner.name },\n\t\t\t\t\t...reg.shared.filter((s) => s.fileId !== fileId)\n\t\t\t\t]\n\t\t\t});\n\t\t},\n\n\t\tasync createShared(fileName, owner): Promise<'ok' | 'failed'> {\n\t\t\tm.set({ lastError: null });\n\t\t\ttry {\n\t\t\t\tif (!(await ensureSession())) {\n\t\t\t\t\tfail('cancelled');\n\t\t\t\t\treturn 'failed';\n\t\t\t\t}\n\t\t\t\tconst { fileId } = await host.create(fileName);\n\t\t\t\t// Registered as shared BEFORE the attach: markActive must find it in\n\t\t\t\t// the shared registry, or it would resolve this account as the owner\n\t\t\t\t// and file the joined wallet under \"mine\".\n\t\t\t\tawait this.registerShared(fileId, owner);\n\t\t\t\tawait attachFixed(fileId, null, 'replace-remote');\n\t\t\t\tawait noteEncrypted(fileId, false); // a member wallet is never protected\n\t\t\t\tawait markActive(fileId);\n\t\t\t\treturn 'ok';\n\t\t\t} catch {\n\t\t\t\tfail('failed');\n\t\t\t\treturn 'failed';\n\t\t\t}\n\t\t},\n\n\t\tasync renameBackup(fileId, label): Promise<'ok' | 'failed'> {\n\t\t\tm.set({ lastError: null });\n\t\t\tconst clean = label.trim();\n\t\t\tif (!clean || !host.rename) {\n\t\t\t\tfail('failed');\n\t\t\t\treturn 'failed';\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tawait host.rename(fileId, naming.namedFileFor(clean));\n\t\t\t\treturn 'ok';\n\t\t\t} catch {\n\t\t\t\tfail('failed');\n\t\t\t\treturn 'failed';\n\t\t\t}\n\t\t},\n\n\t\tasync deleteBackup(fileId): Promise<boolean> {\n\t\t\tm.set({ lastError: null });\n\t\t\ttry {\n\t\t\t\tif (fileId === m.snapshot.activeFileId) {\n\t\t\t\t\t// Detach first: the app goes device-only (local data stays) so\n\t\t\t\t\t// nothing writes to the file as it is removed. keepSession: a\n\t\t\t\t\t// default detach runs the target's disconnect, which forgets the\n\t\t\t\t\t// destination credentials - the remove below still needs them,\n\t\t\t\t\t// and the user deleted a FILE, not their account connection.\n\t\t\t\t\tm.set({ activeFileId: null });\n\t\t\t\t\tawait store.detachTarget({ keepSession: true });\n\t\t\t\t\t// The remembered file id would normally be cleared by that\n\t\t\t\t\t// disconnect; do it here so nothing dangles on a deleted file.\n\t\t\t\t\tawait kv.del(host.activeIdKey);\n\t\t\t\t}\n\t\t\t\tawait host.remove(fileId);\n\t\t\t\tconst reg = await loadRegistry();\n\t\t\t\tawait saveRegistry({\n\t\t\t\t\tpersonalFileId: reg.personalFileId === fileId ? null : reg.personalFileId,\n\t\t\t\t\tshared: reg.shared.filter((s) => s.fileId !== fileId)\n\t\t\t\t});\n\t\t\t\treturn true;\n\t\t\t} catch {\n\t\t\t\tfail('failed');\n\t\t\t\treturn false;\n\t\t\t}\n\t\t},\n\n\t\tasync forgetShared(fileId): Promise<void> {\n\t\t\tconst reg = await loadRegistry();\n\t\t\tawait saveRegistry({\n\t\t\t\tpersonalFileId: reg.personalFileId,\n\t\t\t\tshared: reg.shared.filter((s) => s.fileId !== fileId)\n\t\t\t});\n\t\t}\n\t};\n}\n"]}
1
+ {"version":3,"sources":["../src/backups/drive-host.ts","../src/backups/manager.ts"],"names":[],"mappings":"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","file":"backups.js","sourcesContent":["// The BackupsHost port, wired over the Drive target's own operations.\n//\n// The manager is deliberately injectable: it drives a destination through the\n// port and knows nothing about Drive. But the library already ships every\n// operation that port asks for - list, open-by-id, create, rename, delete,\n// find-or-create the personal file - so every app wiring them together again\n// was writing the same adapter, and getting the same three details wrong:\n//\n// - `open` must be a target over a FIXED id (`preview`), not a connect: a\n// connect re-resolves by name and would silently merge two backups.\n// - the active id lives under the SAME kv key the connect path reads, or a\n// reload adopts a different file than the one on screen.\n// - a listing must not raise the reconnect gate: `ensureSession` re-consents\n// first, so a lapsed session is a question, not an error.\n//\n// This is that adapter, once.\n\nimport type { BackupFileInfo, BackupsHost } from './manager';\nimport type { BackupTarget } from '../persistence/target';\nimport type { DriveOptions } from '../persistence/targets/drive';\nimport {\n\tFILE_ID_KEY,\n\tcreateBackup,\n\tdeleteBackup,\n\tfindOrCreateOwnFile,\n\tlistBackups,\n\tpreview,\n\trenameBackup\n} from '../persistence/targets/drive';\n\nexport interface DriveBackupsHostOptions extends DriveOptions {\n\t/** Narrow the listing server-side to names containing this fragment (the\n\t * app's own stem, typically). The manager still applies its naming rules on\n\t * what comes back, so this is a bandwidth hint and never a filter to rely\n\t * on for correctness. Default: the whole visible listing. */\n\tnameContains?: string;\n}\n\n/**\n * The Drive destination as a `BackupsHost`, ready for `createBackupsManager`\n * (and therefore for `<selfstore-backups>`).\n *\n * ```ts\n * const manager = createBackupsManager({\n * store: store.engine,\n * kv: store.kv,\n * host: driveBackupsHost({ auth, kv: store.kv, fileName: 'app.zip' }),\n * naming: { canonicalName: 'app.zip' }\n * });\n * ```\n *\n * The `drive.file` scope already narrows what a listing can see to the files\n * this app created or was handed, so nothing here can enumerate a user's Drive.\n */\nexport function driveBackupsHost(opts: DriveBackupsHostOptions): BackupsHost {\n\treturn {\n\t\tkind: 'drive',\n\t\tactiveIdKey: FILE_ID_KEY,\n\n\t\tasync list(): Promise<BackupFileInfo[]> {\n\t\t\treturn listBackups({ auth: opts.auth, nameContains: opts.nameContains });\n\t\t},\n\n\t\topen(fileId: string): BackupTarget {\n\t\t\t// `preview`, not `connect`: a target bound to this exact id, and it\n\t\t\t// writes nothing to the kv - the manager decides what becomes active.\n\t\t\treturn preview(opts, fileId);\n\t\t},\n\n\t\tasync create(fileName: string): Promise<{ fileId: string }> {\n\t\t\treturn createBackup({ auth: opts.auth, fileName });\n\t\t},\n\n\t\tasync remove(fileId: string): Promise<void> {\n\t\t\treturn deleteBackup({ auth: opts.auth, fileId });\n\t\t},\n\n\t\tasync rename(fileId: string, fileName: string): Promise<void> {\n\t\t\treturn renameBackup({ auth: opts.auth, fileId, fileName });\n\t\t},\n\n\t\tasync findOrCreatePersonal(): Promise<{ fileId: string; created: boolean } | null> {\n\t\t\treturn findOrCreateOwnFile(opts);\n\t\t},\n\n\t\tasync ensureSession(): Promise<boolean> {\n\t\t\t// A live token is the cheap answer. Only when there is none does the\n\t\t\t// user get a consent popup - and refusing it is an answer, not a\n\t\t\t// failure: the caller aborts the gesture as cancelled.\n\t\t\ttry {\n\t\t\t\tawait opts.auth.token();\n\t\t\t\treturn true;\n\t\t\t} catch {\n\t\t\t\treturn opts.auth.reconnect();\n\t\t\t}\n\t\t}\n\t};\n}\n","// Multi-backup manager: several backup files on one destination account, one\n// attached at a time, each an isolated snapshot - opening one replaces the\n// local data, never merges, never touches the other files.\n//\n// It owns the bookkeeping a backup panel needs: the self-healing registry of\n// files this device has connected to; the per-file memories a listing cannot\n// provide (encrypted? shared? - those bits live inside the files, learned as\n// they are opened or probed and remembered in the kv); and the open / create\n// / rename / delete gestures with their isolation invariants (fixed-id\n// targets, active-id committed only after a successful attach, wipe on every\n// switch so no sync-meta crosses files).\n//\n// Injectable and headless: a LocalStore + a kv + a BackupsHost port for the\n// destination I/O. Errors surface as stable codes, never prose; hosts wrap\n// subscribe in their own reactivity.\n\nimport type { KV } from '../persistence/cache';\nimport type { LocalStore } from '../persistence/store';\nimport type { BackupTarget } from '../persistence/target';\nimport { makeMachine, type FlowStore } from '../flows/machine';\n\n// --- Ports --------------------------------------------------------------------\n\n/** What the destination reports about a listed file (one cheap metadata call). */\nexport interface BackupFileInfo {\n\tid: string;\n\tname: string;\n\t/** RFC 3339 last-write timestamp, when the destination reports one. */\n\tmodifiedTime: string | null;\n}\n\n/** The destination I/O the manager drives - the host wires its own adapter\n * (e.g. over the Drive target ops). Every method may throw; the manager turns\n * failures into stable error codes. */\nexport interface BackupsHost {\n\t/** The target kind the managed files live on (e.g. 'drive'): the manager\n\t * only stamps an active file while the store is attached to this kind. */\n\tkind: string;\n\t/** The kv key the attached file id is persisted under (the same one the\n\t * target's connect path reads first). */\n\tactiveIdKey: string;\n\t/** List candidate backup files on the account. The manager applies the\n\t * naming rules on what comes back; internal files simply do not match. */\n\tlist(): Promise<BackupFileInfo[]>;\n\t/** A target over a FIXED file id - never re-resolved by name. The fixed id\n\t * is what keeps two backups from silently merging mid-switch. */\n\topen(fileId: string): BackupTarget;\n\t/** Create an empty file under this name and return its id. Refuse a name\n\t * the account already carries (a duplicate poisons find-by-name). */\n\tcreate(fileName: string): Promise<{ fileId: string }>;\n\t/** Delete a file for good. */\n\tremove(fileId: string): Promise<void>;\n\t/** Rename a file in place (metadata only: the id must not change). */\n\trename?(fileId: string, fileName: string): Promise<void>;\n\t/** Find (or create) the PERSONAL file by its canonical name, ignoring any\n\t * currently adopted id. Null when the user backs out of the session. */\n\tfindOrCreatePersonal(): Promise<{ fileId: string; created: boolean } | null>;\n\t/** Make sure a usable session exists before a remote call (re-consent when\n\t * it lapsed). False = the user refused; the gesture aborts as 'cancelled'.\n\t * Absent = sessions are the target's own business. */\n\tensureSession?(): Promise<boolean>;\n\t/** Resolve the owner label of a file this account does not own, for the\n\t * shared registry (\"shared by X\"). Best-effort. */\n\tfileOwner?(fileId: string): Promise<{ email: string | null; name: string | null } | null>;\n\t/** File ids that must never enter the registry even though the store may\n\t * attach them (e.g. a sharing engine's published copy file). */\n\texcludedFileIds?(): Promise<(string | null | undefined)[]>;\n}\n\n/** How backup files are named on the destination. */\nexport interface BackupsNaming {\n\t/** The personal, unnamed file (\"App.zip\"). Its label reads as null. */\n\tcanonicalName: string;\n\t/** label -> file name. Default: \"Stem (label).ext\" derived from\n\t * canonicalName (\"App.zip\" + \"family\" -> \"App (family).zip\"). */\n\tnamedFileFor?(label: string): string;\n\t/** file name -> parsed label; null = the personal file; undefined = not one\n\t * of ours (dropped from listings). Default matches the default namedFileFor. */\n\tparseLabel?(name: string): string | null | undefined;\n}\n\n/** The kv keys the manager persists under - override to keep a pre-existing\n * install's records. */\nexport interface BackupsKeys {\n\t/** The known-backups registry. */\n\tregistry?: string;\n\t/** fileId -> encrypted? map. */\n\tencrypted?: string;\n\t/** fileId -> shared? map. */\n\tshared?: string;\n\t/** Set while the ACTIVE file is not the personal one (another account's). */\n\tjoined?: string;\n\t/** Cached owner label of that file. */\n\towner?: string;\n}\n\n// --- Shapes -------------------------------------------------------------------\n\n/** Every backup this device has connected to: its own personal file and each\n * OTHER account's file it was let into. Isolated snapshots, all of them. */\nexport interface KnownBackups {\n\tpersonalFileId: string | null;\n\tshared: { fileId: string; ownerEmail: string | null; ownerName: string | null }[];\n}\n\n/** One row of the \"my backups\" list: a file this account owns. */\nexport interface BackupRow {\n\tfileId: string;\n\tname: string;\n\t/** Parsed display label; null for the personal, unnamed file. */\n\tlabel: string | null;\n\t/** Last-write time, epoch ms (null when the destination did not say). */\n\tmodifiedAt: number | null;\n\t/** true/false once known (opened, created or probed); null until then. */\n\tencrypted: boolean | null;\n\t/** true/false once learned (while this file was the active one); null\n\t * until then. A share link existing is the shared state. */\n\tshared: boolean | null;\n}\n\n/** The owner label of a joined (another account's) file. */\nexport interface BackupOwner {\n\temail: string | null;\n\tname: string | null;\n}\n\n/** Stable failure codes; the host maps them to its own copy. */\nexport type BackupsErrorCode = 'cancelled' | 'gone' | 'failed';\n\nexport interface BackupsSnapshot {\n\tregistry: KnownBackups;\n\t/** The file currently attached, when the store is on the managed kind. */\n\tactiveFileId: string | null;\n\t/** The active file is not the personal one (someone else's). */\n\tjoined: boolean;\n\t/** Who owns the joined file, when known. */\n\towner: BackupOwner | null;\n\t/** The last gesture's failure, or null. Reset at each gesture start. */\n\tlastError: BackupsErrorCode | null;\n}\n\nexport interface BackupsManager extends FlowStore<BackupsSnapshot> {\n\t/** Boot: load registry + joined/owner from kv and stamp the active file.\n\t * Kv-only, no destination call. */\n\thydrate(): Promise<void>;\n\t/** Re-read the registry and restamp the active file. Kv-only, cheap. */\n\trefresh(): Promise<void>;\n\t/** After an attach the manager did not make (a connect widget, a join):\n\t * record the now-active file in the registry and restamp joined/owner. */\n\tmarkActive(fileId: string): Promise<void>;\n\t/** Live list of this account's own files, per the naming rules, decorated\n\t * with the learned memories. One destination listing call. */\n\tlist(): Promise<BackupRow[]>;\n\t/** Learn a listed file's encrypted bit without opening it for real: one\n\t * download + header read, remembered for good. Null when unreachable. */\n\tprobeEncryption(fileId: string): Promise<boolean | null>;\n\t/** Record the ACTIVE file's live share state, keyed by file id, so its row\n\t * stays truthful after a switch away. */\n\tnoteShared(fileId: string, shared: boolean): Promise<void>;\n\t/** Open a known backup: an isolated snapshot replaces the local data.\n\t * 'encrypted' = collect its passphrase and call again with it. A file the\n\t * destination reports GONE is pruned from the registry (lastError 'gone'). */\n\topenBackup(fileId: string, passphrase?: string | null): Promise<'ok' | 'encrypted' | 'failed'>;\n\t/** Open (or create blank) the personal file. */\n\topenPersonal(passphrase?: string | null): Promise<'ok' | 'encrypted' | 'failed'>;\n\t/** Create a brand-new empty named backup and open it (starts blank - never\n\t * seeded from the currently loaded file). */\n\tcreateNamed(label: string): Promise<'ok' | 'failed'>;\n\t/** Record a file in the SHARED registry under the sharer's label, without\n\t * opening it. For a joined portfolio's dedicated wallet: the file may live\n\t * on this very account, yet it presents as \"shared by X\", never as mine. */\n\tregisterShared(fileId: string, owner: BackupOwner): Promise<void>;\n\t/** Create a joined portfolio's dedicated wallet: a file under the given\n\t * name (outside the own-files naming on purpose), attached BLANK - never\n\t * seeded from the previously loaded silo - and recorded in the shared\n\t * registry under the sharer's label. The host stages the shared content\n\t * afterwards (restore the projection, then save). */\n\tcreateShared(fileName: string, owner: BackupOwner): Promise<'ok' | 'failed'>;\n\t/** Rename a file in place (id unchanged, so renaming the ACTIVE file never\n\t * detaches it). The host target refuses a name already carried. */\n\trenameBackup(fileId: string, label: string): Promise<'ok' | 'failed'>;\n\t/** Delete a file for good. Deleting the ACTIVE one detaches first (the app\n\t * goes device-only, local data stays), so nothing writes mid-delete. */\n\tdeleteBackup(fileId: string): Promise<boolean>;\n\t/** Drop a KNOWN shared (another account's) file from this device's\n\t * registry. Forgetting only - the owner's file is never touched. */\n\tforgetShared(fileId: string): Promise<void>;\n\t/** The file name a label produces (the create form's preview line). */\n\tfileNameFor(label: string): string;\n}\n\n// --- Defaults -----------------------------------------------------------------\n\nconst DEFAULT_KEYS: Required<BackupsKeys> = {\n\tregistry: 'selfstore:backups:registry:v1',\n\tencrypted: 'selfstore:backups:encrypted:v1',\n\tshared: 'selfstore:backups:shared:v1',\n\tjoined: 'selfstore:backups:joined:v1',\n\towner: 'selfstore:backups:owner:v1'\n};\n\nconst escapeRe = (s: string): string => s.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n\n/** The default naming scheme: \"App.zip\" -> personal; \"App (label).zip\" ->\n * named. Derived from the canonical name's stem and extension. */\nfunction defaultNaming(\n\tnaming: BackupsNaming\n): Required<Pick<BackupsNaming, 'namedFileFor' | 'parseLabel'>> {\n\tconst dot = naming.canonicalName.lastIndexOf('.');\n\tconst stem = dot > 0 ? naming.canonicalName.slice(0, dot) : naming.canonicalName;\n\tconst ext = dot > 0 ? naming.canonicalName.slice(dot) : '';\n\tconst named = new RegExp(`^${escapeRe(stem)} \\\\((.+)\\\\)${escapeRe(ext)}$`);\n\treturn {\n\t\tnamedFileFor: (label: string) => `${stem} (${label.trim()})${ext}`,\n\t\tparseLabel: (name: string) => {\n\t\t\tif (name === naming.canonicalName) return null;\n\t\t\tconst m = named.exec(name);\n\t\t\treturn m ? m[1] : undefined;\n\t\t}\n\t};\n}\n\n// --- Factory ------------------------------------------------------------------\n\nexport function createBackupsManager(deps: {\n\tstore: LocalStore;\n\tkv: KV;\n\thost: BackupsHost;\n\tnaming: BackupsNaming;\n\tkeys?: BackupsKeys;\n}): BackupsManager {\n\tconst { store, kv, host } = deps;\n\tconst KEYS = { ...DEFAULT_KEYS, ...deps.keys };\n\tconst namingDefaults = defaultNaming(deps.naming);\n\tconst naming = {\n\t\tnamedFileFor: deps.naming.namedFileFor ?? namingDefaults.namedFileFor,\n\t\tparseLabel: deps.naming.parseLabel ?? namingDefaults.parseLabel\n\t};\n\n\tconst m = makeMachine<BackupsSnapshot>({\n\t\tregistry: { personalFileId: null, shared: [] },\n\t\tactiveFileId: null,\n\t\tjoined: false,\n\t\towner: null,\n\t\tlastError: null\n\t});\n\n\tconst fail = (code: BackupsErrorCode): void => m.set({ lastError: code });\n\n\t// Which file ids the destination lists as OURS, and which one carries the\n\t// canonical (personal) name. Filled by list(), refreshed lazily where a\n\t// truthful own/foreign call is needed. Null until first learned.\n\tlet ownIds: Set<string> | null = null;\n\tlet ownCanonicalId: string | null = null;\n\n\tfunction learnOwn(files: BackupFileInfo[]): void {\n\t\tconst ids = new Set<string>();\n\t\tlet canonical: string | null = null;\n\t\tfor (const f of files) {\n\t\t\tconst label = naming.parseLabel(f.name);\n\t\t\tif (label === undefined) continue;\n\t\t\tids.add(f.id);\n\t\t\tif (label === null) canonical = f.id;\n\t\t}\n\t\townIds = ids;\n\t\townCanonicalId = canonical;\n\t}\n\n\tasync function ensureOwnKnown(): Promise<void> {\n\t\tif (ownIds !== null) return;\n\t\ttry {\n\t\t\tlearnOwn(await host.list());\n\t\t} catch {\n\t\t\t// Offline: leave unknown - markActive falls back on the registry alone.\n\t\t}\n\t}\n\n\t/** Load the registry, self-healing: excluded ids (a sharing engine's copy\n\t * file) must never sit in it - recorded as \"personal\" they would mislabel\n\t * the chooser and let a later open treat a shared file as one's own. */\n\tasync function loadRegistry(): Promise<KnownBackups> {\n\t\tconst reg = (await kv.get<KnownBackups>(KEYS.registry)) ?? { personalFileId: null, shared: [] };\n\t\tconst excluded = (await host.excludedFileIds?.()) ?? [];\n\t\tconst bad = new Set(excluded.filter((x): x is string => typeof x === 'string'));\n\t\tif (bad.size === 0) return reg;\n\t\tconst healed: KnownBackups = {\n\t\t\tpersonalFileId: reg.personalFileId && bad.has(reg.personalFileId) ? null : reg.personalFileId,\n\t\t\tshared: reg.shared.filter((s) => !bad.has(s.fileId))\n\t\t};\n\t\tif (\n\t\t\thealed.personalFileId !== reg.personalFileId ||\n\t\t\thealed.shared.length !== reg.shared.length\n\t\t) {\n\t\t\tawait kv.set(KEYS.registry, healed);\n\t\t}\n\t\treturn healed;\n\t}\n\n\tasync function saveRegistry(next: KnownBackups): Promise<void> {\n\t\tm.set({ registry: next });\n\t\tawait kv.set(KEYS.registry, next);\n\t}\n\n\tasync function noteEncrypted(fileId: string, encrypted: boolean): Promise<void> {\n\t\tconst map = (await kv.get<Record<string, boolean>>(KEYS.encrypted)) ?? {};\n\t\tif (map[fileId] === encrypted) return;\n\t\tmap[fileId] = encrypted;\n\t\tawait kv.set(KEYS.encrypted, map);\n\t}\n\n\t/** Stamp the currently attached file (from the target's own persisted id)\n\t * into the snapshot and the registry. Kv-only. */\n\tasync function registerActive(): Promise<void> {\n\t\tif (store.state.targetKind !== host.kind) {\n\t\t\tm.set({ activeFileId: null });\n\t\t\treturn;\n\t\t}\n\t\tconst fileId = (await kv.get<string>(host.activeIdKey)) ?? null;\n\t\tm.set({ activeFileId: fileId });\n\t\tif (!fileId) return;\n\t\tconst excluded = (await host.excludedFileIds?.()) ?? [];\n\t\tif (excluded.includes(fileId)) return;\n\t\tconst reg = await loadRegistry();\n\t\tif (m.snapshot.joined) {\n\t\t\tconst owner = m.snapshot.owner;\n\t\t\tconst entry = { fileId, ownerEmail: owner?.email ?? null, ownerName: owner?.name ?? null };\n\t\t\tawait saveRegistry({\n\t\t\t\tpersonalFileId: reg.personalFileId === fileId ? null : reg.personalFileId,\n\t\t\t\tshared: [entry, ...reg.shared.filter((s) => s.fileId !== fileId)]\n\t\t\t});\n\t\t\treturn;\n\t\t}\n\t\t// Not joined. The personal slot belongs to the CANONICAL file alone: an\n\t\t// own NAMED backup lists live and must not clobber it (the slot is the\n\t\t// escape back to the personal file).\n\t\tconst canonical =\n\t\t\tfileId === ownCanonicalId ||\n\t\t\treg.personalFileId === fileId ||\n\t\t\t(ownIds === null && reg.personalFileId === null);\n\t\tif (canonical) {\n\t\t\tawait saveRegistry({\n\t\t\t\tpersonalFileId: fileId,\n\t\t\t\tshared: reg.shared.filter((s) => s.fileId !== fileId)\n\t\t\t});\n\t\t} else if (reg.shared.some((s) => s.fileId === fileId)) {\n\t\t\tawait saveRegistry({\n\t\t\t\tpersonalFileId: reg.personalFileId,\n\t\t\t\tshared: reg.shared.filter((s) => s.fileId !== fileId)\n\t\t\t});\n\t\t}\n\t}\n\n\t/** Session guard shared by the remote gestures: absent hook = no guard. */\n\tasync function ensureSession(): Promise<boolean> {\n\t\tif (!host.ensureSession) return true;\n\t\treturn host.ensureSession();\n\t}\n\n\tasync function markActive(fileId: string): Promise<void> {\n\t\tconst reg = await loadRegistry();\n\t\tconst knownShared = reg.shared.find((s) => s.fileId === fileId);\n\t\tlet mine = reg.personalFileId === fileId;\n\t\tif (!mine && !knownShared) {\n\t\t\t// Unknown file: ask the destination whose it is (one listing call,\n\t\t\t// cached). An own named backup must never read as joined - labeled\n\t\t\t// \"shared by me\", it would sit in the wrong section for good.\n\t\t\tawait ensureOwnKnown();\n\t\t\tmine = ownIds === null ? reg.personalFileId === null : ownIds.has(fileId);\n\t\t}\n\t\tif (mine || (!knownShared && ownIds?.has(fileId))) {\n\t\t\tawait kv.del(KEYS.joined);\n\t\t\tawait kv.del(KEYS.owner);\n\t\t\tm.set({ joined: false, owner: null });\n\t\t} else {\n\t\t\tconst owner: BackupOwner | null = knownShared\n\t\t\t\t? { email: knownShared.ownerEmail, name: knownShared.ownerName }\n\t\t\t\t: ((await host.fileOwner?.(fileId).catch(() => null)) ?? null);\n\t\t\tm.set({ joined: true, owner });\n\t\t\tawait kv.set(KEYS.joined, true);\n\t\t\tif (owner) await kv.set(KEYS.owner, owner);\n\t\t\telse await kv.del(KEYS.owner);\n\t\t}\n\t\tawait registerActive();\n\t}\n\n\t/** The one attach path every open shares: FIXED-id target, and the\n\t * remembered id committed only after the attach succeeds - committed up\n\t * front, the OUTGOING target (which resolves its file lazily from that\n\t * same key) would flush the old file's data into the one being opened:\n\t * two \"isolated\" backups silently merged. `wipe` keeps sync-meta from\n\t * crossing files (cross-file tombstones erase the other side). */\n\tasync function attachFixed(\n\t\tfileId: string,\n\t\tpassphrase: string | null,\n\t\tstrategy: 'replace-local' | 'replace-remote'\n\t): Promise<void> {\n\t\tconst target = host.open(fileId);\n\t\tawait store.attachTarget(target, {\n\t\t\tpassword: passphrase,\n\t\t\tstrategy,\n\t\t\tkeepSession: store.state.targetKind === host.kind,\n\t\t\twipe: true\n\t\t});\n\t\tawait kv.set(host.activeIdKey, fileId);\n\t}\n\n\treturn {\n\t\tget snapshot() {\n\t\t\treturn m.snapshot;\n\t\t},\n\t\tsubscribe: m.subscribe,\n\n\t\tfileNameFor(label: string): string {\n\t\t\treturn naming.namedFileFor(label.trim());\n\t\t},\n\n\t\tasync hydrate(): Promise<void> {\n\t\t\tconst joined = (await kv.get(KEYS.joined)) === true;\n\t\t\tconst owner = (await kv.get<BackupOwner>(KEYS.owner)) ?? null;\n\t\t\tm.set({ joined, owner, registry: await loadRegistry() });\n\t\t\tawait registerActive();\n\t\t},\n\n\t\tasync refresh(): Promise<void> {\n\t\t\tm.set({ registry: await loadRegistry() });\n\t\t\tawait registerActive();\n\t\t},\n\n\t\tmarkActive,\n\n\t\tasync list(): Promise<BackupRow[]> {\n\t\t\tconst files = await host.list();\n\t\t\tlearnOwn(files);\n\t\t\tconst enc = (await kv.get<Record<string, boolean>>(KEYS.encrypted)) ?? {};\n\t\t\tconst shr = (await kv.get<Record<string, boolean>>(KEYS.shared)) ?? {};\n\t\t\tconst rows: BackupRow[] = [];\n\t\t\tfor (const f of files) {\n\t\t\t\tconst label = naming.parseLabel(f.name);\n\t\t\t\tif (label === undefined) continue; // not one of ours (a copy, a bulletin, ...)\n\t\t\t\trows.push({\n\t\t\t\t\tfileId: f.id,\n\t\t\t\t\tname: f.name,\n\t\t\t\t\tlabel,\n\t\t\t\t\tmodifiedAt: f.modifiedTime ? Date.parse(f.modifiedTime) || null : null,\n\t\t\t\t\tencrypted: enc[f.id] ?? null,\n\t\t\t\t\tshared: shr[f.id] ?? null\n\t\t\t\t});\n\t\t\t}\n\t\t\treturn rows;\n\t\t},\n\n\t\tasync probeEncryption(fileId: string): Promise<boolean | null> {\n\t\t\ttry {\n\t\t\t\tconst info = await store.inspectTarget(host.open(fileId));\n\t\t\t\tif (!info.hasBackup) return null;\n\t\t\t\tawait noteEncrypted(fileId, info.encrypted);\n\t\t\t\treturn info.encrypted;\n\t\t\t} catch {\n\t\t\t\treturn null;\n\t\t\t}\n\t\t},\n\n\t\tasync noteShared(fileId: string, shared: boolean): Promise<void> {\n\t\t\tconst map = (await kv.get<Record<string, boolean>>(KEYS.shared)) ?? {};\n\t\t\tif (map[fileId] === shared) return;\n\t\t\tmap[fileId] = shared;\n\t\t\tawait kv.set(KEYS.shared, map);\n\t\t},\n\n\t\tasync openBackup(fileId, passphrase = null): Promise<'ok' | 'encrypted' | 'failed'> {\n\t\t\tm.set({ lastError: null });\n\t\t\ttry {\n\t\t\t\tif (!(await ensureSession())) {\n\t\t\t\t\tfail('cancelled');\n\t\t\t\t\treturn 'failed';\n\t\t\t\t}\n\t\t\t\tconst info = await store.inspectTarget(host.open(fileId));\n\t\t\t\tif (!info.hasBackup) {\n\t\t\t\t\t// Dead entry (deleted, or unshared from this account): prune it so\n\t\t\t\t\t// its row stops re-failing forever. A transient failure throws\n\t\t\t\t\t// instead and never prunes - dropping a good record on a blip\n\t\t\t\t\t// would be far worse than one more failed tap.\n\t\t\t\t\tconst reg = await loadRegistry();\n\t\t\t\t\tawait saveRegistry({\n\t\t\t\t\t\tpersonalFileId: reg.personalFileId === fileId ? null : reg.personalFileId,\n\t\t\t\t\t\tshared: reg.shared.filter((s) => s.fileId !== fileId)\n\t\t\t\t\t});\n\t\t\t\t\tfail('gone');\n\t\t\t\t\treturn 'failed';\n\t\t\t\t}\n\t\t\t\tawait noteEncrypted(fileId, info.encrypted); // the listing cannot tell\n\t\t\t\tif (info.encrypted && !passphrase) return 'encrypted';\n\t\t\t\tawait attachFixed(fileId, passphrase, 'replace-local');\n\t\t\t\tawait markActive(fileId);\n\t\t\t\treturn store.state.locked ? 'encrypted' : 'ok';\n\t\t\t} catch {\n\t\t\t\tfail('failed');\n\t\t\t\treturn 'failed';\n\t\t\t}\n\t\t},\n\n\t\tasync openPersonal(passphrase = null): Promise<'ok' | 'encrypted' | 'failed'> {\n\t\t\tm.set({ lastError: null });\n\t\t\ttry {\n\t\t\t\tconst own = await host.findOrCreatePersonal();\n\t\t\t\tif (!own) {\n\t\t\t\t\tfail('cancelled');\n\t\t\t\t\treturn 'failed';\n\t\t\t\t}\n\t\t\t\townCanonicalId = own.fileId;\n\t\t\t\townIds?.add(own.fileId);\n\t\t\t\tconst reg = await loadRegistry();\n\t\t\t\tawait saveRegistry({\n\t\t\t\t\tpersonalFileId: own.fileId,\n\t\t\t\t\tshared: reg.shared.filter((s) => s.fileId !== own.fileId)\n\t\t\t\t});\n\t\t\t\tif (!own.created) return await this.openBackup(own.fileId, passphrase);\n\t\t\t\tawait attachFixed(own.fileId, null, 'replace-remote');\n\t\t\t\tawait markActive(own.fileId);\n\t\t\t\treturn 'ok';\n\t\t\t} catch {\n\t\t\t\tfail('failed');\n\t\t\t\treturn 'failed';\n\t\t\t}\n\t\t},\n\n\t\tasync createNamed(label): Promise<'ok' | 'failed'> {\n\t\t\tm.set({ lastError: null });\n\t\t\tconst clean = label.trim();\n\t\t\tif (!clean) {\n\t\t\t\tfail('failed');\n\t\t\t\treturn 'failed';\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tif (!(await ensureSession())) {\n\t\t\t\t\tfail('cancelled');\n\t\t\t\t\treturn 'failed';\n\t\t\t\t}\n\t\t\t\tconst { fileId } = await host.create(naming.namedFileFor(clean));\n\t\t\t\townIds?.add(fileId);\n\t\t\t\tawait attachFixed(fileId, null, 'replace-remote');\n\t\t\t\tawait noteEncrypted(fileId, false); // a fresh backup starts unprotected\n\t\t\t\tawait markActive(fileId);\n\t\t\t\treturn 'ok';\n\t\t\t} catch {\n\t\t\t\tfail('failed');\n\t\t\t\treturn 'failed';\n\t\t\t}\n\t\t},\n\n\t\tasync registerShared(fileId, owner): Promise<void> {\n\t\t\tconst reg = await loadRegistry();\n\t\t\tawait saveRegistry({\n\t\t\t\tpersonalFileId: reg.personalFileId === fileId ? null : reg.personalFileId,\n\t\t\t\tshared: [\n\t\t\t\t\t{ fileId, ownerEmail: owner.email, ownerName: owner.name },\n\t\t\t\t\t...reg.shared.filter((s) => s.fileId !== fileId)\n\t\t\t\t]\n\t\t\t});\n\t\t},\n\n\t\tasync createShared(fileName, owner): Promise<'ok' | 'failed'> {\n\t\t\tm.set({ lastError: null });\n\t\t\ttry {\n\t\t\t\tif (!(await ensureSession())) {\n\t\t\t\t\tfail('cancelled');\n\t\t\t\t\treturn 'failed';\n\t\t\t\t}\n\t\t\t\tconst { fileId } = await host.create(fileName);\n\t\t\t\t// Registered as shared BEFORE the attach: markActive must find it in\n\t\t\t\t// the shared registry, or it would resolve this account as the owner\n\t\t\t\t// and file the joined wallet under \"mine\".\n\t\t\t\tawait this.registerShared(fileId, owner);\n\t\t\t\tawait attachFixed(fileId, null, 'replace-remote');\n\t\t\t\tawait noteEncrypted(fileId, false); // a member wallet is never protected\n\t\t\t\tawait markActive(fileId);\n\t\t\t\treturn 'ok';\n\t\t\t} catch {\n\t\t\t\tfail('failed');\n\t\t\t\treturn 'failed';\n\t\t\t}\n\t\t},\n\n\t\tasync renameBackup(fileId, label): Promise<'ok' | 'failed'> {\n\t\t\tm.set({ lastError: null });\n\t\t\tconst clean = label.trim();\n\t\t\tif (!clean || !host.rename) {\n\t\t\t\tfail('failed');\n\t\t\t\treturn 'failed';\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tawait host.rename(fileId, naming.namedFileFor(clean));\n\t\t\t\treturn 'ok';\n\t\t\t} catch {\n\t\t\t\tfail('failed');\n\t\t\t\treturn 'failed';\n\t\t\t}\n\t\t},\n\n\t\tasync deleteBackup(fileId): Promise<boolean> {\n\t\t\tm.set({ lastError: null });\n\t\t\ttry {\n\t\t\t\tif (fileId === m.snapshot.activeFileId) {\n\t\t\t\t\t// Detach first: the app goes device-only (local data stays) so\n\t\t\t\t\t// nothing writes to the file as it is removed. keepSession: a\n\t\t\t\t\t// default detach runs the target's disconnect, which forgets the\n\t\t\t\t\t// destination credentials - the remove below still needs them,\n\t\t\t\t\t// and the user deleted a FILE, not their account connection.\n\t\t\t\t\tm.set({ activeFileId: null });\n\t\t\t\t\tawait store.detachTarget({ keepSession: true });\n\t\t\t\t\t// The remembered file id would normally be cleared by that\n\t\t\t\t\t// disconnect; do it here so nothing dangles on a deleted file.\n\t\t\t\t\tawait kv.del(host.activeIdKey);\n\t\t\t\t}\n\t\t\t\tawait host.remove(fileId);\n\t\t\t\tconst reg = await loadRegistry();\n\t\t\t\tawait saveRegistry({\n\t\t\t\t\tpersonalFileId: reg.personalFileId === fileId ? null : reg.personalFileId,\n\t\t\t\t\tshared: reg.shared.filter((s) => s.fileId !== fileId)\n\t\t\t\t});\n\t\t\t\treturn true;\n\t\t\t} catch {\n\t\t\t\tfail('failed');\n\t\t\t\treturn false;\n\t\t\t}\n\t\t},\n\n\t\tasync forgetShared(fileId): Promise<void> {\n\t\t\tconst reg = await loadRegistry();\n\t\t\tawait saveRegistry({\n\t\t\t\tpersonalFileId: reg.personalFileId,\n\t\t\t\tshared: reg.shared.filter((s) => s.fileId !== fileId)\n\t\t\t});\n\t\t}\n\t};\n}\n"]}
@@ -1,6 +1,5 @@
1
1
  import { unseal } from './chunk-QBCU7OOP.js';
2
- import { freshKdfParams, deriveKey } from './chunk-CM3HSBCE.js';
3
- import './chunk-SFF7KKM4.js';
2
+ import { freshKdfParams, deriveKey } from './chunk-3OBJOMQ2.js';
4
3
 
5
4
  // src/persistence/cache-lock.ts
6
5
  function freshCacheKdf() {
@@ -19,5 +18,5 @@ async function opens(key, probe) {
19
18
  }
20
19
 
21
20
  export { freshCacheKdf, keyFromPassword, opens };
22
- //# sourceMappingURL=cache-lock-7D6N42ZP.js.map
23
- //# sourceMappingURL=cache-lock-7D6N42ZP.js.map
21
+ //# sourceMappingURL=cache-lock-KWJUORTS.js.map
22
+ //# sourceMappingURL=cache-lock-KWJUORTS.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/persistence/cache-lock.ts"],"names":[],"mappings":";;;;AAeO,SAAS,aAAA,GAA0B;AACzC,EAAA,OAAO,cAAA,EAAe;AACvB;AAGO,SAAS,eAAA,CAAgB,UAAkB,GAAA,EAAmC;AACpF,EAAA,OAAO,SAAA,CAAU,UAAU,GAAG,CAAA;AAC/B;AAIA,eAAsB,KAAA,CAAM,KAAgB,KAAA,EAAsC;AACjF,EAAA,IAAI;AACH,IAAA,MAAM,MAAA,CAAO,KAAK,KAAK,CAAA;AACvB,IAAA,OAAO,IAAA;AAAA,EACR,CAAA,CAAA,MAAQ;AACP,IAAA,OAAO,KAAA;AAAA,EACR;AACD","file":"cache-lock-KWJUORTS.js","sourcesContent":["// Seal-key material for a lock-mode cache. The key is derived from a password\n// (the backup's Argon2id KDF) or supplied by the app (e.g. a passkey PRF key),\n// and is held in memory only - never written next to the data. That is what a\n// device-key seal (cache-crypto) cannot give: a copy of the whole browser\n// profile carries the device key, but not a secret kept in RAM. Only the KDF\n// salt is persisted, and a salt is not secret.\n\nimport { unseal, type EncEnvelope } from './cache-crypto';\nimport { deriveKey, freshKdfParams } from '../selfstore/crypto';\nimport type { KdfParams } from '../selfstore/types';\n\n/** Argon2id parameters for a password-locked cache; persisted in the clear. */\nexport type CacheKdf = KdfParams;\n\n/** Fresh Argon2id parameters (random salt, current default cost). */\nexport function freshCacheKdf(): CacheKdf {\n\treturn freshKdfParams();\n}\n\n/** Derive the AES-GCM seal key from a password and its stored parameters. */\nexport function keyFromPassword(password: string, kdf: CacheKdf): Promise<CryptoKey> {\n\treturn deriveKey(password, kdf);\n}\n\n/** True if `key` decrypts `probe`. A wrong key fails AES-GCM authentication;\n * that is a wrong-secret answer, never an error to surface. */\nexport async function opens(key: CryptoKey, probe: EncEnvelope): Promise<boolean> {\n\ttry {\n\t\tawait unseal(key, probe);\n\t\treturn true;\n\t} catch {\n\t\treturn false;\n\t}\n}\n"]}
@@ -1,4 +1,28 @@
1
- import { SelfstoreError } from './chunk-SFF7KKM4.js';
1
+ // src/selfstore/errors.ts
2
+ function errorLabelKey(code) {
3
+ return "error." + code.toLowerCase().replace(/_([a-z])/g, (_m, c) => c.toUpperCase());
4
+ }
5
+ var SelfstoreError = class extends Error {
6
+ constructor(code, message) {
7
+ super(message);
8
+ this.code = code;
9
+ this.name = "SelfstoreError";
10
+ }
11
+ code;
12
+ };
13
+ var AuthExpiredError = class extends SelfstoreError {
14
+ constructor(message = "Access to the destination expired or was revoked.") {
15
+ super("AUTH_EXPIRED", message);
16
+ this.name = "AuthExpiredError";
17
+ }
18
+ };
19
+ function isSelfstoreError(e) {
20
+ return e instanceof Error && typeof e.code === "string" && (e.name === "SelfstoreError" || e.name === "AuthExpiredError");
21
+ }
22
+ function isAuthExpired(e) {
23
+ if (typeof e !== "object" || e === null) return false;
24
+ return e.code === "AUTH_EXPIRED";
25
+ }
2
26
 
3
27
  // src/selfstore/base64.ts
4
28
  function toBase64(bytes) {
@@ -230,6 +254,6 @@ async function openExternalSlot(slot, secret) {
230
254
  }
231
255
  }
232
256
 
233
- export { MAX_KEY_SLOTS, assertKdfBounds, deriveKey, freshKdfParams, fromBase64, gcmOpenAad, gcmSealAad, mintExternalSlot, mintSlot, openExternalSlot, openSlot, toBase64 };
234
- //# sourceMappingURL=chunk-CM3HSBCE.js.map
235
- //# sourceMappingURL=chunk-CM3HSBCE.js.map
257
+ export { AuthExpiredError, MAX_KEY_SLOTS, SelfstoreError, assertKdfBounds, deriveKey, errorLabelKey, freshKdfParams, fromBase64, gcmOpenAad, gcmSealAad, isAuthExpired, isSelfstoreError, mintExternalSlot, mintSlot, openExternalSlot, openSlot, toBase64 };
258
+ //# sourceMappingURL=chunk-3OBJOMQ2.js.map
259
+ //# sourceMappingURL=chunk-3OBJOMQ2.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/selfstore/errors.ts","../src/selfstore/base64.ts","../src/selfstore/argon2.ts","../src/selfstore/kdf.ts","../src/selfstore/crypto.ts"],"names":[],"mappings":"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","file":"chunk-3OBJOMQ2.js","sourcesContent":["// One stable error-code enum for the whole library, format layer and\n// persistence layer alike. Codes may be added in a minor release (keep a\n// default branch in exhaustive switches); existing codes never change meaning.\n\nexport type SelfstoreErrorCode =\n\t// backup format / crypto\n\t| 'BAD_FORMAT' // not a backup file / corrupt framing\n\t| 'UNSUPPORTED_VERSION' // file (or cipher/KDF) from a newer format version\n\t| 'PASSWORD_REQUIRED' // encrypted backup opened without a password, or vault used before unlock()\n\t| 'DECRYPT_FAILED' // wrong password, or tampered/corrupt ciphertext\n\t| 'TOO_LARGE' // archive entry exceeds the size guard (zip-bomb defence)\n\t| 'VERIFY_FAILED' // a freshly written backup did not read back as expected: do not announce it\n\t| 'FOREIGN_BACKUP' // the backup names another app in its header: refused before any password is asked\n\t// durable targets / store lifecycle\n\t| 'AUTH_EXPIRED' // access genuinely lost: only a user gesture (reconnect) fixes it\n\t| 'TARGET_UNAVAILABLE' // transient: offline, cold start, 5xx - retry later\n\t| 'TARGET_WRITE_FAILED' // destination refused or failed the write (non-auth), retryable\n\t| 'TARGET_GONE' // destination permanently unwritable (file deleted, permission withdrawn, storage full); retrying cannot fix it\n\t| 'NOT_CONNECTED' // no connected destination (no file id, no config)\n\t| 'WEAK_PASSWORD' // password fails the store's configured passwordPolicy\n\t| 'ENCRYPTION_REQUIRED' // store set requireEncryption: a plaintext attach, backup or export is refused\n\t| 'UNEXPECTEDLY_UNENCRYPTED' // downgrade guard: expected encrypted, found plaintext\n\t| 'SCHEMA_TOO_NEW' // data written by a newer app schema: update the app to sync again\n\t// passwordless groups (see PEERS.md)\n\t| 'IDENTITY_REQUIRED' // group-encrypted copy opened without a member identity\n\t| 'SIGNATURE_INVALID' // copy/manifest signature failed, or author not a trusted member\n\t| 'NOT_A_RECIPIENT' // valid group copy, but no envelope for this identity\n\t| 'MANIFEST_ROLLBACK'; // membership manifest older than one already applied\n\n/**\n * i18n key for an error code: lower-camel-cased under `error.`, e.g.\n * 'error.authExpired'. The library ships the key, the app owns the copy;\n * consumers never have to show the raw developer message. Deterministic, so\n * codes added later get a key for free - keep a fallback for unknown ones.\n */\nexport function errorLabelKey(code: SelfstoreErrorCode): string {\n\treturn 'error.' + code.toLowerCase().replace(/_([a-z])/g, (_m, c: string) => c.toUpperCase());\n}\n\nexport class SelfstoreError extends Error {\n\tconstructor(\n\t\treadonly code: SelfstoreErrorCode,\n\t\tmessage: string\n\t) {\n\t\tsuper(message);\n\t\tthis.name = 'SelfstoreError';\n\t}\n}\n\n/**\n * Genuine loss of access to a destination (session gone, token revoked):\n * only a user gesture fixes it. Anything else a target throws is treated as\n * transient and retried, so a network hiccup never raises the blocking\n * reconnect gate over a still-valid connection. Custom targets and DriveAuth\n * implementations throw this to signal the genuine case.\n */\nexport class AuthExpiredError extends SelfstoreError {\n\tconstructor(message = 'Access to the destination expired or was revoked.') {\n\t\tsuper('AUTH_EXPIRED', message);\n\t\tthis.name = 'AuthExpiredError';\n\t}\n}\n\n/**\n * Shape check rather than instanceof, so it survives two bundled copies of\n * the library. The name check keeps foreign coded errors (ENOENT...) out.\n */\nexport function isSelfstoreError(e: unknown): e is SelfstoreError {\n\treturn (\n\t\te instanceof Error &&\n\t\ttypeof (e as { code?: unknown }).code === 'string' &&\n\t\t(e.name === 'SelfstoreError' || e.name === 'AuthExpiredError')\n\t);\n}\n\n/** True on genuine access loss, false on transient blips. */\nexport function isAuthExpired(e: unknown): boolean {\n\tif (typeof e !== 'object' || e === null) return false;\n\treturn (e as { code?: unknown }).code === 'AUTH_EXPIRED';\n}\n","/**\n * Base64 <-> bytes, shared by the crypto layer and the KDF worker (the worker\n * bundle must not drag the whole crypto module in for two helpers).\n */\n\nexport function toBase64(bytes: Uint8Array): string {\n\tlet bin = '';\n\tfor (const b of bytes) bin += String.fromCharCode(b);\n\treturn btoa(bin);\n}\n\nexport function fromBase64(s: string): Uint8Array {\n\tconst bin = atob(s);\n\tconst out = new Uint8Array(bin.length);\n\tfor (let i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i);\n\treturn out;\n}\n","// The one Argon2id call, shared by the calling-thread fallback and the KDF\n// worker so both derive byte-identical keys. hash-wasm loads dynamically -\n// only when a password is actually used.\n\nimport { fromBase64 } from './base64';\n\n/** AES-256 key length. */\nexport const KEY_BYTES = 32;\n\n/** One key-derivation job (postMessage-friendly: plain fields only). */\nexport interface KdfJob {\n\tid: number;\n\tpassword: string;\n\t/** Base64, exactly as stored in the backup header. */\n\tsalt: string;\n\t/** Memory cost in KiB. */\n\tm: number;\n\t/** Iterations (passes). */\n\tt: number;\n\t/** Parallelism (lanes). */\n\tp: number;\n}\n\n/** The worker's reply: the raw key, or the failure message. */\nexport interface KdfReply {\n\tid: number;\n\tkey?: Uint8Array;\n\terror?: string;\n}\n\nexport async function runKdfJob(job: KdfJob): Promise<Uint8Array> {\n\tconst { argon2id } = await import('hash-wasm');\n\treturn argon2id({\n\t\tpassword: job.password,\n\t\tsalt: fromBase64(job.salt),\n\t\tparallelism: job.p,\n\t\titerations: job.t,\n\t\tmemorySize: job.m,\n\t\thashLength: KEY_BYTES,\n\t\toutputType: 'binary'\n\t});\n}\n","// Off-main-thread key derivation. Argon2id takes about a second at the\n// default cost, which would jank every encrypted push and pull if it ran on\n// the UI thread. One lazy dedicated worker, terminated after an idle window\n// so its ~46 MiB of WASM memory is not held forever. Any failure to spawn,\n// load, or answer falls back to the calling thread, which runs the exact\n// same runKdfJob - the worker is a responsiveness optimization, never a\n// correctness dependency.\n\nimport { runKdfJob, type KdfJob, type KdfReply } from './argon2';\nimport type { KdfParams } from './types';\n\n// Generous: a wedged worker is rerun on the calling thread, and Argon2id at\n// the default cost already takes a second or two on a phone.\nconst JOB_TIMEOUT_MS = 30_000;\nconst IDLE_TERMINATE_MS = 30_000;\n\ninterface Pending {\n\tjob: KdfJob;\n\tresolve: (key: Uint8Array) => void;\n\treject: (e: unknown) => void;\n\ttimer: ReturnType<typeof setTimeout>;\n}\n\nlet worker: Worker | null = null;\n// Once the worker path fails (blocked, missing asset), stay on the calling\n// thread for the rest of the session instead of failing again.\nlet broken = false;\nlet seq = 0;\nconst pending = new Map<number, Pending>();\nlet idleTimer: ReturnType<typeof setTimeout> | undefined;\n\nfunction armIdleStop(): void {\n\tclearTimeout(idleTimer);\n\tidleTimer = setTimeout(() => {\n\t\tif (pending.size === 0 && worker) {\n\t\t\tworker.terminate();\n\t\t\tworker = null; // respawned on the next job\n\t\t}\n\t}, IDLE_TERMINATE_MS);\n}\n\n/** Abandon the worker and finish every queued job on the calling thread. */\nfunction fallBack(): void {\n\tbroken = true;\n\tworker?.terminate();\n\tworker = null;\n\tclearTimeout(idleTimer);\n\tfor (const p of pending.values()) {\n\t\tclearTimeout(p.timer);\n\t\trunKdfJob(p.job).then(p.resolve, p.reject);\n\t}\n\tpending.clear();\n}\n\nfunction ensureWorker(): Worker | null {\n\tif (broken) return null;\n\tif (worker) return worker;\n\tif (typeof Worker === 'undefined') {\n\t\tbroken = true; // Node, old runtimes\n\t\treturn null;\n\t}\n\ttry {\n\t\t// Bundlers (Vite, webpack 5, Rollup) detect this exact\n\t\t// `new Worker(new URL(...), import.meta.url)` shape to ship the worker\n\t\t// file as an asset - do not refactor it away.\n\t\tworker = new Worker(new URL('./kdf-worker.js', import.meta.url), {\n\t\t\ttype: 'module',\n\t\t\tname: 'selfstore-kdf'\n\t\t});\n\t} catch {\n\t\tbroken = true;\n\t\treturn null;\n\t}\n\tworker.onmessage = (ev: MessageEvent<KdfReply>): void => {\n\t\tconst reply = ev.data;\n\t\tconst p = pending.get(reply.id);\n\t\tif (!p) return;\n\t\tpending.delete(reply.id);\n\t\tclearTimeout(p.timer);\n\t\tif (reply.key) p.resolve(reply.key);\n\t\telse p.reject(new Error(reply.error ?? 'key derivation failed in the worker'));\n\t\tarmIdleStop();\n\t};\n\t// Script 404, parse error, CSP: rerun the queued jobs locally.\n\tworker.onerror = (): void => fallBack();\n\treturn worker;\n}\n\n/** Derive the raw AES key for `kdf`, off the main thread when possible. */\nexport function deriveRaw(password: string, kdf: KdfParams): Promise<Uint8Array> {\n\tconst job: KdfJob = { id: ++seq, password, salt: kdf.salt, m: kdf.m, t: kdf.t, p: kdf.p };\n\tconst w = ensureWorker();\n\tif (!w) return runKdfJob(job);\n\treturn new Promise<Uint8Array>((resolve, reject) => {\n\t\tconst timer = setTimeout(() => {\n\t\t\t// fallBack() clears every OTHER pending timer, so no job runs twice.\n\t\t\tpending.delete(job.id);\n\t\t\tfallBack();\n\t\t\trunKdfJob(job).then(resolve, reject);\n\t\t}, JOB_TIMEOUT_MS);\n\t\tpending.set(job.id, { job, resolve, reject, timer });\n\t\tw.postMessage(job);\n\t});\n}\n","// AES-256-GCM via WebCrypto over an Argon2id-derived key (hash-wasm). The\n// derivation runs in a worker when the platform has one (see kdf.ts); the\n// WASM is imported dynamically so it only loads when a password is used.\n\nimport type { KdfParams, PasswordSlot, ExternalSlot } from './types';\nimport { deriveRaw } from './kdf';\nimport { toBase64, fromBase64 } from './base64';\nimport { SelfstoreError } from './errors';\n\nexport { toBase64, fromBase64 };\n\nconst IV_BYTES = 12;\nconst SALT_BYTES = 16;\n\n/**\n * Cost for new backups: 46 MiB, 3 passes - above the OWASP Argon2id minimum,\n * ~1-2 s on a phone. Params are stored per file, so older backups keep\n * decrypting with their own cost.\n */\nexport const DEFAULT_ARGON2 = { m: 47104, t: 3, p: 1 } as const;\n\n/**\n * Read-side ceiling (m is in KiB: 1 GiB, 10 passes, 4 lanes). Without it a\n * crafted file could declare m = 8_000_000 and make the reader allocate\n * gigabytes before any password check. Out-of-range params are\n * UNSUPPORTED_VERSION, not DECRYPT_FAILED - the file is not corrupt, its\n * parameters are just outside what this reader will run.\n */\nconst KDF_MAX = { m: 1024 * 1024, t: 10, p: 4 } as const;\nconst KDF_MIN = { m: 8, t: 1, p: 1 } as const;\n\n/** Throws UNSUPPORTED_VERSION on out-of-range Argon2 params. Call before touching the WASM. */\nexport function assertKdfBounds(kdf: KdfParams): void {\n\tconst { m, t, p } = kdf;\n\tconst ok =\n\t\tNumber.isInteger(m) &&\n\t\tNumber.isInteger(t) &&\n\t\tNumber.isInteger(p) &&\n\t\tm >= KDF_MIN.m &&\n\t\tm <= KDF_MAX.m &&\n\t\tt >= KDF_MIN.t &&\n\t\tt <= KDF_MAX.t &&\n\t\tp >= KDF_MIN.p &&\n\t\tp <= KDF_MAX.p;\n\tif (!ok) {\n\t\tthrow new SelfstoreError(\n\t\t\t'UNSUPPORTED_VERSION',\n\t\t\t`Argon2 parameters out of the supported range (m=${m} KiB, t=${t}, p=${p}).`\n\t\t);\n\t}\n}\n\nfunction randomBytes(n: number): Uint8Array {\n\tconst b = new Uint8Array(n);\n\tcrypto.getRandomValues(b);\n\treturn b;\n}\n\n/** Coerce to the ArrayBuffer-backed view WebCrypto's types want (always safe here). */\nfunction src(u8: Uint8Array): Uint8Array<ArrayBuffer> {\n\treturn u8 as Uint8Array<ArrayBuffer>;\n}\n\n/**\n * Derive the AES-256-GCM key for `kdf`, imported non-extractable. Shared by\n * the backup cipher and the identity vault's passphrase lock.\n */\nexport async function deriveKey(password: string, kdf: KdfParams): Promise<CryptoKey> {\n\tconst raw = await deriveRaw(password, kdf);\n\treturn crypto.subtle.importKey('raw', src(raw), 'AES-GCM', false, ['encrypt', 'decrypt']);\n}\n\n/** Fresh salt at the current default cost, for a new password-sealed write. */\nexport function freshKdfParams(): KdfParams {\n\treturn { algo: 'argon2id', salt: toBase64(randomBytes(SALT_BYTES)), ...DEFAULT_ARGON2 };\n}\n\n// --- Password envelope (format 3) slots --------------------------------------\n\n/**\n * Each slot costs one Argon2id trial on open, so an unbounded list would let\n * a crafted file demand unbounded memory-hard work. Same spirit as\n * assertKdfBounds.\n */\nexport const MAX_KEY_SLOTS = 8;\n\n/** Wrap the 32-byte data key under a key derived from `password`, fresh salt and IV. */\nexport async function mintSlot(\n\tpassword: string,\n\tdataKey: Uint8Array,\n\tid?: string\n): Promise<PasswordSlot> {\n\tconst kdf = freshKdfParams();\n\tconst key = await deriveKey(password, kdf);\n\tconst iv = randomBytes(IV_BYTES);\n\tconst wrapped = await crypto.subtle.encrypt({ name: 'AES-GCM', iv: src(iv) }, key, src(dataKey));\n\treturn {\n\t\tid: id ?? toBase64(randomBytes(6)),\n\t\tkdf,\n\t\tiv: toBase64(iv),\n\t\twrapped: toBase64(new Uint8Array(wrapped))\n\t};\n}\n\n/**\n * Returns the data key if `password` opens this slot, else null. GCM keeps\n * wrong-password and tampering indistinguishable (fail closed). Bounds are\n * checked before any derivation, and that refusal is never swallowed - a\n * memory-hard bomb must not hide behind \"wrong password\".\n */\nexport async function openSlot(slot: PasswordSlot, password: string): Promise<Uint8Array | null> {\n\tassertKdfBounds(slot.kdf);\n\ttry {\n\t\tconst key = await deriveKey(password, slot.kdf);\n\t\tconst pt = await crypto.subtle.decrypt(\n\t\t\t{ name: 'AES-GCM', iv: src(fromBase64(slot.iv)) },\n\t\t\tkey,\n\t\t\tsrc(fromBase64(slot.wrapped))\n\t\t);\n\t\tconst dataKey = new Uint8Array(pt);\n\t\treturn dataKey.length === 32 ? dataKey : null;\n\t} catch (e) {\n\t\tif (e instanceof SelfstoreError) throw e;\n\t\treturn null;\n\t}\n}\n\n// --- Authenticated envelope (format 3) data seal -----------------------------\n\n/**\n * Seal the inner ZIP under the data key with the header bytes as AAD. Format\n * 5 passes the exact meta.json bytes here, which binds the slot table to the\n * ciphertext: write access to the file is not enough to strip or swap a key\n * slot without breaking the tag. The caller generates the iv (it rides in\n * meta.json, hence inside the AAD) so seal and open agree byte for byte.\n */\nexport async function gcmSealAad(\n\tkey32: Uint8Array,\n\tiv: Uint8Array,\n\tplaintext: Uint8Array,\n\taad: Uint8Array\n): Promise<Uint8Array> {\n\tconst key = await crypto.subtle.importKey('raw', src(key32), 'AES-GCM', false, ['encrypt']);\n\tconst ct = await crypto.subtle.encrypt(\n\t\t{ name: 'AES-GCM', iv: src(iv), additionalData: src(aad) },\n\t\tkey,\n\t\tsrc(plaintext)\n\t);\n\treturn new Uint8Array(ct);\n}\n\n/**\n * Open sealed data. `aad` is the additional authenticated data bound at seal\n * time (the envelope binds its header bytes); pass none for seals that bound\n * none. Any tag failure - wrong key, corrupt data, altered header - is one\n * DECRYPT_FAILED.\n */\nexport async function gcmOpenAad(\n\tkey32: Uint8Array,\n\tiv: Uint8Array,\n\tciphertext: Uint8Array,\n\taad?: Uint8Array\n): Promise<Uint8Array> {\n\ttry {\n\t\tconst key = await crypto.subtle.importKey('raw', src(key32), 'AES-GCM', false, ['decrypt']);\n\t\tconst params: AesGcmParams = { name: 'AES-GCM', iv: src(iv) };\n\t\tif (aad) params.additionalData = src(aad);\n\t\tconst pt = await crypto.subtle.decrypt(params, key, src(ciphertext));\n\t\treturn new Uint8Array(pt);\n\t} catch {\n\t\tthrow new SelfstoreError(\n\t\t\t'DECRYPT_FAILED',\n\t\t\t'Corrupted, wrong-key, or altered-header ciphertext.'\n\t\t);\n\t}\n}\n\n// --- External-key slots ------------------------------------------------------\n\n// A passkey PRF output is 32 bytes; anything under 16 is too weak to key a backup.\nconst MIN_EXTERNAL_SECRET = 16;\n\nconst EXTERNAL_INFO = new TextEncoder().encode('selfstore-external-slot-v1');\n\n/**\n * KEK for an external slot. The secret is already high-entropy (passkey PRF,\n * hardware token), so one HKDF-SHA256 expansion is enough - Argon2 only\n * exists to stretch low-entropy passwords. The fixed info label\n * domain-separates this use from any other the same secret might see.\n */\nasync function externalKek(secret: Uint8Array): Promise<CryptoKey> {\n\tconst ikm = await crypto.subtle.importKey('raw', src(secret), 'HKDF', false, ['deriveBits']);\n\tconst bits = await crypto.subtle.deriveBits(\n\t\t{ name: 'HKDF', hash: 'SHA-256', salt: new Uint8Array(0), info: src(EXTERNAL_INFO) },\n\t\tikm,\n\t\t256\n\t);\n\treturn crypto.subtle.importKey('raw', src(new Uint8Array(bits)), 'AES-GCM', false, [\n\t\t'encrypt',\n\t\t'decrypt'\n\t]);\n}\n\n/** Wrap the data key under a KEK derived from `secret`; `keyRef` is carried verbatim. */\nexport async function mintExternalSlot(\n\tsecret: Uint8Array,\n\tkeyRef: string,\n\tdataKey: Uint8Array,\n\tid?: string\n): Promise<ExternalSlot> {\n\tif (secret.length < MIN_EXTERNAL_SECRET) {\n\t\tthrow new TypeError(\n\t\t\t`mintExternalSlot(): external secret must be >= ${MIN_EXTERNAL_SECRET} bytes.`\n\t\t);\n\t}\n\tconst key = await externalKek(secret);\n\tconst iv = randomBytes(IV_BYTES);\n\tconst wrapped = await crypto.subtle.encrypt({ name: 'AES-GCM', iv: src(iv) }, key, src(dataKey));\n\treturn {\n\t\tid: id ?? toBase64(randomBytes(6)),\n\t\tkind: 'external',\n\t\tkeyRef,\n\t\tiv: toBase64(iv),\n\t\twrapped: toBase64(new Uint8Array(wrapped))\n\t};\n}\n\n/** Returns the data key if `secret` opens this slot, else null. Fail closed, like openSlot. */\nexport async function openExternalSlot(\n\tslot: ExternalSlot,\n\tsecret: Uint8Array\n): Promise<Uint8Array | null> {\n\tif (secret.length < MIN_EXTERNAL_SECRET) return null;\n\ttry {\n\t\tconst key = await externalKek(secret);\n\t\tconst pt = await crypto.subtle.decrypt(\n\t\t\t{ name: 'AES-GCM', iv: src(fromBase64(slot.iv)) },\n\t\t\tkey,\n\t\t\tsrc(fromBase64(slot.wrapped))\n\t\t);\n\t\tconst dataKey = new Uint8Array(pt);\n\t\treturn dataKey.length === 32 ? dataKey : null;\n\t} catch {\n\t\treturn null;\n\t}\n}\n"]}
@@ -1,4 +1,4 @@
1
- import { BACKUP_EXTENSION, BACKUP_MIME } from './chunk-ZDHTKFAL.js';
1
+ import { BACKUP_EXTENSION, BACKUP_MIME } from './chunk-MGDXR3RB.js';
2
2
 
3
3
  // src/selfstore/targets/local.ts
4
4
  async function pickSaveHandle(filename) {
@@ -61,5 +61,5 @@ function pickFromDisk() {
61
61
  }
62
62
 
63
63
  export { datedName, downloadBlob, pickFromDisk, pickSaveHandle, saveToDisk, writeToHandle };
64
- //# sourceMappingURL=chunk-53WWPEML.js.map
65
- //# sourceMappingURL=chunk-53WWPEML.js.map
64
+ //# sourceMappingURL=chunk-DCEXWSM6.js.map
65
+ //# sourceMappingURL=chunk-DCEXWSM6.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/selfstore/targets/local.ts"],"names":[],"mappings":"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","file":"chunk-53WWPEML.js","sourcesContent":["/**\n * Local-disk helpers: the DOM edge of selfstore, kept out of the core so the\n * core stays environment-agnostic and testable.\n */\n\nimport { BACKUP_EXTENSION, BACKUP_MIME } from '../index';\n\n/** The slice of FileSystemFileHandle this module needs. */\nexport interface SaveHandle {\n\tcreateWritable(): Promise<{ write(d: Blob): Promise<void>; close(): Promise<void> }>;\n}\n\n/**\n * Ask the user where to save, and nothing else.\n *\n * Split out because ORDER decides whether the dialog opens at all. The picker\n * needs the transient user activation of the click, and that activation expires\n * while an encrypted backup is being built (Argon2id is deliberately slow). Ask\n * first, build after, and the dialog is still allowed; build first and the\n * browser refuses it. Hosts that saw a silent download instead of the save\n * dialog were hitting exactly that.\n *\n * Null when the browser has no picker at all (Firefox, Safari) or when it\n * refuses for any reason other than a cancellation - the caller then falls back\n * to a download. Throws nothing; a cancelled dialog is reported as CANCELLED.\n */\nexport async function pickSaveHandle(\n\tfilename: string\n): Promise<{ handle: SaveHandle } | 'CANCELLED' | null> {\n\tconst picker = (\n\t\twindow as unknown as {\n\t\t\tshowSaveFilePicker?: (o: unknown) => Promise<SaveHandle>;\n\t\t}\n\t).showSaveFilePicker;\n\tif (!picker) return null;\n\ttry {\n\t\tconst handle = await picker({\n\t\t\tsuggestedName: filename,\n\t\t\ttypes: [{ description: 'selfstore backup', accept: { [BACKUP_MIME]: [BACKUP_EXTENSION] } }]\n\t\t});\n\t\treturn { handle };\n\t} catch (e) {\n\t\tif (e instanceof DOMException && e.name === 'AbortError') return 'CANCELLED';\n\t\t// Anything else (no activation left, policy): the download still works.\n\t\treturn null;\n\t}\n}\n\n/** Write a blob through a handle obtained earlier by pickSaveHandle. */\nexport async function writeToHandle(handle: SaveHandle, blob: Blob): Promise<void> {\n\tconst writable = await handle.createWritable();\n\tawait writable.write(blob);\n\tawait writable.close();\n}\n\n/** Hand the blob to the browser's download machinery. No cancel to observe:\n * once taken, the host has done all it can. */\nexport function downloadBlob(blob: Blob, filename: string): void {\n\tconst url = URL.createObjectURL(blob);\n\tconst a = document.createElement('a');\n\ta.href = url;\n\ta.download = filename;\n\tdocument.body.appendChild(a);\n\ta.click();\n\ta.remove();\n\tURL.revokeObjectURL(url);\n}\n\n/**\n * Save a backup blob to disk via the File System Access API, else a download.\n *\n * Resolves TRUE when bytes were handed to the browser, FALSE when the user\n * closed the save dialog. Callers must branch on it: a host that records \"last\n * backup: today\" after a cancelled dialog tells the user they are safe when no\n * file exists, and that lie only surfaces the day they need the file.\n *\n * Takes a ready blob, so the dialog opens only after the blob was built. When\n * that blob is an encrypted backup, prefer asking first: see pickSaveHandle.\n */\nexport async function saveToDisk(blob: Blob, filename: string): Promise<boolean> {\n\tconst picked = await pickSaveHandle(filename);\n\tif (picked === 'CANCELLED') return false;\n\tif (picked) {\n\t\tawait writeToHandle(picked.handle, blob);\n\t\treturn true;\n\t}\n\tdownloadBlob(blob, datedName(filename));\n\treturn true;\n}\n\n/**\n * The same name, stamped to the minute.\n *\n * Used only on the download path, and the reasoning is worth stating because it\n * looks backwards. Through a handle the app rewrites the SAME file, so the name\n * must stay stable. A download never replaces anything: the browser appends\n * \" (1)\", \" (2)\", and no web API can stop it. The pile forms either way - so it\n * may as well be readable. A dated series says at a glance which one is the\n * latest and lets the user step back to an earlier one; \"backup (7).zip\" says\n * neither when, nor in what order.\n */\nexport function datedName(filename: string, when = new Date()): string {\n\tconst deux = (n: number) => String(n).padStart(2, '0');\n\tconst stamp =\n\t\t`${when.getFullYear()}-${deux(when.getMonth() + 1)}-${deux(when.getDate())}` +\n\t\t`-${deux(when.getHours())}h${deux(when.getMinutes())}`;\n\tconst dot = filename.lastIndexOf('.');\n\treturn dot <= 0\n\t\t? `${filename}-${stamp}`\n\t\t: `${filename.slice(0, dot)}-${stamp}${filename.slice(dot)}`;\n}\n\n/** Prompt the user to pick a backup file from disk. Resolves null if cancelled. */\nexport function pickFromDisk(): Promise<File | null> {\n\treturn new Promise((resolve) => {\n\t\tconst input = document.createElement('input');\n\t\tinput.type = 'file';\n\t\t// octet-stream: some providers serve a downloaded ZIP under that type.\n\t\tinput.accept = '.zip,application/zip,application/octet-stream';\n\t\tinput.onchange = () => resolve(input.files?.[0] ?? null);\n\t\t// Without this, a cancelled dialog leaves the promise pending forever.\n\t\tinput.oncancel = () => resolve(null);\n\t\t// Some browsers need the input in the DOM for the dialog to open.\n\t\tinput.style.display = 'none';\n\t\tdocument.body.appendChild(input);\n\t\tinput.click();\n\t\tinput.remove();\n\t});\n}\n"]}
1
+ {"version":3,"sources":["../src/selfstore/targets/local.ts"],"names":[],"mappings":"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","file":"chunk-DCEXWSM6.js","sourcesContent":["/**\n * Local-disk helpers: the DOM edge of selfstore, kept out of the core so the\n * core stays environment-agnostic and testable.\n */\n\nimport { BACKUP_EXTENSION, BACKUP_MIME } from '../index';\n\n/** The slice of FileSystemFileHandle this module needs. */\nexport interface SaveHandle {\n\tcreateWritable(): Promise<{ write(d: Blob): Promise<void>; close(): Promise<void> }>;\n}\n\n/**\n * Ask the user where to save, and nothing else.\n *\n * Split out because ORDER decides whether the dialog opens at all. The picker\n * needs the transient user activation of the click, and that activation expires\n * while an encrypted backup is being built (Argon2id is deliberately slow). Ask\n * first, build after, and the dialog is still allowed; build first and the\n * browser refuses it. Hosts that saw a silent download instead of the save\n * dialog were hitting exactly that.\n *\n * Null when the browser has no picker at all (Firefox, Safari) or when it\n * refuses for any reason other than a cancellation - the caller then falls back\n * to a download. Throws nothing; a cancelled dialog is reported as CANCELLED.\n */\nexport async function pickSaveHandle(\n\tfilename: string\n): Promise<{ handle: SaveHandle } | 'CANCELLED' | null> {\n\tconst picker = (\n\t\twindow as unknown as {\n\t\t\tshowSaveFilePicker?: (o: unknown) => Promise<SaveHandle>;\n\t\t}\n\t).showSaveFilePicker;\n\tif (!picker) return null;\n\ttry {\n\t\tconst handle = await picker({\n\t\t\tsuggestedName: filename,\n\t\t\ttypes: [{ description: 'selfstore backup', accept: { [BACKUP_MIME]: [BACKUP_EXTENSION] } }]\n\t\t});\n\t\treturn { handle };\n\t} catch (e) {\n\t\tif (e instanceof DOMException && e.name === 'AbortError') return 'CANCELLED';\n\t\t// Anything else (no activation left, policy): the download still works.\n\t\treturn null;\n\t}\n}\n\n/** Write a blob through a handle obtained earlier by pickSaveHandle. */\nexport async function writeToHandle(handle: SaveHandle, blob: Blob): Promise<void> {\n\tconst writable = await handle.createWritable();\n\tawait writable.write(blob);\n\tawait writable.close();\n}\n\n/** Hand the blob to the browser's download machinery. No cancel to observe:\n * once taken, the host has done all it can. */\nexport function downloadBlob(blob: Blob, filename: string): void {\n\tconst url = URL.createObjectURL(blob);\n\tconst a = document.createElement('a');\n\ta.href = url;\n\ta.download = filename;\n\tdocument.body.appendChild(a);\n\ta.click();\n\ta.remove();\n\tURL.revokeObjectURL(url);\n}\n\n/**\n * Save a backup blob to disk via the File System Access API, else a download.\n *\n * Resolves TRUE when bytes were handed to the browser, FALSE when the user\n * closed the save dialog. Callers must branch on it: a host that records \"last\n * backup: today\" after a cancelled dialog tells the user they are safe when no\n * file exists, and that lie only surfaces the day they need the file.\n *\n * Takes a ready blob, so the dialog opens only after the blob was built. When\n * that blob is an encrypted backup, prefer asking first: see pickSaveHandle.\n */\nexport async function saveToDisk(blob: Blob, filename: string): Promise<boolean> {\n\tconst picked = await pickSaveHandle(filename);\n\tif (picked === 'CANCELLED') return false;\n\tif (picked) {\n\t\tawait writeToHandle(picked.handle, blob);\n\t\treturn true;\n\t}\n\tdownloadBlob(blob, datedName(filename));\n\treturn true;\n}\n\n/**\n * The same name, stamped to the minute.\n *\n * Used only on the download path, and the reasoning is worth stating because it\n * looks backwards. Through a handle the app rewrites the SAME file, so the name\n * must stay stable. A download never replaces anything: the browser appends\n * \" (1)\", \" (2)\", and no web API can stop it. The pile forms either way - so it\n * may as well be readable. A dated series says at a glance which one is the\n * latest and lets the user step back to an earlier one; \"backup (7).zip\" says\n * neither when, nor in what order.\n */\nexport function datedName(filename: string, when = new Date()): string {\n\tconst deux = (n: number) => String(n).padStart(2, '0');\n\tconst stamp =\n\t\t`${when.getFullYear()}-${deux(when.getMonth() + 1)}-${deux(when.getDate())}` +\n\t\t`-${deux(when.getHours())}h${deux(when.getMinutes())}`;\n\tconst dot = filename.lastIndexOf('.');\n\treturn dot <= 0\n\t\t? `${filename}-${stamp}`\n\t\t: `${filename.slice(0, dot)}-${stamp}${filename.slice(dot)}`;\n}\n\n/** Prompt the user to pick a backup file from disk. Resolves null if cancelled. */\nexport function pickFromDisk(): Promise<File | null> {\n\treturn new Promise((resolve) => {\n\t\tconst input = document.createElement('input');\n\t\tinput.type = 'file';\n\t\t// octet-stream: some providers serve a downloaded ZIP under that type.\n\t\tinput.accept = '.zip,application/zip,application/octet-stream';\n\t\tinput.onchange = () => resolve(input.files?.[0] ?? null);\n\t\t// Without this, a cancelled dialog leaves the promise pending forever.\n\t\tinput.oncancel = () => resolve(null);\n\t\t// Some browsers need the input in the DOM for the dialog to open.\n\t\tinput.style.display = 'none';\n\t\tdocument.body.appendChild(input);\n\t\tinput.click();\n\t\tinput.remove();\n\t});\n}\n"]}
@@ -1,9 +1,8 @@
1
1
  import { createNode, createMeta, gcTombstones, stamp, merge, unsyncableCounts, idPathFor, detectConflicts, changes } from './chunk-SKWHKM25.js';
2
- import { inspect, checkPasswordPolicy, writeBox, BACKUP_MIME, readBoxWithSync, asBytes } from './chunk-ZDHTKFAL.js';
3
- import { openManifest, GROUP_KEYING } from './chunk-CT2HTWA7.js';
2
+ import { inspect, checkPasswordPolicy, writeBox, BACKUP_MIME, readBoxWithSync, asBytes } from './chunk-MGDXR3RB.js';
4
3
  import { seal, isEnvelope, unseal, newDeviceKey } from './chunk-QBCU7OOP.js';
5
- import { mintSlot, MAX_KEY_SLOTS, mintExternalSlot, openSlot } from './chunk-CM3HSBCE.js';
6
- import { SelfstoreError, isAuthExpired, errorLabelKey, isSelfstoreError } from './chunk-SFF7KKM4.js';
4
+ import { openManifest, GROUP_KEYING } from './chunk-O3Y6FORN.js';
5
+ import { SelfstoreError, mintSlot, MAX_KEY_SLOTS, mintExternalSlot, isAuthExpired, openSlot, errorLabelKey, isSelfstoreError } from './chunk-3OBJOMQ2.js';
7
6
  import { openDB } from 'idb';
8
7
 
9
8
  // src/persistence/target.ts
@@ -172,6 +171,15 @@ function createLocalStore(opts) {
172
171
  let queuedKind = null;
173
172
  let prefetched = null;
174
173
  const PREFETCH_TTL_MS = 6e4;
174
+ async function remoteAppOf(target) {
175
+ try {
176
+ const blob = prefetched && prefetched.target === target ? prefetched.blob : await target.load();
177
+ if (!blob || blob.size === 0) return null;
178
+ return (await inspect(blob)).app ?? null;
179
+ } catch {
180
+ return null;
181
+ }
182
+ }
175
183
  const peerList = [];
176
184
  const mirrorList = [];
177
185
  const replicaList = [];
@@ -973,6 +981,7 @@ function createLocalStore(opts) {
973
981
  }
974
982
  stateSnapshot = buildState();
975
983
  return {
984
+ app,
976
985
  get state() {
977
986
  return stateSnapshot;
978
987
  },
@@ -1129,6 +1138,13 @@ function createLocalStore(opts) {
1129
1138
  }
1130
1139
  if (attachOpts?.password) assertPasswordPolicy(attachOpts.password);
1131
1140
  return serialize(async () => {
1141
+ const remoteApp = await remoteAppOf(target);
1142
+ if (remoteApp !== null && remoteApp !== app) {
1143
+ throw new SelfstoreError(
1144
+ "FOREIGN_BACKUP",
1145
+ `attachTarget(): the destination holds a backup written by app "${remoteApp}", not "${app}". Open it in its own application, or pick another file.`
1146
+ );
1147
+ }
1132
1148
  const g = cfg ? {
1133
1149
  identity: cfg.identity,
1134
1150
  admin: cfg.admin,
@@ -1186,7 +1202,8 @@ function createLocalStore(opts) {
1186
1202
  },
1187
1203
  async inspectTarget(target) {
1188
1204
  const blob = await target.load();
1189
- if (!blob || blob.size === 0) return { hasBackup: false, date: null, encrypted: false };
1205
+ if (!blob || blob.size === 0)
1206
+ return { hasBackup: false, date: null, encrypted: false, app: null };
1190
1207
  let marker;
1191
1208
  try {
1192
1209
  marker = target.stat ? await target.stat() : null;
@@ -1198,7 +1215,10 @@ function createLocalStore(opts) {
1198
1215
  return {
1199
1216
  hasBackup: true,
1200
1217
  date: header.createdAt ?? null,
1201
- encrypted: header.encryption !== "none"
1218
+ encrypted: header.encryption !== "none",
1219
+ // Required by the format since generation 1, but a hand-made file may
1220
+ // omit it: absent reads as "cannot tell", never as an accusation.
1221
+ app: header.app ?? null
1202
1222
  };
1203
1223
  },
1204
1224
  setManualFile() {
@@ -1909,7 +1929,7 @@ function indexedDbCache(name, opts) {
1909
1929
  sealKey = null;
1910
1930
  },
1911
1931
  async reseal(secret) {
1912
- const { keyFromPassword, freshCacheKdf } = await import('./cache-lock-7D6N42ZP.js');
1932
+ const { keyFromPassword, freshCacheKdf } = await import('./cache-lock-KWJUORTS.js');
1913
1933
  const previous = sealKey;
1914
1934
  if (previous === null) {
1915
1935
  throw new SelfstoreError(
@@ -1943,7 +1963,7 @@ function indexedDbCache(name, opts) {
1943
1963
  sealKey = next;
1944
1964
  },
1945
1965
  async unlock(secret) {
1946
- const { keyFromPassword, freshCacheKdf, opens } = await import('./cache-lock-7D6N42ZP.js');
1966
+ const { keyFromPassword, freshCacheKdf, opens } = await import('./cache-lock-KWJUORTS.js');
1947
1967
  const d = await db();
1948
1968
  let candidate;
1949
1969
  if (typeof secret === "string") {
@@ -2050,5 +2070,5 @@ function gisDriveAuth(opts) {
2050
2070
  }
2051
2071
 
2052
2072
  export { RESERVED_STORE_MODES, createLocalStore, deriveStatus, gisDriveAuth, indexedDbCache, isLockableCache, isReservedStoreMode, memoryCache };
2053
- //# sourceMappingURL=chunk-ZV2S3I76.js.map
2054
- //# sourceMappingURL=chunk-ZV2S3I76.js.map
2073
+ //# sourceMappingURL=chunk-ELDSPUTS.js.map
2074
+ //# sourceMappingURL=chunk-ELDSPUTS.js.map