selfstore 1.5.0 → 1.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -3
- package/dist/advanced.d.ts +1 -1
- package/dist/advanced.js +3 -3
- package/dist/{chunk-I243KZMZ.js → chunk-BRH6LZKM.js} +3 -3
- package/dist/{chunk-I243KZMZ.js.map → chunk-BRH6LZKM.js.map} +1 -1
- package/dist/chunk-QCJSQQX3.js +59 -0
- package/dist/chunk-QCJSQQX3.js.map +1 -0
- package/dist/{chunk-SRKUHFRG.js → chunk-S3FN54M5.js} +21 -6
- package/dist/chunk-S3FN54M5.js.map +1 -0
- package/dist/{chunk-FL66ACWR.js → chunk-S6CJPFBK.js} +3 -3
- package/dist/{chunk-FL66ACWR.js.map → chunk-S6CJPFBK.js.map} +1 -1
- package/dist/flows.js +2 -2
- package/dist/index.d.ts +15 -5
- package/dist/index.js +9 -8
- package/dist/index.js.map +1 -1
- package/dist/{local-6OALceUy.d.ts → local-CwJPULbf.d.ts} +10 -4
- package/dist/local-R5QUGDZU.js +7 -0
- package/dist/{local-KIP25T7I.js.map → local-R5QUGDZU.js.map} +1 -1
- package/dist/widgets.js +5 -2
- package/dist/widgets.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-SRKUHFRG.js.map +0 -1
- package/dist/chunk-UWVAKBU4.js +0 -45
- package/dist/chunk-UWVAKBU4.js.map +0 -1
- package/dist/local-KIP25T7I.js +0 -7
package/README.md
CHANGED
|
@@ -37,8 +37,9 @@ await store.connectWebdav({ url, username, password: appPassword }); // Nextclou
|
|
|
37
37
|
// End-to-end encryption of everything that leaves the device.
|
|
38
38
|
await store.protect('a passphrase the user chose');
|
|
39
39
|
|
|
40
|
-
// A portable encrypted .zip the user can walk away with.
|
|
41
|
-
|
|
40
|
+
// A portable encrypted .zip the user can walk away with. False when they closed
|
|
41
|
+
// the save dialog - nothing was written, so do not tell them they are covered.
|
|
42
|
+
const saved = await store.downloadBackup();
|
|
42
43
|
```
|
|
43
44
|
|
|
44
45
|
`connectDrive` / `connectFile` / `connectWebdav` all resolve to what actually
|
|
@@ -311,7 +312,9 @@ const persistence = {
|
|
|
311
312
|
.clear()` needs no key and abandons the sealed cache, after which they can
|
|
312
313
|
start again from a backup file or from scratch. Throwing from the
|
|
313
314
|
`cacheLock` callback aborts the boot and hands control back to you, which
|
|
314
|
-
is where that offer belongs.
|
|
315
|
+
is where that offer belongs. Read the backup BEFORE you clear anything -
|
|
316
|
+
recipe 4b spells out why that order is the difference between a recovery
|
|
317
|
+
and a second loss.
|
|
315
318
|
- Opt-in hardening for sensitive deployments: `requireEncryption` refuses to
|
|
316
319
|
write or export a plaintext backup, and `passwordPolicy` refuses a password
|
|
317
320
|
weaker than a length / character-class rule - both enforced at the store, so
|
package/dist/advanced.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { B as BackupTarget } from './store-C20_6JBo.js';
|
|
|
2
2
|
export { A as AuthExpiredError, a as BuiltinTargetKind, E as EncodeOptions, H as Header, K as KdfParams, L as LocalStore, b as LocalStoreOptions, c as LocalStoreState, M as MirrorState, d as Mode, P as PasswordCheck, e as PasswordPolicy, f as PasswordRequirement, g as PeerSource, h as PeerState, R as RESERVED_COLLECTION_PREFIX, i as RESERVED_STORE_MODES, j as RecipientStanza, k as ReplicaState, S as SelfstoreError, l as SelfstoreErrorCode, m as Severity, n as Snapshot, o as SnapshotFile, p as StatusAction, q as StatusDescriptor, r as StatusInput, s as StorageState, t as StoreError, u as StoreGroupConfig, v as SyncJournalEntry, w as SyncSource, T as TargetKind, x as checkPasswordPolicy, y as createLocalStore, z as deriveStatus, C as errorLabelKey, D as isAuthExpired, F as isReservedStoreMode, G as isSelfstoreError } from './store-C20_6JBo.js';
|
|
3
3
|
import { K as KV } from './cache-ilsK3Tki.js';
|
|
4
4
|
export { C as CachedFile, L as LocalCache, e as LockableCache, i as indexedDbCache, f as isLockableCache, m as memoryCache } from './cache-ilsK3Tki.js';
|
|
5
|
-
export { e as exportSnapshot, g as gisDriveAuth, i as importSnapshot, a as inspect, b as isEncrypted, p as pickFromDisk, s as saveToDisk } from './local-
|
|
5
|
+
export { e as exportSnapshot, g as gisDriveAuth, i as importSnapshot, a as inspect, b as isEncrypted, p as pickFromDisk, s as saveToDisk } from './local-CwJPULbf.js';
|
|
6
6
|
import { c as connect$1, f as fromSession$1, p as preview, a as adopt, b as findOrCreateOwnFile, l as listBackups, d as createBackup, e as deleteBackup, r as renameBackup, g as connect$2, h as fromSession$2, i as connect$3, j as fromSession$3, k as peer } from './drive-BhiTdk-Q.js';
|
|
7
7
|
export { D as DriveAuth, m as DriveBackupInfo, n as DriveOptions, S as S3Config, o as S3ConnectOptions, W as WebdavConfig, q as WebdavConnectOptions, s as WebdavPeerOptions } from './drive-BhiTdk-Q.js';
|
|
8
8
|
import './sync.js';
|
package/dist/advanced.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
export { RESERVED_STORE_MODES, createLocalStore, deriveStatus, gisDriveAuth, indexedDbCache, isLockableCache, isReservedStoreMode, memoryCache } from './chunk-
|
|
2
|
-
export { pickFromDisk, saveToDisk } from './chunk-
|
|
1
|
+
export { RESERVED_STORE_MODES, createLocalStore, deriveStatus, gisDriveAuth, indexedDbCache, isLockableCache, isReservedStoreMode, memoryCache } from './chunk-S6CJPFBK.js';
|
|
2
|
+
export { pickFromDisk, saveToDisk } from './chunk-QCJSQQX3.js';
|
|
3
3
|
import './chunk-SKWHKM25.js';
|
|
4
4
|
import { FILE_ID_KEY, renameBackup, deleteBackup, createBackup, listBackups, findOrCreateOwnFile, adopt, preview, fromSession, connect, isSupported, fromSession2, connect2, fromSession4, connect4, peer, fromSession3, connect3 } from './chunk-TEIAFKFS.js';
|
|
5
|
-
export { RESERVED_COLLECTION_PREFIX, checkPasswordPolicy, exportSnapshot, importSnapshot, inspect, isEncrypted } from './chunk-
|
|
5
|
+
export { RESERVED_COLLECTION_PREFIX, checkPasswordPolicy, exportSnapshot, importSnapshot, inspect, isEncrypted } from './chunk-S3FN54M5.js';
|
|
6
6
|
import './chunk-KHER6MWR.js';
|
|
7
7
|
import './chunk-QBCU7OOP.js';
|
|
8
8
|
import './chunk-6746AL4I.js';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { connect4, connect3, isSupported, connect, openExisting, connect2 } from './chunk-TEIAFKFS.js';
|
|
2
|
-
import { restore } from './chunk-
|
|
2
|
+
import { restore } from './chunk-S3FN54M5.js';
|
|
3
3
|
import { makeMachine, withDeadline, toStoreError } from './chunk-43BJDRPT.js';
|
|
4
4
|
import { SelfstoreError, isSelfstoreError } from './chunk-VBFMMR7Q.js';
|
|
5
5
|
|
|
@@ -447,5 +447,5 @@ function joinFlow(link, engine, options = {}) {
|
|
|
447
447
|
}
|
|
448
448
|
|
|
449
449
|
export { connectFlow, joinFlow, shareFlow };
|
|
450
|
-
//# sourceMappingURL=chunk-
|
|
451
|
-
//# sourceMappingURL=chunk-
|
|
450
|
+
//# sourceMappingURL=chunk-BRH6LZKM.js.map
|
|
451
|
+
//# sourceMappingURL=chunk-BRH6LZKM.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/flows/connect.ts","../src/flows/share.ts","../src/flows/join.ts"],"names":["connect","config","gen","DEFAULT_DEADLINE_MS"],"mappings":"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","file":"chunk-I243KZMZ.js","sourcesContent":["// connectFlow: the \"where does my data live\" journey as a headless state\n// machine. It owns ordering and failure rules that took a series of\n// production hotfixes to get right, each covered by a test:\n//\n// choose -> (form) -> authorizing -> (password) -> (conflict) -> connected\n// \\-> error\n//\n// One popup per gesture (re-entry ignored while a leg is in flight), and a\n// cancelled popup is not an error - back to 'choose', silently. An encrypted\n// backup's password is proven by a trial read while the store is untouched,\n// so a wrong password can never half-connect (deferUnlock opts out: adopt\n// locked, unlock where the host chooses). overwrite() is the forgotten-\n// password escape: local wiped first, tiny blank write. Merge is the default\n// reconciliation; the destructive choices hide behind 'conflict', which only\n// appears when the app declared local data worth asking about. Network legs\n// run under a deadline; human steps never do. A cancelled journey stays\n// dead - late resolutions are discarded (generation counter).\n\nimport type { LocalStore, StoreError } from '../persistence/store';\nimport type { BackupTarget } from '../persistence/target';\nimport type { KV } from '../persistence/cache';\nimport { restore } from '../selfstore/fluent';\nimport { SelfstoreError, isSelfstoreError } from '../selfstore/errors';\nimport { connect as driveConnect, type DriveAuth } from '../persistence/targets/drive';\nimport {\n\tconnect as fileConnect,\n\tisSupported as fileIsSupported,\n\topenExisting as fileOpenExisting\n} from '../persistence/targets/file';\nimport { connect as webdavConnect, type WebdavConfig } from '../persistence/targets/webdav';\nimport { connect as s3Connect, type S3Config } from '../persistence/targets/s3';\nimport { makeMachine, toStoreError, withDeadline, type FlowStore } from './machine';\n\nexport type ConnectKind = 'drive' | 'file' | 'webdav' | 's3';\n\n/** Produce a connected BackupTarget, or null when the user cancelled the\n * gesture (consent refused, picker closed). Custom connectors slot custom\n * targets - or test fakes - into the same journey. */\nexport type Connector = () => Promise<BackupTarget | null>;\n\n/** Which destinations the flow offers, and how to open each one. The offer\n * renders in this OBJECT's insertion order: the host decides which\n * destination leads by how it writes the object, not by a ranking here. */\nexport interface ConnectTargets {\n\t/** Google Drive: the auth strategy (e.g. gisDriveAuth), or a custom connector. */\n\tdrive?: DriveAuth | Connector;\n\t/** Local file via the browser's picker (true), or a custom connector. On\n\t * browsers without the File System Access API the flow degrades to the\n\t * manual download mode by itself. The object form offers TWO gestures on\n\t * the same destination: `create` (save picker: start or overwrite a backup\n\t * file) and `open` (open picker: adopt an EXISTING backup file - it then\n\t * goes through the same inspect/password/resolution journey). */\n\tfile?: true | Connector | { create?: true | Connector; open?: true | Connector };\n\t/** WebDAV: true renders a 'form' step and expects submitWebdav(config);\n\t * a fixed WebdavConfig skips the form; or a custom connector. */\n\twebdav?: true | WebdavConfig | Connector;\n\t/** S3-compatible bucket (Amazon S3, R2, B2, MinIO): true renders a 'form'\n\t * step and expects submitS3(config); a fixed S3Config skips the form; or a\n\t * custom connector. Offer it alongside `webdav` and the widget groups both\n\t * under one \"your server\" entry with a WebDAV/S3 toggle. */\n\ts3?: true | S3Config | Connector;\n}\n\n/** What the flows need from the store. The simple store exposes this as\n * `store.flowHost`; an advanced-store app builds it by hand. */\nexport interface FlowHost {\n\tengine: LocalStore;\n\tkv: KV;\n\t/** Backup file name on the destination (the simple store uses `app.zip`). */\n\tbackupName: string;\n}\n\n/** What every flow and widget accepts as \"the store\": the simple facade, which\n * carries its ports on `flowHost`, or a hand-built host. */\nexport type StoreLike = FlowHost | { flowHost: FlowHost };\n\nexport interface ConnectFlowOptions {\n\t/** Declare when this device holds data worth a question: only then does an\n\t * existing backup raise the 'conflict' step with the destructive choices.\n\t * Default: never - the flow applies defaultResolution. */\n\thasLocalData?: () => boolean;\n\t/** How to reconcile with a destination that already holds a backup when the\n\t * conflict step is not raised. Default 'merge' - the only choice that\n\t * loses nothing on either side. 'resume' adopts the backup (the\n\t * destination wins; this device's working copy is replaced), 'replace'\n\t * overwrites the backup with this device. An encrypted backup still asks\n\t * for its password first, whatever the resolution; an empty destination\n\t * is not concerned (this device's data simply becomes its content). */\n\tdefaultResolution?: ConnectResolution;\n\t/** Skip the password step on an encrypted backup: adopt it AS is (resume)\n\t * and complete with the store locked - nothing readable, nothing\n\t * clobbered - for the host to unlock on its own surface (engine.unlock)\n\t * after the connect. Adoption is forced whatever defaultResolution says:\n\t * without the proven key a merge is impossible and a replace would\n\t * destroy a backup no one proved they own. Hosts that let a device\n\t * holding real local data connect should keep the password-first\n\t * journey - adoption replaces the working copy. */\n\tdeferUnlock?: boolean;\n\t/**\n\t * A secret the host already holds (an app passphrase, a derived key), used\n\t * to encrypt the backup when this journey creates or adopts one that is NOT\n\t * already encrypted - an empty destination, or a plaintext file.\n\t *\n\t * Two things need it. A store built with `requireEncryption` refuses a\n\t * passwordless attach outright (ENCRYPTION_REQUIRED), so without this the\n\t * connect journey cannot complete at all. And even without that setting, a\n\t * fresh backup is better born encrypted than written in clear and protected\n\t * on a second write, which would leave cleartext on the destination in\n\t * between.\n\t *\n\t * Never used against a destination whose backup is already encrypted: that\n\t * one is proven through the password step (or adopted locked under\n\t * `deferUnlock`), so a host secret that happens to differ can neither fail\n\t * the attach nor overwrite what it cannot read. Resolved lazily, so the\n\t * secret is only read when a connect actually needs it. Must satisfy the\n\t * store's password policy.\n\t */\n\tpassword?: string | (() => string | Promise<string>);\n\t/** Deadline for each network leg (inspect, trial-read, attach), ms. */\n\tdeadlineMs?: number;\n}\n\nexport type ConnectStep =\n\t'choose' | 'form' | 'authorizing' | 'password' | 'conflict' | 'connected' | 'error';\n\n/** How to reconcile with a destination that already holds a backup. */\nexport type ConnectResolution = 'merge' | 'resume' | 'replace';\n\n/** What connecting did. 'merged': both sides folded. 'started': empty\n * destination now holds this device's data. 'resumed': the backup won,\n * this device adopted it. 'replaced': this device won, the backup was\n * overwritten. 'manual': degraded download-on-demand file mode. */\nexport type ConnectFlowOutcome = 'merged' | 'started' | 'resumed' | 'replaced' | 'manual';\n\nexport interface ConnectSnapshot {\n\tstep: ConnectStep;\n\t/** The destinations to offer, in the order given. */\n\tkinds: readonly ConnectKind[];\n\t/** The destination being connected (null while choosing). */\n\tkind: ConnectKind | null;\n\t/** Set when step === 'connected'. */\n\toutcome: ConnectFlowOutcome | null;\n\t/** Known after inspection: the destination already holds a backup. */\n\thasBackup: boolean;\n\t/** ...and that backup is encrypted. */\n\tencrypted: boolean;\n\t/** The last submitPassword() was wrong (stay on 'password', say so). */\n\tpasswordError: boolean;\n\t/** An async leg is in flight (render a spinner, disable the actions). */\n\tbusy: boolean;\n\t/** Set when step === 'error': show `labelKey`, log `message`. */\n\terror: StoreError | null;\n}\n\nexport interface ConnectFlow extends FlowStore<ConnectSnapshot> {\n\t/** Pick a destination. Call it inside the user's click: the consent popup /\n\t * file picker opens synchronously from the gesture. Ignored while a leg is\n\t * already in flight (one popup per gesture). For a `file` target declared\n\t * with the object form, `variant` picks the gesture: 'create' (default,\n\t * save picker) or 'open' (adopt an existing backup file). */\n\tchoose(kind: ConnectKind, variant?: 'create' | 'open'): void;\n\t/** 'form' step (webdav: true): submit the server config. */\n\tsubmitWebdav(config: WebdavConfig): void;\n\t/** 'form' step (s3: true): submit the bucket config. */\n\tsubmitS3(config: S3Config): void;\n\t/** 'password' step: prove the backup's password. Wrong password sets\n\t * passwordError and stays here; nothing is attached either way. */\n\tsubmitPassword(password: string): void;\n\t/** 'password' step escape hatch: give up on a forgotten password and start a\n\t * fresh backup over the protected one. The encrypted backup is overwritten\n\t * by this device's data (possibly empty) and lost for good - the only way\n\t * out when the password cannot be recovered. */\n\toverwrite(): void;\n\t/** 'conflict' step: pick the reconciliation. */\n\tresolveConflict(how: ConnectResolution): void;\n\t/** Abandon the journey in progress: back to 'choose'. A late resolution of\n\t * the abandoned leg is discarded. */\n\tcancel(): void;\n\t/** 'error' step: back to 'choose' to try again. */\n\tretry(): void;\n}\n\nconst DEFAULT_DEADLINE_MS = 30_000;\n\n/** A resolution's attach plan: which strategy runs, which outcome it names. */\nfunction planFor(how: ConnectResolution): {\n\tstrategy: 'merge' | 'replace-local' | 'replace-remote';\n\toutcome: ConnectFlowOutcome;\n} {\n\tif (how === 'resume') return { strategy: 'replace-local', outcome: 'resumed' };\n\tif (how === 'replace') return { strategy: 'replace-remote', outcome: 'replaced' };\n\treturn { strategy: 'merge', outcome: 'merged' };\n}\n\nexport function connectFlow(\n\tstore: StoreLike,\n\ttargets: ConnectTargets,\n\toptions: ConnectFlowOptions = {}\n): ConnectFlow {\n\tconst host = 'flowHost' in store ? store.flowHost : store;\n\tconst deadlineMs = options.deadlineMs ?? DEFAULT_DEADLINE_MS;\n\tconst kinds = (Object.keys(targets) as ConnectKind[]).filter(\n\t\t(k) => (k === 'drive' || k === 'file' || k === 'webdav' || k === 's3') && targets[k] != null\n\t);\n\n\tconst m = makeMachine<ConnectSnapshot>({\n\t\tstep: 'choose',\n\t\tkinds,\n\t\tkind: null,\n\t\toutcome: null,\n\t\thasBackup: false,\n\t\tencrypted: false,\n\t\tpasswordError: false,\n\t\tbusy: false,\n\t\terror: null\n\t});\n\n\t// The journey's working set. `generation` invalidates in-flight legs on\n\t// cancel(): whoever resolves late compares its captured generation and\n\t// drops its result instead of resurrecting an abandoned journey.\n\tlet generation = 0;\n\tlet target: BackupTarget | null = null;\n\tlet password: string | null = null;\n\t// Set at inspection: the destination already holds an ENCRYPTED backup, so\n\t// its key is the one that matters and the host secret must stay out of it.\n\tlet remoteEncrypted = false;\n\n\tconst backToChoose = (): void => {\n\t\tgeneration++;\n\t\ttarget = null;\n\t\tpassword = null;\n\t\tremoteEncrypted = false;\n\t\tm.set({\n\t\t\tstep: 'choose',\n\t\t\tkind: null,\n\t\t\toutcome: null,\n\t\t\thasBackup: false,\n\t\t\tencrypted: false,\n\t\t\tpasswordError: false,\n\t\t\tbusy: false,\n\t\t\terror: null\n\t\t});\n\t};\n\n\tconst fail = (e: unknown): void => {\n\t\tpassword = null;\n\t\tm.set({ step: 'error', busy: false, error: toStoreError(e) });\n\t};\n\n\t/** The host's own secret, resolved on demand (see options.password). */\n\tconst hostSecret = async (): Promise<string | null> => {\n\t\tconst supplied = options.password;\n\t\tif (supplied === undefined) return null;\n\t\treturn typeof supplied === 'function' ? await supplied() : supplied;\n\t};\n\n\tfunction connectorFor(\n\t\tkind: ConnectKind,\n\t\tvariant: 'create' | 'open' = 'create'\n\t): Connector | null {\n\t\tconst spec = targets[kind];\n\t\tif (spec == null) return null;\n\t\tif (typeof spec === 'function') return spec;\n\t\tif (kind === 'drive') {\n\t\t\tconst auth = spec as DriveAuth;\n\t\t\treturn () => driveConnect({ auth, kv: host.kv, fileName: host.backupName });\n\t\t}\n\t\tif (kind === 'file') {\n\t\t\t// Object form: each gesture has its own spec ('open' has no built-in\n\t\t\t// meaning for other kinds, so it resolves here only).\n\t\t\tif (typeof spec === 'object' && !('url' in spec)) {\n\t\t\t\tconst sub = (spec as { create?: true | Connector; open?: true | Connector })[variant];\n\t\t\t\tif (sub == null) return null;\n\t\t\t\tif (typeof sub === 'function') return sub;\n\t\t\t\treturn variant === 'open'\n\t\t\t\t\t? () => fileOpenExisting({ kv: host.kv })\n\t\t\t\t\t: () => fileConnect({ kv: host.kv, fileName: host.backupName });\n\t\t\t}\n\t\t\treturn () => fileConnect({ kv: host.kv, fileName: host.backupName });\n\t\t}\n\t\tif (kind === 's3') {\n\t\t\tconst config = spec as S3Config; // 's3: true' goes through the form step\n\t\t\treturn () => s3Connect({ kv: host.kv, config });\n\t\t}\n\t\tconst config = spec as WebdavConfig; // 'webdav: true' goes through the form step\n\t\treturn () => webdavConnect({ kv: host.kv, config });\n\t}\n\n\t/** After the target is in hand: inspect, then route to password / conflict /\n\t * attach. Everything here is network - deadline-bounded, generation-checked. */\n\tasync function inspectAndRoute(gen: number, t: BackupTarget): Promise<void> {\n\t\tconst info = await withDeadline(host.engine.inspectTarget(t), deadlineMs, 'The destination');\n\t\tif (gen !== generation) return;\n\t\ttarget = t;\n\t\tremoteEncrypted = info.encrypted;\n\t\tm.set({ hasBackup: info.hasBackup, encrypted: info.encrypted });\n\t\tif (!info.hasBackup) {\n\t\t\t// Empty destination: this device's data becomes its content. Nothing\n\t\t\t// to ask, nothing to lose.\n\t\t\tawait attach(gen, 'replace-remote', 'started');\n\t\t\treturn;\n\t\t}\n\t\tif (info.encrypted) {\n\t\t\tif (options.deferUnlock) {\n\t\t\t\t// The host owns the unlock surface: adopt the backup as it stands.\n\t\t\t\t// A null password on an encrypted file lands the store locked -\n\t\t\t\t// nothing readable, nothing clobbered - until engine.unlock()\n\t\t\t\t// proves the password wherever the host asks for it. The conflict\n\t\t\t\t// step is moot here: without the key, merge cannot read the remote\n\t\t\t\t// and replace would destroy a backup no one proved they own.\n\t\t\t\tawait attach(gen, 'replace-local', 'resumed');\n\t\t\t\treturn;\n\t\t\t}\n\t\t\t// Ask before touching anything. The password gates every resolution,\n\t\t\t// including 'replace': overwriting an encrypted backup demands proof\n\t\t\t// of ownership, or a typo away from someone's data.\n\t\t\tm.set({ step: 'password', busy: false, passwordError: false });\n\t\t\treturn;\n\t\t}\n\t\trouteAfterUnlock(gen);\n\t}\n\n\t/** Password proven (or none needed): either ask the conflict question, or\n\t * apply the default resolution - 'merge' unless the host chose otherwise,\n\t * the only default that loses nothing on either side. */\n\tfunction routeAfterUnlock(gen: number): void {\n\t\tif (gen !== generation) return;\n\t\tif (options.hasLocalData?.() === true) {\n\t\t\tm.set({ step: 'conflict', busy: false });\n\t\t\treturn;\n\t\t}\n\t\tconst plan = planFor(options.defaultResolution ?? 'merge');\n\t\tvoid attach(gen, plan.strategy, plan.outcome);\n\t}\n\n\tasync function attach(\n\t\tgen: number,\n\t\tstrategy: 'merge' | 'replace-local' | 'replace-remote',\n\t\toutcome: ConnectFlowOutcome,\n\t\twipe = false\n\t): Promise<void> {\n\t\tif (gen !== generation || !target) return;\n\t\tm.set({ busy: true });\n\t\ttry {\n\t\t\t// A backup that is already encrypted carries its own key: it was proven\n\t\t\t// at the password step, or deliberately left locked. Everywhere else -\n\t\t\t// an empty destination, a plaintext one - the host secret (when it\n\t\t\t// offers one) makes the backup encrypted from its very first write.\n\t\t\tconst secret = password ?? (remoteEncrypted ? null : await hostSecret());\n\t\t\tif (gen !== generation) return;\n\t\t\tawait withDeadline(\n\t\t\t\thost.engine.attachTarget(target, { password: secret, strategy, wipe }),\n\t\t\t\tdeadlineMs,\n\t\t\t\t'The destination'\n\t\t\t);\n\t\t\tif (gen !== generation) return;\n\t\t\tpassword = null;\n\t\t\tm.set({ step: 'connected', outcome, busy: false, error: null });\n\t\t} catch (e) {\n\t\t\tif (gen !== generation) return;\n\t\t\tfail(e);\n\t\t}\n\t}\n\n\treturn {\n\t\tget snapshot() {\n\t\t\treturn m.snapshot;\n\t\t},\n\t\tsubscribe: m.subscribe,\n\n\t\tchoose(kind: ConnectKind, variant: 'create' | 'open' = 'create'): void {\n\t\t\tconst { step, busy } = m.snapshot;\n\t\t\tif (busy || (step !== 'choose' && step !== 'error')) return; // one popup per gesture\n\t\t\tconst connector = connectorFor(kind, variant);\n\t\t\tif (!connector) return;\n\t\t\tif (\n\t\t\t\t(kind === 'webdav' && targets.webdav === true) ||\n\t\t\t\t(kind === 's3' && targets.s3 === true)\n\t\t\t) {\n\t\t\t\tm.set({ step: 'form', kind, error: null });\n\t\t\t\treturn;\n\t\t\t}\n\t\t\t// Degraded manual mode only makes sense for CREATING a backup through\n\t\t\t// the built-in save picker: adopting an existing file needs the real\n\t\t\t// picker, and a custom connector owns its own fallback.\n\t\t\tconst builtinCreate =\n\t\t\t\ttargets.file === true ||\n\t\t\t\t(typeof targets.file === 'object' &&\n\t\t\t\t\t(targets.file as { create?: unknown }).create !== undefined &&\n\t\t\t\t\ttypeof (targets.file as { create?: unknown }).create !== 'function');\n\t\t\tif (kind === 'file' && variant === 'create' && builtinCreate && !fileIsSupported()) {\n\t\t\t\t// No picker on this browser: degrade to download-on-demand instead of\n\t\t\t\t// a dead button.\n\t\t\t\tconst gen = ++generation;\n\t\t\t\tm.set({ step: 'authorizing', kind, busy: true, error: null });\n\t\t\t\tvoid host.engine\n\t\t\t\t\t.setManualFile()\n\t\t\t\t\t.then(() => {\n\t\t\t\t\t\tif (gen !== generation) return;\n\t\t\t\t\t\tm.set({ step: 'connected', outcome: 'manual', busy: false });\n\t\t\t\t\t})\n\t\t\t\t\t.catch((e) => {\n\t\t\t\t\t\tif (gen !== generation) return;\n\t\t\t\t\t\tfail(e);\n\t\t\t\t\t});\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst gen = ++generation;\n\t\t\tm.set({ step: 'authorizing', kind, busy: true, error: null });\n\t\t\t// The connector runs the human gesture (popup, picker): no deadline on\n\t\t\t// people. The network legs after it are bounded in inspectAndRoute.\n\t\t\tvoid connector()\n\t\t\t\t.then(async (t) => {\n\t\t\t\t\tif (gen !== generation) return;\n\t\t\t\t\tif (!t) {\n\t\t\t\t\t\t// Drive/file/custom answer null for a cancelled popup or picker:\n\t\t\t\t\t\t// not an error, no message. A FIXED webdav/s3 config answering\n\t\t\t\t\t\t// null means the server did not: that one is a retryable error.\n\t\t\t\t\t\tif (kind === 'webdav' || kind === 's3') {\n\t\t\t\t\t\t\tfail(\n\t\t\t\t\t\t\t\tnew SelfstoreError(\n\t\t\t\t\t\t\t\t\t'TARGET_UNAVAILABLE',\n\t\t\t\t\t\t\t\t\t'The server did not answer (address, credentials or CORS).'\n\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tbackToChoose();\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tawait inspectAndRoute(gen, t);\n\t\t\t\t})\n\t\t\t\t.catch((e) => {\n\t\t\t\t\tif (gen !== generation) return;\n\t\t\t\t\tfail(e);\n\t\t\t\t});\n\t\t},\n\n\t\tsubmitWebdav(config: WebdavConfig): void {\n\t\t\tconst { step, busy } = m.snapshot;\n\t\t\tif (busy || step !== 'form') return;\n\t\t\tconst gen = ++generation;\n\t\t\tm.set({ step: 'authorizing', busy: true });\n\t\t\tvoid withDeadline(webdavConnect({ kv: host.kv, config }), deadlineMs, 'The WebDAV server')\n\t\t\t\t.then(async (t) => {\n\t\t\t\t\tif (gen !== generation) return;\n\t\t\t\t\tif (!t) {\n\t\t\t\t\t\t// webdavConnect answers null when the server does not: with an\n\t\t\t\t\t\t// explicit config this is a wrong-address error, not a cancel.\n\t\t\t\t\t\tfail(\n\t\t\t\t\t\t\tnew SelfstoreError(\n\t\t\t\t\t\t\t\t'TARGET_UNAVAILABLE',\n\t\t\t\t\t\t\t\t'The WebDAV server did not answer (URL, credentials or CORS).'\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t);\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tawait inspectAndRoute(gen, t);\n\t\t\t\t})\n\t\t\t\t.catch((e) => {\n\t\t\t\t\tif (gen !== generation) return;\n\t\t\t\t\tfail(e);\n\t\t\t\t});\n\t\t},\n\n\t\tsubmitS3(config: S3Config): void {\n\t\t\tconst { step, busy } = m.snapshot;\n\t\t\tif (busy || step !== 'form') return;\n\t\t\tconst gen = ++generation;\n\t\t\tm.set({ step: 'authorizing', busy: true });\n\t\t\tvoid withDeadline(s3Connect({ kv: host.kv, config }), deadlineMs, 'The S3 endpoint')\n\t\t\t\t.then(async (t) => {\n\t\t\t\t\tif (gen !== generation) return;\n\t\t\t\t\tif (!t) {\n\t\t\t\t\t\t// s3Connect answers null when the endpoint does not: with an\n\t\t\t\t\t\t// explicit config this is a wrong-config error, not a cancel.\n\t\t\t\t\t\tfail(\n\t\t\t\t\t\t\tnew SelfstoreError(\n\t\t\t\t\t\t\t\t'TARGET_UNAVAILABLE',\n\t\t\t\t\t\t\t\t'The S3 endpoint did not answer (endpoint, credentials or CORS).'\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t);\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tawait inspectAndRoute(gen, t);\n\t\t\t\t})\n\t\t\t\t.catch((e) => {\n\t\t\t\t\tif (gen !== generation) return;\n\t\t\t\t\tfail(e);\n\t\t\t\t});\n\t\t},\n\n\t\tsubmitPassword(pw: string): void {\n\t\t\tconst { step, busy } = m.snapshot;\n\t\t\tif (busy || step !== 'password' || !target) return;\n\t\t\tconst gen = generation;\n\t\t\tm.set({ busy: true, passwordError: false });\n\t\t\tvoid (async () => {\n\t\t\t\ttry {\n\t\t\t\t\t// PROVE the password against the real backup while the store is\n\t\t\t\t\t// still untouched: download and trial-read. A wrong password is a\n\t\t\t\t\t// retype, never a locked store or a gate.\n\t\t\t\t\tconst blob = await withDeadline(target!.load(), deadlineMs, 'The destination');\n\t\t\t\t\tif (gen !== generation) return;\n\t\t\t\t\tif (!blob) {\n\t\t\t\t\t\tthrow new SelfstoreError('TARGET_UNAVAILABLE', 'The backup could not be downloaded.');\n\t\t\t\t\t}\n\t\t\t\t\tawait restore(blob).withPassword(pw).read();\n\t\t\t\t\tif (gen !== generation) return;\n\t\t\t\t\tpassword = pw;\n\t\t\t\t\tm.set({ busy: false });\n\t\t\t\t\trouteAfterUnlock(gen);\n\t\t\t\t} catch (e) {\n\t\t\t\t\tif (gen !== generation) return;\n\t\t\t\t\tif (isSelfstoreError(e) && e.code === 'DECRYPT_FAILED') {\n\t\t\t\t\t\tm.set({ busy: false, passwordError: true }); // stay, say so, retype\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tfail(e);\n\t\t\t\t}\n\t\t\t})();\n\t\t},\n\n\t\toverwrite(): void {\n\t\t\tconst { step, busy } = m.snapshot;\n\t\t\tif (busy || step !== 'password' || !target) return;\n\t\t\t// The password is forgotten: stop proving it and START fresh over the\n\t\t\t// protected backup. wipe:true empties local first (fresh sync meta), so\n\t\t\t// this writes a blank new backup - never this device's existing data -\n\t\t\t// and the owner is never stuck behind a lost secret. A blank write is\n\t\t\t// also tiny, so it cannot time out the way pushing a large local store\n\t\t\t// would.\n\t\t\tpassword = null;\n\t\t\tvoid attach(generation, 'replace-remote', 'replaced', true);\n\t\t},\n\n\t\tresolveConflict(how: ConnectResolution): void {\n\t\t\tconst { step, busy } = m.snapshot;\n\t\t\tif (busy || step !== 'conflict') return;\n\t\t\tconst plan = planFor(how);\n\t\t\tvoid attach(generation, plan.strategy, plan.outcome);\n\t\t},\n\n\t\tcancel(): void {\n\t\t\tif (m.snapshot.step === 'connected') return; // done is done\n\t\t\tbackToChoose();\n\t\t},\n\n\t\tretry(): void {\n\t\t\tif (m.snapshot.step !== 'error') return;\n\t\t\tbackToChoose();\n\t\t}\n\t};\n}\n","// shareFlow: the \"who can see this\" panel as a headless machine over an\n// injected ShareEngine - how links travel (Drive public link, relay,\n// mailbox) is the app's business. The flow owns what every such panel gets\n// wrong first: a transient listing failure never blanks it (last-known\n// links stay, flagged stale - a member who saw an empty panel once will\n// revoke something in panic); one operation at a time, so a double-click\n// cannot create two links; removals leave the snapshot only after the\n// engine confirmed them, never optimistically; removing yourself is refused\n// up front (leaving is its own journey, not a row action). Every leg runs\n// under a deadline and reports the stable { code, labelKey, message } shape.\n\nimport type { StoreError } from '../persistence/store';\nimport { makeMachine, toStoreError, withDeadline, type FlowStore } from './machine';\n\nexport type ShareLevel = 'read' | 'write';\n\nexport interface ShareLinkInfo {\n\t/** Engine-scoped stable id (revoke keys on it). */\n\tid: string;\n\t/** The invitation URL to hand out. */\n\turl: string;\n\tlevel: ShareLevel;\n}\n\nexport interface ShareMemberInfo {\n\t/** Engine-scoped stable id (removeMember keys on it). */\n\tid: string;\n\tlabel?: string;\n\t/** This row is the local user. */\n\tself?: boolean;\n\t/** This row owns the share (the app may render a badge, the flow does not care). */\n\towner?: boolean;\n}\n\n/** The app-side port the flow drives. Implementations wrap whatever transport\n * the app shares through; every method may throw (SelfstoreError preferred,\n * anything is normalized). */\nexport interface ShareEngine {\n\t/** The current truth: active links and members. */\n\tlist(): Promise<{ links: ShareLinkInfo[]; members: ShareMemberInfo[] }>;\n\tcreateLink(opts: { level: ShareLevel }): Promise<ShareLinkInfo>;\n\trevokeLink(id: string): Promise<void>;\n\t/** Optional: engines without member management simply omit it. */\n\tremoveMember?(id: string): Promise<void>;\n\t/** Optional: END the whole share in one gesture - every link dead, every\n\t * member out. Engines whose transport has such a move (an invitation to\n\t * kill, a file to re-privatize) expose it; the widget renders the action\n\t * only then. */\n\trevokeAll?(): Promise<void>;\n}\n\nexport type ShareBusy = 'refresh' | 'create' | 'revoke' | 'remove' | 'revoke-all' | null;\n\nexport interface ShareSnapshot {\n\tlinks: readonly ShareLinkInfo[];\n\tmembers: readonly ShareMemberInfo[];\n\t/** The operation in flight, or null. */\n\tbusy: ShareBusy;\n\t/** The last refresh failed: the lists shown are the last-known good ones. */\n\tstale: boolean;\n\t/** The last operation's problem (the lists are never cleared by an error). */\n\terror: StoreError | null;\n\t/** True once the engine offers removeMember (render the row action at all). */\n\tcanRemoveMembers: boolean;\n\t/** True once the engine offers revokeAll (render the end-the-share action). */\n\tcanRevokeAll: boolean;\n}\n\nexport interface ShareFlow extends FlowStore<ShareSnapshot> {\n\t/** Re-list from the engine. Failure keeps the last-known lists (stale). */\n\trefresh(): Promise<void>;\n\t/** Create an invitation link. Resolves to it, or null when refused (an\n\t * operation was already in flight). */\n\tcreateLink(opts: { level: ShareLevel }): Promise<ShareLinkInfo | null>;\n\t/** Revoke a link. The link leaves the snapshot only once the engine\n\t * confirmed. Resolves to whether it did. */\n\trevokeLink(id: string): Promise<boolean>;\n\t/** Remove a member (never yourself - refused up front). Resolves to whether\n\t * the engine confirmed. */\n\tremoveMember(id: string): Promise<boolean>;\n\t/** End the whole share: every link and member leaves the snapshot only\n\t * once the engine confirmed. Resolves to whether it did. */\n\trevokeAll(): Promise<boolean>;\n}\n\nconst DEFAULT_DEADLINE_MS = 30_000;\n\nexport function shareFlow(engine: ShareEngine, options: { deadlineMs?: number } = {}): ShareFlow {\n\tconst deadlineMs = options.deadlineMs ?? DEFAULT_DEADLINE_MS;\n\n\tconst m = makeMachine<ShareSnapshot>({\n\t\tlinks: [],\n\t\tmembers: [],\n\t\tbusy: 'refresh',\n\t\tstale: false,\n\t\terror: null,\n\t\tcanRemoveMembers: typeof engine.removeMember === 'function',\n\t\tcanRevokeAll: typeof engine.revokeAll === 'function'\n\t});\n\n\t/** Serialize the operations: one at a time, the others answer \"refused\". */\n\tfunction begin(op: Exclude<ShareBusy, null>): boolean {\n\t\tif (m.snapshot.busy !== null && !(op === 'refresh' && m.snapshot.busy === 'refresh')) {\n\t\t\treturn false;\n\t\t}\n\t\tm.set({ busy: op });\n\t\treturn true;\n\t}\n\n\tasync function doRefresh(): Promise<void> {\n\t\ttry {\n\t\t\tconst { links, members } = await withDeadline(engine.list(), deadlineMs, 'The share listing');\n\t\t\tm.set({ links, members, busy: null, stale: false, error: null });\n\t\t} catch (e) {\n\t\t\t// Keep the last-known lists: a hiccup must never paint the share as\n\t\t\t// gone. Just say the view may be behind.\n\t\t\tm.set({ busy: null, stale: true, error: toStoreError(e) });\n\t\t}\n\t}\n\n\tconst flow: ShareFlow = {\n\t\tget snapshot() {\n\t\t\treturn m.snapshot;\n\t\t},\n\t\tsubscribe: m.subscribe,\n\n\t\tasync refresh(): Promise<void> {\n\t\t\tif (m.snapshot.busy !== null && m.snapshot.busy !== 'refresh') return;\n\t\t\tm.set({ busy: 'refresh' });\n\t\t\tawait doRefresh();\n\t\t},\n\n\t\tasync createLink(opts): Promise<ShareLinkInfo | null> {\n\t\t\tif (!begin('create')) return null;\n\t\t\ttry {\n\t\t\t\tconst link = await withDeadline(engine.createLink(opts), deadlineMs, 'Creating the link');\n\t\t\t\tm.set({ links: [...m.snapshot.links, link], busy: null, error: null });\n\t\t\t\treturn link;\n\t\t\t} catch (e) {\n\t\t\t\tm.set({ busy: null, error: toStoreError(e) });\n\t\t\t\treturn null;\n\t\t\t}\n\t\t},\n\n\t\tasync revokeLink(id: string): Promise<boolean> {\n\t\t\tif (!begin('revoke')) return false;\n\t\t\ttry {\n\t\t\t\tawait withDeadline(engine.revokeLink(id), deadlineMs, 'Revoking the link');\n\t\t\t\t// Confirmed: NOW it leaves the snapshot.\n\t\t\t\tm.set({ links: m.snapshot.links.filter((l) => l.id !== id), busy: null, error: null });\n\t\t\t\treturn true;\n\t\t\t} catch (e) {\n\t\t\t\tm.set({ busy: null, error: toStoreError(e) });\n\t\t\t\treturn false;\n\t\t\t}\n\t\t},\n\n\t\tasync removeMember(id: string): Promise<boolean> {\n\t\t\tconst remove = engine.removeMember?.bind(engine);\n\t\t\tif (!remove) {\n\t\t\t\tthrow new TypeError('shareFlow: this engine does not manage members (no removeMember).');\n\t\t\t}\n\t\t\tconst row = m.snapshot.members.find((mb) => mb.id === id);\n\t\t\tif (row?.self) {\n\t\t\t\tthrow new TypeError(\n\t\t\t\t\t'shareFlow: refusing to remove yourself - leaving a share is its own journey, not a row action.'\n\t\t\t\t);\n\t\t\t}\n\t\t\tif (!begin('remove')) return false;\n\t\t\ttry {\n\t\t\t\tawait withDeadline(remove(id), deadlineMs, 'Removing the member');\n\t\t\t\tm.set({\n\t\t\t\t\tmembers: m.snapshot.members.filter((mb) => mb.id !== id),\n\t\t\t\t\tbusy: null,\n\t\t\t\t\terror: null\n\t\t\t\t});\n\t\t\t\treturn true;\n\t\t\t} catch (e) {\n\t\t\t\tm.set({ busy: null, error: toStoreError(e) });\n\t\t\t\treturn false;\n\t\t\t}\n\t\t},\n\n\t\tasync revokeAll(): Promise<boolean> {\n\t\t\tconst revoke = engine.revokeAll?.bind(engine);\n\t\t\tif (!revoke) {\n\t\t\t\tthrow new TypeError('shareFlow: this engine does not end the share (no revokeAll).');\n\t\t\t}\n\t\t\tif (!begin('revoke-all')) return false;\n\t\t\ttry {\n\t\t\t\tawait withDeadline(revoke(), deadlineMs, 'Ending the share');\n\t\t\t\t// Confirmed: the share is over, every link and member with it.\n\t\t\t\tm.set({ links: [], members: [], busy: null, error: null });\n\t\t\t\treturn true;\n\t\t\t} catch (e) {\n\t\t\t\tm.set({ busy: null, error: toStoreError(e) });\n\t\t\t\treturn false;\n\t\t\t}\n\t\t}\n\t};\n\n\t// The panel opens on the truth: list right away (failures land as stale).\n\tvoid doRefresh();\n\n\treturn flow;\n}\n","// joinFlow: opening an invitation link, as a headless machine over an\n// injected JoinEngine. Preview first, join on an explicit yes. The flow\n// holds the link itself, so a re-render, a login roundtrip or an account\n// switch cannot lose the invitation out from under the user. Joining is\n// single-flight. Mismatch (\"this device already follows another share\") and\n// no-invite (\"spent, or not for this account\") are outcomes with their own\n// steps, not generic errors - each has a different way out. Switching\n// accounts re-previews under the new account. A cancelled or superseded leg\n// cannot resurrect the flow, and every network leg runs under a deadline.\n\nimport type { StoreError } from '../persistence/store';\nimport { makeMachine, toStoreError, withDeadline, type FlowStore } from './machine';\nimport type { ShareLevel } from './share';\n\n/** What the user is about to join - enough to render an honest question. */\nexport interface JoinPreview {\n\t/** The share's display name, when the link carries one. */\n\tlabel?: string;\n\t/** Who shared it (a name, an email - whatever the engine knows). */\n\tfrom?: string;\n\tlevel?: ShareLevel;\n}\n\n/** How joining ended, other than plain failure:\n * 'joined' - this device now follows the share.\n * 'mismatch' - this device already follows a DIFFERENT share: joining a\n * second one is refused (leave the first, or use another\n * device/profile).\n * 'no-invite' - the invitation is spent, expired, or aimed at another\n * account (switching accounts may be the fix). */\nexport type JoinOutcome = 'joined' | 'mismatch' | 'no-invite';\n\n/** The app-side port: how a link is read and honoured on this transport. */\nexport interface JoinEngine {\n\t/** Read-only look at the invitation. Must change NOTHING. */\n\tpreview(link: string): Promise<JoinPreview>;\n\t/** Consume the invitation and wire the share. Resolve null when the user\n\t * backed out of a popup the engine had to open (an account chooser, a\n\t * consent screen): the flow returns to 'ready' silently - backing out of\n\t * a human gesture is not an error, here as everywhere in these flows. */\n\tjoin(link: string): Promise<JoinOutcome | null>;\n\t/** Optional: re-run the account chooser (the invite may belong to another\n\t * account). The flow re-previews after it. */\n\tswitchAccount?(): Promise<void>;\n}\n\nexport type JoinStep =\n\t'previewing' | 'ready' | 'joining' | 'joined' | 'mismatch' | 'no-invite' | 'error';\n\nexport interface JoinSnapshot {\n\t/** The invitation this flow was opened with - held here so the UI can\n\t * always find it again. */\n\tlink: string;\n\tstep: JoinStep;\n\t/** Set once previewed (kept through errors, so retry re-renders context). */\n\tpreview: JoinPreview | null;\n\t/** The engine offers an account switch. */\n\tcanSwitchAccount: boolean;\n\tbusy: boolean;\n\t/** Set when step === 'error': show `labelKey`, log `message`. */\n\terror: StoreError | null;\n}\n\nexport interface JoinFlow extends FlowStore<JoinSnapshot> {\n\t/** Say yes: consume the invitation. Only from 'ready'. */\n\taccept(): void;\n\t/** Re-run the account chooser, then preview again under the new account.\n\t * Available from 'ready', 'no-invite' and 'error' when the engine offers it. */\n\tswitchAccount(): void;\n\t/** From 'error': preview again and resume the journey. */\n\tretry(): void;\n}\n\nconst DEFAULT_DEADLINE_MS = 30_000;\n\nexport function joinFlow(\n\tlink: string,\n\tengine: JoinEngine,\n\toptions: { deadlineMs?: number } = {}\n): JoinFlow {\n\tconst deadlineMs = options.deadlineMs ?? DEFAULT_DEADLINE_MS;\n\n\tconst m = makeMachine<JoinSnapshot>({\n\t\tlink,\n\t\tstep: 'previewing',\n\t\tpreview: null,\n\t\tcanSwitchAccount: typeof engine.switchAccount === 'function',\n\t\tbusy: true,\n\t\terror: null\n\t});\n\n\tlet generation = 0;\n\n\tfunction preview(): void {\n\t\tconst gen = ++generation;\n\t\tm.set({ step: 'previewing', busy: true, error: null });\n\t\tvoid withDeadline(engine.preview(link), deadlineMs, 'The invitation')\n\t\t\t.then((p) => {\n\t\t\t\tif (gen !== generation) return;\n\t\t\t\tm.set({ step: 'ready', preview: p, busy: false });\n\t\t\t})\n\t\t\t.catch((e) => {\n\t\t\t\tif (gen !== generation) return;\n\t\t\t\tm.set({ step: 'error', busy: false, error: toStoreError(e) });\n\t\t\t});\n\t}\n\n\tpreview();\n\n\treturn {\n\t\tget snapshot() {\n\t\t\treturn m.snapshot;\n\t\t},\n\t\tsubscribe: m.subscribe,\n\n\t\taccept(): void {\n\t\t\tconst { step, busy } = m.snapshot;\n\t\t\tif (busy || step !== 'ready') return; // single-flight: one join per yes\n\t\t\tconst gen = ++generation;\n\t\t\tm.set({ step: 'joining', busy: true, error: null });\n\t\t\tvoid withDeadline(engine.join(link), deadlineMs, 'Joining')\n\t\t\t\t.then((outcome) => {\n\t\t\t\t\tif (gen !== generation) return;\n\t\t\t\t\tif (outcome === null) {\n\t\t\t\t\t\t// The user backed out of the engine's popup: the question is\n\t\t\t\t\t\t// still live, the preview still good - just ask again.\n\t\t\t\t\t\tm.set({ step: 'ready', busy: false });\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tm.set({ step: outcome, busy: false });\n\t\t\t\t})\n\t\t\t\t.catch((e) => {\n\t\t\t\t\tif (gen !== generation) return;\n\t\t\t\t\tm.set({ step: 'error', busy: false, error: toStoreError(e) });\n\t\t\t\t});\n\t\t},\n\n\t\tswitchAccount(): void {\n\t\t\tconst switcher = engine.switchAccount?.bind(engine);\n\t\t\tif (!switcher) return;\n\t\t\tconst { step, busy } = m.snapshot;\n\t\t\tif (busy || (step !== 'ready' && step !== 'no-invite' && step !== 'error')) return;\n\t\t\tconst gen = ++generation;\n\t\t\tm.set({ busy: true, error: null });\n\t\t\tvoid switcher()\n\t\t\t\t.then(() => {\n\t\t\t\t\tif (gen !== generation) return;\n\t\t\t\t\tpreview(); // the other account may hold the right invite: look again\n\t\t\t\t})\n\t\t\t\t.catch((e) => {\n\t\t\t\t\tif (gen !== generation) return;\n\t\t\t\t\tm.set({ step: 'error', busy: false, error: toStoreError(e) });\n\t\t\t\t});\n\t\t},\n\n\t\tretry(): void {\n\t\t\tif (m.snapshot.step !== 'error') return;\n\t\t\tpreview();\n\t\t}\n\t};\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/flows/connect.ts","../src/flows/share.ts","../src/flows/join.ts"],"names":["connect","config","gen","DEFAULT_DEADLINE_MS"],"mappings":"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","file":"chunk-BRH6LZKM.js","sourcesContent":["// connectFlow: the \"where does my data live\" journey as a headless state\n// machine. It owns ordering and failure rules that took a series of\n// production hotfixes to get right, each covered by a test:\n//\n// choose -> (form) -> authorizing -> (password) -> (conflict) -> connected\n// \\-> error\n//\n// One popup per gesture (re-entry ignored while a leg is in flight), and a\n// cancelled popup is not an error - back to 'choose', silently. An encrypted\n// backup's password is proven by a trial read while the store is untouched,\n// so a wrong password can never half-connect (deferUnlock opts out: adopt\n// locked, unlock where the host chooses). overwrite() is the forgotten-\n// password escape: local wiped first, tiny blank write. Merge is the default\n// reconciliation; the destructive choices hide behind 'conflict', which only\n// appears when the app declared local data worth asking about. Network legs\n// run under a deadline; human steps never do. A cancelled journey stays\n// dead - late resolutions are discarded (generation counter).\n\nimport type { LocalStore, StoreError } from '../persistence/store';\nimport type { BackupTarget } from '../persistence/target';\nimport type { KV } from '../persistence/cache';\nimport { restore } from '../selfstore/fluent';\nimport { SelfstoreError, isSelfstoreError } from '../selfstore/errors';\nimport { connect as driveConnect, type DriveAuth } from '../persistence/targets/drive';\nimport {\n\tconnect as fileConnect,\n\tisSupported as fileIsSupported,\n\topenExisting as fileOpenExisting\n} from '../persistence/targets/file';\nimport { connect as webdavConnect, type WebdavConfig } from '../persistence/targets/webdav';\nimport { connect as s3Connect, type S3Config } from '../persistence/targets/s3';\nimport { makeMachine, toStoreError, withDeadline, type FlowStore } from './machine';\n\nexport type ConnectKind = 'drive' | 'file' | 'webdav' | 's3';\n\n/** Produce a connected BackupTarget, or null when the user cancelled the\n * gesture (consent refused, picker closed). Custom connectors slot custom\n * targets - or test fakes - into the same journey. */\nexport type Connector = () => Promise<BackupTarget | null>;\n\n/** Which destinations the flow offers, and how to open each one. The offer\n * renders in this OBJECT's insertion order: the host decides which\n * destination leads by how it writes the object, not by a ranking here. */\nexport interface ConnectTargets {\n\t/** Google Drive: the auth strategy (e.g. gisDriveAuth), or a custom connector. */\n\tdrive?: DriveAuth | Connector;\n\t/** Local file via the browser's picker (true), or a custom connector. On\n\t * browsers without the File System Access API the flow degrades to the\n\t * manual download mode by itself. The object form offers TWO gestures on\n\t * the same destination: `create` (save picker: start or overwrite a backup\n\t * file) and `open` (open picker: adopt an EXISTING backup file - it then\n\t * goes through the same inspect/password/resolution journey). */\n\tfile?: true | Connector | { create?: true | Connector; open?: true | Connector };\n\t/** WebDAV: true renders a 'form' step and expects submitWebdav(config);\n\t * a fixed WebdavConfig skips the form; or a custom connector. */\n\twebdav?: true | WebdavConfig | Connector;\n\t/** S3-compatible bucket (Amazon S3, R2, B2, MinIO): true renders a 'form'\n\t * step and expects submitS3(config); a fixed S3Config skips the form; or a\n\t * custom connector. Offer it alongside `webdav` and the widget groups both\n\t * under one \"your server\" entry with a WebDAV/S3 toggle. */\n\ts3?: true | S3Config | Connector;\n}\n\n/** What the flows need from the store. The simple store exposes this as\n * `store.flowHost`; an advanced-store app builds it by hand. */\nexport interface FlowHost {\n\tengine: LocalStore;\n\tkv: KV;\n\t/** Backup file name on the destination (the simple store uses `app.zip`). */\n\tbackupName: string;\n}\n\n/** What every flow and widget accepts as \"the store\": the simple facade, which\n * carries its ports on `flowHost`, or a hand-built host. */\nexport type StoreLike = FlowHost | { flowHost: FlowHost };\n\nexport interface ConnectFlowOptions {\n\t/** Declare when this device holds data worth a question: only then does an\n\t * existing backup raise the 'conflict' step with the destructive choices.\n\t * Default: never - the flow applies defaultResolution. */\n\thasLocalData?: () => boolean;\n\t/** How to reconcile with a destination that already holds a backup when the\n\t * conflict step is not raised. Default 'merge' - the only choice that\n\t * loses nothing on either side. 'resume' adopts the backup (the\n\t * destination wins; this device's working copy is replaced), 'replace'\n\t * overwrites the backup with this device. An encrypted backup still asks\n\t * for its password first, whatever the resolution; an empty destination\n\t * is not concerned (this device's data simply becomes its content). */\n\tdefaultResolution?: ConnectResolution;\n\t/** Skip the password step on an encrypted backup: adopt it AS is (resume)\n\t * and complete with the store locked - nothing readable, nothing\n\t * clobbered - for the host to unlock on its own surface (engine.unlock)\n\t * after the connect. Adoption is forced whatever defaultResolution says:\n\t * without the proven key a merge is impossible and a replace would\n\t * destroy a backup no one proved they own. Hosts that let a device\n\t * holding real local data connect should keep the password-first\n\t * journey - adoption replaces the working copy. */\n\tdeferUnlock?: boolean;\n\t/**\n\t * A secret the host already holds (an app passphrase, a derived key), used\n\t * to encrypt the backup when this journey creates or adopts one that is NOT\n\t * already encrypted - an empty destination, or a plaintext file.\n\t *\n\t * Two things need it. A store built with `requireEncryption` refuses a\n\t * passwordless attach outright (ENCRYPTION_REQUIRED), so without this the\n\t * connect journey cannot complete at all. And even without that setting, a\n\t * fresh backup is better born encrypted than written in clear and protected\n\t * on a second write, which would leave cleartext on the destination in\n\t * between.\n\t *\n\t * Never used against a destination whose backup is already encrypted: that\n\t * one is proven through the password step (or adopted locked under\n\t * `deferUnlock`), so a host secret that happens to differ can neither fail\n\t * the attach nor overwrite what it cannot read. Resolved lazily, so the\n\t * secret is only read when a connect actually needs it. Must satisfy the\n\t * store's password policy.\n\t */\n\tpassword?: string | (() => string | Promise<string>);\n\t/** Deadline for each network leg (inspect, trial-read, attach), ms. */\n\tdeadlineMs?: number;\n}\n\nexport type ConnectStep =\n\t'choose' | 'form' | 'authorizing' | 'password' | 'conflict' | 'connected' | 'error';\n\n/** How to reconcile with a destination that already holds a backup. */\nexport type ConnectResolution = 'merge' | 'resume' | 'replace';\n\n/** What connecting did. 'merged': both sides folded. 'started': empty\n * destination now holds this device's data. 'resumed': the backup won,\n * this device adopted it. 'replaced': this device won, the backup was\n * overwritten. 'manual': degraded download-on-demand file mode. */\nexport type ConnectFlowOutcome = 'merged' | 'started' | 'resumed' | 'replaced' | 'manual';\n\nexport interface ConnectSnapshot {\n\tstep: ConnectStep;\n\t/** The destinations to offer, in the order given. */\n\tkinds: readonly ConnectKind[];\n\t/** The destination being connected (null while choosing). */\n\tkind: ConnectKind | null;\n\t/** Set when step === 'connected'. */\n\toutcome: ConnectFlowOutcome | null;\n\t/** Known after inspection: the destination already holds a backup. */\n\thasBackup: boolean;\n\t/** ...and that backup is encrypted. */\n\tencrypted: boolean;\n\t/** The last submitPassword() was wrong (stay on 'password', say so). */\n\tpasswordError: boolean;\n\t/** An async leg is in flight (render a spinner, disable the actions). */\n\tbusy: boolean;\n\t/** Set when step === 'error': show `labelKey`, log `message`. */\n\terror: StoreError | null;\n}\n\nexport interface ConnectFlow extends FlowStore<ConnectSnapshot> {\n\t/** Pick a destination. Call it inside the user's click: the consent popup /\n\t * file picker opens synchronously from the gesture. Ignored while a leg is\n\t * already in flight (one popup per gesture). For a `file` target declared\n\t * with the object form, `variant` picks the gesture: 'create' (default,\n\t * save picker) or 'open' (adopt an existing backup file). */\n\tchoose(kind: ConnectKind, variant?: 'create' | 'open'): void;\n\t/** 'form' step (webdav: true): submit the server config. */\n\tsubmitWebdav(config: WebdavConfig): void;\n\t/** 'form' step (s3: true): submit the bucket config. */\n\tsubmitS3(config: S3Config): void;\n\t/** 'password' step: prove the backup's password. Wrong password sets\n\t * passwordError and stays here; nothing is attached either way. */\n\tsubmitPassword(password: string): void;\n\t/** 'password' step escape hatch: give up on a forgotten password and start a\n\t * fresh backup over the protected one. The encrypted backup is overwritten\n\t * by this device's data (possibly empty) and lost for good - the only way\n\t * out when the password cannot be recovered. */\n\toverwrite(): void;\n\t/** 'conflict' step: pick the reconciliation. */\n\tresolveConflict(how: ConnectResolution): void;\n\t/** Abandon the journey in progress: back to 'choose'. A late resolution of\n\t * the abandoned leg is discarded. */\n\tcancel(): void;\n\t/** 'error' step: back to 'choose' to try again. */\n\tretry(): void;\n}\n\nconst DEFAULT_DEADLINE_MS = 30_000;\n\n/** A resolution's attach plan: which strategy runs, which outcome it names. */\nfunction planFor(how: ConnectResolution): {\n\tstrategy: 'merge' | 'replace-local' | 'replace-remote';\n\toutcome: ConnectFlowOutcome;\n} {\n\tif (how === 'resume') return { strategy: 'replace-local', outcome: 'resumed' };\n\tif (how === 'replace') return { strategy: 'replace-remote', outcome: 'replaced' };\n\treturn { strategy: 'merge', outcome: 'merged' };\n}\n\nexport function connectFlow(\n\tstore: StoreLike,\n\ttargets: ConnectTargets,\n\toptions: ConnectFlowOptions = {}\n): ConnectFlow {\n\tconst host = 'flowHost' in store ? store.flowHost : store;\n\tconst deadlineMs = options.deadlineMs ?? DEFAULT_DEADLINE_MS;\n\tconst kinds = (Object.keys(targets) as ConnectKind[]).filter(\n\t\t(k) => (k === 'drive' || k === 'file' || k === 'webdav' || k === 's3') && targets[k] != null\n\t);\n\n\tconst m = makeMachine<ConnectSnapshot>({\n\t\tstep: 'choose',\n\t\tkinds,\n\t\tkind: null,\n\t\toutcome: null,\n\t\thasBackup: false,\n\t\tencrypted: false,\n\t\tpasswordError: false,\n\t\tbusy: false,\n\t\terror: null\n\t});\n\n\t// The journey's working set. `generation` invalidates in-flight legs on\n\t// cancel(): whoever resolves late compares its captured generation and\n\t// drops its result instead of resurrecting an abandoned journey.\n\tlet generation = 0;\n\tlet target: BackupTarget | null = null;\n\tlet password: string | null = null;\n\t// Set at inspection: the destination already holds an ENCRYPTED backup, so\n\t// its key is the one that matters and the host secret must stay out of it.\n\tlet remoteEncrypted = false;\n\n\tconst backToChoose = (): void => {\n\t\tgeneration++;\n\t\ttarget = null;\n\t\tpassword = null;\n\t\tremoteEncrypted = false;\n\t\tm.set({\n\t\t\tstep: 'choose',\n\t\t\tkind: null,\n\t\t\toutcome: null,\n\t\t\thasBackup: false,\n\t\t\tencrypted: false,\n\t\t\tpasswordError: false,\n\t\t\tbusy: false,\n\t\t\terror: null\n\t\t});\n\t};\n\n\tconst fail = (e: unknown): void => {\n\t\tpassword = null;\n\t\tm.set({ step: 'error', busy: false, error: toStoreError(e) });\n\t};\n\n\t/** The host's own secret, resolved on demand (see options.password). */\n\tconst hostSecret = async (): Promise<string | null> => {\n\t\tconst supplied = options.password;\n\t\tif (supplied === undefined) return null;\n\t\treturn typeof supplied === 'function' ? await supplied() : supplied;\n\t};\n\n\tfunction connectorFor(\n\t\tkind: ConnectKind,\n\t\tvariant: 'create' | 'open' = 'create'\n\t): Connector | null {\n\t\tconst spec = targets[kind];\n\t\tif (spec == null) return null;\n\t\tif (typeof spec === 'function') return spec;\n\t\tif (kind === 'drive') {\n\t\t\tconst auth = spec as DriveAuth;\n\t\t\treturn () => driveConnect({ auth, kv: host.kv, fileName: host.backupName });\n\t\t}\n\t\tif (kind === 'file') {\n\t\t\t// Object form: each gesture has its own spec ('open' has no built-in\n\t\t\t// meaning for other kinds, so it resolves here only).\n\t\t\tif (typeof spec === 'object' && !('url' in spec)) {\n\t\t\t\tconst sub = (spec as { create?: true | Connector; open?: true | Connector })[variant];\n\t\t\t\tif (sub == null) return null;\n\t\t\t\tif (typeof sub === 'function') return sub;\n\t\t\t\treturn variant === 'open'\n\t\t\t\t\t? () => fileOpenExisting({ kv: host.kv })\n\t\t\t\t\t: () => fileConnect({ kv: host.kv, fileName: host.backupName });\n\t\t\t}\n\t\t\treturn () => fileConnect({ kv: host.kv, fileName: host.backupName });\n\t\t}\n\t\tif (kind === 's3') {\n\t\t\tconst config = spec as S3Config; // 's3: true' goes through the form step\n\t\t\treturn () => s3Connect({ kv: host.kv, config });\n\t\t}\n\t\tconst config = spec as WebdavConfig; // 'webdav: true' goes through the form step\n\t\treturn () => webdavConnect({ kv: host.kv, config });\n\t}\n\n\t/** After the target is in hand: inspect, then route to password / conflict /\n\t * attach. Everything here is network - deadline-bounded, generation-checked. */\n\tasync function inspectAndRoute(gen: number, t: BackupTarget): Promise<void> {\n\t\tconst info = await withDeadline(host.engine.inspectTarget(t), deadlineMs, 'The destination');\n\t\tif (gen !== generation) return;\n\t\ttarget = t;\n\t\tremoteEncrypted = info.encrypted;\n\t\tm.set({ hasBackup: info.hasBackup, encrypted: info.encrypted });\n\t\tif (!info.hasBackup) {\n\t\t\t// Empty destination: this device's data becomes its content. Nothing\n\t\t\t// to ask, nothing to lose.\n\t\t\tawait attach(gen, 'replace-remote', 'started');\n\t\t\treturn;\n\t\t}\n\t\tif (info.encrypted) {\n\t\t\tif (options.deferUnlock) {\n\t\t\t\t// The host owns the unlock surface: adopt the backup as it stands.\n\t\t\t\t// A null password on an encrypted file lands the store locked -\n\t\t\t\t// nothing readable, nothing clobbered - until engine.unlock()\n\t\t\t\t// proves the password wherever the host asks for it. The conflict\n\t\t\t\t// step is moot here: without the key, merge cannot read the remote\n\t\t\t\t// and replace would destroy a backup no one proved they own.\n\t\t\t\tawait attach(gen, 'replace-local', 'resumed');\n\t\t\t\treturn;\n\t\t\t}\n\t\t\t// Ask before touching anything. The password gates every resolution,\n\t\t\t// including 'replace': overwriting an encrypted backup demands proof\n\t\t\t// of ownership, or a typo away from someone's data.\n\t\t\tm.set({ step: 'password', busy: false, passwordError: false });\n\t\t\treturn;\n\t\t}\n\t\trouteAfterUnlock(gen);\n\t}\n\n\t/** Password proven (or none needed): either ask the conflict question, or\n\t * apply the default resolution - 'merge' unless the host chose otherwise,\n\t * the only default that loses nothing on either side. */\n\tfunction routeAfterUnlock(gen: number): void {\n\t\tif (gen !== generation) return;\n\t\tif (options.hasLocalData?.() === true) {\n\t\t\tm.set({ step: 'conflict', busy: false });\n\t\t\treturn;\n\t\t}\n\t\tconst plan = planFor(options.defaultResolution ?? 'merge');\n\t\tvoid attach(gen, plan.strategy, plan.outcome);\n\t}\n\n\tasync function attach(\n\t\tgen: number,\n\t\tstrategy: 'merge' | 'replace-local' | 'replace-remote',\n\t\toutcome: ConnectFlowOutcome,\n\t\twipe = false\n\t): Promise<void> {\n\t\tif (gen !== generation || !target) return;\n\t\tm.set({ busy: true });\n\t\ttry {\n\t\t\t// A backup that is already encrypted carries its own key: it was proven\n\t\t\t// at the password step, or deliberately left locked. Everywhere else -\n\t\t\t// an empty destination, a plaintext one - the host secret (when it\n\t\t\t// offers one) makes the backup encrypted from its very first write.\n\t\t\tconst secret = password ?? (remoteEncrypted ? null : await hostSecret());\n\t\t\tif (gen !== generation) return;\n\t\t\tawait withDeadline(\n\t\t\t\thost.engine.attachTarget(target, { password: secret, strategy, wipe }),\n\t\t\t\tdeadlineMs,\n\t\t\t\t'The destination'\n\t\t\t);\n\t\t\tif (gen !== generation) return;\n\t\t\tpassword = null;\n\t\t\tm.set({ step: 'connected', outcome, busy: false, error: null });\n\t\t} catch (e) {\n\t\t\tif (gen !== generation) return;\n\t\t\tfail(e);\n\t\t}\n\t}\n\n\treturn {\n\t\tget snapshot() {\n\t\t\treturn m.snapshot;\n\t\t},\n\t\tsubscribe: m.subscribe,\n\n\t\tchoose(kind: ConnectKind, variant: 'create' | 'open' = 'create'): void {\n\t\t\tconst { step, busy } = m.snapshot;\n\t\t\tif (busy || (step !== 'choose' && step !== 'error')) return; // one popup per gesture\n\t\t\tconst connector = connectorFor(kind, variant);\n\t\t\tif (!connector) return;\n\t\t\tif (\n\t\t\t\t(kind === 'webdav' && targets.webdav === true) ||\n\t\t\t\t(kind === 's3' && targets.s3 === true)\n\t\t\t) {\n\t\t\t\tm.set({ step: 'form', kind, error: null });\n\t\t\t\treturn;\n\t\t\t}\n\t\t\t// Degraded manual mode only makes sense for CREATING a backup through\n\t\t\t// the built-in save picker: adopting an existing file needs the real\n\t\t\t// picker, and a custom connector owns its own fallback.\n\t\t\tconst builtinCreate =\n\t\t\t\ttargets.file === true ||\n\t\t\t\t(typeof targets.file === 'object' &&\n\t\t\t\t\t(targets.file as { create?: unknown }).create !== undefined &&\n\t\t\t\t\ttypeof (targets.file as { create?: unknown }).create !== 'function');\n\t\t\tif (kind === 'file' && variant === 'create' && builtinCreate && !fileIsSupported()) {\n\t\t\t\t// No picker on this browser: degrade to download-on-demand instead of\n\t\t\t\t// a dead button.\n\t\t\t\tconst gen = ++generation;\n\t\t\t\tm.set({ step: 'authorizing', kind, busy: true, error: null });\n\t\t\t\tvoid host.engine\n\t\t\t\t\t.setManualFile()\n\t\t\t\t\t.then(() => {\n\t\t\t\t\t\tif (gen !== generation) return;\n\t\t\t\t\t\tm.set({ step: 'connected', outcome: 'manual', busy: false });\n\t\t\t\t\t})\n\t\t\t\t\t.catch((e) => {\n\t\t\t\t\t\tif (gen !== generation) return;\n\t\t\t\t\t\tfail(e);\n\t\t\t\t\t});\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst gen = ++generation;\n\t\t\tm.set({ step: 'authorizing', kind, busy: true, error: null });\n\t\t\t// The connector runs the human gesture (popup, picker): no deadline on\n\t\t\t// people. The network legs after it are bounded in inspectAndRoute.\n\t\t\tvoid connector()\n\t\t\t\t.then(async (t) => {\n\t\t\t\t\tif (gen !== generation) return;\n\t\t\t\t\tif (!t) {\n\t\t\t\t\t\t// Drive/file/custom answer null for a cancelled popup or picker:\n\t\t\t\t\t\t// not an error, no message. A FIXED webdav/s3 config answering\n\t\t\t\t\t\t// null means the server did not: that one is a retryable error.\n\t\t\t\t\t\tif (kind === 'webdav' || kind === 's3') {\n\t\t\t\t\t\t\tfail(\n\t\t\t\t\t\t\t\tnew SelfstoreError(\n\t\t\t\t\t\t\t\t\t'TARGET_UNAVAILABLE',\n\t\t\t\t\t\t\t\t\t'The server did not answer (address, credentials or CORS).'\n\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tbackToChoose();\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tawait inspectAndRoute(gen, t);\n\t\t\t\t})\n\t\t\t\t.catch((e) => {\n\t\t\t\t\tif (gen !== generation) return;\n\t\t\t\t\tfail(e);\n\t\t\t\t});\n\t\t},\n\n\t\tsubmitWebdav(config: WebdavConfig): void {\n\t\t\tconst { step, busy } = m.snapshot;\n\t\t\tif (busy || step !== 'form') return;\n\t\t\tconst gen = ++generation;\n\t\t\tm.set({ step: 'authorizing', busy: true });\n\t\t\tvoid withDeadline(webdavConnect({ kv: host.kv, config }), deadlineMs, 'The WebDAV server')\n\t\t\t\t.then(async (t) => {\n\t\t\t\t\tif (gen !== generation) return;\n\t\t\t\t\tif (!t) {\n\t\t\t\t\t\t// webdavConnect answers null when the server does not: with an\n\t\t\t\t\t\t// explicit config this is a wrong-address error, not a cancel.\n\t\t\t\t\t\tfail(\n\t\t\t\t\t\t\tnew SelfstoreError(\n\t\t\t\t\t\t\t\t'TARGET_UNAVAILABLE',\n\t\t\t\t\t\t\t\t'The WebDAV server did not answer (URL, credentials or CORS).'\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t);\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tawait inspectAndRoute(gen, t);\n\t\t\t\t})\n\t\t\t\t.catch((e) => {\n\t\t\t\t\tif (gen !== generation) return;\n\t\t\t\t\tfail(e);\n\t\t\t\t});\n\t\t},\n\n\t\tsubmitS3(config: S3Config): void {\n\t\t\tconst { step, busy } = m.snapshot;\n\t\t\tif (busy || step !== 'form') return;\n\t\t\tconst gen = ++generation;\n\t\t\tm.set({ step: 'authorizing', busy: true });\n\t\t\tvoid withDeadline(s3Connect({ kv: host.kv, config }), deadlineMs, 'The S3 endpoint')\n\t\t\t\t.then(async (t) => {\n\t\t\t\t\tif (gen !== generation) return;\n\t\t\t\t\tif (!t) {\n\t\t\t\t\t\t// s3Connect answers null when the endpoint does not: with an\n\t\t\t\t\t\t// explicit config this is a wrong-config error, not a cancel.\n\t\t\t\t\t\tfail(\n\t\t\t\t\t\t\tnew SelfstoreError(\n\t\t\t\t\t\t\t\t'TARGET_UNAVAILABLE',\n\t\t\t\t\t\t\t\t'The S3 endpoint did not answer (endpoint, credentials or CORS).'\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t);\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tawait inspectAndRoute(gen, t);\n\t\t\t\t})\n\t\t\t\t.catch((e) => {\n\t\t\t\t\tif (gen !== generation) return;\n\t\t\t\t\tfail(e);\n\t\t\t\t});\n\t\t},\n\n\t\tsubmitPassword(pw: string): void {\n\t\t\tconst { step, busy } = m.snapshot;\n\t\t\tif (busy || step !== 'password' || !target) return;\n\t\t\tconst gen = generation;\n\t\t\tm.set({ busy: true, passwordError: false });\n\t\t\tvoid (async () => {\n\t\t\t\ttry {\n\t\t\t\t\t// PROVE the password against the real backup while the store is\n\t\t\t\t\t// still untouched: download and trial-read. A wrong password is a\n\t\t\t\t\t// retype, never a locked store or a gate.\n\t\t\t\t\tconst blob = await withDeadline(target!.load(), deadlineMs, 'The destination');\n\t\t\t\t\tif (gen !== generation) return;\n\t\t\t\t\tif (!blob) {\n\t\t\t\t\t\tthrow new SelfstoreError('TARGET_UNAVAILABLE', 'The backup could not be downloaded.');\n\t\t\t\t\t}\n\t\t\t\t\tawait restore(blob).withPassword(pw).read();\n\t\t\t\t\tif (gen !== generation) return;\n\t\t\t\t\tpassword = pw;\n\t\t\t\t\tm.set({ busy: false });\n\t\t\t\t\trouteAfterUnlock(gen);\n\t\t\t\t} catch (e) {\n\t\t\t\t\tif (gen !== generation) return;\n\t\t\t\t\tif (isSelfstoreError(e) && e.code === 'DECRYPT_FAILED') {\n\t\t\t\t\t\tm.set({ busy: false, passwordError: true }); // stay, say so, retype\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tfail(e);\n\t\t\t\t}\n\t\t\t})();\n\t\t},\n\n\t\toverwrite(): void {\n\t\t\tconst { step, busy } = m.snapshot;\n\t\t\tif (busy || step !== 'password' || !target) return;\n\t\t\t// The password is forgotten: stop proving it and START fresh over the\n\t\t\t// protected backup. wipe:true empties local first (fresh sync meta), so\n\t\t\t// this writes a blank new backup - never this device's existing data -\n\t\t\t// and the owner is never stuck behind a lost secret. A blank write is\n\t\t\t// also tiny, so it cannot time out the way pushing a large local store\n\t\t\t// would.\n\t\t\tpassword = null;\n\t\t\tvoid attach(generation, 'replace-remote', 'replaced', true);\n\t\t},\n\n\t\tresolveConflict(how: ConnectResolution): void {\n\t\t\tconst { step, busy } = m.snapshot;\n\t\t\tif (busy || step !== 'conflict') return;\n\t\t\tconst plan = planFor(how);\n\t\t\tvoid attach(generation, plan.strategy, plan.outcome);\n\t\t},\n\n\t\tcancel(): void {\n\t\t\tif (m.snapshot.step === 'connected') return; // done is done\n\t\t\tbackToChoose();\n\t\t},\n\n\t\tretry(): void {\n\t\t\tif (m.snapshot.step !== 'error') return;\n\t\t\tbackToChoose();\n\t\t}\n\t};\n}\n","// shareFlow: the \"who can see this\" panel as a headless machine over an\n// injected ShareEngine - how links travel (Drive public link, relay,\n// mailbox) is the app's business. The flow owns what every such panel gets\n// wrong first: a transient listing failure never blanks it (last-known\n// links stay, flagged stale - a member who saw an empty panel once will\n// revoke something in panic); one operation at a time, so a double-click\n// cannot create two links; removals leave the snapshot only after the\n// engine confirmed them, never optimistically; removing yourself is refused\n// up front (leaving is its own journey, not a row action). Every leg runs\n// under a deadline and reports the stable { code, labelKey, message } shape.\n\nimport type { StoreError } from '../persistence/store';\nimport { makeMachine, toStoreError, withDeadline, type FlowStore } from './machine';\n\nexport type ShareLevel = 'read' | 'write';\n\nexport interface ShareLinkInfo {\n\t/** Engine-scoped stable id (revoke keys on it). */\n\tid: string;\n\t/** The invitation URL to hand out. */\n\turl: string;\n\tlevel: ShareLevel;\n}\n\nexport interface ShareMemberInfo {\n\t/** Engine-scoped stable id (removeMember keys on it). */\n\tid: string;\n\tlabel?: string;\n\t/** This row is the local user. */\n\tself?: boolean;\n\t/** This row owns the share (the app may render a badge, the flow does not care). */\n\towner?: boolean;\n}\n\n/** The app-side port the flow drives. Implementations wrap whatever transport\n * the app shares through; every method may throw (SelfstoreError preferred,\n * anything is normalized). */\nexport interface ShareEngine {\n\t/** The current truth: active links and members. */\n\tlist(): Promise<{ links: ShareLinkInfo[]; members: ShareMemberInfo[] }>;\n\tcreateLink(opts: { level: ShareLevel }): Promise<ShareLinkInfo>;\n\trevokeLink(id: string): Promise<void>;\n\t/** Optional: engines without member management simply omit it. */\n\tremoveMember?(id: string): Promise<void>;\n\t/** Optional: END the whole share in one gesture - every link dead, every\n\t * member out. Engines whose transport has such a move (an invitation to\n\t * kill, a file to re-privatize) expose it; the widget renders the action\n\t * only then. */\n\trevokeAll?(): Promise<void>;\n}\n\nexport type ShareBusy = 'refresh' | 'create' | 'revoke' | 'remove' | 'revoke-all' | null;\n\nexport interface ShareSnapshot {\n\tlinks: readonly ShareLinkInfo[];\n\tmembers: readonly ShareMemberInfo[];\n\t/** The operation in flight, or null. */\n\tbusy: ShareBusy;\n\t/** The last refresh failed: the lists shown are the last-known good ones. */\n\tstale: boolean;\n\t/** The last operation's problem (the lists are never cleared by an error). */\n\terror: StoreError | null;\n\t/** True once the engine offers removeMember (render the row action at all). */\n\tcanRemoveMembers: boolean;\n\t/** True once the engine offers revokeAll (render the end-the-share action). */\n\tcanRevokeAll: boolean;\n}\n\nexport interface ShareFlow extends FlowStore<ShareSnapshot> {\n\t/** Re-list from the engine. Failure keeps the last-known lists (stale). */\n\trefresh(): Promise<void>;\n\t/** Create an invitation link. Resolves to it, or null when refused (an\n\t * operation was already in flight). */\n\tcreateLink(opts: { level: ShareLevel }): Promise<ShareLinkInfo | null>;\n\t/** Revoke a link. The link leaves the snapshot only once the engine\n\t * confirmed. Resolves to whether it did. */\n\trevokeLink(id: string): Promise<boolean>;\n\t/** Remove a member (never yourself - refused up front). Resolves to whether\n\t * the engine confirmed. */\n\tremoveMember(id: string): Promise<boolean>;\n\t/** End the whole share: every link and member leaves the snapshot only\n\t * once the engine confirmed. Resolves to whether it did. */\n\trevokeAll(): Promise<boolean>;\n}\n\nconst DEFAULT_DEADLINE_MS = 30_000;\n\nexport function shareFlow(engine: ShareEngine, options: { deadlineMs?: number } = {}): ShareFlow {\n\tconst deadlineMs = options.deadlineMs ?? DEFAULT_DEADLINE_MS;\n\n\tconst m = makeMachine<ShareSnapshot>({\n\t\tlinks: [],\n\t\tmembers: [],\n\t\tbusy: 'refresh',\n\t\tstale: false,\n\t\terror: null,\n\t\tcanRemoveMembers: typeof engine.removeMember === 'function',\n\t\tcanRevokeAll: typeof engine.revokeAll === 'function'\n\t});\n\n\t/** Serialize the operations: one at a time, the others answer \"refused\". */\n\tfunction begin(op: Exclude<ShareBusy, null>): boolean {\n\t\tif (m.snapshot.busy !== null && !(op === 'refresh' && m.snapshot.busy === 'refresh')) {\n\t\t\treturn false;\n\t\t}\n\t\tm.set({ busy: op });\n\t\treturn true;\n\t}\n\n\tasync function doRefresh(): Promise<void> {\n\t\ttry {\n\t\t\tconst { links, members } = await withDeadline(engine.list(), deadlineMs, 'The share listing');\n\t\t\tm.set({ links, members, busy: null, stale: false, error: null });\n\t\t} catch (e) {\n\t\t\t// Keep the last-known lists: a hiccup must never paint the share as\n\t\t\t// gone. Just say the view may be behind.\n\t\t\tm.set({ busy: null, stale: true, error: toStoreError(e) });\n\t\t}\n\t}\n\n\tconst flow: ShareFlow = {\n\t\tget snapshot() {\n\t\t\treturn m.snapshot;\n\t\t},\n\t\tsubscribe: m.subscribe,\n\n\t\tasync refresh(): Promise<void> {\n\t\t\tif (m.snapshot.busy !== null && m.snapshot.busy !== 'refresh') return;\n\t\t\tm.set({ busy: 'refresh' });\n\t\t\tawait doRefresh();\n\t\t},\n\n\t\tasync createLink(opts): Promise<ShareLinkInfo | null> {\n\t\t\tif (!begin('create')) return null;\n\t\t\ttry {\n\t\t\t\tconst link = await withDeadline(engine.createLink(opts), deadlineMs, 'Creating the link');\n\t\t\t\tm.set({ links: [...m.snapshot.links, link], busy: null, error: null });\n\t\t\t\treturn link;\n\t\t\t} catch (e) {\n\t\t\t\tm.set({ busy: null, error: toStoreError(e) });\n\t\t\t\treturn null;\n\t\t\t}\n\t\t},\n\n\t\tasync revokeLink(id: string): Promise<boolean> {\n\t\t\tif (!begin('revoke')) return false;\n\t\t\ttry {\n\t\t\t\tawait withDeadline(engine.revokeLink(id), deadlineMs, 'Revoking the link');\n\t\t\t\t// Confirmed: NOW it leaves the snapshot.\n\t\t\t\tm.set({ links: m.snapshot.links.filter((l) => l.id !== id), busy: null, error: null });\n\t\t\t\treturn true;\n\t\t\t} catch (e) {\n\t\t\t\tm.set({ busy: null, error: toStoreError(e) });\n\t\t\t\treturn false;\n\t\t\t}\n\t\t},\n\n\t\tasync removeMember(id: string): Promise<boolean> {\n\t\t\tconst remove = engine.removeMember?.bind(engine);\n\t\t\tif (!remove) {\n\t\t\t\tthrow new TypeError('shareFlow: this engine does not manage members (no removeMember).');\n\t\t\t}\n\t\t\tconst row = m.snapshot.members.find((mb) => mb.id === id);\n\t\t\tif (row?.self) {\n\t\t\t\tthrow new TypeError(\n\t\t\t\t\t'shareFlow: refusing to remove yourself - leaving a share is its own journey, not a row action.'\n\t\t\t\t);\n\t\t\t}\n\t\t\tif (!begin('remove')) return false;\n\t\t\ttry {\n\t\t\t\tawait withDeadline(remove(id), deadlineMs, 'Removing the member');\n\t\t\t\tm.set({\n\t\t\t\t\tmembers: m.snapshot.members.filter((mb) => mb.id !== id),\n\t\t\t\t\tbusy: null,\n\t\t\t\t\terror: null\n\t\t\t\t});\n\t\t\t\treturn true;\n\t\t\t} catch (e) {\n\t\t\t\tm.set({ busy: null, error: toStoreError(e) });\n\t\t\t\treturn false;\n\t\t\t}\n\t\t},\n\n\t\tasync revokeAll(): Promise<boolean> {\n\t\t\tconst revoke = engine.revokeAll?.bind(engine);\n\t\t\tif (!revoke) {\n\t\t\t\tthrow new TypeError('shareFlow: this engine does not end the share (no revokeAll).');\n\t\t\t}\n\t\t\tif (!begin('revoke-all')) return false;\n\t\t\ttry {\n\t\t\t\tawait withDeadline(revoke(), deadlineMs, 'Ending the share');\n\t\t\t\t// Confirmed: the share is over, every link and member with it.\n\t\t\t\tm.set({ links: [], members: [], busy: null, error: null });\n\t\t\t\treturn true;\n\t\t\t} catch (e) {\n\t\t\t\tm.set({ busy: null, error: toStoreError(e) });\n\t\t\t\treturn false;\n\t\t\t}\n\t\t}\n\t};\n\n\t// The panel opens on the truth: list right away (failures land as stale).\n\tvoid doRefresh();\n\n\treturn flow;\n}\n","// joinFlow: opening an invitation link, as a headless machine over an\n// injected JoinEngine. Preview first, join on an explicit yes. The flow\n// holds the link itself, so a re-render, a login roundtrip or an account\n// switch cannot lose the invitation out from under the user. Joining is\n// single-flight. Mismatch (\"this device already follows another share\") and\n// no-invite (\"spent, or not for this account\") are outcomes with their own\n// steps, not generic errors - each has a different way out. Switching\n// accounts re-previews under the new account. A cancelled or superseded leg\n// cannot resurrect the flow, and every network leg runs under a deadline.\n\nimport type { StoreError } from '../persistence/store';\nimport { makeMachine, toStoreError, withDeadline, type FlowStore } from './machine';\nimport type { ShareLevel } from './share';\n\n/** What the user is about to join - enough to render an honest question. */\nexport interface JoinPreview {\n\t/** The share's display name, when the link carries one. */\n\tlabel?: string;\n\t/** Who shared it (a name, an email - whatever the engine knows). */\n\tfrom?: string;\n\tlevel?: ShareLevel;\n}\n\n/** How joining ended, other than plain failure:\n * 'joined' - this device now follows the share.\n * 'mismatch' - this device already follows a DIFFERENT share: joining a\n * second one is refused (leave the first, or use another\n * device/profile).\n * 'no-invite' - the invitation is spent, expired, or aimed at another\n * account (switching accounts may be the fix). */\nexport type JoinOutcome = 'joined' | 'mismatch' | 'no-invite';\n\n/** The app-side port: how a link is read and honoured on this transport. */\nexport interface JoinEngine {\n\t/** Read-only look at the invitation. Must change NOTHING. */\n\tpreview(link: string): Promise<JoinPreview>;\n\t/** Consume the invitation and wire the share. Resolve null when the user\n\t * backed out of a popup the engine had to open (an account chooser, a\n\t * consent screen): the flow returns to 'ready' silently - backing out of\n\t * a human gesture is not an error, here as everywhere in these flows. */\n\tjoin(link: string): Promise<JoinOutcome | null>;\n\t/** Optional: re-run the account chooser (the invite may belong to another\n\t * account). The flow re-previews after it. */\n\tswitchAccount?(): Promise<void>;\n}\n\nexport type JoinStep =\n\t'previewing' | 'ready' | 'joining' | 'joined' | 'mismatch' | 'no-invite' | 'error';\n\nexport interface JoinSnapshot {\n\t/** The invitation this flow was opened with - held here so the UI can\n\t * always find it again. */\n\tlink: string;\n\tstep: JoinStep;\n\t/** Set once previewed (kept through errors, so retry re-renders context). */\n\tpreview: JoinPreview | null;\n\t/** The engine offers an account switch. */\n\tcanSwitchAccount: boolean;\n\tbusy: boolean;\n\t/** Set when step === 'error': show `labelKey`, log `message`. */\n\terror: StoreError | null;\n}\n\nexport interface JoinFlow extends FlowStore<JoinSnapshot> {\n\t/** Say yes: consume the invitation. Only from 'ready'. */\n\taccept(): void;\n\t/** Re-run the account chooser, then preview again under the new account.\n\t * Available from 'ready', 'no-invite' and 'error' when the engine offers it. */\n\tswitchAccount(): void;\n\t/** From 'error': preview again and resume the journey. */\n\tretry(): void;\n}\n\nconst DEFAULT_DEADLINE_MS = 30_000;\n\nexport function joinFlow(\n\tlink: string,\n\tengine: JoinEngine,\n\toptions: { deadlineMs?: number } = {}\n): JoinFlow {\n\tconst deadlineMs = options.deadlineMs ?? DEFAULT_DEADLINE_MS;\n\n\tconst m = makeMachine<JoinSnapshot>({\n\t\tlink,\n\t\tstep: 'previewing',\n\t\tpreview: null,\n\t\tcanSwitchAccount: typeof engine.switchAccount === 'function',\n\t\tbusy: true,\n\t\terror: null\n\t});\n\n\tlet generation = 0;\n\n\tfunction preview(): void {\n\t\tconst gen = ++generation;\n\t\tm.set({ step: 'previewing', busy: true, error: null });\n\t\tvoid withDeadline(engine.preview(link), deadlineMs, 'The invitation')\n\t\t\t.then((p) => {\n\t\t\t\tif (gen !== generation) return;\n\t\t\t\tm.set({ step: 'ready', preview: p, busy: false });\n\t\t\t})\n\t\t\t.catch((e) => {\n\t\t\t\tif (gen !== generation) return;\n\t\t\t\tm.set({ step: 'error', busy: false, error: toStoreError(e) });\n\t\t\t});\n\t}\n\n\tpreview();\n\n\treturn {\n\t\tget snapshot() {\n\t\t\treturn m.snapshot;\n\t\t},\n\t\tsubscribe: m.subscribe,\n\n\t\taccept(): void {\n\t\t\tconst { step, busy } = m.snapshot;\n\t\t\tif (busy || step !== 'ready') return; // single-flight: one join per yes\n\t\t\tconst gen = ++generation;\n\t\t\tm.set({ step: 'joining', busy: true, error: null });\n\t\t\tvoid withDeadline(engine.join(link), deadlineMs, 'Joining')\n\t\t\t\t.then((outcome) => {\n\t\t\t\t\tif (gen !== generation) return;\n\t\t\t\t\tif (outcome === null) {\n\t\t\t\t\t\t// The user backed out of the engine's popup: the question is\n\t\t\t\t\t\t// still live, the preview still good - just ask again.\n\t\t\t\t\t\tm.set({ step: 'ready', busy: false });\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tm.set({ step: outcome, busy: false });\n\t\t\t\t})\n\t\t\t\t.catch((e) => {\n\t\t\t\t\tif (gen !== generation) return;\n\t\t\t\t\tm.set({ step: 'error', busy: false, error: toStoreError(e) });\n\t\t\t\t});\n\t\t},\n\n\t\tswitchAccount(): void {\n\t\t\tconst switcher = engine.switchAccount?.bind(engine);\n\t\t\tif (!switcher) return;\n\t\t\tconst { step, busy } = m.snapshot;\n\t\t\tif (busy || (step !== 'ready' && step !== 'no-invite' && step !== 'error')) return;\n\t\t\tconst gen = ++generation;\n\t\t\tm.set({ busy: true, error: null });\n\t\t\tvoid switcher()\n\t\t\t\t.then(() => {\n\t\t\t\t\tif (gen !== generation) return;\n\t\t\t\t\tpreview(); // the other account may hold the right invite: look again\n\t\t\t\t})\n\t\t\t\t.catch((e) => {\n\t\t\t\t\tif (gen !== generation) return;\n\t\t\t\t\tm.set({ step: 'error', busy: false, error: toStoreError(e) });\n\t\t\t\t});\n\t\t},\n\n\t\tretry(): void {\n\t\t\tif (m.snapshot.step !== 'error') return;\n\t\t\tpreview();\n\t\t}\n\t};\n}\n"]}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { BACKUP_EXTENSION, BACKUP_MIME } from './chunk-S3FN54M5.js';
|
|
2
|
+
|
|
3
|
+
// src/selfstore/targets/local.ts
|
|
4
|
+
async function pickSaveHandle(filename) {
|
|
5
|
+
const picker = window.showSaveFilePicker;
|
|
6
|
+
if (!picker) return null;
|
|
7
|
+
try {
|
|
8
|
+
const handle = await picker({
|
|
9
|
+
suggestedName: filename,
|
|
10
|
+
types: [{ description: "selfstore backup", accept: { [BACKUP_MIME]: [BACKUP_EXTENSION] } }]
|
|
11
|
+
});
|
|
12
|
+
return { handle };
|
|
13
|
+
} catch (e) {
|
|
14
|
+
if (e instanceof DOMException && e.name === "AbortError") return "CANCELLED";
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
async function writeToHandle(handle, blob) {
|
|
19
|
+
const writable = await handle.createWritable();
|
|
20
|
+
await writable.write(blob);
|
|
21
|
+
await writable.close();
|
|
22
|
+
}
|
|
23
|
+
function downloadBlob(blob, filename) {
|
|
24
|
+
const url = URL.createObjectURL(blob);
|
|
25
|
+
const a = document.createElement("a");
|
|
26
|
+
a.href = url;
|
|
27
|
+
a.download = filename;
|
|
28
|
+
document.body.appendChild(a);
|
|
29
|
+
a.click();
|
|
30
|
+
a.remove();
|
|
31
|
+
URL.revokeObjectURL(url);
|
|
32
|
+
}
|
|
33
|
+
async function saveToDisk(blob, filename) {
|
|
34
|
+
const cible = await pickSaveHandle(filename);
|
|
35
|
+
if (cible === "CANCELLED") return false;
|
|
36
|
+
if (cible) {
|
|
37
|
+
await writeToHandle(cible.handle, blob);
|
|
38
|
+
return true;
|
|
39
|
+
}
|
|
40
|
+
downloadBlob(blob, filename);
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
function pickFromDisk() {
|
|
44
|
+
return new Promise((resolve) => {
|
|
45
|
+
const input = document.createElement("input");
|
|
46
|
+
input.type = "file";
|
|
47
|
+
input.accept = ".zip,application/zip,application/octet-stream";
|
|
48
|
+
input.onchange = () => resolve(input.files?.[0] ?? null);
|
|
49
|
+
input.oncancel = () => resolve(null);
|
|
50
|
+
input.style.display = "none";
|
|
51
|
+
document.body.appendChild(input);
|
|
52
|
+
input.click();
|
|
53
|
+
input.remove();
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export { downloadBlob, pickFromDisk, pickSaveHandle, saveToDisk, writeToHandle };
|
|
58
|
+
//# sourceMappingURL=chunk-QCJSQQX3.js.map
|
|
59
|
+
//# sourceMappingURL=chunk-QCJSQQX3.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/selfstore/targets/local.ts"],"names":[],"mappings":";;;AA0BA,eAAsB,eACrB,QAAA,EACuD;AACvD,EAAA,MAAM,SACL,MAAA,CAGC,kBAAA;AACF,EAAA,IAAI,CAAC,QAAQ,OAAO,IAAA;AACpB,EAAA,IAAI;AACH,IAAA,MAAM,MAAA,GAAS,MAAM,MAAA,CAAO;AAAA,MAC3B,aAAA,EAAe,QAAA;AAAA,MACf,KAAA,EAAO,CAAC,EAAE,WAAA,EAAa,oBAAoB,MAAA,EAAQ,EAAE,CAAC,WAAW,GAAG,CAAC,gBAAgB,CAAA,IAAK;AAAA,KAC1F,CAAA;AACD,IAAA,OAAO,EAAE,MAAA,EAAO;AAAA,EACjB,SAAS,CAAA,EAAG;AACX,IAAA,IAAI,CAAA,YAAa,YAAA,IAAgB,CAAA,CAAE,IAAA,KAAS,cAAc,OAAO,WAAA;AAEjE,IAAA,OAAO,IAAA;AAAA,EACR;AACD;AAGA,eAAsB,aAAA,CAAc,QAAoB,IAAA,EAA2B;AAClF,EAAA,MAAM,QAAA,GAAW,MAAM,MAAA,CAAO,cAAA,EAAe;AAC7C,EAAA,MAAM,QAAA,CAAS,MAAM,IAAI,CAAA;AACzB,EAAA,MAAM,SAAS,KAAA,EAAM;AACtB;AAIO,SAAS,YAAA,CAAa,MAAY,QAAA,EAAwB;AAChE,EAAA,MAAM,GAAA,GAAM,GAAA,CAAI,eAAA,CAAgB,IAAI,CAAA;AACpC,EAAA,MAAM,CAAA,GAAI,QAAA,CAAS,aAAA,CAAc,GAAG,CAAA;AACpC,EAAA,CAAA,CAAE,IAAA,GAAO,GAAA;AACT,EAAA,CAAA,CAAE,QAAA,GAAW,QAAA;AACb,EAAA,QAAA,CAAS,IAAA,CAAK,YAAY,CAAC,CAAA;AAC3B,EAAA,CAAA,CAAE,KAAA,EAAM;AACR,EAAA,CAAA,CAAE,MAAA,EAAO;AACT,EAAA,GAAA,CAAI,gBAAgB,GAAG,CAAA;AACxB;AAaA,eAAsB,UAAA,CAAW,MAAY,QAAA,EAAoC;AAChF,EAAA,MAAM,KAAA,GAAQ,MAAM,cAAA,CAAe,QAAQ,CAAA;AAC3C,EAAA,IAAI,KAAA,KAAU,aAAa,OAAO,KAAA;AAClC,EAAA,IAAI,KAAA,EAAO;AACV,IAAA,MAAM,aAAA,CAAc,KAAA,CAAM,MAAA,EAAQ,IAAI,CAAA;AACtC,IAAA,OAAO,IAAA;AAAA,EACR;AACA,EAAA,YAAA,CAAa,MAAM,QAAQ,CAAA;AAC3B,EAAA,OAAO,IAAA;AACR;AAGO,SAAS,YAAA,GAAqC;AACpD,EAAA,OAAO,IAAI,OAAA,CAAQ,CAAC,OAAA,KAAY;AAC/B,IAAA,MAAM,KAAA,GAAQ,QAAA,CAAS,aAAA,CAAc,OAAO,CAAA;AAC5C,IAAA,KAAA,CAAM,IAAA,GAAO,MAAA;AAEb,IAAA,KAAA,CAAM,MAAA,GAAS,+CAAA;AACf,IAAA,KAAA,CAAM,WAAW,MAAM,OAAA,CAAQ,MAAM,KAAA,GAAQ,CAAC,KAAK,IAAI,CAAA;AAEvD,IAAA,KAAA,CAAM,QAAA,GAAW,MAAM,OAAA,CAAQ,IAAI,CAAA;AAEnC,IAAA,KAAA,CAAM,MAAM,OAAA,GAAU,MAAA;AACtB,IAAA,QAAA,CAAS,IAAA,CAAK,YAAY,KAAK,CAAA;AAC/B,IAAA,KAAA,CAAM,KAAA,EAAM;AACZ,IAAA,KAAA,CAAM,MAAA,EAAO;AAAA,EACd,CAAC,CAAA;AACF","file":"chunk-QCJSQQX3.js","sourcesContent":["/**\n * Local-disk helpers: the DOM edge of selfstore, kept out of the core so the\n * core stays environment-agnostic and testable.\n */\n\nimport { BACKUP_EXTENSION, BACKUP_MIME } from '../index';\n\n/** The slice of FileSystemFileHandle this module needs. */\nexport interface SaveHandle {\n\tcreateWritable(): Promise<{ write(d: Blob): Promise<void>; close(): Promise<void> }>;\n}\n\n/**\n * Ask the user where to save, and nothing else.\n *\n * Split out because ORDER decides whether the dialog opens at all. The picker\n * needs the transient user activation of the click, and that activation expires\n * while an encrypted backup is being built (Argon2id is deliberately slow). Ask\n * first, build after, and the dialog is still allowed; build first and the\n * browser refuses it. Hosts that saw a silent download instead of the save\n * dialog were hitting exactly that.\n *\n * Null when the browser has no picker at all (Firefox, Safari) or when it\n * refuses for any reason other than a cancellation - the caller then falls back\n * to a download. Throws nothing; a cancelled dialog is reported as CANCELLED.\n */\nexport async function pickSaveHandle(\n\tfilename: string\n): Promise<{ handle: SaveHandle } | 'CANCELLED' | null> {\n\tconst picker = (\n\t\twindow as unknown as {\n\t\t\tshowSaveFilePicker?: (o: unknown) => Promise<SaveHandle>;\n\t\t}\n\t).showSaveFilePicker;\n\tif (!picker) return null;\n\ttry {\n\t\tconst handle = await picker({\n\t\t\tsuggestedName: filename,\n\t\t\ttypes: [{ description: 'selfstore backup', accept: { [BACKUP_MIME]: [BACKUP_EXTENSION] } }]\n\t\t});\n\t\treturn { handle };\n\t} catch (e) {\n\t\tif (e instanceof DOMException && e.name === 'AbortError') return 'CANCELLED';\n\t\t// Anything else (no activation left, policy): the download still works.\n\t\treturn null;\n\t}\n}\n\n/** Write a blob through a handle obtained earlier by pickSaveHandle. */\nexport async function writeToHandle(handle: SaveHandle, blob: Blob): Promise<void> {\n\tconst writable = await handle.createWritable();\n\tawait writable.write(blob);\n\tawait writable.close();\n}\n\n/** Hand the blob to the browser's download machinery. No cancel to observe:\n * once taken, the host has done all it can. */\nexport function downloadBlob(blob: Blob, filename: string): void {\n\tconst url = URL.createObjectURL(blob);\n\tconst a = document.createElement('a');\n\ta.href = url;\n\ta.download = filename;\n\tdocument.body.appendChild(a);\n\ta.click();\n\ta.remove();\n\tURL.revokeObjectURL(url);\n}\n\n/**\n * Save a backup blob to disk via the File System Access API, else a download.\n *\n * Resolves TRUE when bytes were handed to the browser, FALSE when the user\n * closed the save dialog. Callers must branch on it: a host that records \"last\n * backup: today\" after a cancelled dialog tells the user they are safe when no\n * file exists, and that lie only surfaces the day they need the file.\n *\n * Takes a ready blob, so the dialog opens only after the blob was built. When\n * that blob is an encrypted backup, prefer asking first: see pickSaveHandle.\n */\nexport async function saveToDisk(blob: Blob, filename: string): Promise<boolean> {\n\tconst cible = await pickSaveHandle(filename);\n\tif (cible === 'CANCELLED') return false;\n\tif (cible) {\n\t\tawait writeToHandle(cible.handle, blob);\n\t\treturn true;\n\t}\n\tdownloadBlob(blob, filename);\n\treturn true;\n}\n\n/** Prompt the user to pick a backup file from disk. Resolves null if cancelled. */\nexport function pickFromDisk(): Promise<File | null> {\n\treturn new Promise((resolve) => {\n\t\tconst input = document.createElement('input');\n\t\tinput.type = 'file';\n\t\t// octet-stream: some providers serve a downloaded ZIP under that type.\n\t\tinput.accept = '.zip,application/zip,application/octet-stream';\n\t\tinput.onchange = () => resolve(input.files?.[0] ?? null);\n\t\t// Without this, a cancelled dialog leaves the promise pending forever.\n\t\tinput.oncancel = () => resolve(null);\n\t\t// Some browsers need the input in the DOM for the dialog to open.\n\t\tinput.style.display = 'none';\n\t\tdocument.body.appendChild(input);\n\t\tinput.click();\n\t\tinput.remove();\n\t});\n}\n"]}
|
|
@@ -397,12 +397,27 @@ var BackupBuilder = class {
|
|
|
397
397
|
return new Blob([await this.toBytes()], { type: BACKUP_MIME });
|
|
398
398
|
}
|
|
399
399
|
/** Save the backup to disk (File System Access API, else a download).
|
|
400
|
-
* Browser-only terminal; defaults to `<app>-<date>.zip`.
|
|
400
|
+
* Browser-only terminal; defaults to `<app>-<date>.zip`. False when the
|
|
401
|
+
* user closed the save dialog: nothing was written, so do not tell them
|
|
402
|
+
* they have a backup.
|
|
403
|
+
*
|
|
404
|
+
* Asks WHERE first, encrypts after. The save dialog needs the transient
|
|
405
|
+
* activation of the click that led here, and building an encrypted backup
|
|
406
|
+
* outlives it (Argon2id is deliberately slow): encrypting first made the
|
|
407
|
+
* browser refuse the dialog and silently download instead, turning a file
|
|
408
|
+
* the user chose into one they have to go and find. */
|
|
401
409
|
async toDisk(filename) {
|
|
402
|
-
const blob = await this.toBlob();
|
|
403
410
|
const name = filename ?? `${this.opts.app}-${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}${BACKUP_EXTENSION}`;
|
|
404
|
-
const {
|
|
405
|
-
await
|
|
411
|
+
const { pickSaveHandle, writeToHandle, downloadBlob } = await import('./local-R5QUGDZU.js');
|
|
412
|
+
const cible = await pickSaveHandle(name);
|
|
413
|
+
if (cible === "CANCELLED") return false;
|
|
414
|
+
const blob = await this.toBlob();
|
|
415
|
+
if (cible) {
|
|
416
|
+
await writeToHandle(cible.handle, blob);
|
|
417
|
+
return true;
|
|
418
|
+
}
|
|
419
|
+
downloadBlob(blob, name);
|
|
420
|
+
return true;
|
|
406
421
|
}
|
|
407
422
|
};
|
|
408
423
|
var EncryptedBackupBuilder = class _EncryptedBackupBuilder extends BackupBuilder {
|
|
@@ -479,5 +494,5 @@ async function changePassword(input, opts) {
|
|
|
479
494
|
}
|
|
480
495
|
|
|
481
496
|
export { BACKUP_EXTENSION, BACKUP_MIME, RESERVED_COLLECTION_PREFIX, asBytes, backup, changePassword, checkPasswordPolicy, exportSnapshot, importSnapshot, inspect, isEncrypted, readBoxWithSync, restore, writeBox };
|
|
482
|
-
//# sourceMappingURL=chunk-
|
|
483
|
-
//# sourceMappingURL=chunk-
|
|
497
|
+
//# sourceMappingURL=chunk-S3FN54M5.js.map
|
|
498
|
+
//# sourceMappingURL=chunk-S3FN54M5.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/selfstore/archive.ts","../src/selfstore/box.ts","../src/selfstore/types.ts","../src/selfstore/password-policy.ts","../src/selfstore/fluent.ts","../src/selfstore/index.ts"],"names":["utf8"],"mappings":"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","file":"chunk-S3FN54M5.js","sourcesContent":["// The inner archive: a JSON manifest (selfstore.json) plus one entry per\n// binary file, as a plain ZIP. Encrypted backups wrap these same bytes in\n// AES-GCM (box.ts). fflate is imported dynamically - only needed at\n// export/import time.\n\nimport type { Snapshot, SnapshotFile } from './types';\nimport { SelfstoreError } from './errors';\n\nconst MANIFEST = 'selfstore.json';\n// Opaque caller bookkeeping (createLocalStore's schema version + merge\n// metadata). Its own entry, so the manifest holds nothing but app data.\nconst SIDECAR = 'sync.json';\nconst ARCHIVE_VERSION = 1;\n\n// Zip-bomb guards. Backups are user data measured in MB; half a GiB per\n// entry and 1 GiB total are far above legitimate use and bound worst-case\n// inflation (many individually-legal entries must not add up to a bomb).\nexport const MAX_ENTRY_BYTES = 512 * 1024 * 1024;\nexport const MAX_TOTAL_BYTES = 1024 * 1024 * 1024;\n\nconst utf8 = { enc: new TextEncoder(), dec: new TextDecoder() };\n\ninterface FileMeta {\n\tid: string;\n\tname: string;\n\tmime: string;\n}\n\n/**\n * ZIP a set of named entries. `level` applies to the JSON entries; file\n * attachments are stored uncompressed - photos and PDFs are usually already\n * compressed, and re-deflating them was the biggest chunk of per-save\n * main-thread time for near-zero size gain. Still a plain readable ZIP.\n */\nexport async function zip(\n\tentries: Record<string, Uint8Array>,\n\tlevel: 0 | 6 = 6\n): Promise<Uint8Array> {\n\tconst { zipSync } = await import('fflate');\n\tconst input: Record<string, Uint8Array | [Uint8Array, { level: 0 }]> = {};\n\tfor (const [name, bytes] of Object.entries(entries)) {\n\t\tinput[name] = level !== 0 && name.startsWith('files/') ? [bytes, { level: 0 }] : bytes;\n\t}\n\treturn zipSync(input as Parameters<typeof zipSync>[0], { level });\n}\n\n/** Unzip to named entries. BAD_FORMAT on unreadable bytes, TOO_LARGE past the bomb guards. */\nexport async function unzip(\n\tbytes: Uint8Array,\n\tmaxEntryBytes: number = MAX_ENTRY_BYTES,\n\tmaxTotalBytes: number = MAX_TOTAL_BYTES\n): Promise<Record<string, Uint8Array>> {\n\tconst { unzipSync } = await import('fflate');\n\tlet total = 0;\n\ttry {\n\t\treturn unzipSync(bytes, {\n\t\t\tfilter: (f) => {\n\t\t\t\tif (f.originalSize > maxEntryBytes) {\n\t\t\t\t\tthrow new SelfstoreError('TOO_LARGE', `Archive entry \"${f.name}\" is too large.`);\n\t\t\t\t}\n\t\t\t\ttotal += f.originalSize;\n\t\t\t\tif (total > maxTotalBytes) {\n\t\t\t\t\tthrow new SelfstoreError('TOO_LARGE', 'Archive total size exceeds the guard.');\n\t\t\t\t}\n\t\t\t\treturn true;\n\t\t\t}\n\t\t});\n\t} catch (e) {\n\t\tif (e instanceof SelfstoreError) throw e;\n\t\tthrow new SelfstoreError('BAD_FORMAT', 'Unreadable archive.');\n\t}\n}\n\n/** Manifest + file entries for a snapshot; `sidecar` rides its own sync.json entry. */\nexport function buildEntries(snap: Snapshot, sidecar?: unknown): Record<string, Uint8Array> {\n\tconst entries: Record<string, Uint8Array> = {};\n\tentries[MANIFEST] = utf8.enc.encode(\n\t\tJSON.stringify({\n\t\t\tversion: ARCHIVE_VERSION,\n\t\t\tcollections: snap.collections,\n\t\t\tfiles: snap.files.map(({ id, name, mime }): FileMeta => ({ id, name, mime }))\n\t\t})\n\t);\n\tif (sidecar !== undefined && sidecar !== null) {\n\t\tentries[SIDECAR] = utf8.enc.encode(JSON.stringify(sidecar));\n\t}\n\tfor (const f of snap.files) entries[`files/${f.id}`] = f.bytes;\n\treturn entries;\n}\n\n/** The sync.json sidecar if present, else null. Unreadable content reads as absent. */\nexport function readSidecar(entries: Record<string, Uint8Array>): unknown {\n\tconst bytes = entries[SIDECAR];\n\tif (!bytes) return null;\n\ttry {\n\t\treturn JSON.parse(utf8.dec.decode(bytes));\n\t} catch {\n\t\treturn null;\n\t}\n}\n\n/** Rebuild a snapshot from ZIP entries (the inverse of buildEntries). */\nexport function entriesToSnapshot(entries: Record<string, Uint8Array>): Snapshot {\n\tconst manifestBytes = entries[MANIFEST];\n\tif (!manifestBytes) throw new SelfstoreError('BAD_FORMAT', 'Missing manifest.');\n\tconst manifest = JSON.parse(utf8.dec.decode(manifestBytes)) as {\n\t\tcollections?: Record<string, unknown[]>;\n\t\tfiles?: FileMeta[];\n\t};\n\tconst files: SnapshotFile[] = (manifest.files ?? []).map((m) => ({\n\t\t...m,\n\t\tbytes: entries[`files/${m.id}`] ?? new Uint8Array()\n\t}));\n\treturn { collections: manifest.collections ?? {}, files };\n}\n\n/** Pack a snapshot (plus an optional opaque sidecar) into ZIP bytes. */\nexport async function pack(snap: Snapshot, sidecar?: unknown): Promise<Uint8Array> {\n\treturn zip(buildEntries(snap, sidecar), 6);\n}\n\n/** Unpack ZIP bytes into a snapshot plus its sidecar (null when absent). */\nexport async function unpackWithSidecar(\n\tzipBytes: Uint8Array\n): Promise<{ snapshot: Snapshot; sidecar: unknown }> {\n\tconst entries = await unzip(zipBytes);\n\treturn { snapshot: entriesToSnapshot(entries), sidecar: readSidecar(entries) };\n}\n","// The portable backup container - a real ZIP in both cases, never a mystery\n// blob:\n//\n// unencrypted: meta.json + selfstore.json + files/* (browsable, format 1)\n// encrypted: meta.json + data.enc + LISEZMOI.txt (AES-256-GCM / Argon2id)\n//\n// Encrypted means the password envelope (format 3): a random data key seals\n// the inner ZIP, wrapped once per password in keys[], and the exact meta.json\n// bytes ride as GCM AAD so the slot table is tamper-evident. Format 2 is the\n// passwordless group variant. Anything else is refused.\n\nimport type { Snapshot, Header, EncodeOptions, PasswordSlot, ExternalSlot, KeySlot } from './types';\nimport {\n\tbuildEntries,\n\tentriesToSnapshot,\n\treadSidecar,\n\tpack,\n\tunpackWithSidecar,\n\tzip,\n\tunzip\n} from './archive';\nimport {\n\tmintSlot,\n\topenSlot,\n\topenExternalSlot,\n\tMAX_KEY_SLOTS,\n\tgcmSealAad,\n\tgcmOpenAad,\n\ttoBase64,\n\tfromBase64\n} from './crypto';\nimport {\n\tGROUP_KEYING,\n\tgcmSealRaw,\n\tgcmOpenRaw,\n\tsealDataKey,\n\topenDataKey,\n\tsignBox,\n\tverifyBox,\n\ttype GroupIdentity\n} from './group';\nimport { SelfstoreError } from './errors';\n\n// Genuine ZIPs get the honest extension.\nexport const BACKUP_EXTENSION = '.zip';\nexport const BACKUP_MIME = 'application/zip';\n\nconst META = 'meta.json';\nconst DATA = 'data.enc';\nconst SIG = 'sig.bin';\nconst README = 'LISEZMOI.txt';\n\n// Format identifier, which is also the discriminant: 1 plain ZIP, 2 group\n// mode, 3 authenticated password envelope (header bound as GCM AAD). These are\n// fixed wire ids; anything else is refused.\nconst BOX_FORMAT_PLAIN = 1;\nconst BOX_FORMAT_GROUP = 2;\nconst BOX_FORMAT_ENVELOPE_AUTH = 3;\n\nconst ZIP_MAGIC = [0x50, 0x4b, 0x03, 0x04]; // \"PK\\x03\\x04\"\n\nconst utf8 = { enc: new TextEncoder(), dec: new TextDecoder() };\n\n// Shipped inside encrypted backups for whoever opens the ZIP by hand. ASCII\n// only so it renders in any viewer; apps brand it via EncodeOptions.readme.\nconst DEFAULT_README =\n\t'Encrypted backup / Sauvegarde chiffree\\n\\n' +\n\t'This is a valid ZIP archive.\\n' +\n\t'\"data.enc\" holds your encrypted data (AES-256-GCM, key derived with Argon2id);\\n' +\n\t'\"meta.json\" holds the decryption parameters (cleartext).\\n' +\n\t'To read it, import this file into the application that created it\\n' +\n\t'(named in meta.json) with your password.\\n\\n' +\n\t'Ce fichier est une archive ZIP valide.\\n' +\n\t'\"data.enc\" contient vos donnees chiffrees (AES-256-GCM, cle derivee par Argon2id) ;\\n' +\n\t'\"meta.json\" contient les parametres de dechiffrement (en clair).\\n' +\n\t\"Pour la lire, importez ce fichier dans l'application qui l'a creee\\n\" +\n\t'(nommee dans meta.json) avec votre mot de passe.\\n';\n\n/** Coerce an accepted input (a picked File, a fetched Blob, raw bytes) to bytes. */\nexport async function asBytes(input: Blob | Uint8Array): Promise<Uint8Array> {\n\treturn input instanceof Uint8Array ? input : new Uint8Array(await input.arrayBuffer());\n}\n\nfunction startsWith(bytes: Uint8Array, magic: number[]): boolean {\n\treturn bytes.length >= magic.length && magic.every((b, i) => bytes[i] === b);\n}\n\n// An unknown cipher or per-slot KDF means a newer writer: UNSUPPORTED_VERSION,\n// not a misleading DECRYPT_FAILED from feeding foreign params to AES-GCM.\nfunction assertKnownCrypto(header: Header): void {\n\tif (header.encryption !== 'none' && header.encryption !== 'aes-256-gcm') {\n\t\tthrow new SelfstoreError(\n\t\t\t'UNSUPPORTED_VERSION',\n\t\t\t`Encryption \"${header.encryption}\" is newer than this reader supports.`\n\t\t);\n\t}\n\tif (header.keying !== undefined && header.keying !== GROUP_KEYING) {\n\t\tthrow new SelfstoreError(\n\t\t\t'UNSUPPORTED_VERSION',\n\t\t\t`Key envelope \"${header.keying}\" is newer than this reader supports.`\n\t\t);\n\t}\n\tfor (const slot of header.keys ?? []) {\n\t\t// External slots carry no KDF (KEK comes from the caller's secret).\n\t\tif (!slot || (slot as ExternalSlot).kind === 'external') continue;\n\t\tif ((slot as PasswordSlot).kdf?.algo !== 'argon2id') {\n\t\t\tthrow new SelfstoreError(\n\t\t\t\t'UNSUPPORTED_VERSION',\n\t\t\t\t`Key derivation \"${(slot as PasswordSlot).kdf?.algo}\" is newer than this reader supports.`\n\t\t\t);\n\t\t}\n\t}\n}\n\n/**\n * Serialize a snapshot to backup bytes. The optional `sidecar`\n * (createLocalStore's bookkeeping) rides its own sync.json entry - inside the\n * envelope when encrypting, so it never leaks in cleartext.\n */\nexport async function writeBox(\n\tsnapshot: Snapshot,\n\topts: EncodeOptions,\n\tsidecar?: unknown\n): Promise<Uint8Array> {\n\tconst header: Header = {\n\t\tformat: BOX_FORMAT_PLAIN,\n\t\tapp: opts.app,\n\t\tappVersion: opts.appVersion,\n\t\tschemaVersion: opts.schemaVersion,\n\t\tcreatedAt: new Date().toISOString(),\n\t\tencryption: 'none'\n\t};\n\n\tif (opts.group) {\n\t\tif (opts.password) {\n\t\t\tthrow new TypeError('writeBox(): `group` and `password` are mutually exclusive.');\n\t\t}\n\t\t// Fresh data key enveloped once per recipient; the author's signature\n\t\t// covers the exact meta.json + data.enc bytes, so nothing a reader\n\t\t// trusts (recipients, author claim, iv, ciphertext) can be altered.\n\t\tconst dataKey = crypto.getRandomValues(new Uint8Array(32));\n\t\tconst enc = await gcmSealRaw(dataKey, await pack(snapshot, sidecar));\n\t\tconst meta: Header = {\n\t\t\t...header,\n\t\t\tformat: BOX_FORMAT_GROUP,\n\t\t\tencryption: 'aes-256-gcm',\n\t\t\tkeying: GROUP_KEYING,\n\t\t\tauthor: opts.group.sign.pub,\n\t\t\trecipients: await sealDataKey(dataKey, opts.group.recipients),\n\t\t\tiv: enc.iv\n\t\t};\n\t\tconst metaBytes = utf8.enc.encode(JSON.stringify(meta));\n\t\treturn zip(\n\t\t\t{\n\t\t\t\t[META]: metaBytes,\n\t\t\t\t[DATA]: enc.ciphertext,\n\t\t\t\t[SIG]: await signBox(metaBytes, enc.ciphertext, opts.group.sign.priv),\n\t\t\t\t[README]: utf8.enc.encode(opts.readme || DEFAULT_README)\n\t\t\t},\n\t\t\t0\n\t\t);\n\t}\n\n\tif (opts.envelope && opts.password) {\n\t\tthrow new TypeError('writeBox(): `envelope` and `password` are mutually exclusive.');\n\t}\n\n\tif (opts.envelope || opts.password) {\n\t\t// A rewrite passes `envelope` (held data key + carried slot table) so a\n\t\t// writer that knows only one password preserves every other slot; a\n\t\t// plain `password` mints a fresh single-slot envelope, i.e. a data-key\n\t\t// rotation - that is what makes setEncryption actually revoke.\n\t\t// The iv is minted before meta.json is serialized so it sits inside the\n\t\t// AAD; seal and open then agree on the exact header bytes.\n\t\tconst dataKey = opts.envelope?.dataKey ?? crypto.getRandomValues(new Uint8Array(32));\n\t\tconst slots = opts.envelope?.slots ?? [await mintSlot(opts.password!, dataKey)];\n\t\tif (slots.length === 0 || slots.length > MAX_KEY_SLOTS) {\n\t\t\tthrow new TypeError(`writeBox(): between 1 and ${MAX_KEY_SLOTS} password slots.`);\n\t\t}\n\t\tconst iv = crypto.getRandomValues(new Uint8Array(12));\n\t\tconst meta: Header = {\n\t\t\t...header,\n\t\t\tformat: BOX_FORMAT_ENVELOPE_AUTH,\n\t\t\tencryption: 'aes-256-gcm',\n\t\t\tkeys: slots,\n\t\t\tiv: toBase64(iv)\n\t\t};\n\t\tconst metaBytes = utf8.enc.encode(JSON.stringify(meta));\n\t\tconst ciphertext = await gcmSealAad(dataKey, iv, await pack(snapshot, sidecar), metaBytes);\n\t\treturn zip(\n\t\t\t{\n\t\t\t\t[META]: metaBytes,\n\t\t\t\t[DATA]: ciphertext,\n\t\t\t\t[README]: utf8.enc.encode(opts.readme || DEFAULT_README)\n\t\t\t},\n\t\t\t0\n\t\t);\n\t}\n\n\t// The file is the browsable archive: manifest + files (+ sync.json), plus meta.json.\n\tconst entries = buildEntries(snapshot, sidecar);\n\tentries[META] = utf8.enc.encode(JSON.stringify(header));\n\treturn zip(entries, 6);\n}\n\n/** Read the cleartext metadata (app, date, whether encrypted) without decrypting. */\nexport async function readBoxMeta(bytes: Uint8Array): Promise<Header> {\n\tif (!startsWith(bytes, ZIP_MAGIC)) {\n\t\tthrow new SelfstoreError(\n\t\t\t'BAD_FORMAT',\n\t\t\t'Not a selfstore backup: expected a ZIP. Pass the exact bytes/Blob produced by ' +\n\t\t\t\t'backup().toBlob() / exportSnapshot(), not a decoded object.'\n\t\t);\n\t}\n\treturn metaOf(await unzip(bytes));\n}\n\n/**\n * Group-mode read inputs: the reader's identity and the trusted author keys\n * (manifest members' Ed25519 publics). `authors` is mandatory - a valid\n * signature over an attacker-chosen author proves nothing (SPEC.md 12.6), so\n * there is no \"decrypt without checking who signed\" mode.\n */\nexport interface GroupReadOptions {\n\tidentity: Pick<GroupIdentity, 'encPub' | 'encPriv'>;\n\tauthors: string[];\n}\n\n/**\n * Key material captured at read time: the data key that opened the file plus\n * the slot table verbatim. Carried into rewrites so a session that knows only\n * one key preserves every slot.\n */\nexport interface BoxEnvelope {\n\tdataKey: Uint8Array;\n\tslots: KeySlot[];\n}\n\n/**\n * App-supplied secret fetch for an external-key slot (the WebAuthn/hardware\n * exchange lives app-side). Return null to skip the slot (wrong key, user\n * cancelled).\n */\nexport type ExternalKeyResolver = (slot: ExternalSlot) => Promise<Uint8Array | null>;\n\n/**\n * Read a backup into a snapshot plus its sync.json sidecar (null when the\n * file carries none). Group copies need `group` and return the verified\n * author; envelope copies accept a held `dataKey` (KDF-free re-read) and\n * return the envelope for later rewrites.\n */\nexport async function readBoxWithSync(\n\tbytes: Uint8Array,\n\tpassword?: string,\n\tgroup?: GroupReadOptions,\n\tdataKey?: Uint8Array,\n\texternalResolver?: ExternalKeyResolver\n): Promise<{\n\tsnapshot: Snapshot;\n\tsidecar: unknown;\n\tauthor?: string;\n\tenvelope?: BoxEnvelope;\n\tformat: number;\n}> {\n\tconst entries = await unzip(bytes);\n\tconst meta = metaOf(entries);\n\tconst format = meta.format ?? BOX_FORMAT_PLAIN;\n\tif (\n\t\tformat !== BOX_FORMAT_PLAIN &&\n\t\tformat !== BOX_FORMAT_GROUP &&\n\t\tformat !== BOX_FORMAT_ENVELOPE_AUTH\n\t) {\n\t\tthrow new SelfstoreError(\n\t\t\t'UNSUPPORTED_VERSION',\n\t\t\t`Backup format ${format} is not supported by this reader.`\n\t\t);\n\t}\n\t// Bind security mode to format generation both ways, so neither `keying`\n\t// nor `format` can be stripped to route a box down the wrong path.\n\tif ((format === BOX_FORMAT_GROUP) !== !!meta.keying) {\n\t\tthrow new SelfstoreError(\n\t\t\t'BAD_FORMAT',\n\t\t\t'Group mode (format 2) and the keying field must agree.'\n\t\t);\n\t}\n\tif ((format === BOX_FORMAT_ENVELOPE_AUTH) !== Array.isArray(meta.keys)) {\n\t\tthrow new SelfstoreError(\n\t\t\t'BAD_FORMAT',\n\t\t\t'Password envelope (format 3) and the keys field must agree.'\n\t\t);\n\t}\n\tif (meta.keying) return { ...(await readGroupBox(entries, meta, group)), format };\n\tif (format === BOX_FORMAT_ENVELOPE_AUTH) {\n\t\treturn {\n\t\t\t...(await readEnvelopeBox(entries, meta, password, dataKey, externalResolver)),\n\t\t\tformat\n\t\t};\n\t}\n\tif (meta.encryption === 'none') {\n\t\treturn { snapshot: entriesToSnapshot(entries), sidecar: readSidecar(entries), format };\n\t}\n\t// Encrypted content in a container claiming the plain format: never written\n\t// by this library. Refuse cleanly rather than half-read.\n\tthrow new SelfstoreError('BAD_FORMAT', 'Encrypted content in a plain-format container.');\n}\n\n// Envelope read path: bounds first, the held data key as a KDF-free fast\n// path, then one trial per slot. All slots failing is DECRYPT_FAILED - wrong\n// key and tampering stay one case.\nasync function readEnvelopeBox(\n\tentries: Record<string, Uint8Array>,\n\tmeta: Header,\n\tpassword?: string,\n\tdataKey?: Uint8Array,\n\texternalResolver?: ExternalKeyResolver\n): Promise<{ snapshot: Snapshot; sidecar: unknown; envelope: BoxEnvelope }> {\n\tconst dataEnc = entries[DATA];\n\tconst metaBytes = entries[META];\n\tconst slots = meta.keys;\n\tif (\n\t\t!dataEnc ||\n\t\t!metaBytes ||\n\t\t!Array.isArray(slots) ||\n\t\tslots.length === 0 ||\n\t\ttypeof meta.iv !== 'string' ||\n\t\tmeta.encryption !== 'aes-256-gcm'\n\t) {\n\t\tthrow new SelfstoreError('BAD_FORMAT', 'Missing or malformed envelope entries or parameters.');\n\t}\n\tif (slots.length > MAX_KEY_SLOTS) {\n\t\tthrow new SelfstoreError(\n\t\t\t'UNSUPPORTED_VERSION',\n\t\t\t`Backup declares ${slots.length} key slots; this reader caps at ${MAX_KEY_SLOTS} ` +\n\t\t\t\t'(each password slot is one memory-hard trial).'\n\t\t);\n\t}\n\t// The exact meta.json bytes are bound as GCM AAD: a tampered header (an\n\t// altered slot table, a swapped KDF) fails the decrypt instead of opening.\n\tconst aad = metaBytes;\n\tconst iv = fromBase64(meta.iv);\n\t// A caller re-reading its own file skips the KDF entirely. On failure fall\n\t// through to the slot trials - another device may have rotated the key.\n\tif (dataKey) {\n\t\ttry {\n\t\t\tconst r = await unpackWithSidecar(await gcmOpenAad(dataKey, iv, dataEnc, aad));\n\t\t\treturn { ...r, envelope: { dataKey, slots } };\n\t\t} catch {\n\t\t\t/* rotated, foreign, or tampered file: the slot path decides */\n\t\t}\n\t}\n\tif (!password && !externalResolver) {\n\t\tthrow new SelfstoreError(\n\t\t\t'PASSWORD_REQUIRED',\n\t\t\t'This backup is encrypted. Provide the password, or the external key (e.g. a passkey), to open it.'\n\t\t);\n\t}\n\tfor (const slot of slots) {\n\t\tlet opened: Uint8Array | null = null;\n\t\tif ((slot as ExternalSlot).kind === 'external') {\n\t\t\tif (externalResolver) {\n\t\t\t\tconst secret = await externalResolver(slot as ExternalSlot);\n\t\t\t\tif (secret) opened = await openExternalSlot(slot as ExternalSlot, secret);\n\t\t\t}\n\t\t} else if (password) {\n\t\t\topened = await openSlot(slot as PasswordSlot, password);\n\t\t}\n\t\tif (!opened) continue;\n\t\t// Every slot wraps the same data key, so if the decrypt below fails\n\t\t// (header tamper surfaces here on format 3) trying another slot gains\n\t\t// nothing - let it throw.\n\t\tconst r = await unpackWithSidecar(await gcmOpenAad(opened, iv, dataEnc, aad));\n\t\treturn { ...r, envelope: { dataKey: opened, slots } };\n\t}\n\tthrow new SelfstoreError(\n\t\t'DECRYPT_FAILED',\n\t\t'Wrong key (no slot opened), or the backup is corrupted.'\n\t);\n}\n\n// Group read path, in trust order: shape, membership, signature, then\n// envelope + decrypt.\nasync function readGroupBox(\n\tentries: Record<string, Uint8Array>,\n\tmeta: Header,\n\tgroup?: GroupReadOptions\n): Promise<{ snapshot: Snapshot; sidecar: unknown; author: string }> {\n\tif (!group || !Array.isArray(group.authors)) {\n\t\tthrow new SelfstoreError(\n\t\t\t'IDENTITY_REQUIRED',\n\t\t\t'This copy is group-encrypted (per-member keys, no password). Open it with the member ' +\n\t\t\t\t'identity AND the trusted author list: the store does this when attached with { group }.'\n\t\t);\n\t}\n\tconst metaBytes = entries[META];\n\tconst dataEnc = entries[DATA];\n\tconst sig = entries[SIG];\n\t// Trusted fields must be well-typed, not merely truthy: malformed input\n\t// fails closed as BAD_FORMAT, never reaches the crypto as a TypeError.\n\tif (\n\t\t!metaBytes ||\n\t\t!dataEnc ||\n\t\t!sig ||\n\t\tmeta.format !== BOX_FORMAT_GROUP ||\n\t\ttypeof meta.author !== 'string' ||\n\t\tmeta.author.length === 0 ||\n\t\t!Array.isArray(meta.recipients) ||\n\t\tmeta.recipients.length === 0 ||\n\t\ttypeof meta.iv !== 'string'\n\t) {\n\t\tthrow new SelfstoreError(\n\t\t\t'BAD_FORMAT',\n\t\t\t'Missing or malformed group encryption entries or parameters.'\n\t\t);\n\t}\n\t// Membership before any crypto: a valid signature over an attacker-chosen\n\t// author proves nothing.\n\tif (!group.authors.includes(meta.author)) {\n\t\tthrow new SelfstoreError(\n\t\t\t'SIGNATURE_INVALID',\n\t\t\t'Copy author is not a trusted group member (check the membership manifest).'\n\t\t);\n\t}\n\tif (!(await verifyBox(metaBytes, dataEnc, sig, meta.author))) {\n\t\tthrow new SelfstoreError('SIGNATURE_INVALID', 'Copy signature verification failed.');\n\t}\n\tconst dataKey = await openDataKey(meta.recipients, group.identity);\n\tconst inner = await gcmOpenRaw(dataKey, meta.iv, dataEnc);\n\tconst r = await unpackWithSidecar(inner);\n\treturn { ...r, author: meta.author };\n}\n\n/** Read a backup into a snapshot, ignoring the sync sidecar. */\nexport async function readBox(bytes: Uint8Array, password?: string): Promise<Snapshot> {\n\treturn (await readBoxWithSync(bytes, password)).snapshot;\n}\n\nfunction metaOf(entries: Record<string, Uint8Array>): Header {\n\tconst metaBytes = entries[META];\n\tif (!metaBytes) throw new SelfstoreError('BAD_FORMAT', 'Missing backup metadata.');\n\tconst meta = JSON.parse(utf8.dec.decode(metaBytes)) as Header;\n\tif ((meta.format ?? BOX_FORMAT_PLAIN) > BOX_FORMAT_ENVELOPE_AUTH) {\n\t\tthrow new SelfstoreError(\n\t\t\t'UNSUPPORTED_VERSION',\n\t\t\t`Backup format v${meta.format} is newer than this reader supports.`\n\t\t);\n\t}\n\tassertKnownCrypto(meta);\n\treturn meta;\n}\n","// Public types. selfstore is schema-agnostic: named collections of opaque\n// documents plus binary files. The app owns the schema and its migrations.\n\n/** A binary attachment (image, PDF, any bytes). */\nexport interface SnapshotFile {\n\tid: string;\n\tname: string;\n\tmime: string;\n\tbytes: Uint8Array;\n}\n\n/**\n * Collection names starting with `__` are reserved for library bookkeeping\n * (createLocalStore keeps its sync metadata under `__store`). The fluent\n * `backup()` rejects them and `read()` strips them.\n */\nexport const RESERVED_COLLECTION_PREFIX = '__';\n\n/** Everything a backup carries. Collection names must not start with `__`. */\nexport interface Snapshot {\n\tcollections: Record<string, unknown[]>;\n\tfiles: SnapshotFile[];\n}\n\n/** Key-derivation parameters. Stored cleartext so older files still decode. */\nexport interface KdfParams {\n\talgo: 'argon2id';\n\tsalt: string;\n\tm: number;\n\tt: number;\n\tp: number;\n}\n\n/**\n * One wrap of the random data key under one password; any slot's password\n * opens the backup. Adding or removing a slot leaves the data key alone.\n * Changing a password rotates the data key (setEncryption) - that is the\n * operation that actually revokes access.\n */\nexport interface PasswordSlot {\n\t/** Random id so an app can list or drop a slot without knowing its\n\t * password. Cleartext, not authenticated. */\n\tid: string;\n\t/** Absent on password slots, so files written before external keys\n\t * existed still parse. */\n\tkind?: 'password';\n\tkdf: KdfParams;\n\t/** 96-bit IV of the wrap, base64. */\n\tiv: string;\n\t/** AES-256-GCM(KEK, dataKey) with tag appended: 48 bytes, base64. */\n\twrapped: string;\n}\n\n/**\n * Data key wrapped under a caller-supplied 32-byte secret (passkey PRF,\n * hardware token). The library never performs the WebAuthn or hardware\n * exchange; the app derives the secret and hands it in. KEK is\n * HKDF-SHA256 of the secret - no Argon2, the input is already\n * high-entropy. Shares the `keys[]` table with password slots, so a\n * backup can hold e.g. a passkey plus a recovery password.\n */\nexport interface ExternalSlot {\n\tid: string;\n\tkind: 'external';\n\t/** App-owned locator for re-deriving the secret (say, a WebAuthn\n\t * credential id). Cleartext, never a security input. */\n\tkeyRef: string;\n\t/** 96-bit IV of the wrap, base64. */\n\tiv: string;\n\t/** AES-256-GCM(KEK, dataKey) with tag appended: 48 bytes, base64. */\n\twrapped: string;\n}\n\nexport type KeySlot = PasswordSlot | ExternalSlot;\n\n/**\n * Group mode (format 2): the per-publication AES key wrapped for one\n * recipient. `kid` = first 8 bytes of SHA-256(recipient X25519 public),\n * base64 - a locator, not a name. `epk` is the ephemeral X25519 public;\n * `wrap` is AES-256-GCM(dataKey) under HKDF of the ECDH shared secret.\n */\nexport interface RecipientStanza {\n\tkid: string;\n\tepk: string;\n\tiv: string;\n\twrap: string;\n}\n\n/**\n * Cleartext metadata: the `meta.json` entry of the ZIP.\n *\n * app/appVersion/createdAt are cosmetic and unauthenticated - never base a\n * security decision on them. In password-envelope mode the wrapped data key\n * authenticates the payload; in group mode the author's Ed25519 signature\n * (sig.bin) covers the exact meta.json and data.enc bytes.\n */\nexport interface Header {\n\t/** Format generation: 2 = plain ZIP, 3 = group, 4 = password envelope.\n\t * Absent means 2. */\n\tformat?: number;\n\tapp: string;\n\t/** Writing app's release version, informational. */\n\tappVersion?: string;\n\t/** Writing app's data schema version (createLocalStore `version`).\n\t * Distinct from appVersion: this one drives migration. */\n\tschemaVersion?: number;\n\tcreatedAt: string;\n\tencryption: 'none' | 'aes-256-gcm';\n\t/** 96-bit IV of data.enc, base64. Present when encrypted. */\n\tiv?: string;\n\t/** Group mode: data-key envelope scheme ('x25519-hkdf-sha256'). */\n\tkeying?: string;\n\t/** Group mode: author's Ed25519 public key. Trust only after verifying\n\t * sig.bin and membership in the group manifest. */\n\tauthor?: string;\n\t/** Group mode: one data-key envelope per recipient. */\n\trecipients?: RecipientStanza[];\n\t/** Password envelope only: the data key wrapped once per password.\n\t * Present iff format === 4. */\n\tkeys?: KeySlot[];\n}\n\n/** Options for writing a backup. */\nexport interface EncodeOptions {\n\tapp: string;\n\tappVersion?: string;\n\tschemaVersion?: number;\n\t/** Omit or leave empty for an unencrypted backup (a plain, readable ZIP). */\n\tpassword?: string;\n\t/** Group mode: encrypt to these recipients (raw X25519 publics, base64)\n\t * under a fresh data key and sign with the author's Ed25519 key.\n\t * Mutually exclusive with `password`. */\n\tgroup?: {\n\t\trecipients: string[];\n\t\tsign: { pub: string; priv: string };\n\t};\n\t/** README text shipped inside an encrypted backup, for whoever opens the\n\t * ZIP by hand. Defaults to a neutral note; apps should brand it. */\n\treadme?: string;\n\t/** Rewrite path: reuse this exact envelope (held data key + slot table\n\t * from the last read) so a writer that knows only one password keeps\n\t * every other slot. Mutually exclusive with `password` and `group`. */\n\tenvelope?: { dataKey: Uint8Array; slots: KeySlot[] };\n}\n","// A password strength policy the store can enforce and a UI can preview from\n// the same rules. Deliberately small: length plus character-class requirements,\n// no dictionary or breach checks (those need data the library does not ship).\n// Unicode-aware so an accented letter counts as a letter and an emoji counts as\n// one character, not two.\n\nexport interface PasswordPolicy {\n\t/** Minimum length in code points (so an emoji counts as one). Default 0. */\n\tminLength?: number;\n\t/** Require at least one lowercase letter (any script with case). */\n\trequireLowercase?: boolean;\n\t/** Require at least one uppercase letter (any script with case). */\n\trequireUppercase?: boolean;\n\t/** Require at least one digit. */\n\trequireDigit?: boolean;\n\t/** Require at least one symbol: anything that is not a letter or a number. */\n\trequireSymbol?: boolean;\n}\n\n/** A stable code for each unmet requirement, so the app maps it to its own copy\n * (like the error codes). `minLength` covers the length rule. */\nexport type PasswordRequirement = 'minLength' | 'lowercase' | 'uppercase' | 'digit' | 'symbol';\n\nexport interface PasswordCheck {\n\tok: boolean;\n\t/** The requirements the password fails, in a stable order. Empty when ok. */\n\tunmet: PasswordRequirement[];\n}\n\n/** Measure a password against a policy. Pure and synchronous: the same call\n * drives a live strength hint in the UI and the store's enforcement. An empty\n * or absent policy passes everything. */\nexport function checkPasswordPolicy(password: string, policy: PasswordPolicy): PasswordCheck {\n\tconst unmet: PasswordRequirement[] = [];\n\t// Count code points, not UTF-16 units, so a password of emoji is not\n\t// over-counted and a min-length rule stays intuitive.\n\tif ([...password].length < (policy.minLength ?? 0)) unmet.push('minLength');\n\tif (policy.requireLowercase && !/\\p{Ll}/u.test(password)) unmet.push('lowercase');\n\tif (policy.requireUppercase && !/\\p{Lu}/u.test(password)) unmet.push('uppercase');\n\tif (policy.requireDigit && !/\\p{Nd}/u.test(password)) unmet.push('digit');\n\tif (policy.requireSymbol && !/[^\\p{L}\\p{N}]/u.test(password)) unmet.push('symbol');\n\treturn { ok: unmet.length === 0, unmet };\n}\n","// Fluent facade over the functional API. The chain is staged so an illegal\n// call order does not compile: backup() exposes only .as(), and .withReadme()\n// appears only after .encryptedWith() (the readme ships inside the encrypted\n// ZIP, so it means nothing on a plain one).\n//\n// const blob = await backup(snapshot).as('my-app', '1.2.0').encryptedWith(pw).toBlob();\n// const snap = await restore(file).withPassword(pw).read();\n\nimport type { Snapshot, Header, EncodeOptions } from './types';\nimport { RESERVED_COLLECTION_PREFIX } from './types';\nimport { writeBox, readBox, readBoxMeta, asBytes, BACKUP_MIME, BACKUP_EXTENSION } from './box';\n\n/** Start writing a backup. Naming the app via .as() is the required next step. */\nexport function backup(snapshot: Snapshot): BackupDraft {\n\treturn new BackupDraft(snapshot);\n}\n\n/** Start reading a backup (a picked File, a fetched Blob, or raw bytes). */\nexport function restore(input: Blob | Uint8Array): RestoreBuilder {\n\treturn new RestoreBuilder(input);\n}\n\n// Validation lives in the constructor, not the factory, so direct\n// construction cannot bypass it.\nexport class BackupDraft {\n\tconstructor(private readonly snapshot: Snapshot) {\n\t\tfor (const name of Object.keys(snapshot.collections)) {\n\t\t\tif (name.startsWith(RESERVED_COLLECTION_PREFIX)) {\n\t\t\t\tthrow new TypeError(\n\t\t\t\t\t`backup(): collection \"${name}\" uses the reserved \"${RESERVED_COLLECTION_PREFIX}\" prefix (library bookkeeping) - rename it`\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t}\n\n\t/** Name the writing app (stored cleartext in the backup's metadata). */\n\tas(app: string, appVersion?: string): BackupBuilder {\n\t\treturn new BackupBuilder(this.snapshot, { app, appVersion });\n\t}\n}\n\nexport class BackupBuilder {\n\tconstructor(\n\t\tprotected readonly snapshot: Snapshot,\n\t\tprotected readonly opts: EncodeOptions\n\t) {}\n\n\t/** Encrypt the backup (AES-256-GCM over an Argon2id-derived key). */\n\tencryptedWith(password: string): EncryptedBackupBuilder {\n\t\treturn new EncryptedBackupBuilder(this.snapshot, { ...this.opts, password });\n\t}\n\n\t/** The backup as raw bytes. */\n\tasync toBytes(): Promise<Uint8Array> {\n\t\treturn writeBox(this.snapshot, this.opts);\n\t}\n\n\t/** The backup as a Blob, ready to upload or hand to a save dialog. */\n\tasync toBlob(): Promise<Blob> {\n\t\treturn new Blob([(await this.toBytes()) as BlobPart], { type: BACKUP_MIME });\n\t}\n\n\t/** Save the backup to disk (File System Access API, else a download).\n\t * Browser-only terminal; defaults to `<app>-<date>.zip`. False when the\n\t * user closed the save dialog: nothing was written, so do not tell them\n\t * they have a backup.\n\t *\n\t * Asks WHERE first, encrypts after. The save dialog needs the transient\n\t * activation of the click that led here, and building an encrypted backup\n\t * outlives it (Argon2id is deliberately slow): encrypting first made the\n\t * browser refuse the dialog and silently download instead, turning a file\n\t * the user chose into one they have to go and find. */\n\tasync toDisk(filename?: string): Promise<boolean> {\n\t\tconst name =\n\t\t\tfilename ?? `${this.opts.app}-${new Date().toISOString().slice(0, 10)}${BACKUP_EXTENSION}`;\n\t\tconst { pickSaveHandle, writeToHandle, downloadBlob } = await import('./targets/local');\n\t\tconst cible = await pickSaveHandle(name);\n\t\tif (cible === 'CANCELLED') return false;\n\t\tconst blob = await this.toBlob();\n\t\tif (cible) {\n\t\t\tawait writeToHandle(cible.handle, blob);\n\t\t\treturn true;\n\t\t}\n\t\tdownloadBlob(blob, name);\n\t\treturn true;\n\t}\n}\n\nexport class EncryptedBackupBuilder extends BackupBuilder {\n\tconstructor(snapshot: Snapshot, opts: EncodeOptions) {\n\t\tif (!opts.password) {\n\t\t\tthrow new TypeError(\n\t\t\t\t'EncryptedBackupBuilder: a password is required - use backup(snapshot).as(app).encryptedWith(password)'\n\t\t\t);\n\t\t}\n\t\tsuper(snapshot, opts);\n\t}\n\n\t/** Brand the README shipped inside the encrypted ZIP. */\n\twithReadme(text: string): EncryptedBackupBuilder {\n\t\treturn new EncryptedBackupBuilder(this.snapshot, { ...this.opts, readme: text });\n\t}\n}\n\nexport class RestoreBuilder {\n\tprivate password?: string;\n\n\tconstructor(private readonly input: Blob | Uint8Array) {}\n\n\t/** Accepts undefined so callers can pass straight through from an optional field. */\n\twithPassword(password?: string): this {\n\t\tthis.password = password || undefined;\n\t\treturn this;\n\t}\n\n\t/** The cleartext metadata (app, date, encryption), without decrypting. */\n\tasync meta(): Promise<Header> {\n\t\treturn readBoxMeta(await asBytes(this.input));\n\t}\n\n\t/** True if reading will require a password. */\n\tasync isEncrypted(): Promise<boolean> {\n\t\treturn (await this.meta()).encryption !== 'none';\n\t}\n\n\t/** Read the backup into a snapshot. Reserved `__*` collections (library\n\t * bookkeeping) are stripped. PASSWORD_REQUIRED when encrypted and no\n\t * password given, DECRYPT_FAILED on a wrong one. */\n\tasync read(): Promise<Snapshot> {\n\t\tconst snap = await readBox(await asBytes(this.input), this.password);\n\t\tconst collections = Object.fromEntries(\n\t\t\tObject.entries(snap.collections).filter(\n\t\t\t\t([name]) => !name.startsWith(RESERVED_COLLECTION_PREFIX)\n\t\t\t)\n\t\t);\n\t\treturn { collections, files: snap.files };\n\t}\n}\n","// The backup format: Snapshot in, portable ZIP out, and back. Plain archive\n// without a password, AES-256-GCM (over Argon2id) with one.\n//\n// const blob = await exportSnapshot(snapshot, { app: 'my-app', password });\n// const back = await importSnapshot(blob, { password });\n//\n// or fluent, same engine:\n//\n// const blob = await backup(snapshot).as('my-app').encryptedWith(password).toBlob();\n// const back = await restore(blob).withPassword(password).read();\n\nimport type { Snapshot, Header, EncodeOptions } from './types';\nimport { writeBox, readBox, readBoxMeta, asBytes } from './box';\n\nexport type {\n\tSnapshot,\n\tSnapshotFile,\n\tHeader,\n\tKdfParams,\n\tRecipientStanza,\n\tEncodeOptions,\n\tPasswordSlot,\n\tExternalSlot,\n\tKeySlot\n} from './types';\nexport { RESERVED_COLLECTION_PREFIX } from './types';\nexport {\n\tSelfstoreError,\n\tAuthExpiredError,\n\tisSelfstoreError,\n\tisAuthExpired,\n\terrorLabelKey,\n\ttype SelfstoreErrorCode\n} from './errors';\nexport { BACKUP_EXTENSION, BACKUP_MIME } from './box';\nexport {\n\tcheckPasswordPolicy,\n\ttype PasswordPolicy,\n\ttype PasswordCheck,\n\ttype PasswordRequirement\n} from './password-policy';\nexport {\n\tbackup,\n\trestore,\n\ttype BackupDraft,\n\ttype BackupBuilder,\n\ttype EncryptedBackupBuilder,\n\ttype RestoreBuilder\n} from './fluent';\n\n/** Serialize a snapshot to a portable backup blob (compressed; encrypted if a password is given). */\nexport async function exportSnapshot(snapshot: Snapshot, opts: EncodeOptions): Promise<Blob> {\n\treturn new Blob([(await writeBox(snapshot, opts)) as BlobPart], { type: 'application/zip' });\n}\n\n/** Read a backup blob (or raw bytes) back into a snapshot. */\nexport async function importSnapshot(\n\tinput: Blob | Uint8Array,\n\topts: { password?: string } = {}\n): Promise<Snapshot> {\n\treturn readBox(await asBytes(input), opts.password);\n}\n\n/** Peek at a backup's cleartext metadata (app, date, whether it's encrypted) without decrypting. */\nexport async function inspect(input: Blob | Uint8Array): Promise<Header> {\n\treturn readBoxMeta(await asBytes(input));\n}\n\n/** True if the backup needs a password to import. */\nexport async function isEncrypted(input: Blob | Uint8Array): Promise<boolean> {\n\treturn (await inspect(input)).encryption !== 'none';\n}\n\n/** Re-encrypt under a new password. `from` opens the file, `to` protects the\n * result; omit `to` for an unencrypted backup. */\nexport async function changePassword(\n\tinput: Blob | Uint8Array,\n\topts: { from?: string; to?: string; readme?: string }\n): Promise<Blob> {\n\tconst bytes = await asBytes(input);\n\tconst meta = await readBoxMeta(bytes);\n\tconst snapshot = await readBox(bytes, opts.from);\n\treturn exportSnapshot(snapshot, {\n\t\tapp: meta.app,\n\t\tappVersion: meta.appVersion,\n\t\tschemaVersion: meta.schemaVersion,\n\t\tpassword: opts.to,\n\t\treadme: opts.readme\n\t});\n}\n"]}
|