selfstore 1.8.23 → 1.8.25

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 (86) hide show
  1. package/README.md +66 -0
  2. package/dist/advanced.d.ts +7 -7
  3. package/dist/advanced.js +15 -14
  4. package/dist/advanced.js.map +1 -1
  5. package/dist/backups.d.ts +4 -4
  6. package/dist/backups.js +9 -8
  7. package/dist/{cache-lock-5RNG6CIK.js → cache-lock-VVAB3CDB.js} +5 -4
  8. package/dist/cache-lock-VVAB3CDB.js.map +1 -0
  9. package/dist/{chunk-JTNFZV2B.js → chunk-25U6AKIX.js} +4 -4
  10. package/dist/{chunk-JTNFZV2B.js.map → chunk-25U6AKIX.js.map} +1 -1
  11. package/dist/{chunk-3KAAPYPO.js → chunk-2BNTCXQN.js} +3 -3
  12. package/dist/{chunk-3KAAPYPO.js.map → chunk-2BNTCXQN.js.map} +1 -1
  13. package/dist/{chunk-LYO36VYB.js → chunk-2PIYIIHY.js} +8 -8
  14. package/dist/{chunk-LYO36VYB.js.map → chunk-2PIYIIHY.js.map} +1 -1
  15. package/dist/{chunk-GW5CYP4C.js → chunk-2TIRUEXR.js} +3 -3
  16. package/dist/{chunk-GW5CYP4C.js.map → chunk-2TIRUEXR.js.map} +1 -1
  17. package/dist/{chunk-EBCQ7ZG7.js → chunk-5DAPYC2K.js} +4 -4
  18. package/dist/{chunk-EBCQ7ZG7.js.map → chunk-5DAPYC2K.js.map} +1 -1
  19. package/dist/{chunk-RREJ2MFN.js → chunk-5UYPKCDB.js} +5 -17
  20. package/dist/chunk-5UYPKCDB.js.map +1 -0
  21. package/dist/{chunk-DJZ5S3FI.js → chunk-AZWS7YC3.js} +21 -13
  22. package/dist/chunk-AZWS7YC3.js.map +1 -0
  23. package/dist/{chunk-XMC67RNM.js → chunk-CSYEIXNY.js} +2 -2
  24. package/dist/{chunk-XMC67RNM.js.map → chunk-CSYEIXNY.js.map} +1 -1
  25. package/dist/{chunk-OY2A5DCB.js → chunk-F4ZZEJ5H.js} +4 -4
  26. package/dist/{chunk-OY2A5DCB.js.map → chunk-F4ZZEJ5H.js.map} +1 -1
  27. package/dist/{chunk-3BWBBGFW.js → chunk-FQ5O4PHK.js} +7 -7
  28. package/dist/{chunk-3BWBBGFW.js.map → chunk-FQ5O4PHK.js.map} +1 -1
  29. package/dist/{chunk-JZHWAX34.js → chunk-GHSHIDF3.js} +4 -4
  30. package/dist/{chunk-JZHWAX34.js.map → chunk-GHSHIDF3.js.map} +1 -1
  31. package/dist/{chunk-23UAUYSW.js → chunk-GSZSZGSD.js} +3 -3
  32. package/dist/{chunk-23UAUYSW.js.map → chunk-GSZSZGSD.js.map} +1 -1
  33. package/dist/{chunk-LUNYYH6D.js → chunk-IDW3WQKF.js} +6 -6
  34. package/dist/{chunk-LUNYYH6D.js.map → chunk-IDW3WQKF.js.map} +1 -1
  35. package/dist/{chunk-757ZOSUI.js → chunk-LF3CC2P3.js} +3 -3
  36. package/dist/{chunk-757ZOSUI.js.map → chunk-LF3CC2P3.js.map} +1 -1
  37. package/dist/{chunk-AUH35AAQ.js → chunk-LQS7AMOD.js} +3 -3
  38. package/dist/{chunk-AUH35AAQ.js.map → chunk-LQS7AMOD.js.map} +1 -1
  39. package/dist/{chunk-HHPJPEEG.js → chunk-RVOBONZF.js} +7 -6
  40. package/dist/chunk-RVOBONZF.js.map +1 -0
  41. package/dist/chunk-SJ3PKQPR.js +10 -0
  42. package/dist/chunk-SJ3PKQPR.js.map +1 -0
  43. package/dist/chunk-WXI2NAPA.js +16 -0
  44. package/dist/chunk-WXI2NAPA.js.map +1 -0
  45. package/dist/{connect-Bob2mofK.d.ts → connect-DoB_DN__.d.ts} +3 -3
  46. package/dist/{drive-86CHuTij.d.ts → drive--mndByvF.d.ts} +1 -1
  47. package/dist/flows.d.ts +5 -5
  48. package/dist/flows.js +12 -11
  49. package/dist/flows.js.map +1 -1
  50. package/dist/groups.js +5 -4
  51. package/dist/groups.js.map +1 -1
  52. package/dist/households.d.ts +1 -1
  53. package/dist/index.d.ts +11 -266
  54. package/dist/index.js +21 -24
  55. package/dist/index.js.map +1 -1
  56. package/dist/{local-8H61XxbL.d.ts → local-ClXq1N2M.d.ts} +14 -3
  57. package/dist/local-GGAMRY23.js +8 -0
  58. package/dist/{local-XET4G32B.js.map → local-GGAMRY23.js.map} +1 -1
  59. package/dist/manager-drive-2IUE4WQ2.js +11 -0
  60. package/dist/{manager-drive-YLSLQ63O.js.map → manager-drive-2IUE4WQ2.js.map} +1 -1
  61. package/dist/{manager-Di6lNE4E.d.ts → manager-yCM0M0FL.d.ts} +1 -1
  62. package/dist/proof.d.ts +164 -0
  63. package/dist/proof.js +360 -0
  64. package/dist/proof.js.map +1 -0
  65. package/dist/s3-ABKUTTXE.js +12 -0
  66. package/dist/s3-ABKUTTXE.js.map +1 -0
  67. package/dist/{s3-BsbVn3fw.d.ts → s3-dG1SCnV1.d.ts} +1 -1
  68. package/dist/simple-GdtB5IrG.d.ts +267 -0
  69. package/dist/{store-CIbiNoiE.d.ts → store-CKGrIFQU.d.ts} +2 -2
  70. package/dist/sync.js +2 -2
  71. package/dist/webdav-5SHNR7CL.js +10 -0
  72. package/dist/{webdav-EEQKX5TZ.js.map → webdav-5SHNR7CL.js.map} +1 -1
  73. package/dist/widgets.d.ts +5 -5
  74. package/dist/widgets.js +12 -11
  75. package/dist/widgets.js.map +1 -1
  76. package/llms.txt +19 -2
  77. package/package.json +5 -1
  78. package/dist/cache-lock-5RNG6CIK.js.map +0 -1
  79. package/dist/chunk-DJZ5S3FI.js.map +0 -1
  80. package/dist/chunk-HHPJPEEG.js.map +0 -1
  81. package/dist/chunk-RREJ2MFN.js.map +0 -1
  82. package/dist/local-XET4G32B.js +0 -7
  83. package/dist/manager-drive-YLSLQ63O.js +0 -10
  84. package/dist/s3-43RWNCV3.js +0 -11
  85. package/dist/s3-43RWNCV3.js.map +0 -1
  86. package/dist/webdav-EEQKX5TZ.js +0 -9
package/README.md CHANGED
@@ -121,6 +121,71 @@ the copies meet, and folding them is the entire read path.
121
121
  `examples/yjs-document.ts` is a complete, typechecked integration - real
122
122
  concurrent-edit merging, still no server.
123
123
 
124
+ ## Proving a document existed, without handing it over
125
+
126
+ `selfstore/proof` dates a document by its SHA-256 alone. The bytes are hashed
127
+ on the device, only the digest goes to a notary, and the receipt it returns is
128
+ kept next to the file, in the same store and the same backup:
129
+
130
+ ```ts
131
+ import { prove, verify, rfc3161Notary, PROOFS } from 'selfstore/proof';
132
+
133
+ const notary = rfc3161Notary({ url: 'https://your-tsa.example/tsr' });
134
+ const file = await store.putFile({ bytes, name: 'inventory.pdf', mime: 'application/pdf' });
135
+
136
+ const proof = await prove(bytes, { notary, label: 'inventory', store, file });
137
+ store.get(PROOFS, file); // the receipt, next to its file
138
+
139
+ const { matches } = await verify(bytes, proof); // false the moment one byte moves
140
+ ```
141
+
142
+ A notary is an interface, so any service that dates digests plugs in:
143
+
144
+ ```ts
145
+ interface Notary {
146
+ readonly name: string;
147
+ deposit(hash: string, label?: string): Promise<Receipt>; // a receipt now
148
+ complete(receipt: Receipt): Promise<Receipt>; // what it adds later
149
+ }
150
+ ```
151
+
152
+ `deposit` sees the digest and the label, never the bytes: keep the label a
153
+ type (`inventory`, `invoice`), never a name or a piece of the content. A
154
+ notary that anchors its digests later - in a daily tree, in a public ledger -
155
+ answers `complete: false` and fills the receipt in on `completeProof()` the
156
+ next day. A receipt that answers for another digest, or in another notary's
157
+ name, is refused (`NOTARY_REFUSED`); one that cannot be reached is
158
+ `NOTARY_UNAVAILABLE`.
159
+
160
+ `rfc3161Notary` is the first adapter, over any RFC 3161 time-stamping
161
+ authority. The authority signs the digest and its own time in one token, so
162
+ the receipt is complete on deposit; the token travels as `evidence.tsr`
163
+ (base64), `timestampReplyOf(receipt)` gives back the `.tsr` bytes, and
164
+ `openssl ts -verify -data <file> -in <file>.tsr -CAfile <authority CA>` checks
165
+ it without this library. Most public authorities send no CORS headers: in a
166
+ browser, point `url` at a relay you run, or pass your own `fetch`.
167
+
168
+ `digestJournalNotary` is the second, over a public digest journal: a service
169
+ that writes each deposited SHA-256 as one line of a public file, folds the
170
+ file into a Merkle tree and anchors its root in Bitcoin with OpenTimestamps.
171
+ The deposit answers at once with the journal's own receipt
172
+ (`evidence.receipt`, the JSON its verifier reads) and `complete: false`; a
173
+ later `completeProof()` finds the first tour covering the line and, once that
174
+ tour is in Bitcoin, adds `tour`, `root`, `block`, the `.ots` proof and, when
175
+ the journal stamped that root under RFC 3161, the `.tsr` (both base64). The
176
+ protocol it speaks is at the top of `src/proof/journal.ts`.
177
+
178
+ ```ts
179
+ const notary = digestJournalNotary({ url: 'https://journal.example' });
180
+ const proof = await prove(bytes, { notary, label: 'inventory', store, file });
181
+ // the next day, or at the next launch
182
+ const done = await completeProof(proof, { notary, store });
183
+ ```
184
+
185
+ `verify` proves the bytes are the ones the receipt names. That the receipt is
186
+ genuine is what the notary's evidence is for, checked with the notary's own
187
+ verifier.
188
+
124
189
  ## Status and errors are headless
125
190
 
126
191
  The store never ships UI copy. It exposes a status descriptor
@@ -196,6 +261,7 @@ too, as subpaths of the same install:
196
261
  | `selfstore/widgets` | the same journeys as drop-in web components (`<selfstore-storage>` for the whole journey in one tag - it opens on a **full-screen first-run gate**, so reach for `<selfstore-destination>` or `<selfstore-status>` instead on a page that must work before the user answers; also `<selfstore-connect>`, `<selfstore-gate>`, `<selfstore-account>`, `<selfstore-share>`, `<selfstore-join>` on their own) - framework-free, themable via CSS custom properties and `::part()`, reworded/localized via a `labels` map | you want the screens ready-made and your app's look on top |
197
262
  | `selfstore/advanced` | the pull-model store (`createLocalStore`), custom `BackupTarget`s, caches, headless status derivation, functional codec | your state lives in its own reactive model (Svelte runes, Redux), or you are writing a destination (S3, your KV...) |
198
263
  | `selfstore/groups` **(experimental)** | passwordless group encryption: per-member identities, sealed envelopes, signed membership manifests | several PEOPLE share one encrypted store without a shared password ([PEERS.md](./PEERS.md)) - see [Stability](#stability) for what experimental costs you |
264
+ | `selfstore/proof` | proof of existence: `prove()` hashes on the device and deposits the digest with a notary, `verify()` re-checks, `rfc3161Notary` and `digestJournalNotary` are ready adapters | a user must be able to show a document existed on a date, without the document leaving their device |
199
265
  | `selfstore/sync` | the bare merge engine (HLC + per-collection strategies, no CRDT runtime) | you only want the algorithm inside your own persistence ([SYNC docs in SPEC.md](./SPEC.md)) |
200
266
 
201
267
  `store.advanced` on the simple store IS the `selfstore/advanced` store - same
@@ -1,12 +1,12 @@
1
- import { B as BackupTarget } from './store-CIbiNoiE.js';
2
- export { A as AuthExpiredError, a as BuiltinTargetKind, E as EncodeOptions, H as Header, K as KdfParams, L as LocalStore, b as LocalStoreOptions, c as LocalStoreState, M as MirrorState, d as Mode, e as PasswordCheck, f as PasswordPolicy, g as PasswordRequirement, P as PeerSource, h as PeerState, R as RESERVED_COLLECTION_PREFIX, i as RESERVED_STORE_MODES, j as RecipientStanza, k as ReplicaState, l as SelfstoreError, m as SelfstoreErrorCode, n as Severity, o as Snapshot, p as SnapshotFile, q as StatusAction, r as StatusDescriptor, s as StatusInput, t as StorageState, S as StoreError, u as StoreGroupConfig, v as SyncJournalEntry, w as SyncSource, T as TargetKind, x as checkPasswordPolicy, y as createLocalStore, z as deriveStatus, C as errorLabelKey, D as isAuthExpired, F as isReservedStoreMode, G as isSelfstoreError } from './store-CIbiNoiE.js';
1
+ import { B as BackupTarget } from './store-CKGrIFQU.js';
2
+ export { A as AuthExpiredError, f as BuiltinTargetKind, E as EncodeOptions, H as Header, K as KdfParams, L as LocalStore, g as LocalStoreOptions, e as LocalStoreState, M as MirrorState, h as Mode, i as PasswordCheck, b as PasswordPolicy, j as PasswordRequirement, P as PeerSource, k as PeerState, R as RESERVED_COLLECTION_PREFIX, l as RESERVED_STORE_MODES, m as RecipientStanza, n as ReplicaState, o as SelfstoreError, p as SelfstoreErrorCode, q as Severity, a as Snapshot, c as SnapshotFile, r as StatusAction, d as StatusDescriptor, s as StatusInput, t as StorageState, S as StoreError, u as StoreGroupConfig, v as SyncJournalEntry, w as SyncSource, T as TargetKind, x as checkPasswordPolicy, y as createLocalStore, z as deriveStatus, C as errorLabelKey, D as isAuthExpired, F as isReservedStoreMode, G as isSelfstoreError } from './store-CKGrIFQU.js';
3
3
  import { K as KV } from './cache-ilsK3Tki.js';
4
4
  export { C as CachedFile, L as LocalCache, e as LockableCache, i as indexedDbCache, f as isLockableCache, m as memoryCache } from './cache-ilsK3Tki.js';
5
- export { G as GisDriveAuthOptions, a as GisTokenPersistence, d as datedName, e as exportSnapshot, g as gisDriveAuth, i as importSnapshot, b as inspect, c as isEncrypted, p as pickFromDisk, s as saveToDisk } from './local-8H61XxbL.js';
6
- import { c as connect$2, f as fromSession$2, a as connect$3, b as fromSession$3, p as peer } from './s3-BsbVn3fw.js';
7
- export { S as S3Config, d as S3ConnectOptions, W as WebdavConfig, e as WebdavConnectOptions, g as WebdavPeerOptions } from './s3-BsbVn3fw.js';
8
- import { c as connect$1, f as fromSession$1, p as preview, b as adopt, d as findOrCreateOwnFile, l as listBackups, e as createBackup, g as deleteBackup, r as renameBackup, h as account, i as createCompanion, s as share, u as unshare, o as owner, j as secondary } from './drive-86CHuTij.js';
9
- export { k as DriveAccount, a as DriveAuth, m as DriveBackupInfo, D as DriveOptions } from './drive-86CHuTij.js';
5
+ export { G as GisDriveAuthOptions, a as GisTokenPersistence, d as datedName, e as exportSnapshot, g as gisDriveAuth, i as importSnapshot, b as inspect, c as isEncrypted, p as pickFromDisk, f as preloadGoogleIdentity, s as saveToDisk } from './local-ClXq1N2M.js';
6
+ import { c as connect$2, f as fromSession$2, a as connect$3, b as fromSession$3, p as peer } from './s3-dG1SCnV1.js';
7
+ export { S as S3Config, d as S3ConnectOptions, W as WebdavConfig, e as WebdavConnectOptions, g as WebdavPeerOptions } from './s3-dG1SCnV1.js';
8
+ import { c as connect$1, f as fromSession$1, p as preview, b as adopt, d as findOrCreateOwnFile, l as listBackups, e as createBackup, g as deleteBackup, r as renameBackup, h as account, i as createCompanion, s as share, u as unshare, o as owner, j as secondary } from './drive--mndByvF.js';
9
+ export { k as DriveAccount, a as DriveAuth, m as DriveBackupInfo, D as DriveOptions } from './drive--mndByvF.js';
10
10
  import './sync.js';
11
11
 
12
12
  /**
package/dist/advanced.js CHANGED
@@ -1,18 +1,19 @@
1
- import { peer, fromSession as fromSession$3, connect as connect$3 } from './chunk-JZHWAX34.js';
2
- import { fromSession as fromSession$2, connect as connect$2 } from './chunk-LUNYYH6D.js';
3
- export { RESERVED_STORE_MODES, createLocalStore, deriveStatus, gisDriveAuth, indexedDbCache, isLockableCache, isReservedStoreMode, memoryCache } from './chunk-DJZ5S3FI.js';
4
- export { datedName, pickFromDisk, saveToDisk } from './chunk-AUH35AAQ.js';
5
- import './chunk-QSLHEE2L.js';
6
- import { openExisting, isOpenSupported, isSupported, fromSession as fromSession$1, connect as connect$1 } from './chunk-GW5CYP4C.js';
7
- import { FILE_ID_KEY, secondary, owner, unshare, share, createCompanion, account, renameBackup, deleteBackup, createBackup, listBackups, findOrCreateOwnFile, adopt, preview, fromSession, connect } from './chunk-JTNFZV2B.js';
8
- import './chunk-757ZOSUI.js';
9
- export { RESERVED_COLLECTION_PREFIX, checkPasswordPolicy, exportSnapshot, importSnapshot, inspect, isEncrypted } from './chunk-HHPJPEEG.js';
10
- import './chunk-QBCU7OOP.js';
11
- import './chunk-OY2A5DCB.js';
12
- import './chunk-RREJ2MFN.js';
13
- import './chunk-23UAUYSW.js';
1
+ import { peer, fromSession as fromSession$3, connect as connect$3 } from './chunk-GHSHIDF3.js';
2
+ import { fromSession as fromSession$2, connect as connect$2 } from './chunk-IDW3WQKF.js';
3
+ export { RESERVED_STORE_MODES, createLocalStore, deriveStatus, gisDriveAuth, indexedDbCache, isLockableCache, isReservedStoreMode, memoryCache, preloadGoogleIdentity } from './chunk-AZWS7YC3.js';
4
+ import './chunk-GSZSZGSD.js';
5
+ export { datedName, pickFromDisk, saveToDisk } from './chunk-LQS7AMOD.js';
14
6
  import './chunk-FJGWTBAL.js';
15
- export { AuthExpiredError, SelfstoreError, errorLabelKey, isAuthExpired, isSelfstoreError } from './chunk-XMC67RNM.js';
7
+ import { openExisting, isOpenSupported, isSupported, fromSession as fromSession$1, connect as connect$1 } from './chunk-2TIRUEXR.js';
8
+ import { FILE_ID_KEY, secondary, owner, unshare, share, createCompanion, account, renameBackup, deleteBackup, createBackup, listBackups, findOrCreateOwnFile, adopt, preview, fromSession, connect } from './chunk-25U6AKIX.js';
9
+ import './chunk-LF3CC2P3.js';
10
+ export { RESERVED_COLLECTION_PREFIX, checkPasswordPolicy, exportSnapshot, importSnapshot, inspect, isEncrypted } from './chunk-RVOBONZF.js';
11
+ import './chunk-QBCU7OOP.js';
12
+ import './chunk-F4ZZEJ5H.js';
13
+ import './chunk-5UYPKCDB.js';
14
+ import './chunk-QSLHEE2L.js';
15
+ import './chunk-WXI2NAPA.js';
16
+ export { AuthExpiredError, SelfstoreError, errorLabelKey, isAuthExpired, isSelfstoreError } from './chunk-CSYEIXNY.js';
16
17
 
17
18
  // src/persistence/installation.ts
18
19
  function isSafari(ua) {
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/persistence/installation.ts","../src/entries/advanced.ts"],"names":["connect","fromSession"],"mappings":";;;;;;;;;;;;;;;;;AAuCA,SAAS,SAAS,EAAA,EAAqB;AACtC,EAAA,OAAO,UAAA,CAAW,IAAA,CAAK,EAAE,CAAA,IAAK,CAAC,uBAAA,CAAwB,IAAA,CAAK,EAAE,CAAA,IAAK,CAAC,eAAA,CAAgB,IAAA,CAAK,EAAE,CAAA;AAC5F;AAEA,SAAS,YAAA,GAAwB;AAChC,EAAA,IAAI,OAAO,MAAA,KAAW,WAAA,EAAa,OAAO,KAAA;AAC1C,EAAA,MAAM,WAAA,GAAc,MAAA,CAAO,UAAA,GAAa,4BAA4B,GAAG,OAAA,KAAY,IAAA;AAEnF,EAAA,MAAM,GAAA,GAAO,UAAmD,UAAA,KAAe,IAAA;AAC/E,EAAA,OAAO,WAAA,IAAe,GAAA;AACvB;AAOO,SAAS,aAAA,GAA+B;AAC9C,EAAA,IAAI,OAAO,SAAA,KAAc,WAAA,IAAe,OAAO,WAAW,WAAA,EAAa;AACtE,IAAA,OAAO,EAAE,IAAA,EAAM,MAAA,EAAQ,SAAA,EAAW,KAAA,EAAM;AAAA,EACzC;AACA,EAAA,MAAM,YAAY,YAAA,EAAa;AAC/B,EAAA,IAAI,CAAC,QAAA,CAAS,SAAA,CAAU,SAAS,KAAK,SAAA,EAAW;AAChD,IAAA,OAAO,EAAE,IAAA,EAAM,MAAA,EAAQ,SAAA,EAAU;AAAA,EAClC;AACA,EAAA,MAAM,QAAQ,kBAAA,CAAmB,IAAA,CAAK,UAAU,SAAS,CAAA,IAAK,UAAU,cAAA,GAAiB,CAAA;AACzF,EAAA,OAAO;AAAA,IACN,IAAA,EAAM,mBAAA;AAAA,IACN,MAAA,EAAQ,QAAQ,wBAAA,GAA2B,iBAAA;AAAA,IAC3C,SAAA,EAAW;AAAA,GACZ;AACD;;;ACmEO,IAAM,UAAA,GAAa;AAAA,EACzB,OAAA,EAASA,SAAAA;AAAA,EACT,WAAA,EAAaC,aAAAA;AAAA,EACb,WAAA;AAAA,EACA,eAAA;AAAA;AAAA;AAAA;AAAA,EAIA;AACD;AAKO,IAAM,YAAA,GAAe;AAAA,EAC3B,OAAA,EAASD,SAAAA;AAAA,EACT,WAAA,EAAaC,aAAAA;AAAA,EACb;AACD;AAMO,IAAM,QAAA,GAAW;AAAA,EACvB,OAAA,EAASD,SAAAA;AAAA,EACT,WAAA,EAAaC;AACd;AAkBO,IAAM,WAAA,GAAc;AAAA,EAC1B,OAAA;AAAA,EACA,WAAA;AAAA,EACA,OAAA;AAAA,EACA,KAAA;AAAA,EACA,mBAAA;AAAA,EACA,WAAA;AAAA,EACA,YAAA;AAAA,EACA,YAAA;AAAA,EACA,YAAA;AAAA,EACA,OAAA;AAAA,EACA,eAAA;AAAA,EACA,KAAA;AAAA,EACA,OAAA;AAAA,EACA,KAAA;AAAA,EACA,SAAA;AAAA,EACA;AACD","file":"advanced.js","sourcesContent":["/**\n * Whether this browser will keep the local store, and what the user can do\n * about it.\n *\n * One browser does not merely inconvenience a local-first app, it DELETES its\n * data: Safari erases script-writable storage after seven days of browser use\n * without interaction with the site. Someone who closes for three weeks comes\n * back to an empty app, and nothing warned them - which is also the moment they\n * notice it last.\n *\n * Adding the app to the Dock or the Home Screen switches its storage to\n * persistent mode, which WebKit exempts from its eviction triggers. WebKit does\n * not write anywhere that this neutralises the inactivity rule by name, so this\n * module reports an advisory, never a guarantee.\n *\n * The library ships no user-facing copy: it names the situation, the host writes\n * the sentence.\n */\n\n/** What stands between this browser and keeping the data. */\nexport type StorageRisk =\n\t/** Nothing known against it: the store survives until the user clears it. */\n\t| 'none'\n\t/** Data is erased when the tab closes (private browsing). Refuse to set up here. */\n\t| 'ephemeral'\n\t/** Erased after a stretch of not opening the app, unless it is installed. */\n\t| 'evicted-when-idle';\n\nexport interface StorageAdvice {\n\trisk: StorageRisk;\n\t/** The gesture that removes the risk, when there is one. Stable key; the\n\t * host owns the wording and the platform-specific instructions. */\n\tremedy?: 'install-to-dock' | 'install-to-home-screen';\n\t/** Already running as an installed app, so the remedy is done. */\n\tinstalled: boolean;\n}\n\n/** Real Safari, not Chrome or Edge on macOS - all three carry \"Safari\" in the\n * user agent, and only Safari carries none of the others' tokens. */\nfunction isSafari(ua: string): boolean {\n\treturn /Safari\\//.test(ua) && !/Chrome|Chromium|Edg\\//.test(ua) && !/OPR\\/|Android/.test(ua);\n}\n\nfunction isStandalone(): boolean {\n\tif (typeof window === 'undefined') return false;\n\tconst displayMode = window.matchMedia?.('(display-mode: standalone)')?.matches === true;\n\t// iOS and iPadOS kept their own flag, older than display-mode.\n\tconst ios = (navigator as Navigator & { standalone?: boolean }).standalone === true;\n\treturn displayMode || ios;\n}\n\n/**\n * What this browser will do with the local store, and the one gesture that\n * changes it. Call it before setting up, and again in whatever screen owns the\n * data: the advice disappears on its own once the remedy is applied.\n */\nexport function storageAdvice(): StorageAdvice {\n\tif (typeof navigator === 'undefined' || typeof window === 'undefined') {\n\t\treturn { risk: 'none', installed: false };\n\t}\n\tconst installed = isStandalone();\n\tif (!isSafari(navigator.userAgent) || installed) {\n\t\treturn { risk: 'none', installed };\n\t}\n\tconst touch = /iPhone|iPad|iPod/.test(navigator.userAgent) || navigator.maxTouchPoints > 1;\n\treturn {\n\t\trisk: 'evicted-when-idle',\n\t\tremedy: touch ? 'install-to-home-screen' : 'install-to-dock',\n\t\tinstalled: false\n\t};\n}\n","/**\n * selfstore/advanced - the pull-model store and the machinery under it.\n *\n * The simple `selfstore()` facade (package root) owns its data. This entry\n * inverts that: with createLocalStore your app owns the data and hands the\n * store a gather/apply pair - use it when state lives in your own reactive\n * model (Svelte runes, Redux, signals) and the facade's collections would be\n * a second copy. Everything here also backs `store.advanced` on the simple\n * store: same machinery, same instance, so the two styles compose.\n *\n * Also here: the durable destinations (disk file, Google Drive, WebDAV) as\n * connectable targets, the BackupTarget contract for writing your own, the\n * caches, the headless status derivation, and the functional codec for the\n * backup file format (the fluent one lives at the package root).\n */\n\n// --- The pull-model store: your app owns the data ---\nexport {\n\tcreateLocalStore,\n\ttype LocalStore,\n\ttype LocalStoreOptions,\n\ttype LocalStoreState,\n\ttype StoreError,\n\ttype Mode,\n\ttype TargetKind,\n\ttype SyncSource,\n\ttype SyncJournalEntry,\n\ttype PeerState,\n\ttype MirrorState,\n\ttype ReplicaState,\n\ttype StoreGroupConfig\n} from '../persistence/store';\n\n// --- On-device caches (the store's working copy) ---\nexport {\n\tindexedDbCache,\n\tmemoryCache,\n\tisLockableCache,\n\ttype LocalCache,\n\ttype LockableCache,\n\ttype CachedFile,\n\ttype KV\n} from '../persistence/cache';\n\n// --- Headless status: map store state to your UI copy ---\nexport {\n\tderiveStatus,\n\ttype StatusDescriptor,\n\ttype StatusInput,\n\ttype Severity,\n\ttype StorageState,\n\ttype StatusAction\n} from '../persistence/status';\n\n// --- Will this browser keep the data, and what can the user do about it ---\nexport { storageAdvice, type StorageAdvice, type StorageRisk } from '../persistence/installation';\n\n// --- The target contract (write your own destination) ---\nexport {\n\tRESERVED_STORE_MODES,\n\tisReservedStoreMode,\n\ttype BackupTarget,\n\ttype BuiltinTargetKind,\n\ttype PeerSource\n} from '../persistence/target';\n\n// --- The backup file format, functional style (fluent lives at the root) ---\nexport {\n\texportSnapshot,\n\timportSnapshot,\n\tinspect,\n\tisEncrypted,\n\tRESERVED_COLLECTION_PREFIX,\n\ttype Snapshot,\n\ttype SnapshotFile,\n\ttype Header,\n\ttype KdfParams,\n\ttype RecipientStanza,\n\ttype EncodeOptions\n} from '../selfstore';\n\n// --- Password strength policy (the store enforces it; the UI previews it) ---\nexport {\n\tcheckPasswordPolicy,\n\ttype PasswordPolicy,\n\ttype PasswordCheck,\n\ttype PasswordRequirement\n} from '../selfstore';\n\n// --- The full error contract (AuthExpiredError is what a custom target or\n// DriveAuth THROWS to signal genuine access loss; everything else it\n// throws is treated as transient and retried) ---\nexport {\n\tSelfstoreError,\n\tAuthExpiredError,\n\tisSelfstoreError,\n\tisAuthExpired,\n\terrorLabelKey,\n\ttype SelfstoreErrorCode\n} from '../selfstore';\n\n// --- Durable destinations (opt-in), each implementing BackupTarget ---\n// Explicit namespace objects: every member is deliberately public, so a future\n// internal helper in a target module can never leak into the contract.\nimport {\n\tconnect as fileConnect,\n\tfromSession as fileFromSession,\n\tisSupported as fileIsSupported,\n\tisOpenSupported as fileIsOpenSupported,\n\topenExisting as fileOpenExisting\n} from '../persistence/targets/file';\nimport {\n\tconnect as webdavConnect,\n\tfromSession as webdavFromSession,\n\tpeer as webdavPeer\n} from '../persistence/targets/webdav';\nimport { connect as s3Connect, fromSession as s3FromSession } from '../persistence/targets/s3';\nimport {\n\tconnect as driveConnect,\n\tfromSession as driveFromSession,\n\tpreview as drivePreview,\n\tadopt as driveAdopt,\n\tfindOrCreateOwnFile as driveFindOrCreateOwnFile,\n\tlistBackups as driveListBackups,\n\tcreateBackup as driveCreateBackup,\n\tdeleteBackup as driveDeleteBackup,\n\trenameBackup as driveRenameBackup,\n\taccount as driveAccount,\n\tcreateCompanion as driveCreateCompanion,\n\tshare as driveShare,\n\tunshare as driveUnshare,\n\towner as driveOwner,\n\tsecondary as driveSecondary,\n\tFILE_ID_KEY as DRIVE_FILE_ID_KEY\n} from '../persistence/targets/drive';\n\n/** Disk-file destination (File System Access, Chromium). */\nexport const fileTarget = {\n\tconnect: fileConnect,\n\tfromSession: fileFromSession,\n\tisSupported: fileIsSupported,\n\tisOpenSupported: fileIsOpenSupported,\n\t/** Let the user point at an EXISTING backup file and adopt it as the\n\t * destination. Gate on isOpenSupported(): unlike creating, this has no\n\t * download fallback. */\n\topenExisting: fileOpenExisting\n};\n\n/** WebDAV destination (Nextcloud, ownCloud, any server the user controls), plus\n * `peer()`: a read-only source over another member's shared WebDAV link, so a\n * group can share read-write without Google Drive (see store.attachPeer). */\nexport const webdavTarget = {\n\tconnect: webdavConnect,\n\tfromSession: webdavFromSession,\n\tpeer: webdavPeer\n};\n\n/** S3-compatible destination (Amazon S3, Cloudflare R2, Backblaze B2, MinIO):\n * the browser signs each request with SigV4 and writes one object on a bucket\n * the user controls, no vendor broker. The bucket only ever holds the same\n * end-to-end-encrypted .selfstore bytes. */\nexport const s3Target = {\n\tconnect: s3Connect,\n\tfromSession: s3FromSession\n};\n\n/** Google Drive destination, plus the shared-file primitives (preview a specific\n * file, adopt it as this device's backup, find-or-create the user's own file)\n * and the backup-management ops (list the app's files with dates, create a\n * named empty backup, rename one, delete one for good) - the raw pieces a \"my\n * backups\" panel builds on. `account()` names WHICH Google account holds them,\n * because a brand name is not an address.\n *\n * The last five are the Drive side of SHARING, on companion files the app\n * creates next to the backup: `createCompanion` mints one, `share` publishes it\n * on a link, `unshare` makes it private again, `owner` says whose Drive it\n * lives on, and `secondary` is a read-write target over it whose lifecycle is\n * its own - detaching a shared copy leaves the user's real backup and their\n * Drive session untouched. That is what a ShareBackend needs from Drive and\n * what every app was rewriting; what stays yours is reading ANOTHER account's\n * file, which the drive.file scope cannot do without a Picker gesture or a\n * relay you host. */\nexport const driveTarget = {\n\tconnect: driveConnect,\n\tfromSession: driveFromSession,\n\tpreview: drivePreview,\n\tadopt: driveAdopt,\n\tfindOrCreateOwnFile: driveFindOrCreateOwnFile,\n\tlistBackups: driveListBackups,\n\tcreateBackup: driveCreateBackup,\n\tdeleteBackup: driveDeleteBackup,\n\trenameBackup: driveRenameBackup,\n\taccount: driveAccount,\n\tcreateCompanion: driveCreateCompanion,\n\tshare: driveShare,\n\tunshare: driveUnshare,\n\towner: driveOwner,\n\tsecondary: driveSecondary,\n\tFILE_ID_KEY: DRIVE_FILE_ID_KEY\n};\n\nexport type { FileConnectOptions } from '../persistence/targets/file';\nexport type {\n\tDriveAuth,\n\tDriveOptions,\n\tDriveBackupInfo,\n\tDriveAccount\n} from '../persistence/targets/drive';\nexport type {\n\tWebdavConfig,\n\tWebdavConnectOptions,\n\tWebdavPeerOptions\n} from '../persistence/targets/webdav';\nexport type { S3Config, S3ConnectOptions } from '../persistence/targets/s3';\nexport {\n\tgisDriveAuth,\n\ttype GisDriveAuthOptions,\n\ttype GisTokenPersistence\n} from '../persistence/targets/drive-auth-gis';\nexport { saveToDisk, pickFromDisk, datedName } from '../selfstore/targets/local';\n"]}
1
+ {"version":3,"sources":["../src/persistence/installation.ts","../src/entries/advanced.ts"],"names":["connect","fromSession"],"mappings":";;;;;;;;;;;;;;;;;;AAuCA,SAAS,SAAS,EAAA,EAAqB;AACtC,EAAA,OAAO,UAAA,CAAW,IAAA,CAAK,EAAE,CAAA,IAAK,CAAC,uBAAA,CAAwB,IAAA,CAAK,EAAE,CAAA,IAAK,CAAC,eAAA,CAAgB,IAAA,CAAK,EAAE,CAAA;AAC5F;AAEA,SAAS,YAAA,GAAwB;AAChC,EAAA,IAAI,OAAO,MAAA,KAAW,WAAA,EAAa,OAAO,KAAA;AAC1C,EAAA,MAAM,WAAA,GAAc,MAAA,CAAO,UAAA,GAAa,4BAA4B,GAAG,OAAA,KAAY,IAAA;AAEnF,EAAA,MAAM,GAAA,GAAO,UAAmD,UAAA,KAAe,IAAA;AAC/E,EAAA,OAAO,WAAA,IAAe,GAAA;AACvB;AAOO,SAAS,aAAA,GAA+B;AAC9C,EAAA,IAAI,OAAO,SAAA,KAAc,WAAA,IAAe,OAAO,WAAW,WAAA,EAAa;AACtE,IAAA,OAAO,EAAE,IAAA,EAAM,MAAA,EAAQ,SAAA,EAAW,KAAA,EAAM;AAAA,EACzC;AACA,EAAA,MAAM,YAAY,YAAA,EAAa;AAC/B,EAAA,IAAI,CAAC,QAAA,CAAS,SAAA,CAAU,SAAS,KAAK,SAAA,EAAW;AAChD,IAAA,OAAO,EAAE,IAAA,EAAM,MAAA,EAAQ,SAAA,EAAU;AAAA,EAClC;AACA,EAAA,MAAM,QAAQ,kBAAA,CAAmB,IAAA,CAAK,UAAU,SAAS,CAAA,IAAK,UAAU,cAAA,GAAiB,CAAA;AACzF,EAAA,OAAO;AAAA,IACN,IAAA,EAAM,mBAAA;AAAA,IACN,MAAA,EAAQ,QAAQ,wBAAA,GAA2B,iBAAA;AAAA,IAC3C,SAAA,EAAW;AAAA,GACZ;AACD;;;ACmEO,IAAM,UAAA,GAAa;AAAA,EACzB,OAAA,EAASA,SAAAA;AAAA,EACT,WAAA,EAAaC,aAAAA;AAAA,EACb,WAAA;AAAA,EACA,eAAA;AAAA;AAAA;AAAA;AAAA,EAIA;AACD;AAKO,IAAM,YAAA,GAAe;AAAA,EAC3B,OAAA,EAASD,SAAAA;AAAA,EACT,WAAA,EAAaC,aAAAA;AAAA,EACb;AACD;AAMO,IAAM,QAAA,GAAW;AAAA,EACvB,OAAA,EAASD,SAAAA;AAAA,EACT,WAAA,EAAaC;AACd;AAkBO,IAAM,WAAA,GAAc;AAAA,EAC1B,OAAA;AAAA,EACA,WAAA;AAAA,EACA,OAAA;AAAA,EACA,KAAA;AAAA,EACA,mBAAA;AAAA,EACA,WAAA;AAAA,EACA,YAAA;AAAA,EACA,YAAA;AAAA,EACA,YAAA;AAAA,EACA,OAAA;AAAA,EACA,eAAA;AAAA,EACA,KAAA;AAAA,EACA,OAAA;AAAA,EACA,KAAA;AAAA,EACA,SAAA;AAAA,EACA;AACD","file":"advanced.js","sourcesContent":["/**\n * Whether this browser will keep the local store, and what the user can do\n * about it.\n *\n * One browser does not merely inconvenience a local-first app, it DELETES its\n * data: Safari erases script-writable storage after seven days of browser use\n * without interaction with the site. Someone who closes for three weeks comes\n * back to an empty app, and nothing warned them - which is also the moment they\n * notice it last.\n *\n * Adding the app to the Dock or the Home Screen switches its storage to\n * persistent mode, which WebKit exempts from its eviction triggers. WebKit does\n * not write anywhere that this neutralises the inactivity rule by name, so this\n * module reports an advisory, never a guarantee.\n *\n * The library ships no user-facing copy: it names the situation, the host writes\n * the sentence.\n */\n\n/** What stands between this browser and keeping the data. */\nexport type StorageRisk =\n\t/** Nothing known against it: the store survives until the user clears it. */\n\t| 'none'\n\t/** Data is erased when the tab closes (private browsing). Refuse to set up here. */\n\t| 'ephemeral'\n\t/** Erased after a stretch of not opening the app, unless it is installed. */\n\t| 'evicted-when-idle';\n\nexport interface StorageAdvice {\n\trisk: StorageRisk;\n\t/** The gesture that removes the risk, when there is one. Stable key; the\n\t * host owns the wording and the platform-specific instructions. */\n\tremedy?: 'install-to-dock' | 'install-to-home-screen';\n\t/** Already running as an installed app, so the remedy is done. */\n\tinstalled: boolean;\n}\n\n/** Real Safari, not Chrome or Edge on macOS - all three carry \"Safari\" in the\n * user agent, and only Safari carries none of the others' tokens. */\nfunction isSafari(ua: string): boolean {\n\treturn /Safari\\//.test(ua) && !/Chrome|Chromium|Edg\\//.test(ua) && !/OPR\\/|Android/.test(ua);\n}\n\nfunction isStandalone(): boolean {\n\tif (typeof window === 'undefined') return false;\n\tconst displayMode = window.matchMedia?.('(display-mode: standalone)')?.matches === true;\n\t// iOS and iPadOS kept their own flag, older than display-mode.\n\tconst ios = (navigator as Navigator & { standalone?: boolean }).standalone === true;\n\treturn displayMode || ios;\n}\n\n/**\n * What this browser will do with the local store, and the one gesture that\n * changes it. Call it before setting up, and again in whatever screen owns the\n * data: the advice disappears on its own once the remedy is applied.\n */\nexport function storageAdvice(): StorageAdvice {\n\tif (typeof navigator === 'undefined' || typeof window === 'undefined') {\n\t\treturn { risk: 'none', installed: false };\n\t}\n\tconst installed = isStandalone();\n\tif (!isSafari(navigator.userAgent) || installed) {\n\t\treturn { risk: 'none', installed };\n\t}\n\tconst touch = /iPhone|iPad|iPod/.test(navigator.userAgent) || navigator.maxTouchPoints > 1;\n\treturn {\n\t\trisk: 'evicted-when-idle',\n\t\tremedy: touch ? 'install-to-home-screen' : 'install-to-dock',\n\t\tinstalled: false\n\t};\n}\n","/**\n * selfstore/advanced - the pull-model store and the machinery under it.\n *\n * The simple `selfstore()` facade (package root) owns its data. This entry\n * inverts that: with createLocalStore your app owns the data and hands the\n * store a gather/apply pair - use it when state lives in your own reactive\n * model (Svelte runes, Redux, signals) and the facade's collections would be\n * a second copy. Everything here also backs `store.advanced` on the simple\n * store: same machinery, same instance, so the two styles compose.\n *\n * Also here: the durable destinations (disk file, Google Drive, WebDAV) as\n * connectable targets, the BackupTarget contract for writing your own, the\n * caches, the headless status derivation, and the functional codec for the\n * backup file format (the fluent one lives at the package root).\n */\n\n// --- The pull-model store: your app owns the data ---\nexport {\n\tcreateLocalStore,\n\ttype LocalStore,\n\ttype LocalStoreOptions,\n\ttype LocalStoreState,\n\ttype StoreError,\n\ttype Mode,\n\ttype TargetKind,\n\ttype SyncSource,\n\ttype SyncJournalEntry,\n\ttype PeerState,\n\ttype MirrorState,\n\ttype ReplicaState,\n\ttype StoreGroupConfig\n} from '../persistence/store';\n\n// --- On-device caches (the store's working copy) ---\nexport {\n\tindexedDbCache,\n\tmemoryCache,\n\tisLockableCache,\n\ttype LocalCache,\n\ttype LockableCache,\n\ttype CachedFile,\n\ttype KV\n} from '../persistence/cache';\n\n// --- Headless status: map store state to your UI copy ---\nexport {\n\tderiveStatus,\n\ttype StatusDescriptor,\n\ttype StatusInput,\n\ttype Severity,\n\ttype StorageState,\n\ttype StatusAction\n} from '../persistence/status';\n\n// --- Will this browser keep the data, and what can the user do about it ---\nexport { storageAdvice, type StorageAdvice, type StorageRisk } from '../persistence/installation';\n\n// --- The target contract (write your own destination) ---\nexport {\n\tRESERVED_STORE_MODES,\n\tisReservedStoreMode,\n\ttype BackupTarget,\n\ttype BuiltinTargetKind,\n\ttype PeerSource\n} from '../persistence/target';\n\n// --- The backup file format, functional style (fluent lives at the root) ---\nexport {\n\texportSnapshot,\n\timportSnapshot,\n\tinspect,\n\tisEncrypted,\n\tRESERVED_COLLECTION_PREFIX,\n\ttype Snapshot,\n\ttype SnapshotFile,\n\ttype Header,\n\ttype KdfParams,\n\ttype RecipientStanza,\n\ttype EncodeOptions\n} from '../selfstore';\n\n// --- Password strength policy (the store enforces it; the UI previews it) ---\nexport {\n\tcheckPasswordPolicy,\n\ttype PasswordPolicy,\n\ttype PasswordCheck,\n\ttype PasswordRequirement\n} from '../selfstore';\n\n// --- The full error contract (AuthExpiredError is what a custom target or\n// DriveAuth THROWS to signal genuine access loss; everything else it\n// throws is treated as transient and retried) ---\nexport {\n\tSelfstoreError,\n\tAuthExpiredError,\n\tisSelfstoreError,\n\tisAuthExpired,\n\terrorLabelKey,\n\ttype SelfstoreErrorCode\n} from '../selfstore';\n\n// --- Durable destinations (opt-in), each implementing BackupTarget ---\n// Explicit namespace objects: every member is deliberately public, so a future\n// internal helper in a target module can never leak into the contract.\nimport {\n\tconnect as fileConnect,\n\tfromSession as fileFromSession,\n\tisSupported as fileIsSupported,\n\tisOpenSupported as fileIsOpenSupported,\n\topenExisting as fileOpenExisting\n} from '../persistence/targets/file';\nimport {\n\tconnect as webdavConnect,\n\tfromSession as webdavFromSession,\n\tpeer as webdavPeer\n} from '../persistence/targets/webdav';\nimport { connect as s3Connect, fromSession as s3FromSession } from '../persistence/targets/s3';\nimport {\n\tconnect as driveConnect,\n\tfromSession as driveFromSession,\n\tpreview as drivePreview,\n\tadopt as driveAdopt,\n\tfindOrCreateOwnFile as driveFindOrCreateOwnFile,\n\tlistBackups as driveListBackups,\n\tcreateBackup as driveCreateBackup,\n\tdeleteBackup as driveDeleteBackup,\n\trenameBackup as driveRenameBackup,\n\taccount as driveAccount,\n\tcreateCompanion as driveCreateCompanion,\n\tshare as driveShare,\n\tunshare as driveUnshare,\n\towner as driveOwner,\n\tsecondary as driveSecondary,\n\tFILE_ID_KEY as DRIVE_FILE_ID_KEY\n} from '../persistence/targets/drive';\n\n/** Disk-file destination (File System Access, Chromium). */\nexport const fileTarget = {\n\tconnect: fileConnect,\n\tfromSession: fileFromSession,\n\tisSupported: fileIsSupported,\n\tisOpenSupported: fileIsOpenSupported,\n\t/** Let the user point at an EXISTING backup file and adopt it as the\n\t * destination. Gate on isOpenSupported(): unlike creating, this has no\n\t * download fallback. */\n\topenExisting: fileOpenExisting\n};\n\n/** WebDAV destination (Nextcloud, ownCloud, any server the user controls), plus\n * `peer()`: a read-only source over another member's shared WebDAV link, so a\n * group can share read-write without Google Drive (see store.attachPeer). */\nexport const webdavTarget = {\n\tconnect: webdavConnect,\n\tfromSession: webdavFromSession,\n\tpeer: webdavPeer\n};\n\n/** S3-compatible destination (Amazon S3, Cloudflare R2, Backblaze B2, MinIO):\n * the browser signs each request with SigV4 and writes one object on a bucket\n * the user controls, no vendor broker. The bucket only ever holds the same\n * end-to-end-encrypted .selfstore bytes. */\nexport const s3Target = {\n\tconnect: s3Connect,\n\tfromSession: s3FromSession\n};\n\n/** Google Drive destination, plus the shared-file primitives (preview a specific\n * file, adopt it as this device's backup, find-or-create the user's own file)\n * and the backup-management ops (list the app's files with dates, create a\n * named empty backup, rename one, delete one for good) - the raw pieces a \"my\n * backups\" panel builds on. `account()` names WHICH Google account holds them,\n * because a brand name is not an address.\n *\n * The last five are the Drive side of SHARING, on companion files the app\n * creates next to the backup: `createCompanion` mints one, `share` publishes it\n * on a link, `unshare` makes it private again, `owner` says whose Drive it\n * lives on, and `secondary` is a read-write target over it whose lifecycle is\n * its own - detaching a shared copy leaves the user's real backup and their\n * Drive session untouched. That is what a ShareBackend needs from Drive and\n * what every app was rewriting; what stays yours is reading ANOTHER account's\n * file, which the drive.file scope cannot do without a Picker gesture or a\n * relay you host. */\nexport const driveTarget = {\n\tconnect: driveConnect,\n\tfromSession: driveFromSession,\n\tpreview: drivePreview,\n\tadopt: driveAdopt,\n\tfindOrCreateOwnFile: driveFindOrCreateOwnFile,\n\tlistBackups: driveListBackups,\n\tcreateBackup: driveCreateBackup,\n\tdeleteBackup: driveDeleteBackup,\n\trenameBackup: driveRenameBackup,\n\taccount: driveAccount,\n\tcreateCompanion: driveCreateCompanion,\n\tshare: driveShare,\n\tunshare: driveUnshare,\n\towner: driveOwner,\n\tsecondary: driveSecondary,\n\tFILE_ID_KEY: DRIVE_FILE_ID_KEY\n};\n\nexport type { FileConnectOptions } from '../persistence/targets/file';\nexport type {\n\tDriveAuth,\n\tDriveOptions,\n\tDriveBackupInfo,\n\tDriveAccount\n} from '../persistence/targets/drive';\nexport type {\n\tWebdavConfig,\n\tWebdavConnectOptions,\n\tWebdavPeerOptions\n} from '../persistence/targets/webdav';\nexport type { S3Config, S3ConnectOptions } from '../persistence/targets/s3';\nexport {\n\tgisDriveAuth,\n\tpreloadGoogleIdentity,\n\ttype GisDriveAuthOptions,\n\ttype GisTokenPersistence\n} from '../persistence/targets/drive-auth-gis';\nexport { saveToDisk, pickFromDisk, datedName } from '../selfstore/targets/local';\n"]}
package/dist/backups.d.ts CHANGED
@@ -1,8 +1,8 @@
1
- import { B as BackupsHost } from './manager-Di6lNE4E.js';
2
- export { a as BackupFileInfo, b as BackupOwner, c as BackupRow, d as BackupsErrorCode, e as BackupsKeys, f as BackupsManager, g as BackupsNaming, h as BackupsSnapshot, K as KnownBackups, i as createBackupsManager } from './manager-Di6lNE4E.js';
3
- import { D as DriveOptions } from './drive-86CHuTij.js';
1
+ import { B as BackupsHost } from './manager-yCM0M0FL.js';
2
+ export { a as BackupFileInfo, b as BackupOwner, c as BackupRow, d as BackupsErrorCode, e as BackupsKeys, f as BackupsManager, g as BackupsNaming, h as BackupsSnapshot, K as KnownBackups, i as createBackupsManager } from './manager-yCM0M0FL.js';
3
+ import { D as DriveOptions } from './drive--mndByvF.js';
4
4
  import './cache-ilsK3Tki.js';
5
- import './store-CIbiNoiE.js';
5
+ import './store-CKGrIFQU.js';
6
6
  import './sync.js';
7
7
  import './machine-ALMkT3mk.js';
8
8
 
package/dist/backups.js CHANGED
@@ -1,10 +1,11 @@
1
- export { createBackupsManager, driveBackupsHost } from './chunk-EBCQ7ZG7.js';
2
- import './chunk-3KAAPYPO.js';
3
- import './chunk-JTNFZV2B.js';
4
- import './chunk-757ZOSUI.js';
5
- import './chunk-HHPJPEEG.js';
6
- import './chunk-OY2A5DCB.js';
7
- import './chunk-RREJ2MFN.js';
8
- import './chunk-XMC67RNM.js';
1
+ export { createBackupsManager, driveBackupsHost } from './chunk-5DAPYC2K.js';
2
+ import './chunk-2BNTCXQN.js';
3
+ import './chunk-25U6AKIX.js';
4
+ import './chunk-LF3CC2P3.js';
5
+ import './chunk-RVOBONZF.js';
6
+ import './chunk-F4ZZEJ5H.js';
7
+ import './chunk-5UYPKCDB.js';
8
+ import './chunk-WXI2NAPA.js';
9
+ import './chunk-CSYEIXNY.js';
9
10
  //# sourceMappingURL=backups.js.map
10
11
  //# sourceMappingURL=backups.js.map
@@ -1,6 +1,7 @@
1
1
  import { unseal } from './chunk-QBCU7OOP.js';
2
- import { freshKdfParams, deriveKey } from './chunk-RREJ2MFN.js';
3
- import './chunk-XMC67RNM.js';
2
+ import { freshKdfParams, deriveKey } from './chunk-5UYPKCDB.js';
3
+ import './chunk-WXI2NAPA.js';
4
+ import './chunk-CSYEIXNY.js';
4
5
 
5
6
  // src/persistence/cache-lock.ts
6
7
  function freshCacheKdf() {
@@ -19,5 +20,5 @@ async function opens(key, probe) {
19
20
  }
20
21
 
21
22
  export { freshCacheKdf, keyFromPassword, opens };
22
- //# sourceMappingURL=cache-lock-5RNG6CIK.js.map
23
- //# sourceMappingURL=cache-lock-5RNG6CIK.js.map
23
+ //# sourceMappingURL=cache-lock-VVAB3CDB.js.map
24
+ //# sourceMappingURL=cache-lock-VVAB3CDB.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/persistence/cache-lock.ts"],"names":[],"mappings":";;;;;;AAeO,SAAS,aAAA,GAA0B;AACzC,EAAA,OAAO,cAAA,EAAe;AACvB;AAGO,SAAS,eAAA,CAAgB,UAAkB,GAAA,EAAmC;AACpF,EAAA,OAAO,SAAA,CAAU,UAAU,GAAG,CAAA;AAC/B;AAIA,eAAsB,KAAA,CAAM,KAAgB,KAAA,EAAsC;AACjF,EAAA,IAAI;AACH,IAAA,MAAM,MAAA,CAAO,KAAK,KAAK,CAAA;AACvB,IAAA,OAAO,IAAA;AAAA,EACR,CAAA,CAAA,MAAQ;AACP,IAAA,OAAO,KAAA;AAAA,EACR;AACD","file":"cache-lock-VVAB3CDB.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,5 +1,5 @@
1
- import { lifeLine, boundedSignal } from './chunk-757ZOSUI.js';
2
- import { AuthExpiredError, SelfstoreError, isAuthExpired } from './chunk-XMC67RNM.js';
1
+ import { lifeLine, boundedSignal } from './chunk-LF3CC2P3.js';
2
+ import { AuthExpiredError, SelfstoreError, isAuthExpired } from './chunk-CSYEIXNY.js';
3
3
 
4
4
  // src/persistence/targets/drive.ts
5
5
  var FILE_ID_KEY = "driveFileId";
@@ -358,5 +358,5 @@ function secondary(opts, fileId) {
358
358
  }
359
359
 
360
360
  export { FILE_ID_KEY, account, adopt, connect, createBackup, createCompanion, deleteBackup, findOrCreateOwnFile, fromSession, listBackups, owner, preview, renameBackup, secondary, share, unshare };
361
- //# sourceMappingURL=chunk-JTNFZV2B.js.map
362
- //# sourceMappingURL=chunk-JTNFZV2B.js.map
361
+ //# sourceMappingURL=chunk-25U6AKIX.js.map
362
+ //# sourceMappingURL=chunk-25U6AKIX.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/persistence/targets/drive.ts"],"names":["token"],"mappings":"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","file":"chunk-JTNFZV2B.js","sourcesContent":["/**\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/** Just enough of a permission to find the link grants and delete them. */\nconst PERMISSION_FIELDS = encodeURIComponent('permissions(id,type)');\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\n// --- Companion files: the Drive side of sharing ------------------------------\n//\n// Everything above operates on the user's OWN backup. Sharing needs a second\n// kind of file: a companion the app creates NEXT TO the backup, publishes on a\n// link, and tears down on its own schedule - a member's published copy, an\n// invitation others read before they join.\n//\n// These four calls plus `secondary()` are that whole Drive side. They are here\n// rather than in a consumer because every app that shares over Drive was\n// writing them again, and getting the error protocol wrong on the way: a bare\n// `throw new Error('Drive share failed: 403')` carries no code, so the store\n// cannot tell a lost session from a bad moment, and the app cannot word it.\n//\n// What is deliberately NOT here: reading another account's file. The\n// `drive.file` scope only sees files this app created or the user picked, so a\n// member cannot fetch a link-shared copy from another Drive without either the\n// Google Picker (a user gesture per file, plus a third-party script) or a relay\n// the app hosts. Both are the app's decision, and one of them is a server -\n// this library will not pick either for you. `attachPeer` takes whatever\n// `load()` you can provide.\n\n/** Create an EMPTY companion file in the user's Drive and answer its id.\n *\n * Unlike `createBackup`, it does not refuse a duplicate name: several members\n * of several groups can legitimately hold identically named copies, and the id\n * is the handle everywhere. The caller remembers the id; nothing here does. */\nexport async function createCompanion(opts: {\n\tauth: DriveAuth;\n\tfileName: string;\n}): Promise<{ fileId: string }> {\n\treturn { fileId: await createFile(await opts.auth.token(), opts.fileName) };\n}\n\n/** Make a file readable by ANYONE holding its link - the capability-link model:\n * the file carries ciphertext only, the key travels in the link's fragment, so\n * the LINK is the capability and the recipient needs no Google account.\n *\n * Idempotent from the caller's side: Drive accepts a second identical grant.\n * Publish an ENCRYPTED file, always - this hands the bytes to whoever has the\n * URL, including whoever it gets forwarded to. */\nexport async function share(opts: { auth: DriveAuth; fileId: string }): Promise<void> {\n\tconst r = await driveFetch(opts.auth, (token) =>\n\t\tfetch(`${DRIVE_FILES}/${opts.fileId}/permissions?fields=id`, {\n\t\t\tmethod: 'POST',\n\t\t\theaders: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' },\n\t\t\tbody: JSON.stringify({ role: 'reader', type: 'anyone' }),\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 share failed: ${r.status}`);\n}\n\n/** Undo `share`: drop every link-reader grant, so the file is private again.\n * Named grants (people invited individually) are left alone - this revokes the\n * capability, not the invitations.\n *\n * Call it BEFORE any plaintext rewrite of a file that was shared: a decrypted\n * copy must never stay link-readable for even one save. A file already private\n * is success, not an error. */\nexport async function unshare(opts: { auth: DriveAuth; fileId: string }): Promise<void> {\n\tconst list = await driveFetch(opts.auth, (token) =>\n\t\tfetch(`${DRIVE_FILES}/${opts.fileId}/permissions?fields=${PERMISSION_FIELDS}`, {\n\t\t\theaders: { Authorization: `Bearer ${token}` },\n\t\t\tsignal: AbortSignal.timeout(META_DEADLINE_MS)\n\t\t})\n\t);\n\tif (list.status === 401) throw new AuthExpiredError('Drive rejected the token (401).');\n\tif (list.status === 404) return; // gone is private enough\n\tif (!list.ok) {\n\t\tthrow new SelfstoreError('TARGET_UNAVAILABLE', `Drive permissions failed: ${list.status}`);\n\t}\n\tconst data: { permissions?: { id: string; type: string }[] } = await list.json();\n\tfor (const p of data.permissions ?? []) {\n\t\tif (p.type !== 'anyone') continue;\n\t\tconst r = await driveFetch(opts.auth, (token) =>\n\t\t\tfetch(`${DRIVE_FILES}/${opts.fileId}/permissions/${p.id}`, {\n\t\t\t\tmethod: 'DELETE',\n\t\t\t\theaders: { Authorization: `Bearer ${token}` },\n\t\t\t\tsignal: AbortSignal.timeout(META_DEADLINE_MS)\n\t\t\t})\n\t\t);\n\t\tif (r.status === 401) throw new AuthExpiredError('Drive rejected the token (401).');\n\t\tif (!r.ok && r.status !== 404) {\n\t\t\tthrow new SelfstoreError('TARGET_WRITE_FAILED', `Drive revoke failed: ${r.status}`);\n\t\t}\n\t}\n}\n\n/** Which account OWNS a file - not which one this session belongs to (that is\n * `account()`). Used to label a copy that lives on somebody else's Drive: a\n * member's published copy is \"Google Drive\" for everyone, and only the owner's\n * address says whose.\n *\n * Best-effort by design: a file that will not say answers nulls rather than\n * throwing, because a missing label must never break a sync round. A genuinely\n * lost session still throws, like every other call here. */\nexport async function owner(opts: { auth: DriveAuth; fileId: string }): Promise<DriveAccount> {\n\tconst fields = encodeURIComponent('owners(displayName,emailAddress)');\n\tconst r = await driveFetch(opts.auth, (token) =>\n\t\tfetch(`${DRIVE_FILES}/${opts.fileId}?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) return { email: null, name: null };\n\tconst data: { owners?: { displayName?: string; emailAddress?: string }[] } = await r.json();\n\tconst first = data.owners?.[0];\n\treturn { email: first?.emailAddress ?? null, name: first?.displayName ?? null };\n}\n\n/** A read-WRITE target over one companion file, for a store mirror or replica.\n *\n * `preview()` also binds a target to a given id, and its save() works - but its\n * name says read-only and its `disconnect()` belongs to the primary\n * connection: it calls `auth.forget()` and may drop the remembered backup id,\n * which is right when the user is leaving Drive and catastrophic when they are\n * only dropping a shared copy. This one detaches nothing but itself, and\n * carries its own `kind` so a store that persists it never mistakes it for the\n * primary destination.\n *\n * Deleting the file is `deleteBackup({ auth, fileId })`; making it private\n * again is `unshare`. Do both, in that order, when a member leaves. */\nexport function secondary(opts: { auth: DriveAuth; kv: KV }, fileId: string): BackupTarget {\n\t// No fileName: a target bound to an id never searches or creates by name, so\n\t// asking for one would be a required argument that is read nowhere - and the\n\t// first caller would have to invent a value to satisfy it.\n\treturn {\n\t\t...makeTarget({ ...opts, fileName: '' }, fileId),\n\t\tkind: 'drive-companion',\n\t\tlabel: 'Google Drive (shared copy)',\n\t\t// The companion's life is its own: leaving a share must not forget the\n\t\t// Drive session the user's real backup still rides on.\n\t\tdisconnect: async (): Promise<void> => {}\n\t};\n}\n"]}
1
+ {"version":3,"sources":["../src/persistence/targets/drive.ts"],"names":["token"],"mappings":"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","file":"chunk-25U6AKIX.js","sourcesContent":["/**\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/** Just enough of a permission to find the link grants and delete them. */\nconst PERMISSION_FIELDS = encodeURIComponent('permissions(id,type)');\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\n// --- Companion files: the Drive side of sharing ------------------------------\n//\n// Everything above operates on the user's OWN backup. Sharing needs a second\n// kind of file: a companion the app creates NEXT TO the backup, publishes on a\n// link, and tears down on its own schedule - a member's published copy, an\n// invitation others read before they join.\n//\n// These four calls plus `secondary()` are that whole Drive side. They are here\n// rather than in a consumer because every app that shares over Drive was\n// writing them again, and getting the error protocol wrong on the way: a bare\n// `throw new Error('Drive share failed: 403')` carries no code, so the store\n// cannot tell a lost session from a bad moment, and the app cannot word it.\n//\n// What is deliberately NOT here: reading another account's file. The\n// `drive.file` scope only sees files this app created or the user picked, so a\n// member cannot fetch a link-shared copy from another Drive without either the\n// Google Picker (a user gesture per file, plus a third-party script) or a relay\n// the app hosts. Both are the app's decision, and one of them is a server -\n// this library will not pick either for you. `attachPeer` takes whatever\n// `load()` you can provide.\n\n/** Create an EMPTY companion file in the user's Drive and answer its id.\n *\n * Unlike `createBackup`, it does not refuse a duplicate name: several members\n * of several groups can legitimately hold identically named copies, and the id\n * is the handle everywhere. The caller remembers the id; nothing here does. */\nexport async function createCompanion(opts: {\n\tauth: DriveAuth;\n\tfileName: string;\n}): Promise<{ fileId: string }> {\n\treturn { fileId: await createFile(await opts.auth.token(), opts.fileName) };\n}\n\n/** Make a file readable by ANYONE holding its link - the capability-link model:\n * the file carries ciphertext only, the key travels in the link's fragment, so\n * the LINK is the capability and the recipient needs no Google account.\n *\n * Idempotent from the caller's side: Drive accepts a second identical grant.\n * Publish an ENCRYPTED file, always - this hands the bytes to whoever has the\n * URL, including whoever it gets forwarded to. */\nexport async function share(opts: { auth: DriveAuth; fileId: string }): Promise<void> {\n\tconst r = await driveFetch(opts.auth, (token) =>\n\t\tfetch(`${DRIVE_FILES}/${opts.fileId}/permissions?fields=id`, {\n\t\t\tmethod: 'POST',\n\t\t\theaders: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' },\n\t\t\tbody: JSON.stringify({ role: 'reader', type: 'anyone' }),\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 share failed: ${r.status}`);\n}\n\n/** Undo `share`: drop every link-reader grant, so the file is private again.\n * Named grants (people invited individually) are left alone - this revokes the\n * capability, not the invitations.\n *\n * Call it BEFORE any plaintext rewrite of a file that was shared: a decrypted\n * copy must never stay link-readable for even one save. A file already private\n * is success, not an error. */\nexport async function unshare(opts: { auth: DriveAuth; fileId: string }): Promise<void> {\n\tconst list = await driveFetch(opts.auth, (token) =>\n\t\tfetch(`${DRIVE_FILES}/${opts.fileId}/permissions?fields=${PERMISSION_FIELDS}`, {\n\t\t\theaders: { Authorization: `Bearer ${token}` },\n\t\t\tsignal: AbortSignal.timeout(META_DEADLINE_MS)\n\t\t})\n\t);\n\tif (list.status === 401) throw new AuthExpiredError('Drive rejected the token (401).');\n\tif (list.status === 404) return; // gone is private enough\n\tif (!list.ok) {\n\t\tthrow new SelfstoreError('TARGET_UNAVAILABLE', `Drive permissions failed: ${list.status}`);\n\t}\n\tconst data: { permissions?: { id: string; type: string }[] } = await list.json();\n\tfor (const p of data.permissions ?? []) {\n\t\tif (p.type !== 'anyone') continue;\n\t\tconst r = await driveFetch(opts.auth, (token) =>\n\t\t\tfetch(`${DRIVE_FILES}/${opts.fileId}/permissions/${p.id}`, {\n\t\t\t\tmethod: 'DELETE',\n\t\t\t\theaders: { Authorization: `Bearer ${token}` },\n\t\t\t\tsignal: AbortSignal.timeout(META_DEADLINE_MS)\n\t\t\t})\n\t\t);\n\t\tif (r.status === 401) throw new AuthExpiredError('Drive rejected the token (401).');\n\t\tif (!r.ok && r.status !== 404) {\n\t\t\tthrow new SelfstoreError('TARGET_WRITE_FAILED', `Drive revoke failed: ${r.status}`);\n\t\t}\n\t}\n}\n\n/** Which account OWNS a file - not which one this session belongs to (that is\n * `account()`). Used to label a copy that lives on somebody else's Drive: a\n * member's published copy is \"Google Drive\" for everyone, and only the owner's\n * address says whose.\n *\n * Best-effort by design: a file that will not say answers nulls rather than\n * throwing, because a missing label must never break a sync round. A genuinely\n * lost session still throws, like every other call here. */\nexport async function owner(opts: { auth: DriveAuth; fileId: string }): Promise<DriveAccount> {\n\tconst fields = encodeURIComponent('owners(displayName,emailAddress)');\n\tconst r = await driveFetch(opts.auth, (token) =>\n\t\tfetch(`${DRIVE_FILES}/${opts.fileId}?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) return { email: null, name: null };\n\tconst data: { owners?: { displayName?: string; emailAddress?: string }[] } = await r.json();\n\tconst first = data.owners?.[0];\n\treturn { email: first?.emailAddress ?? null, name: first?.displayName ?? null };\n}\n\n/** A read-WRITE target over one companion file, for a store mirror or replica.\n *\n * `preview()` also binds a target to a given id, and its save() works - but its\n * name says read-only and its `disconnect()` belongs to the primary\n * connection: it calls `auth.forget()` and may drop the remembered backup id,\n * which is right when the user is leaving Drive and catastrophic when they are\n * only dropping a shared copy. This one detaches nothing but itself, and\n * carries its own `kind` so a store that persists it never mistakes it for the\n * primary destination.\n *\n * Deleting the file is `deleteBackup({ auth, fileId })`; making it private\n * again is `unshare`. Do both, in that order, when a member leaves. */\nexport function secondary(opts: { auth: DriveAuth; kv: KV }, fileId: string): BackupTarget {\n\t// No fileName: a target bound to an id never searches or creates by name, so\n\t// asking for one would be a required argument that is read nowhere - and the\n\t// first caller would have to invent a value to satisfy it.\n\treturn {\n\t\t...makeTarget({ ...opts, fileName: '' }, fileId),\n\t\tkind: 'drive-companion',\n\t\tlabel: 'Google Drive (shared copy)',\n\t\t// The companion's life is its own: leaving a share must not forget the\n\t\t// Drive session the user's real backup still rides on.\n\t\tdisconnect: async (): Promise<void> => {}\n\t};\n}\n"]}
@@ -1,4 +1,4 @@
1
- import { SelfstoreError, isSelfstoreError, errorLabelKey } from './chunk-XMC67RNM.js';
1
+ import { SelfstoreError, isSelfstoreError, errorLabelKey } from './chunk-CSYEIXNY.js';
2
2
 
3
3
  // src/flows/machine.ts
4
4
  function makeMachine(initial) {
@@ -42,5 +42,5 @@ function toStoreError(e) {
42
42
  }
43
43
 
44
44
  export { makeMachine, toStoreError, withDeadline };
45
- //# sourceMappingURL=chunk-3KAAPYPO.js.map
46
- //# sourceMappingURL=chunk-3KAAPYPO.js.map
45
+ //# sourceMappingURL=chunk-2BNTCXQN.js.map
46
+ //# sourceMappingURL=chunk-2BNTCXQN.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/flows/machine.ts"],"names":[],"mappings":";;;AAuBO,SAAS,YAA8B,OAAA,EAAwB;AACrE,EAAA,IAAI,QAAA,GAAW,OAAA;AACf,EAAA,MAAM,SAAA,uBAAgB,GAAA,EAAoB;AAC1C,EAAA,OAAO;AAAA,IACN,IAAI,QAAA,GAAc;AACjB,MAAA,OAAO,QAAA;AAAA,IACR,CAAA;AAAA,IACA,IAAI,KAAA,EAAyB;AAC5B,MAAA,QAAA,GAAW,EAAE,GAAG,QAAA,EAAU,GAAG,KAAA,EAAM;AACnC,MAAA,SAAA,CAAU,OAAA,CAAQ,CAAC,EAAA,KAAO,EAAA,CAAG,QAAQ,CAAC,CAAA;AAAA,IACvC,CAAA;AAAA,IACA,UAAU,QAAA,EAAsC;AAC/C,MAAA,SAAA,CAAU,IAAI,QAAQ,CAAA;AACtB,MAAA,QAAA,CAAS,QAAQ,CAAA;AACjB,MAAA,OAAO,MAAM,SAAA,CAAU,MAAA,CAAO,QAAQ,CAAA;AAAA,IACvC;AAAA,GACD;AACD;AAOA,eAAsB,YAAA,CAAgB,IAAA,EAAkB,EAAA,EAAY,IAAA,EAA0B;AAC7F,EAAA,IAAI,KAAA;AACJ,EAAA,MAAM,IAAA,GAAO,IAAI,OAAA,CAAe,CAAC,UAAU,MAAA,KAAW;AACrD,IAAA,KAAA,GAAQ,UAAA;AAAA,MACP,MACC,MAAA,CAAO,IAAI,cAAA,CAAe,oBAAA,EAAsB,GAAG,IAAI,CAAA,uBAAA,EAA0B,EAAE,CAAA,GAAA,CAAK,CAAC,CAAA;AAAA,MAC1F;AAAA,KACD;AAAA,EACD,CAAC,CAAA;AACD,EAAA,IAAI;AACH,IAAA,OAAO,MAAM,OAAA,CAAQ,IAAA,CAAK,CAAC,IAAA,EAAM,IAAI,CAAC,CAAA;AAAA,EACvC,CAAA,SAAE;AACD,IAAA,YAAA,CAAa,KAAK,CAAA;AAAA,EACnB;AACD;AAMO,SAAS,aAAa,CAAA,EAAwB;AACpD,EAAA,IAAI,gBAAA,CAAiB,CAAC,CAAA,EAAG;AACxB,IAAA,OAAO,EAAE,IAAA,EAAM,CAAA,CAAE,IAAA,EAAM,QAAA,EAAU,aAAA,CAAc,CAAA,CAAE,IAAI,CAAA,EAAG,OAAA,EAAS,CAAA,CAAE,OAAA,EAAQ;AAAA,EAC5E;AACA,EAAA,MAAM,UAAU,CAAA,YAAa,KAAA,GAAQ,CAAA,CAAE,OAAA,GAAU,OAAO,CAAC,CAAA;AACzD,EAAA,OAAO,EAAE,IAAA,EAAM,oBAAA,EAAsB,UAAU,aAAA,CAAc,oBAAoB,GAAG,OAAA,EAAQ;AAC7F","file":"chunk-3KAAPYPO.js","sourcesContent":["// Shared plumbing for the flows: a tiny observable snapshot (fits the\n// Svelte store contract and React's useSyncExternalStore), a deadline\n// wrapper for network legs, and the normalizer that turns anything thrown\n// into the stable { code, labelKey, message } shape.\n\nimport type { StoreError } from '../persistence/store';\nimport { SelfstoreError, errorLabelKey, isSelfstoreError } from '../selfstore/errors';\n\n/** What every flow exposes: the current snapshot plus a subscription.\n * `subscribe` calls the listener IMMEDIATELY with the current snapshot and on\n * every change after - that is the Svelte store contract, so a flow works as\n * `$flow` in Svelte; React reads `useSyncExternalStore(flow.subscribe, () =>\n * flow.snapshot)`; everyone else just re-renders in the callback. */\nexport interface FlowStore<T> {\n\treadonly snapshot: T;\n\tsubscribe(listener: (snapshot: T) => void): () => void;\n}\n\nexport interface Machine<T> extends FlowStore<T> {\n\t/** Merge a partial into the snapshot and notify. */\n\tset(patch: Partial<T>): void;\n}\n\nexport function makeMachine<T extends object>(initial: T): Machine<T> {\n\tlet snapshot = initial;\n\tconst listeners = new Set<(s: T) => void>();\n\treturn {\n\t\tget snapshot(): T {\n\t\t\treturn snapshot;\n\t\t},\n\t\tset(patch: Partial<T>): void {\n\t\t\tsnapshot = { ...snapshot, ...patch };\n\t\t\tlisteners.forEach((fn) => fn(snapshot));\n\t\t},\n\t\tsubscribe(listener: (s: T) => void): () => void {\n\t\t\tlisteners.add(listener);\n\t\t\tlistener(snapshot);\n\t\t\treturn () => listeners.delete(listener);\n\t\t}\n\t};\n}\n\n/** Bound a network leg so a stuck destination surfaces as a retryable error\n * instead of a flow frozen on a spinner. The rejection is TARGET_UNAVAILABLE -\n * transient by contract - because \"did not answer in time\" says nothing about\n * access being lost. Never wrap a human step (a consent popup, a picker):\n * people are allowed to be slower than a deadline. */\nexport async function withDeadline<T>(work: Promise<T>, ms: number, what: string): Promise<T> {\n\tlet timer: ReturnType<typeof setTimeout> | undefined;\n\tconst gate = new Promise<never>((_resolve, reject) => {\n\t\ttimer = setTimeout(\n\t\t\t() =>\n\t\t\t\treject(new SelfstoreError('TARGET_UNAVAILABLE', `${what} did not answer within ${ms}ms.`)),\n\t\t\tms\n\t\t);\n\t});\n\ttry {\n\t\treturn await Promise.race([work, gate]);\n\t} finally {\n\t\tclearTimeout(timer);\n\t}\n}\n\n/** Normalize anything thrown into the stable StoreError shape the whole\n * library reports with: show `labelKey`, log `message`. Unknown errors map to\n * TARGET_UNAVAILABLE (transient, retryable) - the flow never invents new\n * codes for the app to branch on. */\nexport function toStoreError(e: unknown): StoreError {\n\tif (isSelfstoreError(e)) {\n\t\treturn { code: e.code, labelKey: errorLabelKey(e.code), message: e.message };\n\t}\n\tconst message = e instanceof Error ? e.message : String(e);\n\treturn { code: 'TARGET_UNAVAILABLE', labelKey: errorLabelKey('TARGET_UNAVAILABLE'), message };\n}\n"]}
1
+ {"version":3,"sources":["../src/flows/machine.ts"],"names":[],"mappings":";;;AAuBO,SAAS,YAA8B,OAAA,EAAwB;AACrE,EAAA,IAAI,QAAA,GAAW,OAAA;AACf,EAAA,MAAM,SAAA,uBAAgB,GAAA,EAAoB;AAC1C,EAAA,OAAO;AAAA,IACN,IAAI,QAAA,GAAc;AACjB,MAAA,OAAO,QAAA;AAAA,IACR,CAAA;AAAA,IACA,IAAI,KAAA,EAAyB;AAC5B,MAAA,QAAA,GAAW,EAAE,GAAG,QAAA,EAAU,GAAG,KAAA,EAAM;AACnC,MAAA,SAAA,CAAU,OAAA,CAAQ,CAAC,EAAA,KAAO,EAAA,CAAG,QAAQ,CAAC,CAAA;AAAA,IACvC,CAAA;AAAA,IACA,UAAU,QAAA,EAAsC;AAC/C,MAAA,SAAA,CAAU,IAAI,QAAQ,CAAA;AACtB,MAAA,QAAA,CAAS,QAAQ,CAAA;AACjB,MAAA,OAAO,MAAM,SAAA,CAAU,MAAA,CAAO,QAAQ,CAAA;AAAA,IACvC;AAAA,GACD;AACD;AAOA,eAAsB,YAAA,CAAgB,IAAA,EAAkB,EAAA,EAAY,IAAA,EAA0B;AAC7F,EAAA,IAAI,KAAA;AACJ,EAAA,MAAM,IAAA,GAAO,IAAI,OAAA,CAAe,CAAC,UAAU,MAAA,KAAW;AACrD,IAAA,KAAA,GAAQ,UAAA;AAAA,MACP,MACC,MAAA,CAAO,IAAI,cAAA,CAAe,oBAAA,EAAsB,GAAG,IAAI,CAAA,uBAAA,EAA0B,EAAE,CAAA,GAAA,CAAK,CAAC,CAAA;AAAA,MAC1F;AAAA,KACD;AAAA,EACD,CAAC,CAAA;AACD,EAAA,IAAI;AACH,IAAA,OAAO,MAAM,OAAA,CAAQ,IAAA,CAAK,CAAC,IAAA,EAAM,IAAI,CAAC,CAAA;AAAA,EACvC,CAAA,SAAE;AACD,IAAA,YAAA,CAAa,KAAK,CAAA;AAAA,EACnB;AACD;AAMO,SAAS,aAAa,CAAA,EAAwB;AACpD,EAAA,IAAI,gBAAA,CAAiB,CAAC,CAAA,EAAG;AACxB,IAAA,OAAO,EAAE,IAAA,EAAM,CAAA,CAAE,IAAA,EAAM,QAAA,EAAU,aAAA,CAAc,CAAA,CAAE,IAAI,CAAA,EAAG,OAAA,EAAS,CAAA,CAAE,OAAA,EAAQ;AAAA,EAC5E;AACA,EAAA,MAAM,UAAU,CAAA,YAAa,KAAA,GAAQ,CAAA,CAAE,OAAA,GAAU,OAAO,CAAC,CAAA;AACzD,EAAA,OAAO,EAAE,IAAA,EAAM,oBAAA,EAAsB,UAAU,aAAA,CAAc,oBAAoB,GAAG,OAAA,EAAQ;AAC7F","file":"chunk-2BNTCXQN.js","sourcesContent":["// Shared plumbing for the flows: a tiny observable snapshot (fits the\n// Svelte store contract and React's useSyncExternalStore), a deadline\n// wrapper for network legs, and the normalizer that turns anything thrown\n// into the stable { code, labelKey, message } shape.\n\nimport type { StoreError } from '../persistence/store';\nimport { SelfstoreError, errorLabelKey, isSelfstoreError } from '../selfstore/errors';\n\n/** What every flow exposes: the current snapshot plus a subscription.\n * `subscribe` calls the listener IMMEDIATELY with the current snapshot and on\n * every change after - that is the Svelte store contract, so a flow works as\n * `$flow` in Svelte; React reads `useSyncExternalStore(flow.subscribe, () =>\n * flow.snapshot)`; everyone else just re-renders in the callback. */\nexport interface FlowStore<T> {\n\treadonly snapshot: T;\n\tsubscribe(listener: (snapshot: T) => void): () => void;\n}\n\nexport interface Machine<T> extends FlowStore<T> {\n\t/** Merge a partial into the snapshot and notify. */\n\tset(patch: Partial<T>): void;\n}\n\nexport function makeMachine<T extends object>(initial: T): Machine<T> {\n\tlet snapshot = initial;\n\tconst listeners = new Set<(s: T) => void>();\n\treturn {\n\t\tget snapshot(): T {\n\t\t\treturn snapshot;\n\t\t},\n\t\tset(patch: Partial<T>): void {\n\t\t\tsnapshot = { ...snapshot, ...patch };\n\t\t\tlisteners.forEach((fn) => fn(snapshot));\n\t\t},\n\t\tsubscribe(listener: (s: T) => void): () => void {\n\t\t\tlisteners.add(listener);\n\t\t\tlistener(snapshot);\n\t\t\treturn () => listeners.delete(listener);\n\t\t}\n\t};\n}\n\n/** Bound a network leg so a stuck destination surfaces as a retryable error\n * instead of a flow frozen on a spinner. The rejection is TARGET_UNAVAILABLE -\n * transient by contract - because \"did not answer in time\" says nothing about\n * access being lost. Never wrap a human step (a consent popup, a picker):\n * people are allowed to be slower than a deadline. */\nexport async function withDeadline<T>(work: Promise<T>, ms: number, what: string): Promise<T> {\n\tlet timer: ReturnType<typeof setTimeout> | undefined;\n\tconst gate = new Promise<never>((_resolve, reject) => {\n\t\ttimer = setTimeout(\n\t\t\t() =>\n\t\t\t\treject(new SelfstoreError('TARGET_UNAVAILABLE', `${what} did not answer within ${ms}ms.`)),\n\t\t\tms\n\t\t);\n\t});\n\ttry {\n\t\treturn await Promise.race([work, gate]);\n\t} finally {\n\t\tclearTimeout(timer);\n\t}\n}\n\n/** Normalize anything thrown into the stable StoreError shape the whole\n * library reports with: show `labelKey`, log `message`. Unknown errors map to\n * TARGET_UNAVAILABLE (transient, retryable) - the flow never invents new\n * codes for the app to branch on. */\nexport function toStoreError(e: unknown): StoreError {\n\tif (isSelfstoreError(e)) {\n\t\treturn { code: e.code, labelKey: errorLabelKey(e.code), message: e.message };\n\t}\n\tconst message = e instanceof Error ? e.message : String(e);\n\treturn { code: 'TARGET_UNAVAILABLE', labelKey: errorLabelKey('TARGET_UNAVAILABLE'), message };\n}\n"]}
@@ -1,9 +1,9 @@
1
- import { s3Connect, webdavConnect } from './chunk-3BWBBGFW.js';
2
- import { isSupported, openExisting, connect as connect$1 } from './chunk-GW5CYP4C.js';
3
- import { makeMachine, withDeadline, toStoreError } from './chunk-3KAAPYPO.js';
4
- import { connect } from './chunk-JTNFZV2B.js';
5
- import { restore } from './chunk-HHPJPEEG.js';
6
- import { SelfstoreError, isSelfstoreError } from './chunk-XMC67RNM.js';
1
+ import { s3Connect, webdavConnect } from './chunk-FQ5O4PHK.js';
2
+ import { isSupported, openExisting, connect as connect$1 } from './chunk-2TIRUEXR.js';
3
+ import { makeMachine, withDeadline, toStoreError } from './chunk-2BNTCXQN.js';
4
+ import { connect } from './chunk-25U6AKIX.js';
5
+ import { restore } from './chunk-RVOBONZF.js';
6
+ import { SelfstoreError, isSelfstoreError } from './chunk-CSYEIXNY.js';
7
7
 
8
8
  // src/flows/connect.ts
9
9
  var DEFAULT_DEADLINE_MS = 3e4;
@@ -493,5 +493,5 @@ function joinFlow(link, engine, options = {}) {
493
493
  }
494
494
 
495
495
  export { connectFlow, joinFlow, shareFlow };
496
- //# sourceMappingURL=chunk-LYO36VYB.js.map
497
- //# sourceMappingURL=chunk-LYO36VYB.js.map
496
+ //# sourceMappingURL=chunk-2PIYIIHY.js.map
497
+ //# sourceMappingURL=chunk-2PIYIIHY.js.map