selfstore 1.0.0 → 1.0.1
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.
- package/README.md +4 -0
- package/dist/advanced.js +7 -7
- package/dist/backups.js +1 -1
- package/dist/backups.js.map +1 -1
- package/dist/{cache-lock-LZILZNLP.js → cache-lock-NXIR6JRB.js} +3 -3
- package/dist/{cache-lock-LZILZNLP.js.map → cache-lock-NXIR6JRB.js.map} +1 -1
- package/dist/{chunk-SGISYMT5.js → chunk-43BJDRPT.js} +2 -2
- package/dist/{chunk-SGISYMT5.js.map → chunk-43BJDRPT.js.map} +1 -1
- package/dist/{chunk-N4RPMX3Y.js → chunk-5AAWNPCP.js} +119 -30
- package/dist/chunk-5AAWNPCP.js.map +1 -0
- package/dist/{chunk-FDPAKHQS.js → chunk-6746AL4I.js} +9 -4
- package/dist/chunk-6746AL4I.js.map +1 -0
- package/dist/{chunk-OWLX6PNT.js → chunk-KHER6MWR.js} +26 -8
- package/dist/chunk-KHER6MWR.js.map +1 -0
- package/dist/{chunk-CUWB5EWF.js → chunk-P34WACTD.js} +12 -7
- package/dist/chunk-P34WACTD.js.map +1 -0
- package/dist/{chunk-IBIEKK36.js → chunk-SKWHKM25.js} +24 -5
- package/dist/chunk-SKWHKM25.js.map +1 -0
- package/dist/{chunk-JB4JHM7V.js → chunk-SRKUHFRG.js} +17 -8
- package/dist/chunk-SRKUHFRG.js.map +1 -0
- package/dist/{chunk-BJ3RUMGI.js → chunk-UWVAKBU4.js} +3 -3
- package/dist/{chunk-BJ3RUMGI.js.map → chunk-UWVAKBU4.js.map} +1 -1
- package/dist/{chunk-ATUPHVLG.js → chunk-Z2MRZSEP.js} +5 -5
- package/dist/chunk-Z2MRZSEP.js.map +1 -0
- package/dist/flows.js +7 -7
- package/dist/flows.js.map +1 -1
- package/dist/groups.js +3 -3
- package/dist/groups.js.map +1 -1
- package/dist/households.js +10 -4
- package/dist/households.js.map +1 -1
- package/dist/index.js +14 -11
- package/dist/index.js.map +1 -1
- package/dist/local-KIP25T7I.js +7 -0
- package/dist/{local-AM22RWRR.js.map → local-KIP25T7I.js.map} +1 -1
- package/dist/sync.js +1 -1
- package/dist/widgets.js +445 -86
- package/dist/widgets.js.map +1 -1
- package/package.json +129 -97
- package/dist/chunk-ATUPHVLG.js.map +0 -1
- package/dist/chunk-CUWB5EWF.js.map +0 -1
- package/dist/chunk-FDPAKHQS.js.map +0 -1
- package/dist/chunk-IBIEKK36.js.map +0 -1
- package/dist/chunk-JB4JHM7V.js.map +0 -1
- package/dist/chunk-N4RPMX3Y.js.map +0 -1
- package/dist/chunk-OWLX6PNT.js.map +0 -1
- package/dist/local-AM22RWRR.js +0 -7
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/selfstore/base64.ts","../src/selfstore/argon2.ts","../src/selfstore/kdf.ts","../src/selfstore/crypto.ts"],"names":[],"mappings":"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","file":"chunk-6746AL4I.js","sourcesContent":["/**\n * Base64 <-> bytes, shared by the crypto layer and the KDF worker (the worker\n * bundle must not drag the whole crypto module in for two helpers).\n */\n\nexport function toBase64(bytes: Uint8Array): string {\n\tlet bin = '';\n\tfor (const b of bytes) bin += String.fromCharCode(b);\n\treturn btoa(bin);\n}\n\nexport function fromBase64(s: string): Uint8Array {\n\tconst bin = atob(s);\n\tconst out = new Uint8Array(bin.length);\n\tfor (let i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i);\n\treturn out;\n}\n","// The one Argon2id call, shared by the calling-thread fallback and the KDF\n// worker so both derive byte-identical keys. hash-wasm loads dynamically -\n// only when a password is actually used.\n\nimport { fromBase64 } from './base64';\n\n/** AES-256 key length. */\nexport const KEY_BYTES = 32;\n\n/** One key-derivation job (postMessage-friendly: plain fields only). */\nexport interface KdfJob {\n\tid: number;\n\tpassword: string;\n\t/** Base64, exactly as stored in the backup header. */\n\tsalt: string;\n\t/** Memory cost in KiB. */\n\tm: number;\n\t/** Iterations (passes). */\n\tt: number;\n\t/** Parallelism (lanes). */\n\tp: number;\n}\n\n/** The worker's reply: the raw key, or the failure message. */\nexport interface KdfReply {\n\tid: number;\n\tkey?: Uint8Array;\n\terror?: string;\n}\n\nexport async function runKdfJob(job: KdfJob): Promise<Uint8Array> {\n\tconst { argon2id } = await import('hash-wasm');\n\treturn argon2id({\n\t\tpassword: job.password,\n\t\tsalt: fromBase64(job.salt),\n\t\tparallelism: job.p,\n\t\titerations: job.t,\n\t\tmemorySize: job.m,\n\t\thashLength: KEY_BYTES,\n\t\toutputType: 'binary'\n\t});\n}\n","// Off-main-thread key derivation. Argon2id takes about a second at the\n// default cost, which would jank every encrypted push and pull if it ran on\n// the UI thread. One lazy dedicated worker, terminated after an idle window\n// so its ~46 MiB of WASM memory is not held forever. Any failure to spawn,\n// load, or answer falls back to the calling thread, which runs the exact\n// same runKdfJob - the worker is a responsiveness optimization, never a\n// correctness dependency.\n\nimport { runKdfJob, type KdfJob, type KdfReply } from './argon2';\nimport type { KdfParams } from './types';\n\n// Generous: a wedged worker is rerun on the calling thread, and Argon2id at\n// the default cost already takes a second or two on a phone.\nconst JOB_TIMEOUT_MS = 30_000;\nconst IDLE_TERMINATE_MS = 30_000;\n\ninterface Pending {\n\tjob: KdfJob;\n\tresolve: (key: Uint8Array) => void;\n\treject: (e: unknown) => void;\n\ttimer: ReturnType<typeof setTimeout>;\n}\n\nlet worker: Worker | null = null;\n// Once the worker path fails (blocked, missing asset), stay on the calling\n// thread for the rest of the session instead of failing again.\nlet broken = false;\nlet seq = 0;\nconst pending = new Map<number, Pending>();\nlet idleTimer: ReturnType<typeof setTimeout> | undefined;\n\nfunction armIdleStop(): void {\n\tclearTimeout(idleTimer);\n\tidleTimer = setTimeout(() => {\n\t\tif (pending.size === 0 && worker) {\n\t\t\tworker.terminate();\n\t\t\tworker = null; // respawned on the next job\n\t\t}\n\t}, IDLE_TERMINATE_MS);\n}\n\n/** Abandon the worker and finish every queued job on the calling thread. */\nfunction fallBack(): void {\n\tbroken = true;\n\tworker?.terminate();\n\tworker = null;\n\tclearTimeout(idleTimer);\n\tfor (const p of pending.values()) {\n\t\tclearTimeout(p.timer);\n\t\trunKdfJob(p.job).then(p.resolve, p.reject);\n\t}\n\tpending.clear();\n}\n\nfunction ensureWorker(): Worker | null {\n\tif (broken) return null;\n\tif (worker) return worker;\n\tif (typeof Worker === 'undefined') {\n\t\tbroken = true; // Node, old runtimes\n\t\treturn null;\n\t}\n\ttry {\n\t\t// Bundlers (Vite, webpack 5, Rollup) detect this exact\n\t\t// `new Worker(new URL(...), import.meta.url)` shape to ship the worker\n\t\t// file as an asset - do not refactor it away.\n\t\tworker = new Worker(new URL('./kdf-worker.js', import.meta.url), {\n\t\t\ttype: 'module',\n\t\t\tname: 'selfstore-kdf'\n\t\t});\n\t} catch {\n\t\tbroken = true;\n\t\treturn null;\n\t}\n\tworker.onmessage = (ev: MessageEvent<KdfReply>): void => {\n\t\tconst reply = ev.data;\n\t\tconst p = pending.get(reply.id);\n\t\tif (!p) return;\n\t\tpending.delete(reply.id);\n\t\tclearTimeout(p.timer);\n\t\tif (reply.key) p.resolve(reply.key);\n\t\telse p.reject(new Error(reply.error ?? 'key derivation failed in the worker'));\n\t\tarmIdleStop();\n\t};\n\t// Script 404, parse error, CSP: rerun the queued jobs locally.\n\tworker.onerror = (): void => fallBack();\n\treturn worker;\n}\n\n/** Derive the raw AES key for `kdf`, off the main thread when possible. */\nexport function deriveRaw(password: string, kdf: KdfParams): Promise<Uint8Array> {\n\tconst job: KdfJob = { id: ++seq, password, salt: kdf.salt, m: kdf.m, t: kdf.t, p: kdf.p };\n\tconst w = ensureWorker();\n\tif (!w) return runKdfJob(job);\n\treturn new Promise<Uint8Array>((resolve, reject) => {\n\t\tconst timer = setTimeout(() => {\n\t\t\t// fallBack() clears every OTHER pending timer, so no job runs twice.\n\t\t\tpending.delete(job.id);\n\t\t\tfallBack();\n\t\t\trunKdfJob(job).then(resolve, reject);\n\t\t}, JOB_TIMEOUT_MS);\n\t\tpending.set(job.id, { job, resolve, reject, timer });\n\t\tw.postMessage(job);\n\t});\n}\n","// AES-256-GCM via WebCrypto over an Argon2id-derived key (hash-wasm). The\n// derivation runs in a worker when the platform has one (see kdf.ts); the\n// WASM is imported dynamically so it only loads when a password is used.\n\nimport type { KdfParams, PasswordSlot, ExternalSlot } from './types';\nimport { deriveRaw } from './kdf';\nimport { toBase64, fromBase64 } from './base64';\nimport { SelfstoreError } from './errors';\n\nexport { toBase64, fromBase64 };\n\nconst IV_BYTES = 12;\nconst SALT_BYTES = 16;\n\n/**\n * Cost for new backups: 46 MiB, 3 passes - above the OWASP Argon2id minimum,\n * ~1-2 s on a phone. Params are stored per file, so older backups keep\n * decrypting with their own cost.\n */\nexport const DEFAULT_ARGON2 = { m: 47104, t: 3, p: 1 } as const;\n\n/**\n * Read-side ceiling (m is in KiB: 1 GiB, 10 passes, 4 lanes). Without it a\n * crafted file could declare m = 8_000_000 and make the reader allocate\n * gigabytes before any password check. Out-of-range params are\n * UNSUPPORTED_VERSION, not DECRYPT_FAILED - the file is not corrupt, its\n * parameters are just outside what this reader will run.\n */\nconst KDF_MAX = { m: 1024 * 1024, t: 10, p: 4 } as const;\nconst KDF_MIN = { m: 8, t: 1, p: 1 } as const;\n\n/** Throws UNSUPPORTED_VERSION on out-of-range Argon2 params. Call before touching the WASM. */\nexport function assertKdfBounds(kdf: KdfParams): void {\n\tconst { m, t, p } = kdf;\n\tconst ok =\n\t\tNumber.isInteger(m) &&\n\t\tNumber.isInteger(t) &&\n\t\tNumber.isInteger(p) &&\n\t\tm >= KDF_MIN.m &&\n\t\tm <= KDF_MAX.m &&\n\t\tt >= KDF_MIN.t &&\n\t\tt <= KDF_MAX.t &&\n\t\tp >= KDF_MIN.p &&\n\t\tp <= KDF_MAX.p;\n\tif (!ok) {\n\t\tthrow new SelfstoreError(\n\t\t\t'UNSUPPORTED_VERSION',\n\t\t\t`Argon2 parameters out of the supported range (m=${m} KiB, t=${t}, p=${p}).`\n\t\t);\n\t}\n}\n\nfunction randomBytes(n: number): Uint8Array {\n\tconst b = new Uint8Array(n);\n\tcrypto.getRandomValues(b);\n\treturn b;\n}\n\n/** Coerce to the ArrayBuffer-backed view WebCrypto's types want (always safe here). */\nfunction src(u8: Uint8Array): Uint8Array<ArrayBuffer> {\n\treturn u8 as Uint8Array<ArrayBuffer>;\n}\n\n/**\n * Derive the AES-256-GCM key for `kdf`, imported non-extractable. Shared by\n * the backup cipher and the identity vault's passphrase lock.\n */\nexport async function deriveKey(password: string, kdf: KdfParams): Promise<CryptoKey> {\n\tconst raw = await deriveRaw(password, kdf);\n\treturn crypto.subtle.importKey('raw', src(raw), 'AES-GCM', false, ['encrypt', 'decrypt']);\n}\n\n/** Fresh salt at the current default cost, for a new password-sealed write. */\nexport function freshKdfParams(): KdfParams {\n\treturn { algo: 'argon2id', salt: toBase64(randomBytes(SALT_BYTES)), ...DEFAULT_ARGON2 };\n}\n\n// --- Password envelope (format 3) slots --------------------------------------\n\n/**\n * Each slot costs one Argon2id trial on open, so an unbounded list would let\n * a crafted file demand unbounded memory-hard work. Same spirit as\n * assertKdfBounds.\n */\nexport const MAX_KEY_SLOTS = 8;\n\n/** Wrap the 32-byte data key under a key derived from `password`, fresh salt and IV. */\nexport async function mintSlot(\n\tpassword: string,\n\tdataKey: Uint8Array,\n\tid?: string\n): Promise<PasswordSlot> {\n\tconst kdf = freshKdfParams();\n\tconst key = await deriveKey(password, kdf);\n\tconst iv = randomBytes(IV_BYTES);\n\tconst wrapped = await crypto.subtle.encrypt({ name: 'AES-GCM', iv: src(iv) }, key, src(dataKey));\n\treturn {\n\t\tid: id ?? toBase64(randomBytes(6)),\n\t\tkdf,\n\t\tiv: toBase64(iv),\n\t\twrapped: toBase64(new Uint8Array(wrapped))\n\t};\n}\n\n/**\n * Returns the data key if `password` opens this slot, else null. GCM keeps\n * wrong-password and tampering indistinguishable (fail closed). Bounds are\n * checked before any derivation, and that refusal is never swallowed - a\n * memory-hard bomb must not hide behind \"wrong password\".\n */\nexport async function openSlot(slot: PasswordSlot, password: string): Promise<Uint8Array | null> {\n\tassertKdfBounds(slot.kdf);\n\ttry {\n\t\tconst key = await deriveKey(password, slot.kdf);\n\t\tconst pt = await crypto.subtle.decrypt(\n\t\t\t{ name: 'AES-GCM', iv: src(fromBase64(slot.iv)) },\n\t\t\tkey,\n\t\t\tsrc(fromBase64(slot.wrapped))\n\t\t);\n\t\tconst dataKey = new Uint8Array(pt);\n\t\treturn dataKey.length === 32 ? dataKey : null;\n\t} catch (e) {\n\t\tif (e instanceof SelfstoreError) throw e;\n\t\treturn null;\n\t}\n}\n\n// --- Authenticated envelope (format 3) data seal -----------------------------\n\n/**\n * Seal the inner ZIP under the data key with the header bytes as AAD. Format\n * 5 passes the exact meta.json bytes here, which binds the slot table to the\n * ciphertext: write access to the file is not enough to strip or swap a key\n * slot without breaking the tag. The caller generates the iv (it rides in\n * meta.json, hence inside the AAD) so seal and open agree byte for byte.\n */\nexport async function gcmSealAad(\n\tkey32: Uint8Array,\n\tiv: Uint8Array,\n\tplaintext: Uint8Array,\n\taad: Uint8Array\n): Promise<Uint8Array> {\n\tconst key = await crypto.subtle.importKey('raw', src(key32), 'AES-GCM', false, ['encrypt']);\n\tconst ct = await crypto.subtle.encrypt(\n\t\t{ name: 'AES-GCM', iv: src(iv), additionalData: src(aad) },\n\t\tkey,\n\t\tsrc(plaintext)\n\t);\n\treturn new Uint8Array(ct);\n}\n\n/**\n * Open sealed data. `aad` is the additional authenticated data bound at seal\n * time (the envelope binds its header bytes); pass none for seals that bound\n * none. Any tag failure - wrong key, corrupt data, altered header - is one\n * DECRYPT_FAILED.\n */\nexport async function gcmOpenAad(\n\tkey32: Uint8Array,\n\tiv: Uint8Array,\n\tciphertext: Uint8Array,\n\taad?: Uint8Array\n): Promise<Uint8Array> {\n\ttry {\n\t\tconst key = await crypto.subtle.importKey('raw', src(key32), 'AES-GCM', false, ['decrypt']);\n\t\tconst params: AesGcmParams = { name: 'AES-GCM', iv: src(iv) };\n\t\tif (aad) params.additionalData = src(aad);\n\t\tconst pt = await crypto.subtle.decrypt(params, key, src(ciphertext));\n\t\treturn new Uint8Array(pt);\n\t} catch {\n\t\tthrow new SelfstoreError(\n\t\t\t'DECRYPT_FAILED',\n\t\t\t'Corrupted, wrong-key, or altered-header ciphertext.'\n\t\t);\n\t}\n}\n\n// --- External-key slots ------------------------------------------------------\n\n// A passkey PRF output is 32 bytes; anything under 16 is too weak to key a backup.\nconst MIN_EXTERNAL_SECRET = 16;\n\nconst EXTERNAL_INFO = new TextEncoder().encode('selfstore-external-slot-v1');\n\n/**\n * KEK for an external slot. The secret is already high-entropy (passkey PRF,\n * hardware token), so one HKDF-SHA256 expansion is enough - Argon2 only\n * exists to stretch low-entropy passwords. The fixed info label\n * domain-separates this use from any other the same secret might see.\n */\nasync function externalKek(secret: Uint8Array): Promise<CryptoKey> {\n\tconst ikm = await crypto.subtle.importKey('raw', src(secret), 'HKDF', false, ['deriveBits']);\n\tconst bits = await crypto.subtle.deriveBits(\n\t\t{ name: 'HKDF', hash: 'SHA-256', salt: new Uint8Array(0), info: src(EXTERNAL_INFO) },\n\t\tikm,\n\t\t256\n\t);\n\treturn crypto.subtle.importKey('raw', src(new Uint8Array(bits)), 'AES-GCM', false, [\n\t\t'encrypt',\n\t\t'decrypt'\n\t]);\n}\n\n/** Wrap the data key under a KEK derived from `secret`; `keyRef` is carried verbatim. */\nexport async function mintExternalSlot(\n\tsecret: Uint8Array,\n\tkeyRef: string,\n\tdataKey: Uint8Array,\n\tid?: string\n): Promise<ExternalSlot> {\n\tif (secret.length < MIN_EXTERNAL_SECRET) {\n\t\tthrow new TypeError(\n\t\t\t`mintExternalSlot(): external secret must be >= ${MIN_EXTERNAL_SECRET} bytes.`\n\t\t);\n\t}\n\tconst key = await externalKek(secret);\n\tconst iv = randomBytes(IV_BYTES);\n\tconst wrapped = await crypto.subtle.encrypt({ name: 'AES-GCM', iv: src(iv) }, key, src(dataKey));\n\treturn {\n\t\tid: id ?? toBase64(randomBytes(6)),\n\t\tkind: 'external',\n\t\tkeyRef,\n\t\tiv: toBase64(iv),\n\t\twrapped: toBase64(new Uint8Array(wrapped))\n\t};\n}\n\n/** Returns the data key if `secret` opens this slot, else null. Fail closed, like openSlot. */\nexport async function openExternalSlot(\n\tslot: ExternalSlot,\n\tsecret: Uint8Array\n): Promise<Uint8Array | null> {\n\tif (secret.length < MIN_EXTERNAL_SECRET) return null;\n\ttry {\n\t\tconst key = await externalKek(secret);\n\t\tconst pt = await crypto.subtle.decrypt(\n\t\t\t{ name: 'AES-GCM', iv: src(fromBase64(slot.iv)) },\n\t\t\tkey,\n\t\t\tsrc(fromBase64(slot.wrapped))\n\t\t);\n\t\tconst dataKey = new Uint8Array(pt);\n\t\treturn dataKey.length === 32 ? dataKey : null;\n\t} catch {\n\t\treturn null;\n\t}\n}\n"]}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { toBase64, fromBase64 } from './chunk-
|
|
1
|
+
import { toBase64, fromBase64 } from './chunk-6746AL4I.js';
|
|
2
2
|
import { SelfstoreError } from './chunk-VBFMMR7Q.js';
|
|
3
3
|
|
|
4
4
|
// src/selfstore/group.ts
|
|
@@ -51,7 +51,10 @@ var importEncPriv = (b64) => crypto.subtle.importKey("pkcs8", src(fromBase64(b64
|
|
|
51
51
|
var importEncPub = (b64) => crypto.subtle.importKey("raw", src(fromBase64(b64)), "X25519", false, []);
|
|
52
52
|
async function generateIdentity() {
|
|
53
53
|
await requireCurves();
|
|
54
|
-
const sig = await crypto.subtle.generateKey("Ed25519", true, [
|
|
54
|
+
const sig = await crypto.subtle.generateKey("Ed25519", true, [
|
|
55
|
+
"sign",
|
|
56
|
+
"verify"
|
|
57
|
+
]);
|
|
55
58
|
const enc = await crypto.subtle.generateKey("X25519", true, ["deriveBits"]);
|
|
56
59
|
const raw = async (k) => toBase64(new Uint8Array(await crypto.subtle.exportKey("raw", k)));
|
|
57
60
|
const pkcs8 = async (k) => toBase64(new Uint8Array(await crypto.subtle.exportKey("pkcs8", k)));
|
|
@@ -113,7 +116,12 @@ async function sealDataKey(dataKey, recipientEncPubsB64) {
|
|
|
113
116
|
const wrap = new Uint8Array(
|
|
114
117
|
await crypto.subtle.encrypt({ name: "AES-GCM", iv }, key, src(dataKey))
|
|
115
118
|
);
|
|
116
|
-
out.push({
|
|
119
|
+
out.push({
|
|
120
|
+
kid: await keyId(r),
|
|
121
|
+
epk: toBase64(ephPubRaw),
|
|
122
|
+
iv: toBase64(iv),
|
|
123
|
+
wrap: toBase64(wrap)
|
|
124
|
+
});
|
|
117
125
|
}
|
|
118
126
|
return out;
|
|
119
127
|
}
|
|
@@ -129,9 +137,19 @@ async function openDataKey(stanzas, identity) {
|
|
|
129
137
|
const myPubRaw = fromBase64(identity.encPub);
|
|
130
138
|
for (const s of candidates) {
|
|
131
139
|
try {
|
|
132
|
-
const key = await stanzaWrapKey(
|
|
140
|
+
const key = await stanzaWrapKey(
|
|
141
|
+
priv,
|
|
142
|
+
await importEncPub(s.epk),
|
|
143
|
+
fromBase64(s.epk),
|
|
144
|
+
myPubRaw,
|
|
145
|
+
"decrypt"
|
|
146
|
+
);
|
|
133
147
|
const pt = new Uint8Array(
|
|
134
|
-
await crypto.subtle.decrypt(
|
|
148
|
+
await crypto.subtle.decrypt(
|
|
149
|
+
{ name: "AES-GCM", iv: src(fromBase64(s.iv)) },
|
|
150
|
+
key,
|
|
151
|
+
src(fromBase64(s.wrap))
|
|
152
|
+
)
|
|
135
153
|
);
|
|
136
154
|
if (pt.length === DATA_KEY_BYTES) return pt;
|
|
137
155
|
} catch {
|
|
@@ -191,7 +209,7 @@ async function openManifest(signed, adminSigPubB64, expectedGroupB64) {
|
|
|
191
209
|
throw bad("Not a signed selfstore manifest.");
|
|
192
210
|
}
|
|
193
211
|
let payload;
|
|
194
|
-
let ok
|
|
212
|
+
let ok;
|
|
195
213
|
try {
|
|
196
214
|
payload = fromBase64(signed.payload);
|
|
197
215
|
const msg = concat(utf8.encode(DOMAIN_MANIFEST), payload);
|
|
@@ -227,5 +245,5 @@ async function openManifest(signed, adminSigPubB64, expectedGroupB64) {
|
|
|
227
245
|
}
|
|
228
246
|
|
|
229
247
|
export { GROUP_KEYING, gcmOpenRaw, gcmSealRaw, generateIdentity, groupCryptoAvailable, keyId, newGroupId, openDataKey, openManifest, publicIdentity, sealDataKey, signBox, signManifest, verifyBox };
|
|
230
|
-
//# sourceMappingURL=chunk-
|
|
231
|
-
//# sourceMappingURL=chunk-
|
|
248
|
+
//# sourceMappingURL=chunk-KHER6MWR.js.map
|
|
249
|
+
//# sourceMappingURL=chunk-KHER6MWR.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/selfstore/group.ts"],"names":[],"mappings":"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","file":"chunk-KHER6MWR.js","sourcesContent":["// Passwordless groups: the public-key layer under peers (see PEERS.md).\n//\n// Each member holds an Ed25519 keypair to sign the copies they publish and an\n// X25519 keypair to receive data keys. A copy is encrypted once under a fresh\n// AES-256-GCM data key, enveloped per member (ephemeral ECDH + HKDF + GCM\n// wrap - the age recipient model). Membership is a manifest signed by a\n// single admin key; a copy is trusted only when its signature verifies and\n// its author is in the manifest.\n//\n// WebCrypto only, no polyfill: the supply-chain surface stays flat, and\n// evergreen browsers plus Node 20+ have everything needed.\n\nimport type { RecipientStanza } from './types';\nimport { toBase64, fromBase64 } from './base64';\nimport { SelfstoreError } from './errors';\n\n/** The `keying` header value for group-mode boxes (see SPEC.md section 12). */\nexport const GROUP_KEYING = 'x25519-hkdf-sha256';\n\n// Enforced on both seal and open, so a member cannot publish a copy under a\n// narrower key (16 bytes -> AES-128) and have readers accept it.\nconst DATA_KEY_BYTES = 32;\nconst CURVE_PUB_BYTES = 32;\n// Group mode targets a handful of members; the cap keeps a crafted copy from\n// demanding one asymmetric operation per stanza without bound.\nexport const MAX_RECIPIENTS = 256;\n\n// Domain separation: every signature and derivation binds its purpose, so\n// bytes signed in one role can never be replayed in another.\nconst DOMAIN_BOX = 'selfstore-group-sig-v1';\nconst DOMAIN_MANIFEST = 'selfstore-group-manifest-v1';\nconst HKDF_INFO = 'selfstore-group-wrap-v1';\n\nconst utf8 = new TextEncoder();\n\n/**\n * A member's full identity: private halves PKCS#8 DER, public halves raw 32\n * bytes, all base64. Treat the *Priv fields like a password - seal them at\n * rest (identityVault) and move them only over a trusted channel.\n */\nexport interface GroupIdentity {\n\tsigPub: string;\n\tsigPriv: string;\n\tencPub: string;\n\tencPriv: string;\n}\n\n/** One member as the manifest lists them: public keys only. */\nexport interface GroupMember {\n\tid: string;\n\tlabel?: string;\n\t/** Ed25519 public key (base64 raw): verifies the copies this member signs. */\n\tsig: string;\n\t/** X25519 public key (base64 raw): receives the data-key envelopes. */\n\tenc: string;\n}\n\n/**\n * The membership document, single-writer (the admin), carried as a\n * SignedManifest. `seq` grows on every change; consumers refuse a lower seq\n * than one already applied (the store persists the high-water mark).\n */\nexport interface GroupManifest {\n\tv: 1;\n\t/** Random group id (base64), so seq high-water marks never collide. */\n\tgroup: string;\n\tseq: number;\n\t/** The admin's Ed25519 public key. Members pin it at join time (TOFU). */\n\tadmin: string;\n\tmembers: GroupMember[];\n}\n\n/**\n * A manifest as it travels: the exact signed payload bytes plus the admin's\n * signature. Signing bytes rather than re-serialized JSON sidesteps\n * canonicalization entirely.\n */\nexport interface SignedManifest {\n\tselfstoreManifest: 1;\n\tpayload: string;\n\tsig: string;\n}\n\n/** Coerce to the ArrayBuffer-backed view WebCrypto's types want (safe here). */\nfunction src(u8: Uint8Array): Uint8Array<ArrayBuffer> {\n\treturn u8 as Uint8Array<ArrayBuffer>;\n}\n\nfunction concat(...parts: Uint8Array[]): Uint8Array {\n\tconst out = new Uint8Array(parts.reduce((n, p) => n + p.length, 0));\n\tlet o = 0;\n\tfor (const p of parts) {\n\t\tout.set(p, o);\n\t\to += p.length;\n\t}\n\treturn out;\n}\n\nasync function sha256(bytes: Uint8Array): Promise<Uint8Array> {\n\treturn new Uint8Array(await crypto.subtle.digest('SHA-256', src(bytes)));\n}\n\nlet curvesOk: Promise<boolean> | null = null;\n\n/** Whether WebCrypto implements Ed25519 + X25519 here. Password mode never needs this. */\nexport function groupCryptoAvailable(): Promise<boolean> {\n\tcurvesOk ??= (async () => {\n\t\ttry {\n\t\t\tawait crypto.subtle.generateKey('Ed25519', false, ['sign', 'verify']);\n\t\t\tawait crypto.subtle.generateKey('X25519', false, ['deriveBits']);\n\t\t\treturn true;\n\t\t} catch {\n\t\t\treturn false;\n\t\t}\n\t})();\n\treturn curvesOk;\n}\n\nasync function requireCurves(): Promise<void> {\n\tif (!(await groupCryptoAvailable())) {\n\t\tthrow new TypeError(\n\t\t\t'Group mode needs WebCrypto Ed25519 and X25519 (evergreen browsers, Node 20+); ' +\n\t\t\t\t'this platform does not provide them. Use password mode instead, or upgrade the runtime.'\n\t\t);\n\t}\n}\n\nconst importSigPriv = (b64: string): Promise<CryptoKey> =>\n\tcrypto.subtle.importKey('pkcs8', src(fromBase64(b64)), 'Ed25519', false, ['sign']);\nconst importSigPub = (b64: string): Promise<CryptoKey> =>\n\tcrypto.subtle.importKey('raw', src(fromBase64(b64)), 'Ed25519', false, ['verify']);\nconst importEncPriv = (b64: string): Promise<CryptoKey> =>\n\tcrypto.subtle.importKey('pkcs8', src(fromBase64(b64)), 'X25519', false, ['deriveBits']);\nconst importEncPub = (b64: string): Promise<CryptoKey> =>\n\tcrypto.subtle.importKey('raw', src(fromBase64(b64)), 'X25519', false, []);\n\n/** Generate a fresh member identity (two keypairs). */\nexport async function generateIdentity(): Promise<GroupIdentity> {\n\tawait requireCurves();\n\tconst sig = (await crypto.subtle.generateKey('Ed25519', true, [\n\t\t'sign',\n\t\t'verify'\n\t])) as CryptoKeyPair;\n\tconst enc = (await crypto.subtle.generateKey('X25519', true, ['deriveBits'])) as CryptoKeyPair;\n\tconst raw = async (k: CryptoKey): Promise<string> =>\n\t\ttoBase64(new Uint8Array(await crypto.subtle.exportKey('raw', k)));\n\tconst pkcs8 = async (k: CryptoKey): Promise<string> =>\n\t\ttoBase64(new Uint8Array(await crypto.subtle.exportKey('pkcs8', k)));\n\treturn {\n\t\tsigPub: await raw(sig.publicKey),\n\t\tsigPriv: await pkcs8(sig.privateKey),\n\t\tencPub: await raw(enc.publicKey),\n\t\tencPriv: await pkcs8(enc.privateKey)\n\t};\n}\n\n/** The shareable half of an identity: what the admin puts in the manifest. */\nexport function publicIdentity(id: GroupIdentity): { sig: string; enc: string } {\n\treturn { sig: id.sigPub, enc: id.encPub };\n}\n\n/** A fresh random group id for a new manifest. */\nexport function newGroupId(): string {\n\treturn toBase64(crypto.getRandomValues(new Uint8Array(16)));\n}\n\n/** First 8 bytes of SHA-256(raw X25519 public), base64: finds a reader's stanza while naming nobody. */\nexport async function keyId(encPubB64: string): Promise<string> {\n\treturn toBase64((await sha256(fromBase64(encPubB64))).subarray(0, 8));\n}\n\n// Wrap key for one stanza: HKDF-SHA256 over the X25519 shared secret, salted\n// with ephemeralPub || recipientPub so the key binds to this exact pair.\nasync function stanzaWrapKey(\n\tpriv: CryptoKey,\n\totherPub: CryptoKey,\n\tephPubRaw: Uint8Array,\n\trecipientPubRaw: Uint8Array,\n\tusage: 'encrypt' | 'decrypt'\n): Promise<CryptoKey> {\n\tconst shared = new Uint8Array(\n\t\tawait crypto.subtle.deriveBits({ name: 'X25519', public: otherPub }, priv, 256)\n\t);\n\tconst ikm = await crypto.subtle.importKey('raw', src(shared), 'HKDF', false, ['deriveBits']);\n\tconst bits = new Uint8Array(\n\t\tawait crypto.subtle.deriveBits(\n\t\t\t{\n\t\t\t\tname: 'HKDF',\n\t\t\t\thash: 'SHA-256',\n\t\t\t\tsalt: src(concat(ephPubRaw, recipientPubRaw)),\n\t\t\t\tinfo: src(utf8.encode(HKDF_INFO))\n\t\t\t},\n\t\t\tikm,\n\t\t\t256\n\t\t)\n\t);\n\treturn crypto.subtle.importKey('raw', src(bits), 'AES-GCM', false, [usage]);\n}\n\n/** Envelope a data key: one ephemeral keypair and one stanza per recipient. */\nexport async function sealDataKey(\n\tdataKey: Uint8Array,\n\trecipientEncPubsB64: string[]\n): Promise<RecipientStanza[]> {\n\tawait requireCurves();\n\tif (dataKey.length !== DATA_KEY_BYTES) {\n\t\tthrow new TypeError(`sealDataKey: data key must be ${DATA_KEY_BYTES} bytes (AES-256).`);\n\t}\n\tif (recipientEncPubsB64.length > MAX_RECIPIENTS) {\n\t\tthrow new TypeError(`sealDataKey: too many recipients (max ${MAX_RECIPIENTS}).`);\n\t}\n\tconst out: RecipientStanza[] = [];\n\tfor (const r of recipientEncPubsB64) {\n\t\tconst eph = (await crypto.subtle.generateKey('X25519', false, ['deriveBits'])) as CryptoKeyPair;\n\t\tconst ephPubRaw = new Uint8Array(await crypto.subtle.exportKey('raw', eph.publicKey));\n\t\tconst key = await stanzaWrapKey(\n\t\t\teph.privateKey,\n\t\t\tawait importEncPub(r),\n\t\t\tephPubRaw,\n\t\t\tfromBase64(r),\n\t\t\t'encrypt'\n\t\t);\n\t\tconst iv = crypto.getRandomValues(new Uint8Array(12));\n\t\tconst wrap = new Uint8Array(\n\t\t\tawait crypto.subtle.encrypt({ name: 'AES-GCM', iv }, key, src(dataKey))\n\t\t);\n\t\tout.push({\n\t\t\tkid: await keyId(r),\n\t\t\tepk: toBase64(ephPubRaw),\n\t\t\tiv: toBase64(iv),\n\t\t\twrap: toBase64(wrap)\n\t\t});\n\t}\n\treturn out;\n}\n\n/**\n * Open the stanza addressed to this identity and return the data key.\n * NOT_A_RECIPIENT when none opens (identity removed, or the copy predates\n * its joining).\n */\nexport async function openDataKey(\n\tstanzas: RecipientStanza[],\n\tidentity: Pick<GroupIdentity, 'encPub' | 'encPriv'>\n): Promise<Uint8Array> {\n\tawait requireCurves();\n\tif (stanzas.length > MAX_RECIPIENTS) {\n\t\tthrow new SelfstoreError('BAD_FORMAT', `Too many recipient stanzas (max ${MAX_RECIPIENTS}).`);\n\t}\n\tconst myKid = await keyId(identity.encPub);\n\tconst mine = stanzas.filter((s) => s.kid === myKid);\n\t// On a kid miss try them all; MAX_RECIPIENTS bounds that work.\n\tconst candidates = mine.length > 0 ? mine : stanzas;\n\tconst priv = await importEncPriv(identity.encPriv);\n\tconst myPubRaw = fromBase64(identity.encPub);\n\tfor (const s of candidates) {\n\t\ttry {\n\t\t\tconst key = await stanzaWrapKey(\n\t\t\t\tpriv,\n\t\t\t\tawait importEncPub(s.epk),\n\t\t\t\tfromBase64(s.epk),\n\t\t\t\tmyPubRaw,\n\t\t\t\t'decrypt'\n\t\t\t);\n\t\t\tconst pt = new Uint8Array(\n\t\t\t\tawait crypto.subtle.decrypt(\n\t\t\t\t\t{ name: 'AES-GCM', iv: src(fromBase64(s.iv)) },\n\t\t\t\t\tkey,\n\t\t\t\t\tsrc(fromBase64(s.wrap))\n\t\t\t\t)\n\t\t\t);\n\t\t\t// Any other length is a key-downgrade attempt, not a valid envelope.\n\t\t\tif (pt.length === DATA_KEY_BYTES) return pt;\n\t\t} catch {\n\t\t\t/* not our stanza (or tampered): try the next candidate */\n\t\t}\n\t}\n\tthrow new SelfstoreError(\n\t\t'NOT_A_RECIPIENT',\n\t\t'No envelope opens for this identity: it is not (or no longer) a recipient of this copy.'\n\t);\n}\n\n/** AES-256-GCM under a raw 32-byte key (the group data key). */\nexport async function gcmSealRaw(\n\tkey32: Uint8Array,\n\tplaintext: Uint8Array\n): Promise<{ iv: string; ciphertext: Uint8Array }> {\n\tconst key = await crypto.subtle.importKey('raw', src(key32), 'AES-GCM', false, ['encrypt']);\n\tconst iv = crypto.getRandomValues(new Uint8Array(12));\n\tconst ct = await crypto.subtle.encrypt({ name: 'AES-GCM', iv }, key, src(plaintext));\n\treturn { iv: toBase64(iv), ciphertext: new Uint8Array(ct) };\n}\n\n/** Decrypt AES-256-GCM under a raw key; a tag failure is DECRYPT_FAILED. */\nexport async function gcmOpenRaw(\n\tkey32: Uint8Array,\n\tivB64: string,\n\tciphertext: Uint8Array\n): Promise<Uint8Array> {\n\ttry {\n\t\tconst key = await crypto.subtle.importKey('raw', src(key32), 'AES-GCM', false, ['decrypt']);\n\t\tconst pt = await crypto.subtle.decrypt(\n\t\t\t{ name: 'AES-GCM', iv: src(fromBase64(ivB64)) },\n\t\t\tkey,\n\t\t\tsrc(ciphertext)\n\t\t);\n\t\treturn new Uint8Array(pt);\n\t} catch {\n\t\tthrow new SelfstoreError('DECRYPT_FAILED', 'Corrupted or mismatched group ciphertext.');\n\t}\n}\n\n/**\n * Ed25519 over domain || SHA-256(meta.json) || SHA-256(data.enc): the two\n * digests cover everything a reader trusts (recipients, author claim, iv,\n * ciphertext). Returns the raw 64-byte sig.bin entry.\n */\nexport async function signBox(\n\tmetaBytes: Uint8Array,\n\tdataEnc: Uint8Array,\n\tsigPrivB64: string\n): Promise<Uint8Array> {\n\tawait requireCurves();\n\tconst msg = concat(utf8.encode(DOMAIN_BOX), await sha256(metaBytes), await sha256(dataEnc));\n\tconst key = await importSigPriv(sigPrivB64);\n\treturn new Uint8Array(await crypto.subtle.sign('Ed25519', key, src(msg)));\n}\n\n/** False on any failure (bad key, bad bytes); the caller maps it to SIGNATURE_INVALID. */\nexport async function verifyBox(\n\tmetaBytes: Uint8Array,\n\tdataEnc: Uint8Array,\n\tsig: Uint8Array,\n\tsigPubB64: string\n): Promise<boolean> {\n\ttry {\n\t\tawait requireCurves();\n\t\tconst msg = concat(utf8.encode(DOMAIN_BOX), await sha256(metaBytes), await sha256(dataEnc));\n\t\treturn await crypto.subtle.verify('Ed25519', await importSigPub(sigPubB64), src(sig), src(msg));\n\t} catch {\n\t\treturn false;\n\t}\n}\n\n/** Sign a manifest with the admin key, over the exact payload bytes. */\nexport async function signManifest(\n\tmanifest: GroupManifest,\n\tadminSigPrivB64: string\n): Promise<SignedManifest> {\n\tawait requireCurves();\n\tconst payload = utf8.encode(JSON.stringify(manifest));\n\tconst key = await importSigPriv(adminSigPrivB64);\n\tconst msg = concat(utf8.encode(DOMAIN_MANIFEST), payload);\n\tconst sig = new Uint8Array(await crypto.subtle.sign('Ed25519', key, src(msg)));\n\treturn { selfstoreManifest: 1, payload: toBase64(payload), sig: toBase64(sig) };\n}\n\n/**\n * Verify and parse a signed manifest against the pinned admin key (TOFU from\n * the invite). Rollback (seq) is enforced by the store, which persists the\n * high-water mark.\n */\nexport async function openManifest(\n\tsigned: SignedManifest,\n\tadminSigPubB64: string,\n\texpectedGroupB64?: string\n): Promise<GroupManifest> {\n\tawait requireCurves();\n\tconst bad = (why: string): SelfstoreError => new SelfstoreError('SIGNATURE_INVALID', why);\n\tif (!signed || signed.selfstoreManifest !== 1 || typeof signed.payload !== 'string') {\n\t\tthrow bad('Not a signed selfstore manifest.');\n\t}\n\tlet payload: Uint8Array;\n\tlet ok: boolean;\n\ttry {\n\t\tpayload = fromBase64(signed.payload);\n\t\tconst msg = concat(utf8.encode(DOMAIN_MANIFEST), payload);\n\t\tok = await crypto.subtle.verify(\n\t\t\t'Ed25519',\n\t\t\tawait importSigPub(adminSigPubB64),\n\t\t\tsrc(fromBase64(signed.sig ?? '')),\n\t\t\tsrc(msg)\n\t\t);\n\t} catch {\n\t\tthrow bad('Malformed manifest signature or keys.');\n\t}\n\tif (!ok) throw bad('Manifest signature invalid for the pinned admin key.');\n\tconst m = JSON.parse(new TextDecoder().decode(payload)) as GroupManifest;\n\t// Reject malformed member keys here, not later at import time.\n\tconst is32ByteKey = (b64: string): boolean => {\n\t\ttry {\n\t\t\treturn fromBase64(b64).length === CURVE_PUB_BYTES;\n\t\t} catch {\n\t\t\treturn false;\n\t\t}\n\t};\n\tconst shapeOk =\n\t\tm &&\n\t\tm.v === 1 &&\n\t\ttypeof m.group === 'string' &&\n\t\tNumber.isInteger(m.seq) &&\n\t\tm.seq >= 0 &&\n\t\tm.admin === adminSigPubB64 &&\n\t\tArray.isArray(m.members) &&\n\t\tm.members.length <= MAX_RECIPIENTS &&\n\t\tm.members.every(\n\t\t\t(mem) =>\n\t\t\t\tmem &&\n\t\t\t\ttypeof mem.id === 'string' &&\n\t\t\t\tmem.id.length > 0 &&\n\t\t\t\tis32ByteKey(mem.sig) &&\n\t\t\t\tis32ByteKey(mem.enc)\n\t\t);\n\tif (!shapeOk) throw bad('Manifest shape invalid or admin key mismatch.');\n\t// A member id resolves an author to a person; duplicates make that ambiguous.\n\tif (new Set(m.members.map((mem) => mem.id)).size !== m.members.length) {\n\t\tthrow bad('Manifest has duplicate member ids.');\n\t}\n\t// An admin who reuses one key across groups must not get a manifest for\n\t// group Y accepted where X is meant.\n\tif (expectedGroupB64 !== undefined && m.group !== expectedGroupB64) {\n\t\tthrow bad('Manifest is for a different group than expected.');\n\t}\n\treturn m;\n}\n"]}
|
|
@@ -432,7 +432,8 @@ function fromConfig(c, kv) {
|
|
|
432
432
|
if (res.status === 401 || res.status === 403) {
|
|
433
433
|
throw new AuthExpiredError(`WebDAV rejected the credentials (${res.status}).`);
|
|
434
434
|
}
|
|
435
|
-
if (!res.ok)
|
|
435
|
+
if (!res.ok)
|
|
436
|
+
throw new SelfstoreError("TARGET_WRITE_FAILED", `WebDAV PUT failed: ${res.status}`);
|
|
436
437
|
return versionOf(res) ?? await stat();
|
|
437
438
|
},
|
|
438
439
|
async load() {
|
|
@@ -445,7 +446,8 @@ function fromConfig(c, kv) {
|
|
|
445
446
|
if (res.status === 401 || res.status === 403) {
|
|
446
447
|
throw new AuthExpiredError(`WebDAV rejected the credentials (${res.status}).`);
|
|
447
448
|
}
|
|
448
|
-
if (!res.ok)
|
|
449
|
+
if (!res.ok)
|
|
450
|
+
throw new SelfstoreError("TARGET_UNAVAILABLE", `WebDAV GET failed: ${res.status}`);
|
|
449
451
|
return res.blob();
|
|
450
452
|
},
|
|
451
453
|
stat,
|
|
@@ -495,7 +497,7 @@ function peer(opts) {
|
|
|
495
497
|
async function connect3(opts) {
|
|
496
498
|
assertSecureUrl(opts.config.url);
|
|
497
499
|
const target = fromConfig(opts.config, opts.kv);
|
|
498
|
-
let ok
|
|
500
|
+
let ok;
|
|
499
501
|
try {
|
|
500
502
|
ok = await target.isReady();
|
|
501
503
|
} catch {
|
|
@@ -577,7 +579,10 @@ async function signS3(input) {
|
|
|
577
579
|
scope,
|
|
578
580
|
await sha256Hex(encoder.encode(canonicalRequest))
|
|
579
581
|
].join("\n");
|
|
580
|
-
const kDate = await hmac(
|
|
582
|
+
const kDate = await hmac(
|
|
583
|
+
encoder.encode("AWS4" + input.secretAccessKey),
|
|
584
|
+
encoder.encode(dateStamp)
|
|
585
|
+
);
|
|
581
586
|
const kRegion = await hmac(kDate, encoder.encode(input.region));
|
|
582
587
|
const kService = await hmac(kRegion, encoder.encode(service));
|
|
583
588
|
const kSigning = await hmac(kService, encoder.encode("aws4_request"));
|
|
@@ -712,7 +717,7 @@ function fromConfig2(c, kv) {
|
|
|
712
717
|
async function connect4(opts) {
|
|
713
718
|
assertSecureEndpoint(opts.config.endpoint);
|
|
714
719
|
const target = fromConfig2(opts.config, opts.kv);
|
|
715
|
-
let ok
|
|
720
|
+
let ok;
|
|
716
721
|
try {
|
|
717
722
|
ok = await target.isReady();
|
|
718
723
|
} catch {
|
|
@@ -728,5 +733,5 @@ async function fromSession4(opts) {
|
|
|
728
733
|
}
|
|
729
734
|
|
|
730
735
|
export { FILE_ID_KEY, adopt, connect, connect2, connect3, connect4, createBackup, deleteBackup, findOrCreateOwnFile, fromSession, fromSession2, fromSession3, fromSession4, isSupported, listBackups, openExisting, peer, preview, renameBackup };
|
|
731
|
-
//# sourceMappingURL=chunk-
|
|
732
|
-
//# sourceMappingURL=chunk-
|
|
736
|
+
//# sourceMappingURL=chunk-P34WACTD.js.map
|
|
737
|
+
//# sourceMappingURL=chunk-P34WACTD.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/persistence/targets/drive.ts","../src/persistence/targets/file.ts","../src/persistence/targets/webdav.ts","../src/persistence/targets/sigv4.ts","../src/persistence/targets/s3.ts"],"names":["token","connect","fromSession","DATA_DEADLINE_MS","META_DEADLINE_MS","CONFIG_KEY","KEY_KEY","configKey","saveConfig","loadConfig","labelOf","versionOf","fromConfig"],"mappings":"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","file":"chunk-P34WACTD.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 { 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';\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\ttoken(opts?: { forceRefresh?: boolean }): 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): Promise<Response> {\n\tconst r = await run(await auth.token());\n\tif (r.status !== 401) return r;\n\treturn run(await auth.token({ forceRefresh: true }));\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\treturn {\n\t\tkind: 'drive',\n\t\tlabel: 'Google Drive',\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(auth, (token) =>\n\t\t\t\tfetch(`${DRIVE_UPLOAD}/${fileId}?uploadType=media&fields=version`, {\n\t\t\t\t\tmethod: 'PATCH',\n\t\t\t\t\theaders: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/octet-stream' },\n\t\t\t\t\tbody: blob,\n\t\t\t\t\tsignal: AbortSignal.timeout(DATA_DEADLINE_MS)\n\t\t\t\t})\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(auth, (token) =>\n\t\t\t\tfetch(`${DRIVE_FILES}/${fileId}?alt=media`, {\n\t\t\t\t\theaders: { Authorization: `Bearer ${token}` },\n\t\t\t\t\tsignal: AbortSignal.timeout(DATA_DEADLINE_MS)\n\t\t\t\t})\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(auth, (token) =>\n\t\t\t\t\tfetch(`${DRIVE_FILES}/${fileId}?fields=version`, {\n\t\t\t\t\t\theaders: { Authorization: `Bearer ${token}` },\n\t\t\t\t\t\tsignal: AbortSignal.timeout(META_DEADLINE_MS)\n\t\t\t\t\t})\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();\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/** 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 * Disk-file backup target (Chromium only). The user picks a backup file once via\n * the File System Access API; the store then re-writes that real file on every\n * change. The handle is persisted through the injected KV, so the target stays\n * storage-agnostic. The browser re-confirms write permission once per session\n * (a one-click reconnect).\n */\n\nimport type { BackupTarget } from '../target';\nimport type { KV } from '../cache';\nimport { AuthExpiredError, SelfstoreError } from '../../selfstore';\n\nconst HANDLE_KEY = 'fileHandle';\n\ninterface FileHandle {\n\tname: string;\n\tcreateWritable(): Promise<{ write(data: Blob): Promise<void>; close(): Promise<void> }>;\n\tgetFile(): Promise<File>;\n\tqueryPermission(d: { mode: 'read' | 'readwrite' }): Promise<PermissionState>;\n\trequestPermission(d: { mode: 'read' | 'readwrite' }): Promise<PermissionState>;\n}\n\ninterface PickerWindow {\n\tshowSaveFilePicker?: (o: {\n\t\tsuggestedName?: string;\n\t\ttypes?: { description: string; accept: Record<string, string[]> }[];\n\t}) => Promise<FileHandle>;\n\tshowOpenFilePicker?: (o: {\n\t\tmultiple?: boolean;\n\t\ttypes?: { description: string; accept: Record<string, string[]> }[];\n\t}) => Promise<FileHandle[]>;\n}\n\nexport interface FileConnectOptions {\n\t/** Where the picked file handle is persisted across sessions. */\n\tkv: KV;\n\t/** Suggested name for the backup file the user creates/picks. */\n\tfileName: string;\n}\n\n/** True when the browser supports picking a re-writable file (Chromium). */\nexport function isSupported(): boolean {\n\treturn typeof window !== 'undefined' && 'showSaveFilePicker' in window;\n}\n\n/** A revoked/denied file permission is a genuine loss of access: the store must\n * raise the reconnect gate (one click re-grants). Everything else is transient. */\nfunction isPermissionLoss(e: unknown): boolean {\n\treturn e instanceof DOMException && (e.name === 'NotAllowedError' || e.name === 'SecurityError');\n}\n\nfunction fromHandle(handle: FileHandle, kv: KV): BackupTarget {\n\treturn {\n\t\tkind: 'file',\n\t\tlabel: handle.name,\n\t\tasync save(blob: Blob): Promise<string | null> {\n\t\t\ttry {\n\t\t\t\tconst writable = await handle.createWritable();\n\t\t\t\tawait writable.write(blob);\n\t\t\t\tawait writable.close();\n\t\t\t} catch (e) {\n\t\t\t\tif (isPermissionLoss(e)) {\n\t\t\t\t\tthrow new AuthExpiredError('File permission was revoked.');\n\t\t\t\t}\n\t\t\t\tthrow new SelfstoreError('TARGET_WRITE_FAILED', `File write failed: ${String(e)}`);\n\t\t\t}\n\t\t\ttry {\n\t\t\t\t// The file's new mtime = OUR write's version marker (shared-disk case).\n\t\t\t\treturn String((await handle.getFile()).lastModified);\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\treturn handle.getFile();\n\t\t},\n\t\tasync stat(): Promise<string | null> {\n\t\t\ttry {\n\t\t\t\treturn String((await handle.getFile()).lastModified);\n\t\t\t} catch {\n\t\t\t\treturn null; // permission not granted yet: \"cannot tell\", never throws\n\t\t\t}\n\t\t},\n\t\tasync isReady(): Promise<boolean> {\n\t\t\treturn (await handle.queryPermission({ mode: 'readwrite' })) === 'granted';\n\t\t},\n\t\tasync reconnect(): Promise<boolean> {\n\t\t\treturn (await handle.requestPermission({ mode: 'readwrite' })) === 'granted';\n\t\t},\n\t\tasync disconnect(): Promise<void> {\n\t\t\tawait kv.del(HANDLE_KEY);\n\t\t}\n\t};\n}\n\n/** Prompt the user to choose/create the backup file. Persists the handle for\n * next sessions. Returns the target, or null if cancelled. */\nexport async function connect(opts: FileConnectOptions): Promise<BackupTarget | null> {\n\tconst picker = (window as unknown as PickerWindow).showSaveFilePicker;\n\tif (!picker) return null;\n\ttry {\n\t\tconst handle = await picker({\n\t\t\tsuggestedName: opts.fileName,\n\t\t\ttypes: [{ description: 'Backup', accept: { 'application/zip': ['.zip'] } }]\n\t\t});\n\t\tawait opts.kv.set(HANDLE_KEY, handle);\n\t\treturn fromHandle(handle, opts.kv);\n\t} catch (e) {\n\t\tif (e instanceof DOMException && e.name === 'AbortError') return null; // user cancelled\n\t\tthrow e;\n\t}\n}\n\n/** Prompt the user to pick an EXISTING backup file and adopt it as the\n * destination (the connect journey then reads it like any backup: password,\n * conflict, resume). Asks for readwrite up front so the first save does not\n * stall on a second permission prompt. Returns null if cancelled. */\nexport async function openExisting(opts: { kv: KV }): Promise<BackupTarget | null> {\n\tconst picker = (window as unknown as PickerWindow).showOpenFilePicker;\n\tif (!picker) return null;\n\ttry {\n\t\tconst [handle] = await picker({\n\t\t\tmultiple: false,\n\t\t\ttypes: [{ description: 'Backup', accept: { 'application/zip': ['.zip'] } }]\n\t\t});\n\t\tif (!handle) return null;\n\t\tif ((await handle.requestPermission({ mode: 'readwrite' })) !== 'granted') return null;\n\t\tawait opts.kv.set(HANDLE_KEY, handle);\n\t\treturn fromHandle(handle, opts.kv);\n\t} catch (e) {\n\t\tif (e instanceof DOMException && e.name === 'AbortError') return null; // user cancelled\n\t\tthrow e;\n\t}\n}\n\n/** Rebuild the target from the handle persisted in a past session, or null. */\nexport async function fromSession(opts: { kv: KV }): Promise<BackupTarget | null> {\n\tconst handle = await opts.kv.get<FileHandle>(HANDLE_KEY);\n\treturn handle ? fromHandle(handle, opts.kv) : null;\n}\n","/**\n * WebDAV backup target: the store re-writes one .selfstore file on a WebDAV\n * server the USER controls (Nextcloud, ownCloud, a plain WebDAV mount...). No\n * Google, no vendor broker - the browser talks straight to the server the user\n * owns. The bytes are the same end-to-end-encrypted .selfstore, so the server\n * only ever holds ciphertext.\n *\n * Two things the environment must allow, both documented for the user:\n * - the server must send CORS for the app origin (methods PUT/GET/HEAD and the\n * Authorization header), since browsers block cross-origin WebDAV otherwise;\n * - the app's CSP connect-src must permit the server host.\n *\n * Credentials are stored ENCRYPTED at rest under a per-device non-extractable\n * key, so a set-and-forget connection never leaves a cleartext password in\n * IndexedDB. The version marker prefers ETag, falling back to Last-Modified\n * (the latter is CORS-safelisted, so it is readable without extra server config).\n */\n\nimport type { BackupTarget, PeerSource } from '../target';\nimport type { KV } from '../cache';\nimport { seal, unseal, isEnvelope, newDeviceKey } from '../cache-crypto';\nimport { AuthExpiredError, SelfstoreError } from '../../selfstore';\n\nconst CONFIG_KEY = 'webdavConfig';\nconst KEY_KEY = 'webdavKey';\n\n/** A hung server must not spin forever: a deadline turns the hang into an\n * ordinary error the store retries (never a gate). Transfers get more room than\n * the HEAD poke. */\nconst DATA_DEADLINE_MS = 30_000;\nconst META_DEADLINE_MS = 15_000;\n\nexport interface WebdavConfig {\n\t/** Full URL of the backup file, e.g. https://cloud.me/remote.php/dav/files/me/backup.zip */\n\turl: string;\n\tusername: string;\n\tpassword: string;\n}\n\nexport interface WebdavConnectOptions {\n\t/** Where the (encrypted) server config is persisted across sessions. */\n\tkv: KV;\n\tconfig: WebdavConfig;\n}\n\nasync function configKey(kv: KV): Promise<CryptoKey> {\n\tconst existing = await kv.get<CryptoKey>(KEY_KEY);\n\tif (existing) return existing;\n\tconst fresh = await newDeviceKey();\n\tawait kv.set(KEY_KEY, fresh);\n\treturn fresh;\n}\n\nasync function saveConfig(kv: KV, c: WebdavConfig): Promise<void> {\n\tconst bytes = new TextEncoder().encode(JSON.stringify(c));\n\tawait kv.set(CONFIG_KEY, await seal(await configKey(kv), bytes));\n}\n\nasync function loadConfig(kv: KV): Promise<WebdavConfig | null> {\n\tconst stored = await kv.get(CONFIG_KEY);\n\tif (!isEnvelope(stored)) return null;\n\tconst bytes = await unseal(await configKey(kv), stored);\n\treturn JSON.parse(new TextDecoder().decode(bytes)) as WebdavConfig;\n}\n\nfunction labelOf(url: string): string {\n\ttry {\n\t\treturn new URL(url).host;\n\t} catch {\n\t\treturn 'WebDAV';\n\t}\n}\n\n/** WebDAV authenticates with HTTP Basic - the password is sent on every request.\n * Over plain http that is cleartext on the wire, so refuse it. Allow https, and\n * http only for loopback (local dev against a container on the same machine). */\nfunction assertSecureUrl(url: string): void {\n\tlet u: URL;\n\ttry {\n\t\tu = new URL(url);\n\t} catch {\n\t\tthrow new TypeError(`webdavTarget: invalid URL \"${url}\".`);\n\t}\n\tconst loopback =\n\t\tu.hostname === 'localhost' || u.hostname === '127.0.0.1' || u.hostname === '[::1]';\n\tif (u.protocol !== 'https:' && !(u.protocol === 'http:' && loopback)) {\n\t\tthrow new TypeError(\n\t\t\t`webdavTarget: refusing \"${u.protocol}\" - Basic-auth credentials need https (http is allowed only for localhost).`\n\t\t);\n\t}\n}\n\nfunction versionOf(res: Response): string | null {\n\treturn res.headers.get('ETag') ?? res.headers.get('Last-Modified') ?? null;\n}\n\nfunction fromConfig(c: WebdavConfig, kv: KV): BackupTarget {\n\tconst headers = { Authorization: 'Basic ' + btoa(`${c.username}:${c.password}`) };\n\n\t/** One HEAD, or null on a network/CORS failure (transient - the caller decides\n\t * what \"cannot reach\" means for it). */\n\tasync function probe(): Promise<Response | null> {\n\t\ttry {\n\t\t\treturn await fetch(c.url, {\n\t\t\t\tmethod: 'HEAD',\n\t\t\t\theaders,\n\t\t\t\tsignal: AbortSignal.timeout(META_DEADLINE_MS)\n\t\t\t});\n\t\t} catch {\n\t\t\treturn null;\n\t\t}\n\t}\n\tasync function stat(): Promise<string | null> {\n\t\tconst res = await probe();\n\t\treturn res && res.ok ? versionOf(res) : null; // 404 / error / offline: \"cannot tell\"\n\t}\n\tasync function ready(): Promise<boolean> {\n\t\tconst res = await probe();\n\t\tif (!res) return false; // offline / CORS blocked: transient, the store retries later\n\t\t// 401/403 = the stored credentials are rejected: a genuine loss of access\n\t\t// only the user can fix (re-enter the config), so gate rather than loop as\n\t\t// \"momentarily unreachable\". 404 = file not created yet, still writable.\n\t\tif (res.status === 401 || res.status === 403) {\n\t\t\tthrow new AuthExpiredError(`WebDAV rejected the credentials (${res.status}).`);\n\t\t}\n\t\treturn res.ok || res.status === 404;\n\t}\n\t/** Credentials are stored; a reconnect just re-checks reachability. It can\n\t * never mint new credentials, so a rejection is reported as false (never\n\t * thrown) - only re-running connect() with a fresh config fixes bad creds. */\n\tasync function reconnectProbe(): Promise<boolean> {\n\t\tconst res = await probe();\n\t\treturn !!res && (res.ok || res.status === 404);\n\t}\n\n\treturn {\n\t\tkind: 'webdav',\n\t\tlabel: labelOf(c.url),\n\t\tasync save(blob: Blob): Promise<string | null> {\n\t\t\tconst res = await fetch(c.url, {\n\t\t\t\tmethod: 'PUT',\n\t\t\t\theaders,\n\t\t\t\tbody: blob,\n\t\t\t\tsignal: AbortSignal.timeout(DATA_DEADLINE_MS)\n\t\t\t});\n\t\t\t// 401/403 = the server rejected the stored credentials: a genuine loss of\n\t\t\t// access the user must fix. Anything else (5xx, conflict) is transient.\n\t\t\tif (res.status === 401 || res.status === 403) {\n\t\t\t\tthrow new AuthExpiredError(`WebDAV rejected the credentials (${res.status}).`);\n\t\t\t}\n\t\t\tif (!res.ok)\n\t\t\t\tthrow new SelfstoreError('TARGET_WRITE_FAILED', `WebDAV PUT failed: ${res.status}`);\n\t\t\treturn versionOf(res) ?? (await stat());\n\t\t},\n\t\tasync load(): Promise<Blob | null> {\n\t\t\tconst res = await fetch(c.url, {\n\t\t\t\tmethod: 'GET',\n\t\t\t\theaders,\n\t\t\t\tsignal: AbortSignal.timeout(DATA_DEADLINE_MS)\n\t\t\t});\n\t\t\tif (res.status === 404) return null;\n\t\t\tif (res.status === 401 || res.status === 403) {\n\t\t\t\tthrow new AuthExpiredError(`WebDAV rejected the credentials (${res.status}).`);\n\t\t\t}\n\t\t\tif (!res.ok)\n\t\t\t\tthrow new SelfstoreError('TARGET_UNAVAILABLE', `WebDAV GET failed: ${res.status}`);\n\t\t\treturn res.blob();\n\t\t},\n\t\tstat,\n\t\tisReady: ready,\n\t\treconnect: reconnectProbe, // stored credentials: re-check reachability, no gesture\n\t\tasync disconnect(): Promise<void> {\n\t\t\tawait kv.del(CONFIG_KEY);\n\t\t\tawait kv.del(KEY_KEY);\n\t\t}\n\t};\n}\n\nexport interface WebdavPeerOptions {\n\t/** URL of ANOTHER member's published copy, read-only: a Nextcloud/ownCloud\n\t * read-only share link, or a file URL they granted you read access to. */\n\turl: string;\n\t/** Optional Basic-auth credentials, when the read-only share needs them.\n\t * Omit for a public/tokenised share link (a Nextcloud `/s/<token>/download`\n\t * URL) that needs none. Held in memory only - a peer is re-attached each\n\t * session (see attachPeer), so nothing is persisted here. */\n\tusername?: string;\n\tpassword?: string;\n\t/** Display label (defaults to the URL host). */\n\tlabel?: string;\n}\n\n/** A read-only WebDAV peer: another member's published copy, fetched over a\n * shared URL, for `store.attachPeer`. It is the read half of a WebDAV target\n * (GET + HEAD only, never a write), so a group can share read-write without\n * Google Drive - each member publishes to their own WebDAV and shares a\n * read-only link. Failures follow the peer protocol: a 401/403 is a genuine\n * loss of read access (AuthExpiredError), 404 means \"not published yet\"\n * (null), anything else is transient. */\nexport function peer(opts: WebdavPeerOptions): PeerSource {\n\t// Basic-auth credentials are cleartext on the wire, so require https when they\n\t// are present; a credential-less public link carries only ciphertext, so any\n\t// host it lives on is the member's own choice.\n\tif (opts.username !== undefined) assertSecureUrl(opts.url);\n\tconst headers =\n\t\topts.username !== undefined\n\t\t\t? { Authorization: 'Basic ' + btoa(`${opts.username}:${opts.password ?? ''}`) }\n\t\t\t: undefined;\n\treturn {\n\t\tlabel: opts.label ?? labelOf(opts.url),\n\t\tasync load(): Promise<Blob | null> {\n\t\t\tconst res = await fetch(opts.url, {\n\t\t\t\tmethod: 'GET',\n\t\t\t\theaders,\n\t\t\t\tsignal: AbortSignal.timeout(DATA_DEADLINE_MS)\n\t\t\t});\n\t\t\tif (res.status === 404) return null; // the member has not published yet\n\t\t\tif (res.status === 401 || res.status === 403) {\n\t\t\t\tthrow new AuthExpiredError(`WebDAV peer rejected the credentials (${res.status}).`);\n\t\t\t}\n\t\t\tif (!res.ok) {\n\t\t\t\tthrow new SelfstoreError('TARGET_UNAVAILABLE', `WebDAV peer GET failed: ${res.status}`);\n\t\t\t}\n\t\t\treturn res.blob();\n\t\t},\n\t\tasync stat(): Promise<string | null> {\n\t\t\ttry {\n\t\t\t\tconst res = await fetch(opts.url, {\n\t\t\t\t\tmethod: 'HEAD',\n\t\t\t\t\theaders,\n\t\t\t\t\tsignal: AbortSignal.timeout(META_DEADLINE_MS)\n\t\t\t\t});\n\t\t\t\treturn res.ok ? versionOf(res) : null; // cannot tell: fall through to a full load\n\t\t\t} catch {\n\t\t\t\treturn null;\n\t\t\t}\n\t\t}\n\t};\n}\n\n/** Validate + persist the config, returning the target, or null when the server\n * is unreachable, rejects the credentials, or blocks the cross-origin request. */\nexport async function connect(opts: WebdavConnectOptions): Promise<BackupTarget | null> {\n\tassertSecureUrl(opts.config.url); // cleartext credentials over http are refused up front\n\tconst target = fromConfig(opts.config, opts.kv);\n\t// isReady() now THROWS on a 401/403 (so a later rejection gates the store); at\n\t// first connect a rejection just means \"wrong credentials, try again\", so a\n\t// throw here reads the same as false - report null, persist nothing.\n\tlet ok: boolean;\n\ttry {\n\t\tok = await target.isReady();\n\t} catch {\n\t\tok = false;\n\t}\n\tif (!ok) return null;\n\tawait saveConfig(opts.kv, opts.config);\n\treturn target;\n}\n\n/** Rebuild the target from the encrypted config persisted last session, or null. */\nexport async function fromSession(opts: { kv: KV }): Promise<BackupTarget | null> {\n\tconst config = await loadConfig(opts.kv);\n\treturn config ? fromConfig(config, opts.kv) : null;\n}\n","// AWS Signature Version 4 for S3 REST requests, in the browser with WebCrypto.\n// Enough of the spec to sign one object's GET/PUT/HEAD - no streaming, no\n// chunked upload, no presigned URLs. Works against AWS S3 and any S3-compatible\n// endpoint (Cloudflare R2, Backblaze B2, MinIO) since they all verify SigV4.\n//\n// Signed headers are host, x-amz-content-sha256 and x-amz-date, plus whatever\n// the caller adds. The browser sets Host itself from the URL and forbids\n// overriding it, so we sign the host derived from that same URL - the value the\n// browser will send - and never try to set the header.\n\nconst encoder = new TextEncoder();\n\nfunction hex(bytes: Uint8Array): string {\n\tlet out = '';\n\tfor (const b of bytes) out += b.toString(16).padStart(2, '0');\n\treturn out;\n}\n\nexport async function sha256Hex(bytes: Uint8Array): Promise<string> {\n\treturn hex(new Uint8Array(await crypto.subtle.digest('SHA-256', bytes as BufferSource)));\n}\n\n/** SHA-256 of the empty body: the payload hash for every GET/HEAD. */\nexport const EMPTY_PAYLOAD_SHA256 =\n\t'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855';\n\nasync function hmac(key: Uint8Array, data: Uint8Array): Promise<Uint8Array> {\n\tconst k = await crypto.subtle.importKey(\n\t\t'raw',\n\t\tkey as BufferSource,\n\t\t{ name: 'HMAC', hash: 'SHA-256' },\n\t\tfalse,\n\t\t['sign']\n\t);\n\treturn new Uint8Array(await crypto.subtle.sign('HMAC', k, data as BufferSource));\n}\n\n// RFC 3986 encoding, applied per path segment (the '/' between segments stays a\n// separator). encodeURIComponent leaves !*'() alone; SigV4 requires them encoded.\nfunction encodeSegment(s: string): string {\n\treturn encodeURIComponent(s).replace(\n\t\t/[!*'()]/g,\n\t\t(c) => '%' + c.charCodeAt(0).toString(16).toUpperCase()\n\t);\n}\n\n/** Canonicalise an object path for both the request URL and the signature, so\n * the two can never disagree on encoding. Pass the RAW key path (unencoded). */\nexport function encodeS3Path(path: string): string {\n\treturn path\n\t\t.split('/')\n\t\t.map((seg) => encodeSegment(seg))\n\t\t.join('/');\n}\n\nexport interface SignS3Input {\n\tmethod: string;\n\t/** Scheme + host(+port), e.g. https://s3.eu-west-3.amazonaws.com */\n\torigin: string;\n\t/** Raw (unencoded) object path, e.g. /my-bucket/backups/app.selfstore */\n\tpath: string;\n\tregion: string;\n\taccessKeyId: string;\n\tsecretAccessKey: string;\n\t/** Hex SHA-256 of the request body (EMPTY_PAYLOAD_SHA256 for GET/HEAD). */\n\tpayloadHashHex: string;\n\t/** Extra headers to sign, e.g. { range: 'bytes=0-0' }. Names are lowercased. */\n\textraHeaders?: Record<string, string>;\n\t/** Basic timestamp (YYYYMMDDTHHMMSSZ). Injected in tests; defaults to now. */\n\tamzDate?: string;\n\tservice?: string;\n}\n\nexport interface SignedS3Request {\n\t/** The fully-encoded URL to fetch. */\n\turl: string;\n\t/** Headers to send: Authorization, x-amz-date, x-amz-content-sha256, extras. */\n\theaders: Record<string, string>;\n}\n\n/** ISO basic UTC now, YYYYMMDDTHHMMSSZ. */\nfunction basicAmzDate(): string {\n\treturn new Date().toISOString().replace(/[:-]|\\.\\d{3}/g, '');\n}\n\n/** Sign one S3 request and return the URL plus the headers to send. */\nexport async function signS3(input: SignS3Input): Promise<SignedS3Request> {\n\tconst service = input.service ?? 's3';\n\tconst amzDate = input.amzDate ?? basicAmzDate();\n\tconst dateStamp = amzDate.slice(0, 8);\n\tconst { host } = new URL(input.origin);\n\tconst canonicalUri = encodeS3Path(input.path);\n\n\tconst headers: Record<string, string> = {\n\t\thost,\n\t\t'x-amz-content-sha256': input.payloadHashHex,\n\t\t'x-amz-date': amzDate\n\t};\n\tfor (const [name, value] of Object.entries(input.extraHeaders ?? {})) {\n\t\theaders[name.toLowerCase()] = value;\n\t}\n\tconst signedNames = Object.keys(headers).sort();\n\tconst canonicalHeaders = signedNames.map((n) => `${n}:${headers[n].trim()}\\n`).join('');\n\tconst signedHeaders = signedNames.join(';');\n\n\tconst canonicalRequest = [\n\t\tinput.method,\n\t\tcanonicalUri,\n\t\t'', // no query string on any of our single-object requests\n\t\tcanonicalHeaders,\n\t\tsignedHeaders,\n\t\tinput.payloadHashHex\n\t].join('\\n');\n\n\tconst scope = `${dateStamp}/${input.region}/${service}/aws4_request`;\n\tconst stringToSign = [\n\t\t'AWS4-HMAC-SHA256',\n\t\tamzDate,\n\t\tscope,\n\t\tawait sha256Hex(encoder.encode(canonicalRequest))\n\t].join('\\n');\n\n\tconst kDate = await hmac(\n\t\tencoder.encode('AWS4' + input.secretAccessKey),\n\t\tencoder.encode(dateStamp)\n\t);\n\tconst kRegion = await hmac(kDate, encoder.encode(input.region));\n\tconst kService = await hmac(kRegion, encoder.encode(service));\n\tconst kSigning = await hmac(kService, encoder.encode('aws4_request'));\n\tconst signature = hex(await hmac(kSigning, encoder.encode(stringToSign)));\n\n\tconst authorization =\n\t\t`AWS4-HMAC-SHA256 Credential=${input.accessKeyId}/${scope}, ` +\n\t\t`SignedHeaders=${signedHeaders}, Signature=${signature}`;\n\n\t// Send every signed header except host (the browser sets Host itself and\n\t// forbids overriding it - we signed the value it will send).\n\tconst sendHeaders: Record<string, string> = { authorization };\n\tfor (const n of signedNames) if (n !== 'host') sendHeaders[n] = headers[n];\n\n\treturn { url: input.origin.replace(/\\/$/, '') + canonicalUri, headers: sendHeaders };\n}\n","/**\n * S3 backup target: the store re-writes one object on an S3-compatible bucket\n * the USER controls - Amazon S3, Cloudflare R2, Backblaze B2, MinIO, any server\n * that speaks the S3 REST API and SigV4. No vendor broker: the browser signs\n * each request itself and talks straight to the endpoint. The bytes are the\n * same end-to-end-encrypted .selfstore, so the bucket only ever holds\n * ciphertext.\n *\n * Two things the environment must allow, both documented for the user:\n * - the bucket must send CORS for the app origin: allowed methods GET/PUT/HEAD,\n * allowed headers authorization/x-amz-*, and ExposeHeaders ETag (so the\n * version marker is readable), or browsers block the cross-origin request;\n * - the app's CSP connect-src must permit the endpoint host.\n *\n * Credentials (the secret access key) are stored ENCRYPTED at rest under a\n * per-device non-extractable key, exactly like the WebDAV target, so a\n * set-and-forget connection never leaves a cleartext secret in IndexedDB.\n *\n * Path style is the default: the object lives at endpoint/bucket/key, which\n * every S3-compatible server accepts and needs no per-bucket DNS. Set\n * forcePathStyle:false for AWS virtual-hosted style (bucket as a host label).\n */\n\nimport type { BackupTarget } from '../target';\nimport type { KV } from '../cache';\nimport { seal, unseal, isEnvelope, newDeviceKey } from '../cache-crypto';\nimport { AuthExpiredError, SelfstoreError } from '../../selfstore';\nimport { signS3, sha256Hex, EMPTY_PAYLOAD_SHA256 } from './sigv4';\n\nconst CONFIG_KEY = 's3Config';\nconst KEY_KEY = 's3Key';\n\nconst DATA_DEADLINE_MS = 30_000;\nconst META_DEADLINE_MS = 15_000;\n\nexport interface S3Config {\n\t/** Endpoint origin, e.g. https://s3.eu-west-3.amazonaws.com,\n\t * https://<account>.r2.cloudflarestorage.com, https://minio.example.com. */\n\tendpoint: string;\n\t/** Signing region (\"eu-west-3\"; R2 uses \"auto\"; MinIO any configured value). */\n\tregion: string;\n\tbucket: string;\n\t/** Object key (the backup file), e.g. backups/app.selfstore. */\n\tkey: string;\n\taccessKeyId: string;\n\tsecretAccessKey: string;\n\t/** endpoint/bucket/key (default) vs bucket.endpoint/key. Default true. */\n\tforcePathStyle?: boolean;\n}\n\nexport interface S3ConnectOptions {\n\t/** Where the (encrypted) config is persisted across sessions. */\n\tkv: KV;\n\tconfig: S3Config;\n}\n\nasync function configKey(kv: KV): Promise<CryptoKey> {\n\tconst existing = await kv.get<CryptoKey>(KEY_KEY);\n\tif (existing) return existing;\n\tconst fresh = await newDeviceKey();\n\tawait kv.set(KEY_KEY, fresh);\n\treturn fresh;\n}\n\nasync function saveConfig(kv: KV, c: S3Config): Promise<void> {\n\tconst bytes = new TextEncoder().encode(JSON.stringify(c));\n\tawait kv.set(CONFIG_KEY, await seal(await configKey(kv), bytes));\n}\n\nasync function loadConfig(kv: KV): Promise<S3Config | null> {\n\tconst stored = await kv.get(CONFIG_KEY);\n\tif (!isEnvelope(stored)) return null;\n\tconst bytes = await unseal(await configKey(kv), stored);\n\treturn JSON.parse(new TextDecoder().decode(bytes)) as S3Config;\n}\n\n/** SigV4 puts the secret on the wire only as a derived signature, but the\n * request still needs https: an http endpoint would expose the object bytes\n * and headers. Allow http for loopback (a local MinIO in dev) only. */\nfunction assertSecureEndpoint(endpoint: string): URL {\n\tlet u: URL;\n\ttry {\n\t\tu = new URL(endpoint);\n\t} catch {\n\t\tthrow new TypeError(`s3Target: invalid endpoint \"${endpoint}\".`);\n\t}\n\tconst loopback =\n\t\tu.hostname === 'localhost' || u.hostname === '127.0.0.1' || u.hostname === '[::1]';\n\tif (u.protocol !== 'https:' && !(u.protocol === 'http:' && loopback)) {\n\t\tthrow new TypeError(\n\t\t\t`s3Target: refusing \"${u.protocol}\" - S3 requests need https (http is allowed only for localhost).`\n\t\t);\n\t}\n\treturn u;\n}\n\nfunction labelOf(c: S3Config): string {\n\ttry {\n\t\treturn `${c.bucket} (${new URL(c.endpoint).host})`;\n\t} catch {\n\t\treturn c.bucket || 'S3';\n\t}\n}\n\nfunction versionOf(res: Response): string | null {\n\treturn res.headers.get('ETag') ?? res.headers.get('Last-Modified') ?? null;\n}\n\nfunction fromConfig(c: S3Config, kv: KV): BackupTarget {\n\tconst endpointUrl = assertSecureEndpoint(c.endpoint);\n\tconst pathStyle = c.forcePathStyle ?? true;\n\t// Path style keeps one host and no per-bucket DNS; virtual-hosted folds the\n\t// bucket into the host label (AWS's preferred form).\n\tconst origin = pathStyle\n\t\t? `${endpointUrl.protocol}//${endpointUrl.host}`\n\t\t: `${endpointUrl.protocol}//${c.bucket}.${endpointUrl.host}`;\n\tconst objectPath = pathStyle ? `/${c.bucket}/${c.key}` : `/${c.key}`;\n\n\tasync function request(\n\t\tmethod: string,\n\t\tpayloadHashHex: string,\n\t\tbody?: BodyInit,\n\t\textraHeaders?: Record<string, string>,\n\t\tdeadlineMs = DATA_DEADLINE_MS\n\t): Promise<Response> {\n\t\tconst signed = await signS3({\n\t\t\tmethod,\n\t\t\torigin,\n\t\t\tpath: objectPath,\n\t\t\tregion: c.region,\n\t\t\taccessKeyId: c.accessKeyId,\n\t\t\tsecretAccessKey: c.secretAccessKey,\n\t\t\tpayloadHashHex,\n\t\t\textraHeaders\n\t\t});\n\t\treturn fetch(signed.url, {\n\t\t\tmethod,\n\t\t\theaders: signed.headers,\n\t\t\tbody,\n\t\t\tsignal: AbortSignal.timeout(deadlineMs)\n\t\t});\n\t}\n\n\t/** One HEAD, or null on a network/CORS failure (transient). */\n\tasync function probe(): Promise<Response | null> {\n\t\ttry {\n\t\t\treturn await request('HEAD', EMPTY_PAYLOAD_SHA256, undefined, undefined, META_DEADLINE_MS);\n\t\t} catch {\n\t\t\treturn null;\n\t\t}\n\t}\n\tasync function stat(): Promise<string | null> {\n\t\tconst res = await probe();\n\t\treturn res && res.ok ? versionOf(res) : null; // 404 / error / offline: cannot tell\n\t}\n\tasync function ready(): Promise<boolean> {\n\t\tconst res = await probe();\n\t\tif (!res) return false; // offline / CORS blocked: transient, retried later\n\t\t// 403 = the credentials are rejected (a HEAD with GetObject permission\n\t\t// returns 404, not 403, for a missing object - so 403 is a real auth loss,\n\t\t// not \"no backup yet\"). 404 = object not created yet, still writable.\n\t\tif (res.status === 403) {\n\t\t\tthrow new AuthExpiredError('S3 rejected the credentials (403).');\n\t\t}\n\t\treturn res.ok || res.status === 404;\n\t}\n\t/** Stored credentials: a reconnect just re-checks reachability, never mints new\n\t * keys, so a rejection is reported as false rather than thrown. */\n\tasync function reconnectProbe(): Promise<boolean> {\n\t\tconst res = await probe();\n\t\treturn !!res && (res.ok || res.status === 404);\n\t}\n\n\treturn {\n\t\tkind: 's3',\n\t\tlabel: labelOf(c),\n\t\tasync save(blob: Blob): Promise<string | null> {\n\t\t\tconst bytes = new Uint8Array(await blob.arrayBuffer());\n\t\t\tconst res = await request('PUT', await sha256Hex(bytes), bytes);\n\t\t\tif (res.status === 403) {\n\t\t\t\tthrow new AuthExpiredError('S3 rejected the credentials (403).');\n\t\t\t}\n\t\t\tif (!res.ok) throw new SelfstoreError('TARGET_WRITE_FAILED', `S3 PUT failed: ${res.status}`);\n\t\t\treturn versionOf(res) ?? (await stat());\n\t\t},\n\t\tasync load(): Promise<Blob | null> {\n\t\t\tconst res = await request('GET', EMPTY_PAYLOAD_SHA256);\n\t\t\tif (res.status === 404) return null;\n\t\t\tif (res.status === 403) {\n\t\t\t\tthrow new AuthExpiredError('S3 rejected the credentials (403).');\n\t\t\t}\n\t\t\tif (!res.ok) throw new SelfstoreError('TARGET_UNAVAILABLE', `S3 GET failed: ${res.status}`);\n\t\t\treturn res.blob();\n\t\t},\n\t\tstat,\n\t\tisReady: ready,\n\t\treconnect: reconnectProbe,\n\t\tasync disconnect(): Promise<void> {\n\t\t\tawait kv.del(CONFIG_KEY);\n\t\t\tawait kv.del(KEY_KEY);\n\t\t}\n\t};\n}\n\n/** Validate + persist the config, returning the target, or null when the bucket\n * is unreachable, rejects the credentials, or blocks the cross-origin request. */\nexport async function connect(opts: S3ConnectOptions): Promise<BackupTarget | null> {\n\tassertSecureEndpoint(opts.config.endpoint);\n\tconst target = fromConfig(opts.config, opts.kv);\n\t// isReady() throws on a 403 (a later rejection gates the store); at first\n\t// connect a rejection just means \"wrong credentials, try again\", so a throw\n\t// here reads the same as false - report null, persist nothing.\n\tlet ok: boolean;\n\ttry {\n\t\tok = await target.isReady();\n\t} catch {\n\t\tok = false;\n\t}\n\tif (!ok) return null;\n\tawait saveConfig(opts.kv, opts.config);\n\treturn target;\n}\n\n/** Rebuild the target from the encrypted config persisted last session, or null. */\nexport async function fromSession(opts: { kv: KV }): Promise<BackupTarget | null> {\n\tconst config = await loadConfig(opts.kv);\n\treturn config ? fromConfig(config, opts.kv) : null;\n}\n"]}
|
|
@@ -54,7 +54,11 @@ function emptyCol() {
|
|
|
54
54
|
}
|
|
55
55
|
function cloneCol(c) {
|
|
56
56
|
if (!c) return emptyCol();
|
|
57
|
-
const out = {
|
|
57
|
+
const out = {
|
|
58
|
+
clocks: { ...c.clocks },
|
|
59
|
+
deleted: { ...c.deleted },
|
|
60
|
+
hashes: { ...c.hashes }
|
|
61
|
+
};
|
|
58
62
|
if (c.fields) {
|
|
59
63
|
out.fields = {};
|
|
60
64
|
for (const [id, f] of Object.entries(c.fields)) out.fields[id] = { ...f };
|
|
@@ -185,7 +189,16 @@ function detectConflicts(a, b, config, base) {
|
|
|
185
189
|
const aClk = aMeta.clocks[id] ?? null;
|
|
186
190
|
const bClk = bMeta.clocks[id] ?? null;
|
|
187
191
|
const winnerSide = pickWinner(aClk, bClk, aDocs.has(id));
|
|
188
|
-
const conflict = strategy === "lww-map" ? fieldConflict(
|
|
192
|
+
const conflict = strategy === "lww-map" ? fieldConflict(
|
|
193
|
+
name,
|
|
194
|
+
id,
|
|
195
|
+
aDocs,
|
|
196
|
+
bDocs,
|
|
197
|
+
aMeta.fields?.[id],
|
|
198
|
+
bMeta.fields?.[id],
|
|
199
|
+
baseMeta?.fields?.[id],
|
|
200
|
+
winnerSide
|
|
201
|
+
) : manualConflict(name, id, aDocs, bDocs, aClk, bClk, baseMeta, winnerSide);
|
|
189
202
|
if (conflict) out.push(conflict);
|
|
190
203
|
}
|
|
191
204
|
}
|
|
@@ -201,7 +214,13 @@ function fieldConflict(name, id, aDocs, bDocs, aF, bF, baseF, winnerSide) {
|
|
|
201
214
|
const aChanged = !baseClk || compare(a[0], baseClk) > 0;
|
|
202
215
|
const bChanged = !baseClk || compare(b[0], baseClk) > 0;
|
|
203
216
|
if (aChanged && bChanged)
|
|
204
|
-
return {
|
|
217
|
+
return {
|
|
218
|
+
collection: name,
|
|
219
|
+
id,
|
|
220
|
+
local: aDocs.get(id),
|
|
221
|
+
remote: bDocs.get(id),
|
|
222
|
+
kept: winnerSide
|
|
223
|
+
};
|
|
205
224
|
}
|
|
206
225
|
return null;
|
|
207
226
|
}
|
|
@@ -327,5 +346,5 @@ function changes(before, after, config) {
|
|
|
327
346
|
}
|
|
328
347
|
|
|
329
348
|
export { changes, createMeta, createNode, detectConflicts, gcTombstones, idPathFor, merge, stamp, unsyncableCounts };
|
|
330
|
-
//# sourceMappingURL=chunk-
|
|
331
|
-
//# sourceMappingURL=chunk-
|
|
349
|
+
//# sourceMappingURL=chunk-SKWHKM25.js.map
|
|
350
|
+
//# sourceMappingURL=chunk-SKWHKM25.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/sync/hlc.ts","../src/sync/merge.ts"],"names":[],"mappings":"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","file":"chunk-SKWHKM25.js","sourcesContent":["/**\n * Hybrid Logical Clock: wall time + a counter + a node id, giving a\n * deterministic total order that tolerates clock skew. Encoded as a fixed-width\n * string so a plain string comparison equals causal order.\n */\n\nexport type Hlc = string;\n\ninterface Parts {\n\twall: number;\n\tcounter: number;\n\tnode: string;\n}\n\nconst WALL_W = 15;\nconst CTR_W = 6;\n\nfunction encode(p: Parts): Hlc {\n\treturn `${String(p.wall).padStart(WALL_W, '0')}|${String(p.counter).padStart(CTR_W, '0')}|${p.node}`;\n}\n\nfunction decode(h: Hlc): Parts {\n\tconst i = h.indexOf('|');\n\tconst j = h.indexOf('|', i + 1);\n\treturn { wall: Number(h.slice(0, i)), counter: Number(h.slice(i + 1, j)), node: h.slice(j + 1) };\n}\n\n/** A fresh random replica id (one per device). */\nexport function createNode(): string {\n\treturn (\n\t\ttypeof crypto !== 'undefined' && crypto.randomUUID\n\t\t\t? crypto.randomUUID()\n\t\t\t: Math.random().toString(36).slice(2)\n\t).replace(/\\|/g, '');\n}\n\n/** Issue a clock for a local event. Monotonic per node even if the wall clock moves back. */\nexport function issue(prev: Hlc | null, node: string, wallNow: number = Date.now()): Hlc {\n\tconst p = prev ? decode(prev) : null;\n\tconst prevWall = p ? p.wall : 0;\n\tconst wall = Math.max(wallNow, prevWall);\n\tconst counter = wall === prevWall && p ? p.counter + 1 : 0;\n\treturn encode({ wall, counter, node });\n}\n\n/** Fold a clock observed from another replica into the local one. */\nexport function receive(\n\tlocal: Hlc | null,\n\tremote: Hlc,\n\tnode: string,\n\twallNow: number = Date.now()\n): Hlc {\n\tconst l = local ? decode(local) : { wall: 0, counter: 0, node };\n\tconst r = decode(remote);\n\tconst wall = Math.max(wallNow, l.wall, r.wall);\n\tlet counter: number;\n\tif (wall === l.wall && wall === r.wall) counter = Math.max(l.counter, r.counter) + 1;\n\telse if (wall === l.wall) counter = l.counter + 1;\n\telse if (wall === r.wall) counter = r.counter + 1;\n\telse counter = 0;\n\treturn encode({ wall, counter, node });\n}\n\n/** The wall-time component (ms since epoch) encoded in a clock, for age-based\n * pruning (tombstone GC). Not a precise event time - HLC wall can run ahead of\n * real time under skew - but a safe lower bound on \"how old is this at least\". */\nexport function hlcWall(h: Hlc): number {\n\treturn Number(h.slice(0, h.indexOf('|')));\n}\n\n/** Total order: <0 if a precedes b, >0 if a follows b, 0 only if identical. */\nexport function compare(a: Hlc, b: Hlc): number {\n\tif (a < b) return -1;\n\tif (a > b) return 1;\n\treturn 0;\n}\n\n/** The later of two clocks. */\nexport function max(a: Hlc | null, b: Hlc | null): Hlc {\n\tif (!a) return b as Hlc;\n\tif (!b) return a;\n\treturn compare(a, b) >= 0 ? a : b;\n}\n","/**\n * Schema-agnostic convergent merge for plain JSON. It keeps a small sidecar of\n * metadata (one logical clock per record) and reconciles two replica states with\n * well-known set semantics. Documents stay plain objects: identity is a\n * configured id field, change detection is by content hash, nothing is injected.\n *\n * Per-collection strategy:\n * 'lww-set' records keyed by id; later write per id wins; deletes tombstoned.\n * 'lww-map' records keyed by id, merged field BY field: concurrent edits\n * to different fields of one record both survive; the same field\n * goes to the later clock. Deletes stay record-level.\n * 'grow-set' append-only; entries immutable; union, never a conflict.\n * 'lww-register' a single value; later write wins.\n * 'manual' surface concurrent edits to the same id instead of resolving.\n *\n * For collaborative rich text, store an Automerge/Yjs document as a binary file\n * in the snapshot instead; this engine stops short of sequence CRDTs.\n */\n\nimport { type Hlc, issue, max, compare, createNode, hlcWall } from './hlc';\n\nexport type { Hlc } from './hlc';\nexport type Id = string;\nexport type MergeStrategy = 'lww-set' | 'lww-map' | 'grow-set' | 'lww-register' | 'manual';\n\nexport interface SyncConfig {\n\t/** Field holding each record's stable id (default 'id'). Dot-notation allowed. */\n\tidField?: string;\n\t/** Per-collection id path override for records whose id is not at the top level. */\n\tids?: Record<string, string>;\n\t/** Strategy per collection name; anything unlisted uses `fallback`. */\n\tstrategies?: Record<string, MergeStrategy>;\n\t/** Strategy for unlisted collections (default 'lww-set'). */\n\tfallback?: MergeStrategy;\n}\n\n/** Sidecar metadata for one collection, keyed by record id. */\nexport interface ColMeta {\n\tclocks: Record<Id, Hlc>;\n\tdeleted: Record<Id, Hlc>;\n\thashes: Record<Id, number>;\n\t/** 'lww-map' only: per-field [clock, contentHash], so concurrent edits to\n\t * different fields of one record merge instead of one side losing. */\n\tfields?: Record<Id, Record<string, [Hlc, number]>>;\n}\n\n/** Everything one replica tracks. Travels with the data so the other side can merge. */\nexport interface SyncMeta {\n\tnode: string;\n\tclock: Hlc | null;\n\tcols: Record<string, ColMeta>;\n}\n\n/** A same-id concurrent edit the 'manual' strategy refused to auto-resolve.\n * `local`/`remote` carry the two full versions during the session; consumers\n * persisting conflicts (e.g. the store's journal) redact them, so they are\n * absent after a reload - never write user records to unencrypted storage. */\nexport interface Conflict {\n\tcollection: string;\n\tid: Id;\n\tlocal?: unknown;\n\tremote?: unknown;\n\t/** The side kept as the provisional value (so it still converges). */\n\tkept: 'local' | 'remote';\n}\n\nexport interface MergeResult {\n\tcollections: Record<string, unknown[]>;\n\tmeta: SyncMeta;\n\tconflicts: Conflict[];\n}\n\nfunction stableStringify(v: unknown): string {\n\tif (v === null || typeof v !== 'object') return JSON.stringify(v) ?? 'null';\n\tif (Array.isArray(v)) return '[' + v.map(stableStringify).join(',') + ']';\n\tconst o = v as Record<string, unknown>;\n\treturn (\n\t\t'{' +\n\t\tObject.keys(o)\n\t\t\t.sort()\n\t\t\t.map((k) => JSON.stringify(k) + ':' + stableStringify(o[k]))\n\t\t\t.join(',') +\n\t\t'}'\n\t);\n}\n\n/** FNV-1a 32-bit hash, enough to detect that a record changed. */\nfunction hash(v: unknown): number {\n\tconst s = stableStringify(v);\n\tlet h = 0x811c9dc5;\n\tfor (let i = 0; i < s.length; i++) {\n\t\th ^= s.charCodeAt(i);\n\t\th = Math.imul(h, 0x01000193);\n\t}\n\treturn h >>> 0;\n}\n\nfunction emptyCol(): ColMeta {\n\treturn { clocks: {}, deleted: {}, hashes: {} };\n}\n\nfunction cloneCol(c: ColMeta | undefined): ColMeta {\n\tif (!c) return emptyCol();\n\tconst out: ColMeta = {\n\t\tclocks: { ...c.clocks },\n\t\tdeleted: { ...c.deleted },\n\t\thashes: { ...c.hashes }\n\t};\n\tif (c.fields) {\n\t\tout.fields = {};\n\t\tfor (const [id, f] of Object.entries(c.fields)) out.fields[id] = { ...f };\n\t}\n\treturn out;\n}\n\nfunction strategyOf(config: SyncConfig, collection: string): MergeStrategy {\n\treturn config.strategies?.[collection] ?? config.fallback ?? 'lww-set';\n}\n\nfunction idOf(config: SyncConfig, collection: string, doc: unknown): Id | null {\n\tconst path = config.ids?.[collection] ?? config.idField ?? 'id';\n\tlet v: unknown = doc;\n\tfor (const key of path.split('.')) {\n\t\tif (v === null || typeof v !== 'object') return null;\n\t\tv = (v as Record<string, unknown>)[key];\n\t}\n\treturn typeof v === 'string' ? v : null;\n}\n\n/** The id path this config resolves for a collection (for diagnostics). */\nexport function idPathFor(config: SyncConfig, collection: string): string {\n\treturn config.ids?.[collection] ?? config.idField ?? 'id';\n}\n\n/** Records the engine cannot track because they have no string id at the\n * configured path. Identity is a string id field, so such a record is invisible\n * to stamp/merge - it would silently never replicate. Returns the count per\n * collection (nonzero entries only), for a dev-time warning. A number or missing\n * id is the classic footgun: give each record a string id, or map the field via\n * `sync.ids`. */\nexport function unsyncableCounts(\n\tcollections: Record<string, unknown[]>,\n\tconfig: SyncConfig = {}\n): Record<string, number> {\n\tconst out: Record<string, number> = {};\n\tfor (const [name, docs] of Object.entries(collections)) {\n\t\tlet n = 0;\n\t\tfor (const doc of docs ?? []) if (idOf(config, name, doc) === null) n++;\n\t\tif (n > 0) out[name] = n;\n\t}\n\treturn out;\n}\n\nexport function createMeta(node: string = createNode()): SyncMeta {\n\treturn { node, clock: null, cols: {} };\n}\n\n/**\n * Prune tombstones (delete markers) whose clock is older than `beforeWall` (a\n * wall-time ms cutoff, e.g. `Date.now() - horizon`). Tombstones otherwise\n * accumulate forever, since a delete must be remembered to out-vote a stale\n * replica still holding the record.\n *\n * Safe only when the cutoff is older than the longest a replica can stay offline\n * before it next syncs: a replica that never saw a delete, and whose tombstone\n * has been GC'd everywhere, will resurrect that record on its next merge. Pick a\n * horizon comfortably beyond your worst-case offline window. Returns new meta\n * (input untouched); present-record clocks are untouched.\n */\nexport function gcTombstones(meta: SyncMeta, beforeWall: number): SyncMeta {\n\tconst cols: Record<string, ColMeta> = {};\n\tfor (const [name, col] of Object.entries(meta.cols)) {\n\t\tconst next = cloneCol(col);\n\t\tfor (const [id, clk] of Object.entries(next.deleted)) {\n\t\t\tif (hlcWall(clk) < beforeWall) delete next.deleted[id];\n\t\t}\n\t\tcols[name] = next;\n\t}\n\treturn { node: meta.node, clock: meta.clock, cols };\n}\n\n/**\n * Diff `collections` against the metadata's content hashes, assign a fresh clock\n * to every added or changed record, and tombstone records that disappeared.\n * Returns new metadata (input untouched). Call on every local save.\n */\ntype Tick = () => Hlc;\n\n/** Stamp each present record: a fresh clock for anything added or changed (by\n * content hash). Returns the set of ids seen, for the tombstone pass. */\nfunction stampPresent(\n\tcol: ColMeta,\n\tpresent: unknown[],\n\tconfig: SyncConfig,\n\tname: string,\n\ttick: Tick\n): Set<Id> {\n\tconst fieldLevel = strategyOf(config, name) === 'lww-map';\n\tconst seen = new Set<Id>();\n\tfor (const doc of present) {\n\t\tconst id = idOf(config, name, doc);\n\t\tif (id === null) continue;\n\t\tseen.add(id);\n\t\tconst h = hash(doc);\n\t\tif (col.hashes[id] !== h) {\n\t\t\tcol.clocks[id] = tick();\n\t\t\tcol.hashes[id] = h;\n\t\t\tdelete col.deleted[id];\n\t\t\tif (fieldLevel && isRecord(doc)) stampFields(col, id, doc, tick);\n\t\t}\n\t}\n\treturn seen;\n}\n\n/** Stamp each added/changed field of one record ('lww-map'): a field keeps its\n * clock until its own content changes, so field clocks stay independent and a\n * merge can tell which side last touched which field. */\nfunction stampFields(col: ColMeta, id: Id, doc: Record<string, unknown>, tick: Tick): void {\n\tconst rec = ((col.fields ??= {})[id] ??= {});\n\tfor (const key of Object.keys(doc)) {\n\t\tconst h = hash(doc[key]);\n\t\tif (rec[key]?.[1] !== h) rec[key] = [tick(), h];\n\t}\n\tfor (const key of Object.keys(rec)) if (!(key in doc)) delete rec[key];\n}\n\n/** Tombstone every previously-tracked id that is no longer present. */\nfunction tombstoneMissing(col: ColMeta, seen: Set<Id>, tick: Tick): void {\n\tfor (const id of Object.keys(col.clocks)) {\n\t\tif (!seen.has(id)) {\n\t\t\tcol.deleted[id] = tick();\n\t\t\tdelete col.clocks[id];\n\t\t\tdelete col.hashes[id];\n\t\t\tdelete col.fields?.[id];\n\t\t}\n\t}\n}\n\nexport function stamp(\n\tmeta: SyncMeta,\n\tcollections: Record<string, unknown[]>,\n\tconfig: SyncConfig,\n\twallNow: number = Date.now()\n): SyncMeta {\n\tlet clock = meta.clock;\n\tconst tick: Tick = () => (clock = issue(clock, meta.node, wallNow));\n\tconst cols: Record<string, ColMeta> = {};\n\tconst names = new Set([...Object.keys(meta.cols), ...Object.keys(collections)]);\n\n\tfor (const name of names) {\n\t\tconst strategy = strategyOf(config, name);\n\t\tconst col = cloneCol(meta.cols[name]);\n\t\tconst present = collections[name] ?? [];\n\n\t\tconst seen = stampPresent(col, present, config, name, tick);\n\t\tif (strategy !== 'grow-set') tombstoneMissing(col, seen, tick);\n\t\tcols[name] = col;\n\t}\n\treturn { node: meta.node, clock, cols };\n}\n\n/** One replica's full state: the collections plus their sync metadata. What\n * `merge` and `detectConflicts` take on each side. */\nexport interface ReplicaState {\n\tcollections: Record<string, unknown[]>;\n\tmeta: SyncMeta;\n}\ntype State = ReplicaState;\n\n/**\n * Merge replica `b` into `a` (commutative for the convergent strategies). The\n * optional `base` lets 'manual' tell a true concurrent conflict from a one-sided\n * change; without it, 'manual' errs toward surfacing.\n */\nexport function merge(a: State, b: State, config: SyncConfig, base?: SyncMeta): MergeResult {\n\tconst conflicts: Conflict[] = [];\n\tconst outCols: Record<string, unknown[]> = {};\n\tconst outMeta: SyncMeta = {\n\t\tnode: a.meta.node,\n\t\tclock: max(a.meta.clock, b.meta.clock),\n\t\tcols: {}\n\t};\n\n\tconst names = new Set([\n\t\t...Object.keys(a.collections),\n\t\t...Object.keys(b.collections),\n\t\t...Object.keys(a.meta.cols),\n\t\t...Object.keys(b.meta.cols)\n\t]);\n\n\tfor (const name of names) {\n\t\tconst merged = mergeCollection(name, a, b, config, base, conflicts);\n\t\toutCols[name] = merged.docs;\n\t\toutMeta.cols[name] = merged.col;\n\t}\n\n\treturn { collections: outCols, meta: outMeta, conflicts };\n}\n\n/**\n * Concurrent same-id edits that a convergent strategy resolves silently by keeping\n * the later clock. Informational only: it never changes what `merge` keeps, it\n * reports which records both replicas changed since `base` (the loser's version is\n * the one merge dropped). Empty without a base, since two-way state alone cannot\n * tell a concurrent edit from a one-sided change.\n */\nexport function detectConflicts(\n\ta: State,\n\tb: State,\n\tconfig: SyncConfig,\n\tbase?: SyncMeta\n): Conflict[] {\n\tif (!base) return [];\n\tconst out: Conflict[] = [];\n\tconst names = new Set([...Object.keys(a.collections), ...Object.keys(b.collections)]);\n\tfor (const name of names) {\n\t\tconst strategy = strategyOf(config, name);\n\t\tconst aDocs = byId(config, name, a.collections[name]);\n\t\tconst bDocs = byId(config, name, b.collections[name]);\n\t\tconst aMeta = a.meta.cols[name] ?? emptyCol();\n\t\tconst bMeta = b.meta.cols[name] ?? emptyCol();\n\t\tconst baseMeta = base.cols[name];\n\t\tfor (const id of new Set<Id>([...aDocs.keys(), ...bDocs.keys()])) {\n\t\t\tconst aClk = aMeta.clocks[id] ?? null;\n\t\t\tconst bClk = bMeta.clocks[id] ?? null;\n\t\t\tconst winnerSide = pickWinner(aClk, bClk, aDocs.has(id));\n\t\t\t// lww-map merges different-field edits cleanly: only the same field\n\t\t\t// changed on both sides is a real (auto-resolved) conflict.\n\t\t\tconst conflict =\n\t\t\t\tstrategy === 'lww-map'\n\t\t\t\t\t? fieldConflict(\n\t\t\t\t\t\t\tname,\n\t\t\t\t\t\t\tid,\n\t\t\t\t\t\t\taDocs,\n\t\t\t\t\t\t\tbDocs,\n\t\t\t\t\t\t\taMeta.fields?.[id],\n\t\t\t\t\t\t\tbMeta.fields?.[id],\n\t\t\t\t\t\t\tbaseMeta?.fields?.[id],\n\t\t\t\t\t\t\twinnerSide\n\t\t\t\t\t\t)\n\t\t\t\t\t: manualConflict(name, id, aDocs, bDocs, aClk, bClk, baseMeta, winnerSide);\n\t\t\tif (conflict) out.push(conflict);\n\t\t}\n\t}\n\treturn out;\n}\n\n/** The 'lww-map' conflict check: a Conflict only when the same field changed on\n * both sides since base (different-field edits merge without loss). */\nfunction fieldConflict(\n\tname: string,\n\tid: Id,\n\taDocs: Map<Id, unknown>,\n\tbDocs: Map<Id, unknown>,\n\taF: Record<string, [Hlc, number]> | undefined,\n\tbF: Record<string, [Hlc, number]> | undefined,\n\tbaseF: Record<string, [Hlc, number]> | undefined,\n\twinnerSide: 'local' | 'remote'\n): Conflict | null {\n\tif (!(aDocs.has(id) && bDocs.has(id) && aF && bF)) return null;\n\tfor (const key of Object.keys(aF)) {\n\t\tconst a = aF[key];\n\t\tconst b = bF[key];\n\t\tif (!b || a[0] === b[0]) continue;\n\t\tconst baseClk = baseF?.[key]?.[0] ?? null;\n\t\tconst aChanged = !baseClk || compare(a[0], baseClk) > 0;\n\t\tconst bChanged = !baseClk || compare(b[0], baseClk) > 0;\n\t\tif (aChanged && bChanged)\n\t\t\treturn {\n\t\t\t\tcollection: name,\n\t\t\t\tid,\n\t\t\t\tlocal: aDocs.get(id),\n\t\t\t\tremote: bDocs.get(id),\n\t\t\t\tkept: winnerSide\n\t\t\t};\n\t}\n\treturn null;\n}\n\n/** The 'manual' concurrent-edit check for one record: a Conflict when both sides\n * changed the same id since `base` (or, without a base, both simply hold it and\n * their clocks differ). Pure; null when there is nothing to surface. */\nfunction manualConflict(\n\tname: string,\n\tid: Id,\n\taDocs: Map<Id, unknown>,\n\tbDocs: Map<Id, unknown>,\n\taClk: Hlc | null,\n\tbClk: Hlc | null,\n\tbaseMeta: ColMeta | undefined,\n\twinnerSide: 'local' | 'remote'\n): Conflict | null {\n\tif (!(aDocs.has(id) && bDocs.has(id) && aClk && bClk && aClk !== bClk)) return null;\n\tconst baseClk = baseMeta?.clocks[id] ?? null;\n\tconst aChanged = !baseClk || compare(aClk, baseClk) > 0;\n\tconst bChanged = !baseClk || compare(bClk, baseClk) > 0;\n\tif (!(aChanged && bChanged)) return null;\n\treturn { collection: name, id, local: aDocs.get(id), remote: bDocs.get(id), kept: winnerSide };\n}\n\n/** Reconcile every id of one collection across the two replicas. */\nfunction mergeCollection(\n\tname: string,\n\ta: State,\n\tb: State,\n\tconfig: SyncConfig,\n\tbase: SyncMeta | undefined,\n\tconflicts: Conflict[]\n): { docs: unknown[]; col: ColMeta } {\n\tconst strategy = strategyOf(config, name);\n\tconst aDocs = byId(config, name, a.collections[name]);\n\tconst bDocs = byId(config, name, b.collections[name]);\n\tconst aMeta = a.meta.cols[name] ?? emptyCol();\n\tconst bMeta = b.meta.cols[name] ?? emptyCol();\n\tconst baseMeta = base?.cols[name];\n\tconst col = emptyCol();\n\tconst docs: unknown[] = [];\n\n\tconst ids = new Set<Id>([\n\t\t...Object.keys(aMeta.clocks),\n\t\t...Object.keys(bMeta.clocks),\n\t\t...Object.keys(aMeta.deleted),\n\t\t...Object.keys(bMeta.deleted),\n\t\t...aDocs.keys(),\n\t\t...bDocs.keys()\n\t]);\n\n\tfor (const id of ids) {\n\t\tconst aClk = aMeta.clocks[id] ?? null;\n\t\tconst bClk = bMeta.clocks[id] ?? null;\n\t\tconst aliveClk = max(aClk, bClk);\n\t\tconst deadClk = max(aMeta.deleted[id] ?? null, bMeta.deleted[id] ?? null);\n\n\t\tconst deleteWins =\n\t\t\tstrategy !== 'grow-set' && deadClk && (!aliveClk || compare(deadClk, aliveClk) > 0);\n\n\t\tif (deleteWins) {\n\t\t\tcol.deleted[id] = deadClk as Hlc;\n\t\t\tcontinue;\n\t\t}\n\t\tif (!aliveClk) {\n\t\t\tif (deadClk) col.deleted[id] = deadClk;\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst aHas = aDocs.has(id);\n\t\tconst winnerSide = pickWinner(aClk, bClk, aHas);\n\t\tlet doc = winnerSide === 'local' ? aDocs.get(id) : bDocs.get(id);\n\n\t\tif (strategy === 'manual') {\n\t\t\tconst conflict = manualConflict(name, id, aDocs, bDocs, aClk, bClk, baseMeta, winnerSide);\n\t\t\tif (conflict) conflicts.push(conflict);\n\t\t}\n\n\t\tif (strategy === 'lww-map') {\n\t\t\tconst merged = mergeFields(id, aDocs.get(id), bDocs.get(id), aMeta, bMeta, winnerSide);\n\t\t\tif (merged) {\n\t\t\t\tdoc = merged.doc;\n\t\t\t\t(col.fields ??= {})[id] = merged.fields;\n\t\t\t} else {\n\t\t\t\t// One side has no document: the record-level winner stands; carry its\n\t\t\t\t// field metadata forward so later merges stay field-aware.\n\t\t\t\tconst f = (winnerSide === 'local' ? aMeta : bMeta).fields?.[id];\n\t\t\t\tif (f) (col.fields ??= {})[id] = { ...f };\n\t\t\t}\n\t\t}\n\n\t\tif (doc !== undefined) {\n\t\t\tdocs.push(doc);\n\t\t\tcol.clocks[id] = aliveClk;\n\t\t\tcol.hashes[id] = hash(doc);\n\t\t}\n\t}\n\n\treturn { docs, col };\n}\n\n/** Field-by-field merge of one record ('lww-map'): each field goes to the side\n * whose field clock is later; a field only one side stamped keeps that side's\n * value; unstamped fields (written under a record-level strategy) follow the\n * record-level winner.\n * Null when either document is missing (the record-level result stands). */\nfunction mergeFields(\n\tid: Id,\n\taDoc: unknown,\n\tbDoc: unknown,\n\taMeta: ColMeta,\n\tbMeta: ColMeta,\n\twinnerSide: 'local' | 'remote'\n): { doc: Record<string, unknown>; fields: Record<string, [Hlc, number]> } | null {\n\tif (!isRecord(aDoc) || !isRecord(bDoc)) return null;\n\tconst aF = aMeta.fields?.[id] ?? {};\n\tconst bF = bMeta.fields?.[id] ?? {};\n\tconst doc: Record<string, unknown> = {};\n\tconst fields: Record<string, [Hlc, number]> = {};\n\tfor (const key of new Set([...Object.keys(aDoc), ...Object.keys(bDoc)])) {\n\t\tconst a = aF[key] ?? null;\n\t\tconst b = bF[key] ?? null;\n\t\tconst side = pickFieldSide(a, b, winnerSide);\n\t\tconst src = side === 'local' ? aDoc : bDoc;\n\t\tif (!(key in src)) continue; // the winning side removed the field: drop it\n\t\tdoc[key] = src[key];\n\t\tconst stamped = side === 'local' ? a : b;\n\t\tif (stamped) fields[key] = stamped;\n\t}\n\treturn { doc, fields };\n}\n\n/** Which side supplies one field of an 'lww-map' record: the later field clock\n * when both sides stamped it, the stamping side when only one did, else the\n * record-level winner (unstamped fields follow the record). */\nfunction pickFieldSide(\n\ta: [Hlc, number] | null,\n\tb: [Hlc, number] | null,\n\twinnerSide: 'local' | 'remote'\n): 'local' | 'remote' {\n\tif (a && b) return compare(a[0], b[0]) >= 0 ? 'local' : 'remote';\n\tif (a) return 'local';\n\tif (b) return 'remote';\n\treturn winnerSide;\n}\n\nfunction isRecord(v: unknown): v is Record<string, unknown> {\n\treturn typeof v === 'object' && v !== null && !Array.isArray(v);\n}\n\n/** Which side's value to keep for a record. With both clocks present, the later\n * clock wins (ties to local); otherwise the side that actually holds the doc. */\nfunction pickWinner(aClk: Hlc | null, bClk: Hlc | null, aHas: boolean): 'local' | 'remote' {\n\tif (aClk && bClk) return compare(aClk, bClk) >= 0 ? 'local' : 'remote';\n\treturn aHas ? 'local' : 'remote';\n}\n\nfunction byId(\n\tconfig: SyncConfig,\n\tcollection: string,\n\tdocs: unknown[] | undefined\n): Map<Id, unknown> {\n\tconst m = new Map<Id, unknown>();\n\tfor (const d of docs ?? []) {\n\t\tconst id = idOf(config, collection, d);\n\t\tif (id !== null) m.set(id, d);\n\t}\n\treturn m;\n}\n\n/** What a merge did to one collection, counted by record. */\nexport interface CollectionChanges {\n\tadded: number;\n\tupdated: number;\n\tremoved: number;\n}\n\n/** Per-collection record counts of what changed between two states (same id\n * resolution as the merge). Collections with no change are omitted, so an empty\n * result means the states are equivalent. Feeds the sync journal. */\nexport function changes(\n\tbefore: Record<string, unknown[]>,\n\tafter: Record<string, unknown[]>,\n\tconfig: SyncConfig\n): Record<string, CollectionChanges> {\n\tconst out: Record<string, CollectionChanges> = {};\n\tfor (const name of new Set([...Object.keys(before), ...Object.keys(after)])) {\n\t\tconst b = byId(config, name, before[name]);\n\t\tconst a = byId(config, name, after[name]);\n\t\tlet added = 0;\n\t\tlet updated = 0;\n\t\tlet removed = 0;\n\t\tfor (const [id, doc] of a) {\n\t\t\tif (!b.has(id)) added++;\n\t\t\telse if (hash(doc) !== hash(b.get(id))) updated++;\n\t\t}\n\t\tfor (const id of b.keys()) if (!a.has(id)) removed++;\n\t\tif (added || updated || removed) out[name] = { added, updated, removed };\n\t}\n\treturn out;\n}\n"]}
|