selfstore 1.8.33 → 1.8.34
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/dist/advanced.d.ts +9 -9
- package/dist/advanced.js +3 -3
- package/dist/backups.d.ts +4 -4
- package/dist/backups.js +1 -1
- package/dist/{cache-BEv-Har2.d.ts → cache-D8Z4u5Jz.d.ts} +0 -2
- package/dist/chunk-DJ6YPMBK.js +10 -0
- package/dist/chunk-DJ6YPMBK.js.map +1 -0
- package/dist/{chunk-7U5XMKVP.js → chunk-E4S77JSN.js} +34 -6
- package/dist/chunk-E4S77JSN.js.map +1 -0
- package/dist/{chunk-AFBPJ7KA.js → chunk-HQSK7HK3.js} +37 -15
- package/dist/chunk-HQSK7HK3.js.map +1 -0
- package/dist/{chunk-JDDWWQEB.js → chunk-TNYL6FNW.js} +6 -6
- package/dist/{chunk-JDDWWQEB.js.map → chunk-TNYL6FNW.js.map} +1 -1
- package/dist/{chunk-CI5JYSWE.js → chunk-VRWAV3GI.js} +4 -4
- package/dist/{chunk-CI5JYSWE.js.map → chunk-VRWAV3GI.js.map} +1 -1
- package/dist/{chunk-Q3NO477P.js → chunk-XAONTUEC.js} +3 -3
- package/dist/{chunk-Q3NO477P.js.map → chunk-XAONTUEC.js.map} +1 -1
- package/dist/{connect-CDazV36v.d.ts → connect-B9f58dFZ.d.ts} +4 -4
- package/dist/{drive-CwlKlowN.d.ts → drive-D5YLXmp_.d.ts} +2 -2
- package/dist/flows.d.ts +4 -4
- package/dist/flows.js +3 -3
- package/dist/groups.d.ts +2 -2
- package/dist/index.d.ts +9 -9
- package/dist/index.js +22 -12
- package/dist/index.js.map +1 -1
- package/dist/{local-DytMg5GC.d.ts → local-CmS0xFkK.d.ts} +2 -2
- package/dist/{local-S3WUZSDX.js → local-D72LJOKX.js} +4 -4
- package/dist/{local-S3WUZSDX.js.map → local-D72LJOKX.js.map} +1 -1
- package/dist/{manager-CnlIs-hY.d.ts → manager-DCV_7B6M.d.ts} +2 -2
- package/dist/{manager-drive-KSZIHMLF.js → manager-drive-TEYNJVUU.js} +3 -3
- package/dist/{manager-drive-KSZIHMLF.js.map → manager-drive-TEYNJVUU.js.map} +1 -1
- package/dist/proof.d.ts +6 -6
- package/dist/proof.js +1 -1
- package/dist/{s3-ORDHS7G3.js → s3-KZPMSI4J.js} +3 -3
- package/dist/s3-KZPMSI4J.js.map +1 -0
- package/dist/{s3-Bexn9kzT.d.ts → s3-mHLrEywJ.d.ts} +2 -2
- package/dist/{simple-cLzoTGhN.d.ts → simple-Bnh2j78j.d.ts} +6 -6
- package/dist/{store-DSPbCCtM.d.ts → store-DSvZwwr1.d.ts} +11 -3
- package/dist/{webdav-PXKELPA4.js → webdav-5YKO47EE.js} +3 -3
- package/dist/{webdav-PXKELPA4.js.map → webdav-5YKO47EE.js.map} +1 -1
- package/dist/widgets.d.ts +5 -5
- package/dist/widgets.js +4 -4
- package/package.json +1 -1
- package/dist/chunk-7U5XMKVP.js.map +0 -1
- package/dist/chunk-AFBPJ7KA.js.map +0 -1
- package/dist/chunk-SJ3PKQPR.js +0 -10
- package/dist/chunk-SJ3PKQPR.js.map +0 -1
- package/dist/s3-ORDHS7G3.js.map +0 -1
package/dist/advanced.d.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import { B as BackupTarget } from './store-
|
|
2
|
-
export { A as AuthExpiredError, f as BuiltinTargetKind, E as EncodeOptions, H as Header, K as KdfParams, L as LocalStore, g as LocalStoreOptions, e as LocalStoreState, M as MirrorState, h as Mode, i as PasswordCheck, b as PasswordPolicy, j as PasswordRequirement, P as PeerSource, k as PeerState, R as RESERVED_COLLECTION_PREFIX, l as RESERVED_STORE_MODES, m as RecipientStanza, n as ReplicaState, o as SelfstoreError, p as SelfstoreErrorCode, q as Severity, a as Snapshot, c as SnapshotFile, r as StatusAction, d as StatusDescriptor, s as StatusInput, t as StorageState, S 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-
|
|
3
|
-
import { K as KV } from './cache-
|
|
4
|
-
export { C as CachedFile, L as LocalCache, e as LockableCache, i as indexedDbCache, f as isLockableCache, m as memoryCache } from './cache-
|
|
5
|
-
export { G as GisDriveAuthOptions, a as GisTokenPersistence, d as datedName, e as exportSnapshot, g as gisDriveAuth, i as importSnapshot, b as inspect, c as isEncrypted, p as pickFromDisk, f as preloadGoogleIdentity, s as saveToDisk } from './local-
|
|
6
|
-
import { c as connect$2, f as fromSession$2, a as connect$3, b as fromSession$3, p as peer } from './s3-
|
|
7
|
-
export { S as S3Config, d as S3ConnectOptions, W as WebdavConfig, e as WebdavConnectOptions, g as WebdavPeerOptions } from './s3-
|
|
8
|
-
import { c as connect$1, f as fromSession$1, p as preview, b as adopt, d as findOrCreateOwnFile, l as listBackups, e as createBackup, g as deleteBackup, r as renameBackup, h as account, i as createCompanion, s as share, u as unshare, o as owner, j as secondary } from './drive-
|
|
9
|
-
export { k as DriveAccount, a as DriveAuth, m as DriveBackupInfo, D as DriveOptions } from './drive-
|
|
1
|
+
import { B as BackupTarget } from './store-DSvZwwr1.js';
|
|
2
|
+
export { A as AuthExpiredError, f as BuiltinTargetKind, E as EncodeOptions, H as Header, K as KdfParams, L as LocalStore, g as LocalStoreOptions, e as LocalStoreState, M as MirrorState, h as Mode, i as PasswordCheck, b as PasswordPolicy, j as PasswordRequirement, P as PeerSource, k as PeerState, R as RESERVED_COLLECTION_PREFIX, l as RESERVED_STORE_MODES, m as RecipientStanza, n as ReplicaState, o as SelfstoreError, p as SelfstoreErrorCode, q as Severity, a as Snapshot, c as SnapshotFile, r as StatusAction, d as StatusDescriptor, s as StatusInput, t as StorageState, S 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-DSvZwwr1.js';
|
|
3
|
+
import { K as KV } from './cache-D8Z4u5Jz.js';
|
|
4
|
+
export { C as CachedFile, L as LocalCache, e as LockableCache, i as indexedDbCache, f as isLockableCache, m as memoryCache } from './cache-D8Z4u5Jz.js';
|
|
5
|
+
export { G as GisDriveAuthOptions, a as GisTokenPersistence, d as datedName, e as exportSnapshot, g as gisDriveAuth, i as importSnapshot, b as inspect, c as isEncrypted, p as pickFromDisk, f as preloadGoogleIdentity, s as saveToDisk } from './local-CmS0xFkK.js';
|
|
6
|
+
import { c as connect$2, f as fromSession$2, a as connect$3, b as fromSession$3, p as peer } from './s3-mHLrEywJ.js';
|
|
7
|
+
export { S as S3Config, d as S3ConnectOptions, W as WebdavConfig, e as WebdavConnectOptions, g as WebdavPeerOptions } from './s3-mHLrEywJ.js';
|
|
8
|
+
import { c as connect$1, f as fromSession$1, p as preview, b as adopt, d as findOrCreateOwnFile, l as listBackups, e as createBackup, g as deleteBackup, r as renameBackup, h as account, i as createCompanion, s as share, u as unshare, o as owner, j as secondary } from './drive-D5YLXmp_.js';
|
|
9
|
+
export { k as DriveAccount, a as DriveAuth, m as DriveBackupInfo, D as DriveOptions } from './drive-D5YLXmp_.js';
|
|
10
10
|
import './sync.js';
|
|
11
11
|
|
|
12
12
|
/**
|
package/dist/advanced.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { peer, fromSession as fromSession$3, connect as connect$3 } from './chunk-GHSHIDF3.js';
|
|
2
2
|
import { fromSession as fromSession$2, connect as connect$2 } from './chunk-IDW3WQKF.js';
|
|
3
|
-
export { RESERVED_STORE_MODES, createLocalStore, deriveStatus, gisDriveAuth, indexedDbCache, isLockableCache, isReservedStoreMode, memoryCache, preloadGoogleIdentity } from './chunk-
|
|
3
|
+
export { RESERVED_STORE_MODES, createLocalStore, deriveStatus, gisDriveAuth, indexedDbCache, isLockableCache, isReservedStoreMode, memoryCache, preloadGoogleIdentity } from './chunk-HQSK7HK3.js';
|
|
4
4
|
import './chunk-A2RRJLIF.js';
|
|
5
|
-
export { datedName, pickFromDisk, saveToDisk } from './chunk-
|
|
5
|
+
export { datedName, pickFromDisk, saveToDisk } from './chunk-XAONTUEC.js';
|
|
6
6
|
import './chunk-FJGWTBAL.js';
|
|
7
7
|
import { openExisting, isOpenSupported, isSupported, fromSession as fromSession$1, connect as connect$1 } from './chunk-HVHQLPP6.js';
|
|
8
8
|
import { FILE_ID_KEY, secondary, owner, unshare, share, createCompanion, account, renameBackup, deleteBackup, createBackup, listBackups, findOrCreateOwnFile, adopt, preview, fromSession, connect } from './chunk-IUVOVVEA.js';
|
|
9
9
|
import './chunk-LF3CC2P3.js';
|
|
10
|
-
export { RESERVED_COLLECTION_PREFIX, checkPasswordPolicy, exportSnapshot, importSnapshot, inspect, isEncrypted } from './chunk-
|
|
10
|
+
export { RESERVED_COLLECTION_PREFIX, checkPasswordPolicy, exportSnapshot, importSnapshot, inspect, isEncrypted } from './chunk-E4S77JSN.js';
|
|
11
11
|
import './chunk-QBCU7OOP.js';
|
|
12
12
|
import './chunk-F4ZZEJ5H.js';
|
|
13
13
|
import './chunk-H2T4S3KM.js';
|
package/dist/backups.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { B as BackupsHost } from './manager-
|
|
2
|
-
export { a as BackupFileInfo, b as BackupOwner, c as BackupRow, d as BackupsErrorCode, e as BackupsKeys, f as BackupsManager, g as BackupsNaming, h as BackupsSnapshot, K as KnownBackups, i as createBackupsManager } from './manager-
|
|
1
|
+
import { B as BackupsHost } from './manager-DCV_7B6M.js';
|
|
2
|
+
export { a as BackupFileInfo, b as BackupOwner, c as BackupRow, d as BackupsErrorCode, e as BackupsKeys, f as BackupsManager, g as BackupsNaming, h as BackupsSnapshot, K as KnownBackups, i as createBackupsManager } from './manager-DCV_7B6M.js';
|
|
3
3
|
import { DriveOptions } from './advanced.js';
|
|
4
|
-
import './cache-
|
|
5
|
-
import './store-
|
|
4
|
+
import './cache-D8Z4u5Jz.js';
|
|
5
|
+
import './store-DSvZwwr1.js';
|
|
6
6
|
import './sync.js';
|
|
7
7
|
import './machine-Dhbx3Eut.js';
|
|
8
8
|
|
package/dist/backups.js
CHANGED
|
@@ -2,7 +2,7 @@ export { createBackupsManager, driveBackupsHost } from './chunk-MDEEAQSU.js';
|
|
|
2
2
|
import './chunk-2BNTCXQN.js';
|
|
3
3
|
import './chunk-IUVOVVEA.js';
|
|
4
4
|
import './chunk-LF3CC2P3.js';
|
|
5
|
-
import './chunk-
|
|
5
|
+
import './chunk-E4S77JSN.js';
|
|
6
6
|
import './chunk-F4ZZEJ5H.js';
|
|
7
7
|
import './chunk-H2T4S3KM.js';
|
|
8
8
|
import './chunk-WXI2NAPA.js';
|
|
@@ -128,8 +128,6 @@ declare function isLockableCache(cache: LocalCache): cache is LockableCache;
|
|
|
128
128
|
/** In-memory LocalCache for tests and SSR paths where IndexedDB does not
|
|
129
129
|
* exist. Not encrypted - nothing is at rest. */
|
|
130
130
|
declare function memoryCache(): LocalCache;
|
|
131
|
-
/** The default browser cache. `name` scopes the database to one app, so two
|
|
132
|
-
* apps on the same origin never collide. */
|
|
133
131
|
declare function indexedDbCache(name: string, opts: {
|
|
134
132
|
lock: true;
|
|
135
133
|
}): LockableCache;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// src/simple/bytes.ts
|
|
2
|
+
async function toBytes(input) {
|
|
3
|
+
if (input instanceof Uint8Array) return input.slice();
|
|
4
|
+
if (input instanceof ArrayBuffer) return new Uint8Array(input.slice(0));
|
|
5
|
+
return new Uint8Array(await input.arrayBuffer());
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export { toBytes };
|
|
9
|
+
//# sourceMappingURL=chunk-DJ6YPMBK.js.map
|
|
10
|
+
//# sourceMappingURL=chunk-DJ6YPMBK.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/simple/bytes.ts"],"names":[],"mappings":";AAQA,eAAsB,QAAQ,KAAA,EAAuC;AAGpE,EAAA,IAAI,KAAA,YAAiB,UAAA,EAAY,OAAO,KAAA,CAAM,KAAA,EAAM;AACpD,EAAA,IAAI,KAAA,YAAiB,aAAa,OAAO,IAAI,WAAW,KAAA,CAAM,KAAA,CAAM,CAAC,CAAC,CAAA;AACtE,EAAA,OAAO,IAAI,UAAA,CAAW,MAAM,KAAA,CAAM,aAAa,CAAA;AAChD","file":"chunk-DJ6YPMBK.js","sourcesContent":["// A leaf on purpose: the proof entry needs these two and must not pull the\n// whole store graph in with them.\n\n/** What a file body may be handed in as. */\nexport type FileBytes = Uint8Array | ArrayBuffer | Blob;\n\n/** Blob and ArrayBuffer are accepted because that is what a picker, a fetch and\n * a canvas hand back; the snapshot only ever holds Uint8Array. */\nexport async function toBytes(input: FileBytes): Promise<Uint8Array> {\n\t// Copied: a file is stored under the hash of its bytes, and a caller writing\n\t// into a shared buffer later would change the file under that id.\n\tif (input instanceof Uint8Array) return input.slice();\n\tif (input instanceof ArrayBuffer) return new Uint8Array(input.slice(0));\n\treturn new Uint8Array(await input.arrayBuffer());\n}\n"]}
|
|
@@ -71,10 +71,29 @@ function parseJsonEntry(bytes, name) {
|
|
|
71
71
|
throw new SelfstoreError("BAD_FORMAT", `Unreadable ${name}.`);
|
|
72
72
|
}
|
|
73
73
|
}
|
|
74
|
+
var isObject = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
75
|
+
function checkedManifest(raw) {
|
|
76
|
+
const bad = (why) => {
|
|
77
|
+
throw new SelfstoreError("BAD_FORMAT", `Malformed manifest: ${why}.`);
|
|
78
|
+
};
|
|
79
|
+
if (!isObject(raw)) return bad("not an object");
|
|
80
|
+
const { collections, files } = raw;
|
|
81
|
+
if (collections !== void 0) {
|
|
82
|
+
if (!isObject(collections)) bad("collections is not an object");
|
|
83
|
+
for (const [name, rows] of Object.entries(collections)) {
|
|
84
|
+
if (!Array.isArray(rows) || !rows.every(isObject))
|
|
85
|
+
bad(`collection "${name}" is not a list of records`);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
if (files !== void 0 && (!Array.isArray(files) || !files.every((f) => isObject(f) && typeof f.id === "string"))) {
|
|
89
|
+
bad("files is not a list of files with an id");
|
|
90
|
+
}
|
|
91
|
+
return raw;
|
|
92
|
+
}
|
|
74
93
|
function entriesToSnapshot(entries) {
|
|
75
94
|
const manifestBytes = entries[MANIFEST];
|
|
76
95
|
if (!manifestBytes) throw new SelfstoreError("BAD_FORMAT", "Missing manifest.");
|
|
77
|
-
const manifest = parseJsonEntry(manifestBytes, MANIFEST);
|
|
96
|
+
const manifest = checkedManifest(parseJsonEntry(manifestBytes, MANIFEST));
|
|
78
97
|
const files = (manifest.files ?? []).map((m) => {
|
|
79
98
|
const bytes = entries[`files/${m.id}`];
|
|
80
99
|
if (bytes === void 0) {
|
|
@@ -169,7 +188,10 @@ async function writeBox(snapshot, opts, sidecar) {
|
|
|
169
188
|
keying: GROUP_KEYING,
|
|
170
189
|
author: opts.group.sign.pub,
|
|
171
190
|
recipients: await sealDataKey(dataKey, opts.group.recipients),
|
|
172
|
-
iv: enc.iv
|
|
191
|
+
iv: enc.iv,
|
|
192
|
+
// Under the signature with the rest of meta.json: a host cannot move
|
|
193
|
+
// this copy to another group the author also belongs to.
|
|
194
|
+
...opts.group.id === void 0 ? {} : { group: opts.group.id }
|
|
173
195
|
};
|
|
174
196
|
const metaBytes = utf82.enc.encode(JSON.stringify(meta));
|
|
175
197
|
return zip(
|
|
@@ -317,7 +339,7 @@ async function readGroupBox(entries, meta, group) {
|
|
|
317
339
|
const metaBytes = entries[META];
|
|
318
340
|
const dataEnc = entries[DATA];
|
|
319
341
|
const sig = entries[SIG];
|
|
320
|
-
if (!metaBytes || !dataEnc || !sig || meta.format !== BOX_FORMAT_GROUP || typeof meta.author !== "string" || meta.author.length === 0 || !Array.isArray(meta.recipients) || meta.recipients.length === 0 || typeof meta.iv !== "string") {
|
|
342
|
+
if (!metaBytes || !dataEnc || !sig || meta.format !== BOX_FORMAT_GROUP || typeof meta.author !== "string" || meta.author.length === 0 || !Array.isArray(meta.recipients) || meta.recipients.length === 0 || typeof meta.iv !== "string" || meta.group !== void 0 && typeof meta.group !== "string") {
|
|
321
343
|
throw new SelfstoreError(
|
|
322
344
|
"BAD_FORMAT",
|
|
323
345
|
"Missing or malformed group encryption entries or parameters."
|
|
@@ -329,6 +351,12 @@ async function readGroupBox(entries, meta, group) {
|
|
|
329
351
|
"Copy author is not a trusted group member (check the membership manifest)."
|
|
330
352
|
);
|
|
331
353
|
}
|
|
354
|
+
if (group.group !== void 0 && meta.group !== void 0 && meta.group !== group.group) {
|
|
355
|
+
throw new SelfstoreError(
|
|
356
|
+
"SIGNATURE_INVALID",
|
|
357
|
+
"Copy was published for another group (group id mismatch)."
|
|
358
|
+
);
|
|
359
|
+
}
|
|
332
360
|
if (!await verifyBox(metaBytes, dataEnc, sig, meta.author)) {
|
|
333
361
|
throw new SelfstoreError("SIGNATURE_INVALID", "Copy signature verification failed.");
|
|
334
362
|
}
|
|
@@ -503,7 +531,7 @@ var BackupBuilder = class _BackupBuilder {
|
|
|
503
531
|
* the user chose into one they have to go and find. */
|
|
504
532
|
async toDisk(filename) {
|
|
505
533
|
const name = filename ?? `${this.opts.app}-${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}${BACKUP_EXTENSION}`;
|
|
506
|
-
const { pickSaveHandle, writeToHandle, downloadBlob } = await import('./local-
|
|
534
|
+
const { pickSaveHandle, writeToHandle, downloadBlob } = await import('./local-D72LJOKX.js');
|
|
507
535
|
const cible = await pickSaveHandle(name);
|
|
508
536
|
if (cible === "CANCELLED") return false;
|
|
509
537
|
const blob = await this.toBlob();
|
|
@@ -590,5 +618,5 @@ var RestoreBuilder = class {
|
|
|
590
618
|
};
|
|
591
619
|
|
|
592
620
|
export { BACKUP_EXTENSION, BACKUP_MIME, RESERVED_COLLECTION_PREFIX, asBytes, backup, changePassword, checkPasswordPolicy, exportSnapshot, importSnapshot, inspect, isEncrypted, readBoxWithSync, restore, writeBox };
|
|
593
|
-
//# sourceMappingURL=chunk-
|
|
594
|
-
//# sourceMappingURL=chunk-
|
|
621
|
+
//# sourceMappingURL=chunk-E4S77JSN.js.map
|
|
622
|
+
//# sourceMappingURL=chunk-E4S77JSN.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/index.ts","../src/selfstore/verify.ts","../src/selfstore/fluent.ts"],"names":["utf8"],"mappings":"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","file":"chunk-E4S77JSN.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/**\n * What readSidecar returns for a sync.json that is present but not JSON. Not\n * null: a reader that merges must not take a damaged history for a foreign\n * backup with none. A reader that only wants the data ignores it either way.\n */\nexport const UNREADABLE_SIDECAR: unique symbol = Symbol('selfstore.unreadableSidecar');\n\n/** The sync.json sidecar if present, null when absent, UNREADABLE_SIDECAR when not JSON. */\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 UNREADABLE_SIDECAR;\n\t}\n}\n\n/** Parse a JSON entry; BAD_FORMAT rather than a bare SyntaxError on damaged bytes. */\nexport function parseJsonEntry(bytes: Uint8Array, name: string): unknown {\n\ttry {\n\t\treturn JSON.parse(utf8.dec.decode(bytes));\n\t} catch {\n\t\tthrow new SelfstoreError('BAD_FORMAT', `Unreadable ${name}.`);\n\t}\n}\n\n/** Rebuild a snapshot from ZIP entries (the inverse of buildEntries). A file the\n * manifest lists without its entry is BAD_FORMAT: rebuilt as empty bytes it\n * would restore as a success and be written back as the loss. */\nconst isObject = (v: unknown): v is Record<string, unknown> =>\n\ttypeof v === 'object' && v !== null && !Array.isArray(v);\n\n/** The manifest's shape, checked before anything is applied: valid JSON that is\n * not a manifest would otherwise replace sound data with unusable collections. */\nfunction checkedManifest(raw: unknown): {\n\tcollections?: Record<string, unknown[]>;\n\tfiles?: FileMeta[];\n} {\n\tconst bad = (why: string): never => {\n\t\tthrow new SelfstoreError('BAD_FORMAT', `Malformed manifest: ${why}.`);\n\t};\n\tif (!isObject(raw)) return bad('not an object');\n\tconst { collections, files } = raw;\n\tif (collections !== undefined) {\n\t\tif (!isObject(collections)) bad('collections is not an object');\n\t\tfor (const [name, rows] of Object.entries(collections as Record<string, unknown>)) {\n\t\t\tif (!Array.isArray(rows) || !rows.every(isObject))\n\t\t\t\tbad(`collection \"${name}\" is not a list of records`);\n\t\t}\n\t}\n\tif (\n\t\tfiles !== undefined &&\n\t\t(!Array.isArray(files) || !files.every((f) => isObject(f) && typeof f.id === 'string'))\n\t) {\n\t\tbad('files is not a list of files with an id');\n\t}\n\treturn raw as { collections?: Record<string, unknown[]>; files?: FileMeta[] };\n}\n\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 = checkedManifest(parseJsonEntry(manifestBytes, MANIFEST));\n\tconst files: SnapshotFile[] = (manifest.files ?? []).map((m) => {\n\t\tconst bytes = entries[`files/${m.id}`];\n\t\tif (bytes === undefined) {\n\t\t\tthrow new SelfstoreError('BAD_FORMAT', `Archive is missing the bytes of file \"${m.id}\".`);\n\t\t}\n\t\treturn { ...m, bytes };\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\tparseJsonEntry,\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\t// Under the signature with the rest of meta.json: a host cannot move\n\t\t\t// this copy to another group the author also belongs to.\n\t\t\t...(opts.group.id === undefined ? {} : { group: opts.group.id })\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\t// Every extra secret gets its own slot around the SAME data key: either\n\t\t// one opens the file, and neither can be derived from the other. A rewrite\n\t\t// (`envelope`) already carries the slot table, so extras only apply when\n\t\t// minting a fresh envelope.\n\t\tconst slots = opts.envelope?.slots ?? [\n\t\t\tawait mintSlot(opts.password!, dataKey),\n\t\t\t...(await Promise.all((opts.extraSecrets ?? []).map((s) => mintSlot(s, dataKey))))\n\t\t];\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\t/** The reader's group id. A copy that names another group is refused\n\t * (SIGNATURE_INVALID) even when its author is a trusted member of both;\n\t * a copy written before the id existed carries none and still opens. */\n\tgroup?: 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\t(meta.group !== undefined && typeof meta.group !== '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\t// A member of two groups signs copies for both; the group id under the\n\t// signature is what stops a host from serving one group's copy to the other.\n\tif (group.group !== undefined && meta.group !== undefined && meta.group !== group.group) {\n\t\tthrow new SelfstoreError(\n\t\t\t'SIGNATURE_INVALID',\n\t\t\t'Copy was published for another group (group id mismatch).'\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 = parseJsonEntry(metaBytes, META) 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: 1 = plain ZIP, 2 = group, 3 = password envelope.\n\t * Absent means 1. */\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/** Group mode: the manifest's group id this copy was published for, under\n\t * the author's signature. A reader that knows its group refuses another\n\t * group's copy, however well signed. Absent on copies written before it\n\t * existed. */\n\tgroup?: string;\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\t/** The manifest's group id, written into the signed header so the\n\t\t * copy cannot be served to another group. */\n\t\tid?: 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\t/** Read the bytes back before returning them, and throw VERIFY_FAILED if\n\t * they do not hold what went in. Carried in the options rather than on the\n\t * builder so it survives every link of the chain: set on one link and lost\n\t * by the next is the silent failure this whole feature exists to prevent. */\n\tverify?: boolean;\n\t/** Extra secrets that ALSO open this backup, each getting its own key slot\n\t * around the same data key - typically a recovery code the user printed.\n\t * Only meaningful alongside `password`. */\n\textraSecrets?: string[];\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","// 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';\nexport { verifyBackup, countsOf, type ExpectedContents } from './verify';\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","/**\n * Read a freshly written backup back, before anyone is told it exists.\n *\n * A backup encrypted with a key nobody can reproduce, truncated, or built from\n * an empty snapshot looks exactly like a good one: right name, right date,\n * plausible size. The difference only shows up on the day of the disaster, when\n * it is far too late to make another. The only way to know is to open it.\n *\n * The cost is one decrypt of data the app already holds - a few hundred\n * milliseconds at these sizes, once per save. Cheap enough that the honest\n * default is to do it, and cheap enough that a host doing it on every write is\n * not being wasteful.\n */\n\nimport { SelfstoreError } from './errors';\nimport { importSnapshot } from './index';\nimport type { Snapshot } from './types';\n\n/** What the caller believes it just wrote. */\nexport interface ExpectedContents {\n\t/** Per-collection record counts. Only the named collections are checked, so\n\t * a caller can pin the two that matter instead of listing everything. */\n\tcounts?: Record<string, number>;\n\t/** Number of binary files. */\n\tfiles?: number;\n}\n\n/**\n * Open `bytes` and check they hold what was meant to be written. Throws\n * VERIFY_FAILED otherwise - never returns false, because a caller that ignores\n * a boolean is exactly how a backup that lies gets announced as done.\n */\nexport async function verifyBackup(\n\tbytes: Blob | Uint8Array,\n\topts: { password?: string; expect?: ExpectedContents } = {}\n): Promise<void> {\n\tlet read: Snapshot;\n\ttry {\n\t\tread = await importSnapshot(bytes, { password: opts.password });\n\t} catch {\n\t\t// The password is the likeliest culprit and the most expensive one to\n\t\t// discover late: an archive nobody can open is not a backup.\n\t\tthrow new SelfstoreError('VERIFY_FAILED', 'The backup just written could not be read back.');\n\t}\n\tconst expect = opts.expect;\n\tif (!expect) return;\n\tfor (const [name, count] of Object.entries(expect.counts ?? {})) {\n\t\tconst got = read.collections[name]?.length ?? 0;\n\t\tif (got !== count) {\n\t\t\tthrow new SelfstoreError(\n\t\t\t\t'VERIFY_FAILED',\n\t\t\t\t`The backup just written holds ${got} \"${name}\" where ${count} were expected.`\n\t\t\t);\n\t\t}\n\t}\n\tif (expect.files !== undefined && read.files.length !== expect.files) {\n\t\tthrow new SelfstoreError(\n\t\t\t'VERIFY_FAILED',\n\t\t\t`The backup just written holds ${read.files.length} files where ${expect.files} were expected.`\n\t\t);\n\t}\n}\n\n/** Every collection's record count, for a caller that wants the whole snapshot\n * checked rather than picking which parts matter. */\nexport function countsOf(snapshot: Snapshot): ExpectedContents {\n\tconst counts: Record<string, number> = {};\n\tfor (const [name, rows] of Object.entries(snapshot.collections)) {\n\t\tcounts[name] = rows.length;\n\t}\n\treturn { counts, files: snapshot.files.length };\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';\nimport { verifyBackup, countsOf } from './verify';\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/**\n\t * Read the backup back before handing it over, and throw VERIFY_FAILED if it\n\t * does not hold what went in.\n\t *\n\t * A backup encrypted with a key nobody can reproduce, truncated, or built\n\t * from an empty snapshot looks exactly like a good one: right name, right\n\t * date, plausible size. The difference only shows up on the day of the\n\t * disaster, when it is too late to make another. The only way to know is to\n\t * open it, and it costs one decrypt of data the app already holds.\n\t *\n\t * Applies to every terminal below, so a host cannot verify and then write a\n\t * different set of bytes by accident.\n\t */\n\tverified(): BackupBuilder {\n\t\treturn new BackupBuilder(this.snapshot, { ...this.opts, verify: true });\n\t}\n\n\t/** The backup as raw bytes. */\n\tasync toBytes(): Promise<Uint8Array> {\n\t\tconst bytes = await writeBox(this.snapshot, this.opts);\n\t\tif (this.opts.verify) {\n\t\t\tawait verifyBackup(bytes, { password: this.opts.password, expect: countsOf(this.snapshot) });\n\t\t}\n\t\treturn bytes;\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/** Same guarantee, keeping the encrypted chain's own links reachable. */\n\toverride verified(): EncryptedBackupBuilder {\n\t\treturn new EncryptedBackupBuilder(this.snapshot, { ...this.opts, verify: true });\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\t/**\n\t * A second secret that also opens this backup - a recovery code the user\n\t * printed and put away.\n\t *\n\t * A password that only lives in one person's memory is the likeliest way a\n\t * local-first backup dies: no server can reset it, so the day it is\n\t * forgotten the data is gone for good. The envelope has always held several\n\t * key slots (see `keys[]` in the header); this exposes them where a backup\n\t * is actually written. Each secret wraps the SAME data key, so either one\n\t * opens the file and neither can read the other.\n\t *\n\t * Call it more than once for more than one recovery secret. Reading needs no\n\t * change: `restore(...).withPassword(code)` already tries every slot.\n\t */\n\talsoOpenedWith(secret: string): EncryptedBackupBuilder {\n\t\tif (!secret) {\n\t\t\tthrow new TypeError('alsoOpenedWith(): a non-empty secret is required.');\n\t\t}\n\t\treturn new EncryptedBackupBuilder(this.snapshot, {\n\t\t\t...this.opts,\n\t\t\textraSecrets: [...(this.opts.extraSecrets ?? []), secret]\n\t\t});\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"]}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createNode, createMeta, gcTombstones, stamp, merge, unsyncableCounts, idPathFor, detectConflicts, changes } from './chunk-A2RRJLIF.js';
|
|
2
|
-
import { inspect, checkPasswordPolicy, writeBox, BACKUP_MIME, readBoxWithSync, asBytes } from './chunk-
|
|
2
|
+
import { inspect, checkPasswordPolicy, writeBox, BACKUP_MIME, readBoxWithSync, asBytes } from './chunk-E4S77JSN.js';
|
|
3
3
|
import { seal, isEnvelope, unseal, newDeviceKey } from './chunk-QBCU7OOP.js';
|
|
4
4
|
import { openManifest, GROUP_KEYING } from './chunk-F4ZZEJ5H.js';
|
|
5
5
|
import { mintSlot, MAX_KEY_SLOTS, mintExternalSlot, openSlot } from './chunk-H2T4S3KM.js';
|
|
@@ -253,8 +253,12 @@ function createLocalStore(opts) {
|
|
|
253
253
|
subs.forEach((f) => f());
|
|
254
254
|
};
|
|
255
255
|
const locked = () => durableKind() && encrypted && !pass && !envelope && !group;
|
|
256
|
-
const ownGroupRead = () => group ? { identity: group.identity, authors: [group.identity.sigPub] } : void 0;
|
|
257
|
-
const peerGroupRead = () => group ? {
|
|
256
|
+
const ownGroupRead = () => group ? { identity: group.identity, authors: [group.identity.sigPub], group: group.manifest.group } : void 0;
|
|
257
|
+
const peerGroupRead = () => group ? {
|
|
258
|
+
identity: group.identity,
|
|
259
|
+
authors: group.manifest.members.map((m) => m.sig),
|
|
260
|
+
group: group.manifest.group
|
|
261
|
+
} : void 0;
|
|
258
262
|
const groupSeqKey = (id) => `groupSeq:${id}`;
|
|
259
263
|
async function persistGroupSeq(id, seq) {
|
|
260
264
|
await kv.set(groupSeqKey(id), seq);
|
|
@@ -349,7 +353,8 @@ function createLocalStore(opts) {
|
|
|
349
353
|
recipients: [
|
|
350
354
|
.../* @__PURE__ */ new Set([...group.manifest.members.map((m) => m.enc), group.identity.encPub])
|
|
351
355
|
],
|
|
352
|
-
sign: { pub: group.identity.sigPub, priv: group.identity.sigPriv }
|
|
356
|
+
sign: { pub: group.identity.sigPub, priv: group.identity.sigPriv },
|
|
357
|
+
id: group.manifest.group
|
|
353
358
|
} : void 0;
|
|
354
359
|
const bytes = await writeBox(
|
|
355
360
|
{ collections: snap.collections, files: snap.files },
|
|
@@ -1894,6 +1899,24 @@ function memoryCache() {
|
|
|
1894
1899
|
var COLLECTIONS_KEY = "__collections";
|
|
1895
1900
|
var DEVICE_KEY = "__deviceKey";
|
|
1896
1901
|
var CACHE_KDF = "__cacheKdf";
|
|
1902
|
+
async function sealFile(k, meta, bytes) {
|
|
1903
|
+
const sealedMeta = await seal(k, new TextEncoder().encode(JSON.stringify(meta)));
|
|
1904
|
+
return { meta: sealedMeta, ...await seal(k, bytes) };
|
|
1905
|
+
}
|
|
1906
|
+
async function openFileMeta(k, v) {
|
|
1907
|
+
if (!("meta" in v)) return { name: v.name, mime: v.mime };
|
|
1908
|
+
if (!isEnvelope(v.meta)) {
|
|
1909
|
+
throw new SelfstoreError("BAD_FORMAT", "Unrecognized cache record (not a sealed envelope).");
|
|
1910
|
+
}
|
|
1911
|
+
const meta = JSON.parse(new TextDecoder().decode(await unseal(k, v.meta)));
|
|
1912
|
+
if (typeof meta.name !== "string" || typeof meta.mime !== "string") {
|
|
1913
|
+
throw new SelfstoreError(
|
|
1914
|
+
"BAD_FORMAT",
|
|
1915
|
+
"Unrecognized cache record (file name or type missing)."
|
|
1916
|
+
);
|
|
1917
|
+
}
|
|
1918
|
+
return { name: meta.name, mime: meta.mime };
|
|
1919
|
+
}
|
|
1897
1920
|
function indexedDbCache(name, opts) {
|
|
1898
1921
|
let _db = null;
|
|
1899
1922
|
const db = () => _db ??= openDB(name, 1, {
|
|
@@ -1930,8 +1953,10 @@ function indexedDbCache(name, opts) {
|
|
|
1930
1953
|
if (!isEnvelope(v)) {
|
|
1931
1954
|
throw new SelfstoreError("BAD_FORMAT", "Unrecognized cache record (not a sealed envelope).");
|
|
1932
1955
|
}
|
|
1933
|
-
const
|
|
1934
|
-
|
|
1956
|
+
const k = await key();
|
|
1957
|
+
const { name: name2, mime } = await openFileMeta(k, v);
|
|
1958
|
+
const bytes = await unseal(k, v);
|
|
1959
|
+
return { name: name2, mime, blob: new Blob([bytes], { type: mime }) };
|
|
1935
1960
|
}
|
|
1936
1961
|
const kv = {
|
|
1937
1962
|
async get(key2) {
|
|
@@ -1978,8 +2003,8 @@ function indexedDbCache(name, opts) {
|
|
|
1978
2003
|
const k = await key();
|
|
1979
2004
|
const fresh = await Promise.all(
|
|
1980
2005
|
files.filter((f) => !have.has(f.id)).map(async (f) => {
|
|
1981
|
-
const
|
|
1982
|
-
return { id: f.id, rec: { name: f.name, mime: f.mime,
|
|
2006
|
+
const bytes = new Uint8Array(await f.blob.arrayBuffer());
|
|
2007
|
+
return { id: f.id, rec: await sealFile(k, { name: f.name, mime: f.mime }, bytes) };
|
|
1983
2008
|
})
|
|
1984
2009
|
);
|
|
1985
2010
|
const tx = d.transaction("files", "readwrite");
|
|
@@ -2031,11 +2056,8 @@ function indexedDbCache(name, opts) {
|
|
|
2031
2056
|
for (const fileKey of await d.getAllKeys("files")) {
|
|
2032
2057
|
const v = await d.get("files", fileKey);
|
|
2033
2058
|
if (!v) continue;
|
|
2034
|
-
const
|
|
2035
|
-
files.push({
|
|
2036
|
-
id: fileKey,
|
|
2037
|
-
rec: { name: v.name, mime: v.mime, ...await seal(next, bytes) }
|
|
2038
|
-
});
|
|
2059
|
+
const meta = await openFileMeta(previous, v);
|
|
2060
|
+
files.push({ id: fileKey, rec: await sealFile(next, meta, await unseal(previous, v)) });
|
|
2039
2061
|
}
|
|
2040
2062
|
const tx = d.transaction(["kv", "files"], "readwrite");
|
|
2041
2063
|
const store = tx.objectStore("kv");
|
|
@@ -2203,5 +2225,5 @@ function gisDriveAuth(opts) {
|
|
|
2203
2225
|
}
|
|
2204
2226
|
|
|
2205
2227
|
export { RESERVED_STORE_MODES, createLocalStore, deriveStatus, gisDriveAuth, indexedDbCache, isLockableCache, isReservedStoreMode, memoryCache, preloadGoogleIdentity };
|
|
2206
|
-
//# sourceMappingURL=chunk-
|
|
2207
|
-
//# sourceMappingURL=chunk-
|
|
2228
|
+
//# sourceMappingURL=chunk-HQSK7HK3.js.map
|
|
2229
|
+
//# sourceMappingURL=chunk-HQSK7HK3.js.map
|