selfstore 1.8.26 → 1.8.28

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 (36) hide show
  1. package/dist/advanced.js +4 -4
  2. package/dist/backups.js +2 -2
  3. package/dist/{cache-lock-VVAB3CDB.js → cache-lock-RGP74GXQ.js} +3 -3
  4. package/dist/{cache-lock-VVAB3CDB.js.map → cache-lock-RGP74GXQ.js.map} +1 -1
  5. package/dist/{chunk-RVOBONZF.js → chunk-7U5XMKVP.js} +22 -11
  6. package/dist/chunk-7U5XMKVP.js.map +1 -0
  7. package/dist/{chunk-2PIYIIHY.js → chunk-A6OQP7DR.js} +4 -4
  8. package/dist/{chunk-2PIYIIHY.js.map → chunk-A6OQP7DR.js.map} +1 -1
  9. package/dist/{chunk-AZWS7YC3.js → chunk-GM47BISW.js} +52 -67
  10. package/dist/chunk-GM47BISW.js.map +1 -0
  11. package/dist/{chunk-5UYPKCDB.js → chunk-H2T4S3KM.js} +11 -8
  12. package/dist/chunk-H2T4S3KM.js.map +1 -0
  13. package/dist/{chunk-FQ5O4PHK.js → chunk-JDDWWQEB.js} +6 -6
  14. package/dist/{chunk-FQ5O4PHK.js.map → chunk-JDDWWQEB.js.map} +1 -1
  15. package/dist/{chunk-LQS7AMOD.js → chunk-Q3NO477P.js} +3 -3
  16. package/dist/{chunk-LQS7AMOD.js.map → chunk-Q3NO477P.js.map} +1 -1
  17. package/dist/flows.js +4 -4
  18. package/dist/groups.js +1 -1
  19. package/dist/index.js +9 -9
  20. package/dist/kdf-worker.js +136 -1853
  21. package/dist/kdf-worker.js.map +1 -1
  22. package/dist/local-S3WUZSDX.js +8 -0
  23. package/dist/{local-GGAMRY23.js.map → local-S3WUZSDX.js.map} +1 -1
  24. package/dist/{manager-drive-2IUE4WQ2.js → manager-drive-T3RXUWAY.js} +4 -4
  25. package/dist/{manager-drive-2IUE4WQ2.js.map → manager-drive-T3RXUWAY.js.map} +1 -1
  26. package/dist/{s3-ABKUTTXE.js → s3-ORDHS7G3.js} +4 -4
  27. package/dist/s3-ORDHS7G3.js.map +1 -0
  28. package/dist/{webdav-5SHNR7CL.js → webdav-PXKELPA4.js} +4 -4
  29. package/dist/{webdav-5SHNR7CL.js.map → webdav-PXKELPA4.js.map} +1 -1
  30. package/dist/widgets.js +5 -5
  31. package/package.json +4 -3
  32. package/dist/chunk-5UYPKCDB.js.map +0 -1
  33. package/dist/chunk-AZWS7YC3.js.map +0 -1
  34. package/dist/chunk-RVOBONZF.js.map +0 -1
  35. package/dist/local-GGAMRY23.js +0 -8
  36. package/dist/s3-ABKUTTXE.js.map +0 -1
@@ -3,8 +3,7 @@ import { SelfstoreError } from './chunk-CSYEIXNY.js';
3
3
 
4
4
  // src/selfstore/argon2.ts
5
5
  var KEY_BYTES = 32;
6
- async function runKdfJob(job) {
7
- const { argon2id } = await import('hash-wasm');
6
+ function deriveKey(argon2id, job) {
8
7
  return argon2id({
9
8
  password: job.password,
10
9
  salt: fromBase64(job.salt),
@@ -15,6 +14,10 @@ async function runKdfJob(job) {
15
14
  outputType: "binary"
16
15
  });
17
16
  }
17
+ async function runKdfJob(job) {
18
+ const { argon2id } = await import('hash-wasm');
19
+ return deriveKey(argon2id, job);
20
+ }
18
21
 
19
22
  // src/selfstore/kdf.ts
20
23
  var JOB_TIMEOUT_MS = 3e4;
@@ -112,7 +115,7 @@ function randomBytes(n) {
112
115
  function src(u8) {
113
116
  return u8;
114
117
  }
115
- async function deriveKey(password, kdf) {
118
+ async function deriveKey2(password, kdf) {
116
119
  const raw = await deriveRaw(password, kdf);
117
120
  return crypto.subtle.importKey("raw", src(raw), "AES-GCM", false, ["encrypt", "decrypt"]);
118
121
  }
@@ -122,7 +125,7 @@ function freshKdfParams() {
122
125
  var MAX_KEY_SLOTS = 8;
123
126
  async function mintSlot(password, dataKey, id) {
124
127
  const kdf = freshKdfParams();
125
- const key = await deriveKey(password, kdf);
128
+ const key = await deriveKey2(password, kdf);
126
129
  const iv = randomBytes(IV_BYTES);
127
130
  const wrapped = await crypto.subtle.encrypt({ name: "AES-GCM", iv: src(iv) }, key, src(dataKey));
128
131
  return {
@@ -135,7 +138,7 @@ async function mintSlot(password, dataKey, id) {
135
138
  async function openSlot(slot, password) {
136
139
  assertKdfBounds(slot.kdf);
137
140
  try {
138
- const key = await deriveKey(password, slot.kdf);
141
+ const key = await deriveKey2(password, slot.kdf);
139
142
  const pt = await crypto.subtle.decrypt(
140
143
  { name: "AES-GCM", iv: src(fromBase64(slot.iv)) },
141
144
  key,
@@ -218,6 +221,6 @@ async function openExternalSlot(slot, secret) {
218
221
  }
219
222
  }
220
223
 
221
- export { MAX_KEY_SLOTS, assertKdfBounds, deriveKey, freshKdfParams, gcmOpenAad, gcmSealAad, mintExternalSlot, mintSlot, openExternalSlot, openSlot };
222
- //# sourceMappingURL=chunk-5UYPKCDB.js.map
223
- //# sourceMappingURL=chunk-5UYPKCDB.js.map
224
+ export { MAX_KEY_SLOTS, assertKdfBounds, deriveKey2 as deriveKey, freshKdfParams, gcmOpenAad, gcmSealAad, mintExternalSlot, mintSlot, openExternalSlot, openSlot };
225
+ //# sourceMappingURL=chunk-H2T4S3KM.js.map
226
+ //# sourceMappingURL=chunk-H2T4S3KM.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/selfstore/argon2.ts","../src/selfstore/kdf.ts","../src/selfstore/crypto.ts"],"names":["deriveKey"],"mappings":"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","file":"chunk-H2T4S3KM.js","sourcesContent":["// The one Argon2id call, shared by the calling-thread fallback and the KDF\n// worker so both derive byte-identical keys. On the calling thread hash-wasm\n// loads dynamically - only when a password is actually used. The worker hands\n// its own static import instead: esbuild cannot tree-shake a dynamic import of\n// an inlined module, and the worker then shipped every hash-wasm algorithm.\n\nimport type { argon2id as Argon2id } from 'hash-wasm';\nimport { fromBase64 } from './base64';\n\n/** AES-256 key length. */\nexport const KEY_BYTES = 32;\n\n/** One key-derivation job (postMessage-friendly: plain fields only). */\nexport interface KdfJob {\n\tid: number;\n\tpassword: string;\n\t/** Base64, exactly as stored in the backup header. */\n\tsalt: string;\n\t/** Memory cost in KiB. */\n\tm: number;\n\t/** Iterations (passes). */\n\tt: number;\n\t/** Parallelism (lanes). */\n\tp: number;\n}\n\n/** The worker's reply: the raw key, or the failure message. */\nexport interface KdfReply {\n\tid: number;\n\tkey?: Uint8Array;\n\terror?: string;\n}\n\n/** Derive the key of one job with the given hash-wasm argon2id. */\nexport function deriveKey(argon2id: typeof Argon2id, job: KdfJob): Promise<Uint8Array> {\n\treturn argon2id({\n\t\tpassword: job.password,\n\t\tsalt: fromBase64(job.salt),\n\t\tparallelism: job.p,\n\t\titerations: job.t,\n\t\tmemorySize: job.m,\n\t\thashLength: KEY_BYTES,\n\t\toutputType: 'binary'\n\t});\n}\n\nexport async function runKdfJob(job: KdfJob): Promise<Uint8Array> {\n\tconst { argon2id } = await import('hash-wasm');\n\treturn deriveKey(argon2id, job);\n}\n","// Off-main-thread key derivation. Argon2id takes about a second at the\n// default cost, which would jank every encrypted push and pull if it ran on\n// the UI thread. One lazy dedicated worker, terminated after an idle window\n// so its ~46 MiB of WASM memory is not held forever. Any failure to spawn,\n// load, or answer falls back to the calling thread, which runs the exact\n// same runKdfJob - the worker is a responsiveness optimization, never a\n// correctness dependency.\n\nimport { runKdfJob, type KdfJob, type KdfReply } from './argon2';\nimport type { KdfParams } from './types';\n\n// Generous: a wedged worker is rerun on the calling thread, and Argon2id at\n// the default cost already takes a second or two on a phone.\nconst JOB_TIMEOUT_MS = 30_000;\nconst IDLE_TERMINATE_MS = 30_000;\n\ninterface Pending {\n\tjob: KdfJob;\n\tresolve: (key: Uint8Array) => void;\n\treject: (e: unknown) => void;\n\ttimer: ReturnType<typeof setTimeout>;\n}\n\nlet worker: Worker | null = null;\n// Once the worker path fails (blocked, missing asset), stay on the calling\n// thread for the rest of the session instead of failing again.\nlet broken = false;\nlet seq = 0;\nconst pending = new Map<number, Pending>();\nlet idleTimer: ReturnType<typeof setTimeout> | undefined;\n\nfunction armIdleStop(): void {\n\tclearTimeout(idleTimer);\n\tidleTimer = setTimeout(() => {\n\t\tif (pending.size === 0 && worker) {\n\t\t\tworker.terminate();\n\t\t\tworker = null; // respawned on the next job\n\t\t}\n\t}, IDLE_TERMINATE_MS);\n}\n\n/** Abandon the worker and finish every queued job on the calling thread. */\nfunction fallBack(): void {\n\tbroken = true;\n\tworker?.terminate();\n\tworker = null;\n\tclearTimeout(idleTimer);\n\tfor (const p of pending.values()) {\n\t\tclearTimeout(p.timer);\n\t\trunKdfJob(p.job).then(p.resolve, p.reject);\n\t}\n\tpending.clear();\n}\n\nfunction ensureWorker(): Worker | null {\n\tif (broken) return null;\n\tif (worker) return worker;\n\tif (typeof Worker === 'undefined') {\n\t\tbroken = true; // Node, old runtimes\n\t\treturn null;\n\t}\n\ttry {\n\t\t// Bundlers (Vite, webpack 5, Rollup) detect this exact\n\t\t// `new Worker(new URL(...), import.meta.url)` shape to ship the worker\n\t\t// file as an asset - do not refactor it away.\n\t\tworker = new Worker(new URL('./kdf-worker.js', import.meta.url), {\n\t\t\ttype: 'module',\n\t\t\tname: 'selfstore-kdf'\n\t\t});\n\t} catch {\n\t\tbroken = true;\n\t\treturn null;\n\t}\n\tworker.onmessage = (ev: MessageEvent<KdfReply>): void => {\n\t\tconst reply = ev.data;\n\t\tconst p = pending.get(reply.id);\n\t\tif (!p) return;\n\t\tpending.delete(reply.id);\n\t\tclearTimeout(p.timer);\n\t\tif (reply.key) p.resolve(reply.key);\n\t\telse p.reject(new Error(reply.error ?? 'key derivation failed in the worker'));\n\t\tarmIdleStop();\n\t};\n\t// Script 404, parse error, CSP: rerun the queued jobs locally.\n\tworker.onerror = (): void => fallBack();\n\treturn worker;\n}\n\n/** Derive the raw AES key for `kdf`, off the main thread when possible. */\nexport function deriveRaw(password: string, kdf: KdfParams): Promise<Uint8Array> {\n\tconst job: KdfJob = { id: ++seq, password, salt: kdf.salt, m: kdf.m, t: kdf.t, p: kdf.p };\n\tconst w = ensureWorker();\n\tif (!w) return runKdfJob(job);\n\treturn new Promise<Uint8Array>((resolve, reject) => {\n\t\tconst timer = setTimeout(() => {\n\t\t\t// fallBack() clears every OTHER pending timer, so no job runs twice.\n\t\t\tpending.delete(job.id);\n\t\t\tfallBack();\n\t\t\trunKdfJob(job).then(resolve, reject);\n\t\t}, JOB_TIMEOUT_MS);\n\t\tpending.set(job.id, { job, resolve, reject, timer });\n\t\tw.postMessage(job);\n\t});\n}\n","// AES-256-GCM via WebCrypto over an Argon2id-derived key (hash-wasm). The\n// derivation runs in a worker when the platform has one (see kdf.ts); the\n// WASM is imported dynamically so it only loads when a password is used.\n\nimport type { KdfParams, PasswordSlot, ExternalSlot } from './types';\nimport { deriveRaw } from './kdf';\nimport { toBase64, fromBase64 } from './base64';\nimport { SelfstoreError } from './errors';\n\nexport { toBase64, fromBase64 };\n\nconst IV_BYTES = 12;\nconst SALT_BYTES = 16;\n\n/**\n * Cost for new backups: 46 MiB, 3 passes - above the OWASP Argon2id minimum,\n * ~1-2 s on a phone. Params are stored per file, so older backups keep\n * decrypting with their own cost.\n */\nexport const DEFAULT_ARGON2 = { m: 47104, t: 3, p: 1 } as const;\n\n/**\n * Read-side ceiling (m is in KiB: 1 GiB, 10 passes, 4 lanes). Without it a\n * crafted file could declare m = 8_000_000 and make the reader allocate\n * gigabytes before any password check. Out-of-range params are\n * UNSUPPORTED_VERSION, not DECRYPT_FAILED - the file is not corrupt, its\n * parameters are just outside what this reader will run.\n */\nconst KDF_MAX = { m: 1024 * 1024, t: 10, p: 4 } as const;\nconst KDF_MIN = { m: 8, t: 1, p: 1 } as const;\n\n/** Throws UNSUPPORTED_VERSION on out-of-range Argon2 params. Call before touching the WASM. */\nexport function assertKdfBounds(kdf: KdfParams): void {\n\tconst { m, t, p } = kdf;\n\tconst ok =\n\t\tNumber.isInteger(m) &&\n\t\tNumber.isInteger(t) &&\n\t\tNumber.isInteger(p) &&\n\t\tm >= KDF_MIN.m &&\n\t\tm <= KDF_MAX.m &&\n\t\tt >= KDF_MIN.t &&\n\t\tt <= KDF_MAX.t &&\n\t\tp >= KDF_MIN.p &&\n\t\tp <= KDF_MAX.p;\n\tif (!ok) {\n\t\tthrow new SelfstoreError(\n\t\t\t'UNSUPPORTED_VERSION',\n\t\t\t`Argon2 parameters out of the supported range (m=${m} KiB, t=${t}, p=${p}).`\n\t\t);\n\t}\n}\n\nfunction randomBytes(n: number): Uint8Array {\n\tconst b = new Uint8Array(n);\n\tcrypto.getRandomValues(b);\n\treturn b;\n}\n\n/** Coerce to the ArrayBuffer-backed view WebCrypto's types want (always safe here). */\nfunction src(u8: Uint8Array): Uint8Array<ArrayBuffer> {\n\treturn u8 as Uint8Array<ArrayBuffer>;\n}\n\n/**\n * Derive the AES-256-GCM key for `kdf`, imported non-extractable. Shared by\n * the backup cipher and the identity vault's passphrase lock.\n */\nexport async function deriveKey(password: string, kdf: KdfParams): Promise<CryptoKey> {\n\tconst raw = await deriveRaw(password, kdf);\n\treturn crypto.subtle.importKey('raw', src(raw), 'AES-GCM', false, ['encrypt', 'decrypt']);\n}\n\n/** Fresh salt at the current default cost, for a new password-sealed write. */\nexport function freshKdfParams(): KdfParams {\n\treturn { algo: 'argon2id', salt: toBase64(randomBytes(SALT_BYTES)), ...DEFAULT_ARGON2 };\n}\n\n// --- Password envelope (format 3) slots --------------------------------------\n\n/**\n * Each slot costs one Argon2id trial on open, so an unbounded list would let\n * a crafted file demand unbounded memory-hard work. Same spirit as\n * assertKdfBounds.\n */\nexport const MAX_KEY_SLOTS = 8;\n\n/** Wrap the 32-byte data key under a key derived from `password`, fresh salt and IV. */\nexport async function mintSlot(\n\tpassword: string,\n\tdataKey: Uint8Array,\n\tid?: string\n): Promise<PasswordSlot> {\n\tconst kdf = freshKdfParams();\n\tconst key = await deriveKey(password, kdf);\n\tconst iv = randomBytes(IV_BYTES);\n\tconst wrapped = await crypto.subtle.encrypt({ name: 'AES-GCM', iv: src(iv) }, key, src(dataKey));\n\treturn {\n\t\tid: id ?? toBase64(randomBytes(6)),\n\t\tkdf,\n\t\tiv: toBase64(iv),\n\t\twrapped: toBase64(new Uint8Array(wrapped))\n\t};\n}\n\n/**\n * Returns the data key if `password` opens this slot, else null. GCM keeps\n * wrong-password and tampering indistinguishable (fail closed). Bounds are\n * checked before any derivation, and that refusal is never swallowed - a\n * memory-hard bomb must not hide behind \"wrong password\".\n */\nexport async function openSlot(slot: PasswordSlot, password: string): Promise<Uint8Array | null> {\n\tassertKdfBounds(slot.kdf);\n\ttry {\n\t\tconst key = await deriveKey(password, slot.kdf);\n\t\tconst pt = await crypto.subtle.decrypt(\n\t\t\t{ name: 'AES-GCM', iv: src(fromBase64(slot.iv)) },\n\t\t\tkey,\n\t\t\tsrc(fromBase64(slot.wrapped))\n\t\t);\n\t\tconst dataKey = new Uint8Array(pt);\n\t\treturn dataKey.length === 32 ? dataKey : null;\n\t} catch (e) {\n\t\tif (e instanceof SelfstoreError) throw e;\n\t\treturn null;\n\t}\n}\n\n// --- Authenticated envelope (format 3) data seal -----------------------------\n\n/**\n * Seal the inner ZIP under the data key with the header bytes as AAD.\n * Generation 3 passes the exact meta.json bytes here, which binds the slot\n * table to the ciphertext: write access to the file is not enough to strip or\n * swap a key slot without breaking the tag. The caller generates the iv (it\n * rides in meta.json, hence inside the AAD) so seal and open agree byte for\n * byte.\n */\nexport async function gcmSealAad(\n\tkey32: Uint8Array,\n\tiv: Uint8Array,\n\tplaintext: Uint8Array,\n\taad: Uint8Array\n): Promise<Uint8Array> {\n\tconst key = await crypto.subtle.importKey('raw', src(key32), 'AES-GCM', false, ['encrypt']);\n\tconst ct = await crypto.subtle.encrypt(\n\t\t{ name: 'AES-GCM', iv: src(iv), additionalData: src(aad) },\n\t\tkey,\n\t\tsrc(plaintext)\n\t);\n\treturn new Uint8Array(ct);\n}\n\n/**\n * Open sealed data. `aad` is the additional authenticated data bound at seal\n * time (the envelope binds its header bytes); pass none for seals that bound\n * none. Any tag failure - wrong key, corrupt data, altered header - is one\n * DECRYPT_FAILED.\n */\nexport async function gcmOpenAad(\n\tkey32: Uint8Array,\n\tiv: Uint8Array,\n\tciphertext: Uint8Array,\n\taad?: Uint8Array\n): Promise<Uint8Array> {\n\ttry {\n\t\tconst key = await crypto.subtle.importKey('raw', src(key32), 'AES-GCM', false, ['decrypt']);\n\t\tconst params: AesGcmParams = { name: 'AES-GCM', iv: src(iv) };\n\t\tif (aad) params.additionalData = src(aad);\n\t\tconst pt = await crypto.subtle.decrypt(params, key, src(ciphertext));\n\t\treturn new Uint8Array(pt);\n\t} catch {\n\t\tthrow new SelfstoreError(\n\t\t\t'DECRYPT_FAILED',\n\t\t\t'Corrupted, wrong-key, or altered-header ciphertext.'\n\t\t);\n\t}\n}\n\n// --- External-key slots ------------------------------------------------------\n\n// A passkey PRF output is 32 bytes; anything under 16 is too weak to key a backup.\nconst MIN_EXTERNAL_SECRET = 16;\n\nconst EXTERNAL_INFO = new TextEncoder().encode('selfstore-external-slot-v1');\n\n/**\n * KEK for an external slot. The secret is already high-entropy (passkey PRF,\n * hardware token), so one HKDF-SHA256 expansion is enough - Argon2 only\n * exists to stretch low-entropy passwords. The fixed info label\n * domain-separates this use from any other the same secret might see.\n */\nasync function externalKek(secret: Uint8Array): Promise<CryptoKey> {\n\tconst ikm = await crypto.subtle.importKey('raw', src(secret), 'HKDF', false, ['deriveBits']);\n\tconst bits = await crypto.subtle.deriveBits(\n\t\t{ name: 'HKDF', hash: 'SHA-256', salt: new Uint8Array(0), info: src(EXTERNAL_INFO) },\n\t\tikm,\n\t\t256\n\t);\n\treturn crypto.subtle.importKey('raw', src(new Uint8Array(bits)), 'AES-GCM', false, [\n\t\t'encrypt',\n\t\t'decrypt'\n\t]);\n}\n\n/** Wrap the data key under a KEK derived from `secret`; `keyRef` is carried verbatim. */\nexport async function mintExternalSlot(\n\tsecret: Uint8Array,\n\tkeyRef: string,\n\tdataKey: Uint8Array,\n\tid?: string\n): Promise<ExternalSlot> {\n\tif (secret.length < MIN_EXTERNAL_SECRET) {\n\t\tthrow new TypeError(\n\t\t\t`mintExternalSlot(): external secret must be >= ${MIN_EXTERNAL_SECRET} bytes.`\n\t\t);\n\t}\n\tconst key = await externalKek(secret);\n\tconst iv = randomBytes(IV_BYTES);\n\tconst wrapped = await crypto.subtle.encrypt({ name: 'AES-GCM', iv: src(iv) }, key, src(dataKey));\n\treturn {\n\t\tid: id ?? toBase64(randomBytes(6)),\n\t\tkind: 'external',\n\t\tkeyRef,\n\t\tiv: toBase64(iv),\n\t\twrapped: toBase64(new Uint8Array(wrapped))\n\t};\n}\n\n/** Returns the data key if `secret` opens this slot, else null. Fail closed, like openSlot. */\nexport async function openExternalSlot(\n\tslot: ExternalSlot,\n\tsecret: Uint8Array\n): Promise<Uint8Array | null> {\n\tif (secret.length < MIN_EXTERNAL_SECRET) return null;\n\ttry {\n\t\tconst key = await externalKek(secret);\n\t\tconst pt = await crypto.subtle.decrypt(\n\t\t\t{ name: 'AES-GCM', iv: src(fromBase64(slot.iv)) },\n\t\t\tkey,\n\t\t\tsrc(fromBase64(slot.wrapped))\n\t\t);\n\t\tconst dataKey = new Uint8Array(pt);\n\t\treturn dataKey.length === 32 ? dataKey : null;\n\t} catch {\n\t\treturn null;\n\t}\n}\n"]}
@@ -12,18 +12,18 @@ async function load(what, mod) {
12
12
  }
13
13
  }
14
14
  async function webdavConnect(opts) {
15
- return (await load("WebDAV", () => import('./webdav-5SHNR7CL.js'))).connect(opts);
15
+ return (await load("WebDAV", () => import('./webdav-PXKELPA4.js'))).connect(opts);
16
16
  }
17
17
  async function webdavFromSession(opts) {
18
- return (await load("WebDAV", () => import('./webdav-5SHNR7CL.js'))).fromSession(opts);
18
+ return (await load("WebDAV", () => import('./webdav-PXKELPA4.js'))).fromSession(opts);
19
19
  }
20
20
  async function s3Connect(opts) {
21
- return (await load("S3", () => import('./s3-ABKUTTXE.js'))).connect(opts);
21
+ return (await load("S3", () => import('./s3-ORDHS7G3.js'))).connect(opts);
22
22
  }
23
23
  async function s3FromSession(opts) {
24
- return (await load("S3", () => import('./s3-ABKUTTXE.js'))).fromSession(opts);
24
+ return (await load("S3", () => import('./s3-ORDHS7G3.js'))).fromSession(opts);
25
25
  }
26
26
 
27
27
  export { s3Connect, s3FromSession, webdavConnect, webdavFromSession };
28
- //# sourceMappingURL=chunk-FQ5O4PHK.js.map
29
- //# sourceMappingURL=chunk-FQ5O4PHK.js.map
28
+ //# sourceMappingURL=chunk-JDDWWQEB.js.map
29
+ //# sourceMappingURL=chunk-JDDWWQEB.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/persistence/targets/servers.ts"],"names":[],"mappings":";;;AA4BA,eAAe,IAAA,CAAQ,MAAc,GAAA,EAAmC;AACvE,EAAA,IAAI;AACH,IAAA,OAAO,MAAM,GAAA,EAAI;AAAA,EAClB,SAAS,CAAA,EAAG;AACX,IAAA,MAAM,IAAI,cAAA;AAAA,MACT,oBAAA;AAAA,MACA,CAAA,IAAA,EAAO,IAAI,CAAA,8BAAA,EAAiC,CAAA,YAAa,QAAQ,CAAA,CAAE,OAAA,GAAU,MAAA,CAAO,CAAC,CAAC,CAAA,EAAA;AAAA,KACvF;AAAA,EACD;AACD;AAEA,eAAsB,cAAc,IAAA,EAA0D;AAC7F,EAAA,OAAA,CAAQ,MAAM,KAAK,QAAA,EAAU,MAAM,OAAO,sBAAU,CAAC,CAAA,EAAG,OAAA,CAAQ,IAAI,CAAA;AACrE;AAEA,eAAsB,kBAAkB,IAAA,EAAgD;AACvF,EAAA,OAAA,CAAQ,MAAM,KAAK,QAAA,EAAU,MAAM,OAAO,sBAAU,CAAC,CAAA,EAAG,WAAA,CAAY,IAAI,CAAA;AACzE;AAEA,eAAsB,UAAU,IAAA,EAAsD;AACrF,EAAA,OAAA,CAAQ,MAAM,KAAK,IAAA,EAAM,MAAM,OAAO,kBAAM,CAAC,CAAA,EAAG,OAAA,CAAQ,IAAI,CAAA;AAC7D;AAEA,eAAsB,cAAc,IAAA,EAAgD;AACnF,EAAA,OAAA,CAAQ,MAAM,KAAK,IAAA,EAAM,MAAM,OAAO,kBAAM,CAAC,CAAA,EAAG,WAAA,CAAY,IAAI,CAAA;AACjE","file":"chunk-FQ5O4PHK.js","sourcesContent":["// The two destinations that reach a server the user runs - WebDAV and an\n// S3-compatible bucket - loaded on the gesture that uses them instead of with\n// the module that offers them.\n//\n// Why these two and not all four: Drive and the disk file both open a picker or\n// a consent popup, and a browser only allows that inside the transient\n// activation of the user's click. An `await import()` before the call can spend\n// that activation and turn a working button into a silently blocked popup. A\n// WebDAV or S3 connection is plain fetch behind a form the user already\n// submitted - nothing to preserve, so the chunk can arrive late.\n//\n// What it buys: an app that offers Drive and a disk file - the common case -\n// stops shipping the SigV4 request signer and the WebDAV client. Before this,\n// importing the connect journey (so: any of the widgets) pulled both in whether\n// or not the app named them, which made \"destinations are opt-in\" true of the\n// API and false of the bytes.\n//\n// `selfstore/advanced` keeps its STATIC `webdavTarget` / `s3Target`: naming one\n// of those is the opt-in, and an app that wrote the import wants the code.\n\nimport { SelfstoreError } from '../../selfstore/errors';\nimport type { BackupTarget } from '../target';\nimport type { KV } from '../cache';\nimport type { WebdavConnectOptions } from './webdav';\nimport type { S3ConnectOptions } from './s3';\n\n/** A chunk that does not arrive is the destination not answering: same code,\n * same retry, rather than a bare Error escaping a public path. */\nasync function load<T>(what: string, mod: () => Promise<T>): Promise<T> {\n\ttry {\n\t\treturn await mod();\n\t} catch (e) {\n\t\tthrow new SelfstoreError(\n\t\t\t'TARGET_UNAVAILABLE',\n\t\t\t`The ${what} support could not be loaded (${e instanceof Error ? e.message : String(e)}).`\n\t\t);\n\t}\n}\n\nexport async function webdavConnect(opts: WebdavConnectOptions): Promise<BackupTarget | null> {\n\treturn (await load('WebDAV', () => import('./webdav'))).connect(opts);\n}\n\nexport async function webdavFromSession(opts: { kv: KV }): Promise<BackupTarget | null> {\n\treturn (await load('WebDAV', () => import('./webdav'))).fromSession(opts);\n}\n\nexport async function s3Connect(opts: S3ConnectOptions): Promise<BackupTarget | null> {\n\treturn (await load('S3', () => import('./s3'))).connect(opts);\n}\n\nexport async function s3FromSession(opts: { kv: KV }): Promise<BackupTarget | null> {\n\treturn (await load('S3', () => import('./s3'))).fromSession(opts);\n}\n"]}
1
+ {"version":3,"sources":["../src/persistence/targets/servers.ts"],"names":[],"mappings":";;;AA4BA,eAAe,IAAA,CAAQ,MAAc,GAAA,EAAmC;AACvE,EAAA,IAAI;AACH,IAAA,OAAO,MAAM,GAAA,EAAI;AAAA,EAClB,SAAS,CAAA,EAAG;AACX,IAAA,MAAM,IAAI,cAAA;AAAA,MACT,oBAAA;AAAA,MACA,CAAA,IAAA,EAAO,IAAI,CAAA,8BAAA,EAAiC,CAAA,YAAa,QAAQ,CAAA,CAAE,OAAA,GAAU,MAAA,CAAO,CAAC,CAAC,CAAA,EAAA;AAAA,KACvF;AAAA,EACD;AACD;AAEA,eAAsB,cAAc,IAAA,EAA0D;AAC7F,EAAA,OAAA,CAAQ,MAAM,KAAK,QAAA,EAAU,MAAM,OAAO,sBAAU,CAAC,CAAA,EAAG,OAAA,CAAQ,IAAI,CAAA;AACrE;AAEA,eAAsB,kBAAkB,IAAA,EAAgD;AACvF,EAAA,OAAA,CAAQ,MAAM,KAAK,QAAA,EAAU,MAAM,OAAO,sBAAU,CAAC,CAAA,EAAG,WAAA,CAAY,IAAI,CAAA;AACzE;AAEA,eAAsB,UAAU,IAAA,EAAsD;AACrF,EAAA,OAAA,CAAQ,MAAM,KAAK,IAAA,EAAM,MAAM,OAAO,kBAAM,CAAC,CAAA,EAAG,OAAA,CAAQ,IAAI,CAAA;AAC7D;AAEA,eAAsB,cAAc,IAAA,EAAgD;AACnF,EAAA,OAAA,CAAQ,MAAM,KAAK,IAAA,EAAM,MAAM,OAAO,kBAAM,CAAC,CAAA,EAAG,WAAA,CAAY,IAAI,CAAA;AACjE","file":"chunk-JDDWWQEB.js","sourcesContent":["// The two destinations that reach a server the user runs - WebDAV and an\n// S3-compatible bucket - loaded on the gesture that uses them instead of with\n// the module that offers them.\n//\n// Why these two and not all four: Drive and the disk file both open a picker or\n// a consent popup, and a browser only allows that inside the transient\n// activation of the user's click. An `await import()` before the call can spend\n// that activation and turn a working button into a silently blocked popup. A\n// WebDAV or S3 connection is plain fetch behind a form the user already\n// submitted - nothing to preserve, so the chunk can arrive late.\n//\n// What it buys: an app that offers Drive and a disk file - the common case -\n// stops shipping the SigV4 request signer and the WebDAV client. Before this,\n// importing the connect journey (so: any of the widgets) pulled both in whether\n// or not the app named them, which made \"destinations are opt-in\" true of the\n// API and false of the bytes.\n//\n// `selfstore/advanced` keeps its STATIC `webdavTarget` / `s3Target`: naming one\n// of those is the opt-in, and an app that wrote the import wants the code.\n\nimport { SelfstoreError } from '../../selfstore/errors';\nimport type { BackupTarget } from '../target';\nimport type { KV } from '../cache';\nimport type { WebdavConnectOptions } from './webdav';\nimport type { S3ConnectOptions } from './s3';\n\n/** A chunk that does not arrive is the destination not answering: same code,\n * same retry, rather than a bare Error escaping a public path. */\nasync function load<T>(what: string, mod: () => Promise<T>): Promise<T> {\n\ttry {\n\t\treturn await mod();\n\t} catch (e) {\n\t\tthrow new SelfstoreError(\n\t\t\t'TARGET_UNAVAILABLE',\n\t\t\t`The ${what} support could not be loaded (${e instanceof Error ? e.message : String(e)}).`\n\t\t);\n\t}\n}\n\nexport async function webdavConnect(opts: WebdavConnectOptions): Promise<BackupTarget | null> {\n\treturn (await load('WebDAV', () => import('./webdav'))).connect(opts);\n}\n\nexport async function webdavFromSession(opts: { kv: KV }): Promise<BackupTarget | null> {\n\treturn (await load('WebDAV', () => import('./webdav'))).fromSession(opts);\n}\n\nexport async function s3Connect(opts: S3ConnectOptions): Promise<BackupTarget | null> {\n\treturn (await load('S3', () => import('./s3'))).connect(opts);\n}\n\nexport async function s3FromSession(opts: { kv: KV }): Promise<BackupTarget | null> {\n\treturn (await load('S3', () => import('./s3'))).fromSession(opts);\n}\n"]}
@@ -1,4 +1,4 @@
1
- import { BACKUP_EXTENSION, BACKUP_MIME } from './chunk-RVOBONZF.js';
1
+ import { BACKUP_EXTENSION, BACKUP_MIME } from './chunk-7U5XMKVP.js';
2
2
 
3
3
  // src/selfstore/targets/local.ts
4
4
  async function pickSaveHandle(filename) {
@@ -61,5 +61,5 @@ function pickFromDisk() {
61
61
  }
62
62
 
63
63
  export { datedName, downloadBlob, pickFromDisk, pickSaveHandle, saveToDisk, writeToHandle };
64
- //# sourceMappingURL=chunk-LQS7AMOD.js.map
65
- //# sourceMappingURL=chunk-LQS7AMOD.js.map
64
+ //# sourceMappingURL=chunk-Q3NO477P.js.map
65
+ //# sourceMappingURL=chunk-Q3NO477P.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/selfstore/targets/local.ts"],"names":[],"mappings":"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","file":"chunk-LQS7AMOD.js","sourcesContent":["/**\n * Local-disk helpers: the DOM edge of selfstore, kept out of the core so the\n * core stays environment-agnostic and testable.\n */\n\nimport { BACKUP_EXTENSION, BACKUP_MIME } from '../index';\n\n/** The slice of FileSystemFileHandle this module needs. */\nexport interface SaveHandle {\n\tcreateWritable(): Promise<{ write(d: Blob): Promise<void>; close(): Promise<void> }>;\n}\n\n/**\n * Ask the user where to save, and nothing else.\n *\n * Split out because ORDER decides whether the dialog opens at all. The picker\n * needs the transient user activation of the click, and that activation expires\n * while an encrypted backup is being built (Argon2id is deliberately slow). Ask\n * first, build after, and the dialog is still allowed; build first and the\n * browser refuses it. Hosts that saw a silent download instead of the save\n * dialog were hitting exactly that.\n *\n * Null when the browser has no picker at all (Firefox, Safari) or when it\n * refuses for any reason other than a cancellation - the caller then falls back\n * to a download. Throws nothing; a cancelled dialog is reported as CANCELLED.\n */\nexport async function pickSaveHandle(\n\tfilename: string\n): Promise<{ handle: SaveHandle } | 'CANCELLED' | null> {\n\tconst picker = (\n\t\twindow as unknown as {\n\t\t\tshowSaveFilePicker?: (o: unknown) => Promise<SaveHandle>;\n\t\t}\n\t).showSaveFilePicker;\n\tif (!picker) return null;\n\ttry {\n\t\tconst handle = await picker({\n\t\t\tsuggestedName: filename,\n\t\t\ttypes: [{ description: 'selfstore backup', accept: { [BACKUP_MIME]: [BACKUP_EXTENSION] } }]\n\t\t});\n\t\treturn { handle };\n\t} catch (e) {\n\t\tif (e instanceof DOMException && e.name === 'AbortError') return 'CANCELLED';\n\t\t// Anything else (no activation left, policy): the download still works.\n\t\treturn null;\n\t}\n}\n\n/** Write a blob through a handle obtained earlier by pickSaveHandle. */\nexport async function writeToHandle(handle: SaveHandle, blob: Blob): Promise<void> {\n\tconst writable = await handle.createWritable();\n\tawait writable.write(blob);\n\tawait writable.close();\n}\n\n/** Hand the blob to the browser's download machinery. No cancel to observe:\n * once taken, the host has done all it can. */\nexport function downloadBlob(blob: Blob, filename: string): void {\n\tconst url = URL.createObjectURL(blob);\n\tconst a = document.createElement('a');\n\ta.href = url;\n\ta.download = filename;\n\tdocument.body.appendChild(a);\n\ta.click();\n\ta.remove();\n\tURL.revokeObjectURL(url);\n}\n\n/**\n * Save a backup blob to disk via the File System Access API, else a download.\n *\n * Resolves TRUE when bytes were handed to the browser, FALSE when the user\n * closed the save dialog. Callers must branch on it: a host that records \"last\n * backup: today\" after a cancelled dialog tells the user they are safe when no\n * file exists, and that lie only surfaces the day they need the file.\n *\n * Takes a ready blob, so the dialog opens only after the blob was built. When\n * that blob is an encrypted backup, prefer asking first: see pickSaveHandle.\n */\nexport async function saveToDisk(blob: Blob, filename: string): Promise<boolean> {\n\tconst picked = await pickSaveHandle(filename);\n\tif (picked === 'CANCELLED') return false;\n\tif (picked) {\n\t\tawait writeToHandle(picked.handle, blob);\n\t\treturn true;\n\t}\n\tdownloadBlob(blob, datedName(filename));\n\treturn true;\n}\n\n/**\n * The same name, stamped to the minute.\n *\n * Used only on the download path, and the reasoning is worth stating because it\n * looks backwards. Through a handle the app rewrites the SAME file, so the name\n * must stay stable. A download never replaces anything: the browser appends\n * \" (1)\", \" (2)\", and no web API can stop it. The pile forms either way - so it\n * may as well be readable. A dated series says at a glance which one is the\n * latest and lets the user step back to an earlier one; \"backup (7).zip\" says\n * neither when, nor in what order.\n */\nexport function datedName(filename: string, when = new Date()): string {\n\tconst deux = (n: number) => String(n).padStart(2, '0');\n\tconst stamp =\n\t\t`${when.getFullYear()}-${deux(when.getMonth() + 1)}-${deux(when.getDate())}` +\n\t\t`-${deux(when.getHours())}h${deux(when.getMinutes())}`;\n\tconst dot = filename.lastIndexOf('.');\n\treturn dot <= 0\n\t\t? `${filename}-${stamp}`\n\t\t: `${filename.slice(0, dot)}-${stamp}${filename.slice(dot)}`;\n}\n\n/** Prompt the user to pick a backup file from disk. Resolves null if cancelled. */\nexport function pickFromDisk(): Promise<File | null> {\n\treturn new Promise((resolve) => {\n\t\tconst input = document.createElement('input');\n\t\tinput.type = 'file';\n\t\t// octet-stream: some providers serve a downloaded ZIP under that type.\n\t\tinput.accept = '.zip,application/zip,application/octet-stream';\n\t\tinput.onchange = () => resolve(input.files?.[0] ?? null);\n\t\t// Without this, a cancelled dialog leaves the promise pending forever.\n\t\tinput.oncancel = () => resolve(null);\n\t\t// Some browsers need the input in the DOM for the dialog to open.\n\t\tinput.style.display = 'none';\n\t\tdocument.body.appendChild(input);\n\t\tinput.click();\n\t\tinput.remove();\n\t});\n}\n"]}
1
+ {"version":3,"sources":["../src/selfstore/targets/local.ts"],"names":[],"mappings":"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","file":"chunk-Q3NO477P.js","sourcesContent":["/**\n * Local-disk helpers: the DOM edge of selfstore, kept out of the core so the\n * core stays environment-agnostic and testable.\n */\n\nimport { BACKUP_EXTENSION, BACKUP_MIME } from '../index';\n\n/** The slice of FileSystemFileHandle this module needs. */\nexport interface SaveHandle {\n\tcreateWritable(): Promise<{ write(d: Blob): Promise<void>; close(): Promise<void> }>;\n}\n\n/**\n * Ask the user where to save, and nothing else.\n *\n * Split out because ORDER decides whether the dialog opens at all. The picker\n * needs the transient user activation of the click, and that activation expires\n * while an encrypted backup is being built (Argon2id is deliberately slow). Ask\n * first, build after, and the dialog is still allowed; build first and the\n * browser refuses it. Hosts that saw a silent download instead of the save\n * dialog were hitting exactly that.\n *\n * Null when the browser has no picker at all (Firefox, Safari) or when it\n * refuses for any reason other than a cancellation - the caller then falls back\n * to a download. Throws nothing; a cancelled dialog is reported as CANCELLED.\n */\nexport async function pickSaveHandle(\n\tfilename: string\n): Promise<{ handle: SaveHandle } | 'CANCELLED' | null> {\n\tconst picker = (\n\t\twindow as unknown as {\n\t\t\tshowSaveFilePicker?: (o: unknown) => Promise<SaveHandle>;\n\t\t}\n\t).showSaveFilePicker;\n\tif (!picker) return null;\n\ttry {\n\t\tconst handle = await picker({\n\t\t\tsuggestedName: filename,\n\t\t\ttypes: [{ description: 'selfstore backup', accept: { [BACKUP_MIME]: [BACKUP_EXTENSION] } }]\n\t\t});\n\t\treturn { handle };\n\t} catch (e) {\n\t\tif (e instanceof DOMException && e.name === 'AbortError') return 'CANCELLED';\n\t\t// Anything else (no activation left, policy): the download still works.\n\t\treturn null;\n\t}\n}\n\n/** Write a blob through a handle obtained earlier by pickSaveHandle. */\nexport async function writeToHandle(handle: SaveHandle, blob: Blob): Promise<void> {\n\tconst writable = await handle.createWritable();\n\tawait writable.write(blob);\n\tawait writable.close();\n}\n\n/** Hand the blob to the browser's download machinery. No cancel to observe:\n * once taken, the host has done all it can. */\nexport function downloadBlob(blob: Blob, filename: string): void {\n\tconst url = URL.createObjectURL(blob);\n\tconst a = document.createElement('a');\n\ta.href = url;\n\ta.download = filename;\n\tdocument.body.appendChild(a);\n\ta.click();\n\ta.remove();\n\tURL.revokeObjectURL(url);\n}\n\n/**\n * Save a backup blob to disk via the File System Access API, else a download.\n *\n * Resolves TRUE when bytes were handed to the browser, FALSE when the user\n * closed the save dialog. Callers must branch on it: a host that records \"last\n * backup: today\" after a cancelled dialog tells the user they are safe when no\n * file exists, and that lie only surfaces the day they need the file.\n *\n * Takes a ready blob, so the dialog opens only after the blob was built. When\n * that blob is an encrypted backup, prefer asking first: see pickSaveHandle.\n */\nexport async function saveToDisk(blob: Blob, filename: string): Promise<boolean> {\n\tconst picked = await pickSaveHandle(filename);\n\tif (picked === 'CANCELLED') return false;\n\tif (picked) {\n\t\tawait writeToHandle(picked.handle, blob);\n\t\treturn true;\n\t}\n\tdownloadBlob(blob, datedName(filename));\n\treturn true;\n}\n\n/**\n * The same name, stamped to the minute.\n *\n * Used only on the download path, and the reasoning is worth stating because it\n * looks backwards. Through a handle the app rewrites the SAME file, so the name\n * must stay stable. A download never replaces anything: the browser appends\n * \" (1)\", \" (2)\", and no web API can stop it. The pile forms either way - so it\n * may as well be readable. A dated series says at a glance which one is the\n * latest and lets the user step back to an earlier one; \"backup (7).zip\" says\n * neither when, nor in what order.\n */\nexport function datedName(filename: string, when = new Date()): string {\n\tconst deux = (n: number) => String(n).padStart(2, '0');\n\tconst stamp =\n\t\t`${when.getFullYear()}-${deux(when.getMonth() + 1)}-${deux(when.getDate())}` +\n\t\t`-${deux(when.getHours())}h${deux(when.getMinutes())}`;\n\tconst dot = filename.lastIndexOf('.');\n\treturn dot <= 0\n\t\t? `${filename}-${stamp}`\n\t\t: `${filename.slice(0, dot)}-${stamp}${filename.slice(dot)}`;\n}\n\n/** Prompt the user to pick a backup file from disk. Resolves null if cancelled. */\nexport function pickFromDisk(): Promise<File | null> {\n\treturn new Promise((resolve) => {\n\t\tconst input = document.createElement('input');\n\t\tinput.type = 'file';\n\t\t// octet-stream: some providers serve a downloaded ZIP under that type.\n\t\tinput.accept = '.zip,application/zip,application/octet-stream';\n\t\tinput.onchange = () => resolve(input.files?.[0] ?? null);\n\t\t// Without this, a cancelled dialog leaves the promise pending forever.\n\t\tinput.oncancel = () => resolve(null);\n\t\t// Some browsers need the input in the DOM for the dialog to open.\n\t\tinput.style.display = 'none';\n\t\tdocument.body.appendChild(input);\n\t\tinput.click();\n\t\tinput.remove();\n\t});\n}\n"]}
package/dist/flows.js CHANGED
@@ -1,13 +1,13 @@
1
- export { connectFlow, joinFlow, shareFlow } from './chunk-2PIYIIHY.js';
2
- import { s3Connect, webdavConnect, webdavFromSession, s3FromSession } from './chunk-FQ5O4PHK.js';
1
+ export { connectFlow, joinFlow, shareFlow } from './chunk-A6OQP7DR.js';
2
+ import { s3Connect, webdavConnect, webdavFromSession, s3FromSession } from './chunk-JDDWWQEB.js';
3
3
  import { connect as connect$1, fromSession as fromSession$1 } from './chunk-2TIRUEXR.js';
4
4
  import { makeMachine, toStoreError } from './chunk-2BNTCXQN.js';
5
5
  export { withDeadline } from './chunk-2BNTCXQN.js';
6
6
  import { connect, fromSession } from './chunk-25U6AKIX.js';
7
7
  import './chunk-LF3CC2P3.js';
8
- import './chunk-RVOBONZF.js';
8
+ import './chunk-7U5XMKVP.js';
9
9
  import './chunk-F4ZZEJ5H.js';
10
- import './chunk-5UYPKCDB.js';
10
+ import './chunk-H2T4S3KM.js';
11
11
  import './chunk-WXI2NAPA.js';
12
12
  import './chunk-CSYEIXNY.js';
13
13
 
package/dist/groups.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { seal, isEnvelope, unseal, newDeviceKey } from './chunk-QBCU7OOP.js';
2
2
  import { generateIdentity } from './chunk-F4ZZEJ5H.js';
3
3
  export { GROUP_KEYING, generateIdentity, groupCryptoAvailable, keyId, newGroupId, openManifest, publicIdentity, signManifest } from './chunk-F4ZZEJ5H.js';
4
- import { assertKdfBounds, deriveKey, freshKdfParams } from './chunk-5UYPKCDB.js';
4
+ import { assertKdfBounds, deriveKey, freshKdfParams } from './chunk-H2T4S3KM.js';
5
5
  import './chunk-WXI2NAPA.js';
6
6
  import { SelfstoreError } from './chunk-CSYEIXNY.js';
7
7
 
package/dist/index.js CHANGED
@@ -1,19 +1,19 @@
1
- import { indexedDbCache, memoryCache, gisDriveAuth, isLockableCache, createLocalStore } from './chunk-AZWS7YC3.js';
2
- export { gisDriveAuth, preloadGoogleIdentity } from './chunk-AZWS7YC3.js';
1
+ import { indexedDbCache, memoryCache, gisDriveAuth, isLockableCache, createLocalStore } from './chunk-GM47BISW.js';
2
+ export { gisDriveAuth, preloadGoogleIdentity } from './chunk-GM47BISW.js';
3
3
  import './chunk-GSZSZGSD.js';
4
- import { saveToDisk } from './chunk-LQS7AMOD.js';
5
- export { pickFromDisk, saveToDisk } from './chunk-LQS7AMOD.js';
4
+ import { saveToDisk } from './chunk-Q3NO477P.js';
5
+ export { pickFromDisk, saveToDisk } from './chunk-Q3NO477P.js';
6
6
  import './chunk-FJGWTBAL.js';
7
- import { webdavFromSession, s3Connect, webdavConnect } from './chunk-FQ5O4PHK.js';
7
+ import { webdavFromSession, s3Connect, webdavConnect } from './chunk-JDDWWQEB.js';
8
8
  import { fromSession as fromSession$1, isSupported, connect } from './chunk-2TIRUEXR.js';
9
9
  export { hasDesktopFiles, useDesktopFiles } from './chunk-2TIRUEXR.js';
10
10
  import { fromSession, connect as connect$1, account } from './chunk-25U6AKIX.js';
11
11
  import './chunk-LF3CC2P3.js';
12
- import { restore } from './chunk-RVOBONZF.js';
13
- export { BACKUP_EXTENSION, BACKUP_MIME, backup, changePassword, checkPasswordPolicy, restore } from './chunk-RVOBONZF.js';
12
+ import { restore } from './chunk-7U5XMKVP.js';
13
+ export { BACKUP_EXTENSION, BACKUP_MIME, backup, changePassword, checkPasswordPolicy, restore } from './chunk-7U5XMKVP.js';
14
14
  import './chunk-QBCU7OOP.js';
15
15
  import './chunk-F4ZZEJ5H.js';
16
- import './chunk-5UYPKCDB.js';
16
+ import './chunk-H2T4S3KM.js';
17
17
  import { toBytes } from './chunk-SJ3PKQPR.js';
18
18
  import { sha256Hex } from './chunk-QSLHEE2L.js';
19
19
  import './chunk-WXI2NAPA.js';
@@ -365,7 +365,7 @@ async function selfstore(app, options = {}) {
365
365
  async backups() {
366
366
  if (!driveAuth) return null;
367
367
  backupsManager ??= await (async () => {
368
- const { createBackupsManager, driveBackupsHost } = await import('./manager-drive-2IUE4WQ2.js');
368
+ const { createBackupsManager, driveBackupsHost } = await import('./manager-drive-T3RXUWAY.js');
369
369
  return createBackupsManager({
370
370
  store,
371
371
  kv: cache.kv,