selfstore 1.8.19 → 1.8.21

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 (89) hide show
  1. package/README.md +107 -12
  2. package/dist/advanced.d.ts +23 -8
  3. package/dist/advanced.js +23 -12
  4. package/dist/advanced.js.map +1 -1
  5. package/dist/backups.d.ts +4 -4
  6. package/dist/backups.js +8 -6
  7. package/dist/{cache-lock-KWJUORTS.js → cache-lock-5RNG6CIK.js} +4 -3
  8. package/dist/cache-lock-5RNG6CIK.js.map +1 -0
  9. package/dist/{chunk-SKWHKM25.js → chunk-23UAUYSW.js} +40 -4
  10. package/dist/chunk-23UAUYSW.js.map +1 -0
  11. package/dist/chunk-3BWBBGFW.js +29 -0
  12. package/dist/chunk-3BWBBGFW.js.map +1 -0
  13. package/dist/{chunk-W22V5CRM.js → chunk-3KAAPYPO.js} +3 -3
  14. package/dist/{chunk-W22V5CRM.js.map → chunk-3KAAPYPO.js.map} +1 -1
  15. package/dist/chunk-757ZOSUI.js +27 -0
  16. package/dist/chunk-757ZOSUI.js.map +1 -0
  17. package/dist/{chunk-DCEXWSM6.js → chunk-AUH35AAQ.js} +3 -3
  18. package/dist/{chunk-DCEXWSM6.js.map → chunk-AUH35AAQ.js.map} +1 -1
  19. package/dist/{chunk-TQH7ZYID.js → chunk-DJZ5S3FI.js} +31 -14
  20. package/dist/chunk-DJZ5S3FI.js.map +1 -0
  21. package/dist/{chunk-HOG2IKEP.js → chunk-EBCQ7ZG7.js} +4 -4
  22. package/dist/{chunk-HOG2IKEP.js.map → chunk-EBCQ7ZG7.js.map} +1 -1
  23. package/dist/chunk-FJGWTBAL.js +6 -0
  24. package/dist/chunk-FJGWTBAL.js.map +1 -0
  25. package/dist/chunk-GW5CYP4C.js +212 -0
  26. package/dist/chunk-GW5CYP4C.js.map +1 -0
  27. package/dist/{chunk-MGDXR3RB.js → chunk-HHPJPEEG.js} +6 -5
  28. package/dist/chunk-HHPJPEEG.js.map +1 -0
  29. package/dist/{chunk-J5MFXJU6.js → chunk-JTNFZV2B.js} +78 -26
  30. package/dist/chunk-JTNFZV2B.js.map +1 -0
  31. package/dist/chunk-JZHWAX34.js +178 -0
  32. package/dist/chunk-JZHWAX34.js.map +1 -0
  33. package/dist/chunk-LUNYYH6D.js +223 -0
  34. package/dist/chunk-LUNYYH6D.js.map +1 -0
  35. package/dist/{chunk-LKIRWVXM.js → chunk-LYO36VYB.js} +12 -11
  36. package/dist/chunk-LYO36VYB.js.map +1 -0
  37. package/dist/{chunk-O3Y6FORN.js → chunk-OY2A5DCB.js} +4 -3
  38. package/dist/chunk-OY2A5DCB.js.map +1 -0
  39. package/dist/chunk-QSLHEE2L.js +13 -0
  40. package/dist/chunk-QSLHEE2L.js.map +1 -0
  41. package/dist/{chunk-3OBJOMQ2.js → chunk-RREJ2MFN.js} +4 -28
  42. package/dist/chunk-RREJ2MFN.js.map +1 -0
  43. package/dist/chunk-XMC67RNM.js +29 -0
  44. package/dist/chunk-XMC67RNM.js.map +1 -0
  45. package/dist/{connect-BTVzRJY8.d.ts → connect-Bob2mofK.d.ts} +3 -3
  46. package/dist/{drive-CY_Vsj7V.d.ts → drive-86CHuTij.d.ts} +63 -2
  47. package/dist/flows.d.ts +5 -5
  48. package/dist/flows.js +17 -15
  49. package/dist/flows.js.map +1 -1
  50. package/dist/groups.js +4 -3
  51. package/dist/groups.js.map +1 -1
  52. package/dist/households.d.ts +1 -1
  53. package/dist/index.d.ts +66 -10
  54. package/dist/index.js +73 -18
  55. package/dist/index.js.map +1 -1
  56. package/dist/kdf-worker.js +7 -2
  57. package/dist/kdf-worker.js.map +1 -1
  58. package/dist/{local-Dh6eGiK9.d.ts → local-8H61XxbL.d.ts} +2 -2
  59. package/dist/local-XET4G32B.js +7 -0
  60. package/dist/{local-DPS35F5C.js.map → local-XET4G32B.js.map} +1 -1
  61. package/dist/{manager-DhoIbJyg.d.ts → manager-Di6lNE4E.d.ts} +1 -1
  62. package/dist/manager-drive-YLSLQ63O.js +10 -0
  63. package/dist/{manager-drive-4WLKOOXO.js.map → manager-drive-YLSLQ63O.js.map} +1 -1
  64. package/dist/s3-43RWNCV3.js +11 -0
  65. package/dist/s3-43RWNCV3.js.map +1 -0
  66. package/dist/{s3-0qqIyphl.d.ts → s3-BsbVn3fw.d.ts} +1 -1
  67. package/dist/{store-Cs6QIzzG.d.ts → store-CIbiNoiE.d.ts} +11 -3
  68. package/dist/sync.d.ts +7 -6
  69. package/dist/sync.js +3 -1
  70. package/dist/webdav-EEQKX5TZ.js +9 -0
  71. package/dist/webdav-EEQKX5TZ.js.map +1 -0
  72. package/dist/widgets.d.ts +51 -10
  73. package/dist/widgets.js +134 -34
  74. package/dist/widgets.js.map +1 -1
  75. package/llms.txt +51 -4
  76. package/package.json +12 -5
  77. package/skills/selfstore/SKILL.md +115 -0
  78. package/dist/cache-lock-KWJUORTS.js.map +0 -1
  79. package/dist/chunk-3OBJOMQ2.js.map +0 -1
  80. package/dist/chunk-J5MFXJU6.js.map +0 -1
  81. package/dist/chunk-LKIRWVXM.js.map +0 -1
  82. package/dist/chunk-MGDXR3RB.js.map +0 -1
  83. package/dist/chunk-O3Y6FORN.js.map +0 -1
  84. package/dist/chunk-SKWHKM25.js.map +0 -1
  85. package/dist/chunk-TKWU52LK.js +0 -607
  86. package/dist/chunk-TKWU52LK.js.map +0 -1
  87. package/dist/chunk-TQH7ZYID.js.map +0 -1
  88. package/dist/local-DPS35F5C.js +0 -6
  89. package/dist/manager-drive-4WLKOOXO.js +0 -8
package/llms.txt CHANGED
@@ -41,12 +41,17 @@ collaborative editing (use a CRDT library), or Node-side storage.
41
41
  defineSelfstoreWidgets() registers <selfstore-connect>, <selfstore-gate>,
42
42
  <selfstore-share>, <selfstore-join>, <selfstore-status>,
43
43
  <selfstore-backups>, <selfstore-destination>, <selfstore-storage>.
44
- START WITH <selfstore-storage>: assign .store and the whole journey is wired -
44
+ START WITH <selfstore-storage> when the app's data means nothing until the
45
+ user says where it lives: assign .store and the whole journey is wired -
45
46
  it shows the gate while there is no home and the destination panel once there
46
47
  is, taking destinations (store.destinations()), the resume card
47
48
  (store.resumeOffer()) and the account (store.account) from the store itself;
48
49
  targets / manager / confirmAction / deferrable / recommended pass through to
49
- the composed widgets. Framework-free
50
+ the composed widgets. Before a home is chosen it IS the gate, and the gate is
51
+ a FULL-VIEWPORT MODAL. So do NOT reach for it on a page that must work before
52
+ any answer (a demo, a scratchpad, a landing page): there, mount
53
+ <selfstore-destination> (the same panel, no modal) or <selfstore-status>, and
54
+ put the connect journey behind a control the user reaches deliberately. Framework-free
50
55
  custom elements over the flows; theme via
51
56
  --selfstore-* CSS custom properties and ::part() selectors; reword/localize
52
57
  via the `labels` property (partial map over EN defaults); listen to
@@ -100,7 +105,9 @@ collaborative editing (use a CRDT library), or Node-side storage.
100
105
  attachPeer(source, {password}) reads another member's copy with ITS OWN
101
106
  key (state.peers) - crossed mirrors + keyed peers converge two PLAINTEXT
102
107
  copies, each member editing only their own file.
103
- - 'selfstore/groups' - passwordless group crypto: identityVault, generateIdentity,
108
+ - 'selfstore/groups' (EXPERIMENTAL - outside semver, may change or be
109
+ withdrawn in a MINOR; the FILE format is not, see SPEC section 12) -
110
+ passwordless group crypto: identityVault, generateIdentity,
104
111
  signManifest, openManifest, GROUP_KEYING, groupCryptoAvailable.
105
112
  - 'selfstore/households' - read-write sharing that never touches the
106
113
  backup file (the MIRROR model): each member keeps their own file and
@@ -133,6 +140,24 @@ collaborative editing (use a CRDT library), or Node-side storage.
133
140
  await store.clear('todos');
134
141
  store.onChange(render); // local writes AND external merges; returns unsubscribe
135
142
 
143
+ // Bytes, in the same store / backup / merge. The id DEFAULTS to the
144
+ // SHA-256 of the bytes - keep that default.
145
+ const fid = await store.putFile({ bytes, name: 'scan.pdf', mime: 'application/pdf' });
146
+ store.getFile(fid); // { id, name, mime, bytes } or undefined
147
+ store.allFiles(); // readonly array
148
+ await store.removeFile(fid);
149
+
150
+ Files are merged by a UNION ON ID with no clock, so two devices holding
151
+ different bytes under one id lose one of them silently - hence the content-hash
152
+ default, which puts different bytes on different ids. putFile REFUSES different
153
+ bytes under an id you named (identical bytes: no-op); `{ replace: true }` is the
154
+ explicit opt-out, correct only for a body written on one device. File DELETIONS
155
+ do not propagate: tie a file's lifetime to a record.
156
+
157
+ This is also how a CRDT rides along: store Yjs/Automerge UPDATES under content
158
+ ids and the union IS the merge (updates are commutative and idempotent); fold
159
+ them with applyUpdate at load. See examples/yjs-document.ts.
160
+
136
161
  The store OWNS the data (collections of plain JSON records). Auto-wired in
137
162
  browsers: IndexedDB cache named after the app, debounced auto-save, save on
138
163
  tab hide, converge on tab focus / network return / a slow interval. In
@@ -159,6 +184,8 @@ downloadBackup()):
159
184
  // backup" card for connectFlow's options.resume) with no call going out.
160
185
  await store.connectFile({ password? }); // Chromium FSA
161
186
  await store.connectWebdav({ url, username, password }, { password? });
187
+ await store.connectS3({ endpoint, region, bucket, key, accessKeyId,
188
+ secretAccessKey, forcePathStyle? }, { password? }); // S3, R2, MinIO
162
189
  await store.connectTarget(anyBackupTarget, { password? }); // your own
163
190
  await store.disconnect(); // back to device-only (remote keeps its last backup)
164
191
  store.addReplica(target); // also write the same encrypted backup there
@@ -320,12 +347,32 @@ the store's restoreTarget option):
320
347
  listBackups({ auth, nameContains? }) / createBackup({ auth, fileName }) /
321
348
  renameBackup({ auth, fileId, fileName }) / deleteBackup({ auth, fileId }) -
322
349
  create and rename refuse a name another owned file carries.
350
+ SHARING over Drive, on companion files created NEXT TO the backup:
351
+ createCompanion({ auth, fileName }) -> { fileId } (no duplicate-name refusal,
352
+ two members may hold identically named copies) / share({ auth, fileId })
353
+ (anyone-with-the-link READER; publish ciphertext only) /
354
+ unshare({ auth, fileId }) (drops every link grant, keeps named ones; call it
355
+ BEFORE any plaintext rewrite) / owner({ auth, fileId }) -> { email, name }
356
+ (whose Drive a copy lives on; nulls rather than throwing when Drive will not
357
+ say) / secondary({ auth, kv }, fileId) -> BackupTarget, read-write
358
+ over that file with kind 'drive-companion' and a disconnect() that forgets
359
+ NEITHER the Drive session NOR the backup id - use it for attachMirror, never
360
+ preview() (whose disconnect ends the primary connection). Teardown = unshare
361
+ then deleteBackup, in that order. NOT provided, because it needs a server or a
362
+ gesture: reading ANOTHER account's link-shared file (drive.file only sees what
363
+ this app created or the user picked) - supply your own load() to attachPeer.
323
364
  Custom targets: implement BackupTarget with any `kind` string except the reserved
324
365
  store modes ('device', 'file-manual'). Error protocol: throw AuthExpiredError for
325
366
  a GENUINE access loss (raises the reconnect gate); any other throw is transient
326
367
  and auto-retried. The simple store connects any of these via connectTarget().
327
368
 
328
- ## Passwordless groups, 'selfstore/groups'
369
+ ## Passwordless groups, 'selfstore/groups' (EXPERIMENTAL)
370
+
371
+ Outside the semver promise: these exports may change shape or be withdrawn in
372
+ a MINOR release, because no application has shipped them yet. Warn the user
373
+ before recommending this subpath. The FILE it writes is NOT experimental -
374
+ group mode is format generation 2, specified with a canonical vector, and stays
375
+ readable by every later version.
329
376
 
330
377
  No shared secret: identity = Ed25519 + X25519 keypairs per member.
331
378
  `const identity = await identityVault(kv).loadOrCreate()` (sealed at rest
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "selfstore",
3
- "version": "1.8.19",
3
+ "version": "1.8.21",
4
4
  "description": "Local-first storage for browser apps: an IndexedDB working copy, portable encrypted ZIP backups (AES-256-GCM over Argon2id), durable homes (disk, Google Drive, WebDAV) and a serverless multi-device merge.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -80,7 +80,8 @@
80
80
  },
81
81
  "files": [
82
82
  "dist",
83
- "llms.txt"
83
+ "llms.txt",
84
+ "skills"
84
85
  ],
85
86
  "scripts": {
86
87
  "build": "tsup",
@@ -90,11 +91,13 @@
90
91
  "lint": "eslint . --max-warnings 0",
91
92
  "format": "prettier --write .",
92
93
  "format:check": "prettier --check .",
94
+ "skill": "node scripts/skill.mjs",
93
95
  "knip": "knip",
94
96
  "dup": "jscpd src --format typescript,javascript --ignore \"**/*.test.ts\" --threshold 3",
95
- "gate": "npm run format:check && npm run lint && npm run typecheck && npm run knip && npm run dup && npm run test:coverage",
97
+ "gate": "npm run format:check && npm run lint && npm run typecheck && npm run skill && npm run knip && npm run dup && npm run test:coverage",
96
98
  "prepare": "simple-git-hooks",
97
- "prepublishOnly": "npm run typecheck && npm test && npm run build"
99
+ "prepublishOnly": "npm run typecheck && npm test && npm run build",
100
+ "consumers": "node scripts/verify-consumers.mjs"
98
101
  },
99
102
  "simple-git-hooks": {
100
103
  "pre-commit": "npx lint-staged"
@@ -113,6 +116,9 @@
113
116
  "hash-wasm": "4.12.0",
114
117
  "idb": "^8.0.3"
115
118
  },
119
+ "overrides": {
120
+ "esbuild": "^0.28.1"
121
+ },
116
122
  "devDependencies": {
117
123
  "@eslint/js": "^10.0.1",
118
124
  "@types/node": "^26.1.1",
@@ -131,6 +137,7 @@
131
137
  "tsup": "^8.3.5",
132
138
  "typescript": "^6.0.3",
133
139
  "typescript-eslint": "^8.65.0",
134
- "vitest": "^4.1.9"
140
+ "vitest": "^4.1.9",
141
+ "yjs": "^13.6.31"
135
142
  }
136
143
  }
@@ -0,0 +1,115 @@
1
+ ---
2
+ name: selfstore
3
+ description: Give a browser app saving, backup, restore and multi-device sync with no server and no account, using the selfstore library - an IndexedDB working copy plus a portable encrypted ZIP the user owns, on a disk file, Google Drive, WebDAV or S3. Use when the task is local-first or offline storage in a web app, persisting data without a backend, letting users own or export their data as a file, syncing one person's devices, sharing over read-only links, or writing and debugging selfstore code.
4
+ ---
5
+
6
+ # Storing a browser app's data with selfstore
7
+
8
+ One npm package, nine imports. The store OWNS the data: named JSON collections
9
+ plus binary files, never interpreted. ESM, TypeScript, browser-first, MIT.
10
+
11
+ ```sh
12
+ npm install selfstore
13
+ ```
14
+
15
+ ## Decide first whether this is the right tool
16
+
17
+ Reach for it when a web app wants save, backup, restore or sync WITHOUT a
18
+ server or an account, when users must own their data as a portable file, for
19
+ multi-device convergence for one person, or for async sharing between a few
20
+ people over read-only links.
21
+
22
+ Do NOT reach for it for multi-GB data (everything is in memory), realtime
23
+ collaborative editing (use a CRDT library - though CRDT updates can ride along,
24
+ see below), or server-side storage. Say so rather than bending it.
25
+
26
+ ## The default path, and it is short
27
+
28
+ ```ts
29
+ import { selfstore } from 'selfstore';
30
+
31
+ const store = await selfstore<{ todos: Todo }>('todo-app'); // ready when resolved
32
+
33
+ await store.put('todos', { id: crypto.randomUUID(), text: 'ship it' });
34
+ store.all('todos'); // readonly array
35
+ store.onChange(render); // local writes AND merges from other devices
36
+ ```
37
+
38
+ That is the whole first step. IndexedDB cache, debounced auto-save, save on tab
39
+ hide and converge on focus are wired already. In a test or under SSR, with no
40
+ IndexedDB, it falls back to an in-memory cache, so the same code runs under
41
+ vitest with zero mocks.
42
+
43
+ Reach for `createLocalStore` from `selfstore/advanced` only when the app must
44
+ keep owning its own state and hand snapshots over. Writing it first is the most
45
+ common wrong turn.
46
+
47
+ ## The five things a first integration gets wrong
48
+
49
+ **1. A record needs a STRING `id`.** The merge keys on it. `put()` throws a
50
+ TypeError naming the fix; the advanced store only warns, and the record then
51
+ silently never replicates. Remap the field per collection with
52
+ `selfstore('app', { sync: { ids: { events: 'ref' } } })`.
53
+
54
+ **2. `<selfstore-storage>` is a full-viewport modal until a home is chosen.**
55
+ Correct when the app's data means nothing until the user says where it lives.
56
+ Wrong on a page that must work before any answer - a demo, a landing page, a
57
+ scratchpad. There, mount `<selfstore-destination>` or `<selfstore-status>` and
58
+ put the connect journey behind a control the user reaches deliberately.
59
+
60
+ **3. Reach for the widget before writing the screen.** `selfstore/widgets`
61
+ ships the whole journey as framework-free custom elements, themed with
62
+ `--selfstore-*` properties and `::part()`, reworded through the `labels`
63
+ property. If one is close but not right, fix the widget rather than rebuild it
64
+ by hand - a hand-rolled connect screen misses the cases the flow already
65
+ handles.
66
+
67
+ **4. Branch on `err.code`, never on a message.** Every failure is a
68
+ `SelfstoreError` with a stable code and an i18n `labelKey`. Show the key, log
69
+ the message, keep a default branch: new codes arrive in minors. The one to
70
+ handle by name is `PASSWORD_REQUIRED`, thrown BEFORE anything attaches when a
71
+ destination holds an encrypted backup - prompt, then retry with `{ password }`.
72
+
73
+ **5. Handle all four connect outcomes.** `connectDrive` / `connectFile` /
74
+ `connectWebdav` / `connectS3` resolve to `'merged'` (both sides folded, nothing
75
+ lost), `'started'` (the destination was empty), `'cancelled'` (the user closed
76
+ the picker) or `'manual'` (no File System Access in this browser - offer
77
+ `downloadBackup()`). Treating the last one as a failure breaks every non-Chromium
78
+ visitor.
79
+
80
+ ## Growing, one call at a time
81
+
82
+ ```ts
83
+ await store.connectDrive(gisDriveAuth({ clientId })); // or connectFile / connectWebdav / connectS3
84
+ await store.protect(passphrase); // end-to-end encryption from here on
85
+ await store.downloadBackup(); // hand the user a real .zip
86
+ await store.importBackup(file); // REPLACES local data
87
+ ```
88
+
89
+ Binary files live in the same store, backup and merge. `putFile` defaults the
90
+ id to the SHA-256 of the bytes: keep that default. Files merge by union on id
91
+ with no clock, so two devices holding different bytes under one id lose one
92
+ silently, and the content hash is what puts different bytes on different ids.
93
+ File deletions do not propagate - tie a file's lifetime to a record.
94
+
95
+ `selfstore/groups` is EXPERIMENTAL and sits outside the semver promise. Warn
96
+ before recommending it.
97
+
98
+ ## Where the rest is
99
+
100
+ The full API reference ships inside the package, so it is on disk at the exact
101
+ version installed:
102
+
103
+ ```sh
104
+ cat node_modules/selfstore/llms.txt
105
+ ```
106
+
107
+ Online, every documentation page is served as markdown at the same path with
108
+ `.md`:
109
+
110
+ - <https://selfstore.dev/llms.txt> - the map, one line per page
111
+ - <https://selfstore.dev/docs/quick-start.md> - this page, in full
112
+ - <https://selfstore.dev/docs/widgets.md> - the drop-in components
113
+ - <https://selfstore.dev/docs/errors.md> - every code and its label key
114
+ - <https://selfstore.dev/docs/sync.md> - what converges, and what cannot
115
+ - <https://selfstore.dev/llms-full.txt> - all of it in one file
@@ -1 +0,0 @@
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 +0,0 @@
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 +0,0 @@
1
- {"version":3,"sources":["../src/persistence/targets/abort.ts","../src/persistence/targets/drive.ts"],"names":["token"],"mappings":"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","file":"chunk-J5MFXJU6.js","sourcesContent":["/**\n * Deadline + life-signal composition for target requests. Every target fetch\n * carries a per-request deadline; composing it with the target's LIFE signal\n * lets abortInFlight() (the user detaching the destination) cut a suspended\n * request NOW instead of letting the detach wait behind the deadline. The\n * composition is done by hand (not AbortSignal.any) so behaviour is identical\n * on every engine the library supports.\n */\n\nimport { SelfstoreError } from '../../selfstore';\n\nexport function boundedSignal(deadlineMs: number, life?: AbortSignal): AbortSignal {\n\tconst deadline = AbortSignal.timeout(deadlineMs);\n\tif (!life) return deadline;\n\tif (life.aborted) return life;\n\tconst ctl = new AbortController();\n\tdeadline.addEventListener('abort', () => ctl.abort(deadline.reason), { once: true });\n\tlife.addEventListener('abort', () => ctl.abort(life.reason), { once: true });\n\treturn ctl.signal;\n}\n\n/** A target's resettable LIFE line. cut() aborts everything bound to the\n * CURRENT signal (with the standard transient detach reason) and re-arms,\n * so work started after the cut lives normally. cut is `this`-free: targets\n * expose it directly as their abortInFlight. */\nexport function lifeLine(): { current(): AbortSignal; cut(): void } {\n\tlet ctl = new AbortController();\n\treturn {\n\t\tcurrent: () => ctl.signal,\n\t\tcut(): void {\n\t\t\tconst c = ctl;\n\t\t\tctl = new AbortController();\n\t\t\tc.abort(new SelfstoreError('TARGET_UNAVAILABLE', 'Request aborted: target detaching.'));\n\t\t}\n\t};\n}\n","/**\n * Google Drive backup target. Does the Drive REST file operations (find-or-create,\n * upload, download) for a single backup file. It only ships bytes; the store\n * decides whether they are encrypted. How an access token is obtained (a browser\n * OAuth flow, a backend broker, ...) is injected via DriveAuth, so this target\n * stays free of any specific auth strategy or backend.\n *\n * Beyond the standard connect/fromSession lifecycle, this module exposes\n * shared-file primitives (preview, adopt, findOrCreateOwnFile) for apps whose\n * users point several devices - or several people - at one Drive-hosted backup\n * file: preview it read-only, then adopt it as this device's backup.\n */\n\nimport type { BackupTarget } from '../target';\nimport type { KV } from '../cache';\nimport { boundedSignal, lifeLine } from './abort';\nimport { AuthExpiredError, SelfstoreError, isAuthExpired } from '../../selfstore';\n\n/** KV key holding the connected backup's Drive file id. Public because app-level\n * flows that operate on the same file (e.g. sharing it) need to read it. */\nexport const FILE_ID_KEY = 'driveFileId';\nconst DRIVE_UPLOAD = 'https://www.googleapis.com/upload/drive/v3/files';\nconst DRIVE_FILES = 'https://www.googleapis.com/drive/v3/files';\nconst DRIVE_ABOUT = 'https://www.googleapis.com/drive/v3/about';\n\n/** A hung request must not spin forever behind a dead connection - the very\n * \"long moment trying to connect\" a stuck backup shows. A deadline turns the\n * hang into an ordinary error (transient: the store retries), never a gate.\n * Data transfers get more room than metadata pokes. AbortSignal.timeout is\n * minted per fetch (a fired signal cannot be reused across driveFetch's retry). */\nconst DATA_DEADLINE_MS = 30_000;\nconst META_DEADLINE_MS = 15_000;\n\n/** How the target gets access. Injected, so the auth strategy lives in the app.\n * Error protocol: a genuine loss of access (the session is gone, re-consent\n * needed) must throw AuthExpiredError - or any error with code 'AUTH_EXPIRED' -\n * while a transient offline/cold-start failure throws a plain error, so the\n * store retries it instead of raising the reconnect gate. */\nexport interface DriveAuth {\n\t/** A fresh access token, or throw if access must be re-established. Pass\n\t * `{ forceRefresh: true }` to bypass any cached token and mint a new one:\n\t * the target calls this after a 401 to tell a merely STALE token (a refresh\n\t * fixes it, no gate) from a genuinely lost session (the refresh 401s too).\n\t * An implementation that cannot force a refresh may ignore the flag; the\n\t * target then retries with the same token, no worse than before.\n\t * `signal` fires when the target's in-flight work is cut (the user is\n\t * detaching): an implementation that runs its own retry loop should abort\n\t * it then (pass the signal to its fetches, stop retrying); ignoring the\n\t * signal only means the loop runs to its own deadline first. */\n\ttoken(opts?: { forceRefresh?: boolean; signal?: AbortSignal }): Promise<string>;\n\t/** Re-establish access with a user gesture; resolves to whether it worked. */\n\treconnect(): Promise<boolean>;\n\t/** Forget the connection, locally and server-side. */\n\tforget(): Promise<void>;\n}\n\n/** Run a Drive request; if Google answers 401 the access token may just be\n * STALE (rejected before its nominal expiry - rotation, an early revoke), so\n * force a fresh token and try once more. Only a SECOND 401 means the session\n * is genuinely gone. This is what stops a stale-token blip - common when a\n * flow makes many Drive calls, e.g. group-mode republishing every converge -\n * from raising the blocking reconnect gate over a still-valid connection. */\nasync function driveFetch(\n\tauth: DriveAuth,\n\trun: (token: string) => Promise<Response>,\n\tsignal?: AbortSignal\n): Promise<Response> {\n\tconst r = await run(await auth.token({ signal }));\n\tif (r.status !== 401) return r;\n\treturn run(await auth.token({ forceRefresh: true, signal }));\n}\n\nexport interface DriveOptions {\n\tauth: DriveAuth;\n\tkv: KV;\n\t/** Name of the backup file created in the user's Drive. */\n\tfileName: string;\n}\n\nasync function findExistingFile(token: string, fileNames: string[]): Promise<string | null> {\n\tfor (const name of fileNames) {\n\t\t// 'me' in owners: a shared backup carries the same name but lives on someone\n\t\t// else's Drive - only an OWNED file may be (re)connected as the personal\n\t\t// backup, or a fresh connect could silently re-adopt theirs.\n\t\t// Escape the name for Drive's query grammar (backslash then single-quote):\n\t\t// a name carrying a quote must not break out of the `name='...'` clause and\n\t\t// alter the filter (encodeURIComponent handles transport, not query syntax).\n\t\tconst safe = name.replace(/\\\\/g, '\\\\\\\\').replace(/'/g, \"\\\\'\");\n\t\tconst q = encodeURIComponent(`name='${safe}' and trashed=false and 'me' in owners`);\n\t\tconst r = await fetch(`${DRIVE_FILES}?q=${q}&fields=files(id)&pageSize=1`, {\n\t\t\theaders: { Authorization: `Bearer ${token}` },\n\t\t\tsignal: AbortSignal.timeout(META_DEADLINE_MS)\n\t\t});\n\t\tif (!r.ok) continue;\n\t\tconst data: { files?: { id: string }[] } = await r.json();\n\t\tconst id = data.files?.[0]?.id;\n\t\tif (id) return id;\n\t}\n\treturn null;\n}\n\n/** Classify a non-OK Drive write we could not otherwise handle (the 401 case is\n * the caller's). A 404 - the bound file is gone (deleted, trashed, id no longer\n * valid) - or a 403 that is not a rate limit (permission withdrawn, storage\n * full) is PERMANENT: retrying writes the same doomed request forever (the\n * silent \"momentarily unreachable\" trap), so the store must raise its gate and\n * let the user reconnect to a fresh file or free space. A 403 rate limit and\n * everything else (5xx, network) stay transient. The body carries Drive's\n * `reason` (e.g. userRateLimitExceeded vs storageQuotaExceeded); absent, a bare\n * 403 is treated as permanent (the safer default - a real rate limit names\n * itself). */\nfunction isPermanentDriveWrite(status: number, body: string): boolean {\n\tif (status === 404) return true;\n\tif (status === 403) return !/ratelimit|dailylimit/i.test(body);\n\treturn false;\n}\n\nasync function createFile(token: string, fileName: string): Promise<string> {\n\tconst boundary = 'SLFS_BOUNDARY';\n\tconst meta = JSON.stringify({ name: fileName, mimeType: 'application/octet-stream' });\n\tconst body = [\n\t\t`--${boundary}\\r\\nContent-Type: application/json; charset=UTF-8\\r\\n\\r\\n`,\n\t\tmeta,\n\t\t`\\r\\n--${boundary}\\r\\nContent-Type: application/octet-stream\\r\\n\\r\\n\\r\\n--${boundary}--`\n\t].join('');\n\tconst r = await fetch(`${DRIVE_UPLOAD}?uploadType=multipart`, {\n\t\tmethod: 'POST',\n\t\theaders: {\n\t\t\tAuthorization: `Bearer ${token}`,\n\t\t\t'Content-Type': `multipart/related; boundary=${boundary}`\n\t\t},\n\t\tbody,\n\t\tsignal: AbortSignal.timeout(META_DEADLINE_MS)\n\t});\n\tif (!r.ok) throw new SelfstoreError('TARGET_WRITE_FAILED', `Drive create failed: ${r.status}`);\n\tconst data: { id: string } = await r.json();\n\treturn data.id;\n}\n\nfunction makeTarget(opts: DriveOptions, fixedId?: string): BackupTarget {\n\tconst { auth, kv } = opts;\n\t// A target is BOUND to one file for its whole life: the id is the fixed one,\n\t// or the remembered id CAPTURED on first use - never re-read per operation.\n\t// The old lazy per-call read let a backup SWITCH re-point the remembered id\n\t// while the OUTGOING target was still attached: its final courtesy flush\n\t// then wrote the old store (data and sync bookkeeping) into the file being\n\t// opened, silently merging two \"isolated\" backups.\n\tlet captured: string | undefined = fixedId;\n\tconst fileIdOf = async (): Promise<string | undefined> =>\n\t\t(captured ??= await kv.get<string>(FILE_ID_KEY));\n\t// The instance's LIFE line: abortInFlight() (a user detach) cuts every\n\t// suspended request now instead of at its deadline. Requests mint their\n\t// signal per call, so work started AFTER a cut lives normally.\n\tconst life = lifeLine();\n\tconst bounded = (ms: number): AbortSignal => boundedSignal(ms, life.current());\n\treturn {\n\t\tkind: 'drive',\n\t\tlabel: 'Google Drive',\n\t\tabortInFlight: life.cut,\n\t\tasync save(blob: Blob): Promise<string | null> {\n\t\t\tconst fileId = await fileIdOf();\n\t\t\tif (!fileId) throw new SelfstoreError('NOT_CONNECTED', 'Drive not connected.');\n\t\t\t// fields=version: the response reports the file's new version, so the\n\t\t\t// store records exactly OUR write (no marker race with other replicas).\n\t\t\tconst r = await driveFetch(\n\t\t\t\tauth,\n\t\t\t\t(token) =>\n\t\t\t\t\tfetch(`${DRIVE_UPLOAD}/${fileId}?uploadType=media&fields=version`, {\n\t\t\t\t\t\tmethod: 'PATCH',\n\t\t\t\t\t\theaders: {\n\t\t\t\t\t\t\tAuthorization: `Bearer ${token}`,\n\t\t\t\t\t\t\t'Content-Type': 'application/octet-stream'\n\t\t\t\t\t\t},\n\t\t\t\t\t\tbody: blob,\n\t\t\t\t\t\tsignal: bounded(DATA_DEADLINE_MS)\n\t\t\t\t\t}),\n\t\t\t\tlife.current()\n\t\t\t);\n\t\t\tif (!r.ok) {\n\t\t\t\t// A 401 that survives a forced token refresh (driveFetch) is a genuine\n\t\t\t\t// loss of access, not a stale-token blip. A 404 (the bound file is\n\t\t\t\t// gone) or a non-rate-limit 403 (permission withdrawn, storage full) is\n\t\t\t\t// PERMANENT: gate so the user reconnects to a fresh file, never loop it\n\t\t\t\t// as transient. Everything else (5xx, network) stays transient.\n\t\t\t\tif (r.status === 401) throw new AuthExpiredError('Drive rejected the token (401).');\n\t\t\t\tconst body = await r.text().catch(() => '');\n\t\t\t\tif (isPermanentDriveWrite(r.status, body)) {\n\t\t\t\t\tthrow new SelfstoreError(\n\t\t\t\t\t\t'TARGET_GONE',\n\t\t\t\t\t\t`Drive backup unwritable (${r.status}): the file is gone or access was withdrawn.`\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tthrow new SelfstoreError('TARGET_WRITE_FAILED', `Drive upload failed: ${r.status}`);\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst data: { version?: string | number } = await r.json();\n\t\t\t\treturn data.version != null ? String(data.version) : null;\n\t\t\t} catch {\n\t\t\t\treturn null;\n\t\t\t}\n\t\t},\n\t\tasync load(): Promise<Blob | null> {\n\t\t\tconst fileId = await fileIdOf();\n\t\t\tif (!fileId) return null;\n\t\t\tconst r = await driveFetch(\n\t\t\t\tauth,\n\t\t\t\t(token) =>\n\t\t\t\t\tfetch(`${DRIVE_FILES}/${fileId}?alt=media`, {\n\t\t\t\t\t\theaders: { Authorization: `Bearer ${token}` },\n\t\t\t\t\t\tsignal: bounded(DATA_DEADLINE_MS)\n\t\t\t\t\t}),\n\t\t\t\tlife.current()\n\t\t\t);\n\t\t\t// A failed read must never pass for an empty file: null tells the store\n\t\t\t// \"nothing there\", and a fresh connect would then overwrite the backup\n\t\t\t// this download merely failed to fetch. Only a 404 - the file is\n\t\t\t// genuinely gone - reads as empty; a 401 that survived driveFetch's\n\t\t\t// forced refresh is a real loss of access; anything else is a typed\n\t\t\t// transient failure (the sync legs retry it, connect flows surface it).\n\t\t\tif (r.status === 404) return null;\n\t\t\tif (r.status === 401) throw new AuthExpiredError('Drive rejected the token (401).');\n\t\t\tif (!r.ok) {\n\t\t\t\tthrow new SelfstoreError('TARGET_UNAVAILABLE', `Drive download failed: ${r.status}`);\n\t\t\t}\n\t\t\treturn r.blob();\n\t\t},\n\t\tasync stat(): Promise<string | null> {\n\t\t\ttry {\n\t\t\t\tconst fileId = await fileIdOf();\n\t\t\t\tif (!fileId) return null;\n\t\t\t\tconst r = await driveFetch(\n\t\t\t\t\tauth,\n\t\t\t\t\t(token) =>\n\t\t\t\t\t\tfetch(`${DRIVE_FILES}/${fileId}?fields=version`, {\n\t\t\t\t\t\t\theaders: { Authorization: `Bearer ${token}` },\n\t\t\t\t\t\t\tsignal: bounded(META_DEADLINE_MS)\n\t\t\t\t\t\t}),\n\t\t\t\t\tlife.current()\n\t\t\t\t);\n\t\t\t\tif (!r.ok) return null;\n\t\t\t\tconst data: { version?: string | number } = await r.json();\n\t\t\t\treturn data.version != null ? String(data.version) : null;\n\t\t\t} catch {\n\t\t\t\treturn null; // offline or token lapse: \"cannot tell\", never throws\n\t\t\t}\n\t\t},\n\t\tasync isReady(): Promise<boolean> {\n\t\t\ttry {\n\t\t\t\tawait auth.token({ signal: life.current() });\n\t\t\t\treturn true;\n\t\t\t} catch (e) {\n\t\t\t\t// A transient token failure means \"not writable right now\", not a lost\n\t\t\t\t// session: report false so the store retries. Only a genuine auth loss\n\t\t\t\t// propagates, so the boot path can raise the reconnect gate.\n\t\t\t\tif (isAuthExpired(e)) throw e;\n\t\t\t\treturn false;\n\t\t\t}\n\t\t},\n\t\tasync reconnect(): Promise<boolean> {\n\t\t\treturn auth.reconnect();\n\t\t},\n\t\tasync disconnect(): Promise<void> {\n\t\t\tawait auth.forget();\n\t\t\t// Delete the remembered id only while it still points at this target's\n\t\t\t// file: after a backup switch the key already belongs to the new\n\t\t\t// target, and a late disconnect of the old one must not orphan it.\n\t\t\tconst current = await kv.get<string>(FILE_ID_KEY);\n\t\t\tconst own = await fileIdOf();\n\t\t\tif (!current || !own || current === own) await kv.del(FILE_ID_KEY);\n\t\t}\n\t};\n}\n\n/** First-time connect: authorize (a user gesture), then find-or-create the file.\n * Returns the target, or null if the user cancelled the consent. */\nexport async function connect(opts: DriveOptions): Promise<BackupTarget | null> {\n\tif (!(await opts.auth.reconnect())) return null;\n\tconst token = await opts.auth.token();\n\tlet fileId = await opts.kv.get<string>(FILE_ID_KEY);\n\tif (!fileId) {\n\t\tfileId =\n\t\t\t(await findExistingFile(token, [opts.fileName])) ?? (await createFile(token, opts.fileName));\n\t\tawait opts.kv.set(FILE_ID_KEY, fileId);\n\t}\n\treturn makeTarget(opts);\n}\n\n/** Rebuild the target from a past session's saved connection (no UI), or null. */\nexport async function fromSession(opts: DriveOptions): Promise<BackupTarget | null> {\n\treturn (await opts.kv.get<string>(FILE_ID_KEY)) ? makeTarget(opts) : null;\n}\n\n/** A read-only-intent preview of one specific (typically shared) file, before\n * adopting it. Writes NOTHING to the KV: until the user confirms, the device's\n * own backup wiring must stay untouched. */\nexport function preview(opts: DriveOptions, fileId: string): BackupTarget {\n\treturn makeTarget(opts, fileId);\n}\n\n/** Adopt a specific (typically shared) file as this device's backup. Persists\n * the file id, so next sessions restore it like any Drive backup. */\nexport async function adopt(opts: DriveOptions, fileId: string): Promise<BackupTarget> {\n\tawait opts.kv.set(FILE_ID_KEY, fileId);\n\treturn makeTarget(opts);\n}\n\n/** Find (or create) the user's own backup file, ignoring the currently adopted\n * id (which may point at someone else's shared file). Reuses the live session\n * when possible and only falls back to the consent popup. Adopts NOTHING: the\n * caller decides whether to load the file or start it blank. Null when the\n * consent is refused. */\nexport async function findOrCreateOwnFile(\n\topts: DriveOptions\n): Promise<{ fileId: string; created: boolean } | null> {\n\tlet token: string;\n\ttry {\n\t\ttoken = await opts.auth.token();\n\t} catch {\n\t\tif (!(await opts.auth.reconnect())) return null;\n\t\ttoken = await opts.auth.token();\n\t}\n\tconst existing = await findExistingFile(token, [opts.fileName]);\n\tif (existing) return { fileId: existing, created: false };\n\treturn { fileId: await createFile(token, opts.fileName), created: true };\n}\n\n/** Who a destination is held by, as far as it will say. */\nexport interface DriveAccount {\n\t/** The account's address, when Drive reports one. */\n\temail: string | null;\n\t/** Its display name, when Drive reports one. */\n\tname: string | null;\n}\n\n/** Which Google account the current session belongs to.\n *\n * A destination named \"Google Drive\" is not an address: several accounts look\n * alike, and a user who connected the wrong one has no way to tell from a\n * brand name alone. This is the one call that lets a surface say WHICH.\n *\n * Cheap (one metadata poke, allowed by the `drive.file` scope) but not free:\n * the answer only changes when the session does, so a caller should ask on\n * connect and on reconnect - not on every render. A destination that will not\n * say answers nulls rather than throwing; a genuinely lost session throws\n * AuthExpiredError like every other call here. */\nexport async function account(opts: { auth: DriveAuth }): Promise<DriveAccount> {\n\tconst fields = encodeURIComponent('user(emailAddress,displayName)');\n\tconst r = await driveFetch(opts.auth, (token) =>\n\t\tfetch(`${DRIVE_ABOUT}?fields=${fields}`, {\n\t\t\theaders: { Authorization: `Bearer ${token}` },\n\t\t\tsignal: AbortSignal.timeout(META_DEADLINE_MS)\n\t\t})\n\t);\n\tif (r.status === 401) throw new AuthExpiredError('Drive rejected the token (401).');\n\tif (!r.ok) throw new SelfstoreError('TARGET_UNAVAILABLE', `Drive about failed: ${r.status}`);\n\tconst data: { user?: { emailAddress?: string; displayName?: string } } = await r.json();\n\treturn {\n\t\temail: data.user?.emailAddress ?? null,\n\t\tname: data.user?.displayName ?? null\n\t};\n}\n\n/** One row of `listBackups()`: what Drive reports about a file the app can\n * see. Whether the backup is encrypted is not here on purpose - that lives\n * inside the file (inspect it, or remember it app-side); a listing must stay\n * one cheap metadata call, never N downloads. */\nexport interface DriveBackupInfo {\n\tid: string;\n\tname: string;\n\t/** RFC 3339 timestamp of the last write, as Drive reports it. */\n\tmodifiedTime: string | null;\n\t/** Size in bytes, when Drive reports one. */\n\tsize: number | null;\n}\n\n/** List the files this app can see on the user's own Drive (the drive.file\n * scope already narrows visibility to files the app created or was handed).\n * `nameContains` narrows server-side; selfstore ships no naming convention -\n * the app applies its own exact rules on what comes back (its backups, its\n * internal files). Newest first. */\nexport async function listBackups(opts: {\n\tauth: DriveAuth;\n\tnameContains?: string;\n}): Promise<DriveBackupInfo[]> {\n\tconst clauses = ['trashed=false', \"'me' in owners\"];\n\tif (opts.nameContains) {\n\t\tconst safe = opts.nameContains.replace(/\\\\/g, '\\\\\\\\').replace(/'/g, \"\\\\'\");\n\t\tclauses.push(`name contains '${safe}'`);\n\t}\n\tconst q = encodeURIComponent(clauses.join(' and '));\n\tconst fields = encodeURIComponent('files(id,name,modifiedTime,size)');\n\tconst r = await driveFetch(opts.auth, (token) =>\n\t\tfetch(`${DRIVE_FILES}?q=${q}&fields=${fields}&pageSize=100&orderBy=modifiedTime%20desc`, {\n\t\t\theaders: { Authorization: `Bearer ${token}` },\n\t\t\tsignal: AbortSignal.timeout(META_DEADLINE_MS)\n\t\t})\n\t);\n\tif (r.status === 401) throw new AuthExpiredError('Drive rejected the token (401).');\n\tif (!r.ok) throw new SelfstoreError('TARGET_UNAVAILABLE', `Drive list failed: ${r.status}`);\n\tconst data: {\n\t\tfiles?: { id: string; name: string; modifiedTime?: string; size?: string | number }[];\n\t} = await r.json();\n\treturn (data.files ?? []).map((f) => ({\n\t\tid: f.id,\n\t\tname: f.name,\n\t\tmodifiedTime: f.modifiedTime ?? null,\n\t\tsize: f.size != null ? Number(f.size) : null\n\t}));\n}\n\n/** Create a brand-new empty backup file with this exact name and answer its\n * id. Refuses when a non-trashed owned file already carries the name: Drive\n * happily stores duplicate names, and a duplicate would poison every\n * find-by-name reconnect that follows. Adopts nothing - the caller decides\n * to attach it (typically with `wipe: true` so it starts blank). */\nexport async function createBackup(opts: {\n\tauth: DriveAuth;\n\tfileName: string;\n}): Promise<{ fileId: string }> {\n\tconst token = await opts.auth.token();\n\tconst existing = await findExistingFile(token, [opts.fileName]);\n\tif (existing) {\n\t\tthrow new SelfstoreError(\n\t\t\t'TARGET_WRITE_FAILED',\n\t\t\t`A file named \"${opts.fileName}\" already exists on Drive.`\n\t\t);\n\t}\n\treturn { fileId: await createFile(token, opts.fileName) };\n}\n\n/** Delete a backup file from Drive, for good (not the trash: the file's data\n * must actually be gone, not lingering). A 404 is success - deleting what is\n * already gone is not an error. The caller owns the guard against deleting\n * the currently attached file. */\nexport async function deleteBackup(opts: { auth: DriveAuth; fileId: string }): Promise<void> {\n\tconst r = await driveFetch(opts.auth, (token) =>\n\t\tfetch(`${DRIVE_FILES}/${opts.fileId}`, {\n\t\t\tmethod: 'DELETE',\n\t\t\theaders: { Authorization: `Bearer ${token}` },\n\t\t\tsignal: AbortSignal.timeout(META_DEADLINE_MS)\n\t\t})\n\t);\n\tif (r.status === 401) throw new AuthExpiredError('Drive rejected the token (401).');\n\tif (r.status === 404) return;\n\tif (!r.ok) throw new SelfstoreError('TARGET_WRITE_FAILED', `Drive delete failed: ${r.status}`);\n}\n\n/** Rename a backup file (metadata only - the id, the content and every open\n * target bound to it are untouched). Refuses when another owned file already\n * carries the new name, for the same reason createBackup does: a duplicate\n * poisons find-by-name. A 404 means the file is gone, not a success here. */\nexport async function renameBackup(opts: {\n\tauth: DriveAuth;\n\tfileId: string;\n\tfileName: string;\n}): Promise<void> {\n\tconst token = await opts.auth.token();\n\tconst existing = await findExistingFile(token, [opts.fileName]);\n\tif (existing && existing !== opts.fileId) {\n\t\tthrow new SelfstoreError(\n\t\t\t'TARGET_WRITE_FAILED',\n\t\t\t`A file named \"${opts.fileName}\" already exists on Drive.`\n\t\t);\n\t}\n\tif (existing === opts.fileId) return; // already this name: nothing to do\n\tconst r = await driveFetch(opts.auth, (token) =>\n\t\tfetch(`${DRIVE_FILES}/${opts.fileId}?fields=id`, {\n\t\t\tmethod: 'PATCH',\n\t\t\theaders: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' },\n\t\t\tbody: JSON.stringify({ name: opts.fileName }),\n\t\t\tsignal: AbortSignal.timeout(META_DEADLINE_MS)\n\t\t})\n\t);\n\tif (r.status === 401) throw new AuthExpiredError('Drive rejected the token (401).');\n\tif (!r.ok) throw new SelfstoreError('TARGET_WRITE_FAILED', `Drive rename failed: ${r.status}`);\n}\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/flows/connect.ts","../src/flows/share.ts","../src/flows/join.ts"],"names":["connect","config","DEFAULT_DEADLINE_MS"],"mappings":"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","file":"chunk-LKIRWVXM.js","sourcesContent":["// connectFlow: the \"where does my data live\" journey as a headless state\n// machine. It owns ordering and failure rules that took a series of\n// production hotfixes to get right, each covered by a test:\n//\n// choose -> (form) -> authorizing -> (password) -> (conflict) -> connected\n// \\-> error\n//\n// One popup per gesture (re-entry ignored while a leg is in flight), and a\n// cancelled popup is not an error - back to 'choose', silently. An encrypted\n// backup's password is proven by a trial read while the store is untouched,\n// so a wrong password can never half-connect (deferUnlock opts out: adopt\n// locked, unlock where the host chooses). overwrite() is the forgotten-\n// password escape: local wiped first, tiny blank write. Merge is the default\n// reconciliation; the destructive choices hide behind 'conflict', which only\n// appears when the app declared local data worth asking about. Network legs\n// run under a deadline; human steps never do. A cancelled journey stays\n// dead - late resolutions are discarded (generation counter).\n\nimport type { LocalStore, StoreError } from '../persistence/store';\nimport type { BackupTarget } from '../persistence/target';\nimport type { KV } from '../persistence/cache';\nimport { restore } from '../selfstore/fluent';\nimport { SelfstoreError, isSelfstoreError } from '../selfstore/errors';\nimport { connect as driveConnect, type DriveAuth } from '../persistence/targets/drive';\nimport {\n\tconnect as fileConnect,\n\tisSupported as fileIsSupported,\n\topenExisting as fileOpenExisting\n} from '../persistence/targets/file';\nimport { connect as webdavConnect, type WebdavConfig } from '../persistence/targets/webdav';\nimport { connect as s3Connect, type S3Config } from '../persistence/targets/s3';\nimport { makeMachine, toStoreError, withDeadline, type FlowStore } from './machine';\n\nexport type ConnectKind = 'drive' | 'file' | 'webdav' | 's3';\n\n/** Produce a connected BackupTarget, or null when the user cancelled the\n * gesture (consent refused, picker closed). Custom connectors slot custom\n * targets - or test fakes - into the same journey. */\nexport type Connector = () => Promise<BackupTarget | null>;\n\n/** Which destinations the flow offers, and how to open each one. The offer\n * renders in this OBJECT's insertion order: the host decides which\n * destination leads by how it writes the object, not by a ranking here. */\nexport interface ConnectTargets {\n\t/** Google Drive: the auth strategy (e.g. gisDriveAuth), or a custom connector. */\n\tdrive?: DriveAuth | Connector;\n\t/** Local file via the browser's picker (true), or a custom connector. On\n\t * browsers without the File System Access API the flow degrades to the\n\t * manual download mode by itself. The object form offers TWO gestures on\n\t * the same destination: `create` (save picker: start or overwrite a backup\n\t * file) and `open` (open picker: adopt an EXISTING backup file - it then\n\t * goes through the same inspect/password/resolution journey). */\n\tfile?: true | Connector | { create?: true | Connector; open?: true | Connector };\n\t/** WebDAV: true renders a 'form' step and expects submitWebdav(config);\n\t * a fixed WebdavConfig skips the form; or a custom connector. */\n\twebdav?: true | WebdavConfig | Connector;\n\t/** S3-compatible bucket (Amazon S3, R2, B2, MinIO): true renders a 'form'\n\t * step and expects submitS3(config); a fixed S3Config skips the form; or a\n\t * custom connector. Offer it alongside `webdav` and the widget groups both\n\t * under one \"your server\" entry with a WebDAV/S3 toggle. */\n\ts3?: true | S3Config | Connector;\n}\n\n/** What the flows need from the store. The simple store exposes this as\n * `store.flowHost`; an advanced-store app builds it by hand. */\nexport interface FlowHost {\n\tengine: LocalStore;\n\tkv: KV;\n\t/** Backup file name on the destination (the simple store uses `app.zip`). */\n\tbackupName: string;\n}\n\n/** What every flow and widget accepts as \"the store\": the simple facade, which\n * carries its ports on `flowHost`, or a hand-built host. */\nexport type StoreLike = FlowHost | { flowHost: FlowHost };\n\n/**\n * A destination this device has already used, offered as one gesture back to\n * it rather than as the question it answered the first time.\n *\n * The screen that asks \"where should we save your data?\" is right exactly\n * once. Every time after that - a device whose destination was detached, a\n * browser whose storage was cleared - the app usually still knows the answer,\n * and asking again makes the returning user re-derive a decision they already\n * made. This offer carries that memory: it goes first, above a separator, and\n * says WHICH backup it will reopen.\n *\n * The host supplies the connector, because only the host knows how to reach\n * the remembered destination (adopting a known file id, not searching by\n * name). The flow supplies what follows: no conflict step, and no chooser.\n */\nexport interface ResumeOffer {\n\t/** Which destination it is. Picks the icon and the wording, nothing else. */\n\tkind: ConnectKind;\n\t/**\n\t * What exactly is being reopened - the account, the file name. Rendered as\n\t * the card's second line: \"resume my backup\" only answers the user's\n\t * question when it can name the backup.\n\t */\n\tdetail?: string;\n\t/** Reopen it. Same contract as any connector: null when the user cancels. */\n\tconnect: Connector;\n}\n\nexport interface ConnectFlowOptions {\n\t/**\n\t * The destination this device already used, offered ahead of the others.\n\t * Absent (the default) means every visit is a first visit.\n\t *\n\t * Taking this offer never raises the conflict step, whatever\n\t * `hasLocalData` says: reopening one's own backup is not a merge question,\n\t * it IS the answer to it. The question still stands for a destination the\n\t * user newly points at.\n\t */\n\tresume?: ResumeOffer;\n\t/** Declare when this device holds data worth a question: only then does an\n\t * existing backup raise the 'conflict' step with the destructive choices.\n\t * Default: never - the flow applies defaultResolution. */\n\thasLocalData?: () => boolean;\n\t/** How to reconcile with a destination that already holds a backup when the\n\t * conflict step is not raised. Default 'merge' - the only choice that\n\t * loses nothing on either side. 'resume' adopts the backup (the\n\t * destination wins; this device's working copy is replaced), 'replace'\n\t * overwrites the backup with this device. An encrypted backup still asks\n\t * for its password first, whatever the resolution; an empty destination\n\t * is not concerned (this device's data simply becomes its content). */\n\tdefaultResolution?: ConnectResolution;\n\t/** Skip the password step on an encrypted backup: adopt it AS is (resume)\n\t * and complete with the store locked - nothing readable, nothing\n\t * clobbered - for the host to unlock on its own surface (engine.unlock)\n\t * after the connect. Adoption is forced whatever defaultResolution says:\n\t * without the proven key a merge is impossible and a replace would\n\t * destroy a backup no one proved they own. Hosts that let a device\n\t * holding real local data connect should keep the password-first\n\t * journey - adoption replaces the working copy. */\n\tdeferUnlock?: boolean;\n\t/**\n\t * A secret the host already holds (an app passphrase, a derived key), used\n\t * to encrypt the backup when this journey creates or adopts one that is NOT\n\t * already encrypted - an empty destination, or a plaintext file.\n\t *\n\t * Two things need it. A store built with `requireEncryption` refuses a\n\t * passwordless attach outright (ENCRYPTION_REQUIRED), so without this the\n\t * connect journey cannot complete at all. And even without that setting, a\n\t * fresh backup is better born encrypted than written in clear and protected\n\t * on a second write, which would leave cleartext on the destination in\n\t * between.\n\t *\n\t * Never used against a destination whose backup is already encrypted: that\n\t * one is proven through the password step (or adopted locked under\n\t * `deferUnlock`), so a host secret that happens to differ can neither fail\n\t * the attach nor overwrite what it cannot read. Resolved lazily, so the\n\t * secret is only read when a connect actually needs it. Must satisfy the\n\t * store's password policy.\n\t */\n\tpassword?: string | (() => string | Promise<string>);\n\t/** Deadline for each network leg (inspect, trial-read, attach), ms. */\n\tdeadlineMs?: number;\n}\n\nexport type ConnectStep =\n\t'choose' | 'form' | 'authorizing' | 'password' | 'conflict' | 'connected' | 'error';\n\n/** How to reconcile with a destination that already holds a backup. */\nexport type ConnectResolution = 'merge' | 'resume' | 'replace';\n\n/** What connecting did. 'merged': both sides folded. 'started': empty\n * destination now holds this device's data. 'resumed': the backup won,\n * this device adopted it. 'replaced': this device won, the backup was\n * overwritten. 'manual': degraded download-on-demand file mode. */\nexport type ConnectFlowOutcome = 'merged' | 'started' | 'resumed' | 'replaced' | 'manual';\n\nexport interface ConnectSnapshot {\n\tstep: ConnectStep;\n\t/** The destinations to offer, in the order given. */\n\tkinds: readonly ConnectKind[];\n\t/** The destination being connected (null while choosing). */\n\tkind: ConnectKind | null;\n\t/** Set when step === 'connected'. */\n\toutcome: ConnectFlowOutcome | null;\n\t/** Known after inspection: the destination already holds a backup. */\n\thasBackup: boolean;\n\t/** ...and that backup is encrypted. */\n\tencrypted: boolean;\n\t/** The last submitPassword() was wrong (stay on 'password', say so). */\n\tpasswordError: boolean;\n\t/** An async leg is in flight (render a spinner, disable the actions). */\n\tbusy: boolean;\n\t/** Set when step === 'error': show `labelKey`, log `message`. */\n\terror: StoreError | null;\n}\n\nexport interface ConnectFlow extends FlowStore<ConnectSnapshot> {\n\t/** Pick a destination. Call it inside the user's click: the consent popup /\n\t * file picker opens synchronously from the gesture. Ignored while a leg is\n\t * already in flight (one popup per gesture). For a `file` target declared\n\t * with the object form, `variant` picks the gesture: 'create' (default,\n\t * save picker) or 'open' (adopt an existing backup file). */\n\tchoose(kind: ConnectKind, variant?: 'create' | 'open'): void;\n\t/** Reopen the remembered destination (`options.resume`). Same gesture rule\n\t * as choose - call it inside the click. No-op when nothing is remembered.\n\t * Never raises the conflict step: this backup is already this device's. */\n\tresume(): void;\n\t/** 'form' step (webdav: true): submit the server config. */\n\tsubmitWebdav(config: WebdavConfig): void;\n\t/** 'form' step (s3: true): submit the bucket config. */\n\tsubmitS3(config: S3Config): void;\n\t/** 'password' step: prove the backup's password. Wrong password sets\n\t * passwordError and stays here; nothing is attached either way. */\n\tsubmitPassword(password: string): void;\n\t/** 'password' step escape hatch: give up on a forgotten password and start a\n\t * fresh backup over the protected one. The encrypted backup is overwritten\n\t * by this device's data (possibly empty) and lost for good - the only way\n\t * out when the password cannot be recovered. */\n\toverwrite(): void;\n\t/** 'conflict' step: pick the reconciliation. */\n\tresolveConflict(how: ConnectResolution): void;\n\t/** Abandon the journey in progress: back to 'choose'. A late resolution of\n\t * the abandoned leg is discarded. */\n\tcancel(): void;\n\t/** 'error' step: back to 'choose' to try again. */\n\tretry(): void;\n}\n\nconst DEFAULT_DEADLINE_MS = 30_000;\n\n/** A resolution's attach plan: which strategy runs, which outcome it names. */\nfunction planFor(how: ConnectResolution): {\n\tstrategy: 'merge' | 'replace-local' | 'replace-remote';\n\toutcome: ConnectFlowOutcome;\n} {\n\tif (how === 'resume') return { strategy: 'replace-local', outcome: 'resumed' };\n\tif (how === 'replace') return { strategy: 'replace-remote', outcome: 'replaced' };\n\treturn { strategy: 'merge', outcome: 'merged' };\n}\n\nexport function connectFlow(\n\tstore: StoreLike,\n\ttargets: ConnectTargets,\n\toptions: ConnectFlowOptions = {}\n): ConnectFlow {\n\tconst host = 'flowHost' in store ? store.flowHost : store;\n\tconst deadlineMs = options.deadlineMs ?? DEFAULT_DEADLINE_MS;\n\tconst kinds = (Object.keys(targets) as ConnectKind[]).filter(\n\t\t(k) => (k === 'drive' || k === 'file' || k === 'webdav' || k === 's3') && targets[k] != null\n\t);\n\n\tconst m = makeMachine<ConnectSnapshot>({\n\t\tstep: 'choose',\n\t\tkinds,\n\t\tkind: null,\n\t\toutcome: null,\n\t\thasBackup: false,\n\t\tencrypted: false,\n\t\tpasswordError: false,\n\t\tbusy: false,\n\t\terror: null\n\t});\n\n\t// The journey's working set. `generation` invalidates in-flight legs on\n\t// cancel(): whoever resolves late compares its captured generation and\n\t// drops its result instead of resurrecting an abandoned journey.\n\tlet generation = 0;\n\tlet target: BackupTarget | null = null;\n\tlet password: string | null = null;\n\t// Set at inspection: the destination already holds an ENCRYPTED backup, so\n\t// its key is the one that matters and the host secret must stay out of it.\n\tlet remoteEncrypted = false;\n\t// This journey took the resume offer rather than picking a destination.\n\t// Reset with every other piece of journey state, so a cancelled resume\n\t// followed by an ordinary choice is an ordinary choice.\n\tlet resuming = false;\n\n\tconst backToChoose = (): void => {\n\t\tgeneration++;\n\t\ttarget = null;\n\t\tpassword = null;\n\t\tremoteEncrypted = false;\n\t\tresuming = false;\n\t\tm.set({\n\t\t\tstep: 'choose',\n\t\t\tkind: null,\n\t\t\toutcome: null,\n\t\t\thasBackup: false,\n\t\t\tencrypted: false,\n\t\t\tpasswordError: false,\n\t\t\tbusy: false,\n\t\t\terror: null\n\t\t});\n\t};\n\n\tconst fail = (e: unknown): void => {\n\t\tpassword = null;\n\t\tm.set({ step: 'error', busy: false, error: toStoreError(e) });\n\t};\n\n\t/** The host's own secret, resolved on demand (see options.password). */\n\tconst hostSecret = async (): Promise<string | null> => {\n\t\tconst supplied = options.password;\n\t\tif (supplied === undefined) return null;\n\t\treturn typeof supplied === 'function' ? await supplied() : supplied;\n\t};\n\n\tfunction connectorFor(\n\t\tkind: ConnectKind,\n\t\tvariant: 'create' | 'open' = 'create'\n\t): Connector | null {\n\t\tconst spec = targets[kind];\n\t\tif (spec == null) return null;\n\t\tif (typeof spec === 'function') return spec;\n\t\tif (kind === 'drive') {\n\t\t\tconst auth = spec as DriveAuth;\n\t\t\treturn () => driveConnect({ auth, kv: host.kv, fileName: host.backupName });\n\t\t}\n\t\tif (kind === 'file') {\n\t\t\t// Object form: each gesture has its own spec ('open' has no built-in\n\t\t\t// meaning for other kinds, so it resolves here only).\n\t\t\tif (typeof spec === 'object' && !('url' in spec)) {\n\t\t\t\tconst sub = (spec as { create?: true | Connector; open?: true | Connector })[variant];\n\t\t\t\tif (sub == null) return null;\n\t\t\t\tif (typeof sub === 'function') return sub;\n\t\t\t\treturn variant === 'open'\n\t\t\t\t\t? () => fileOpenExisting({ kv: host.kv })\n\t\t\t\t\t: () => fileConnect({ kv: host.kv, fileName: host.backupName });\n\t\t\t}\n\t\t\treturn () => fileConnect({ kv: host.kv, fileName: host.backupName });\n\t\t}\n\t\tif (kind === 's3') {\n\t\t\tconst config = spec as S3Config; // 's3: true' goes through the form step\n\t\t\treturn () => s3Connect({ kv: host.kv, config });\n\t\t}\n\t\tconst config = spec as WebdavConfig; // 'webdav: true' goes through the form step\n\t\treturn () => webdavConnect({ kv: host.kv, config });\n\t}\n\n\t/** No picker on this browser: download-on-demand instead of a dead button. */\n\tfunction degradeToManual(kind: ConnectKind): void {\n\t\tconst gen = ++generation;\n\t\tm.set({ step: 'authorizing', kind, busy: true, error: null });\n\t\tvoid host.engine\n\t\t\t.setManualFile()\n\t\t\t.then(() => {\n\t\t\t\tif (gen !== generation) return;\n\t\t\t\tm.set({ step: 'connected', outcome: 'manual', busy: false });\n\t\t\t})\n\t\t\t.catch((e) => {\n\t\t\t\tif (gen !== generation) return;\n\t\t\t\tfail(e);\n\t\t\t});\n\t}\n\n\t/** After the target is in hand: inspect, then route to password / conflict /\n\t * attach. Everything here is network - deadline-bounded, generation-checked. */\n\tasync function inspectAndRoute(gen: number, t: BackupTarget): Promise<void> {\n\t\tconst info = await withDeadline(host.engine.inspectTarget(t), deadlineMs, 'The destination');\n\t\tif (gen !== generation) return;\n\t\ttarget = t;\n\t\tremoteEncrypted = info.encrypted;\n\t\tm.set({ hasBackup: info.hasBackup, encrypted: info.encrypted });\n\t\tif (!info.hasBackup) {\n\t\t\t// Empty destination: this device's data becomes its content. Nothing\n\t\t\t// to ask, nothing to lose.\n\t\t\tawait attach(gen, 'replace-remote', 'started');\n\t\t\treturn;\n\t\t}\n\t\tif (info.app !== null && info.app !== host.engine.app) {\n\t\t\t// Another app's backup: say so NOW, before the password step. Asking\n\t\t\t// first taught the user their password \"does not work\" on a file that\n\t\t\t// was never this app's to open - and deferUnlock went further and\n\t\t\t// silently adopted the foreign file as this app's home. The header\n\t\t\t// names its app in cleartext precisely so a wrong pick can be named\n\t\t\t// for what it is; a header that does not say accuses no one.\n\t\t\tm.set({\n\t\t\t\tstep: 'error',\n\t\t\t\tbusy: false,\n\t\t\t\terror: toStoreError(\n\t\t\t\t\tnew SelfstoreError(\n\t\t\t\t\t\t'FOREIGN_BACKUP',\n\t\t\t\t\t\t`This backup was written by \"${info.app}\", not \"${host.engine.app}\". Open it in its own application.`\n\t\t\t\t\t)\n\t\t\t\t)\n\t\t\t});\n\t\t\treturn;\n\t\t}\n\t\tif (info.encrypted) {\n\t\t\tif (options.deferUnlock) {\n\t\t\t\t// The host owns the unlock surface: adopt the backup as it stands.\n\t\t\t\t// A null password on an encrypted file lands the store locked -\n\t\t\t\t// nothing readable, nothing clobbered - until engine.unlock()\n\t\t\t\t// proves the password wherever the host asks for it. The conflict\n\t\t\t\t// step is moot here: without the key, merge cannot read the remote\n\t\t\t\t// and replace would destroy a backup no one proved they own.\n\t\t\t\tawait attach(gen, 'replace-local', 'resumed');\n\t\t\t\treturn;\n\t\t\t}\n\t\t\t// Ask before touching anything. The password gates every resolution,\n\t\t\t// including 'replace': overwriting an encrypted backup demands proof\n\t\t\t// of ownership, or a typo away from someone's data.\n\t\t\tm.set({ step: 'password', busy: false, passwordError: false });\n\t\t\treturn;\n\t\t}\n\t\trouteAfterUnlock(gen);\n\t}\n\n\t/** Password proven (or none needed): either ask the conflict question, or\n\t * apply the default resolution - 'merge' unless the host chose otherwise,\n\t * the only default that loses nothing on either side. */\n\tfunction routeAfterUnlock(gen: number): void {\n\t\tif (gen !== generation) return;\n\t\t// `resuming` survives the password detour on purpose: the journey is one\n\t\t// generation, and the question \"is this the backup I already had\" does\n\t\t// not change while its password is being typed. Reopening one's own\n\t\t// backup is not a merge question, so the conflict step never rises on\n\t\t// that path - only on a destination newly pointed at.\n\t\tif (!resuming && options.hasLocalData?.() === true) {\n\t\t\tm.set({ step: 'conflict', busy: false });\n\t\t\treturn;\n\t\t}\n\t\tconst plan = planFor(resuming ? 'resume' : (options.defaultResolution ?? 'merge'));\n\t\tvoid attach(gen, plan.strategy, plan.outcome);\n\t}\n\n\tasync function attach(\n\t\tgen: number,\n\t\tstrategy: 'merge' | 'replace-local' | 'replace-remote',\n\t\toutcome: ConnectFlowOutcome,\n\t\twipe = false\n\t): Promise<void> {\n\t\tif (gen !== generation || !target) return;\n\t\tm.set({ busy: true });\n\t\ttry {\n\t\t\t// A backup that is already encrypted carries its own key: it was proven\n\t\t\t// at the password step, or deliberately left locked. Everywhere else -\n\t\t\t// an empty destination, a plaintext one - the host secret (when it\n\t\t\t// offers one) makes the backup encrypted from its very first write.\n\t\t\tconst secret = password ?? (remoteEncrypted ? null : await hostSecret());\n\t\t\tif (gen !== generation) return;\n\t\t\tawait withDeadline(\n\t\t\t\thost.engine.attachTarget(target, { password: secret, strategy, wipe }),\n\t\t\t\tdeadlineMs,\n\t\t\t\t'The destination'\n\t\t\t);\n\t\t\tif (gen !== generation) return;\n\t\t\tpassword = null;\n\t\t\tm.set({ step: 'connected', outcome, busy: false, error: null });\n\t\t} catch (e) {\n\t\t\tif (gen !== generation) return;\n\t\t\tfail(e);\n\t\t}\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\tresume(): void {\n\t\t\tconst { step, busy } = m.snapshot;\n\t\t\tif (busy || (step !== 'choose' && step !== 'error')) return; // one popup per gesture\n\t\t\tconst offer = options.resume;\n\t\t\tif (!offer) return;\n\t\t\tresuming = true;\n\t\t\tconst gen = ++generation;\n\t\t\tm.set({ step: 'authorizing', kind: offer.kind, busy: true, error: null });\n\t\t\t// No degraded mode and no form here: the host handed us a connector\n\t\t\t// that knows exactly which backup to reopen. A null answer is the\n\t\t\t// user closing the consent popup - back to the choice, no error.\n\t\t\tvoid offer\n\t\t\t\t.connect()\n\t\t\t\t.then(async (t) => {\n\t\t\t\t\tif (gen !== generation) return;\n\t\t\t\t\tif (!t) {\n\t\t\t\t\t\tbackToChoose();\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tawait inspectAndRoute(gen, t);\n\t\t\t\t})\n\t\t\t\t.catch((e) => {\n\t\t\t\t\tif (gen !== generation) return;\n\t\t\t\t\tfail(e);\n\t\t\t\t});\n\t\t},\n\n\t\tchoose(kind: ConnectKind, variant: 'create' | 'open' = 'create'): void {\n\t\t\tconst { step, busy } = m.snapshot;\n\t\t\tif (busy || (step !== 'choose' && step !== 'error')) return; // one popup per gesture\n\t\t\tresuming = false;\n\t\t\tconst connector = connectorFor(kind, variant);\n\t\t\tif (!connector) return;\n\t\t\tif (\n\t\t\t\t(kind === 'webdav' && targets.webdav === true) ||\n\t\t\t\t(kind === 's3' && targets.s3 === true)\n\t\t\t) {\n\t\t\t\tm.set({ step: 'form', kind, error: null });\n\t\t\t\treturn;\n\t\t\t}\n\t\t\t// Degraded manual mode only makes sense for CREATING a backup through\n\t\t\t// the built-in save picker: adopting an existing file needs the real\n\t\t\t// picker, and a custom connector owns its own fallback.\n\t\t\tconst builtinCreate =\n\t\t\t\ttargets.file === true ||\n\t\t\t\t(typeof targets.file === 'object' &&\n\t\t\t\t\t(targets.file as { create?: unknown }).create !== undefined &&\n\t\t\t\t\ttypeof (targets.file as { create?: unknown }).create !== 'function');\n\t\t\tconst canDegrade = kind === 'file' && variant === 'create' && builtinCreate;\n\t\t\tif (canDegrade && !fileIsSupported()) {\n\t\t\t\tdegradeToManual(kind);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst gen = ++generation;\n\t\t\tm.set({ step: 'authorizing', kind, busy: true, error: null });\n\t\t\t// The connector runs the human gesture (popup, picker): no deadline on\n\t\t\t// people. The network legs after it are bounded in inspectAndRoute.\n\t\t\tvoid connector()\n\t\t\t\t.then(async (t) => {\n\t\t\t\t\tif (gen !== generation) return;\n\t\t\t\t\tif (!t) {\n\t\t\t\t\t\t// The picker was there, and refused: this browser has no file\n\t\t\t\t\t\t// mode, we just could not know before asking. Same landing as\n\t\t\t\t\t\t// a browser without the API - the offer must not send the\n\t\t\t\t\t\t// practitioner back to a button that will refuse again.\n\t\t\t\t\t\tif (canDegrade && !fileIsSupported()) {\n\t\t\t\t\t\t\tdegradeToManual(kind);\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t\t// Drive/file/custom answer null for a cancelled popup or picker:\n\t\t\t\t\t\t// not an error, no message. A FIXED webdav/s3 config answering\n\t\t\t\t\t\t// null means the server did not: that one is a retryable error.\n\t\t\t\t\t\tif (kind === 'webdav' || kind === 's3') {\n\t\t\t\t\t\t\tfail(\n\t\t\t\t\t\t\t\tnew SelfstoreError(\n\t\t\t\t\t\t\t\t\t'TARGET_UNAVAILABLE',\n\t\t\t\t\t\t\t\t\t'The server did not answer (address, credentials or CORS).'\n\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tbackToChoose();\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tawait inspectAndRoute(gen, t);\n\t\t\t\t})\n\t\t\t\t.catch((e) => {\n\t\t\t\t\tif (gen !== generation) return;\n\t\t\t\t\tfail(e);\n\t\t\t\t});\n\t\t},\n\n\t\tsubmitWebdav(config: WebdavConfig): void {\n\t\t\tconst { step, busy } = m.snapshot;\n\t\t\tif (busy || step !== 'form') return;\n\t\t\tconst gen = ++generation;\n\t\t\tm.set({ step: 'authorizing', busy: true });\n\t\t\tvoid withDeadline(webdavConnect({ kv: host.kv, config }), deadlineMs, 'The WebDAV server')\n\t\t\t\t.then(async (t) => {\n\t\t\t\t\tif (gen !== generation) return;\n\t\t\t\t\tif (!t) {\n\t\t\t\t\t\t// webdavConnect answers null when the server does not: with an\n\t\t\t\t\t\t// explicit config this is a wrong-address error, not a cancel.\n\t\t\t\t\t\tfail(\n\t\t\t\t\t\t\tnew SelfstoreError(\n\t\t\t\t\t\t\t\t'TARGET_UNAVAILABLE',\n\t\t\t\t\t\t\t\t'The WebDAV server did not answer (URL, credentials or CORS).'\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t);\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tawait inspectAndRoute(gen, t);\n\t\t\t\t})\n\t\t\t\t.catch((e) => {\n\t\t\t\t\tif (gen !== generation) return;\n\t\t\t\t\tfail(e);\n\t\t\t\t});\n\t\t},\n\n\t\tsubmitS3(config: S3Config): void {\n\t\t\tconst { step, busy } = m.snapshot;\n\t\t\tif (busy || step !== 'form') return;\n\t\t\tconst gen = ++generation;\n\t\t\tm.set({ step: 'authorizing', busy: true });\n\t\t\tvoid withDeadline(s3Connect({ kv: host.kv, config }), deadlineMs, 'The S3 endpoint')\n\t\t\t\t.then(async (t) => {\n\t\t\t\t\tif (gen !== generation) return;\n\t\t\t\t\tif (!t) {\n\t\t\t\t\t\t// s3Connect answers null when the endpoint does not: with an\n\t\t\t\t\t\t// explicit config this is a wrong-config error, not a cancel.\n\t\t\t\t\t\tfail(\n\t\t\t\t\t\t\tnew SelfstoreError(\n\t\t\t\t\t\t\t\t'TARGET_UNAVAILABLE',\n\t\t\t\t\t\t\t\t'The S3 endpoint did not answer (endpoint, credentials or CORS).'\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t);\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tawait inspectAndRoute(gen, t);\n\t\t\t\t})\n\t\t\t\t.catch((e) => {\n\t\t\t\t\tif (gen !== generation) return;\n\t\t\t\t\tfail(e);\n\t\t\t\t});\n\t\t},\n\n\t\tsubmitPassword(pw: string): void {\n\t\t\tconst { step, busy } = m.snapshot;\n\t\t\tif (busy || step !== 'password' || !target) return;\n\t\t\tconst gen = generation;\n\t\t\tm.set({ busy: true, passwordError: false });\n\t\t\tvoid (async () => {\n\t\t\t\ttry {\n\t\t\t\t\t// PROVE the password against the real backup while the store is\n\t\t\t\t\t// still untouched: download and trial-read. A wrong password is a\n\t\t\t\t\t// retype, never a locked store or a gate.\n\t\t\t\t\tconst blob = await withDeadline(target!.load(), deadlineMs, 'The destination');\n\t\t\t\t\tif (gen !== generation) return;\n\t\t\t\t\tif (!blob) {\n\t\t\t\t\t\tthrow new SelfstoreError('TARGET_UNAVAILABLE', 'The backup could not be downloaded.');\n\t\t\t\t\t}\n\t\t\t\t\tawait restore(blob).withPassword(pw).read();\n\t\t\t\t\tif (gen !== generation) return;\n\t\t\t\t\tpassword = pw;\n\t\t\t\t\tm.set({ busy: false });\n\t\t\t\t\trouteAfterUnlock(gen);\n\t\t\t\t} catch (e) {\n\t\t\t\t\tif (gen !== generation) return;\n\t\t\t\t\tif (isSelfstoreError(e) && e.code === 'DECRYPT_FAILED') {\n\t\t\t\t\t\tm.set({ busy: false, passwordError: true }); // stay, say so, retype\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tfail(e);\n\t\t\t\t}\n\t\t\t})();\n\t\t},\n\n\t\toverwrite(): void {\n\t\t\tconst { step, busy } = m.snapshot;\n\t\t\tif (busy || step !== 'password' || !target) return;\n\t\t\t// The password is forgotten: stop proving it and START fresh over the\n\t\t\t// protected backup. wipe:true empties local first (fresh sync meta), so\n\t\t\t// this writes a blank new backup - never this device's existing data -\n\t\t\t// and the owner is never stuck behind a lost secret. A blank write is\n\t\t\t// also tiny, so it cannot time out the way pushing a large local store\n\t\t\t// would.\n\t\t\tpassword = null;\n\t\t\tvoid attach(generation, 'replace-remote', 'replaced', true);\n\t\t},\n\n\t\tresolveConflict(how: ConnectResolution): void {\n\t\t\tconst { step, busy } = m.snapshot;\n\t\t\tif (busy || step !== 'conflict') return;\n\t\t\tconst plan = planFor(how);\n\t\t\tvoid attach(generation, plan.strategy, plan.outcome);\n\t\t},\n\n\t\tcancel(): void {\n\t\t\tif (m.snapshot.step === 'connected') return; // done is done\n\t\t\tbackToChoose();\n\t\t},\n\n\t\tretry(): void {\n\t\t\tif (m.snapshot.step !== 'error') return;\n\t\t\tbackToChoose();\n\t\t}\n\t};\n}\n","// shareFlow: the \"who can see this\" panel as a headless machine over an\n// injected ShareEngine - how links travel (Drive public link, relay,\n// mailbox) is the app's business. The flow owns what every such panel gets\n// wrong first: a transient listing failure never blanks it (last-known\n// links stay, flagged stale - a member who saw an empty panel once will\n// revoke something in panic); one operation at a time, so a double-click\n// cannot create two links; removals leave the snapshot only after the\n// engine confirmed them, never optimistically; removing yourself is refused\n// up front (leaving is its own journey, not a row action). Every leg runs\n// under a deadline and reports the stable { code, labelKey, message } shape.\n\nimport type { StoreError } from '../persistence/store';\nimport { makeMachine, toStoreError, withDeadline, type FlowStore } from './machine';\n\nexport type ShareLevel = 'read' | 'write';\n\nexport interface ShareLinkInfo {\n\t/** Engine-scoped stable id (revoke keys on it). */\n\tid: string;\n\t/** The invitation URL to hand out. */\n\turl: string;\n\tlevel: ShareLevel;\n}\n\nexport interface ShareMemberInfo {\n\t/** Engine-scoped stable id (removeMember keys on it). */\n\tid: string;\n\tlabel?: string;\n\t/** This row is the local user. */\n\tself?: boolean;\n\t/** This row owns the share (the app may render a badge, the flow does not care). */\n\towner?: boolean;\n}\n\n/** The app-side port the flow drives. Implementations wrap whatever transport\n * the app shares through; every method may throw (SelfstoreError preferred,\n * anything is normalized). */\nexport interface ShareEngine {\n\t/** The current truth: active links and members. */\n\tlist(): Promise<{ links: ShareLinkInfo[]; members: ShareMemberInfo[] }>;\n\tcreateLink(opts: { level: ShareLevel }): Promise<ShareLinkInfo>;\n\trevokeLink(id: string): Promise<void>;\n\t/** Optional: engines without member management simply omit it. */\n\tremoveMember?(id: string): Promise<void>;\n\t/** Optional: END the whole share in one gesture - every link dead, every\n\t * member out. Engines whose transport has such a move (an invitation to\n\t * kill, a file to re-privatize) expose it; the widget renders the action\n\t * only then. */\n\trevokeAll?(): Promise<void>;\n}\n\nexport type ShareBusy = 'refresh' | 'create' | 'revoke' | 'remove' | 'revoke-all' | null;\n\nexport interface ShareSnapshot {\n\tlinks: readonly ShareLinkInfo[];\n\tmembers: readonly ShareMemberInfo[];\n\t/** The operation in flight, or null. */\n\tbusy: ShareBusy;\n\t/** The last refresh failed: the lists shown are the last-known good ones. */\n\tstale: boolean;\n\t/** The last operation's problem (the lists are never cleared by an error). */\n\terror: StoreError | null;\n\t/** True once the engine offers removeMember (render the row action at all). */\n\tcanRemoveMembers: boolean;\n\t/** True once the engine offers revokeAll (render the end-the-share action). */\n\tcanRevokeAll: boolean;\n}\n\nexport interface ShareFlow extends FlowStore<ShareSnapshot> {\n\t/** Re-list from the engine. Failure keeps the last-known lists (stale). */\n\trefresh(): Promise<void>;\n\t/** Create an invitation link. Resolves to it, or null when refused (an\n\t * operation was already in flight). */\n\tcreateLink(opts: { level: ShareLevel }): Promise<ShareLinkInfo | null>;\n\t/** Revoke a link. The link leaves the snapshot only once the engine\n\t * confirmed. Resolves to whether it did. */\n\trevokeLink(id: string): Promise<boolean>;\n\t/** Remove a member (never yourself - refused up front). Resolves to whether\n\t * the engine confirmed. */\n\tremoveMember(id: string): Promise<boolean>;\n\t/** End the whole share: every link and member leaves the snapshot only\n\t * once the engine confirmed. Resolves to whether it did. */\n\trevokeAll(): Promise<boolean>;\n}\n\nconst DEFAULT_DEADLINE_MS = 30_000;\n\nexport function shareFlow(engine: ShareEngine, options: { deadlineMs?: number } = {}): ShareFlow {\n\tconst deadlineMs = options.deadlineMs ?? DEFAULT_DEADLINE_MS;\n\n\tconst m = makeMachine<ShareSnapshot>({\n\t\tlinks: [],\n\t\tmembers: [],\n\t\tbusy: 'refresh',\n\t\tstale: false,\n\t\terror: null,\n\t\tcanRemoveMembers: typeof engine.removeMember === 'function',\n\t\tcanRevokeAll: typeof engine.revokeAll === 'function'\n\t});\n\n\t/** Serialize the operations: one at a time, the others answer \"refused\". */\n\tfunction begin(op: Exclude<ShareBusy, null>): boolean {\n\t\tif (m.snapshot.busy !== null && !(op === 'refresh' && m.snapshot.busy === 'refresh')) {\n\t\t\treturn false;\n\t\t}\n\t\tm.set({ busy: op });\n\t\treturn true;\n\t}\n\n\tasync function doRefresh(): Promise<void> {\n\t\ttry {\n\t\t\tconst { links, members } = await withDeadline(engine.list(), deadlineMs, 'The share listing');\n\t\t\tm.set({ links, members, busy: null, stale: false, error: null });\n\t\t} catch (e) {\n\t\t\t// Keep the last-known lists: a hiccup must never paint the share as\n\t\t\t// gone. Just say the view may be behind.\n\t\t\tm.set({ busy: null, stale: true, error: toStoreError(e) });\n\t\t}\n\t}\n\n\tconst flow: ShareFlow = {\n\t\tget snapshot() {\n\t\t\treturn m.snapshot;\n\t\t},\n\t\tsubscribe: m.subscribe,\n\n\t\tasync refresh(): Promise<void> {\n\t\t\tif (m.snapshot.busy !== null && m.snapshot.busy !== 'refresh') return;\n\t\t\tm.set({ busy: 'refresh' });\n\t\t\tawait doRefresh();\n\t\t},\n\n\t\tasync createLink(opts): Promise<ShareLinkInfo | null> {\n\t\t\tif (!begin('create')) return null;\n\t\t\ttry {\n\t\t\t\tconst link = await withDeadline(engine.createLink(opts), deadlineMs, 'Creating the link');\n\t\t\t\tm.set({ links: [...m.snapshot.links, link], busy: null, error: null });\n\t\t\t\treturn link;\n\t\t\t} catch (e) {\n\t\t\t\tm.set({ busy: null, error: toStoreError(e) });\n\t\t\t\treturn null;\n\t\t\t}\n\t\t},\n\n\t\tasync revokeLink(id: string): Promise<boolean> {\n\t\t\tif (!begin('revoke')) return false;\n\t\t\ttry {\n\t\t\t\tawait withDeadline(engine.revokeLink(id), deadlineMs, 'Revoking the link');\n\t\t\t\t// Confirmed: NOW it leaves the snapshot.\n\t\t\t\tm.set({ links: m.snapshot.links.filter((l) => l.id !== id), busy: null, error: null });\n\t\t\t\treturn true;\n\t\t\t} catch (e) {\n\t\t\t\tm.set({ busy: null, error: toStoreError(e) });\n\t\t\t\treturn false;\n\t\t\t}\n\t\t},\n\n\t\tasync removeMember(id: string): Promise<boolean> {\n\t\t\tconst remove = engine.removeMember?.bind(engine);\n\t\t\tif (!remove) {\n\t\t\t\tthrow new TypeError('shareFlow: this engine does not manage members (no removeMember).');\n\t\t\t}\n\t\t\tconst row = m.snapshot.members.find((mb) => mb.id === id);\n\t\t\tif (row?.self) {\n\t\t\t\tthrow new TypeError(\n\t\t\t\t\t'shareFlow: refusing to remove yourself - leaving a share is its own journey, not a row action.'\n\t\t\t\t);\n\t\t\t}\n\t\t\tif (!begin('remove')) return false;\n\t\t\ttry {\n\t\t\t\tawait withDeadline(remove(id), deadlineMs, 'Removing the member');\n\t\t\t\tm.set({\n\t\t\t\t\tmembers: m.snapshot.members.filter((mb) => mb.id !== id),\n\t\t\t\t\tbusy: null,\n\t\t\t\t\terror: null\n\t\t\t\t});\n\t\t\t\treturn true;\n\t\t\t} catch (e) {\n\t\t\t\tm.set({ busy: null, error: toStoreError(e) });\n\t\t\t\treturn false;\n\t\t\t}\n\t\t},\n\n\t\tasync revokeAll(): Promise<boolean> {\n\t\t\tconst revoke = engine.revokeAll?.bind(engine);\n\t\t\tif (!revoke) {\n\t\t\t\tthrow new TypeError('shareFlow: this engine does not end the share (no revokeAll).');\n\t\t\t}\n\t\t\tif (!begin('revoke-all')) return false;\n\t\t\ttry {\n\t\t\t\tawait withDeadline(revoke(), deadlineMs, 'Ending the share');\n\t\t\t\t// Confirmed: the share is over, every link and member with it.\n\t\t\t\tm.set({ links: [], members: [], busy: null, error: null });\n\t\t\t\treturn true;\n\t\t\t} catch (e) {\n\t\t\t\tm.set({ busy: null, error: toStoreError(e) });\n\t\t\t\treturn false;\n\t\t\t}\n\t\t}\n\t};\n\n\t// The panel opens on the truth: list right away (failures land as stale).\n\tvoid doRefresh();\n\n\treturn flow;\n}\n","// joinFlow: opening an invitation link, as a headless machine over an\n// injected JoinEngine. Preview first, join on an explicit yes. The flow\n// holds the link itself, so a re-render, a login roundtrip or an account\n// switch cannot lose the invitation out from under the user. Joining is\n// single-flight. Mismatch (\"this device already follows another share\") and\n// no-invite (\"spent, or not for this account\") are outcomes with their own\n// steps, not generic errors - each has a different way out. Switching\n// accounts re-previews under the new account. A cancelled or superseded leg\n// cannot resurrect the flow, and every network leg runs under a deadline.\n\nimport type { StoreError } from '../persistence/store';\nimport { makeMachine, toStoreError, withDeadline, type FlowStore } from './machine';\nimport type { ShareLevel } from './share';\n\n/** What the user is about to join - enough to render an honest question. */\nexport interface JoinPreview {\n\t/** The share's display name, when the link carries one. */\n\tlabel?: string;\n\t/** Who shared it (a name, an email - whatever the engine knows). */\n\tfrom?: string;\n\tlevel?: ShareLevel;\n}\n\n/** How joining ended, other than plain failure:\n * 'joined' - this device now follows the share.\n * 'mismatch' - this device already follows a DIFFERENT share: joining a\n * second one is refused (leave the first, or use another\n * device/profile).\n * 'no-invite' - the invitation is spent, expired, or aimed at another\n * account (switching accounts may be the fix). */\nexport type JoinOutcome = 'joined' | 'mismatch' | 'no-invite';\n\n/** The app-side port: how a link is read and honoured on this transport. */\nexport interface JoinEngine {\n\t/** Read-only look at the invitation. Must change NOTHING. */\n\tpreview(link: string): Promise<JoinPreview>;\n\t/** Consume the invitation and wire the share. Resolve null when the user\n\t * backed out of a popup the engine had to open (an account chooser, a\n\t * consent screen): the flow returns to 'ready' silently - backing out of\n\t * a human gesture is not an error, here as everywhere in these flows. */\n\tjoin(link: string): Promise<JoinOutcome | null>;\n\t/** Optional: re-run the account chooser (the invite may belong to another\n\t * account). The flow re-previews after it. */\n\tswitchAccount?(): Promise<void>;\n}\n\nexport type JoinStep =\n\t'previewing' | 'ready' | 'joining' | 'joined' | 'mismatch' | 'no-invite' | 'error';\n\nexport interface JoinSnapshot {\n\t/** The invitation this flow was opened with - held here so the UI can\n\t * always find it again. */\n\tlink: string;\n\tstep: JoinStep;\n\t/** Set once previewed (kept through errors, so retry re-renders context). */\n\tpreview: JoinPreview | null;\n\t/** The engine offers an account switch. */\n\tcanSwitchAccount: boolean;\n\tbusy: boolean;\n\t/** Set when step === 'error': show `labelKey`, log `message`. */\n\terror: StoreError | null;\n}\n\nexport interface JoinFlow extends FlowStore<JoinSnapshot> {\n\t/** Say yes: consume the invitation. Only from 'ready'. */\n\taccept(): void;\n\t/** Re-run the account chooser, then preview again under the new account.\n\t * Available from 'ready', 'no-invite' and 'error' when the engine offers it. */\n\tswitchAccount(): void;\n\t/** From 'error': preview again and resume the journey. */\n\tretry(): void;\n}\n\nconst DEFAULT_DEADLINE_MS = 30_000;\n\nexport function joinFlow(\n\tlink: string,\n\tengine: JoinEngine,\n\toptions: { deadlineMs?: number } = {}\n): JoinFlow {\n\tconst deadlineMs = options.deadlineMs ?? DEFAULT_DEADLINE_MS;\n\n\tconst m = makeMachine<JoinSnapshot>({\n\t\tlink,\n\t\tstep: 'previewing',\n\t\tpreview: null,\n\t\tcanSwitchAccount: typeof engine.switchAccount === 'function',\n\t\tbusy: true,\n\t\terror: null\n\t});\n\n\tlet generation = 0;\n\n\tfunction preview(): void {\n\t\tconst gen = ++generation;\n\t\tm.set({ step: 'previewing', busy: true, error: null });\n\t\tvoid withDeadline(engine.preview(link), deadlineMs, 'The invitation')\n\t\t\t.then((p) => {\n\t\t\t\tif (gen !== generation) return;\n\t\t\t\tm.set({ step: 'ready', preview: p, busy: false });\n\t\t\t})\n\t\t\t.catch((e) => {\n\t\t\t\tif (gen !== generation) return;\n\t\t\t\tm.set({ step: 'error', busy: false, error: toStoreError(e) });\n\t\t\t});\n\t}\n\n\tpreview();\n\n\treturn {\n\t\tget snapshot() {\n\t\t\treturn m.snapshot;\n\t\t},\n\t\tsubscribe: m.subscribe,\n\n\t\taccept(): void {\n\t\t\tconst { step, busy } = m.snapshot;\n\t\t\tif (busy || step !== 'ready') return; // single-flight: one join per yes\n\t\t\tconst gen = ++generation;\n\t\t\tm.set({ step: 'joining', busy: true, error: null });\n\t\t\tvoid withDeadline(engine.join(link), deadlineMs, 'Joining')\n\t\t\t\t.then((outcome) => {\n\t\t\t\t\tif (gen !== generation) return;\n\t\t\t\t\tif (outcome === null) {\n\t\t\t\t\t\t// The user backed out of the engine's popup: the question is\n\t\t\t\t\t\t// still live, the preview still good - just ask again.\n\t\t\t\t\t\tm.set({ step: 'ready', busy: false });\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tm.set({ step: outcome, busy: false });\n\t\t\t\t})\n\t\t\t\t.catch((e) => {\n\t\t\t\t\tif (gen !== generation) return;\n\t\t\t\t\tm.set({ step: 'error', busy: false, error: toStoreError(e) });\n\t\t\t\t});\n\t\t},\n\n\t\tswitchAccount(): void {\n\t\t\tconst switcher = engine.switchAccount?.bind(engine);\n\t\t\tif (!switcher) return;\n\t\t\tconst { step, busy } = m.snapshot;\n\t\t\tif (busy || (step !== 'ready' && step !== 'no-invite' && step !== 'error')) return;\n\t\t\tconst gen = ++generation;\n\t\t\tm.set({ busy: true, error: null });\n\t\t\tvoid switcher()\n\t\t\t\t.then(() => {\n\t\t\t\t\tif (gen !== generation) return;\n\t\t\t\t\tpreview(); // the other account may hold the right invite: look again\n\t\t\t\t})\n\t\t\t\t.catch((e) => {\n\t\t\t\t\tif (gen !== generation) return;\n\t\t\t\t\tm.set({ step: 'error', busy: false, error: toStoreError(e) });\n\t\t\t\t});\n\t\t},\n\n\t\tretry(): void {\n\t\t\tif (m.snapshot.step !== 'error') return;\n\t\t\tpreview();\n\t\t}\n\t};\n}\n"]}