selfstore 1.8.32 → 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 +5 -5
- package/dist/backups.d.ts +4 -4
- package/dist/backups.js +3 -3
- package/dist/{cache-BEv-Har2.d.ts → cache-D8Z4u5Jz.d.ts} +0 -2
- package/dist/{chunk-GSZSZGSD.js → chunk-A2RRJLIF.js} +3 -3
- package/dist/{chunk-GSZSZGSD.js.map → chunk-A2RRJLIF.js.map} +1 -1
- 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-TB3CKC6Z.js → chunk-HQSK7HK3.js} +38 -16
- package/dist/chunk-HQSK7HK3.js.map +1 -0
- package/dist/{chunk-25U6AKIX.js → chunk-IUVOVVEA.js} +26 -15
- package/dist/chunk-IUVOVVEA.js.map +1 -0
- package/dist/{chunk-5DAPYC2K.js → chunk-MDEEAQSU.js} +3 -3
- package/dist/{chunk-5DAPYC2K.js.map → chunk-MDEEAQSU.js.map} +1 -1
- 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-HRYIQSTH.js → chunk-VRWAV3GI.js} +5 -5
- package/dist/{chunk-HRYIQSTH.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 +4 -4
- package/dist/groups.d.ts +2 -2
- package/dist/index.d.ts +9 -9
- package/dist/index.js +45 -43
- 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-T3RXUWAY.js → manager-drive-TEYNJVUU.js} +5 -5
- package/dist/{manager-drive-T3RXUWAY.js.map → manager-drive-TEYNJVUU.js.map} +1 -1
- package/dist/proof.d.ts +10 -6
- package/dist/proof.js +22 -3
- package/dist/proof.js.map +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/sync.js +1 -1
- 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 +5 -5
- package/package.json +1 -1
- package/dist/chunk-25U6AKIX.js.map +0 -1
- package/dist/chunk-7U5XMKVP.js.map +0 -1
- package/dist/chunk-SJ3PKQPR.js +0 -10
- package/dist/chunk-SJ3PKQPR.js.map +0 -1
- package/dist/chunk-TB3CKC6Z.js.map +0 -1
- package/dist/s3-ORDHS7G3.js.map +0 -1
|
@@ -22,7 +22,8 @@ async function findExistingFile(token, fileNames) {
|
|
|
22
22
|
headers: { Authorization: `Bearer ${token}` },
|
|
23
23
|
signal: AbortSignal.timeout(META_DEADLINE_MS)
|
|
24
24
|
});
|
|
25
|
-
if (
|
|
25
|
+
if (r.status === 401) throw new AuthExpiredError("Drive rejected the token (401).");
|
|
26
|
+
if (!r.ok) throw new SelfstoreError("TARGET_UNAVAILABLE", `Drive search failed: ${r.status}`);
|
|
26
27
|
const data = await r.json();
|
|
27
28
|
const id = data.files?.[0]?.id;
|
|
28
29
|
if (id) return id;
|
|
@@ -220,18 +221,28 @@ async function listBackups(opts) {
|
|
|
220
221
|
clauses.push(`name contains '${safe}'`);
|
|
221
222
|
}
|
|
222
223
|
const q = encodeURIComponent(clauses.join(" and "));
|
|
223
|
-
const fields = encodeURIComponent("files(id,name,modifiedTime,size)");
|
|
224
|
-
const
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
224
|
+
const fields = encodeURIComponent("nextPageToken,files(id,name,modifiedTime,size)");
|
|
225
|
+
const files = [];
|
|
226
|
+
let pageToken = "";
|
|
227
|
+
do {
|
|
228
|
+
const next = pageToken ? `&pageToken=${encodeURIComponent(pageToken)}` : "";
|
|
229
|
+
const r = await driveFetch(
|
|
230
|
+
opts.auth,
|
|
231
|
+
(token) => fetch(
|
|
232
|
+
`${DRIVE_FILES}?q=${q}&fields=${fields}&pageSize=100&orderBy=modifiedTime%20desc${next}`,
|
|
233
|
+
{
|
|
234
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
235
|
+
signal: AbortSignal.timeout(META_DEADLINE_MS)
|
|
236
|
+
}
|
|
237
|
+
)
|
|
238
|
+
);
|
|
239
|
+
if (r.status === 401) throw new AuthExpiredError("Drive rejected the token (401).");
|
|
240
|
+
if (!r.ok) throw new SelfstoreError("TARGET_UNAVAILABLE", `Drive list failed: ${r.status}`);
|
|
241
|
+
const data = await r.json();
|
|
242
|
+
files.push(...data.files ?? []);
|
|
243
|
+
pageToken = data.nextPageToken ?? "";
|
|
244
|
+
} while (pageToken);
|
|
245
|
+
return files.map((f) => ({
|
|
235
246
|
id: f.id,
|
|
236
247
|
name: f.name,
|
|
237
248
|
modifiedTime: f.modifiedTime ?? null,
|
|
@@ -358,5 +369,5 @@ function secondary(opts, fileId) {
|
|
|
358
369
|
}
|
|
359
370
|
|
|
360
371
|
export { FILE_ID_KEY, account, adopt, connect, createBackup, createCompanion, deleteBackup, findOrCreateOwnFile, fromSession, listBackups, owner, preview, renameBackup, secondary, share, unshare };
|
|
361
|
-
//# sourceMappingURL=chunk-
|
|
362
|
-
//# sourceMappingURL=chunk-
|
|
372
|
+
//# sourceMappingURL=chunk-IUVOVVEA.js.map
|
|
373
|
+
//# sourceMappingURL=chunk-IUVOVVEA.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/persistence/targets/drive.ts"],"names":["token"],"mappings":"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","file":"chunk-IUVOVVEA.js","sourcesContent":["/**\n * Google Drive backup target. Does the Drive REST file operations (find-or-create,\n * upload, download) for a single backup file. It only ships bytes; the store\n * decides whether they are encrypted. How an access token is obtained (a browser\n * OAuth flow, a backend broker, ...) is injected via DriveAuth, so this target\n * stays free of any specific auth strategy or backend.\n *\n * Beyond the standard connect/fromSession lifecycle, this module exposes\n * shared-file primitives (preview, adopt, findOrCreateOwnFile) for apps whose\n * users point several devices - or several people - at one Drive-hosted backup\n * file: preview it read-only, then adopt it as this device's backup.\n */\n\nimport type { BackupTarget } from '../target';\nimport type { KV } from '../cache';\nimport { boundedSignal, lifeLine } from './abort';\nimport { AuthExpiredError, SelfstoreError, isAuthExpired } from '../../selfstore';\n\n/** KV key holding the connected backup's Drive file id. Public because app-level\n * flows that operate on the same file (e.g. sharing it) need to read it. */\nexport const FILE_ID_KEY = 'driveFileId';\nconst DRIVE_UPLOAD = 'https://www.googleapis.com/upload/drive/v3/files';\nconst DRIVE_FILES = 'https://www.googleapis.com/drive/v3/files';\nconst DRIVE_ABOUT = 'https://www.googleapis.com/drive/v3/about';\n/** Just enough of a permission to find the link grants and delete them. */\nconst PERMISSION_FIELDS = encodeURIComponent('permissions(id,type)');\n\n/** A hung request must not spin forever behind a dead connection - the very\n * \"long moment trying to connect\" a stuck backup shows. A deadline turns the\n * hang into an ordinary error (transient: the store retries), never a gate.\n * Data transfers get more room than metadata pokes. AbortSignal.timeout is\n * minted per fetch (a fired signal cannot be reused across driveFetch's retry). */\nconst DATA_DEADLINE_MS = 30_000;\nconst META_DEADLINE_MS = 15_000;\n\n/** How the target gets access. Injected, so the auth strategy lives in the app.\n * Error protocol: a genuine loss of access (the session is gone, re-consent\n * needed) must throw AuthExpiredError - or any error with code 'AUTH_EXPIRED' -\n * while a transient offline/cold-start failure throws a plain error, so the\n * store retries it instead of raising the reconnect gate. */\nexport interface DriveAuth {\n\t/** A fresh access token, or throw if access must be re-established. Pass\n\t * `{ forceRefresh: true }` to bypass any cached token and mint a new one:\n\t * the target calls this after a 401 to tell a merely STALE token (a refresh\n\t * fixes it, no gate) from a genuinely lost session (the refresh 401s too).\n\t * An implementation that cannot force a refresh may ignore the flag; the\n\t * target then retries with the same token, no worse than before.\n\t * `signal` fires when the target's in-flight work is cut (the user is\n\t * detaching): an implementation that runs its own retry loop should abort\n\t * it then (pass the signal to its fetches, stop retrying); ignoring the\n\t * signal only means the loop runs to its own deadline first. */\n\ttoken(opts?: { forceRefresh?: boolean; signal?: AbortSignal }): Promise<string>;\n\t/** Re-establish access with a user gesture; resolves to whether it worked. */\n\treconnect(): Promise<boolean>;\n\t/** Forget the connection, locally and server-side. */\n\tforget(): Promise<void>;\n}\n\n/** Run a Drive request; if Google answers 401 the access token may just be\n * STALE (rejected before its nominal expiry - rotation, an early revoke), so\n * force a fresh token and try once more. Only a SECOND 401 means the session\n * is genuinely gone. This is what stops a stale-token blip - common when a\n * flow makes many Drive calls, e.g. group-mode republishing every converge -\n * from raising the blocking reconnect gate over a still-valid connection. */\nasync function driveFetch(\n\tauth: DriveAuth,\n\trun: (token: string) => Promise<Response>,\n\tsignal?: AbortSignal\n): Promise<Response> {\n\tconst r = await run(await auth.token({ signal }));\n\tif (r.status !== 401) return r;\n\treturn run(await auth.token({ forceRefresh: true, signal }));\n}\n\nexport interface DriveOptions {\n\tauth: DriveAuth;\n\tkv: KV;\n\t/** Name of the backup file created in the user's Drive. */\n\tfileName: string;\n}\n\nasync function findExistingFile(token: string, fileNames: string[]): Promise<string | null> {\n\tfor (const name of fileNames) {\n\t\t// 'me' in owners: a shared backup carries the same name but lives on someone\n\t\t// else's Drive - only an OWNED file may be (re)connected as the personal\n\t\t// backup, or a fresh connect could silently re-adopt theirs.\n\t\t// Escape the name for Drive's query grammar (backslash then single-quote):\n\t\t// a name carrying a quote must not break out of the `name='...'` clause and\n\t\t// alter the filter (encodeURIComponent handles transport, not query syntax).\n\t\tconst safe = name.replace(/\\\\/g, '\\\\\\\\').replace(/'/g, \"\\\\'\");\n\t\tconst q = encodeURIComponent(`name='${safe}' and trashed=false and 'me' in owners`);\n\t\tconst r = await fetch(`${DRIVE_FILES}?q=${q}&fields=files(id)&pageSize=1`, {\n\t\t\theaders: { Authorization: `Bearer ${token}` },\n\t\t\tsignal: AbortSignal.timeout(META_DEADLINE_MS)\n\t\t});\n\t\t// Only a search that ANSWERED may conclude a file is absent: read as\n\t\t// absent, a failed one made the caller create an empty namesake.\n\t\tif (r.status === 401) throw new AuthExpiredError('Drive rejected the token (401).');\n\t\tif (!r.ok) throw new SelfstoreError('TARGET_UNAVAILABLE', `Drive search failed: ${r.status}`);\n\t\tconst data: { files?: { id: string }[] } = await r.json();\n\t\tconst id = data.files?.[0]?.id;\n\t\tif (id) return id;\n\t}\n\treturn null;\n}\n\n/** Classify a non-OK Drive write we could not otherwise handle (the 401 case is\n * the caller's). A 404 - the bound file is gone (deleted, trashed, id no longer\n * valid) - or a 403 that is not a rate limit (permission withdrawn, storage\n * full) is PERMANENT: retrying writes the same doomed request forever (the\n * silent \"momentarily unreachable\" trap), so the store must raise its gate and\n * let the user reconnect to a fresh file or free space. A 403 rate limit and\n * everything else (5xx, network) stay transient. The body carries Drive's\n * `reason` (e.g. userRateLimitExceeded vs storageQuotaExceeded); absent, a bare\n * 403 is treated as permanent (the safer default - a real rate limit names\n * itself). */\nfunction isPermanentDriveWrite(status: number, body: string): boolean {\n\tif (status === 404) return true;\n\tif (status === 403) return !/ratelimit|dailylimit/i.test(body);\n\treturn false;\n}\n\nasync function createFile(token: string, fileName: string): Promise<string> {\n\tconst boundary = 'SLFS_BOUNDARY';\n\tconst meta = JSON.stringify({ name: fileName, mimeType: 'application/octet-stream' });\n\tconst body = [\n\t\t`--${boundary}\\r\\nContent-Type: application/json; charset=UTF-8\\r\\n\\r\\n`,\n\t\tmeta,\n\t\t`\\r\\n--${boundary}\\r\\nContent-Type: application/octet-stream\\r\\n\\r\\n\\r\\n--${boundary}--`\n\t].join('');\n\tconst r = await fetch(`${DRIVE_UPLOAD}?uploadType=multipart`, {\n\t\tmethod: 'POST',\n\t\theaders: {\n\t\t\tAuthorization: `Bearer ${token}`,\n\t\t\t'Content-Type': `multipart/related; boundary=${boundary}`\n\t\t},\n\t\tbody,\n\t\tsignal: AbortSignal.timeout(META_DEADLINE_MS)\n\t});\n\tif (!r.ok) throw new SelfstoreError('TARGET_WRITE_FAILED', `Drive create failed: ${r.status}`);\n\tconst data: { id: string } = await r.json();\n\treturn data.id;\n}\n\nfunction makeTarget(opts: DriveOptions, fixedId?: string): BackupTarget {\n\tconst { auth, kv } = opts;\n\t// A target is BOUND to one file for its whole life: the id is the fixed one,\n\t// or the remembered id CAPTURED on first use - never re-read per operation.\n\t// The old lazy per-call read let a backup SWITCH re-point the remembered id\n\t// while the OUTGOING target was still attached: its final courtesy flush\n\t// then wrote the old store (data and sync bookkeeping) into the file being\n\t// opened, silently merging two \"isolated\" backups.\n\tlet captured: string | undefined = fixedId;\n\tconst fileIdOf = async (): Promise<string | undefined> =>\n\t\t(captured ??= await kv.get<string>(FILE_ID_KEY));\n\t// The instance's LIFE line: abortInFlight() (a user detach) cuts every\n\t// suspended request now instead of at its deadline. Requests mint their\n\t// signal per call, so work started AFTER a cut lives normally.\n\tconst life = lifeLine();\n\tconst bounded = (ms: number): AbortSignal => boundedSignal(ms, life.current());\n\treturn {\n\t\tkind: 'drive',\n\t\tlabel: 'Google Drive',\n\t\tabortInFlight: life.cut,\n\t\tasync save(blob: Blob): Promise<string | null> {\n\t\t\tconst fileId = await fileIdOf();\n\t\t\tif (!fileId) throw new SelfstoreError('NOT_CONNECTED', 'Drive not connected.');\n\t\t\t// fields=version: the response reports the file's new version, so the\n\t\t\t// store records exactly OUR write (no marker race with other replicas).\n\t\t\tconst r = await driveFetch(\n\t\t\t\tauth,\n\t\t\t\t(token) =>\n\t\t\t\t\tfetch(`${DRIVE_UPLOAD}/${fileId}?uploadType=media&fields=version`, {\n\t\t\t\t\t\tmethod: 'PATCH',\n\t\t\t\t\t\theaders: {\n\t\t\t\t\t\t\tAuthorization: `Bearer ${token}`,\n\t\t\t\t\t\t\t'Content-Type': 'application/octet-stream'\n\t\t\t\t\t\t},\n\t\t\t\t\t\tbody: blob,\n\t\t\t\t\t\tsignal: bounded(DATA_DEADLINE_MS)\n\t\t\t\t\t}),\n\t\t\t\tlife.current()\n\t\t\t);\n\t\t\tif (!r.ok) {\n\t\t\t\t// A 401 that survives a forced token refresh (driveFetch) is a genuine\n\t\t\t\t// loss of access, not a stale-token blip. A 404 (the bound file is\n\t\t\t\t// gone) or a non-rate-limit 403 (permission withdrawn, storage full) is\n\t\t\t\t// PERMANENT: gate so the user reconnects to a fresh file, never loop it\n\t\t\t\t// as transient. Everything else (5xx, network) stays transient.\n\t\t\t\tif (r.status === 401) throw new AuthExpiredError('Drive rejected the token (401).');\n\t\t\t\tconst body = await r.text().catch(() => '');\n\t\t\t\tif (isPermanentDriveWrite(r.status, body)) {\n\t\t\t\t\tthrow new SelfstoreError(\n\t\t\t\t\t\t'TARGET_GONE',\n\t\t\t\t\t\t`Drive backup unwritable (${r.status}): the file is gone or access was withdrawn.`\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tthrow new SelfstoreError('TARGET_WRITE_FAILED', `Drive upload failed: ${r.status}`);\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst data: { version?: string | number } = await r.json();\n\t\t\t\treturn data.version != null ? String(data.version) : null;\n\t\t\t} catch {\n\t\t\t\treturn null;\n\t\t\t}\n\t\t},\n\t\tasync load(): Promise<Blob | null> {\n\t\t\tconst fileId = await fileIdOf();\n\t\t\tif (!fileId) return null;\n\t\t\tconst r = await driveFetch(\n\t\t\t\tauth,\n\t\t\t\t(token) =>\n\t\t\t\t\tfetch(`${DRIVE_FILES}/${fileId}?alt=media`, {\n\t\t\t\t\t\theaders: { Authorization: `Bearer ${token}` },\n\t\t\t\t\t\tsignal: bounded(DATA_DEADLINE_MS)\n\t\t\t\t\t}),\n\t\t\t\tlife.current()\n\t\t\t);\n\t\t\t// A failed read must never pass for an empty file: null tells the store\n\t\t\t// \"nothing there\", and a fresh connect would then overwrite the backup\n\t\t\t// this download merely failed to fetch. Only a 404 - the file is\n\t\t\t// genuinely gone - reads as empty; a 401 that survived driveFetch's\n\t\t\t// forced refresh is a real loss of access; anything else is a typed\n\t\t\t// transient failure (the sync legs retry it, connect flows surface it).\n\t\t\tif (r.status === 404) return null;\n\t\t\tif (r.status === 401) throw new AuthExpiredError('Drive rejected the token (401).');\n\t\t\tif (!r.ok) {\n\t\t\t\tthrow new SelfstoreError('TARGET_UNAVAILABLE', `Drive download failed: ${r.status}`);\n\t\t\t}\n\t\t\treturn r.blob();\n\t\t},\n\t\tasync stat(): Promise<string | null> {\n\t\t\ttry {\n\t\t\t\tconst fileId = await fileIdOf();\n\t\t\t\tif (!fileId) return null;\n\t\t\t\tconst r = await driveFetch(\n\t\t\t\t\tauth,\n\t\t\t\t\t(token) =>\n\t\t\t\t\t\tfetch(`${DRIVE_FILES}/${fileId}?fields=version`, {\n\t\t\t\t\t\t\theaders: { Authorization: `Bearer ${token}` },\n\t\t\t\t\t\t\tsignal: bounded(META_DEADLINE_MS)\n\t\t\t\t\t\t}),\n\t\t\t\t\tlife.current()\n\t\t\t\t);\n\t\t\t\tif (!r.ok) return null;\n\t\t\t\tconst data: { version?: string | number } = await r.json();\n\t\t\t\treturn data.version != null ? String(data.version) : null;\n\t\t\t} catch {\n\t\t\t\treturn null; // offline or token lapse: \"cannot tell\", never throws\n\t\t\t}\n\t\t},\n\t\tasync isReady(): Promise<boolean> {\n\t\t\ttry {\n\t\t\t\tawait auth.token({ signal: life.current() });\n\t\t\t\treturn true;\n\t\t\t} catch (e) {\n\t\t\t\t// A transient token failure means \"not writable right now\", not a lost\n\t\t\t\t// session: report false so the store retries. Only a genuine auth loss\n\t\t\t\t// propagates, so the boot path can raise the reconnect gate.\n\t\t\t\tif (isAuthExpired(e)) throw e;\n\t\t\t\treturn false;\n\t\t\t}\n\t\t},\n\t\tasync reconnect(): Promise<boolean> {\n\t\t\treturn auth.reconnect();\n\t\t},\n\t\tasync disconnect(): Promise<void> {\n\t\t\tawait auth.forget();\n\t\t\t// Delete the remembered id only while it still points at this target's\n\t\t\t// file: after a backup switch the key already belongs to the new\n\t\t\t// target, and a late disconnect of the old one must not orphan it.\n\t\t\tconst current = await kv.get<string>(FILE_ID_KEY);\n\t\t\tconst own = await fileIdOf();\n\t\t\tif (!current || !own || current === own) await kv.del(FILE_ID_KEY);\n\t\t}\n\t};\n}\n\n/** First-time connect: authorize (a user gesture), then find-or-create the file.\n * Returns the target, or null if the user cancelled the consent. */\nexport async function connect(opts: DriveOptions): Promise<BackupTarget | null> {\n\tif (!(await opts.auth.reconnect())) return null;\n\tconst token = await opts.auth.token();\n\tlet fileId = await opts.kv.get<string>(FILE_ID_KEY);\n\tif (!fileId) {\n\t\tfileId =\n\t\t\t(await findExistingFile(token, [opts.fileName])) ?? (await createFile(token, opts.fileName));\n\t\tawait opts.kv.set(FILE_ID_KEY, fileId);\n\t}\n\treturn makeTarget(opts);\n}\n\n/** Rebuild the target from a past session's saved connection (no UI), or null. */\nexport async function fromSession(opts: DriveOptions): Promise<BackupTarget | null> {\n\treturn (await opts.kv.get<string>(FILE_ID_KEY)) ? makeTarget(opts) : null;\n}\n\n/** A read-only-intent preview of one specific (typically shared) file, before\n * adopting it. Writes NOTHING to the KV: until the user confirms, the device's\n * own backup wiring must stay untouched. */\nexport function preview(opts: DriveOptions, fileId: string): BackupTarget {\n\treturn makeTarget(opts, fileId);\n}\n\n/** Adopt a specific (typically shared) file as this device's backup. Persists\n * the file id, so next sessions restore it like any Drive backup. */\nexport async function adopt(opts: DriveOptions, fileId: string): Promise<BackupTarget> {\n\tawait opts.kv.set(FILE_ID_KEY, fileId);\n\treturn makeTarget(opts);\n}\n\n/** Find (or create) the user's own backup file, ignoring the currently adopted\n * id (which may point at someone else's shared file). Reuses the live session\n * when possible and only falls back to the consent popup. Adopts NOTHING: the\n * caller decides whether to load the file or start it blank. Null when the\n * consent is refused. */\nexport async function findOrCreateOwnFile(\n\topts: DriveOptions\n): Promise<{ fileId: string; created: boolean } | null> {\n\tlet token: string;\n\ttry {\n\t\ttoken = await opts.auth.token();\n\t} catch {\n\t\tif (!(await opts.auth.reconnect())) return null;\n\t\ttoken = await opts.auth.token();\n\t}\n\tconst existing = await findExistingFile(token, [opts.fileName]);\n\tif (existing) return { fileId: existing, created: false };\n\treturn { fileId: await createFile(token, opts.fileName), created: true };\n}\n\n/** Who a destination is held by, as far as it will say. */\nexport interface DriveAccount {\n\t/** The account's address, when Drive reports one. */\n\temail: string | null;\n\t/** Its display name, when Drive reports one. */\n\tname: string | null;\n}\n\n/** Which Google account the current session belongs to.\n *\n * A destination named \"Google Drive\" is not an address: several accounts look\n * alike, and a user who connected the wrong one has no way to tell from a\n * brand name alone. This is the one call that lets a surface say WHICH.\n *\n * Cheap (one metadata poke, allowed by the `drive.file` scope) but not free:\n * the answer only changes when the session does, so a caller should ask on\n * connect and on reconnect - not on every render. A destination that will not\n * say answers nulls rather than throwing; a genuinely lost session throws\n * AuthExpiredError like every other call here. */\nexport async function account(opts: { auth: DriveAuth }): Promise<DriveAccount> {\n\tconst fields = encodeURIComponent('user(emailAddress,displayName)');\n\tconst r = await driveFetch(opts.auth, (token) =>\n\t\tfetch(`${DRIVE_ABOUT}?fields=${fields}`, {\n\t\t\theaders: { Authorization: `Bearer ${token}` },\n\t\t\tsignal: AbortSignal.timeout(META_DEADLINE_MS)\n\t\t})\n\t);\n\tif (r.status === 401) throw new AuthExpiredError('Drive rejected the token (401).');\n\tif (!r.ok) throw new SelfstoreError('TARGET_UNAVAILABLE', `Drive about failed: ${r.status}`);\n\tconst data: { user?: { emailAddress?: string; displayName?: string } } = await r.json();\n\treturn {\n\t\temail: data.user?.emailAddress ?? null,\n\t\tname: data.user?.displayName ?? null\n\t};\n}\n\n/** One row of `listBackups()`: what Drive reports about a file the app can\n * see. Whether the backup is encrypted is not here on purpose - that lives\n * inside the file (inspect it, or remember it app-side); a listing must stay\n * one cheap metadata call, never N downloads. */\nexport interface DriveBackupInfo {\n\tid: string;\n\tname: string;\n\t/** RFC 3339 timestamp of the last write, as Drive reports it. */\n\tmodifiedTime: string | null;\n\t/** Size in bytes, when Drive reports one. */\n\tsize: number | null;\n}\n\n/** List the files this app can see on the user's own Drive (the drive.file\n * scope already narrows visibility to files the app created or was handed).\n * `nameContains` narrows server-side; selfstore ships no naming convention -\n * the app applies its own exact rules on what comes back (its backups, its\n * internal files). Newest first. */\nexport async function listBackups(opts: {\n\tauth: DriveAuth;\n\tnameContains?: string;\n}): Promise<DriveBackupInfo[]> {\n\tconst clauses = ['trashed=false', \"'me' in owners\"];\n\tif (opts.nameContains) {\n\t\tconst safe = opts.nameContains.replace(/\\\\/g, '\\\\\\\\').replace(/'/g, \"\\\\'\");\n\t\tclauses.push(`name contains '${safe}'`);\n\t}\n\tconst q = encodeURIComponent(clauses.join(' and '));\n\tconst fields = encodeURIComponent('nextPageToken,files(id,name,modifiedTime,size)');\n\ttype Page = {\n\t\tnextPageToken?: string;\n\t\tfiles?: { id: string; name: string; modifiedTime?: string; size?: string | number }[];\n\t};\n\t// Every page, not the first hundred: older backups fell out of the chooser.\n\tconst files: NonNullable<Page['files']> = [];\n\tlet pageToken = '';\n\tdo {\n\t\tconst next = pageToken ? `&pageToken=${encodeURIComponent(pageToken)}` : '';\n\t\tconst r = await driveFetch(opts.auth, (token) =>\n\t\t\tfetch(\n\t\t\t\t`${DRIVE_FILES}?q=${q}&fields=${fields}&pageSize=100&orderBy=modifiedTime%20desc${next}`,\n\t\t\t\t{\n\t\t\t\t\theaders: { Authorization: `Bearer ${token}` },\n\t\t\t\t\tsignal: AbortSignal.timeout(META_DEADLINE_MS)\n\t\t\t\t}\n\t\t\t)\n\t\t);\n\t\tif (r.status === 401) throw new AuthExpiredError('Drive rejected the token (401).');\n\t\tif (!r.ok) throw new SelfstoreError('TARGET_UNAVAILABLE', `Drive list failed: ${r.status}`);\n\t\tconst data: Page = await r.json();\n\t\tfiles.push(...(data.files ?? []));\n\t\tpageToken = data.nextPageToken ?? '';\n\t} while (pageToken);\n\treturn files.map((f) => ({\n\t\tid: f.id,\n\t\tname: f.name,\n\t\tmodifiedTime: f.modifiedTime ?? null,\n\t\tsize: f.size != null ? Number(f.size) : null\n\t}));\n}\n\n/** Create a brand-new empty backup file with this exact name and answer its\n * id. Refuses when a non-trashed owned file already carries the name: Drive\n * happily stores duplicate names, and a duplicate would poison every\n * find-by-name reconnect that follows. Adopts nothing - the caller decides\n * to attach it (typically with `wipe: true` so it starts blank). */\nexport async function createBackup(opts: {\n\tauth: DriveAuth;\n\tfileName: string;\n}): Promise<{ fileId: string }> {\n\tconst token = await opts.auth.token();\n\tconst existing = await findExistingFile(token, [opts.fileName]);\n\tif (existing) {\n\t\tthrow new SelfstoreError(\n\t\t\t'TARGET_WRITE_FAILED',\n\t\t\t`A file named \"${opts.fileName}\" already exists on Drive.`\n\t\t);\n\t}\n\treturn { fileId: await createFile(token, opts.fileName) };\n}\n\n/** Delete a backup file from Drive, for good (not the trash: the file's data\n * must actually be gone, not lingering). A 404 is success - deleting what is\n * already gone is not an error. The caller owns the guard against deleting\n * the currently attached file. */\nexport async function deleteBackup(opts: { auth: DriveAuth; fileId: string }): Promise<void> {\n\tconst r = await driveFetch(opts.auth, (token) =>\n\t\tfetch(`${DRIVE_FILES}/${opts.fileId}`, {\n\t\t\tmethod: 'DELETE',\n\t\t\theaders: { Authorization: `Bearer ${token}` },\n\t\t\tsignal: AbortSignal.timeout(META_DEADLINE_MS)\n\t\t})\n\t);\n\tif (r.status === 401) throw new AuthExpiredError('Drive rejected the token (401).');\n\tif (r.status === 404) return;\n\tif (!r.ok) throw new SelfstoreError('TARGET_WRITE_FAILED', `Drive delete failed: ${r.status}`);\n}\n\n/** Rename a backup file (metadata only - the id, the content and every open\n * target bound to it are untouched). Refuses when another owned file already\n * carries the new name, for the same reason createBackup does: a duplicate\n * poisons find-by-name. A 404 means the file is gone, not a success here. */\nexport async function renameBackup(opts: {\n\tauth: DriveAuth;\n\tfileId: string;\n\tfileName: string;\n}): Promise<void> {\n\tconst token = await opts.auth.token();\n\tconst existing = await findExistingFile(token, [opts.fileName]);\n\tif (existing && existing !== opts.fileId) {\n\t\tthrow new SelfstoreError(\n\t\t\t'TARGET_WRITE_FAILED',\n\t\t\t`A file named \"${opts.fileName}\" already exists on Drive.`\n\t\t);\n\t}\n\tif (existing === opts.fileId) return; // already this name: nothing to do\n\tconst r = await driveFetch(opts.auth, (token) =>\n\t\tfetch(`${DRIVE_FILES}/${opts.fileId}?fields=id`, {\n\t\t\tmethod: 'PATCH',\n\t\t\theaders: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' },\n\t\t\tbody: JSON.stringify({ name: opts.fileName }),\n\t\t\tsignal: AbortSignal.timeout(META_DEADLINE_MS)\n\t\t})\n\t);\n\tif (r.status === 401) throw new AuthExpiredError('Drive rejected the token (401).');\n\tif (!r.ok) throw new SelfstoreError('TARGET_WRITE_FAILED', `Drive rename failed: ${r.status}`);\n}\n\n// --- Companion files: the Drive side of sharing ------------------------------\n//\n// Everything above operates on the user's OWN backup. Sharing needs a second\n// kind of file: a companion the app creates NEXT TO the backup, publishes on a\n// link, and tears down on its own schedule - a member's published copy, an\n// invitation others read before they join.\n//\n// These four calls plus `secondary()` are that whole Drive side. They are here\n// rather than in a consumer because every app that shares over Drive was\n// writing them again, and getting the error protocol wrong on the way: a bare\n// `throw new Error('Drive share failed: 403')` carries no code, so the store\n// cannot tell a lost session from a bad moment, and the app cannot word it.\n//\n// What is deliberately NOT here: reading another account's file. The\n// `drive.file` scope only sees files this app created or the user picked, so a\n// member cannot fetch a link-shared copy from another Drive without either the\n// Google Picker (a user gesture per file, plus a third-party script) or a relay\n// the app hosts. Both are the app's decision, and one of them is a server -\n// this library will not pick either for you. `attachPeer` takes whatever\n// `load()` you can provide.\n\n/** Create an EMPTY companion file in the user's Drive and answer its id.\n *\n * Unlike `createBackup`, it does not refuse a duplicate name: several members\n * of several groups can legitimately hold identically named copies, and the id\n * is the handle everywhere. The caller remembers the id; nothing here does. */\nexport async function createCompanion(opts: {\n\tauth: DriveAuth;\n\tfileName: string;\n}): Promise<{ fileId: string }> {\n\treturn { fileId: await createFile(await opts.auth.token(), opts.fileName) };\n}\n\n/** Make a file readable by ANYONE holding its link - the capability-link model:\n * the file carries ciphertext only, the key travels in the link's fragment, so\n * the LINK is the capability and the recipient needs no Google account.\n *\n * Idempotent from the caller's side: Drive accepts a second identical grant.\n * Publish an ENCRYPTED file, always - this hands the bytes to whoever has the\n * URL, including whoever it gets forwarded to. */\nexport async function share(opts: { auth: DriveAuth; fileId: string }): Promise<void> {\n\tconst r = await driveFetch(opts.auth, (token) =>\n\t\tfetch(`${DRIVE_FILES}/${opts.fileId}/permissions?fields=id`, {\n\t\t\tmethod: 'POST',\n\t\t\theaders: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' },\n\t\t\tbody: JSON.stringify({ role: 'reader', type: 'anyone' }),\n\t\t\tsignal: AbortSignal.timeout(META_DEADLINE_MS)\n\t\t})\n\t);\n\tif (r.status === 401) throw new AuthExpiredError('Drive rejected the token (401).');\n\tif (!r.ok) throw new SelfstoreError('TARGET_WRITE_FAILED', `Drive share failed: ${r.status}`);\n}\n\n/** Undo `share`: drop every link-reader grant, so the file is private again.\n * Named grants (people invited individually) are left alone - this revokes the\n * capability, not the invitations.\n *\n * Call it BEFORE any plaintext rewrite of a file that was shared: a decrypted\n * copy must never stay link-readable for even one save. A file already private\n * is success, not an error. */\nexport async function unshare(opts: { auth: DriveAuth; fileId: string }): Promise<void> {\n\tconst list = await driveFetch(opts.auth, (token) =>\n\t\tfetch(`${DRIVE_FILES}/${opts.fileId}/permissions?fields=${PERMISSION_FIELDS}`, {\n\t\t\theaders: { Authorization: `Bearer ${token}` },\n\t\t\tsignal: AbortSignal.timeout(META_DEADLINE_MS)\n\t\t})\n\t);\n\tif (list.status === 401) throw new AuthExpiredError('Drive rejected the token (401).');\n\tif (list.status === 404) return; // gone is private enough\n\tif (!list.ok) {\n\t\tthrow new SelfstoreError('TARGET_UNAVAILABLE', `Drive permissions failed: ${list.status}`);\n\t}\n\tconst data: { permissions?: { id: string; type: string }[] } = await list.json();\n\tfor (const p of data.permissions ?? []) {\n\t\tif (p.type !== 'anyone') continue;\n\t\tconst r = await driveFetch(opts.auth, (token) =>\n\t\t\tfetch(`${DRIVE_FILES}/${opts.fileId}/permissions/${p.id}`, {\n\t\t\t\tmethod: 'DELETE',\n\t\t\t\theaders: { Authorization: `Bearer ${token}` },\n\t\t\t\tsignal: AbortSignal.timeout(META_DEADLINE_MS)\n\t\t\t})\n\t\t);\n\t\tif (r.status === 401) throw new AuthExpiredError('Drive rejected the token (401).');\n\t\tif (!r.ok && r.status !== 404) {\n\t\t\tthrow new SelfstoreError('TARGET_WRITE_FAILED', `Drive revoke failed: ${r.status}`);\n\t\t}\n\t}\n}\n\n/** Which account OWNS a file - not which one this session belongs to (that is\n * `account()`). Used to label a copy that lives on somebody else's Drive: a\n * member's published copy is \"Google Drive\" for everyone, and only the owner's\n * address says whose.\n *\n * Best-effort by design: a file that will not say answers nulls rather than\n * throwing, because a missing label must never break a sync round. A genuinely\n * lost session still throws, like every other call here. */\nexport async function owner(opts: { auth: DriveAuth; fileId: string }): Promise<DriveAccount> {\n\tconst fields = encodeURIComponent('owners(displayName,emailAddress)');\n\tconst r = await driveFetch(opts.auth, (token) =>\n\t\tfetch(`${DRIVE_FILES}/${opts.fileId}?fields=${fields}`, {\n\t\t\theaders: { Authorization: `Bearer ${token}` },\n\t\t\tsignal: AbortSignal.timeout(META_DEADLINE_MS)\n\t\t})\n\t);\n\tif (r.status === 401) throw new AuthExpiredError('Drive rejected the token (401).');\n\tif (!r.ok) return { email: null, name: null };\n\tconst data: { owners?: { displayName?: string; emailAddress?: string }[] } = await r.json();\n\tconst first = data.owners?.[0];\n\treturn { email: first?.emailAddress ?? null, name: first?.displayName ?? null };\n}\n\n/** A read-WRITE target over one companion file, for a store mirror or replica.\n *\n * `preview()` also binds a target to a given id, and its save() works - but its\n * name says read-only and its `disconnect()` belongs to the primary\n * connection: it calls `auth.forget()` and may drop the remembered backup id,\n * which is right when the user is leaving Drive and catastrophic when they are\n * only dropping a shared copy. This one detaches nothing but itself, and\n * carries its own `kind` so a store that persists it never mistakes it for the\n * primary destination.\n *\n * Deleting the file is `deleteBackup({ auth, fileId })`; making it private\n * again is `unshare`. Do both, in that order, when a member leaves. */\nexport function secondary(opts: { auth: DriveAuth; kv: KV }, fileId: string): BackupTarget {\n\t// No fileName: a target bound to an id never searches or creates by name, so\n\t// asking for one would be a required argument that is read nowhere - and the\n\t// first caller would have to invent a value to satisfy it.\n\treturn {\n\t\t...makeTarget({ ...opts, fileName: '' }, fileId),\n\t\tkind: 'drive-companion',\n\t\tlabel: 'Google Drive (shared copy)',\n\t\t// The companion's life is its own: leaving a share must not forget the\n\t\t// Drive session the user's real backup still rides on.\n\t\tdisconnect: async (): Promise<void> => {}\n\t};\n}\n"]}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { makeMachine } from './chunk-2BNTCXQN.js';
|
|
2
|
-
import { FILE_ID_KEY, findOrCreateOwnFile, renameBackup, deleteBackup, createBackup, preview, listBackups } from './chunk-
|
|
2
|
+
import { FILE_ID_KEY, findOrCreateOwnFile, renameBackup, deleteBackup, createBackup, preview, listBackups } from './chunk-IUVOVVEA.js';
|
|
3
3
|
|
|
4
4
|
// src/backups/manager.ts
|
|
5
5
|
var DEFAULT_KEYS = {
|
|
@@ -390,5 +390,5 @@ function driveBackupsHost(opts) {
|
|
|
390
390
|
}
|
|
391
391
|
|
|
392
392
|
export { createBackupsManager, driveBackupsHost };
|
|
393
|
-
//# sourceMappingURL=chunk-
|
|
394
|
-
//# sourceMappingURL=chunk-
|
|
393
|
+
//# sourceMappingURL=chunk-MDEEAQSU.js.map
|
|
394
|
+
//# sourceMappingURL=chunk-MDEEAQSU.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/backups/manager.ts","../src/backups/drive-host.ts"],"names":[],"mappings":"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","file":"chunk-5DAPYC2K.js","sourcesContent":["// Multi-backup manager: several backup files on one destination account, one\n// attached at a time, each an isolated snapshot - opening one replaces the\n// local data, never merges, never touches the other files.\n//\n// It owns the bookkeeping a backup panel needs: the self-healing registry of\n// files this device has connected to; the per-file memories a listing cannot\n// provide (encrypted? shared? - those bits live inside the files, learned as\n// they are opened or probed and remembered in the kv); and the open / create\n// / rename / delete gestures with their isolation invariants (fixed-id\n// targets, active-id committed only after a successful attach, wipe on every\n// switch so no sync-meta crosses files).\n//\n// Injectable and headless: a LocalStore + a kv + a BackupsHost port for the\n// destination I/O. Errors surface as stable codes, never prose; hosts wrap\n// subscribe in their own reactivity.\n\nimport type { KV } from '../persistence/cache';\nimport type { LocalStore } from '../persistence/store';\nimport type { BackupTarget } from '../persistence/target';\nimport { makeMachine, type FlowStore } from '../flows/machine';\n\n// --- Ports --------------------------------------------------------------------\n\n/** What the destination reports about a listed file (one cheap metadata call). */\nexport interface BackupFileInfo {\n\tid: string;\n\tname: string;\n\t/** RFC 3339 last-write timestamp, when the destination reports one. */\n\tmodifiedTime: string | null;\n}\n\n/** The destination I/O the manager drives - the host wires its own adapter\n * (e.g. over the Drive target ops). Every method may throw; the manager turns\n * failures into stable error codes. */\nexport interface BackupsHost {\n\t/** The target kind the managed files live on (e.g. 'drive'): the manager\n\t * only stamps an active file while the store is attached to this kind. */\n\tkind: string;\n\t/** The kv key the attached file id is persisted under (the same one the\n\t * target's connect path reads first). */\n\tactiveIdKey: string;\n\t/** List candidate backup files on the account. The manager applies the\n\t * naming rules on what comes back; internal files simply do not match. */\n\tlist(): Promise<BackupFileInfo[]>;\n\t/** A target over a FIXED file id - never re-resolved by name. The fixed id\n\t * is what keeps two backups from silently merging mid-switch. */\n\topen(fileId: string): BackupTarget;\n\t/** Create an empty file under this name and return its id. Refuse a name\n\t * the account already carries (a duplicate poisons find-by-name). */\n\tcreate(fileName: string): Promise<{ fileId: string }>;\n\t/** Delete a file for good. */\n\tremove(fileId: string): Promise<void>;\n\t/** Rename a file in place (metadata only: the id must not change). */\n\trename?(fileId: string, fileName: string): Promise<void>;\n\t/** Find (or create) the PERSONAL file by its canonical name, ignoring any\n\t * currently adopted id. Null when the user backs out of the session. */\n\tfindOrCreatePersonal(): Promise<{ fileId: string; created: boolean } | null>;\n\t/** Make sure a usable session exists before a remote call (re-consent when\n\t * it lapsed). False = the user refused; the gesture aborts as 'cancelled'.\n\t * Absent = sessions are the target's own business. */\n\tensureSession?(): Promise<boolean>;\n\t/** Resolve the owner label of a file this account does not own, for the\n\t * shared registry (\"shared by X\"). Best-effort. */\n\tfileOwner?(fileId: string): Promise<{ email: string | null; name: string | null } | null>;\n\t/** File ids that must never enter the registry even though the store may\n\t * attach them (e.g. a sharing engine's published copy file). */\n\texcludedFileIds?(): Promise<(string | null | undefined)[]>;\n}\n\n/** How backup files are named on the destination. */\nexport interface BackupsNaming {\n\t/** The personal, unnamed file (\"App.zip\"). Its label reads as null. */\n\tcanonicalName: string;\n\t/** label -> file name. Default: \"Stem (label).ext\" derived from\n\t * canonicalName (\"App.zip\" + \"family\" -> \"App (family).zip\"). */\n\tnamedFileFor?(label: string): string;\n\t/** file name -> parsed label; null = the personal file; undefined = not one\n\t * of ours (dropped from listings). Default matches the default namedFileFor. */\n\tparseLabel?(name: string): string | null | undefined;\n}\n\n/** The kv keys the manager persists under - override to keep a pre-existing\n * install's records. */\nexport interface BackupsKeys {\n\t/** The known-backups registry. */\n\tregistry?: string;\n\t/** fileId -> encrypted? map. */\n\tencrypted?: string;\n\t/** fileId -> shared? map. */\n\tshared?: string;\n\t/** Set while the ACTIVE file is not the personal one (another account's). */\n\tjoined?: string;\n\t/** Cached owner label of that file. */\n\towner?: string;\n}\n\n// --- Shapes -------------------------------------------------------------------\n\n/** Every backup this device has connected to: its own personal file and each\n * OTHER account's file it was let into. Isolated snapshots, all of them. */\nexport interface KnownBackups {\n\tpersonalFileId: string | null;\n\tshared: { fileId: string; ownerEmail: string | null; ownerName: string | null }[];\n}\n\n/** One row of the \"my backups\" list: a file this account owns. */\nexport interface BackupRow {\n\tfileId: string;\n\tname: string;\n\t/** Parsed display label; null for the personal, unnamed file. */\n\tlabel: string | null;\n\t/** Last-write time, epoch ms (null when the destination did not say). */\n\tmodifiedAt: number | null;\n\t/** true/false once known (opened, created or probed); null until then. */\n\tencrypted: boolean | null;\n\t/** true/false once learned (while this file was the active one); null\n\t * until then. A share link existing is the shared state. */\n\tshared: boolean | null;\n}\n\n/** The owner label of a joined (another account's) file. */\nexport interface BackupOwner {\n\temail: string | null;\n\tname: string | null;\n}\n\n/** Stable failure codes; the host maps them to its own copy. */\nexport type BackupsErrorCode = 'cancelled' | 'gone' | 'failed';\n\nexport interface BackupsSnapshot {\n\tregistry: KnownBackups;\n\t/** The file currently attached, when the store is on the managed kind. */\n\tactiveFileId: string | null;\n\t/** The active file is not the personal one (someone else's). */\n\tjoined: boolean;\n\t/** Who owns the joined file, when known. */\n\towner: BackupOwner | null;\n\t/** The last gesture's failure, or null. Reset at each gesture start. */\n\tlastError: BackupsErrorCode | null;\n}\n\nexport interface BackupsManager extends FlowStore<BackupsSnapshot> {\n\t/** Boot: load registry + joined/owner from kv and stamp the active file.\n\t * Kv-only, no destination call. */\n\thydrate(): Promise<void>;\n\t/** Re-read the registry and restamp the active file. Kv-only, cheap. */\n\trefresh(): Promise<void>;\n\t/** After an attach the manager did not make (a connect widget, a join):\n\t * record the now-active file in the registry and restamp joined/owner. */\n\tmarkActive(fileId: string): Promise<void>;\n\t/** Live list of this account's own files, per the naming rules, decorated\n\t * with the learned memories. One destination listing call. */\n\tlist(): Promise<BackupRow[]>;\n\t/** Learn a listed file's encrypted bit without opening it for real: one\n\t * download + header read, remembered for good. Null when unreachable. */\n\tprobeEncryption(fileId: string): Promise<boolean | null>;\n\t/** Record the ACTIVE file's live share state, keyed by file id, so its row\n\t * stays truthful after a switch away. */\n\tnoteShared(fileId: string, shared: boolean): Promise<void>;\n\t/** Open a known backup: an isolated snapshot replaces the local data.\n\t * 'encrypted' = collect its passphrase and call again with it. A file the\n\t * destination reports GONE is pruned from the registry (lastError 'gone'). */\n\topenBackup(fileId: string, passphrase?: string | null): Promise<'ok' | 'encrypted' | 'failed'>;\n\t/** Open (or create blank) the personal file. */\n\topenPersonal(passphrase?: string | null): Promise<'ok' | 'encrypted' | 'failed'>;\n\t/** Create a brand-new empty named backup and open it (starts blank - never\n\t * seeded from the currently loaded file). */\n\tcreateNamed(label: string): Promise<'ok' | 'failed'>;\n\t/** Record a file in the SHARED registry under the sharer's label, without\n\t * opening it. For a joined portfolio's dedicated wallet: the file may live\n\t * on this very account, yet it presents as \"shared by X\", never as mine. */\n\tregisterShared(fileId: string, owner: BackupOwner): Promise<void>;\n\t/** Create a joined portfolio's dedicated wallet: a file under the given\n\t * name (outside the own-files naming on purpose), attached BLANK - never\n\t * seeded from the previously loaded silo - and recorded in the shared\n\t * registry under the sharer's label. The host stages the shared content\n\t * afterwards (restore the projection, then save). */\n\tcreateShared(fileName: string, owner: BackupOwner): Promise<'ok' | 'failed'>;\n\t/** Rename a file in place (id unchanged, so renaming the ACTIVE file never\n\t * detaches it). The host target refuses a name already carried. */\n\trenameBackup(fileId: string, label: string): Promise<'ok' | 'failed'>;\n\t/** Delete a file for good. Deleting the ACTIVE one detaches first (the app\n\t * goes device-only, local data stays), so nothing writes mid-delete. */\n\tdeleteBackup(fileId: string): Promise<boolean>;\n\t/** Drop a KNOWN shared (another account's) file from this device's\n\t * registry. Forgetting only - the owner's file is never touched. */\n\tforgetShared(fileId: string): Promise<void>;\n\t/** The file name a label produces (the create form's preview line). */\n\tfileNameFor(label: string): string;\n}\n\n// --- Defaults -----------------------------------------------------------------\n\nconst DEFAULT_KEYS: Required<BackupsKeys> = {\n\tregistry: 'selfstore:backups:registry:v1',\n\tencrypted: 'selfstore:backups:encrypted:v1',\n\tshared: 'selfstore:backups:shared:v1',\n\tjoined: 'selfstore:backups:joined:v1',\n\towner: 'selfstore:backups:owner:v1'\n};\n\nconst escapeRe = (s: string): string => s.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n\n/** The default naming scheme: \"App.zip\" -> personal; \"App (label).zip\" ->\n * named. Derived from the canonical name's stem and extension. */\nfunction defaultNaming(\n\tnaming: BackupsNaming\n): Required<Pick<BackupsNaming, 'namedFileFor' | 'parseLabel'>> {\n\tconst dot = naming.canonicalName.lastIndexOf('.');\n\tconst stem = dot > 0 ? naming.canonicalName.slice(0, dot) : naming.canonicalName;\n\tconst ext = dot > 0 ? naming.canonicalName.slice(dot) : '';\n\tconst named = new RegExp(`^${escapeRe(stem)} \\\\((.+)\\\\)${escapeRe(ext)}$`);\n\treturn {\n\t\tnamedFileFor: (label: string) => `${stem} (${label.trim()})${ext}`,\n\t\tparseLabel: (name: string) => {\n\t\t\tif (name === naming.canonicalName) return null;\n\t\t\tconst m = named.exec(name);\n\t\t\treturn m ? m[1] : undefined;\n\t\t}\n\t};\n}\n\n// --- Factory ------------------------------------------------------------------\n\nexport function createBackupsManager(deps: {\n\tstore: LocalStore;\n\tkv: KV;\n\thost: BackupsHost;\n\tnaming: BackupsNaming;\n\tkeys?: BackupsKeys;\n}): BackupsManager {\n\tconst { store, kv, host } = deps;\n\tconst KEYS = { ...DEFAULT_KEYS, ...deps.keys };\n\tconst namingDefaults = defaultNaming(deps.naming);\n\tconst naming = {\n\t\tnamedFileFor: deps.naming.namedFileFor ?? namingDefaults.namedFileFor,\n\t\tparseLabel: deps.naming.parseLabel ?? namingDefaults.parseLabel\n\t};\n\n\tconst m = makeMachine<BackupsSnapshot>({\n\t\tregistry: { personalFileId: null, shared: [] },\n\t\tactiveFileId: null,\n\t\tjoined: false,\n\t\towner: null,\n\t\tlastError: null\n\t});\n\n\tconst fail = (code: BackupsErrorCode): void => m.set({ lastError: code });\n\n\t// Which file ids the destination lists as OURS, and which one carries the\n\t// canonical (personal) name. Filled by list(), refreshed lazily where a\n\t// truthful own/foreign call is needed. Null until first learned.\n\tlet ownIds: Set<string> | null = null;\n\tlet ownCanonicalId: string | null = null;\n\n\tfunction learnOwn(files: BackupFileInfo[]): void {\n\t\tconst ids = new Set<string>();\n\t\tlet canonical: string | null = null;\n\t\tfor (const f of files) {\n\t\t\tconst label = naming.parseLabel(f.name);\n\t\t\tif (label === undefined) continue;\n\t\t\tids.add(f.id);\n\t\t\tif (label === null) canonical = f.id;\n\t\t}\n\t\townIds = ids;\n\t\townCanonicalId = canonical;\n\t}\n\n\tasync function ensureOwnKnown(): Promise<void> {\n\t\tif (ownIds !== null) return;\n\t\ttry {\n\t\t\tlearnOwn(await host.list());\n\t\t} catch {\n\t\t\t// Offline: leave unknown - markActive falls back on the registry alone.\n\t\t}\n\t}\n\n\t/** Load the registry, self-healing: excluded ids (a sharing engine's copy\n\t * file) must never sit in it - recorded as \"personal\" they would mislabel\n\t * the chooser and let a later open treat a shared file as one's own. */\n\tasync function loadRegistry(): Promise<KnownBackups> {\n\t\tconst reg = (await kv.get<KnownBackups>(KEYS.registry)) ?? { personalFileId: null, shared: [] };\n\t\tconst excluded = (await host.excludedFileIds?.()) ?? [];\n\t\tconst bad = new Set(excluded.filter((x): x is string => typeof x === 'string'));\n\t\tif (bad.size === 0) return reg;\n\t\tconst healed: KnownBackups = {\n\t\t\tpersonalFileId: reg.personalFileId && bad.has(reg.personalFileId) ? null : reg.personalFileId,\n\t\t\tshared: reg.shared.filter((s) => !bad.has(s.fileId))\n\t\t};\n\t\tif (\n\t\t\thealed.personalFileId !== reg.personalFileId ||\n\t\t\thealed.shared.length !== reg.shared.length\n\t\t) {\n\t\t\tawait kv.set(KEYS.registry, healed);\n\t\t}\n\t\treturn healed;\n\t}\n\n\tasync function saveRegistry(next: KnownBackups): Promise<void> {\n\t\tm.set({ registry: next });\n\t\tawait kv.set(KEYS.registry, next);\n\t}\n\n\tasync function noteEncrypted(fileId: string, encrypted: boolean): Promise<void> {\n\t\tconst map = (await kv.get<Record<string, boolean>>(KEYS.encrypted)) ?? {};\n\t\tif (map[fileId] === encrypted) return;\n\t\tmap[fileId] = encrypted;\n\t\tawait kv.set(KEYS.encrypted, map);\n\t}\n\n\t/** Stamp the currently attached file (from the target's own persisted id)\n\t * into the snapshot and the registry. Kv-only. */\n\tasync function registerActive(): Promise<void> {\n\t\tif (store.state.targetKind !== host.kind) {\n\t\t\tm.set({ activeFileId: null });\n\t\t\treturn;\n\t\t}\n\t\tconst fileId = (await kv.get<string>(host.activeIdKey)) ?? null;\n\t\tm.set({ activeFileId: fileId });\n\t\tif (!fileId) return;\n\t\tconst excluded = (await host.excludedFileIds?.()) ?? [];\n\t\tif (excluded.includes(fileId)) return;\n\t\tconst reg = await loadRegistry();\n\t\tif (m.snapshot.joined) {\n\t\t\tconst owner = m.snapshot.owner;\n\t\t\tconst entry = { fileId, ownerEmail: owner?.email ?? null, ownerName: owner?.name ?? null };\n\t\t\tawait saveRegistry({\n\t\t\t\tpersonalFileId: reg.personalFileId === fileId ? null : reg.personalFileId,\n\t\t\t\tshared: [entry, ...reg.shared.filter((s) => s.fileId !== fileId)]\n\t\t\t});\n\t\t\treturn;\n\t\t}\n\t\t// Not joined. The personal slot belongs to the CANONICAL file alone: an\n\t\t// own NAMED backup lists live and must not clobber it (the slot is the\n\t\t// escape back to the personal file).\n\t\tconst canonical =\n\t\t\tfileId === ownCanonicalId ||\n\t\t\treg.personalFileId === fileId ||\n\t\t\t(ownIds === null && reg.personalFileId === null);\n\t\tif (canonical) {\n\t\t\tawait saveRegistry({\n\t\t\t\tpersonalFileId: fileId,\n\t\t\t\tshared: reg.shared.filter((s) => s.fileId !== fileId)\n\t\t\t});\n\t\t} else if (reg.shared.some((s) => s.fileId === fileId)) {\n\t\t\tawait saveRegistry({\n\t\t\t\tpersonalFileId: reg.personalFileId,\n\t\t\t\tshared: reg.shared.filter((s) => s.fileId !== fileId)\n\t\t\t});\n\t\t}\n\t}\n\n\t/** Session guard shared by the remote gestures: absent hook = no guard. */\n\tasync function ensureSession(): Promise<boolean> {\n\t\tif (!host.ensureSession) return true;\n\t\treturn host.ensureSession();\n\t}\n\n\tasync function markActive(fileId: string): Promise<void> {\n\t\tconst reg = await loadRegistry();\n\t\tconst knownShared = reg.shared.find((s) => s.fileId === fileId);\n\t\tlet mine = reg.personalFileId === fileId;\n\t\tif (!mine && !knownShared) {\n\t\t\t// Unknown file: ask the destination whose it is (one listing call,\n\t\t\t// cached). An own named backup must never read as joined - labeled\n\t\t\t// \"shared by me\", it would sit in the wrong section for good.\n\t\t\tawait ensureOwnKnown();\n\t\t\tmine = ownIds === null ? reg.personalFileId === null : ownIds.has(fileId);\n\t\t}\n\t\tif (mine || (!knownShared && ownIds?.has(fileId))) {\n\t\t\tawait kv.del(KEYS.joined);\n\t\t\tawait kv.del(KEYS.owner);\n\t\t\tm.set({ joined: false, owner: null });\n\t\t} else {\n\t\t\tconst owner: BackupOwner | null = knownShared\n\t\t\t\t? { email: knownShared.ownerEmail, name: knownShared.ownerName }\n\t\t\t\t: ((await host.fileOwner?.(fileId).catch(() => null)) ?? null);\n\t\t\tm.set({ joined: true, owner });\n\t\t\tawait kv.set(KEYS.joined, true);\n\t\t\tif (owner) await kv.set(KEYS.owner, owner);\n\t\t\telse await kv.del(KEYS.owner);\n\t\t}\n\t\tawait registerActive();\n\t}\n\n\t/** The one attach path every open shares: FIXED-id target, and the\n\t * remembered id committed only after the attach succeeds - committed up\n\t * front, the OUTGOING target (which resolves its file lazily from that\n\t * same key) would flush the old file's data into the one being opened:\n\t * two \"isolated\" backups silently merged. `wipe` keeps sync-meta from\n\t * crossing files (cross-file tombstones erase the other side). */\n\tasync function attachFixed(\n\t\tfileId: string,\n\t\tpassphrase: string | null,\n\t\tstrategy: 'replace-local' | 'replace-remote'\n\t): Promise<void> {\n\t\tconst target = host.open(fileId);\n\t\tawait store.attachTarget(target, {\n\t\t\tpassword: passphrase,\n\t\t\tstrategy,\n\t\t\tkeepSession: store.state.targetKind === host.kind,\n\t\t\twipe: true\n\t\t});\n\t\tawait kv.set(host.activeIdKey, fileId);\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\tfileNameFor(label: string): string {\n\t\t\treturn naming.namedFileFor(label.trim());\n\t\t},\n\n\t\tasync hydrate(): Promise<void> {\n\t\t\tconst joined = (await kv.get(KEYS.joined)) === true;\n\t\t\tconst owner = (await kv.get<BackupOwner>(KEYS.owner)) ?? null;\n\t\t\tm.set({ joined, owner, registry: await loadRegistry() });\n\t\t\tawait registerActive();\n\t\t},\n\n\t\tasync refresh(): Promise<void> {\n\t\t\tm.set({ registry: await loadRegistry() });\n\t\t\tawait registerActive();\n\t\t},\n\n\t\tmarkActive,\n\n\t\tasync list(): Promise<BackupRow[]> {\n\t\t\tconst files = await host.list();\n\t\t\tlearnOwn(files);\n\t\t\tconst enc = (await kv.get<Record<string, boolean>>(KEYS.encrypted)) ?? {};\n\t\t\tconst shr = (await kv.get<Record<string, boolean>>(KEYS.shared)) ?? {};\n\t\t\tconst rows: BackupRow[] = [];\n\t\t\tfor (const f of files) {\n\t\t\t\tconst label = naming.parseLabel(f.name);\n\t\t\t\tif (label === undefined) continue; // not one of ours (a copy, a bulletin, ...)\n\t\t\t\trows.push({\n\t\t\t\t\tfileId: f.id,\n\t\t\t\t\tname: f.name,\n\t\t\t\t\tlabel,\n\t\t\t\t\tmodifiedAt: f.modifiedTime ? Date.parse(f.modifiedTime) || null : null,\n\t\t\t\t\tencrypted: enc[f.id] ?? null,\n\t\t\t\t\tshared: shr[f.id] ?? null\n\t\t\t\t});\n\t\t\t}\n\t\t\treturn rows;\n\t\t},\n\n\t\tasync probeEncryption(fileId: string): Promise<boolean | null> {\n\t\t\ttry {\n\t\t\t\tconst info = await store.inspectTarget(host.open(fileId));\n\t\t\t\tif (!info.hasBackup) return null;\n\t\t\t\tawait noteEncrypted(fileId, info.encrypted);\n\t\t\t\treturn info.encrypted;\n\t\t\t} catch {\n\t\t\t\treturn null;\n\t\t\t}\n\t\t},\n\n\t\tasync noteShared(fileId: string, shared: boolean): Promise<void> {\n\t\t\tconst map = (await kv.get<Record<string, boolean>>(KEYS.shared)) ?? {};\n\t\t\tif (map[fileId] === shared) return;\n\t\t\tmap[fileId] = shared;\n\t\t\tawait kv.set(KEYS.shared, map);\n\t\t},\n\n\t\tasync openBackup(fileId, passphrase = null): Promise<'ok' | 'encrypted' | 'failed'> {\n\t\t\tm.set({ lastError: null });\n\t\t\ttry {\n\t\t\t\tif (!(await ensureSession())) {\n\t\t\t\t\tfail('cancelled');\n\t\t\t\t\treturn 'failed';\n\t\t\t\t}\n\t\t\t\tconst info = await store.inspectTarget(host.open(fileId));\n\t\t\t\tif (!info.hasBackup) {\n\t\t\t\t\t// Dead entry (deleted, or unshared from this account): prune it so\n\t\t\t\t\t// its row stops re-failing forever. A transient failure throws\n\t\t\t\t\t// instead and never prunes - dropping a good record on a blip\n\t\t\t\t\t// would be far worse than one more failed tap.\n\t\t\t\t\tconst reg = await loadRegistry();\n\t\t\t\t\tawait saveRegistry({\n\t\t\t\t\t\tpersonalFileId: reg.personalFileId === fileId ? null : reg.personalFileId,\n\t\t\t\t\t\tshared: reg.shared.filter((s) => s.fileId !== fileId)\n\t\t\t\t\t});\n\t\t\t\t\tfail('gone');\n\t\t\t\t\treturn 'failed';\n\t\t\t\t}\n\t\t\t\tawait noteEncrypted(fileId, info.encrypted); // the listing cannot tell\n\t\t\t\tif (info.encrypted && !passphrase) return 'encrypted';\n\t\t\t\tawait attachFixed(fileId, passphrase, 'replace-local');\n\t\t\t\tawait markActive(fileId);\n\t\t\t\treturn store.state.locked ? 'encrypted' : 'ok';\n\t\t\t} catch {\n\t\t\t\tfail('failed');\n\t\t\t\treturn 'failed';\n\t\t\t}\n\t\t},\n\n\t\tasync openPersonal(passphrase = null): Promise<'ok' | 'encrypted' | 'failed'> {\n\t\t\tm.set({ lastError: null });\n\t\t\ttry {\n\t\t\t\tconst own = await host.findOrCreatePersonal();\n\t\t\t\tif (!own) {\n\t\t\t\t\tfail('cancelled');\n\t\t\t\t\treturn 'failed';\n\t\t\t\t}\n\t\t\t\townCanonicalId = own.fileId;\n\t\t\t\townIds?.add(own.fileId);\n\t\t\t\tconst reg = await loadRegistry();\n\t\t\t\tawait saveRegistry({\n\t\t\t\t\tpersonalFileId: own.fileId,\n\t\t\t\t\tshared: reg.shared.filter((s) => s.fileId !== own.fileId)\n\t\t\t\t});\n\t\t\t\tif (!own.created) return await this.openBackup(own.fileId, passphrase);\n\t\t\t\tawait attachFixed(own.fileId, null, 'replace-remote');\n\t\t\t\tawait markActive(own.fileId);\n\t\t\t\treturn 'ok';\n\t\t\t} catch {\n\t\t\t\tfail('failed');\n\t\t\t\treturn 'failed';\n\t\t\t}\n\t\t},\n\n\t\tasync createNamed(label): Promise<'ok' | 'failed'> {\n\t\t\tm.set({ lastError: null });\n\t\t\tconst clean = label.trim();\n\t\t\tif (!clean) {\n\t\t\t\tfail('failed');\n\t\t\t\treturn 'failed';\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tif (!(await ensureSession())) {\n\t\t\t\t\tfail('cancelled');\n\t\t\t\t\treturn 'failed';\n\t\t\t\t}\n\t\t\t\tconst { fileId } = await host.create(naming.namedFileFor(clean));\n\t\t\t\townIds?.add(fileId);\n\t\t\t\tawait attachFixed(fileId, null, 'replace-remote');\n\t\t\t\tawait noteEncrypted(fileId, false); // a fresh backup starts unprotected\n\t\t\t\tawait markActive(fileId);\n\t\t\t\treturn 'ok';\n\t\t\t} catch {\n\t\t\t\tfail('failed');\n\t\t\t\treturn 'failed';\n\t\t\t}\n\t\t},\n\n\t\tasync registerShared(fileId, owner): Promise<void> {\n\t\t\tconst reg = await loadRegistry();\n\t\t\tawait saveRegistry({\n\t\t\t\tpersonalFileId: reg.personalFileId === fileId ? null : reg.personalFileId,\n\t\t\t\tshared: [\n\t\t\t\t\t{ fileId, ownerEmail: owner.email, ownerName: owner.name },\n\t\t\t\t\t...reg.shared.filter((s) => s.fileId !== fileId)\n\t\t\t\t]\n\t\t\t});\n\t\t},\n\n\t\tasync createShared(fileName, owner): Promise<'ok' | 'failed'> {\n\t\t\tm.set({ lastError: null });\n\t\t\ttry {\n\t\t\t\tif (!(await ensureSession())) {\n\t\t\t\t\tfail('cancelled');\n\t\t\t\t\treturn 'failed';\n\t\t\t\t}\n\t\t\t\tconst { fileId } = await host.create(fileName);\n\t\t\t\t// Registered as shared BEFORE the attach: markActive must find it in\n\t\t\t\t// the shared registry, or it would resolve this account as the owner\n\t\t\t\t// and file the joined wallet under \"mine\".\n\t\t\t\tawait this.registerShared(fileId, owner);\n\t\t\t\tawait attachFixed(fileId, null, 'replace-remote');\n\t\t\t\tawait noteEncrypted(fileId, false); // a member wallet is never protected\n\t\t\t\tawait markActive(fileId);\n\t\t\t\treturn 'ok';\n\t\t\t} catch {\n\t\t\t\tfail('failed');\n\t\t\t\treturn 'failed';\n\t\t\t}\n\t\t},\n\n\t\tasync renameBackup(fileId, label): Promise<'ok' | 'failed'> {\n\t\t\tm.set({ lastError: null });\n\t\t\tconst clean = label.trim();\n\t\t\tif (!clean || !host.rename) {\n\t\t\t\tfail('failed');\n\t\t\t\treturn 'failed';\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tawait host.rename(fileId, naming.namedFileFor(clean));\n\t\t\t\treturn 'ok';\n\t\t\t} catch {\n\t\t\t\tfail('failed');\n\t\t\t\treturn 'failed';\n\t\t\t}\n\t\t},\n\n\t\tasync deleteBackup(fileId): Promise<boolean> {\n\t\t\tm.set({ lastError: null });\n\t\t\ttry {\n\t\t\t\tif (fileId === m.snapshot.activeFileId) {\n\t\t\t\t\t// Detach first: the app goes device-only (local data stays) so\n\t\t\t\t\t// nothing writes to the file as it is removed. keepSession: a\n\t\t\t\t\t// default detach runs the target's disconnect, which forgets the\n\t\t\t\t\t// destination credentials - the remove below still needs them,\n\t\t\t\t\t// and the user deleted a FILE, not their account connection.\n\t\t\t\t\tm.set({ activeFileId: null });\n\t\t\t\t\tawait store.detachTarget({ keepSession: true });\n\t\t\t\t\t// The remembered file id would normally be cleared by that\n\t\t\t\t\t// disconnect; do it here so nothing dangles on a deleted file.\n\t\t\t\t\tawait kv.del(host.activeIdKey);\n\t\t\t\t}\n\t\t\t\tawait host.remove(fileId);\n\t\t\t\tconst reg = await loadRegistry();\n\t\t\t\tawait saveRegistry({\n\t\t\t\t\tpersonalFileId: reg.personalFileId === fileId ? null : reg.personalFileId,\n\t\t\t\t\tshared: reg.shared.filter((s) => s.fileId !== fileId)\n\t\t\t\t});\n\t\t\t\treturn true;\n\t\t\t} catch {\n\t\t\t\tfail('failed');\n\t\t\t\treturn false;\n\t\t\t}\n\t\t},\n\n\t\tasync forgetShared(fileId): Promise<void> {\n\t\t\tconst reg = await loadRegistry();\n\t\t\tawait saveRegistry({\n\t\t\t\tpersonalFileId: reg.personalFileId,\n\t\t\t\tshared: reg.shared.filter((s) => s.fileId !== fileId)\n\t\t\t});\n\t\t}\n\t};\n}\n","// The BackupsHost port, wired over the Drive target's own operations.\n//\n// The manager is deliberately injectable: it drives a destination through the\n// port and knows nothing about Drive. But the library already ships every\n// operation that port asks for - list, open-by-id, create, rename, delete,\n// find-or-create the personal file - so every app wiring them together again\n// was writing the same adapter, and getting the same three details wrong:\n//\n// - `open` must be a target over a FIXED id (`preview`), not a connect: a\n// connect re-resolves by name and would silently merge two backups.\n// - the active id lives under the SAME kv key the connect path reads, or a\n// reload adopts a different file than the one on screen.\n// - a listing must not raise the reconnect gate: `ensureSession` re-consents\n// first, so a lapsed session is a question, not an error.\n//\n// This is that adapter, once.\n\nimport type { BackupFileInfo, BackupsHost } from './manager';\nimport type { BackupTarget } from '../persistence/target';\nimport type { DriveOptions } from '../persistence/targets/drive';\nimport {\n\tFILE_ID_KEY,\n\tcreateBackup,\n\tdeleteBackup,\n\tfindOrCreateOwnFile,\n\tlistBackups,\n\tpreview,\n\trenameBackup\n} from '../persistence/targets/drive';\n\nexport interface DriveBackupsHostOptions extends DriveOptions {\n\t/** Narrow the listing server-side to names containing this fragment (the\n\t * app's own stem, typically). The manager still applies its naming rules on\n\t * what comes back, so this is a bandwidth hint and never a filter to rely\n\t * on for correctness. Default: the whole visible listing. */\n\tnameContains?: string;\n}\n\n/**\n * The Drive destination as a `BackupsHost`, ready for `createBackupsManager`\n * (and therefore for `<selfstore-backups>`).\n *\n * ```ts\n * const manager = createBackupsManager({\n * store: store.engine,\n * kv: store.kv,\n * host: driveBackupsHost({ auth, kv: store.kv, fileName: 'app.zip' }),\n * naming: { canonicalName: 'app.zip' }\n * });\n * ```\n *\n * The `drive.file` scope already narrows what a listing can see to the files\n * this app created or was handed, so nothing here can enumerate a user's Drive.\n */\nexport function driveBackupsHost(opts: DriveBackupsHostOptions): BackupsHost {\n\treturn {\n\t\tkind: 'drive',\n\t\tactiveIdKey: FILE_ID_KEY,\n\n\t\tasync list(): Promise<BackupFileInfo[]> {\n\t\t\treturn listBackups({ auth: opts.auth, nameContains: opts.nameContains });\n\t\t},\n\n\t\topen(fileId: string): BackupTarget {\n\t\t\t// `preview`, not `connect`: a target bound to this exact id, and it\n\t\t\t// writes nothing to the kv - the manager decides what becomes active.\n\t\t\treturn preview(opts, fileId);\n\t\t},\n\n\t\tasync create(fileName: string): Promise<{ fileId: string }> {\n\t\t\treturn createBackup({ auth: opts.auth, fileName });\n\t\t},\n\n\t\tasync remove(fileId: string): Promise<void> {\n\t\t\treturn deleteBackup({ auth: opts.auth, fileId });\n\t\t},\n\n\t\tasync rename(fileId: string, fileName: string): Promise<void> {\n\t\t\treturn renameBackup({ auth: opts.auth, fileId, fileName });\n\t\t},\n\n\t\tasync findOrCreatePersonal(): Promise<{ fileId: string; created: boolean } | null> {\n\t\t\treturn findOrCreateOwnFile(opts);\n\t\t},\n\n\t\tasync ensureSession(): Promise<boolean> {\n\t\t\t// A live token is the cheap answer. Only when there is none does the\n\t\t\t// user get a consent popup - and refusing it is an answer, not a\n\t\t\t// failure: the caller aborts the gesture as cancelled.\n\t\t\ttry {\n\t\t\t\tawait opts.auth.token();\n\t\t\t\treturn true;\n\t\t\t} catch {\n\t\t\t\treturn opts.auth.reconnect();\n\t\t\t}\n\t\t}\n\t};\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/backups/manager.ts","../src/backups/drive-host.ts"],"names":[],"mappings":"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","file":"chunk-MDEEAQSU.js","sourcesContent":["// Multi-backup manager: several backup files on one destination account, one\n// attached at a time, each an isolated snapshot - opening one replaces the\n// local data, never merges, never touches the other files.\n//\n// It owns the bookkeeping a backup panel needs: the self-healing registry of\n// files this device has connected to; the per-file memories a listing cannot\n// provide (encrypted? shared? - those bits live inside the files, learned as\n// they are opened or probed and remembered in the kv); and the open / create\n// / rename / delete gestures with their isolation invariants (fixed-id\n// targets, active-id committed only after a successful attach, wipe on every\n// switch so no sync-meta crosses files).\n//\n// Injectable and headless: a LocalStore + a kv + a BackupsHost port for the\n// destination I/O. Errors surface as stable codes, never prose; hosts wrap\n// subscribe in their own reactivity.\n\nimport type { KV } from '../persistence/cache';\nimport type { LocalStore } from '../persistence/store';\nimport type { BackupTarget } from '../persistence/target';\nimport { makeMachine, type FlowStore } from '../flows/machine';\n\n// --- Ports --------------------------------------------------------------------\n\n/** What the destination reports about a listed file (one cheap metadata call). */\nexport interface BackupFileInfo {\n\tid: string;\n\tname: string;\n\t/** RFC 3339 last-write timestamp, when the destination reports one. */\n\tmodifiedTime: string | null;\n}\n\n/** The destination I/O the manager drives - the host wires its own adapter\n * (e.g. over the Drive target ops). Every method may throw; the manager turns\n * failures into stable error codes. */\nexport interface BackupsHost {\n\t/** The target kind the managed files live on (e.g. 'drive'): the manager\n\t * only stamps an active file while the store is attached to this kind. */\n\tkind: string;\n\t/** The kv key the attached file id is persisted under (the same one the\n\t * target's connect path reads first). */\n\tactiveIdKey: string;\n\t/** List candidate backup files on the account. The manager applies the\n\t * naming rules on what comes back; internal files simply do not match. */\n\tlist(): Promise<BackupFileInfo[]>;\n\t/** A target over a FIXED file id - never re-resolved by name. The fixed id\n\t * is what keeps two backups from silently merging mid-switch. */\n\topen(fileId: string): BackupTarget;\n\t/** Create an empty file under this name and return its id. Refuse a name\n\t * the account already carries (a duplicate poisons find-by-name). */\n\tcreate(fileName: string): Promise<{ fileId: string }>;\n\t/** Delete a file for good. */\n\tremove(fileId: string): Promise<void>;\n\t/** Rename a file in place (metadata only: the id must not change). */\n\trename?(fileId: string, fileName: string): Promise<void>;\n\t/** Find (or create) the PERSONAL file by its canonical name, ignoring any\n\t * currently adopted id. Null when the user backs out of the session. */\n\tfindOrCreatePersonal(): Promise<{ fileId: string; created: boolean } | null>;\n\t/** Make sure a usable session exists before a remote call (re-consent when\n\t * it lapsed). False = the user refused; the gesture aborts as 'cancelled'.\n\t * Absent = sessions are the target's own business. */\n\tensureSession?(): Promise<boolean>;\n\t/** Resolve the owner label of a file this account does not own, for the\n\t * shared registry (\"shared by X\"). Best-effort. */\n\tfileOwner?(fileId: string): Promise<{ email: string | null; name: string | null } | null>;\n\t/** File ids that must never enter the registry even though the store may\n\t * attach them (e.g. a sharing engine's published copy file). */\n\texcludedFileIds?(): Promise<(string | null | undefined)[]>;\n}\n\n/** How backup files are named on the destination. */\nexport interface BackupsNaming {\n\t/** The personal, unnamed file (\"App.zip\"). Its label reads as null. */\n\tcanonicalName: string;\n\t/** label -> file name. Default: \"Stem (label).ext\" derived from\n\t * canonicalName (\"App.zip\" + \"family\" -> \"App (family).zip\"). */\n\tnamedFileFor?(label: string): string;\n\t/** file name -> parsed label; null = the personal file; undefined = not one\n\t * of ours (dropped from listings). Default matches the default namedFileFor. */\n\tparseLabel?(name: string): string | null | undefined;\n}\n\n/** The kv keys the manager persists under - override to keep a pre-existing\n * install's records. */\nexport interface BackupsKeys {\n\t/** The known-backups registry. */\n\tregistry?: string;\n\t/** fileId -> encrypted? map. */\n\tencrypted?: string;\n\t/** fileId -> shared? map. */\n\tshared?: string;\n\t/** Set while the ACTIVE file is not the personal one (another account's). */\n\tjoined?: string;\n\t/** Cached owner label of that file. */\n\towner?: string;\n}\n\n// --- Shapes -------------------------------------------------------------------\n\n/** Every backup this device has connected to: its own personal file and each\n * OTHER account's file it was let into. Isolated snapshots, all of them. */\nexport interface KnownBackups {\n\tpersonalFileId: string | null;\n\tshared: { fileId: string; ownerEmail: string | null; ownerName: string | null }[];\n}\n\n/** One row of the \"my backups\" list: a file this account owns. */\nexport interface BackupRow {\n\tfileId: string;\n\tname: string;\n\t/** Parsed display label; null for the personal, unnamed file. */\n\tlabel: string | null;\n\t/** Last-write time, epoch ms (null when the destination did not say). */\n\tmodifiedAt: number | null;\n\t/** true/false once known (opened, created or probed); null until then. */\n\tencrypted: boolean | null;\n\t/** true/false once learned (while this file was the active one); null\n\t * until then. A share link existing is the shared state. */\n\tshared: boolean | null;\n}\n\n/** The owner label of a joined (another account's) file. */\nexport interface BackupOwner {\n\temail: string | null;\n\tname: string | null;\n}\n\n/** Stable failure codes; the host maps them to its own copy. */\nexport type BackupsErrorCode = 'cancelled' | 'gone' | 'failed';\n\nexport interface BackupsSnapshot {\n\tregistry: KnownBackups;\n\t/** The file currently attached, when the store is on the managed kind. */\n\tactiveFileId: string | null;\n\t/** The active file is not the personal one (someone else's). */\n\tjoined: boolean;\n\t/** Who owns the joined file, when known. */\n\towner: BackupOwner | null;\n\t/** The last gesture's failure, or null. Reset at each gesture start. */\n\tlastError: BackupsErrorCode | null;\n}\n\nexport interface BackupsManager extends FlowStore<BackupsSnapshot> {\n\t/** Boot: load registry + joined/owner from kv and stamp the active file.\n\t * Kv-only, no destination call. */\n\thydrate(): Promise<void>;\n\t/** Re-read the registry and restamp the active file. Kv-only, cheap. */\n\trefresh(): Promise<void>;\n\t/** After an attach the manager did not make (a connect widget, a join):\n\t * record the now-active file in the registry and restamp joined/owner. */\n\tmarkActive(fileId: string): Promise<void>;\n\t/** Live list of this account's own files, per the naming rules, decorated\n\t * with the learned memories. One destination listing call. */\n\tlist(): Promise<BackupRow[]>;\n\t/** Learn a listed file's encrypted bit without opening it for real: one\n\t * download + header read, remembered for good. Null when unreachable. */\n\tprobeEncryption(fileId: string): Promise<boolean | null>;\n\t/** Record the ACTIVE file's live share state, keyed by file id, so its row\n\t * stays truthful after a switch away. */\n\tnoteShared(fileId: string, shared: boolean): Promise<void>;\n\t/** Open a known backup: an isolated snapshot replaces the local data.\n\t * 'encrypted' = collect its passphrase and call again with it. A file the\n\t * destination reports GONE is pruned from the registry (lastError 'gone'). */\n\topenBackup(fileId: string, passphrase?: string | null): Promise<'ok' | 'encrypted' | 'failed'>;\n\t/** Open (or create blank) the personal file. */\n\topenPersonal(passphrase?: string | null): Promise<'ok' | 'encrypted' | 'failed'>;\n\t/** Create a brand-new empty named backup and open it (starts blank - never\n\t * seeded from the currently loaded file). */\n\tcreateNamed(label: string): Promise<'ok' | 'failed'>;\n\t/** Record a file in the SHARED registry under the sharer's label, without\n\t * opening it. For a joined portfolio's dedicated wallet: the file may live\n\t * on this very account, yet it presents as \"shared by X\", never as mine. */\n\tregisterShared(fileId: string, owner: BackupOwner): Promise<void>;\n\t/** Create a joined portfolio's dedicated wallet: a file under the given\n\t * name (outside the own-files naming on purpose), attached BLANK - never\n\t * seeded from the previously loaded silo - and recorded in the shared\n\t * registry under the sharer's label. The host stages the shared content\n\t * afterwards (restore the projection, then save). */\n\tcreateShared(fileName: string, owner: BackupOwner): Promise<'ok' | 'failed'>;\n\t/** Rename a file in place (id unchanged, so renaming the ACTIVE file never\n\t * detaches it). The host target refuses a name already carried. */\n\trenameBackup(fileId: string, label: string): Promise<'ok' | 'failed'>;\n\t/** Delete a file for good. Deleting the ACTIVE one detaches first (the app\n\t * goes device-only, local data stays), so nothing writes mid-delete. */\n\tdeleteBackup(fileId: string): Promise<boolean>;\n\t/** Drop a KNOWN shared (another account's) file from this device's\n\t * registry. Forgetting only - the owner's file is never touched. */\n\tforgetShared(fileId: string): Promise<void>;\n\t/** The file name a label produces (the create form's preview line). */\n\tfileNameFor(label: string): string;\n}\n\n// --- Defaults -----------------------------------------------------------------\n\nconst DEFAULT_KEYS: Required<BackupsKeys> = {\n\tregistry: 'selfstore:backups:registry:v1',\n\tencrypted: 'selfstore:backups:encrypted:v1',\n\tshared: 'selfstore:backups:shared:v1',\n\tjoined: 'selfstore:backups:joined:v1',\n\towner: 'selfstore:backups:owner:v1'\n};\n\nconst escapeRe = (s: string): string => s.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n\n/** The default naming scheme: \"App.zip\" -> personal; \"App (label).zip\" ->\n * named. Derived from the canonical name's stem and extension. */\nfunction defaultNaming(\n\tnaming: BackupsNaming\n): Required<Pick<BackupsNaming, 'namedFileFor' | 'parseLabel'>> {\n\tconst dot = naming.canonicalName.lastIndexOf('.');\n\tconst stem = dot > 0 ? naming.canonicalName.slice(0, dot) : naming.canonicalName;\n\tconst ext = dot > 0 ? naming.canonicalName.slice(dot) : '';\n\tconst named = new RegExp(`^${escapeRe(stem)} \\\\((.+)\\\\)${escapeRe(ext)}$`);\n\treturn {\n\t\tnamedFileFor: (label: string) => `${stem} (${label.trim()})${ext}`,\n\t\tparseLabel: (name: string) => {\n\t\t\tif (name === naming.canonicalName) return null;\n\t\t\tconst m = named.exec(name);\n\t\t\treturn m ? m[1] : undefined;\n\t\t}\n\t};\n}\n\n// --- Factory ------------------------------------------------------------------\n\nexport function createBackupsManager(deps: {\n\tstore: LocalStore;\n\tkv: KV;\n\thost: BackupsHost;\n\tnaming: BackupsNaming;\n\tkeys?: BackupsKeys;\n}): BackupsManager {\n\tconst { store, kv, host } = deps;\n\tconst KEYS = { ...DEFAULT_KEYS, ...deps.keys };\n\tconst namingDefaults = defaultNaming(deps.naming);\n\tconst naming = {\n\t\tnamedFileFor: deps.naming.namedFileFor ?? namingDefaults.namedFileFor,\n\t\tparseLabel: deps.naming.parseLabel ?? namingDefaults.parseLabel\n\t};\n\n\tconst m = makeMachine<BackupsSnapshot>({\n\t\tregistry: { personalFileId: null, shared: [] },\n\t\tactiveFileId: null,\n\t\tjoined: false,\n\t\towner: null,\n\t\tlastError: null\n\t});\n\n\tconst fail = (code: BackupsErrorCode): void => m.set({ lastError: code });\n\n\t// Which file ids the destination lists as OURS, and which one carries the\n\t// canonical (personal) name. Filled by list(), refreshed lazily where a\n\t// truthful own/foreign call is needed. Null until first learned.\n\tlet ownIds: Set<string> | null = null;\n\tlet ownCanonicalId: string | null = null;\n\n\tfunction learnOwn(files: BackupFileInfo[]): void {\n\t\tconst ids = new Set<string>();\n\t\tlet canonical: string | null = null;\n\t\tfor (const f of files) {\n\t\t\tconst label = naming.parseLabel(f.name);\n\t\t\tif (label === undefined) continue;\n\t\t\tids.add(f.id);\n\t\t\tif (label === null) canonical = f.id;\n\t\t}\n\t\townIds = ids;\n\t\townCanonicalId = canonical;\n\t}\n\n\tasync function ensureOwnKnown(): Promise<void> {\n\t\tif (ownIds !== null) return;\n\t\ttry {\n\t\t\tlearnOwn(await host.list());\n\t\t} catch {\n\t\t\t// Offline: leave unknown - markActive falls back on the registry alone.\n\t\t}\n\t}\n\n\t/** Load the registry, self-healing: excluded ids (a sharing engine's copy\n\t * file) must never sit in it - recorded as \"personal\" they would mislabel\n\t * the chooser and let a later open treat a shared file as one's own. */\n\tasync function loadRegistry(): Promise<KnownBackups> {\n\t\tconst reg = (await kv.get<KnownBackups>(KEYS.registry)) ?? { personalFileId: null, shared: [] };\n\t\tconst excluded = (await host.excludedFileIds?.()) ?? [];\n\t\tconst bad = new Set(excluded.filter((x): x is string => typeof x === 'string'));\n\t\tif (bad.size === 0) return reg;\n\t\tconst healed: KnownBackups = {\n\t\t\tpersonalFileId: reg.personalFileId && bad.has(reg.personalFileId) ? null : reg.personalFileId,\n\t\t\tshared: reg.shared.filter((s) => !bad.has(s.fileId))\n\t\t};\n\t\tif (\n\t\t\thealed.personalFileId !== reg.personalFileId ||\n\t\t\thealed.shared.length !== reg.shared.length\n\t\t) {\n\t\t\tawait kv.set(KEYS.registry, healed);\n\t\t}\n\t\treturn healed;\n\t}\n\n\tasync function saveRegistry(next: KnownBackups): Promise<void> {\n\t\tm.set({ registry: next });\n\t\tawait kv.set(KEYS.registry, next);\n\t}\n\n\tasync function noteEncrypted(fileId: string, encrypted: boolean): Promise<void> {\n\t\tconst map = (await kv.get<Record<string, boolean>>(KEYS.encrypted)) ?? {};\n\t\tif (map[fileId] === encrypted) return;\n\t\tmap[fileId] = encrypted;\n\t\tawait kv.set(KEYS.encrypted, map);\n\t}\n\n\t/** Stamp the currently attached file (from the target's own persisted id)\n\t * into the snapshot and the registry. Kv-only. */\n\tasync function registerActive(): Promise<void> {\n\t\tif (store.state.targetKind !== host.kind) {\n\t\t\tm.set({ activeFileId: null });\n\t\t\treturn;\n\t\t}\n\t\tconst fileId = (await kv.get<string>(host.activeIdKey)) ?? null;\n\t\tm.set({ activeFileId: fileId });\n\t\tif (!fileId) return;\n\t\tconst excluded = (await host.excludedFileIds?.()) ?? [];\n\t\tif (excluded.includes(fileId)) return;\n\t\tconst reg = await loadRegistry();\n\t\tif (m.snapshot.joined) {\n\t\t\tconst owner = m.snapshot.owner;\n\t\t\tconst entry = { fileId, ownerEmail: owner?.email ?? null, ownerName: owner?.name ?? null };\n\t\t\tawait saveRegistry({\n\t\t\t\tpersonalFileId: reg.personalFileId === fileId ? null : reg.personalFileId,\n\t\t\t\tshared: [entry, ...reg.shared.filter((s) => s.fileId !== fileId)]\n\t\t\t});\n\t\t\treturn;\n\t\t}\n\t\t// Not joined. The personal slot belongs to the CANONICAL file alone: an\n\t\t// own NAMED backup lists live and must not clobber it (the slot is the\n\t\t// escape back to the personal file).\n\t\tconst canonical =\n\t\t\tfileId === ownCanonicalId ||\n\t\t\treg.personalFileId === fileId ||\n\t\t\t(ownIds === null && reg.personalFileId === null);\n\t\tif (canonical) {\n\t\t\tawait saveRegistry({\n\t\t\t\tpersonalFileId: fileId,\n\t\t\t\tshared: reg.shared.filter((s) => s.fileId !== fileId)\n\t\t\t});\n\t\t} else if (reg.shared.some((s) => s.fileId === fileId)) {\n\t\t\tawait saveRegistry({\n\t\t\t\tpersonalFileId: reg.personalFileId,\n\t\t\t\tshared: reg.shared.filter((s) => s.fileId !== fileId)\n\t\t\t});\n\t\t}\n\t}\n\n\t/** Session guard shared by the remote gestures: absent hook = no guard. */\n\tasync function ensureSession(): Promise<boolean> {\n\t\tif (!host.ensureSession) return true;\n\t\treturn host.ensureSession();\n\t}\n\n\tasync function markActive(fileId: string): Promise<void> {\n\t\tconst reg = await loadRegistry();\n\t\tconst knownShared = reg.shared.find((s) => s.fileId === fileId);\n\t\tlet mine = reg.personalFileId === fileId;\n\t\tif (!mine && !knownShared) {\n\t\t\t// Unknown file: ask the destination whose it is (one listing call,\n\t\t\t// cached). An own named backup must never read as joined - labeled\n\t\t\t// \"shared by me\", it would sit in the wrong section for good.\n\t\t\tawait ensureOwnKnown();\n\t\t\tmine = ownIds === null ? reg.personalFileId === null : ownIds.has(fileId);\n\t\t}\n\t\tif (mine || (!knownShared && ownIds?.has(fileId))) {\n\t\t\tawait kv.del(KEYS.joined);\n\t\t\tawait kv.del(KEYS.owner);\n\t\t\tm.set({ joined: false, owner: null });\n\t\t} else {\n\t\t\tconst owner: BackupOwner | null = knownShared\n\t\t\t\t? { email: knownShared.ownerEmail, name: knownShared.ownerName }\n\t\t\t\t: ((await host.fileOwner?.(fileId).catch(() => null)) ?? null);\n\t\t\tm.set({ joined: true, owner });\n\t\t\tawait kv.set(KEYS.joined, true);\n\t\t\tif (owner) await kv.set(KEYS.owner, owner);\n\t\t\telse await kv.del(KEYS.owner);\n\t\t}\n\t\tawait registerActive();\n\t}\n\n\t/** The one attach path every open shares: FIXED-id target, and the\n\t * remembered id committed only after the attach succeeds - committed up\n\t * front, the OUTGOING target (which resolves its file lazily from that\n\t * same key) would flush the old file's data into the one being opened:\n\t * two \"isolated\" backups silently merged. `wipe` keeps sync-meta from\n\t * crossing files (cross-file tombstones erase the other side). */\n\tasync function attachFixed(\n\t\tfileId: string,\n\t\tpassphrase: string | null,\n\t\tstrategy: 'replace-local' | 'replace-remote'\n\t): Promise<void> {\n\t\tconst target = host.open(fileId);\n\t\tawait store.attachTarget(target, {\n\t\t\tpassword: passphrase,\n\t\t\tstrategy,\n\t\t\tkeepSession: store.state.targetKind === host.kind,\n\t\t\twipe: true\n\t\t});\n\t\tawait kv.set(host.activeIdKey, fileId);\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\tfileNameFor(label: string): string {\n\t\t\treturn naming.namedFileFor(label.trim());\n\t\t},\n\n\t\tasync hydrate(): Promise<void> {\n\t\t\tconst joined = (await kv.get(KEYS.joined)) === true;\n\t\t\tconst owner = (await kv.get<BackupOwner>(KEYS.owner)) ?? null;\n\t\t\tm.set({ joined, owner, registry: await loadRegistry() });\n\t\t\tawait registerActive();\n\t\t},\n\n\t\tasync refresh(): Promise<void> {\n\t\t\tm.set({ registry: await loadRegistry() });\n\t\t\tawait registerActive();\n\t\t},\n\n\t\tmarkActive,\n\n\t\tasync list(): Promise<BackupRow[]> {\n\t\t\tconst files = await host.list();\n\t\t\tlearnOwn(files);\n\t\t\tconst enc = (await kv.get<Record<string, boolean>>(KEYS.encrypted)) ?? {};\n\t\t\tconst shr = (await kv.get<Record<string, boolean>>(KEYS.shared)) ?? {};\n\t\t\tconst rows: BackupRow[] = [];\n\t\t\tfor (const f of files) {\n\t\t\t\tconst label = naming.parseLabel(f.name);\n\t\t\t\tif (label === undefined) continue; // not one of ours (a copy, a bulletin, ...)\n\t\t\t\trows.push({\n\t\t\t\t\tfileId: f.id,\n\t\t\t\t\tname: f.name,\n\t\t\t\t\tlabel,\n\t\t\t\t\tmodifiedAt: f.modifiedTime ? Date.parse(f.modifiedTime) || null : null,\n\t\t\t\t\tencrypted: enc[f.id] ?? null,\n\t\t\t\t\tshared: shr[f.id] ?? null\n\t\t\t\t});\n\t\t\t}\n\t\t\treturn rows;\n\t\t},\n\n\t\tasync probeEncryption(fileId: string): Promise<boolean | null> {\n\t\t\ttry {\n\t\t\t\tconst info = await store.inspectTarget(host.open(fileId));\n\t\t\t\tif (!info.hasBackup) return null;\n\t\t\t\tawait noteEncrypted(fileId, info.encrypted);\n\t\t\t\treturn info.encrypted;\n\t\t\t} catch {\n\t\t\t\treturn null;\n\t\t\t}\n\t\t},\n\n\t\tasync noteShared(fileId: string, shared: boolean): Promise<void> {\n\t\t\tconst map = (await kv.get<Record<string, boolean>>(KEYS.shared)) ?? {};\n\t\t\tif (map[fileId] === shared) return;\n\t\t\tmap[fileId] = shared;\n\t\t\tawait kv.set(KEYS.shared, map);\n\t\t},\n\n\t\tasync openBackup(fileId, passphrase = null): Promise<'ok' | 'encrypted' | 'failed'> {\n\t\t\tm.set({ lastError: null });\n\t\t\ttry {\n\t\t\t\tif (!(await ensureSession())) {\n\t\t\t\t\tfail('cancelled');\n\t\t\t\t\treturn 'failed';\n\t\t\t\t}\n\t\t\t\tconst info = await store.inspectTarget(host.open(fileId));\n\t\t\t\tif (!info.hasBackup) {\n\t\t\t\t\t// Dead entry (deleted, or unshared from this account): prune it so\n\t\t\t\t\t// its row stops re-failing forever. A transient failure throws\n\t\t\t\t\t// instead and never prunes - dropping a good record on a blip\n\t\t\t\t\t// would be far worse than one more failed tap.\n\t\t\t\t\tconst reg = await loadRegistry();\n\t\t\t\t\tawait saveRegistry({\n\t\t\t\t\t\tpersonalFileId: reg.personalFileId === fileId ? null : reg.personalFileId,\n\t\t\t\t\t\tshared: reg.shared.filter((s) => s.fileId !== fileId)\n\t\t\t\t\t});\n\t\t\t\t\tfail('gone');\n\t\t\t\t\treturn 'failed';\n\t\t\t\t}\n\t\t\t\tawait noteEncrypted(fileId, info.encrypted); // the listing cannot tell\n\t\t\t\tif (info.encrypted && !passphrase) return 'encrypted';\n\t\t\t\tawait attachFixed(fileId, passphrase, 'replace-local');\n\t\t\t\tawait markActive(fileId);\n\t\t\t\treturn store.state.locked ? 'encrypted' : 'ok';\n\t\t\t} catch {\n\t\t\t\tfail('failed');\n\t\t\t\treturn 'failed';\n\t\t\t}\n\t\t},\n\n\t\tasync openPersonal(passphrase = null): Promise<'ok' | 'encrypted' | 'failed'> {\n\t\t\tm.set({ lastError: null });\n\t\t\ttry {\n\t\t\t\tconst own = await host.findOrCreatePersonal();\n\t\t\t\tif (!own) {\n\t\t\t\t\tfail('cancelled');\n\t\t\t\t\treturn 'failed';\n\t\t\t\t}\n\t\t\t\townCanonicalId = own.fileId;\n\t\t\t\townIds?.add(own.fileId);\n\t\t\t\tconst reg = await loadRegistry();\n\t\t\t\tawait saveRegistry({\n\t\t\t\t\tpersonalFileId: own.fileId,\n\t\t\t\t\tshared: reg.shared.filter((s) => s.fileId !== own.fileId)\n\t\t\t\t});\n\t\t\t\tif (!own.created) return await this.openBackup(own.fileId, passphrase);\n\t\t\t\tawait attachFixed(own.fileId, null, 'replace-remote');\n\t\t\t\tawait markActive(own.fileId);\n\t\t\t\treturn 'ok';\n\t\t\t} catch {\n\t\t\t\tfail('failed');\n\t\t\t\treturn 'failed';\n\t\t\t}\n\t\t},\n\n\t\tasync createNamed(label): Promise<'ok' | 'failed'> {\n\t\t\tm.set({ lastError: null });\n\t\t\tconst clean = label.trim();\n\t\t\tif (!clean) {\n\t\t\t\tfail('failed');\n\t\t\t\treturn 'failed';\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tif (!(await ensureSession())) {\n\t\t\t\t\tfail('cancelled');\n\t\t\t\t\treturn 'failed';\n\t\t\t\t}\n\t\t\t\tconst { fileId } = await host.create(naming.namedFileFor(clean));\n\t\t\t\townIds?.add(fileId);\n\t\t\t\tawait attachFixed(fileId, null, 'replace-remote');\n\t\t\t\tawait noteEncrypted(fileId, false); // a fresh backup starts unprotected\n\t\t\t\tawait markActive(fileId);\n\t\t\t\treturn 'ok';\n\t\t\t} catch {\n\t\t\t\tfail('failed');\n\t\t\t\treturn 'failed';\n\t\t\t}\n\t\t},\n\n\t\tasync registerShared(fileId, owner): Promise<void> {\n\t\t\tconst reg = await loadRegistry();\n\t\t\tawait saveRegistry({\n\t\t\t\tpersonalFileId: reg.personalFileId === fileId ? null : reg.personalFileId,\n\t\t\t\tshared: [\n\t\t\t\t\t{ fileId, ownerEmail: owner.email, ownerName: owner.name },\n\t\t\t\t\t...reg.shared.filter((s) => s.fileId !== fileId)\n\t\t\t\t]\n\t\t\t});\n\t\t},\n\n\t\tasync createShared(fileName, owner): Promise<'ok' | 'failed'> {\n\t\t\tm.set({ lastError: null });\n\t\t\ttry {\n\t\t\t\tif (!(await ensureSession())) {\n\t\t\t\t\tfail('cancelled');\n\t\t\t\t\treturn 'failed';\n\t\t\t\t}\n\t\t\t\tconst { fileId } = await host.create(fileName);\n\t\t\t\t// Registered as shared BEFORE the attach: markActive must find it in\n\t\t\t\t// the shared registry, or it would resolve this account as the owner\n\t\t\t\t// and file the joined wallet under \"mine\".\n\t\t\t\tawait this.registerShared(fileId, owner);\n\t\t\t\tawait attachFixed(fileId, null, 'replace-remote');\n\t\t\t\tawait noteEncrypted(fileId, false); // a member wallet is never protected\n\t\t\t\tawait markActive(fileId);\n\t\t\t\treturn 'ok';\n\t\t\t} catch {\n\t\t\t\tfail('failed');\n\t\t\t\treturn 'failed';\n\t\t\t}\n\t\t},\n\n\t\tasync renameBackup(fileId, label): Promise<'ok' | 'failed'> {\n\t\t\tm.set({ lastError: null });\n\t\t\tconst clean = label.trim();\n\t\t\tif (!clean || !host.rename) {\n\t\t\t\tfail('failed');\n\t\t\t\treturn 'failed';\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tawait host.rename(fileId, naming.namedFileFor(clean));\n\t\t\t\treturn 'ok';\n\t\t\t} catch {\n\t\t\t\tfail('failed');\n\t\t\t\treturn 'failed';\n\t\t\t}\n\t\t},\n\n\t\tasync deleteBackup(fileId): Promise<boolean> {\n\t\t\tm.set({ lastError: null });\n\t\t\ttry {\n\t\t\t\tif (fileId === m.snapshot.activeFileId) {\n\t\t\t\t\t// Detach first: the app goes device-only (local data stays) so\n\t\t\t\t\t// nothing writes to the file as it is removed. keepSession: a\n\t\t\t\t\t// default detach runs the target's disconnect, which forgets the\n\t\t\t\t\t// destination credentials - the remove below still needs them,\n\t\t\t\t\t// and the user deleted a FILE, not their account connection.\n\t\t\t\t\tm.set({ activeFileId: null });\n\t\t\t\t\tawait store.detachTarget({ keepSession: true });\n\t\t\t\t\t// The remembered file id would normally be cleared by that\n\t\t\t\t\t// disconnect; do it here so nothing dangles on a deleted file.\n\t\t\t\t\tawait kv.del(host.activeIdKey);\n\t\t\t\t}\n\t\t\t\tawait host.remove(fileId);\n\t\t\t\tconst reg = await loadRegistry();\n\t\t\t\tawait saveRegistry({\n\t\t\t\t\tpersonalFileId: reg.personalFileId === fileId ? null : reg.personalFileId,\n\t\t\t\t\tshared: reg.shared.filter((s) => s.fileId !== fileId)\n\t\t\t\t});\n\t\t\t\treturn true;\n\t\t\t} catch {\n\t\t\t\tfail('failed');\n\t\t\t\treturn false;\n\t\t\t}\n\t\t},\n\n\t\tasync forgetShared(fileId): Promise<void> {\n\t\t\tconst reg = await loadRegistry();\n\t\t\tawait saveRegistry({\n\t\t\t\tpersonalFileId: reg.personalFileId,\n\t\t\t\tshared: reg.shared.filter((s) => s.fileId !== fileId)\n\t\t\t});\n\t\t}\n\t};\n}\n","// The BackupsHost port, wired over the Drive target's own operations.\n//\n// The manager is deliberately injectable: it drives a destination through the\n// port and knows nothing about Drive. But the library already ships every\n// operation that port asks for - list, open-by-id, create, rename, delete,\n// find-or-create the personal file - so every app wiring them together again\n// was writing the same adapter, and getting the same three details wrong:\n//\n// - `open` must be a target over a FIXED id (`preview`), not a connect: a\n// connect re-resolves by name and would silently merge two backups.\n// - the active id lives under the SAME kv key the connect path reads, or a\n// reload adopts a different file than the one on screen.\n// - a listing must not raise the reconnect gate: `ensureSession` re-consents\n// first, so a lapsed session is a question, not an error.\n//\n// This is that adapter, once.\n\nimport type { BackupFileInfo, BackupsHost } from './manager';\nimport type { BackupTarget } from '../persistence/target';\nimport type { DriveOptions } from '../persistence/targets/drive';\nimport {\n\tFILE_ID_KEY,\n\tcreateBackup,\n\tdeleteBackup,\n\tfindOrCreateOwnFile,\n\tlistBackups,\n\tpreview,\n\trenameBackup\n} from '../persistence/targets/drive';\n\nexport interface DriveBackupsHostOptions extends DriveOptions {\n\t/** Narrow the listing server-side to names containing this fragment (the\n\t * app's own stem, typically). The manager still applies its naming rules on\n\t * what comes back, so this is a bandwidth hint and never a filter to rely\n\t * on for correctness. Default: the whole visible listing. */\n\tnameContains?: string;\n}\n\n/**\n * The Drive destination as a `BackupsHost`, ready for `createBackupsManager`\n * (and therefore for `<selfstore-backups>`).\n *\n * ```ts\n * const manager = createBackupsManager({\n * store: store.engine,\n * kv: store.kv,\n * host: driveBackupsHost({ auth, kv: store.kv, fileName: 'app.zip' }),\n * naming: { canonicalName: 'app.zip' }\n * });\n * ```\n *\n * The `drive.file` scope already narrows what a listing can see to the files\n * this app created or was handed, so nothing here can enumerate a user's Drive.\n */\nexport function driveBackupsHost(opts: DriveBackupsHostOptions): BackupsHost {\n\treturn {\n\t\tkind: 'drive',\n\t\tactiveIdKey: FILE_ID_KEY,\n\n\t\tasync list(): Promise<BackupFileInfo[]> {\n\t\t\treturn listBackups({ auth: opts.auth, nameContains: opts.nameContains });\n\t\t},\n\n\t\topen(fileId: string): BackupTarget {\n\t\t\t// `preview`, not `connect`: a target bound to this exact id, and it\n\t\t\t// writes nothing to the kv - the manager decides what becomes active.\n\t\t\treturn preview(opts, fileId);\n\t\t},\n\n\t\tasync create(fileName: string): Promise<{ fileId: string }> {\n\t\t\treturn createBackup({ auth: opts.auth, fileName });\n\t\t},\n\n\t\tasync remove(fileId: string): Promise<void> {\n\t\t\treturn deleteBackup({ auth: opts.auth, fileId });\n\t\t},\n\n\t\tasync rename(fileId: string, fileName: string): Promise<void> {\n\t\t\treturn renameBackup({ auth: opts.auth, fileId, fileName });\n\t\t},\n\n\t\tasync findOrCreatePersonal(): Promise<{ fileId: string; created: boolean } | null> {\n\t\t\treturn findOrCreateOwnFile(opts);\n\t\t},\n\n\t\tasync ensureSession(): Promise<boolean> {\n\t\t\t// A live token is the cheap answer. Only when there is none does the\n\t\t\t// user get a consent popup - and refusing it is an answer, not a\n\t\t\t// failure: the caller aborts the gesture as cancelled.\n\t\t\ttry {\n\t\t\t\tawait opts.auth.token();\n\t\t\t\treturn true;\n\t\t\t} catch {\n\t\t\t\treturn opts.auth.reconnect();\n\t\t\t}\n\t\t}\n\t};\n}\n"]}
|
|
@@ -12,18 +12,18 @@ async function load(what, mod) {
|
|
|
12
12
|
}
|
|
13
13
|
}
|
|
14
14
|
async function webdavConnect(opts) {
|
|
15
|
-
return (await load("WebDAV", () => import('./webdav-
|
|
15
|
+
return (await load("WebDAV", () => import('./webdav-5YKO47EE.js'))).connect(opts);
|
|
16
16
|
}
|
|
17
17
|
async function webdavFromSession(opts) {
|
|
18
|
-
return (await load("WebDAV", () => import('./webdav-
|
|
18
|
+
return (await load("WebDAV", () => import('./webdav-5YKO47EE.js'))).fromSession(opts);
|
|
19
19
|
}
|
|
20
20
|
async function s3Connect(opts) {
|
|
21
|
-
return (await load("S3", () => import('./s3-
|
|
21
|
+
return (await load("S3", () => import('./s3-KZPMSI4J.js'))).connect(opts);
|
|
22
22
|
}
|
|
23
23
|
async function s3FromSession(opts) {
|
|
24
|
-
return (await load("S3", () => import('./s3-
|
|
24
|
+
return (await load("S3", () => import('./s3-KZPMSI4J.js'))).fromSession(opts);
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
export { s3Connect, s3FromSession, webdavConnect, webdavFromSession };
|
|
28
|
-
//# sourceMappingURL=chunk-
|
|
29
|
-
//# sourceMappingURL=chunk-
|
|
28
|
+
//# sourceMappingURL=chunk-TNYL6FNW.js.map
|
|
29
|
+
//# sourceMappingURL=chunk-TNYL6FNW.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/persistence/targets/servers.ts"],"names":[],"mappings":";;;AA4BA,eAAe,IAAA,CAAQ,MAAc,GAAA,EAAmC;AACvE,EAAA,IAAI;AACH,IAAA,OAAO,MAAM,GAAA,EAAI;AAAA,EAClB,SAAS,CAAA,EAAG;AACX,IAAA,MAAM,IAAI,cAAA;AAAA,MACT,oBAAA;AAAA,MACA,CAAA,IAAA,EAAO,IAAI,CAAA,8BAAA,EAAiC,CAAA,YAAa,QAAQ,CAAA,CAAE,OAAA,GAAU,MAAA,CAAO,CAAC,CAAC,CAAA,EAAA;AAAA,KACvF;AAAA,EACD;AACD;AAEA,eAAsB,cAAc,IAAA,EAA0D;AAC7F,EAAA,OAAA,CAAQ,MAAM,KAAK,QAAA,EAAU,MAAM,OAAO,sBAAU,CAAC,CAAA,EAAG,OAAA,CAAQ,IAAI,CAAA;AACrE;AAEA,eAAsB,kBAAkB,IAAA,EAAgD;AACvF,EAAA,OAAA,CAAQ,MAAM,KAAK,QAAA,EAAU,MAAM,OAAO,sBAAU,CAAC,CAAA,EAAG,WAAA,CAAY,IAAI,CAAA;AACzE;AAEA,eAAsB,UAAU,IAAA,EAAsD;AACrF,EAAA,OAAA,CAAQ,MAAM,KAAK,IAAA,EAAM,MAAM,OAAO,kBAAM,CAAC,CAAA,EAAG,OAAA,CAAQ,IAAI,CAAA;AAC7D;AAEA,eAAsB,cAAc,IAAA,EAAgD;AACnF,EAAA,OAAA,CAAQ,MAAM,KAAK,IAAA,EAAM,MAAM,OAAO,kBAAM,CAAC,CAAA,EAAG,WAAA,CAAY,IAAI,CAAA;AACjE","file":"chunk-
|
|
1
|
+
{"version":3,"sources":["../src/persistence/targets/servers.ts"],"names":[],"mappings":";;;AA4BA,eAAe,IAAA,CAAQ,MAAc,GAAA,EAAmC;AACvE,EAAA,IAAI;AACH,IAAA,OAAO,MAAM,GAAA,EAAI;AAAA,EAClB,SAAS,CAAA,EAAG;AACX,IAAA,MAAM,IAAI,cAAA;AAAA,MACT,oBAAA;AAAA,MACA,CAAA,IAAA,EAAO,IAAI,CAAA,8BAAA,EAAiC,CAAA,YAAa,QAAQ,CAAA,CAAE,OAAA,GAAU,MAAA,CAAO,CAAC,CAAC,CAAA,EAAA;AAAA,KACvF;AAAA,EACD;AACD;AAEA,eAAsB,cAAc,IAAA,EAA0D;AAC7F,EAAA,OAAA,CAAQ,MAAM,KAAK,QAAA,EAAU,MAAM,OAAO,sBAAU,CAAC,CAAA,EAAG,OAAA,CAAQ,IAAI,CAAA;AACrE;AAEA,eAAsB,kBAAkB,IAAA,EAAgD;AACvF,EAAA,OAAA,CAAQ,MAAM,KAAK,QAAA,EAAU,MAAM,OAAO,sBAAU,CAAC,CAAA,EAAG,WAAA,CAAY,IAAI,CAAA;AACzE;AAEA,eAAsB,UAAU,IAAA,EAAsD;AACrF,EAAA,OAAA,CAAQ,MAAM,KAAK,IAAA,EAAM,MAAM,OAAO,kBAAM,CAAC,CAAA,EAAG,OAAA,CAAQ,IAAI,CAAA;AAC7D;AAEA,eAAsB,cAAc,IAAA,EAAgD;AACnF,EAAA,OAAA,CAAQ,MAAM,KAAK,IAAA,EAAM,MAAM,OAAO,kBAAM,CAAC,CAAA,EAAG,WAAA,CAAY,IAAI,CAAA;AACjE","file":"chunk-TNYL6FNW.js","sourcesContent":["// The two destinations that reach a server the user runs - WebDAV and an\n// S3-compatible bucket - loaded on the gesture that uses them instead of with\n// the module that offers them.\n//\n// Why these two and not all four: Drive and the disk file both open a picker or\n// a consent popup, and a browser only allows that inside the transient\n// activation of the user's click. An `await import()` before the call can spend\n// that activation and turn a working button into a silently blocked popup. A\n// WebDAV or S3 connection is plain fetch behind a form the user already\n// submitted - nothing to preserve, so the chunk can arrive late.\n//\n// What it buys: an app that offers Drive and a disk file - the common case -\n// stops shipping the SigV4 request signer and the WebDAV client. Before this,\n// importing the connect journey (so: any of the widgets) pulled both in whether\n// or not the app named them, which made \"destinations are opt-in\" true of the\n// API and false of the bytes.\n//\n// `selfstore/advanced` keeps its STATIC `webdavTarget` / `s3Target`: naming one\n// of those is the opt-in, and an app that wrote the import wants the code.\n\nimport { SelfstoreError } from '../../selfstore/errors';\nimport type { BackupTarget } from '../target';\nimport type { KV } from '../cache';\nimport type { WebdavConnectOptions } from './webdav';\nimport type { S3ConnectOptions } from './s3';\n\n/** A chunk that does not arrive is the destination not answering: same code,\n * same retry, rather than a bare Error escaping a public path. */\nasync function load<T>(what: string, mod: () => Promise<T>): Promise<T> {\n\ttry {\n\t\treturn await mod();\n\t} catch (e) {\n\t\tthrow new SelfstoreError(\n\t\t\t'TARGET_UNAVAILABLE',\n\t\t\t`The ${what} support could not be loaded (${e instanceof Error ? e.message : String(e)}).`\n\t\t);\n\t}\n}\n\nexport async function webdavConnect(opts: WebdavConnectOptions): Promise<BackupTarget | null> {\n\treturn (await load('WebDAV', () => import('./webdav'))).connect(opts);\n}\n\nexport async function webdavFromSession(opts: { kv: KV }): Promise<BackupTarget | null> {\n\treturn (await load('WebDAV', () => import('./webdav'))).fromSession(opts);\n}\n\nexport async function s3Connect(opts: S3ConnectOptions): Promise<BackupTarget | null> {\n\treturn (await load('S3', () => import('./s3'))).connect(opts);\n}\n\nexport async function s3FromSession(opts: { kv: KV }): Promise<BackupTarget | null> {\n\treturn (await load('S3', () => import('./s3'))).fromSession(opts);\n}\n"]}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { s3Connect, webdavConnect } from './chunk-
|
|
1
|
+
import { s3Connect, webdavConnect } from './chunk-TNYL6FNW.js';
|
|
2
2
|
import { isSupported, openExisting, connect as connect$1 } from './chunk-HVHQLPP6.js';
|
|
3
3
|
import { makeMachine, withDeadline, toStoreError } from './chunk-2BNTCXQN.js';
|
|
4
|
-
import { connect } from './chunk-
|
|
5
|
-
import { restore } from './chunk-
|
|
4
|
+
import { connect } from './chunk-IUVOVVEA.js';
|
|
5
|
+
import { restore } from './chunk-E4S77JSN.js';
|
|
6
6
|
import { SelfstoreError, isSelfstoreError } from './chunk-CSYEIXNY.js';
|
|
7
7
|
|
|
8
8
|
// src/flows/connect.ts
|
|
@@ -493,5 +493,5 @@ function joinFlow(link, engine, options = {}) {
|
|
|
493
493
|
}
|
|
494
494
|
|
|
495
495
|
export { connectFlow, joinFlow, shareFlow };
|
|
496
|
-
//# sourceMappingURL=chunk-
|
|
497
|
-
//# sourceMappingURL=chunk-
|
|
496
|
+
//# sourceMappingURL=chunk-VRWAV3GI.js.map
|
|
497
|
+
//# sourceMappingURL=chunk-VRWAV3GI.js.map
|