selfstore 1.7.5 → 1.8.0
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 +23 -1
- package/dist/advanced.d.ts +12 -3
- package/dist/advanced.js +1 -1
- package/dist/{chunk-RYYO5PKJ.js → chunk-OZS7BSC2.js} +3 -3
- package/dist/{chunk-RYYO5PKJ.js.map → chunk-OZS7BSC2.js.map} +1 -1
- package/dist/{chunk-PK5BFQLI.js → chunk-XBPJRUA3.js} +105 -10
- package/dist/chunk-XBPJRUA3.js.map +1 -0
- package/dist/flows.js +2 -2
- package/dist/index.d.ts +74 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/widgets.js +2 -2
- package/package.json +1 -1
- package/dist/chunk-PK5BFQLI.js.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/persistence/targets/abort.ts","../src/persistence/targets/drive.ts","../src/persistence/targets/desktop.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","openExisting","DATA_DEADLINE_MS","META_DEADLINE_MS","CONFIG_KEY","KEY_KEY","configKey","saveConfig","loadConfig","labelOf","versionOf","fromConfig"],"mappings":"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","file":"chunk-XBPJRUA3.js","sourcesContent":["/**\n * Deadline + life-signal composition for target requests. Every target fetch\n * carries a per-request deadline; composing it with the target's LIFE signal\n * lets abortInFlight() (the user detaching the destination) cut a suspended\n * request NOW instead of letting the detach wait behind the deadline. The\n * composition is done by hand (not AbortSignal.any) so behaviour is identical\n * on every engine the library supports.\n */\n\nimport { SelfstoreError } from '../../selfstore';\n\nexport function boundedSignal(deadlineMs: number, life?: AbortSignal): AbortSignal {\n\tconst deadline = AbortSignal.timeout(deadlineMs);\n\tif (!life) return deadline;\n\tif (life.aborted) return life;\n\tconst ctl = new AbortController();\n\tdeadline.addEventListener('abort', () => ctl.abort(deadline.reason), { once: true });\n\tlife.addEventListener('abort', () => ctl.abort(life.reason), { once: true });\n\treturn ctl.signal;\n}\n\n/** A target's resettable LIFE line. cut() aborts everything bound to the\n * CURRENT signal (with the standard transient detach reason) and re-arms,\n * so work started after the cut lives normally. cut is `this`-free: targets\n * expose it directly as their abortInFlight. */\nexport function lifeLine(): { current(): AbortSignal; cut(): void } {\n\tlet ctl = new AbortController();\n\treturn {\n\t\tcurrent: () => ctl.signal,\n\t\tcut(): void {\n\t\t\tconst c = ctl;\n\t\t\tctl = new AbortController();\n\t\t\tc.abort(new SelfstoreError('TARGET_UNAVAILABLE', 'Request aborted: target detaching.'));\n\t\t}\n\t};\n}\n","/**\n * Google Drive backup target. Does the Drive REST file operations (find-or-create,\n * upload, download) for a single backup file. It only ships bytes; the store\n * decides whether they are encrypted. How an access token is obtained (a browser\n * OAuth flow, a backend broker, ...) is injected via DriveAuth, so this target\n * stays free of any specific auth strategy or backend.\n *\n * Beyond the standard connect/fromSession lifecycle, this module exposes\n * shared-file primitives (preview, adopt, findOrCreateOwnFile) for apps whose\n * users point several devices - or several people - at one Drive-hosted backup\n * file: preview it read-only, then adopt it as this device's backup.\n */\n\nimport type { BackupTarget } from '../target';\nimport type { KV } from '../cache';\nimport { boundedSignal, lifeLine } from './abort';\nimport { AuthExpiredError, SelfstoreError, isAuthExpired } from '../../selfstore';\n\n/** KV key holding the connected backup's Drive file id. Public because app-level\n * flows that operate on the same file (e.g. sharing it) need to read it. */\nexport const FILE_ID_KEY = 'driveFileId';\nconst DRIVE_UPLOAD = 'https://www.googleapis.com/upload/drive/v3/files';\nconst DRIVE_FILES = 'https://www.googleapis.com/drive/v3/files';\n\n/** A hung request must not spin forever behind a dead connection - the very\n * \"long moment trying to connect\" a stuck backup shows. A deadline turns the\n * hang into an ordinary error (transient: the store retries), never a gate.\n * Data transfers get more room than metadata pokes. AbortSignal.timeout is\n * minted per fetch (a fired signal cannot be reused across driveFetch's retry). */\nconst DATA_DEADLINE_MS = 30_000;\nconst META_DEADLINE_MS = 15_000;\n\n/** How the target gets access. Injected, so the auth strategy lives in the app.\n * Error protocol: a genuine loss of access (the session is gone, re-consent\n * needed) must throw AuthExpiredError - or any error with code 'AUTH_EXPIRED' -\n * while a transient offline/cold-start failure throws a plain error, so the\n * store retries it instead of raising the reconnect gate. */\nexport interface DriveAuth {\n\t/** A fresh access token, or throw if access must be re-established. Pass\n\t * `{ forceRefresh: true }` to bypass any cached token and mint a new one:\n\t * the target calls this after a 401 to tell a merely STALE token (a refresh\n\t * fixes it, no gate) from a genuinely lost session (the refresh 401s too).\n\t * An implementation that cannot force a refresh may ignore the flag; the\n\t * target then retries with the same token, no worse than before.\n\t * `signal` fires when the target's in-flight work is cut (the user is\n\t * detaching): an implementation that runs its own retry loop should abort\n\t * it then (pass the signal to its fetches, stop retrying); ignoring the\n\t * signal only means the loop runs to its own deadline first. */\n\ttoken(opts?: { forceRefresh?: boolean; signal?: AbortSignal }): Promise<string>;\n\t/** Re-establish access with a user gesture; resolves to whether it worked. */\n\treconnect(): Promise<boolean>;\n\t/** Forget the connection, locally and server-side. */\n\tforget(): Promise<void>;\n}\n\n/** Run a Drive request; if Google answers 401 the access token may just be\n * STALE (rejected before its nominal expiry - rotation, an early revoke), so\n * force a fresh token and try once more. Only a SECOND 401 means the session\n * is genuinely gone. This is what stops a stale-token blip - common when a\n * flow makes many Drive calls, e.g. group-mode republishing every converge -\n * from raising the blocking reconnect gate over a still-valid connection. */\nasync function driveFetch(\n\tauth: DriveAuth,\n\trun: (token: string) => Promise<Response>,\n\tsignal?: AbortSignal\n): Promise<Response> {\n\tconst r = await run(await auth.token({ signal }));\n\tif (r.status !== 401) return r;\n\treturn run(await auth.token({ forceRefresh: true, signal }));\n}\n\nexport interface DriveOptions {\n\tauth: DriveAuth;\n\tkv: KV;\n\t/** Name of the backup file created in the user's Drive. */\n\tfileName: string;\n}\n\nasync function findExistingFile(token: string, fileNames: string[]): Promise<string | null> {\n\tfor (const name of fileNames) {\n\t\t// 'me' in owners: a shared backup carries the same name but lives on someone\n\t\t// else's Drive - only an OWNED file may be (re)connected as the personal\n\t\t// backup, or a fresh connect could silently re-adopt theirs.\n\t\t// Escape the name for Drive's query grammar (backslash then single-quote):\n\t\t// a name carrying a quote must not break out of the `name='...'` clause and\n\t\t// alter the filter (encodeURIComponent handles transport, not query syntax).\n\t\tconst safe = name.replace(/\\\\/g, '\\\\\\\\').replace(/'/g, \"\\\\'\");\n\t\tconst q = encodeURIComponent(`name='${safe}' and trashed=false and 'me' in owners`);\n\t\tconst r = await fetch(`${DRIVE_FILES}?q=${q}&fields=files(id)&pageSize=1`, {\n\t\t\theaders: { Authorization: `Bearer ${token}` },\n\t\t\tsignal: AbortSignal.timeout(META_DEADLINE_MS)\n\t\t});\n\t\tif (!r.ok) continue;\n\t\tconst data: { files?: { id: string }[] } = await r.json();\n\t\tconst id = data.files?.[0]?.id;\n\t\tif (id) return id;\n\t}\n\treturn null;\n}\n\n/** Classify a non-OK Drive write we could not otherwise handle (the 401 case is\n * the caller's). A 404 - the bound file is gone (deleted, trashed, id no longer\n * valid) - or a 403 that is not a rate limit (permission withdrawn, storage\n * full) is PERMANENT: retrying writes the same doomed request forever (the\n * silent \"momentarily unreachable\" trap), so the store must raise its gate and\n * let the user reconnect to a fresh file or free space. A 403 rate limit and\n * everything else (5xx, network) stay transient. The body carries Drive's\n * `reason` (e.g. userRateLimitExceeded vs storageQuotaExceeded); absent, a bare\n * 403 is treated as permanent (the safer default - a real rate limit names\n * itself). */\nfunction isPermanentDriveWrite(status: number, body: string): boolean {\n\tif (status === 404) return true;\n\tif (status === 403) return !/ratelimit|dailylimit/i.test(body);\n\treturn false;\n}\n\nasync function createFile(token: string, fileName: string): Promise<string> {\n\tconst boundary = 'SLFS_BOUNDARY';\n\tconst meta = JSON.stringify({ name: fileName, mimeType: 'application/octet-stream' });\n\tconst body = [\n\t\t`--${boundary}\\r\\nContent-Type: application/json; charset=UTF-8\\r\\n\\r\\n`,\n\t\tmeta,\n\t\t`\\r\\n--${boundary}\\r\\nContent-Type: application/octet-stream\\r\\n\\r\\n\\r\\n--${boundary}--`\n\t].join('');\n\tconst r = await fetch(`${DRIVE_UPLOAD}?uploadType=multipart`, {\n\t\tmethod: 'POST',\n\t\theaders: {\n\t\t\tAuthorization: `Bearer ${token}`,\n\t\t\t'Content-Type': `multipart/related; boundary=${boundary}`\n\t\t},\n\t\tbody,\n\t\tsignal: AbortSignal.timeout(META_DEADLINE_MS)\n\t});\n\tif (!r.ok) throw new SelfstoreError('TARGET_WRITE_FAILED', `Drive create failed: ${r.status}`);\n\tconst data: { id: string } = await r.json();\n\treturn data.id;\n}\n\nfunction makeTarget(opts: DriveOptions, fixedId?: string): BackupTarget {\n\tconst { auth, kv } = opts;\n\t// A target is BOUND to one file for its whole life: the id is the fixed one,\n\t// or the remembered id CAPTURED on first use - never re-read per operation.\n\t// The old lazy per-call read let a backup SWITCH re-point the remembered id\n\t// while the OUTGOING target was still attached: its final courtesy flush\n\t// then wrote the old store (data and sync bookkeeping) into the file being\n\t// opened, silently merging two \"isolated\" backups.\n\tlet captured: string | undefined = fixedId;\n\tconst fileIdOf = async (): Promise<string | undefined> =>\n\t\t(captured ??= await kv.get<string>(FILE_ID_KEY));\n\t// The instance's LIFE line: abortInFlight() (a user detach) cuts every\n\t// suspended request now instead of at its deadline. Requests mint their\n\t// signal per call, so work started AFTER a cut lives normally.\n\tconst life = lifeLine();\n\tconst bounded = (ms: number): AbortSignal => boundedSignal(ms, life.current());\n\treturn {\n\t\tkind: 'drive',\n\t\tlabel: 'Google Drive',\n\t\tabortInFlight: life.cut,\n\t\tasync save(blob: Blob): Promise<string | null> {\n\t\t\tconst fileId = await fileIdOf();\n\t\t\tif (!fileId) throw new SelfstoreError('NOT_CONNECTED', 'Drive not connected.');\n\t\t\t// fields=version: the response reports the file's new version, so the\n\t\t\t// store records exactly OUR write (no marker race with other replicas).\n\t\t\tconst r = await driveFetch(\n\t\t\t\tauth,\n\t\t\t\t(token) =>\n\t\t\t\t\tfetch(`${DRIVE_UPLOAD}/${fileId}?uploadType=media&fields=version`, {\n\t\t\t\t\t\tmethod: 'PATCH',\n\t\t\t\t\t\theaders: {\n\t\t\t\t\t\t\tAuthorization: `Bearer ${token}`,\n\t\t\t\t\t\t\t'Content-Type': 'application/octet-stream'\n\t\t\t\t\t\t},\n\t\t\t\t\t\tbody: blob,\n\t\t\t\t\t\tsignal: bounded(DATA_DEADLINE_MS)\n\t\t\t\t\t}),\n\t\t\t\tlife.current()\n\t\t\t);\n\t\t\tif (!r.ok) {\n\t\t\t\t// A 401 that survives a forced token refresh (driveFetch) is a genuine\n\t\t\t\t// loss of access, not a stale-token blip. A 404 (the bound file is\n\t\t\t\t// gone) or a non-rate-limit 403 (permission withdrawn, storage full) is\n\t\t\t\t// PERMANENT: gate so the user reconnects to a fresh file, never loop it\n\t\t\t\t// as transient. Everything else (5xx, network) stays transient.\n\t\t\t\tif (r.status === 401) throw new AuthExpiredError('Drive rejected the token (401).');\n\t\t\t\tconst body = await r.text().catch(() => '');\n\t\t\t\tif (isPermanentDriveWrite(r.status, body)) {\n\t\t\t\t\tthrow new SelfstoreError(\n\t\t\t\t\t\t'TARGET_GONE',\n\t\t\t\t\t\t`Drive backup unwritable (${r.status}): the file is gone or access was withdrawn.`\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tthrow new SelfstoreError('TARGET_WRITE_FAILED', `Drive upload failed: ${r.status}`);\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst data: { version?: string | number } = await r.json();\n\t\t\t\treturn data.version != null ? String(data.version) : null;\n\t\t\t} catch {\n\t\t\t\treturn null;\n\t\t\t}\n\t\t},\n\t\tasync load(): Promise<Blob | null> {\n\t\t\tconst fileId = await fileIdOf();\n\t\t\tif (!fileId) return null;\n\t\t\tconst r = await driveFetch(\n\t\t\t\tauth,\n\t\t\t\t(token) =>\n\t\t\t\t\tfetch(`${DRIVE_FILES}/${fileId}?alt=media`, {\n\t\t\t\t\t\theaders: { Authorization: `Bearer ${token}` },\n\t\t\t\t\t\tsignal: bounded(DATA_DEADLINE_MS)\n\t\t\t\t\t}),\n\t\t\t\tlife.current()\n\t\t\t);\n\t\t\t// A failed read must never pass for an empty file: null tells the store\n\t\t\t// \"nothing there\", and a fresh connect would then overwrite the backup\n\t\t\t// this download merely failed to fetch. Only a 404 - the file is\n\t\t\t// genuinely gone - reads as empty; a 401 that survived driveFetch's\n\t\t\t// forced refresh is a real loss of access; anything else is a typed\n\t\t\t// transient failure (the sync legs retry it, connect flows surface it).\n\t\t\tif (r.status === 404) return null;\n\t\t\tif (r.status === 401) throw new AuthExpiredError('Drive rejected the token (401).');\n\t\t\tif (!r.ok) {\n\t\t\t\tthrow new SelfstoreError('TARGET_UNAVAILABLE', `Drive download failed: ${r.status}`);\n\t\t\t}\n\t\t\treturn r.blob();\n\t\t},\n\t\tasync stat(): Promise<string | null> {\n\t\t\ttry {\n\t\t\t\tconst fileId = await fileIdOf();\n\t\t\t\tif (!fileId) return null;\n\t\t\t\tconst r = await driveFetch(\n\t\t\t\t\tauth,\n\t\t\t\t\t(token) =>\n\t\t\t\t\t\tfetch(`${DRIVE_FILES}/${fileId}?fields=version`, {\n\t\t\t\t\t\t\theaders: { Authorization: `Bearer ${token}` },\n\t\t\t\t\t\t\tsignal: bounded(META_DEADLINE_MS)\n\t\t\t\t\t\t}),\n\t\t\t\t\tlife.current()\n\t\t\t\t);\n\t\t\t\tif (!r.ok) return null;\n\t\t\t\tconst data: { version?: string | number } = await r.json();\n\t\t\t\treturn data.version != null ? String(data.version) : null;\n\t\t\t} catch {\n\t\t\t\treturn null; // offline or token lapse: \"cannot tell\", never throws\n\t\t\t}\n\t\t},\n\t\tasync isReady(): Promise<boolean> {\n\t\t\ttry {\n\t\t\t\tawait auth.token({ signal: life.current() });\n\t\t\t\treturn true;\n\t\t\t} catch (e) {\n\t\t\t\t// A transient token failure means \"not writable right now\", not a lost\n\t\t\t\t// session: report false so the store retries. Only a genuine auth loss\n\t\t\t\t// propagates, so the boot path can raise the reconnect gate.\n\t\t\t\tif (isAuthExpired(e)) throw e;\n\t\t\t\treturn false;\n\t\t\t}\n\t\t},\n\t\tasync reconnect(): Promise<boolean> {\n\t\t\treturn auth.reconnect();\n\t\t},\n\t\tasync disconnect(): Promise<void> {\n\t\t\tawait auth.forget();\n\t\t\t// Delete the remembered id only while it still points at this target's\n\t\t\t// file: after a backup switch the key already belongs to the new\n\t\t\t// target, and a late disconnect of the old one must not orphan it.\n\t\t\tconst current = await kv.get<string>(FILE_ID_KEY);\n\t\t\tconst own = await fileIdOf();\n\t\t\tif (!current || !own || current === own) await kv.del(FILE_ID_KEY);\n\t\t}\n\t};\n}\n\n/** First-time connect: authorize (a user gesture), then find-or-create the file.\n * Returns the target, or null if the user cancelled the consent. */\nexport async function connect(opts: DriveOptions): Promise<BackupTarget | null> {\n\tif (!(await opts.auth.reconnect())) return null;\n\tconst token = await opts.auth.token();\n\tlet fileId = await opts.kv.get<string>(FILE_ID_KEY);\n\tif (!fileId) {\n\t\tfileId =\n\t\t\t(await findExistingFile(token, [opts.fileName])) ?? (await createFile(token, opts.fileName));\n\t\tawait opts.kv.set(FILE_ID_KEY, fileId);\n\t}\n\treturn makeTarget(opts);\n}\n\n/** Rebuild the target from a past session's saved connection (no UI), or null. */\nexport async function fromSession(opts: DriveOptions): Promise<BackupTarget | null> {\n\treturn (await opts.kv.get<string>(FILE_ID_KEY)) ? makeTarget(opts) : null;\n}\n\n/** A read-only-intent preview of one specific (typically shared) file, before\n * adopting it. Writes NOTHING to the KV: until the user confirms, the device's\n * own backup wiring must stay untouched. */\nexport function preview(opts: DriveOptions, fileId: string): BackupTarget {\n\treturn makeTarget(opts, fileId);\n}\n\n/** Adopt a specific (typically shared) file as this device's backup. Persists\n * the file id, so next sessions restore it like any Drive backup. */\nexport async function adopt(opts: DriveOptions, fileId: string): Promise<BackupTarget> {\n\tawait opts.kv.set(FILE_ID_KEY, fileId);\n\treturn makeTarget(opts);\n}\n\n/** Find (or create) the user's own backup file, ignoring the currently adopted\n * id (which may point at someone else's shared file). Reuses the live session\n * when possible and only falls back to the consent popup. Adopts NOTHING: the\n * caller decides whether to load the file or start it blank. Null when the\n * consent is refused. */\nexport async function findOrCreateOwnFile(\n\topts: DriveOptions\n): Promise<{ fileId: string; created: boolean } | null> {\n\tlet token: string;\n\ttry {\n\t\ttoken = await opts.auth.token();\n\t} catch {\n\t\tif (!(await opts.auth.reconnect())) return null;\n\t\ttoken = await opts.auth.token();\n\t}\n\tconst existing = await findExistingFile(token, [opts.fileName]);\n\tif (existing) return { fileId: existing, created: false };\n\treturn { fileId: await createFile(token, opts.fileName), created: true };\n}\n\n/** 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 for a desktop shell (Tauri, and anything exposing the\n * same filesystem and dialog calls).\n *\n * The browser's File System Access API is Chromium-only and, inside a native\n * webview, usually absent altogether: on macOS and Linux the shell embeds\n * WebKit, which never shipped it. So the file destination would silently\n * degrade to download-on-demand in the one place where writing a real file is\n * easiest. This target restores it.\n *\n * What changes against the browser target is what gets persisted: a PATH, not a\n * handle. A path survives a restart, an app update and a backup of the profile\n * directory, and it needs no permission to be re-granted at each launch. The\n * store therefore reconnects silently where the browser asks for a click.\n *\n * The shell's own calls are INJECTED, never imported: this library keeps three\n * runtime dependencies, and a desktop toolkit is not going to be the fourth for\n * the sake of the web build that will never call it.\n */\n\nimport type { BackupTarget } from '../target';\nimport type { KV } from '../cache';\nimport { SelfstoreError } from '../../selfstore';\n\nconst PATH_KEY = 'desktopFilePath';\n\n/** Filter shape shared by the save and open dialogs. */\nexport interface DesktopDialogFilter {\n\tname: string;\n\textensions: string[];\n}\n\n/**\n * The filesystem and dialog calls this target needs, shaped so a host can pass\n * its shell's functions straight through with no adapter of its own.\n *\n * With Tauri v2 the whole wiring is the two plugin imports:\n *\n * ```ts\n * import { readFile, writeFile, stat, exists } from '@tauri-apps/plugin-fs';\n * import { save, open } from '@tauri-apps/plugin-dialog';\n * import { useDesktopFiles } from 'selfstore';\n *\n * useDesktopFiles({ readFile, writeFile, stat, exists, save, open });\n * ```\n *\n * Do that once at start-up and every `file` destination in this library runs on\n * a real path. Nothing else in a host changes.\n */\nexport interface DesktopFileBridge {\n\treadFile(path: string): Promise<Uint8Array>;\n\twriteFile(path: string, data: Uint8Array): Promise<void>;\n\t/** File metadata. Only `mtime` is read, and a shell that cannot report it\n\t * may leave it absent: the store then falls back to its own bookkeeping. */\n\tstat(path: string): Promise<{ mtime?: Date | number | null } | null>;\n\texists(path: string): Promise<boolean>;\n\t/** Save dialog. Resolves to the chosen path, or null when cancelled. */\n\tsave(options: { defaultPath?: string; filters?: DesktopDialogFilter[] }): Promise<string | null>;\n\t/** Open dialog. Resolves to the chosen path, or null when cancelled. The\n\t * array form is accepted so a host can pass its shell's function unchanged. */\n\topen(options: {\n\t\tmultiple?: boolean;\n\t\tfilters?: DesktopDialogFilter[];\n\t}): Promise<string | string[] | null>;\n}\n\nlet bridge: DesktopFileBridge | null = null;\n\n/**\n * Register the desktop shell's filesystem and dialog calls. Call once at\n * start-up, before opening the store. Passing null unregisters, which is what a\n * test wants between cases.\n */\nexport function useDesktopFiles(b: DesktopFileBridge | null): void {\n\tbridge = b;\n}\n\n/** True once a shell has been registered: the file destination then writes a\n * real path instead of asking the browser for a handle. */\nexport function hasDesktopFiles(): boolean {\n\treturn bridge != null;\n}\n\nconst FILTERS: DesktopDialogFilter[] = [{ name: 'Backup', extensions: ['zip'] }];\n\n/** Last path segment, for the label. Windows and POSIX separators both, since\n * the same build runs on either. */\nfunction baseName(path: string): string {\n\tconst parts = path.split(/[\\\\/]/);\n\treturn parts[parts.length - 1] || path;\n}\n\n/** Epoch milliseconds as the version marker, or null when the shell cannot\n * tell. Never throws: a marker that cannot be read is \"cannot tell\", and the\n * store treats that as no information rather than as a fault. */\nasync function markerOf(b: DesktopFileBridge, path: string): Promise<string | null> {\n\ttry {\n\t\tconst info = await b.stat(path);\n\t\tconst mtime = info?.mtime;\n\t\tif (mtime == null) return null;\n\t\tconst ms = mtime instanceof Date ? mtime.getTime() : Number(mtime);\n\t\treturn Number.isFinite(ms) ? String(ms) : null;\n\t} catch {\n\t\treturn null;\n\t}\n}\n\n/** Only the first path, whichever shape the shell's open dialog returns. */\nfunction firstPath(picked: string | string[] | null): string | null {\n\tif (picked == null) return null;\n\treturn Array.isArray(picked) ? (picked[0] ?? null) : picked;\n}\n\nfunction fromPath(path: string, b: DesktopFileBridge, kv: KV): BackupTarget {\n\t// reconnect() can move the destination to another file, so the path the\n\t// closure writes to has to be able to change under the object the store\n\t// already holds.\n\tlet current = path;\n\treturn {\n\t\tkind: 'file',\n\t\tget label(): string {\n\t\t\treturn baseName(current);\n\t\t},\n\t\tasync save(blob: Blob): Promise<string | null> {\n\t\t\ttry {\n\t\t\t\tawait b.writeFile(current, new Uint8Array(await blob.arrayBuffer()));\n\t\t\t} catch (e) {\n\t\t\t\t// No AuthExpiredError here on purpose. A desktop write fails because\n\t\t\t\t// the disk is full, the volume is unplugged or the file is locked by\n\t\t\t\t// another program - all transient, all resolved without the user\n\t\t\t\t// re-granting anything. Raising the reconnect gate would ask them to\n\t\t\t\t// re-pick a file that is exactly where they left it.\n\t\t\t\tthrow new SelfstoreError('TARGET_WRITE_FAILED', `File write failed: ${String(e)}`);\n\t\t\t}\n\t\t\treturn markerOf(b, current);\n\t\t},\n\t\tasync load(): Promise<Blob | null> {\n\t\t\ttry {\n\t\t\t\tconst bytes = await b.readFile(current);\n\t\t\t\treturn new Blob([new Uint8Array(bytes)]);\n\t\t\t} catch {\n\t\t\t\t// A path chosen through the save dialog names a file that does not\n\t\t\t\t// exist yet. \"No backup there\" is the honest answer, not a failure.\n\t\t\t\treturn null;\n\t\t\t}\n\t\t},\n\t\tasync stat(): Promise<string | null> {\n\t\t\treturn markerOf(b, current);\n\t\t},\n\t\tasync isReady(): Promise<boolean> {\n\t\t\t// A desktop write needs no user gesture and no grant that can lapse, so\n\t\t\t// the answer is yes even when the file is not there yet: refusing until\n\t\t\t// it exists would deadlock the very first save, which is what creates\n\t\t\t// it. A volume that went away surfaces when save() throws, and that\n\t\t\t// reads as transient - which is what an unplugged drive is.\n\t\t\treturn true;\n\t\t},\n\t\tasync reconnect(): Promise<boolean> {\n\t\t\tconst picked = await b.save({ defaultPath: current, filters: FILTERS });\n\t\t\tif (!picked) return false;\n\t\t\tcurrent = picked;\n\t\t\tawait kv.set(PATH_KEY, picked);\n\t\t\treturn true;\n\t\t},\n\t\tasync disconnect(): Promise<void> {\n\t\t\tawait kv.del(PATH_KEY);\n\t\t}\n\t};\n}\n\nexport interface DesktopConnectOptions {\n\t/** Where the chosen path is persisted across launches. */\n\tkv: KV;\n\t/** Suggested name for the backup file. */\n\tfileName: string;\n}\n\n/** Ask for a file to write the backup to, and remember it. Null when the user\n * cancelled, or when no shell is registered. */\nexport async function connect(opts: DesktopConnectOptions): Promise<BackupTarget | null> {\n\tif (!bridge) return null;\n\tconst picked = await bridge.save({ defaultPath: opts.fileName, filters: FILTERS });\n\tif (!picked) return null;\n\tawait opts.kv.set(PATH_KEY, picked);\n\treturn fromPath(picked, bridge, opts.kv);\n}\n\n/** Ask for an EXISTING backup file and adopt it as the destination; the connect\n * journey then reads it like any backup. Null when cancelled. */\nexport async function openExisting(opts: { kv: KV }): Promise<BackupTarget | null> {\n\tif (!bridge) return null;\n\tconst picked = firstPath(await bridge.open({ multiple: false, filters: FILTERS }));\n\tif (!picked) return null;\n\tawait opts.kv.set(PATH_KEY, picked);\n\treturn fromPath(picked, bridge, opts.kv);\n}\n\n/** Rebuild the target from the path kept in a past launch, or null. Unlike the\n * browser handle this needs no permission check, so a desktop app reopens on\n * its file with nothing asked of the user. */\nexport async function fromSession(opts: { kv: KV }): Promise<BackupTarget | null> {\n\tif (!bridge) return null;\n\tconst path = await opts.kv.get<string>(PATH_KEY);\n\treturn path ? fromPath(path, bridge, opts.kv) : null;\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';\nimport * as desktop from './desktop';\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/** Set once a picker that EXISTS has refused to open. Presence is not\n * capability: some browsers ship the methods and throw on call unless a flag\n * is turned on, so a host that trusts `'showSaveFilePicker' in window` offers\n * a file mode that can never work, and the button looks broken. The attempt is\n * the only honest probe there is - it costs one click, once. */\nlet refused = false;\n\n/** Was the call made inside a live user gesture?\n *\n * A picker REQUIRES transient activation, and a call made without one throws\n * the same DOMException as a browser that ships the picker and refuses it. Read\n * as a refusal, one mistimed call condemns the file mode for the whole session:\n * the host then falls back to manual downloads on a Chrome that was never\n * asked properly. A throw only teaches us about the browser when the browser\n * was actually allowed to answer. Undefined support (no userActivation) reads\n * as \"cannot tell\" - and cannot-tell must not accuse. */\nfunction insideGesture(): boolean {\n\tconst ua = typeof navigator !== 'undefined' ? navigator.userActivation : undefined;\n\treturn ua ? ua.isActive : true;\n}\n\n/** True when a re-writable file can be picked: a registered desktop shell, or\n * a browser that ships the File System Access API (Chromium) and has not\n * already refused to open a picker in this session.\n *\n * A desktop shell answers first and unconditionally. Inside a native webview\n * the browser probe is not merely negative, it is misleading: WebKit never\n * shipped the API, so the honest capability there is the shell's, and asking\n * the webview instead would degrade the file mode in the one environment where\n * writing a real file is easiest. */\nexport function isSupported(): boolean {\n\tif (desktop.hasDesktopFiles()) return true;\n\treturn !refused && typeof window !== 'undefined' && 'showSaveFilePicker' in window;\n}\n\n/** True when the browser can let the user point at an EXISTING file. Hosts must\n * gate that gesture on this: unlike creating, it has no download fallback, so\n * offering it where it cannot run leaves a button that answers nothing. The\n * refusal is shared with `isSupported` - a browser that refuses one picker\n * refuses the family. */\nexport function isOpenSupported(): boolean {\n\tif (desktop.hasDesktopFiles()) return true;\n\treturn !refused && typeof window !== 'undefined' && 'showOpenFilePicker' in window;\n}\n\n/** Run a picker. Cancelling and refusing both mean \"no file\", but only a\n * refusal says something about the browser - and it must not surface as an\n * error, or the host reports a fault where it should offer another way. */\nasync function ask<T>(open: () => Promise<T>): Promise<T | null> {\n\tconst asked = insideGesture();\n\ttry {\n\t\treturn await open();\n\t} catch (e) {\n\t\tif (e instanceof DOMException && e.name === 'AbortError') return null; // user cancelled\n\t\t// Outside a gesture the browser refused the CALL, not the feature: keep\n\t\t// the file mode intact so the host can ask again from a real click.\n\t\trefused = asked;\n\t\treturn null;\n\t}\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 - or if the browser\n * refused to open a picker at all, which `isSupported()` then reports. */\nexport async function connect(opts: FileConnectOptions): Promise<BackupTarget | null> {\n\tif (desktop.hasDesktopFiles()) return desktop.connect(opts);\n\tconst picker = (window as unknown as PickerWindow).showSaveFilePicker;\n\tif (!picker) return null;\n\tconst handle = await ask(() =>\n\t\tpicker({\n\t\t\tsuggestedName: opts.fileName,\n\t\t\ttypes: [{ description: 'Backup', accept: { 'application/zip': ['.zip'] } }]\n\t\t})\n\t);\n\tif (!handle) return null;\n\tawait opts.kv.set(HANDLE_KEY, handle);\n\treturn fromHandle(handle, opts.kv);\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). Tries to raise the grant to readwrite up front so the\n * first save does not stall on a second prompt. Returns null if cancelled. */\nexport async function openExisting(opts: { kv: KV }): Promise<BackupTarget | null> {\n\tif (desktop.hasDesktopFiles()) return desktop.openExisting(opts);\n\tconst picker = (window as unknown as PickerWindow).showOpenFilePicker;\n\tif (!picker) return null;\n\tconst picked = await ask(() =>\n\t\tpicker({\n\t\t\tmultiple: false,\n\t\t\ttypes: [{ description: 'Backup', accept: { 'application/zip': ['.zip'] } }]\n\t\t})\n\t);\n\tconst handle = picked?.[0];\n\tif (!handle) return null;\n\t// The upgrade to readwrite is a COURTESY, not a condition. It can fail for a\n\t// reason that has nothing to do with the file - the open picker consumes the\n\t// transient activation a permission prompt needs, so a browser may answer\n\t// 'prompt' without ever asking anyone. Throwing the picked file away then\n\t// dropped the host back on its own screen with nothing said and nothing\n\t// changed: the user pointed at their file and the app appeared to ignore it.\n\t// So we adopt it either way. Reading works on the grant the picker gave, and\n\t// a write that cannot happen raises the store's own reconnect gate, whose\n\t// one click re-asks from inside a real gesture - the only place it can work.\n\t// Outside `ask`: a refusal here is about THIS file, never about the picker,\n\t// which has just proved it opens.\n\ttry {\n\t\tawait handle.requestPermission({ mode: 'readwrite' });\n\t} catch {\n\t\t/* no prompt was possible: the reconnect gesture will ask for real */\n\t}\n\tawait opts.kv.set(HANDLE_KEY, handle);\n\treturn fromHandle(handle, opts.kv);\n}\n\n/** Rebuild the target from what a past session persisted, or null. On a desktop\n * shell that is a path and it reconnects silently; in a browser it is a handle\n * and the first write may still ask for the permission back. */\nexport async function fromSession(opts: { kv: KV }): Promise<BackupTarget | null> {\n\tif (desktop.hasDesktopFiles()) return desktop.fromSession(opts);\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 { boundedSignal, lifeLine } from './abort';\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\t// The instance's LIFE line: abortInFlight() (a user detach) cuts every\n\t// suspended request now instead of at its deadline (see the Drive target).\n\tconst life = lifeLine();\n\tconst bounded = (ms: number): AbortSignal => boundedSignal(ms, life.current());\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: bounded(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\tabortInFlight: life.cut,\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: bounded(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: bounded(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 { boundedSignal, lifeLine } from './abort';\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\t// The instance's LIFE line: abortInFlight() (a user detach) cuts every\n\t// suspended request now instead of at its deadline (see the Drive target).\n\tconst life = lifeLine();\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: boundedSignal(deadlineMs, life.current())\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\tabortInFlight: life.cut,\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"]}
|
package/dist/flows.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export { connectFlow, joinFlow, shareFlow } from './chunk-
|
|
2
|
-
import { connect, connect2, connect4, connect3, fromSession, fromSession2, fromSession3, fromSession4 } from './chunk-
|
|
1
|
+
export { connectFlow, joinFlow, shareFlow } from './chunk-OZS7BSC2.js';
|
|
2
|
+
import { connect, connect2, connect4, connect3, fromSession, fromSession2, fromSession3, fromSession4 } from './chunk-XBPJRUA3.js';
|
|
3
3
|
import './chunk-ZDHTKFAL.js';
|
|
4
4
|
import { makeMachine, toStoreError } from './chunk-OIAXYI35.js';
|
|
5
5
|
export { withDeadline } from './chunk-OIAXYI35.js';
|
package/dist/index.d.ts
CHANGED
|
@@ -243,4 +243,77 @@ interface SimpleStore<S extends Record<string, SimpleRecord> = Record<string, Si
|
|
|
243
243
|
*/
|
|
244
244
|
declare function selfstore<S extends Record<string, SimpleRecord> = Record<string, SimpleRecord>>(app: string, options?: SimpleOptions): Promise<SimpleStore<S>>;
|
|
245
245
|
|
|
246
|
-
|
|
246
|
+
/**
|
|
247
|
+
* Disk-file backup target for a desktop shell (Tauri, and anything exposing the
|
|
248
|
+
* same filesystem and dialog calls).
|
|
249
|
+
*
|
|
250
|
+
* The browser's File System Access API is Chromium-only and, inside a native
|
|
251
|
+
* webview, usually absent altogether: on macOS and Linux the shell embeds
|
|
252
|
+
* WebKit, which never shipped it. So the file destination would silently
|
|
253
|
+
* degrade to download-on-demand in the one place where writing a real file is
|
|
254
|
+
* easiest. This target restores it.
|
|
255
|
+
*
|
|
256
|
+
* What changes against the browser target is what gets persisted: a PATH, not a
|
|
257
|
+
* handle. A path survives a restart, an app update and a backup of the profile
|
|
258
|
+
* directory, and it needs no permission to be re-granted at each launch. The
|
|
259
|
+
* store therefore reconnects silently where the browser asks for a click.
|
|
260
|
+
*
|
|
261
|
+
* The shell's own calls are INJECTED, never imported: this library keeps three
|
|
262
|
+
* runtime dependencies, and a desktop toolkit is not going to be the fourth for
|
|
263
|
+
* the sake of the web build that will never call it.
|
|
264
|
+
*/
|
|
265
|
+
|
|
266
|
+
/** Filter shape shared by the save and open dialogs. */
|
|
267
|
+
interface DesktopDialogFilter {
|
|
268
|
+
name: string;
|
|
269
|
+
extensions: string[];
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* The filesystem and dialog calls this target needs, shaped so a host can pass
|
|
273
|
+
* its shell's functions straight through with no adapter of its own.
|
|
274
|
+
*
|
|
275
|
+
* With Tauri v2 the whole wiring is the two plugin imports:
|
|
276
|
+
*
|
|
277
|
+
* ```ts
|
|
278
|
+
* import { readFile, writeFile, stat, exists } from '@tauri-apps/plugin-fs';
|
|
279
|
+
* import { save, open } from '@tauri-apps/plugin-dialog';
|
|
280
|
+
* import { useDesktopFiles } from 'selfstore';
|
|
281
|
+
*
|
|
282
|
+
* useDesktopFiles({ readFile, writeFile, stat, exists, save, open });
|
|
283
|
+
* ```
|
|
284
|
+
*
|
|
285
|
+
* Do that once at start-up and every `file` destination in this library runs on
|
|
286
|
+
* a real path. Nothing else in a host changes.
|
|
287
|
+
*/
|
|
288
|
+
interface DesktopFileBridge {
|
|
289
|
+
readFile(path: string): Promise<Uint8Array>;
|
|
290
|
+
writeFile(path: string, data: Uint8Array): Promise<void>;
|
|
291
|
+
/** File metadata. Only `mtime` is read, and a shell that cannot report it
|
|
292
|
+
* may leave it absent: the store then falls back to its own bookkeeping. */
|
|
293
|
+
stat(path: string): Promise<{
|
|
294
|
+
mtime?: Date | number | null;
|
|
295
|
+
} | null>;
|
|
296
|
+
exists(path: string): Promise<boolean>;
|
|
297
|
+
/** Save dialog. Resolves to the chosen path, or null when cancelled. */
|
|
298
|
+
save(options: {
|
|
299
|
+
defaultPath?: string;
|
|
300
|
+
filters?: DesktopDialogFilter[];
|
|
301
|
+
}): Promise<string | null>;
|
|
302
|
+
/** Open dialog. Resolves to the chosen path, or null when cancelled. The
|
|
303
|
+
* array form is accepted so a host can pass its shell's function unchanged. */
|
|
304
|
+
open(options: {
|
|
305
|
+
multiple?: boolean;
|
|
306
|
+
filters?: DesktopDialogFilter[];
|
|
307
|
+
}): Promise<string | string[] | null>;
|
|
308
|
+
}
|
|
309
|
+
/**
|
|
310
|
+
* Register the desktop shell's filesystem and dialog calls. Call once at
|
|
311
|
+
* start-up, before opening the store. Passing null unregisters, which is what a
|
|
312
|
+
* test wants between cases.
|
|
313
|
+
*/
|
|
314
|
+
declare function useDesktopFiles(b: DesktopFileBridge | null): void;
|
|
315
|
+
/** True once a shell has been registered: the file destination then writes a
|
|
316
|
+
* real path instead of asking the browser for a handle. */
|
|
317
|
+
declare function hasDesktopFiles(): boolean;
|
|
318
|
+
|
|
319
|
+
export { BACKUP_EXTENSION, BACKUP_MIME, BackupBuilder, BackupDraft, type CacheUnlock, type ConnectOutcome, type DesktopDialogFilter, type DesktopFileBridge, DriveAuth, EncryptedBackupBuilder, Header, LocalStore, LocalStoreState, PasswordPolicy, RestoreBuilder, S3Config, type SimpleOptions, type SimpleRecord, type SimpleStore, Snapshot, StatusDescriptor, StoreError, SyncConfig, WebdavConfig, backup, hasDesktopFiles, restore, selfstore, useDesktopFiles };
|
package/dist/index.js
CHANGED
|
@@ -3,7 +3,8 @@ export { gisDriveAuth } from './chunk-ZV2S3I76.js';
|
|
|
3
3
|
import { saveToDisk } from './chunk-53WWPEML.js';
|
|
4
4
|
export { pickFromDisk, saveToDisk } from './chunk-53WWPEML.js';
|
|
5
5
|
import './chunk-SKWHKM25.js';
|
|
6
|
-
import { fromSession, fromSession2, fromSession3, connect4, connect3, isSupported, connect2, connect } from './chunk-
|
|
6
|
+
import { fromSession, fromSession2, fromSession3, connect4, connect3, isSupported, connect2, connect } from './chunk-XBPJRUA3.js';
|
|
7
|
+
export { hasDesktopFiles, useDesktopFiles } from './chunk-XBPJRUA3.js';
|
|
7
8
|
import { restore } from './chunk-ZDHTKFAL.js';
|
|
8
9
|
export { BACKUP_EXTENSION, BACKUP_MIME, backup, changePassword, checkPasswordPolicy, restore } from './chunk-ZDHTKFAL.js';
|
|
9
10
|
import './chunk-CT2HTWA7.js';
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/simple/simple.ts"],"names":["fromSession","connect"],"mappings":"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","file":"index.js","sourcesContent":["// The simple store: one call, sensible defaults, and the store owns the data.\n//\n// const store = await selfstore('notes-app');\n// await store.put('todos', { id: 't1', text: 'hello' });\n// store.onChange(render);\n//\n// Convention over configuration: schema version 1, an IndexedDB cache named\n// after the app (in-memory where IndexedDB does not exist, so tests and SSR\n// just work), debounced auto-save, and the browser sync moments wired.\n// Every default has an option; everything deeper lives on store.advanced -\n// the full createLocalStore, same instance.\n//\n// One rule is enforced rather than documented: records need a non-empty\n// string id (the merge identifies records by it), so put() throws a\n// TypeError right away. The advanced pull-model store only logs - it cannot\n// know which collections an app considers mergeable.\n\nimport {\n\tcreateLocalStore,\n\ttype LocalStore,\n\ttype LocalStoreState,\n\ttype StoreError\n} from '../persistence/store';\nimport {\n\tindexedDbCache,\n\tmemoryCache,\n\tisLockableCache,\n\ttype KV,\n\ttype LocalCache\n} from '../persistence/cache';\nimport type { BackupTarget } from '../persistence/target';\nimport type { StatusDescriptor } from '../persistence/status';\nimport type { SyncConfig } from '../sync';\nimport type { Snapshot, SnapshotFile } from '../selfstore/types';\nimport { SelfstoreError } from '../selfstore/errors';\nimport { restore, type PasswordPolicy } from '../selfstore';\nimport { saveToDisk } from '../selfstore/targets/local';\nimport {\n\tconnect as driveConnect,\n\tfromSession as driveFromSession,\n\ttype DriveAuth\n} from '../persistence/targets/drive';\nimport {\n\tconnect as fileConnect,\n\tfromSession as fileFromSession,\n\tisSupported as fileIsSupported\n} from '../persistence/targets/file';\nimport {\n\tconnect as webdavConnect,\n\tfromSession as webdavFromSession,\n\ttype WebdavConfig\n} from '../persistence/targets/webdav';\nimport { connect as s3Connect, type S3Config } from '../persistence/targets/s3';\n\n/** A record as the simple store sees it: plain JSON with a string id (the id\n * field name can be remapped per collection via `sync.ids`). */\nexport type SimpleRecord = Record<string, unknown>;\n\n/** Unlock callback for the ultra-sensitive cacheLock mode: return the session\n * secret - a password (Argon2id-derived) or an app-provided key (e.g. a passkey\n * PRF result). Called at boot; called again with { failed: true } after a wrong\n * secret so the app can re-prompt. Throw to abort the boot (user cancelled). */\nexport type CacheUnlock = (attempt: { failed: boolean }) => Promise<string | CryptoKey>;\n\n/** Options - every one of them optional, the defaults carry a real app. */\nexport interface SimpleOptions {\n\t/** Your DATA schema version (default 1). Bump it together with `migrate`. */\n\tschema?: number;\n\t/** Upgrade a snapshot written by an older schema version. */\n\tmigrate?: (fromVersion: number, snap: Snapshot) => Snapshot;\n\t/** Per-collection merge tuning (id field mapping, strategies). The default -\n\t * records keyed by `id`, later edit wins, deletes propagate - fits most apps. */\n\tsync?: SyncConfig;\n\t/** Google Drive auth, when this app uses Drive: providing it here lets a\n\t * connected Drive backup restore itself on the next start. */\n\tdrive?: DriveAuth;\n\t/** Where the working copy lives (default: IndexedDB named after the app;\n\t * in-memory when IndexedDB does not exist, e.g. tests or SSR). */\n\tcache?: LocalCache;\n\t/** Auto-save debounce in ms. */\n\tdebounceMs?: number;\n\t/** Cross-tab coordination (default on in browsers). */\n\tmultiTab?: boolean;\n\t/** Refuse to ever write or export a plaintext backup: connecting a\n\t * destination then demands a password (or a group). Off by default. */\n\trequireEncryption?: boolean;\n\t/** Reject a backup password weaker than this policy (length, character\n\t * classes) at protect()/addKey time. Preview the rules with\n\t * checkPasswordPolicy for a live UI hint. Omit for no policy. */\n\tpasswordPolicy?: PasswordPolicy;\n\t/** Ultra-sensitive mode: seal the local cache under a key derived from a\n\t * secret held in memory (this callback), never on disk - so a copy of the\n\t * browser profile cannot read it. One unlock per session; branch it on the\n\t * app's existing login to leave the UX unchanged. Browser (IndexedDB) only. */\n\tcacheLock?: CacheUnlock;\n\t/** Wire tab focus / network return / interval / tab hide automatically\n\t * (default true in browsers). Pass false to drive syncing yourself. */\n\tautoSync?: boolean;\n}\n\n/** What connecting a destination did. 'merged': it already held a backup and\n * both sides were folded together. 'started': it was empty, this device's data\n * is now its content. 'manual': non-Chromium file fallback - offer\n * downloadBackup() instead. 'cancelled': the user closed the picker/consent. */\nexport type ConnectOutcome = 'merged' | 'started' | 'manual' | 'cancelled';\n\nexport interface SimpleStore<\n\tS extends Record<string, SimpleRecord> = Record<string, SimpleRecord>\n> {\n\t// --- Your data ---------------------------------------------------------\n\t/** Every record of a collection (treat as read-only; write via put/remove). */\n\tall<K extends keyof S & string>(collection: K): readonly S[K][];\n\t/** One record by id, or undefined. */\n\tget<K extends keyof S & string>(collection: K, id: string): S[K] | undefined;\n\t/** Insert or replace one record (auto-saves, debounced). Throws a TypeError\n\t * when the record has no non-empty string id. */\n\tput<K extends keyof S & string>(collection: K, record: S[K]): Promise<void>;\n\t/** Insert or replace many records in one save. */\n\tputAll<K extends keyof S & string>(collection: K, records: S[K][]): Promise<void>;\n\t/** Delete one record by id (propagates to other devices). Unknown id: no-op. */\n\tremove<K extends keyof S & string>(collection: K, id: string): Promise<void>;\n\t/** Empty a collection (every removal propagates). */\n\tclear<K extends keyof S & string>(collection: K): Promise<void>;\n\t/** Called after any data change: your writes, another tab, another device,\n\t * a restore. Returns an unsubscribe. */\n\tonChange(fn: () => void): () => void;\n\n\t// --- Where it lives ----------------------------------------------------\n\t/** Connect Google Drive as the durable home. An existing backup there is\n\t * MERGED with this device (multi-device); an encrypted one needs its\n\t * password up front (PASSWORD_REQUIRED is thrown before anything changes). */\n\tconnectDrive(auth: DriveAuth, opts?: { password?: string }): Promise<ConnectOutcome>;\n\t/** Connect a disk file (File System Access; falls back to manual download). */\n\tconnectFile(opts?: { password?: string }): Promise<ConnectOutcome>;\n\t/** Connect a WebDAV server (Nextcloud, ownCloud, your own). */\n\tconnectWebdav(config: WebdavConfig, opts?: { password?: string }): Promise<ConnectOutcome>;\n\t/** Connect an S3-compatible bucket you control (Amazon S3, R2, B2, MinIO). */\n\tconnectS3(config: S3Config, opts?: { password?: string }): Promise<ConnectOutcome>;\n\t/** Connect any custom BackupTarget with the same merge semantics. */\n\tconnectTarget(target: BackupTarget, opts?: { password?: string }): Promise<ConnectOutcome>;\n\t/** Back to device-only (the destination keeps its last backup). */\n\tdisconnect(): Promise<void>;\n\n\t// --- Backup copy (replica) ---------------------------------------------\n\t/** Also write the same encrypted backup to this second destination on\n\t * every save. A broken copy never gates the store (state.replicas). */\n\taddReplica(target: BackupTarget, opts?: { id?: string }): string;\n\t/** Stop writing the copy (the destination keeps its last backup). */\n\tremoveReplica(id: string): void;\n\n\t// --- Protection & the two gate gestures --------------------------------\n\t/** Encrypt the durable backup end to end with this password (reversible). */\n\tprotect(password: string): Promise<void>;\n\t/** Remove the backup password. */\n\tunprotect(): Promise<void>;\n\t/** status.action === 'unlock': supply the password to resume. */\n\tunlock(password: string): Promise<boolean>;\n\t/** status.action === 'reconnect': re-run the destination's auth gesture. */\n\treconnect(): Promise<boolean>;\n\n\t// --- Portable backups ---------------------------------------------------\n\t/** The portable backup file (a real ZIP; encrypted when protect() is on). */\n\texportBackup(): Promise<Blob>;\n\t/** Download the backup (also resolves the 'manual' file mode's pending flag).\n\t * False when the user closed the save dialog: nothing was written, the\n\t * pending flag stands, and the app must not record a backup. */\n\tdownloadBackup(filename?: string): Promise<boolean>;\n\t/** Load a backup file into this store, replacing the local data (removals\n\t * propagate like edits). Throws PASSWORD_REQUIRED / DECRYPT_FAILED. */\n\timportBackup(file: Blob | Uint8Array, opts?: { password?: string }): Promise<void>;\n\n\t// --- Observability ------------------------------------------------------\n\t/** Headless status: { state, severity, action, labelKey } - map labelKey to\n\t * your own copy. */\n\treadonly status: StatusDescriptor;\n\t/** The last problem: { code, labelKey, message } - show labelKey, log message. */\n\treadonly error: StoreError | null;\n\t/** The full underlying state (advanced reads: journal, peers, mode...). */\n\treadonly state: LocalStoreState;\n\t/** Fires on any state change (status flips included) - for framework bindings. */\n\tsubscribe(fn: () => void): () => void;\n\n\t// --- Lifecycle ----------------------------------------------------------\n\t/** Save now (called for you on tab hide when autoSync is on). */\n\tflush(): Promise<void>;\n\t/** Converge with the destination now (a user gesture like pull-to-refresh). */\n\tsync(): Promise<void>;\n\t/** Drop timers and listeners (tests, SPA teardown). */\n\tdispose(): void;\n\n\t/** The escape hatch: the full advanced store this simple one is built on -\n\t * peers, groups, custom strategies, everything (see selfstore/advanced). */\n\treadonly advanced: LocalStore;\n\n\t/** The attachment point for 'selfstore/flows': the engine plus the cache KV\n\t * and backup file name a flow needs to build destination targets. Apps on\n\t * the advanced store hand a flow the same three by themselves. */\n\treadonly flowHost: { engine: LocalStore; kv: KV; backupName: string };\n}\n\n/** The id field a collection's records are keyed by: `sync.ids` may remap it;\n * a DOTTED path (nested id) is an advanced setup the simple check skips. */\nfunction idFieldOf(sync: SyncConfig | undefined, collection: string): string | null {\n\tconst mapped = sync?.ids?.[collection];\n\tif (mapped == null) return 'id';\n\treturn mapped.includes('.') ? null : mapped;\n}\n\nfunction requireStringId(record: SimpleRecord, field: string | null, collection: string): string {\n\tif (field === null) return ''; // nested-path ids: validated by the engine's own rules\n\tconst id = record[field];\n\tif (typeof id !== 'string' || id.length === 0) {\n\t\tthrow new TypeError(\n\t\t\t`selfstore: a record in \"${collection}\" needs a non-empty STRING \"${field}\" ` +\n\t\t\t\t`(got ${JSON.stringify(id)}). String ids are what the multi-device merge keys on; ` +\n\t\t\t\t`map another field with { sync: { ids: { ${collection}: 'yourField' } } }.`\n\t\t);\n\t}\n\treturn id;\n}\n\n/** True when a browser IndexedDB is available (SSR and plain Node lack it). */\nfunction hasIndexedDb(): boolean {\n\treturn typeof indexedDB !== 'undefined';\n}\n\n/**\n * Open (or create) the app's local store. Awaits the initial load, so the\n * returned store is ready: data readable, destination restored, first converge\n * done. See SimpleOptions for the defaults.\n */\nexport async function selfstore<\n\tS extends Record<string, SimpleRecord> = Record<string, SimpleRecord>\n>(app: string, options: SimpleOptions = {}): Promise<SimpleStore<S>> {\n\tif (typeof app !== 'string' || app.length === 0) {\n\t\tthrow new TypeError('selfstore: pass your app name, e.g. selfstore(\"my-app\").');\n\t}\n\n\t// The one copy of the data. The engine pulls it on save (gather) and pushes\n\t// into it on restores/folds (apply); the facade's mutators edit it in place\n\t// copy-on-write and schedule a save.\n\tlet collections: Record<string, SimpleRecord[]> = {};\n\t// Binary files ride along untouched (a backup that carries them keeps them);\n\t// managing them is an advanced-store concern.\n\tlet files: SnapshotFile[] = [];\n\n\tconst dataSubs = new Set<() => void>();\n\tconst emitData = (): void => dataSubs.forEach((fn) => fn());\n\n\tconst cache =\n\t\toptions.cache ??\n\t\t(hasIndexedDb()\n\t\t\t? indexedDbCache(app, options.cacheLock ? { lock: true } : undefined)\n\t\t\t: memoryCache());\n\tconst backupName = `${app}.zip`;\n\t// connectDrive() remembers the auth so a later restoreTarget can use it even\n\t// when options.drive was not provided up front (same session only).\n\tlet driveAuth: DriveAuth | null = options.drive ?? null;\n\n\t// Ultra-sensitive: unlock the cache before the store hydrates from it. One\n\t// prompt per session, driven by the app (branch it on an existing login to\n\t// keep the UX unchanged). A wrong secret re-prompts; throw to abort.\n\tif (options.cacheLock && isLockableCache(cache)) {\n\t\tlet failed = false;\n\t\twhile (!(await cache.unlock(await options.cacheLock({ failed })))) failed = true;\n\t}\n\n\tconst store = createLocalStore({\n\t\tapp,\n\t\tschemaVersion: options.schema ?? 1,\n\t\tgather: (): Snapshot => structuredClone({ collections, files }),\n\t\tapply: (snap: Snapshot): void => {\n\t\t\tcollections = (snap.collections ?? {}) as Record<string, SimpleRecord[]>;\n\t\t\tfiles = snap.files ?? [];\n\t\t\temitData();\n\t\t},\n\t\tmigrate: options.migrate,\n\t\tsync: options.sync,\n\t\tcache,\n\t\tdebounceMs: options.debounceMs,\n\t\tmultiTab: options.multiTab,\n\t\trequireEncryption: options.requireEncryption,\n\t\tpasswordPolicy: options.passwordPolicy,\n\t\trestoreTarget: async (kind) => {\n\t\t\tif (kind === 'drive' && driveAuth) {\n\t\t\t\treturn driveFromSession({ auth: driveAuth, kv: cache.kv, fileName: backupName });\n\t\t\t}\n\t\t\tif (kind === 'file') return fileFromSession({ kv: cache.kv });\n\t\t\tif (kind === 'webdav') return webdavFromSession({ kv: cache.kv });\n\t\t\treturn null;\n\t\t}\n\t});\n\n\tawait store.init();\n\n\t// Auto-wired sync moments (Spring-Boot-style autoconfiguration): tab focus,\n\t// network return, a slow interval, and save-on-hide. Opt out with\n\t// { autoSync: false } to drive these yourself.\n\tconst teardown: (() => void)[] = [];\n\tif (\n\t\toptions.autoSync !== false &&\n\t\ttypeof document !== 'undefined' &&\n\t\ttypeof window !== 'undefined'\n\t) {\n\t\tconst onVisibility = (): void => {\n\t\t\tif (document.visibilityState === 'hidden') void store.flush();\n\t\t\telse void store.syncIfStale('focus');\n\t\t};\n\t\tconst onOnline = (): void => void store.syncIfStale('online');\n\t\tconst onPageHide = (): void => void store.flush();\n\t\tconst interval = setInterval(() => {\n\t\t\tif (document.visibilityState === 'visible') void store.syncIfStale('interval');\n\t\t}, 5 * 60_000);\n\t\tdocument.addEventListener('visibilitychange', onVisibility);\n\t\twindow.addEventListener('online', onOnline);\n\t\twindow.addEventListener('pagehide', onPageHide);\n\t\tteardown.push(() => {\n\t\t\tclearInterval(interval);\n\t\t\tdocument.removeEventListener('visibilitychange', onVisibility);\n\t\t\twindow.removeEventListener('online', onOnline);\n\t\t\twindow.removeEventListener('pagehide', onPageHide);\n\t\t});\n\t}\n\n\tfunction mutate(collection: string, next: SimpleRecord[]): Promise<void> {\n\t\tcollections = { ...collections, [collection]: next };\n\t\temitData();\n\t\tstore.schedule();\n\t\treturn Promise.resolve();\n\t}\n\n\tasync function connectTarget(\n\t\ttarget: BackupTarget,\n\t\topts: { password?: string } = {}\n\t): Promise<ConnectOutcome> {\n\t\tconst info = await store.inspectTarget(target);\n\t\tif (info.hasBackup) {\n\t\t\t// Fail before attaching: an encrypted backup without its password would\n\t\t\t// otherwise attach locked, which is a support case, not an outcome.\n\t\t\tif (info.encrypted && !opts.password) {\n\t\t\t\tthrow new SelfstoreError(\n\t\t\t\t\t'PASSWORD_REQUIRED',\n\t\t\t\t\t'This destination holds an encrypted backup: pass its password to connect.'\n\t\t\t\t);\n\t\t\t}\n\t\t\tawait store.attachTarget(target, { password: opts.password ?? null, strategy: 'merge' });\n\t\t\treturn 'merged';\n\t\t}\n\t\tawait store.attachTarget(target, {\n\t\t\tpassword: opts.password ?? null,\n\t\t\tstrategy: 'replace-remote'\n\t\t});\n\t\treturn 'started';\n\t}\n\n\tconst simple: SimpleStore<S> = {\n\t\tall<K extends keyof S & string>(collection: K): readonly S[K][] {\n\t\t\treturn (collections[collection] ?? []) as S[K][];\n\t\t},\n\t\tget<K extends keyof S & string>(collection: K, id: string): S[K] | undefined {\n\t\t\tconst field = idFieldOf(options.sync, collection) ?? 'id';\n\t\t\treturn (collections[collection] ?? []).find((r) => r[field] === id) as S[K] | undefined;\n\t\t},\n\t\tput<K extends keyof S & string>(collection: K, record: S[K]): Promise<void> {\n\t\t\tconst field = idFieldOf(options.sync, collection);\n\t\t\tconst id = requireStringId(record, field, collection);\n\t\t\tconst rows = collections[collection] ?? [];\n\t\t\tconst at = field === null ? -1 : rows.findIndex((r) => r[field] === id);\n\t\t\treturn mutate(\n\t\t\t\tcollection,\n\t\t\t\tat === -1 ? [...rows, record] : rows.map((r, i) => (i === at ? record : r))\n\t\t\t);\n\t\t},\n\t\tputAll<K extends keyof S & string>(collection: K, records: S[K][]): Promise<void> {\n\t\t\tconst field = idFieldOf(options.sync, collection);\n\t\t\tconst byId = new Map<unknown, number>();\n\t\t\tfor (const r of records) requireStringId(r, field, collection);\n\t\t\tconst rows = [...(collections[collection] ?? [])];\n\t\t\tif (field !== null) {\n\t\t\t\trows.forEach((r, i) => byId.set(r[field], i));\n\t\t\t\tfor (const r of records) {\n\t\t\t\t\tconst at = byId.get(r[field]);\n\t\t\t\t\tif (typeof at === 'number') rows[at] = r;\n\t\t\t\t\telse rows.push(r);\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\trows.push(...records);\n\t\t\t}\n\t\t\treturn mutate(collection, rows);\n\t\t},\n\t\tremove<K extends keyof S & string>(collection: K, id: string): Promise<void> {\n\t\t\tconst field = idFieldOf(options.sync, collection) ?? 'id';\n\t\t\tconst rows = collections[collection] ?? [];\n\t\t\tconst next = rows.filter((r) => r[field] !== id);\n\t\t\treturn next.length === rows.length ? Promise.resolve() : mutate(collection, next);\n\t\t},\n\t\tclear<K extends keyof S & string>(collection: K): Promise<void> {\n\t\t\tif ((collections[collection] ?? []).length === 0) return Promise.resolve();\n\t\t\treturn mutate(collection, []);\n\t\t},\n\t\tonChange(fn: () => void): () => void {\n\t\t\tdataSubs.add(fn);\n\t\t\treturn () => dataSubs.delete(fn);\n\t\t},\n\n\t\tasync connectDrive(auth: DriveAuth, opts?: { password?: string }): Promise<ConnectOutcome> {\n\t\t\tdriveAuth = auth;\n\t\t\tconst target = await driveConnect({ auth, kv: cache.kv, fileName: backupName });\n\t\t\tif (!target) return 'cancelled';\n\t\t\treturn connectTarget(target, opts);\n\t\t},\n\t\tasync connectFile(opts?: { password?: string }): Promise<ConnectOutcome> {\n\t\t\t// Asked twice on purpose: the first call answers from what the browser\n\t\t\t// exposes, the second from what it did when actually asked. A browser\n\t\t\t// that ships the picker and then refuses it only tells us so here.\n\t\t\tif (fileIsSupported()) {\n\t\t\t\tconst target = await fileConnect({ kv: cache.kv, fileName: backupName });\n\t\t\t\tif (target) return connectTarget(target, opts);\n\t\t\t\tif (fileIsSupported()) return 'cancelled';\n\t\t\t}\n\t\t\tawait store.setManualFile();\n\t\t\treturn 'manual';\n\t\t},\n\t\tasync connectWebdav(\n\t\t\tconfig: WebdavConfig,\n\t\t\topts?: { password?: string }\n\t\t): Promise<ConnectOutcome> {\n\t\t\tconst target = await webdavConnect({ kv: cache.kv, config });\n\t\t\tif (!target) {\n\t\t\t\tthrow new SelfstoreError(\n\t\t\t\t\t'TARGET_UNAVAILABLE',\n\t\t\t\t\t'The WebDAV server did not answer (URL, credentials or CORS).'\n\t\t\t\t);\n\t\t\t}\n\t\t\treturn connectTarget(target, opts);\n\t\t},\n\t\tasync connectS3(config: S3Config, opts?: { password?: string }): Promise<ConnectOutcome> {\n\t\t\tconst target = await s3Connect({ kv: cache.kv, config });\n\t\t\tif (!target) {\n\t\t\t\tthrow new SelfstoreError(\n\t\t\t\t\t'TARGET_UNAVAILABLE',\n\t\t\t\t\t'The S3 endpoint did not answer (endpoint, credentials or CORS).'\n\t\t\t\t);\n\t\t\t}\n\t\t\treturn connectTarget(target, opts);\n\t\t},\n\t\tconnectTarget,\n\t\tdisconnect: () => store.detachTarget(),\n\t\taddReplica: (target, opts) => store.attachReplica(target, opts),\n\t\tremoveReplica: (id) => store.detachReplica(id),\n\n\t\tprotect: (password: string) => store.setEncryption(password),\n\t\tunprotect: () => store.setEncryption(null),\n\t\tunlock: (password: string) => store.unlock(password),\n\t\treconnect: () => store.reconnect(),\n\n\t\texportBackup: () => store.exportBlob(),\n\t\tasync downloadBackup(filename?: string): Promise<boolean> {\n\t\t\tconst written = await saveToDisk(await store.exportBlob(), filename ?? backupName);\n\t\t\t// Clearing the pending flag on a cancelled dialog would drop the very\n\t\t\t// nudge that asks for the file the user still does not have.\n\t\t\tif (written) store.markDownloaded();\n\t\t\treturn written;\n\t\t},\n\t\tasync importBackup(file: Blob | Uint8Array, opts?: { password?: string }): Promise<void> {\n\t\t\t// The fluent read() strips reserved bookkeeping collections; removals\n\t\t\t// then propagate because the next save diffs against the sync meta.\n\t\t\tconst reader = restore(file);\n\t\t\tconst snap = opts?.password\n\t\t\t\t? await reader.withPassword(opts.password).read()\n\t\t\t\t: await reader.read();\n\t\t\tcollections = (snap.collections ?? {}) as Record<string, SimpleRecord[]>;\n\t\t\tfiles = snap.files ?? [];\n\t\t\temitData();\n\t\t\tstore.schedule();\n\t\t},\n\n\t\tget status(): StatusDescriptor {\n\t\t\treturn store.state.status;\n\t\t},\n\t\tget error(): StoreError | null {\n\t\t\treturn store.state.lastError;\n\t\t},\n\t\tget state(): LocalStoreState {\n\t\t\treturn store.state;\n\t\t},\n\t\tsubscribe: (fn: () => void) => store.subscribe(fn),\n\n\t\tflush: () => store.flush(),\n\t\tasync sync(): Promise<void> {\n\t\t\tawait store.syncNow();\n\t\t},\n\t\tdispose(): void {\n\t\t\tteardown.forEach((fn) => fn());\n\t\t\tteardown.length = 0;\n\t\t\tstore.dispose();\n\t\t},\n\n\t\tadvanced: store,\n\n\t\tflowHost: { engine: store, kv: cache.kv, backupName }\n\t};\n\n\treturn simple;\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/simple/simple.ts"],"names":["fromSession","connect"],"mappings":"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","file":"index.js","sourcesContent":["// The simple store: one call, sensible defaults, and the store owns the data.\n//\n// const store = await selfstore('notes-app');\n// await store.put('todos', { id: 't1', text: 'hello' });\n// store.onChange(render);\n//\n// Convention over configuration: schema version 1, an IndexedDB cache named\n// after the app (in-memory where IndexedDB does not exist, so tests and SSR\n// just work), debounced auto-save, and the browser sync moments wired.\n// Every default has an option; everything deeper lives on store.advanced -\n// the full createLocalStore, same instance.\n//\n// One rule is enforced rather than documented: records need a non-empty\n// string id (the merge identifies records by it), so put() throws a\n// TypeError right away. The advanced pull-model store only logs - it cannot\n// know which collections an app considers mergeable.\n\nimport {\n\tcreateLocalStore,\n\ttype LocalStore,\n\ttype LocalStoreState,\n\ttype StoreError\n} from '../persistence/store';\nimport {\n\tindexedDbCache,\n\tmemoryCache,\n\tisLockableCache,\n\ttype KV,\n\ttype LocalCache\n} from '../persistence/cache';\nimport type { BackupTarget } from '../persistence/target';\nimport type { StatusDescriptor } from '../persistence/status';\nimport type { SyncConfig } from '../sync';\nimport type { Snapshot, SnapshotFile } from '../selfstore/types';\nimport { SelfstoreError } from '../selfstore/errors';\nimport { restore, type PasswordPolicy } from '../selfstore';\nimport { saveToDisk } from '../selfstore/targets/local';\nimport {\n\tconnect as driveConnect,\n\tfromSession as driveFromSession,\n\ttype DriveAuth\n} from '../persistence/targets/drive';\nimport {\n\tconnect as fileConnect,\n\tfromSession as fileFromSession,\n\tisSupported as fileIsSupported\n} from '../persistence/targets/file';\nimport {\n\tconnect as webdavConnect,\n\tfromSession as webdavFromSession,\n\ttype WebdavConfig\n} from '../persistence/targets/webdav';\nimport { connect as s3Connect, type S3Config } from '../persistence/targets/s3';\n\n/** A record as the simple store sees it: plain JSON with a string id (the id\n * field name can be remapped per collection via `sync.ids`). */\nexport type SimpleRecord = Record<string, unknown>;\n\n/** Unlock callback for the ultra-sensitive cacheLock mode: return the session\n * secret - a password (Argon2id-derived) or an app-provided key (e.g. a passkey\n * PRF result). Called at boot; called again with { failed: true } after a wrong\n * secret so the app can re-prompt. Throw to abort the boot (user cancelled). */\nexport type CacheUnlock = (attempt: { failed: boolean }) => Promise<string | CryptoKey>;\n\n/** Options - every one of them optional, the defaults carry a real app. */\nexport interface SimpleOptions {\n\t/** Your DATA schema version (default 1). Bump it together with `migrate`. */\n\tschema?: number;\n\t/** Upgrade a snapshot written by an older schema version. */\n\tmigrate?: (fromVersion: number, snap: Snapshot) => Snapshot;\n\t/** Per-collection merge tuning (id field mapping, strategies). The default -\n\t * records keyed by `id`, later edit wins, deletes propagate - fits most apps. */\n\tsync?: SyncConfig;\n\t/** Google Drive auth, when this app uses Drive: providing it here lets a\n\t * connected Drive backup restore itself on the next start. */\n\tdrive?: DriveAuth;\n\t/** Where the working copy lives (default: IndexedDB named after the app;\n\t * in-memory when IndexedDB does not exist, e.g. tests or SSR). */\n\tcache?: LocalCache;\n\t/** Auto-save debounce in ms. */\n\tdebounceMs?: number;\n\t/** Cross-tab coordination (default on in browsers). */\n\tmultiTab?: boolean;\n\t/** Refuse to ever write or export a plaintext backup: connecting a\n\t * destination then demands a password (or a group). Off by default. */\n\trequireEncryption?: boolean;\n\t/** Reject a backup password weaker than this policy (length, character\n\t * classes) at protect()/addKey time. Preview the rules with\n\t * checkPasswordPolicy for a live UI hint. Omit for no policy. */\n\tpasswordPolicy?: PasswordPolicy;\n\t/** Ultra-sensitive mode: seal the local cache under a key derived from a\n\t * secret held in memory (this callback), never on disk - so a copy of the\n\t * browser profile cannot read it. One unlock per session; branch it on the\n\t * app's existing login to leave the UX unchanged. Browser (IndexedDB) only. */\n\tcacheLock?: CacheUnlock;\n\t/** Wire tab focus / network return / interval / tab hide automatically\n\t * (default true in browsers). Pass false to drive syncing yourself. */\n\tautoSync?: boolean;\n}\n\n/** What connecting a destination did. 'merged': it already held a backup and\n * both sides were folded together. 'started': it was empty, this device's data\n * is now its content. 'manual': non-Chromium file fallback - offer\n * downloadBackup() instead. 'cancelled': the user closed the picker/consent. */\nexport type ConnectOutcome = 'merged' | 'started' | 'manual' | 'cancelled';\n\nexport interface SimpleStore<\n\tS extends Record<string, SimpleRecord> = Record<string, SimpleRecord>\n> {\n\t// --- Your data ---------------------------------------------------------\n\t/** Every record of a collection (treat as read-only; write via put/remove). */\n\tall<K extends keyof S & string>(collection: K): readonly S[K][];\n\t/** One record by id, or undefined. */\n\tget<K extends keyof S & string>(collection: K, id: string): S[K] | undefined;\n\t/** Insert or replace one record (auto-saves, debounced). Throws a TypeError\n\t * when the record has no non-empty string id. */\n\tput<K extends keyof S & string>(collection: K, record: S[K]): Promise<void>;\n\t/** Insert or replace many records in one save. */\n\tputAll<K extends keyof S & string>(collection: K, records: S[K][]): Promise<void>;\n\t/** Delete one record by id (propagates to other devices). Unknown id: no-op. */\n\tremove<K extends keyof S & string>(collection: K, id: string): Promise<void>;\n\t/** Empty a collection (every removal propagates). */\n\tclear<K extends keyof S & string>(collection: K): Promise<void>;\n\t/** Called after any data change: your writes, another tab, another device,\n\t * a restore. Returns an unsubscribe. */\n\tonChange(fn: () => void): () => void;\n\n\t// --- Where it lives ----------------------------------------------------\n\t/** Connect Google Drive as the durable home. An existing backup there is\n\t * MERGED with this device (multi-device); an encrypted one needs its\n\t * password up front (PASSWORD_REQUIRED is thrown before anything changes). */\n\tconnectDrive(auth: DriveAuth, opts?: { password?: string }): Promise<ConnectOutcome>;\n\t/** Connect a disk file (File System Access; falls back to manual download). */\n\tconnectFile(opts?: { password?: string }): Promise<ConnectOutcome>;\n\t/** Connect a WebDAV server (Nextcloud, ownCloud, your own). */\n\tconnectWebdav(config: WebdavConfig, opts?: { password?: string }): Promise<ConnectOutcome>;\n\t/** Connect an S3-compatible bucket you control (Amazon S3, R2, B2, MinIO). */\n\tconnectS3(config: S3Config, opts?: { password?: string }): Promise<ConnectOutcome>;\n\t/** Connect any custom BackupTarget with the same merge semantics. */\n\tconnectTarget(target: BackupTarget, opts?: { password?: string }): Promise<ConnectOutcome>;\n\t/** Back to device-only (the destination keeps its last backup). */\n\tdisconnect(): Promise<void>;\n\n\t// --- Backup copy (replica) ---------------------------------------------\n\t/** Also write the same encrypted backup to this second destination on\n\t * every save. A broken copy never gates the store (state.replicas). */\n\taddReplica(target: BackupTarget, opts?: { id?: string }): string;\n\t/** Stop writing the copy (the destination keeps its last backup). */\n\tremoveReplica(id: string): void;\n\n\t// --- Protection & the two gate gestures --------------------------------\n\t/** Encrypt the durable backup end to end with this password (reversible). */\n\tprotect(password: string): Promise<void>;\n\t/** Remove the backup password. */\n\tunprotect(): Promise<void>;\n\t/** status.action === 'unlock': supply the password to resume. */\n\tunlock(password: string): Promise<boolean>;\n\t/** status.action === 'reconnect': re-run the destination's auth gesture. */\n\treconnect(): Promise<boolean>;\n\n\t// --- Portable backups ---------------------------------------------------\n\t/** The portable backup file (a real ZIP; encrypted when protect() is on). */\n\texportBackup(): Promise<Blob>;\n\t/** Download the backup (also resolves the 'manual' file mode's pending flag).\n\t * False when the user closed the save dialog: nothing was written, the\n\t * pending flag stands, and the app must not record a backup. */\n\tdownloadBackup(filename?: string): Promise<boolean>;\n\t/** Load a backup file into this store, replacing the local data (removals\n\t * propagate like edits). Throws PASSWORD_REQUIRED / DECRYPT_FAILED. */\n\timportBackup(file: Blob | Uint8Array, opts?: { password?: string }): Promise<void>;\n\n\t// --- Observability ------------------------------------------------------\n\t/** Headless status: { state, severity, action, labelKey } - map labelKey to\n\t * your own copy. */\n\treadonly status: StatusDescriptor;\n\t/** The last problem: { code, labelKey, message } - show labelKey, log message. */\n\treadonly error: StoreError | null;\n\t/** The full underlying state (advanced reads: journal, peers, mode...). */\n\treadonly state: LocalStoreState;\n\t/** Fires on any state change (status flips included) - for framework bindings. */\n\tsubscribe(fn: () => void): () => void;\n\n\t// --- Lifecycle ----------------------------------------------------------\n\t/** Save now (called for you on tab hide when autoSync is on). */\n\tflush(): Promise<void>;\n\t/** Converge with the destination now (a user gesture like pull-to-refresh). */\n\tsync(): Promise<void>;\n\t/** Drop timers and listeners (tests, SPA teardown). */\n\tdispose(): void;\n\n\t/** The escape hatch: the full advanced store this simple one is built on -\n\t * peers, groups, custom strategies, everything (see selfstore/advanced). */\n\treadonly advanced: LocalStore;\n\n\t/** The attachment point for 'selfstore/flows': the engine plus the cache KV\n\t * and backup file name a flow needs to build destination targets. Apps on\n\t * the advanced store hand a flow the same three by themselves. */\n\treadonly flowHost: { engine: LocalStore; kv: KV; backupName: string };\n}\n\n/** The id field a collection's records are keyed by: `sync.ids` may remap it;\n * a DOTTED path (nested id) is an advanced setup the simple check skips. */\nfunction idFieldOf(sync: SyncConfig | undefined, collection: string): string | null {\n\tconst mapped = sync?.ids?.[collection];\n\tif (mapped == null) return 'id';\n\treturn mapped.includes('.') ? null : mapped;\n}\n\nfunction requireStringId(record: SimpleRecord, field: string | null, collection: string): string {\n\tif (field === null) return ''; // nested-path ids: validated by the engine's own rules\n\tconst id = record[field];\n\tif (typeof id !== 'string' || id.length === 0) {\n\t\tthrow new TypeError(\n\t\t\t`selfstore: a record in \"${collection}\" needs a non-empty STRING \"${field}\" ` +\n\t\t\t\t`(got ${JSON.stringify(id)}). String ids are what the multi-device merge keys on; ` +\n\t\t\t\t`map another field with { sync: { ids: { ${collection}: 'yourField' } } }.`\n\t\t);\n\t}\n\treturn id;\n}\n\n/** True when a browser IndexedDB is available (SSR and plain Node lack it). */\nfunction hasIndexedDb(): boolean {\n\treturn typeof indexedDB !== 'undefined';\n}\n\n/**\n * Open (or create) the app's local store. Awaits the initial load, so the\n * returned store is ready: data readable, destination restored, first converge\n * done. See SimpleOptions for the defaults.\n */\nexport async function selfstore<\n\tS extends Record<string, SimpleRecord> = Record<string, SimpleRecord>\n>(app: string, options: SimpleOptions = {}): Promise<SimpleStore<S>> {\n\tif (typeof app !== 'string' || app.length === 0) {\n\t\tthrow new TypeError('selfstore: pass your app name, e.g. selfstore(\"my-app\").');\n\t}\n\n\t// The one copy of the data. The engine pulls it on save (gather) and pushes\n\t// into it on restores/folds (apply); the facade's mutators edit it in place\n\t// copy-on-write and schedule a save.\n\tlet collections: Record<string, SimpleRecord[]> = {};\n\t// Binary files ride along untouched (a backup that carries them keeps them);\n\t// managing them is an advanced-store concern.\n\tlet files: SnapshotFile[] = [];\n\n\tconst dataSubs = new Set<() => void>();\n\tconst emitData = (): void => dataSubs.forEach((fn) => fn());\n\n\tconst cache =\n\t\toptions.cache ??\n\t\t(hasIndexedDb()\n\t\t\t? indexedDbCache(app, options.cacheLock ? { lock: true } : undefined)\n\t\t\t: memoryCache());\n\tconst backupName = `${app}.zip`;\n\t// connectDrive() remembers the auth so a later restoreTarget can use it even\n\t// when options.drive was not provided up front (same session only).\n\tlet driveAuth: DriveAuth | null = options.drive ?? null;\n\n\t// Ultra-sensitive: unlock the cache before the store hydrates from it. One\n\t// prompt per session, driven by the app (branch it on an existing login to\n\t// keep the UX unchanged). A wrong secret re-prompts; throw to abort.\n\tif (options.cacheLock && isLockableCache(cache)) {\n\t\tlet failed = false;\n\t\twhile (!(await cache.unlock(await options.cacheLock({ failed })))) failed = true;\n\t}\n\n\tconst store = createLocalStore({\n\t\tapp,\n\t\tschemaVersion: options.schema ?? 1,\n\t\tgather: (): Snapshot => structuredClone({ collections, files }),\n\t\tapply: (snap: Snapshot): void => {\n\t\t\tcollections = (snap.collections ?? {}) as Record<string, SimpleRecord[]>;\n\t\t\tfiles = snap.files ?? [];\n\t\t\temitData();\n\t\t},\n\t\tmigrate: options.migrate,\n\t\tsync: options.sync,\n\t\tcache,\n\t\tdebounceMs: options.debounceMs,\n\t\tmultiTab: options.multiTab,\n\t\trequireEncryption: options.requireEncryption,\n\t\tpasswordPolicy: options.passwordPolicy,\n\t\trestoreTarget: async (kind) => {\n\t\t\tif (kind === 'drive' && driveAuth) {\n\t\t\t\treturn driveFromSession({ auth: driveAuth, kv: cache.kv, fileName: backupName });\n\t\t\t}\n\t\t\tif (kind === 'file') return fileFromSession({ kv: cache.kv });\n\t\t\tif (kind === 'webdav') return webdavFromSession({ kv: cache.kv });\n\t\t\treturn null;\n\t\t}\n\t});\n\n\tawait store.init();\n\n\t// Auto-wired sync moments (Spring-Boot-style autoconfiguration): tab focus,\n\t// network return, a slow interval, and save-on-hide. Opt out with\n\t// { autoSync: false } to drive these yourself.\n\tconst teardown: (() => void)[] = [];\n\tif (\n\t\toptions.autoSync !== false &&\n\t\ttypeof document !== 'undefined' &&\n\t\ttypeof window !== 'undefined'\n\t) {\n\t\tconst onVisibility = (): void => {\n\t\t\tif (document.visibilityState === 'hidden') void store.flush();\n\t\t\telse void store.syncIfStale('focus');\n\t\t};\n\t\tconst onOnline = (): void => void store.syncIfStale('online');\n\t\tconst onPageHide = (): void => void store.flush();\n\t\tconst interval = setInterval(() => {\n\t\t\tif (document.visibilityState === 'visible') void store.syncIfStale('interval');\n\t\t}, 5 * 60_000);\n\t\tdocument.addEventListener('visibilitychange', onVisibility);\n\t\twindow.addEventListener('online', onOnline);\n\t\twindow.addEventListener('pagehide', onPageHide);\n\t\tteardown.push(() => {\n\t\t\tclearInterval(interval);\n\t\t\tdocument.removeEventListener('visibilitychange', onVisibility);\n\t\t\twindow.removeEventListener('online', onOnline);\n\t\t\twindow.removeEventListener('pagehide', onPageHide);\n\t\t});\n\t}\n\n\tfunction mutate(collection: string, next: SimpleRecord[]): Promise<void> {\n\t\tcollections = { ...collections, [collection]: next };\n\t\temitData();\n\t\tstore.schedule();\n\t\treturn Promise.resolve();\n\t}\n\n\tasync function connectTarget(\n\t\ttarget: BackupTarget,\n\t\topts: { password?: string } = {}\n\t): Promise<ConnectOutcome> {\n\t\tconst info = await store.inspectTarget(target);\n\t\tif (info.hasBackup) {\n\t\t\t// Fail before attaching: an encrypted backup without its password would\n\t\t\t// otherwise attach locked, which is a support case, not an outcome.\n\t\t\tif (info.encrypted && !opts.password) {\n\t\t\t\tthrow new SelfstoreError(\n\t\t\t\t\t'PASSWORD_REQUIRED',\n\t\t\t\t\t'This destination holds an encrypted backup: pass its password to connect.'\n\t\t\t\t);\n\t\t\t}\n\t\t\tawait store.attachTarget(target, { password: opts.password ?? null, strategy: 'merge' });\n\t\t\treturn 'merged';\n\t\t}\n\t\tawait store.attachTarget(target, {\n\t\t\tpassword: opts.password ?? null,\n\t\t\tstrategy: 'replace-remote'\n\t\t});\n\t\treturn 'started';\n\t}\n\n\tconst simple: SimpleStore<S> = {\n\t\tall<K extends keyof S & string>(collection: K): readonly S[K][] {\n\t\t\treturn (collections[collection] ?? []) as S[K][];\n\t\t},\n\t\tget<K extends keyof S & string>(collection: K, id: string): S[K] | undefined {\n\t\t\tconst field = idFieldOf(options.sync, collection) ?? 'id';\n\t\t\treturn (collections[collection] ?? []).find((r) => r[field] === id) as S[K] | undefined;\n\t\t},\n\t\tput<K extends keyof S & string>(collection: K, record: S[K]): Promise<void> {\n\t\t\tconst field = idFieldOf(options.sync, collection);\n\t\t\tconst id = requireStringId(record, field, collection);\n\t\t\tconst rows = collections[collection] ?? [];\n\t\t\tconst at = field === null ? -1 : rows.findIndex((r) => r[field] === id);\n\t\t\treturn mutate(\n\t\t\t\tcollection,\n\t\t\t\tat === -1 ? [...rows, record] : rows.map((r, i) => (i === at ? record : r))\n\t\t\t);\n\t\t},\n\t\tputAll<K extends keyof S & string>(collection: K, records: S[K][]): Promise<void> {\n\t\t\tconst field = idFieldOf(options.sync, collection);\n\t\t\tconst byId = new Map<unknown, number>();\n\t\t\tfor (const r of records) requireStringId(r, field, collection);\n\t\t\tconst rows = [...(collections[collection] ?? [])];\n\t\t\tif (field !== null) {\n\t\t\t\trows.forEach((r, i) => byId.set(r[field], i));\n\t\t\t\tfor (const r of records) {\n\t\t\t\t\tconst at = byId.get(r[field]);\n\t\t\t\t\tif (typeof at === 'number') rows[at] = r;\n\t\t\t\t\telse rows.push(r);\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\trows.push(...records);\n\t\t\t}\n\t\t\treturn mutate(collection, rows);\n\t\t},\n\t\tremove<K extends keyof S & string>(collection: K, id: string): Promise<void> {\n\t\t\tconst field = idFieldOf(options.sync, collection) ?? 'id';\n\t\t\tconst rows = collections[collection] ?? [];\n\t\t\tconst next = rows.filter((r) => r[field] !== id);\n\t\t\treturn next.length === rows.length ? Promise.resolve() : mutate(collection, next);\n\t\t},\n\t\tclear<K extends keyof S & string>(collection: K): Promise<void> {\n\t\t\tif ((collections[collection] ?? []).length === 0) return Promise.resolve();\n\t\t\treturn mutate(collection, []);\n\t\t},\n\t\tonChange(fn: () => void): () => void {\n\t\t\tdataSubs.add(fn);\n\t\t\treturn () => dataSubs.delete(fn);\n\t\t},\n\n\t\tasync connectDrive(auth: DriveAuth, opts?: { password?: string }): Promise<ConnectOutcome> {\n\t\t\tdriveAuth = auth;\n\t\t\tconst target = await driveConnect({ auth, kv: cache.kv, fileName: backupName });\n\t\t\tif (!target) return 'cancelled';\n\t\t\treturn connectTarget(target, opts);\n\t\t},\n\t\tasync connectFile(opts?: { password?: string }): Promise<ConnectOutcome> {\n\t\t\t// Asked twice on purpose: the first call answers from what the browser\n\t\t\t// exposes, the second from what it did when actually asked. A browser\n\t\t\t// that ships the picker and then refuses it only tells us so here.\n\t\t\tif (fileIsSupported()) {\n\t\t\t\tconst target = await fileConnect({ kv: cache.kv, fileName: backupName });\n\t\t\t\tif (target) return connectTarget(target, opts);\n\t\t\t\tif (fileIsSupported()) return 'cancelled';\n\t\t\t}\n\t\t\tawait store.setManualFile();\n\t\t\treturn 'manual';\n\t\t},\n\t\tasync connectWebdav(\n\t\t\tconfig: WebdavConfig,\n\t\t\topts?: { password?: string }\n\t\t): Promise<ConnectOutcome> {\n\t\t\tconst target = await webdavConnect({ kv: cache.kv, config });\n\t\t\tif (!target) {\n\t\t\t\tthrow new SelfstoreError(\n\t\t\t\t\t'TARGET_UNAVAILABLE',\n\t\t\t\t\t'The WebDAV server did not answer (URL, credentials or CORS).'\n\t\t\t\t);\n\t\t\t}\n\t\t\treturn connectTarget(target, opts);\n\t\t},\n\t\tasync connectS3(config: S3Config, opts?: { password?: string }): Promise<ConnectOutcome> {\n\t\t\tconst target = await s3Connect({ kv: cache.kv, config });\n\t\t\tif (!target) {\n\t\t\t\tthrow new SelfstoreError(\n\t\t\t\t\t'TARGET_UNAVAILABLE',\n\t\t\t\t\t'The S3 endpoint did not answer (endpoint, credentials or CORS).'\n\t\t\t\t);\n\t\t\t}\n\t\t\treturn connectTarget(target, opts);\n\t\t},\n\t\tconnectTarget,\n\t\tdisconnect: () => store.detachTarget(),\n\t\taddReplica: (target, opts) => store.attachReplica(target, opts),\n\t\tremoveReplica: (id) => store.detachReplica(id),\n\n\t\tprotect: (password: string) => store.setEncryption(password),\n\t\tunprotect: () => store.setEncryption(null),\n\t\tunlock: (password: string) => store.unlock(password),\n\t\treconnect: () => store.reconnect(),\n\n\t\texportBackup: () => store.exportBlob(),\n\t\tasync downloadBackup(filename?: string): Promise<boolean> {\n\t\t\tconst written = await saveToDisk(await store.exportBlob(), filename ?? backupName);\n\t\t\t// Clearing the pending flag on a cancelled dialog would drop the very\n\t\t\t// nudge that asks for the file the user still does not have.\n\t\t\tif (written) store.markDownloaded();\n\t\t\treturn written;\n\t\t},\n\t\tasync importBackup(file: Blob | Uint8Array, opts?: { password?: string }): Promise<void> {\n\t\t\t// The fluent read() strips reserved bookkeeping collections; removals\n\t\t\t// then propagate because the next save diffs against the sync meta.\n\t\t\tconst reader = restore(file);\n\t\t\tconst snap = opts?.password\n\t\t\t\t? await reader.withPassword(opts.password).read()\n\t\t\t\t: await reader.read();\n\t\t\tcollections = (snap.collections ?? {}) as Record<string, SimpleRecord[]>;\n\t\t\tfiles = snap.files ?? [];\n\t\t\temitData();\n\t\t\tstore.schedule();\n\t\t},\n\n\t\tget status(): StatusDescriptor {\n\t\t\treturn store.state.status;\n\t\t},\n\t\tget error(): StoreError | null {\n\t\t\treturn store.state.lastError;\n\t\t},\n\t\tget state(): LocalStoreState {\n\t\t\treturn store.state;\n\t\t},\n\t\tsubscribe: (fn: () => void) => store.subscribe(fn),\n\n\t\tflush: () => store.flush(),\n\t\tasync sync(): Promise<void> {\n\t\t\tawait store.syncNow();\n\t\t},\n\t\tdispose(): void {\n\t\t\tteardown.forEach((fn) => fn());\n\t\t\tteardown.length = 0;\n\t\t\tstore.dispose();\n\t\t},\n\n\t\tadvanced: store,\n\n\t\tflowHost: { engine: store, kv: cache.kv, backupName }\n\t};\n\n\treturn simple;\n}\n"]}
|
package/dist/widgets.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { connectFlow, shareFlow, joinFlow } from './chunk-
|
|
2
|
-
import { isOpenSupported } from './chunk-
|
|
1
|
+
import { connectFlow, shareFlow, joinFlow } from './chunk-OZS7BSC2.js';
|
|
2
|
+
import { isOpenSupported } from './chunk-XBPJRUA3.js';
|
|
3
3
|
import './chunk-ZDHTKFAL.js';
|
|
4
4
|
import './chunk-OIAXYI35.js';
|
|
5
5
|
import './chunk-CT2HTWA7.js';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "selfstore",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.8.0",
|
|
4
4
|
"description": "Local-first storage for browser apps: an IndexedDB working copy, portable encrypted ZIP backups (AES-256-GCM over Argon2id), durable homes (disk, Google Drive, WebDAV) and a serverless multi-device merge.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|