selfstore 1.8.20 → 1.8.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +107 -12
- package/dist/advanced.d.ts +23 -8
- package/dist/advanced.js +23 -12
- package/dist/advanced.js.map +1 -1
- package/dist/backups.d.ts +4 -4
- package/dist/backups.js +8 -6
- package/dist/{cache-lock-KWJUORTS.js → cache-lock-5RNG6CIK.js} +4 -3
- package/dist/cache-lock-5RNG6CIK.js.map +1 -0
- package/dist/{chunk-SKWHKM25.js → chunk-23UAUYSW.js} +40 -4
- package/dist/chunk-23UAUYSW.js.map +1 -0
- package/dist/chunk-3BWBBGFW.js +29 -0
- package/dist/chunk-3BWBBGFW.js.map +1 -0
- package/dist/{chunk-W22V5CRM.js → chunk-3KAAPYPO.js} +3 -3
- package/dist/{chunk-W22V5CRM.js.map → chunk-3KAAPYPO.js.map} +1 -1
- package/dist/chunk-757ZOSUI.js +27 -0
- package/dist/chunk-757ZOSUI.js.map +1 -0
- package/dist/{chunk-DCEXWSM6.js → chunk-AUH35AAQ.js} +3 -3
- package/dist/{chunk-DCEXWSM6.js.map → chunk-AUH35AAQ.js.map} +1 -1
- package/dist/{chunk-ULPGGZ6L.js → chunk-DJZ5S3FI.js} +11 -10
- package/dist/chunk-DJZ5S3FI.js.map +1 -0
- package/dist/{chunk-HOG2IKEP.js → chunk-EBCQ7ZG7.js} +4 -4
- package/dist/{chunk-HOG2IKEP.js.map → chunk-EBCQ7ZG7.js.map} +1 -1
- package/dist/chunk-FJGWTBAL.js +6 -0
- package/dist/chunk-FJGWTBAL.js.map +1 -0
- package/dist/chunk-GW5CYP4C.js +212 -0
- package/dist/chunk-GW5CYP4C.js.map +1 -0
- package/dist/{chunk-MGDXR3RB.js → chunk-HHPJPEEG.js} +6 -5
- package/dist/chunk-HHPJPEEG.js.map +1 -0
- package/dist/{chunk-J5MFXJU6.js → chunk-JTNFZV2B.js} +78 -26
- package/dist/chunk-JTNFZV2B.js.map +1 -0
- package/dist/chunk-JZHWAX34.js +178 -0
- package/dist/chunk-JZHWAX34.js.map +1 -0
- package/dist/chunk-LUNYYH6D.js +223 -0
- package/dist/chunk-LUNYYH6D.js.map +1 -0
- package/dist/{chunk-LKIRWVXM.js → chunk-LYO36VYB.js} +12 -11
- package/dist/chunk-LYO36VYB.js.map +1 -0
- package/dist/{chunk-O3Y6FORN.js → chunk-OY2A5DCB.js} +4 -3
- package/dist/chunk-OY2A5DCB.js.map +1 -0
- package/dist/chunk-QSLHEE2L.js +13 -0
- package/dist/chunk-QSLHEE2L.js.map +1 -0
- package/dist/{chunk-3OBJOMQ2.js → chunk-RREJ2MFN.js} +4 -28
- package/dist/chunk-RREJ2MFN.js.map +1 -0
- package/dist/chunk-XMC67RNM.js +29 -0
- package/dist/chunk-XMC67RNM.js.map +1 -0
- package/dist/{connect-GY1R9c6Q.d.ts → connect-Bob2mofK.d.ts} +3 -3
- package/dist/{drive-BLRsK32w.d.ts → drive-86CHuTij.d.ts} +63 -2
- package/dist/flows.d.ts +5 -5
- package/dist/flows.js +17 -15
- package/dist/flows.js.map +1 -1
- package/dist/groups.js +4 -3
- package/dist/groups.js.map +1 -1
- package/dist/households.d.ts +1 -1
- package/dist/index.d.ts +50 -8
- package/dist/index.js +72 -17
- package/dist/index.js.map +1 -1
- package/dist/kdf-worker.js +7 -2
- package/dist/kdf-worker.js.map +1 -1
- package/dist/{local-DrziuI_t.d.ts → local-8H61XxbL.d.ts} +2 -2
- package/dist/local-XET4G32B.js +7 -0
- package/dist/{local-DPS35F5C.js.map → local-XET4G32B.js.map} +1 -1
- package/dist/{manager-BlSs6Q5S.d.ts → manager-Di6lNE4E.d.ts} +1 -1
- package/dist/manager-drive-YLSLQ63O.js +10 -0
- package/dist/{manager-drive-4WLKOOXO.js.map → manager-drive-YLSLQ63O.js.map} +1 -1
- package/dist/s3-43RWNCV3.js +11 -0
- package/dist/s3-43RWNCV3.js.map +1 -0
- package/dist/{s3-BLgf6hqR.d.ts → s3-BsbVn3fw.d.ts} +1 -1
- package/dist/{store-DhrWMYg_.d.ts → store-CIbiNoiE.d.ts} +1 -1
- package/dist/sync.d.ts +7 -6
- package/dist/sync.js +3 -1
- package/dist/webdav-EEQKX5TZ.js +9 -0
- package/dist/webdav-EEQKX5TZ.js.map +1 -0
- package/dist/widgets.d.ts +19 -5
- package/dist/widgets.js +17 -10
- package/dist/widgets.js.map +1 -1
- package/llms.txt +51 -4
- package/package.json +12 -5
- package/skills/selfstore/SKILL.md +115 -0
- package/dist/cache-lock-KWJUORTS.js.map +0 -1
- package/dist/chunk-3OBJOMQ2.js.map +0 -1
- package/dist/chunk-J5MFXJU6.js.map +0 -1
- package/dist/chunk-LKIRWVXM.js.map +0 -1
- package/dist/chunk-MGDXR3RB.js.map +0 -1
- package/dist/chunk-O3Y6FORN.js.map +0 -1
- package/dist/chunk-SKWHKM25.js.map +0 -1
- package/dist/chunk-TKWU52LK.js +0 -607
- package/dist/chunk-TKWU52LK.js.map +0 -1
- package/dist/chunk-ULPGGZ6L.js.map +0 -1
- package/dist/local-DPS35F5C.js +0 -6
- package/dist/manager-drive-4WLKOOXO.js +0 -8
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../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":["connect","openExisting","fromSession","CONFIG_KEY","KEY_KEY","DATA_DEADLINE_MS","META_DEADLINE_MS","configKey","saveConfig","loadConfig","labelOf","versionOf","fromConfig"],"mappings":"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","file":"chunk-TKWU52LK.js","sourcesContent":["/**\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, type SelfstoreErrorCode } 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/** Give a browser exception the stable code the target contract requires, so a\n * host reads WHICH kind of failure it hit instead of a DOMException name.\n *\n * Untyped, everything the handle throws lands in the store's default branch -\n * \"transient, the next save retries\" - and that is the one reading always\n * wrong here: no amount of retrying re-grants a permission, and a file that\n * was deleted does not come back on its own. Both then loop forever while the\n * store keeps reporting the data as saved. */\nfunction asTargetError(e: unknown, transient: SelfstoreErrorCode, what: string): Error {\n\tconst name = e instanceof DOMException ? e.name : '';\n\t// Revoked or denied: only a user gesture re-grants it, which is exactly what\n\t// the reconnect gate asks for.\n\tif (name === 'NotAllowedError' || name === 'SecurityError') {\n\t\treturn new AuthExpiredError('File permission was revoked.');\n\t}\n\t// Deleted, renamed, or on a volume that is no longer mounted. Permanent as\n\t// far as this handle goes: reconnecting means pointing at a file again.\n\tif (name === 'NotFoundError') {\n\t\treturn new SelfstoreError('TARGET_GONE', `${what}: the backup file is no longer there.`);\n\t}\n\treturn new SelfstoreError(transient, `${what}: ${String(e)}`);\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\tthrow asTargetError(e, 'TARGET_WRITE_FAILED', 'File write failed');\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\t// A read that fails is NOT a destination that is empty: returning null\n\t\t\t// here would tell the store \"no backup there\", and the connect that\n\t\t\t// follows would overwrite the file this read merely failed to open.\n\t\t\ttry {\n\t\t\t\treturn await handle.getFile();\n\t\t\t} catch (e) {\n\t\t\t\tthrow asTargetError(e, 'TARGET_UNAVAILABLE', 'File read failed');\n\t\t\t}\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"]}
|