selfstore 1.8.23 → 1.8.25
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +66 -0
- package/dist/advanced.d.ts +7 -7
- package/dist/advanced.js +15 -14
- package/dist/advanced.js.map +1 -1
- package/dist/backups.d.ts +4 -4
- package/dist/backups.js +9 -8
- package/dist/{cache-lock-5RNG6CIK.js → cache-lock-VVAB3CDB.js} +5 -4
- package/dist/cache-lock-VVAB3CDB.js.map +1 -0
- package/dist/{chunk-JTNFZV2B.js → chunk-25U6AKIX.js} +4 -4
- package/dist/{chunk-JTNFZV2B.js.map → chunk-25U6AKIX.js.map} +1 -1
- package/dist/{chunk-3KAAPYPO.js → chunk-2BNTCXQN.js} +3 -3
- package/dist/{chunk-3KAAPYPO.js.map → chunk-2BNTCXQN.js.map} +1 -1
- package/dist/{chunk-LYO36VYB.js → chunk-2PIYIIHY.js} +8 -8
- package/dist/{chunk-LYO36VYB.js.map → chunk-2PIYIIHY.js.map} +1 -1
- package/dist/{chunk-GW5CYP4C.js → chunk-2TIRUEXR.js} +3 -3
- package/dist/{chunk-GW5CYP4C.js.map → chunk-2TIRUEXR.js.map} +1 -1
- package/dist/{chunk-EBCQ7ZG7.js → chunk-5DAPYC2K.js} +4 -4
- package/dist/{chunk-EBCQ7ZG7.js.map → chunk-5DAPYC2K.js.map} +1 -1
- package/dist/{chunk-RREJ2MFN.js → chunk-5UYPKCDB.js} +5 -17
- package/dist/chunk-5UYPKCDB.js.map +1 -0
- package/dist/{chunk-DJZ5S3FI.js → chunk-AZWS7YC3.js} +21 -13
- package/dist/chunk-AZWS7YC3.js.map +1 -0
- package/dist/{chunk-XMC67RNM.js → chunk-CSYEIXNY.js} +2 -2
- package/dist/{chunk-XMC67RNM.js.map → chunk-CSYEIXNY.js.map} +1 -1
- package/dist/{chunk-OY2A5DCB.js → chunk-F4ZZEJ5H.js} +4 -4
- package/dist/{chunk-OY2A5DCB.js.map → chunk-F4ZZEJ5H.js.map} +1 -1
- package/dist/{chunk-3BWBBGFW.js → chunk-FQ5O4PHK.js} +7 -7
- package/dist/{chunk-3BWBBGFW.js.map → chunk-FQ5O4PHK.js.map} +1 -1
- package/dist/{chunk-JZHWAX34.js → chunk-GHSHIDF3.js} +4 -4
- package/dist/{chunk-JZHWAX34.js.map → chunk-GHSHIDF3.js.map} +1 -1
- package/dist/{chunk-23UAUYSW.js → chunk-GSZSZGSD.js} +3 -3
- package/dist/{chunk-23UAUYSW.js.map → chunk-GSZSZGSD.js.map} +1 -1
- package/dist/{chunk-LUNYYH6D.js → chunk-IDW3WQKF.js} +6 -6
- package/dist/{chunk-LUNYYH6D.js.map → chunk-IDW3WQKF.js.map} +1 -1
- package/dist/{chunk-757ZOSUI.js → chunk-LF3CC2P3.js} +3 -3
- package/dist/{chunk-757ZOSUI.js.map → chunk-LF3CC2P3.js.map} +1 -1
- package/dist/{chunk-AUH35AAQ.js → chunk-LQS7AMOD.js} +3 -3
- package/dist/{chunk-AUH35AAQ.js.map → chunk-LQS7AMOD.js.map} +1 -1
- package/dist/{chunk-HHPJPEEG.js → chunk-RVOBONZF.js} +7 -6
- package/dist/chunk-RVOBONZF.js.map +1 -0
- package/dist/chunk-SJ3PKQPR.js +10 -0
- package/dist/chunk-SJ3PKQPR.js.map +1 -0
- package/dist/chunk-WXI2NAPA.js +16 -0
- package/dist/chunk-WXI2NAPA.js.map +1 -0
- package/dist/{connect-Bob2mofK.d.ts → connect-DoB_DN__.d.ts} +3 -3
- package/dist/{drive-86CHuTij.d.ts → drive--mndByvF.d.ts} +1 -1
- package/dist/flows.d.ts +5 -5
- package/dist/flows.js +12 -11
- package/dist/flows.js.map +1 -1
- package/dist/groups.js +5 -4
- package/dist/groups.js.map +1 -1
- package/dist/households.d.ts +1 -1
- package/dist/index.d.ts +11 -266
- package/dist/index.js +21 -24
- package/dist/index.js.map +1 -1
- package/dist/{local-8H61XxbL.d.ts → local-ClXq1N2M.d.ts} +14 -3
- package/dist/local-GGAMRY23.js +8 -0
- package/dist/{local-XET4G32B.js.map → local-GGAMRY23.js.map} +1 -1
- package/dist/manager-drive-2IUE4WQ2.js +11 -0
- package/dist/{manager-drive-YLSLQ63O.js.map → manager-drive-2IUE4WQ2.js.map} +1 -1
- package/dist/{manager-Di6lNE4E.d.ts → manager-yCM0M0FL.d.ts} +1 -1
- package/dist/proof.d.ts +164 -0
- package/dist/proof.js +360 -0
- package/dist/proof.js.map +1 -0
- package/dist/s3-ABKUTTXE.js +12 -0
- package/dist/s3-ABKUTTXE.js.map +1 -0
- package/dist/{s3-BsbVn3fw.d.ts → s3-dG1SCnV1.d.ts} +1 -1
- package/dist/simple-GdtB5IrG.d.ts +267 -0
- package/dist/{store-CIbiNoiE.d.ts → store-CKGrIFQU.d.ts} +2 -2
- package/dist/sync.js +2 -2
- package/dist/webdav-5SHNR7CL.js +10 -0
- package/dist/{webdav-EEQKX5TZ.js.map → webdav-5SHNR7CL.js.map} +1 -1
- package/dist/widgets.d.ts +5 -5
- package/dist/widgets.js +12 -11
- package/dist/widgets.js.map +1 -1
- package/llms.txt +19 -2
- package/package.json +5 -1
- package/dist/cache-lock-5RNG6CIK.js.map +0 -1
- package/dist/chunk-DJZ5S3FI.js.map +0 -1
- package/dist/chunk-HHPJPEEG.js.map +0 -1
- package/dist/chunk-RREJ2MFN.js.map +0 -1
- package/dist/local-XET4G32B.js +0 -7
- package/dist/manager-drive-YLSLQ63O.js +0 -10
- package/dist/s3-43RWNCV3.js +0 -11
- package/dist/s3-43RWNCV3.js.map +0 -1
- package/dist/webdav-EEQKX5TZ.js +0 -9
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/persistence/targets/sigv4.ts","../src/persistence/targets/s3.ts"],"names":[],"mappings":"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","file":"chunk-LUNYYH6D.js","sourcesContent":["// AWS Signature Version 4 for S3 REST requests, in the browser with WebCrypto.\n// Enough of the spec to sign one object's GET/PUT/HEAD - no streaming, no\n// chunked upload, no presigned URLs. Works against AWS S3 and any S3-compatible\n// endpoint (Cloudflare R2, Backblaze B2, MinIO) since they all verify SigV4.\n//\n// Signed headers are host, x-amz-content-sha256 and x-amz-date, plus whatever\n// the caller adds. The browser sets Host itself from the URL and forbids\n// overriding it, so we sign the host derived from that same URL - the value the\n// browser will send - and never try to set the header.\n\nimport { hex, sha256Hex } from '../../selfstore/digest';\nimport { byCodePoint } from '../../ordering';\n\nconst encoder = new TextEncoder();\n\n/** SHA-256 of the empty body: the payload hash for every GET/HEAD. */\nexport const EMPTY_PAYLOAD_SHA256 =\n\t'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855';\n\nasync function hmac(key: Uint8Array, data: Uint8Array): Promise<Uint8Array> {\n\tconst k = await crypto.subtle.importKey(\n\t\t'raw',\n\t\tkey as BufferSource,\n\t\t{ name: 'HMAC', hash: 'SHA-256' },\n\t\tfalse,\n\t\t['sign']\n\t);\n\treturn new Uint8Array(await crypto.subtle.sign('HMAC', k, data as BufferSource));\n}\n\n// RFC 3986 encoding, applied per path segment (the '/' between segments stays a\n// separator). encodeURIComponent leaves !*'() alone; SigV4 requires them encoded.\nfunction encodeSegment(s: string): string {\n\treturn encodeURIComponent(s).replace(\n\t\t/[!*'()]/g,\n\t\t(c) => '%' + c.charCodeAt(0).toString(16).toUpperCase()\n\t);\n}\n\n/** Canonicalise an object path for both the request URL and the signature, so\n * the two can never disagree on encoding. Pass the RAW key path (unencoded). */\nexport function encodeS3Path(path: string): string {\n\treturn path\n\t\t.split('/')\n\t\t.map((seg) => encodeSegment(seg))\n\t\t.join('/');\n}\n\nexport interface SignS3Input {\n\tmethod: string;\n\t/** Scheme + host(+port), e.g. https://s3.eu-west-3.amazonaws.com */\n\torigin: string;\n\t/** Raw (unencoded) object path, e.g. /my-bucket/backups/app.selfstore */\n\tpath: string;\n\tregion: string;\n\taccessKeyId: string;\n\tsecretAccessKey: string;\n\t/** Hex SHA-256 of the request body (EMPTY_PAYLOAD_SHA256 for GET/HEAD). */\n\tpayloadHashHex: string;\n\t/** Extra headers to sign, e.g. { range: 'bytes=0-0' }. Names are lowercased. */\n\textraHeaders?: Record<string, string>;\n\t/** Basic timestamp (YYYYMMDDTHHMMSSZ). Injected in tests; defaults to now. */\n\tamzDate?: string;\n\tservice?: string;\n}\n\nexport interface SignedS3Request {\n\t/** The fully-encoded URL to fetch. */\n\turl: string;\n\t/** Headers to send: Authorization, x-amz-date, x-amz-content-sha256, extras. */\n\theaders: Record<string, string>;\n}\n\n/** ISO basic UTC now, YYYYMMDDTHHMMSSZ. */\nfunction basicAmzDate(): string {\n\treturn new Date().toISOString().replace(/[:-]|\\.\\d{3}/g, '');\n}\n\n/** Sign one S3 request and return the URL plus the headers to send. */\nexport async function signS3(input: SignS3Input): Promise<SignedS3Request> {\n\tconst service = input.service ?? 's3';\n\tconst amzDate = input.amzDate ?? basicAmzDate();\n\tconst dateStamp = amzDate.slice(0, 8);\n\tconst { host } = new URL(input.origin);\n\tconst canonicalUri = encodeS3Path(input.path);\n\n\tconst headers: Record<string, string> = {\n\t\thost,\n\t\t'x-amz-content-sha256': input.payloadHashHex,\n\t\t'x-amz-date': amzDate\n\t};\n\tfor (const [name, value] of Object.entries(input.extraHeaders ?? {})) {\n\t\theaders[name.toLowerCase()] = value;\n\t}\n\t// BY CODE POINT: that is what the canonical request is defined over, and a\n\t// locale-aware order produces a signature the service rejects. See ordering.\n\tconst signedNames = Object.keys(headers).sort(byCodePoint);\n\tconst canonicalHeaders = signedNames.map((n) => `${n}:${headers[n].trim()}\\n`).join('');\n\tconst signedHeaders = signedNames.join(';');\n\n\tconst canonicalRequest = [\n\t\tinput.method,\n\t\tcanonicalUri,\n\t\t'', // no query string on any of our single-object requests\n\t\tcanonicalHeaders,\n\t\tsignedHeaders,\n\t\tinput.payloadHashHex\n\t].join('\\n');\n\n\tconst scope = `${dateStamp}/${input.region}/${service}/aws4_request`;\n\tconst stringToSign = [\n\t\t'AWS4-HMAC-SHA256',\n\t\tamzDate,\n\t\tscope,\n\t\tawait sha256Hex(encoder.encode(canonicalRequest))\n\t].join('\\n');\n\n\tconst kDate = await hmac(\n\t\tencoder.encode('AWS4' + input.secretAccessKey),\n\t\tencoder.encode(dateStamp)\n\t);\n\tconst kRegion = await hmac(kDate, encoder.encode(input.region));\n\tconst kService = await hmac(kRegion, encoder.encode(service));\n\tconst kSigning = await hmac(kService, encoder.encode('aws4_request'));\n\tconst signature = hex(await hmac(kSigning, encoder.encode(stringToSign)));\n\n\tconst authorization =\n\t\t`AWS4-HMAC-SHA256 Credential=${input.accessKeyId}/${scope}, ` +\n\t\t`SignedHeaders=${signedHeaders}, Signature=${signature}`;\n\n\t// Send every signed header except host (the browser sets Host itself and\n\t// forbids overriding it - we signed the value it will send).\n\tconst sendHeaders: Record<string, string> = { authorization };\n\tfor (const n of signedNames) if (n !== 'host') sendHeaders[n] = headers[n];\n\n\treturn { url: input.origin.replace(/\\/$/, '') + canonicalUri, headers: sendHeaders };\n}\n","/**\n * S3 backup target: the store re-writes one object on an S3-compatible bucket\n * the USER controls - Amazon S3, Cloudflare R2, Backblaze B2, MinIO, any server\n * that speaks the S3 REST API and SigV4. No vendor broker: the browser signs\n * each request itself and talks straight to the endpoint. The bytes are the\n * same end-to-end-encrypted .selfstore, so the bucket only ever holds\n * ciphertext.\n *\n * Two things the environment must allow, both documented for the user:\n * - the bucket must send CORS for the app origin: allowed methods GET/PUT/HEAD,\n * allowed headers authorization/x-amz-*, and ExposeHeaders ETag (so the\n * version marker is readable), or browsers block the cross-origin request;\n * - the app's CSP connect-src must permit the endpoint host.\n *\n * Credentials (the secret access key) are stored ENCRYPTED at rest under a\n * per-device non-extractable key, exactly like the WebDAV target, so a\n * set-and-forget connection never leaves a cleartext secret in IndexedDB.\n *\n * Path style is the default: the object lives at endpoint/bucket/key, which\n * every S3-compatible server accepts and needs no per-bucket DNS. Set\n * forcePathStyle:false for AWS virtual-hosted style (bucket as a host label).\n */\n\nimport type { BackupTarget } from '../target';\nimport type { KV } from '../cache';\nimport { seal, unseal, isEnvelope, newDeviceKey } from '../cache-crypto';\nimport { boundedSignal, lifeLine } from './abort';\nimport { AuthExpiredError, SelfstoreError } from '../../selfstore';\nimport { signS3, EMPTY_PAYLOAD_SHA256 } from './sigv4';\nimport { sha256Hex } from '../../selfstore/digest';\n\nconst CONFIG_KEY = 's3Config';\nconst KEY_KEY = 's3Key';\n\nconst DATA_DEADLINE_MS = 30_000;\nconst META_DEADLINE_MS = 15_000;\n\nexport interface S3Config {\n\t/** Endpoint origin, e.g. https://s3.eu-west-3.amazonaws.com,\n\t * https://<account>.r2.cloudflarestorage.com, https://minio.example.com. */\n\tendpoint: string;\n\t/** Signing region (\"eu-west-3\"; R2 uses \"auto\"; MinIO any configured value). */\n\tregion: string;\n\tbucket: string;\n\t/** Object key (the backup file), e.g. backups/app.selfstore. */\n\tkey: string;\n\taccessKeyId: string;\n\tsecretAccessKey: string;\n\t/** endpoint/bucket/key (default) vs bucket.endpoint/key. Default true. */\n\tforcePathStyle?: boolean;\n}\n\nexport interface S3ConnectOptions {\n\t/** Where the (encrypted) config is persisted across sessions. */\n\tkv: KV;\n\tconfig: S3Config;\n}\n\nasync function configKey(kv: KV): Promise<CryptoKey> {\n\tconst existing = await kv.get<CryptoKey>(KEY_KEY);\n\tif (existing) return existing;\n\tconst fresh = await newDeviceKey();\n\tawait kv.set(KEY_KEY, fresh);\n\treturn fresh;\n}\n\nasync function saveConfig(kv: KV, c: S3Config): Promise<void> {\n\tconst bytes = new TextEncoder().encode(JSON.stringify(c));\n\tawait kv.set(CONFIG_KEY, await seal(await configKey(kv), bytes));\n}\n\nasync function loadConfig(kv: KV): Promise<S3Config | null> {\n\tconst stored = await kv.get(CONFIG_KEY);\n\tif (!isEnvelope(stored)) return null;\n\tconst bytes = await unseal(await configKey(kv), stored);\n\treturn JSON.parse(new TextDecoder().decode(bytes)) as S3Config;\n}\n\n/** SigV4 puts the secret on the wire only as a derived signature, but the\n * request still needs https: an http endpoint would expose the object bytes\n * and headers. Allow http for loopback (a local MinIO in dev) only. */\nfunction assertSecureEndpoint(endpoint: string): URL {\n\tlet u: URL;\n\ttry {\n\t\tu = new URL(endpoint);\n\t} catch {\n\t\tthrow new TypeError(`s3Target: invalid endpoint \"${endpoint}\".`);\n\t}\n\tconst loopback =\n\t\tu.hostname === 'localhost' || u.hostname === '127.0.0.1' || u.hostname === '[::1]';\n\tif (u.protocol !== 'https:' && !(u.protocol === 'http:' && loopback)) {\n\t\tthrow new TypeError(\n\t\t\t`s3Target: refusing \"${u.protocol}\" - S3 requests need https (http is allowed only for localhost).`\n\t\t);\n\t}\n\treturn u;\n}\n\nfunction labelOf(c: S3Config): string {\n\ttry {\n\t\treturn `${c.bucket} (${new URL(c.endpoint).host})`;\n\t} catch {\n\t\treturn c.bucket || 'S3';\n\t}\n}\n\nfunction versionOf(res: Response): string | null {\n\treturn res.headers.get('ETag') ?? res.headers.get('Last-Modified') ?? null;\n}\n\nfunction fromConfig(c: S3Config, kv: KV): BackupTarget {\n\tconst endpointUrl = assertSecureEndpoint(c.endpoint);\n\tconst pathStyle = c.forcePathStyle ?? true;\n\t// Path style keeps one host and no per-bucket DNS; virtual-hosted folds the\n\t// bucket into the host label (AWS's preferred form).\n\tconst origin = pathStyle\n\t\t? `${endpointUrl.protocol}//${endpointUrl.host}`\n\t\t: `${endpointUrl.protocol}//${c.bucket}.${endpointUrl.host}`;\n\tconst objectPath = pathStyle ? `/${c.bucket}/${c.key}` : `/${c.key}`;\n\t// The instance's LIFE line: abortInFlight() (a user detach) cuts every\n\t// suspended request now instead of at its deadline (see the Drive target).\n\tconst life = lifeLine();\n\n\tasync function request(\n\t\tmethod: string,\n\t\tpayloadHashHex: string,\n\t\tbody?: BodyInit,\n\t\textraHeaders?: Record<string, string>,\n\t\tdeadlineMs = DATA_DEADLINE_MS\n\t): Promise<Response> {\n\t\tconst signed = await signS3({\n\t\t\tmethod,\n\t\t\torigin,\n\t\t\tpath: objectPath,\n\t\t\tregion: c.region,\n\t\t\taccessKeyId: c.accessKeyId,\n\t\t\tsecretAccessKey: c.secretAccessKey,\n\t\t\tpayloadHashHex,\n\t\t\textraHeaders\n\t\t});\n\t\treturn fetch(signed.url, {\n\t\t\tmethod,\n\t\t\theaders: signed.headers,\n\t\t\tbody,\n\t\t\tsignal: boundedSignal(deadlineMs, life.current())\n\t\t});\n\t}\n\n\t/** One HEAD, or null on a network/CORS failure (transient). */\n\tasync function probe(): Promise<Response | null> {\n\t\ttry {\n\t\t\treturn await request('HEAD', EMPTY_PAYLOAD_SHA256, undefined, undefined, META_DEADLINE_MS);\n\t\t} catch {\n\t\t\treturn null;\n\t\t}\n\t}\n\tasync function stat(): Promise<string | null> {\n\t\tconst res = await probe();\n\t\treturn res && res.ok ? versionOf(res) : null; // 404 / error / offline: cannot tell\n\t}\n\tasync function ready(): Promise<boolean> {\n\t\tconst res = await probe();\n\t\tif (!res) return false; // offline / CORS blocked: transient, retried later\n\t\t// 403 = the credentials are rejected (a HEAD with GetObject permission\n\t\t// returns 404, not 403, for a missing object - so 403 is a real auth loss,\n\t\t// not \"no backup yet\"). 404 = object not created yet, still writable.\n\t\tif (res.status === 403) {\n\t\t\tthrow new AuthExpiredError('S3 rejected the credentials (403).');\n\t\t}\n\t\treturn res.ok || res.status === 404;\n\t}\n\t/** Stored credentials: a reconnect just re-checks reachability, never mints new\n\t * keys, so a rejection is reported as false rather than thrown. */\n\tasync function reconnectProbe(): Promise<boolean> {\n\t\tconst res = await probe();\n\t\treturn !!res && (res.ok || res.status === 404);\n\t}\n\n\treturn {\n\t\tkind: 's3',\n\t\tlabel: labelOf(c),\n\t\tabortInFlight: life.cut,\n\t\tasync save(blob: Blob): Promise<string | null> {\n\t\t\tconst bytes = new Uint8Array(await blob.arrayBuffer());\n\t\t\tconst res = await request('PUT', await sha256Hex(bytes), bytes);\n\t\t\tif (res.status === 403) {\n\t\t\t\tthrow new AuthExpiredError('S3 rejected the credentials (403).');\n\t\t\t}\n\t\t\tif (!res.ok) throw new SelfstoreError('TARGET_WRITE_FAILED', `S3 PUT failed: ${res.status}`);\n\t\t\treturn versionOf(res) ?? (await stat());\n\t\t},\n\t\tasync load(): Promise<Blob | null> {\n\t\t\tconst res = await request('GET', EMPTY_PAYLOAD_SHA256);\n\t\t\tif (res.status === 404) return null;\n\t\t\tif (res.status === 403) {\n\t\t\t\tthrow new AuthExpiredError('S3 rejected the credentials (403).');\n\t\t\t}\n\t\t\tif (!res.ok) throw new SelfstoreError('TARGET_UNAVAILABLE', `S3 GET failed: ${res.status}`);\n\t\t\treturn res.blob();\n\t\t},\n\t\tstat,\n\t\tisReady: ready,\n\t\treconnect: reconnectProbe,\n\t\tasync disconnect(): Promise<void> {\n\t\t\tawait kv.del(CONFIG_KEY);\n\t\t\tawait kv.del(KEY_KEY);\n\t\t}\n\t};\n}\n\n/** Validate + persist the config, returning the target, or null when the bucket\n * is unreachable, rejects the credentials, or blocks the cross-origin request. */\nexport async function connect(opts: S3ConnectOptions): Promise<BackupTarget | null> {\n\tassertSecureEndpoint(opts.config.endpoint);\n\tconst target = fromConfig(opts.config, opts.kv);\n\t// isReady() throws on a 403 (a later rejection gates the store); at first\n\t// connect a rejection just means \"wrong credentials, try again\", so a throw\n\t// here reads the same as false - report null, persist nothing.\n\tlet ok: boolean;\n\ttry {\n\t\tok = await target.isReady();\n\t} catch {\n\t\tok = false;\n\t}\n\tif (!ok) return null;\n\tawait saveConfig(opts.kv, opts.config);\n\treturn target;\n}\n\n/** Rebuild the target from the encrypted config persisted last session, or null. */\nexport async function fromSession(opts: { kv: KV }): Promise<BackupTarget | null> {\n\tconst config = await loadConfig(opts.kv);\n\treturn config ? fromConfig(config, opts.kv) : null;\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/persistence/targets/sigv4.ts","../src/persistence/targets/s3.ts"],"names":[],"mappings":"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","file":"chunk-IDW3WQKF.js","sourcesContent":["// AWS Signature Version 4 for S3 REST requests, in the browser with WebCrypto.\n// Enough of the spec to sign one object's GET/PUT/HEAD - no streaming, no\n// chunked upload, no presigned URLs. Works against AWS S3 and any S3-compatible\n// endpoint (Cloudflare R2, Backblaze B2, MinIO) since they all verify SigV4.\n//\n// Signed headers are host, x-amz-content-sha256 and x-amz-date, plus whatever\n// the caller adds. The browser sets Host itself from the URL and forbids\n// overriding it, so we sign the host derived from that same URL - the value the\n// browser will send - and never try to set the header.\n\nimport { hex, sha256Hex } from '../../selfstore/digest';\nimport { byCodePoint } from '../../ordering';\n\nconst encoder = new TextEncoder();\n\n/** SHA-256 of the empty body: the payload hash for every GET/HEAD. */\nexport const EMPTY_PAYLOAD_SHA256 =\n\t'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855';\n\nasync function hmac(key: Uint8Array, data: Uint8Array): Promise<Uint8Array> {\n\tconst k = await crypto.subtle.importKey(\n\t\t'raw',\n\t\tkey as BufferSource,\n\t\t{ name: 'HMAC', hash: 'SHA-256' },\n\t\tfalse,\n\t\t['sign']\n\t);\n\treturn new Uint8Array(await crypto.subtle.sign('HMAC', k, data as BufferSource));\n}\n\n// RFC 3986 encoding, applied per path segment (the '/' between segments stays a\n// separator). encodeURIComponent leaves !*'() alone; SigV4 requires them encoded.\nfunction encodeSegment(s: string): string {\n\treturn encodeURIComponent(s).replace(\n\t\t/[!*'()]/g,\n\t\t(c) => '%' + c.charCodeAt(0).toString(16).toUpperCase()\n\t);\n}\n\n/** Canonicalise an object path for both the request URL and the signature, so\n * the two can never disagree on encoding. Pass the RAW key path (unencoded). */\nexport function encodeS3Path(path: string): string {\n\treturn path\n\t\t.split('/')\n\t\t.map((seg) => encodeSegment(seg))\n\t\t.join('/');\n}\n\nexport interface SignS3Input {\n\tmethod: string;\n\t/** Scheme + host(+port), e.g. https://s3.eu-west-3.amazonaws.com */\n\torigin: string;\n\t/** Raw (unencoded) object path, e.g. /my-bucket/backups/app.selfstore */\n\tpath: string;\n\tregion: string;\n\taccessKeyId: string;\n\tsecretAccessKey: string;\n\t/** Hex SHA-256 of the request body (EMPTY_PAYLOAD_SHA256 for GET/HEAD). */\n\tpayloadHashHex: string;\n\t/** Extra headers to sign, e.g. { range: 'bytes=0-0' }. Names are lowercased. */\n\textraHeaders?: Record<string, string>;\n\t/** Basic timestamp (YYYYMMDDTHHMMSSZ). Injected in tests; defaults to now. */\n\tamzDate?: string;\n\tservice?: string;\n}\n\nexport interface SignedS3Request {\n\t/** The fully-encoded URL to fetch. */\n\turl: string;\n\t/** Headers to send: Authorization, x-amz-date, x-amz-content-sha256, extras. */\n\theaders: Record<string, string>;\n}\n\n/** ISO basic UTC now, YYYYMMDDTHHMMSSZ. */\nfunction basicAmzDate(): string {\n\treturn new Date().toISOString().replace(/[:-]|\\.\\d{3}/g, '');\n}\n\n/** Sign one S3 request and return the URL plus the headers to send. */\nexport async function signS3(input: SignS3Input): Promise<SignedS3Request> {\n\tconst service = input.service ?? 's3';\n\tconst amzDate = input.amzDate ?? basicAmzDate();\n\tconst dateStamp = amzDate.slice(0, 8);\n\tconst { host } = new URL(input.origin);\n\tconst canonicalUri = encodeS3Path(input.path);\n\n\tconst headers: Record<string, string> = {\n\t\thost,\n\t\t'x-amz-content-sha256': input.payloadHashHex,\n\t\t'x-amz-date': amzDate\n\t};\n\tfor (const [name, value] of Object.entries(input.extraHeaders ?? {})) {\n\t\theaders[name.toLowerCase()] = value;\n\t}\n\t// BY CODE POINT: that is what the canonical request is defined over, and a\n\t// locale-aware order produces a signature the service rejects. See ordering.\n\tconst signedNames = Object.keys(headers).sort(byCodePoint);\n\tconst canonicalHeaders = signedNames.map((n) => `${n}:${headers[n].trim()}\\n`).join('');\n\tconst signedHeaders = signedNames.join(';');\n\n\tconst canonicalRequest = [\n\t\tinput.method,\n\t\tcanonicalUri,\n\t\t'', // no query string on any of our single-object requests\n\t\tcanonicalHeaders,\n\t\tsignedHeaders,\n\t\tinput.payloadHashHex\n\t].join('\\n');\n\n\tconst scope = `${dateStamp}/${input.region}/${service}/aws4_request`;\n\tconst stringToSign = [\n\t\t'AWS4-HMAC-SHA256',\n\t\tamzDate,\n\t\tscope,\n\t\tawait sha256Hex(encoder.encode(canonicalRequest))\n\t].join('\\n');\n\n\tconst kDate = await hmac(\n\t\tencoder.encode('AWS4' + input.secretAccessKey),\n\t\tencoder.encode(dateStamp)\n\t);\n\tconst kRegion = await hmac(kDate, encoder.encode(input.region));\n\tconst kService = await hmac(kRegion, encoder.encode(service));\n\tconst kSigning = await hmac(kService, encoder.encode('aws4_request'));\n\tconst signature = hex(await hmac(kSigning, encoder.encode(stringToSign)));\n\n\tconst authorization =\n\t\t`AWS4-HMAC-SHA256 Credential=${input.accessKeyId}/${scope}, ` +\n\t\t`SignedHeaders=${signedHeaders}, Signature=${signature}`;\n\n\t// Send every signed header except host (the browser sets Host itself and\n\t// forbids overriding it - we signed the value it will send).\n\tconst sendHeaders: Record<string, string> = { authorization };\n\tfor (const n of signedNames) if (n !== 'host') sendHeaders[n] = headers[n];\n\n\treturn { url: input.origin.replace(/\\/$/, '') + canonicalUri, headers: sendHeaders };\n}\n","/**\n * S3 backup target: the store re-writes one object on an S3-compatible bucket\n * the USER controls - Amazon S3, Cloudflare R2, Backblaze B2, MinIO, any server\n * that speaks the S3 REST API and SigV4. No vendor broker: the browser signs\n * each request itself and talks straight to the endpoint. The bytes are the\n * same end-to-end-encrypted .selfstore, so the bucket only ever holds\n * ciphertext.\n *\n * Two things the environment must allow, both documented for the user:\n * - the bucket must send CORS for the app origin: allowed methods GET/PUT/HEAD,\n * allowed headers authorization/x-amz-*, and ExposeHeaders ETag (so the\n * version marker is readable), or browsers block the cross-origin request;\n * - the app's CSP connect-src must permit the endpoint host.\n *\n * Credentials (the secret access key) are stored ENCRYPTED at rest under a\n * per-device non-extractable key, exactly like the WebDAV target, so a\n * set-and-forget connection never leaves a cleartext secret in IndexedDB.\n *\n * Path style is the default: the object lives at endpoint/bucket/key, which\n * every S3-compatible server accepts and needs no per-bucket DNS. Set\n * forcePathStyle:false for AWS virtual-hosted style (bucket as a host label).\n */\n\nimport type { BackupTarget } from '../target';\nimport type { KV } from '../cache';\nimport { seal, unseal, isEnvelope, newDeviceKey } from '../cache-crypto';\nimport { boundedSignal, lifeLine } from './abort';\nimport { AuthExpiredError, SelfstoreError } from '../../selfstore';\nimport { signS3, EMPTY_PAYLOAD_SHA256 } from './sigv4';\nimport { sha256Hex } from '../../selfstore/digest';\n\nconst CONFIG_KEY = 's3Config';\nconst KEY_KEY = 's3Key';\n\nconst DATA_DEADLINE_MS = 30_000;\nconst META_DEADLINE_MS = 15_000;\n\nexport interface S3Config {\n\t/** Endpoint origin, e.g. https://s3.eu-west-3.amazonaws.com,\n\t * https://<account>.r2.cloudflarestorage.com, https://minio.example.com. */\n\tendpoint: string;\n\t/** Signing region (\"eu-west-3\"; R2 uses \"auto\"; MinIO any configured value). */\n\tregion: string;\n\tbucket: string;\n\t/** Object key (the backup file), e.g. backups/app.selfstore. */\n\tkey: string;\n\taccessKeyId: string;\n\tsecretAccessKey: string;\n\t/** endpoint/bucket/key (default) vs bucket.endpoint/key. Default true. */\n\tforcePathStyle?: boolean;\n}\n\nexport interface S3ConnectOptions {\n\t/** Where the (encrypted) config is persisted across sessions. */\n\tkv: KV;\n\tconfig: S3Config;\n}\n\nasync function configKey(kv: KV): Promise<CryptoKey> {\n\tconst existing = await kv.get<CryptoKey>(KEY_KEY);\n\tif (existing) return existing;\n\tconst fresh = await newDeviceKey();\n\tawait kv.set(KEY_KEY, fresh);\n\treturn fresh;\n}\n\nasync function saveConfig(kv: KV, c: S3Config): Promise<void> {\n\tconst bytes = new TextEncoder().encode(JSON.stringify(c));\n\tawait kv.set(CONFIG_KEY, await seal(await configKey(kv), bytes));\n}\n\nasync function loadConfig(kv: KV): Promise<S3Config | null> {\n\tconst stored = await kv.get(CONFIG_KEY);\n\tif (!isEnvelope(stored)) return null;\n\tconst bytes = await unseal(await configKey(kv), stored);\n\treturn JSON.parse(new TextDecoder().decode(bytes)) as S3Config;\n}\n\n/** SigV4 puts the secret on the wire only as a derived signature, but the\n * request still needs https: an http endpoint would expose the object bytes\n * and headers. Allow http for loopback (a local MinIO in dev) only. */\nfunction assertSecureEndpoint(endpoint: string): URL {\n\tlet u: URL;\n\ttry {\n\t\tu = new URL(endpoint);\n\t} catch {\n\t\tthrow new TypeError(`s3Target: invalid endpoint \"${endpoint}\".`);\n\t}\n\tconst loopback =\n\t\tu.hostname === 'localhost' || u.hostname === '127.0.0.1' || u.hostname === '[::1]';\n\tif (u.protocol !== 'https:' && !(u.protocol === 'http:' && loopback)) {\n\t\tthrow new TypeError(\n\t\t\t`s3Target: refusing \"${u.protocol}\" - S3 requests need https (http is allowed only for localhost).`\n\t\t);\n\t}\n\treturn u;\n}\n\nfunction labelOf(c: S3Config): string {\n\ttry {\n\t\treturn `${c.bucket} (${new URL(c.endpoint).host})`;\n\t} catch {\n\t\treturn c.bucket || 'S3';\n\t}\n}\n\nfunction versionOf(res: Response): string | null {\n\treturn res.headers.get('ETag') ?? res.headers.get('Last-Modified') ?? null;\n}\n\nfunction fromConfig(c: S3Config, kv: KV): BackupTarget {\n\tconst endpointUrl = assertSecureEndpoint(c.endpoint);\n\tconst pathStyle = c.forcePathStyle ?? true;\n\t// Path style keeps one host and no per-bucket DNS; virtual-hosted folds the\n\t// bucket into the host label (AWS's preferred form).\n\tconst origin = pathStyle\n\t\t? `${endpointUrl.protocol}//${endpointUrl.host}`\n\t\t: `${endpointUrl.protocol}//${c.bucket}.${endpointUrl.host}`;\n\tconst objectPath = pathStyle ? `/${c.bucket}/${c.key}` : `/${c.key}`;\n\t// The instance's LIFE line: abortInFlight() (a user detach) cuts every\n\t// suspended request now instead of at its deadline (see the Drive target).\n\tconst life = lifeLine();\n\n\tasync function request(\n\t\tmethod: string,\n\t\tpayloadHashHex: string,\n\t\tbody?: BodyInit,\n\t\textraHeaders?: Record<string, string>,\n\t\tdeadlineMs = DATA_DEADLINE_MS\n\t): Promise<Response> {\n\t\tconst signed = await signS3({\n\t\t\tmethod,\n\t\t\torigin,\n\t\t\tpath: objectPath,\n\t\t\tregion: c.region,\n\t\t\taccessKeyId: c.accessKeyId,\n\t\t\tsecretAccessKey: c.secretAccessKey,\n\t\t\tpayloadHashHex,\n\t\t\textraHeaders\n\t\t});\n\t\treturn fetch(signed.url, {\n\t\t\tmethod,\n\t\t\theaders: signed.headers,\n\t\t\tbody,\n\t\t\tsignal: boundedSignal(deadlineMs, life.current())\n\t\t});\n\t}\n\n\t/** One HEAD, or null on a network/CORS failure (transient). */\n\tasync function probe(): Promise<Response | null> {\n\t\ttry {\n\t\t\treturn await request('HEAD', EMPTY_PAYLOAD_SHA256, undefined, undefined, META_DEADLINE_MS);\n\t\t} catch {\n\t\t\treturn null;\n\t\t}\n\t}\n\tasync function stat(): Promise<string | null> {\n\t\tconst res = await probe();\n\t\treturn res && res.ok ? versionOf(res) : null; // 404 / error / offline: cannot tell\n\t}\n\tasync function ready(): Promise<boolean> {\n\t\tconst res = await probe();\n\t\tif (!res) return false; // offline / CORS blocked: transient, retried later\n\t\t// 403 = the credentials are rejected (a HEAD with GetObject permission\n\t\t// returns 404, not 403, for a missing object - so 403 is a real auth loss,\n\t\t// not \"no backup yet\"). 404 = object not created yet, still writable.\n\t\tif (res.status === 403) {\n\t\t\tthrow new AuthExpiredError('S3 rejected the credentials (403).');\n\t\t}\n\t\treturn res.ok || res.status === 404;\n\t}\n\t/** Stored credentials: a reconnect just re-checks reachability, never mints new\n\t * keys, so a rejection is reported as false rather than thrown. */\n\tasync function reconnectProbe(): Promise<boolean> {\n\t\tconst res = await probe();\n\t\treturn !!res && (res.ok || res.status === 404);\n\t}\n\n\treturn {\n\t\tkind: 's3',\n\t\tlabel: labelOf(c),\n\t\tabortInFlight: life.cut,\n\t\tasync save(blob: Blob): Promise<string | null> {\n\t\t\tconst bytes = new Uint8Array(await blob.arrayBuffer());\n\t\t\tconst res = await request('PUT', await sha256Hex(bytes), bytes);\n\t\t\tif (res.status === 403) {\n\t\t\t\tthrow new AuthExpiredError('S3 rejected the credentials (403).');\n\t\t\t}\n\t\t\tif (!res.ok) throw new SelfstoreError('TARGET_WRITE_FAILED', `S3 PUT failed: ${res.status}`);\n\t\t\treturn versionOf(res) ?? (await stat());\n\t\t},\n\t\tasync load(): Promise<Blob | null> {\n\t\t\tconst res = await request('GET', EMPTY_PAYLOAD_SHA256);\n\t\t\tif (res.status === 404) return null;\n\t\t\tif (res.status === 403) {\n\t\t\t\tthrow new AuthExpiredError('S3 rejected the credentials (403).');\n\t\t\t}\n\t\t\tif (!res.ok) throw new SelfstoreError('TARGET_UNAVAILABLE', `S3 GET failed: ${res.status}`);\n\t\t\treturn res.blob();\n\t\t},\n\t\tstat,\n\t\tisReady: ready,\n\t\treconnect: reconnectProbe,\n\t\tasync disconnect(): Promise<void> {\n\t\t\tawait kv.del(CONFIG_KEY);\n\t\t\tawait kv.del(KEY_KEY);\n\t\t}\n\t};\n}\n\n/** Validate + persist the config, returning the target, or null when the bucket\n * is unreachable, rejects the credentials, or blocks the cross-origin request. */\nexport async function connect(opts: S3ConnectOptions): Promise<BackupTarget | null> {\n\tassertSecureEndpoint(opts.config.endpoint);\n\tconst target = fromConfig(opts.config, opts.kv);\n\t// isReady() throws on a 403 (a later rejection gates the store); at first\n\t// connect a rejection just means \"wrong credentials, try again\", so a throw\n\t// here reads the same as false - report null, persist nothing.\n\tlet ok: boolean;\n\ttry {\n\t\tok = await target.isReady();\n\t} catch {\n\t\tok = false;\n\t}\n\tif (!ok) return null;\n\tawait saveConfig(opts.kv, opts.config);\n\treturn target;\n}\n\n/** Rebuild the target from the encrypted config persisted last session, or null. */\nexport async function fromSession(opts: { kv: KV }): Promise<BackupTarget | null> {\n\tconst config = await loadConfig(opts.kv);\n\treturn config ? fromConfig(config, opts.kv) : null;\n}\n"]}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { SelfstoreError } from './chunk-
|
|
1
|
+
import { SelfstoreError } from './chunk-CSYEIXNY.js';
|
|
2
2
|
|
|
3
3
|
// src/persistence/targets/abort.ts
|
|
4
4
|
function boundedSignal(deadlineMs, life) {
|
|
@@ -23,5 +23,5 @@ function lifeLine() {
|
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
export { boundedSignal, lifeLine };
|
|
26
|
-
//# sourceMappingURL=chunk-
|
|
27
|
-
//# sourceMappingURL=chunk-
|
|
26
|
+
//# sourceMappingURL=chunk-LF3CC2P3.js.map
|
|
27
|
+
//# sourceMappingURL=chunk-LF3CC2P3.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/persistence/targets/abort.ts"],"names":[],"mappings":";;;AAWO,SAAS,aAAA,CAAc,YAAoB,IAAA,EAAiC;AAClF,EAAA,MAAM,QAAA,GAAW,WAAA,CAAY,OAAA,CAAQ,UAAU,CAAA;AAC/C,EAAA,IAAI,CAAC,MAAM,OAAO,QAAA;AAClB,EAAA,IAAI,IAAA,CAAK,SAAS,OAAO,IAAA;AACzB,EAAA,MAAM,GAAA,GAAM,IAAI,eAAA,EAAgB;AAChC,EAAA,QAAA,CAAS,gBAAA,CAAiB,OAAA,EAAS,MAAM,GAAA,CAAI,KAAA,CAAM,QAAA,CAAS,MAAM,CAAA,EAAG,EAAE,IAAA,EAAM,IAAA,EAAM,CAAA;AACnF,EAAA,IAAA,CAAK,gBAAA,CAAiB,OAAA,EAAS,MAAM,GAAA,CAAI,KAAA,CAAM,IAAA,CAAK,MAAM,CAAA,EAAG,EAAE,IAAA,EAAM,IAAA,EAAM,CAAA;AAC3E,EAAA,OAAO,GAAA,CAAI,MAAA;AACZ;AAMO,SAAS,QAAA,GAAoD;AACnE,EAAA,IAAI,GAAA,GAAM,IAAI,eAAA,EAAgB;AAC9B,EAAA,OAAO;AAAA,IACN,OAAA,EAAS,MAAM,GAAA,CAAI,MAAA;AAAA,IACnB,GAAA,GAAY;AACX,MAAA,MAAM,CAAA,GAAI,GAAA;AACV,MAAA,GAAA,GAAM,IAAI,eAAA,EAAgB;AAC1B,MAAA,CAAA,CAAE,KAAA,CAAM,IAAI,cAAA,CAAe,oBAAA,EAAsB,oCAAoC,CAAC,CAAA;AAAA,IACvF;AAAA,GACD;AACD","file":"chunk-
|
|
1
|
+
{"version":3,"sources":["../src/persistence/targets/abort.ts"],"names":[],"mappings":";;;AAWO,SAAS,aAAA,CAAc,YAAoB,IAAA,EAAiC;AAClF,EAAA,MAAM,QAAA,GAAW,WAAA,CAAY,OAAA,CAAQ,UAAU,CAAA;AAC/C,EAAA,IAAI,CAAC,MAAM,OAAO,QAAA;AAClB,EAAA,IAAI,IAAA,CAAK,SAAS,OAAO,IAAA;AACzB,EAAA,MAAM,GAAA,GAAM,IAAI,eAAA,EAAgB;AAChC,EAAA,QAAA,CAAS,gBAAA,CAAiB,OAAA,EAAS,MAAM,GAAA,CAAI,KAAA,CAAM,QAAA,CAAS,MAAM,CAAA,EAAG,EAAE,IAAA,EAAM,IAAA,EAAM,CAAA;AACnF,EAAA,IAAA,CAAK,gBAAA,CAAiB,OAAA,EAAS,MAAM,GAAA,CAAI,KAAA,CAAM,IAAA,CAAK,MAAM,CAAA,EAAG,EAAE,IAAA,EAAM,IAAA,EAAM,CAAA;AAC3E,EAAA,OAAO,GAAA,CAAI,MAAA;AACZ;AAMO,SAAS,QAAA,GAAoD;AACnE,EAAA,IAAI,GAAA,GAAM,IAAI,eAAA,EAAgB;AAC9B,EAAA,OAAO;AAAA,IACN,OAAA,EAAS,MAAM,GAAA,CAAI,MAAA;AAAA,IACnB,GAAA,GAAY;AACX,MAAA,MAAM,CAAA,GAAI,GAAA;AACV,MAAA,GAAA,GAAM,IAAI,eAAA,EAAgB;AAC1B,MAAA,CAAA,CAAE,KAAA,CAAM,IAAI,cAAA,CAAe,oBAAA,EAAsB,oCAAoC,CAAC,CAAA;AAAA,IACvF;AAAA,GACD;AACD","file":"chunk-LF3CC2P3.js","sourcesContent":["/**\n * Deadline + life-signal composition for target requests. Every target fetch\n * carries a per-request deadline; composing it with the target's LIFE signal\n * lets abortInFlight() (the user detaching the destination) cut a suspended\n * request NOW instead of letting the detach wait behind the deadline. The\n * composition is done by hand (not AbortSignal.any) so behaviour is identical\n * on every engine the library supports.\n */\n\nimport { SelfstoreError } from '../../selfstore';\n\nexport function boundedSignal(deadlineMs: number, life?: AbortSignal): AbortSignal {\n\tconst deadline = AbortSignal.timeout(deadlineMs);\n\tif (!life) return deadline;\n\tif (life.aborted) return life;\n\tconst ctl = new AbortController();\n\tdeadline.addEventListener('abort', () => ctl.abort(deadline.reason), { once: true });\n\tlife.addEventListener('abort', () => ctl.abort(life.reason), { once: true });\n\treturn ctl.signal;\n}\n\n/** A target's resettable LIFE line. cut() aborts everything bound to the\n * CURRENT signal (with the standard transient detach reason) and re-arms,\n * so work started after the cut lives normally. cut is `this`-free: targets\n * expose it directly as their abortInFlight. */\nexport function lifeLine(): { current(): AbortSignal; cut(): void } {\n\tlet ctl = new AbortController();\n\treturn {\n\t\tcurrent: () => ctl.signal,\n\t\tcut(): void {\n\t\t\tconst c = ctl;\n\t\t\tctl = new AbortController();\n\t\t\tc.abort(new SelfstoreError('TARGET_UNAVAILABLE', 'Request aborted: target detaching.'));\n\t\t}\n\t};\n}\n"]}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { BACKUP_EXTENSION, BACKUP_MIME } from './chunk-
|
|
1
|
+
import { BACKUP_EXTENSION, BACKUP_MIME } from './chunk-RVOBONZF.js';
|
|
2
2
|
|
|
3
3
|
// src/selfstore/targets/local.ts
|
|
4
4
|
async function pickSaveHandle(filename) {
|
|
@@ -61,5 +61,5 @@ function pickFromDisk() {
|
|
|
61
61
|
}
|
|
62
62
|
|
|
63
63
|
export { datedName, downloadBlob, pickFromDisk, pickSaveHandle, saveToDisk, writeToHandle };
|
|
64
|
-
//# sourceMappingURL=chunk-
|
|
65
|
-
//# sourceMappingURL=chunk-
|
|
64
|
+
//# sourceMappingURL=chunk-LQS7AMOD.js.map
|
|
65
|
+
//# sourceMappingURL=chunk-LQS7AMOD.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/selfstore/targets/local.ts"],"names":[],"mappings":"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","file":"chunk-
|
|
1
|
+
{"version":3,"sources":["../src/selfstore/targets/local.ts"],"names":[],"mappings":"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","file":"chunk-LQS7AMOD.js","sourcesContent":["/**\n * Local-disk helpers: the DOM edge of selfstore, kept out of the core so the\n * core stays environment-agnostic and testable.\n */\n\nimport { BACKUP_EXTENSION, BACKUP_MIME } from '../index';\n\n/** The slice of FileSystemFileHandle this module needs. */\nexport interface SaveHandle {\n\tcreateWritable(): Promise<{ write(d: Blob): Promise<void>; close(): Promise<void> }>;\n}\n\n/**\n * Ask the user where to save, and nothing else.\n *\n * Split out because ORDER decides whether the dialog opens at all. The picker\n * needs the transient user activation of the click, and that activation expires\n * while an encrypted backup is being built (Argon2id is deliberately slow). Ask\n * first, build after, and the dialog is still allowed; build first and the\n * browser refuses it. Hosts that saw a silent download instead of the save\n * dialog were hitting exactly that.\n *\n * Null when the browser has no picker at all (Firefox, Safari) or when it\n * refuses for any reason other than a cancellation - the caller then falls back\n * to a download. Throws nothing; a cancelled dialog is reported as CANCELLED.\n */\nexport async function pickSaveHandle(\n\tfilename: string\n): Promise<{ handle: SaveHandle } | 'CANCELLED' | null> {\n\tconst picker = (\n\t\twindow as unknown as {\n\t\t\tshowSaveFilePicker?: (o: unknown) => Promise<SaveHandle>;\n\t\t}\n\t).showSaveFilePicker;\n\tif (!picker) return null;\n\ttry {\n\t\tconst handle = await picker({\n\t\t\tsuggestedName: filename,\n\t\t\ttypes: [{ description: 'selfstore backup', accept: { [BACKUP_MIME]: [BACKUP_EXTENSION] } }]\n\t\t});\n\t\treturn { handle };\n\t} catch (e) {\n\t\tif (e instanceof DOMException && e.name === 'AbortError') return 'CANCELLED';\n\t\t// Anything else (no activation left, policy): the download still works.\n\t\treturn null;\n\t}\n}\n\n/** Write a blob through a handle obtained earlier by pickSaveHandle. */\nexport async function writeToHandle(handle: SaveHandle, blob: Blob): Promise<void> {\n\tconst writable = await handle.createWritable();\n\tawait writable.write(blob);\n\tawait writable.close();\n}\n\n/** Hand the blob to the browser's download machinery. No cancel to observe:\n * once taken, the host has done all it can. */\nexport function downloadBlob(blob: Blob, filename: string): void {\n\tconst url = URL.createObjectURL(blob);\n\tconst a = document.createElement('a');\n\ta.href = url;\n\ta.download = filename;\n\tdocument.body.appendChild(a);\n\ta.click();\n\ta.remove();\n\tURL.revokeObjectURL(url);\n}\n\n/**\n * Save a backup blob to disk via the File System Access API, else a download.\n *\n * Resolves TRUE when bytes were handed to the browser, FALSE when the user\n * closed the save dialog. Callers must branch on it: a host that records \"last\n * backup: today\" after a cancelled dialog tells the user they are safe when no\n * file exists, and that lie only surfaces the day they need the file.\n *\n * Takes a ready blob, so the dialog opens only after the blob was built. When\n * that blob is an encrypted backup, prefer asking first: see pickSaveHandle.\n */\nexport async function saveToDisk(blob: Blob, filename: string): Promise<boolean> {\n\tconst picked = await pickSaveHandle(filename);\n\tif (picked === 'CANCELLED') return false;\n\tif (picked) {\n\t\tawait writeToHandle(picked.handle, blob);\n\t\treturn true;\n\t}\n\tdownloadBlob(blob, datedName(filename));\n\treturn true;\n}\n\n/**\n * The same name, stamped to the minute.\n *\n * Used only on the download path, and the reasoning is worth stating because it\n * looks backwards. Through a handle the app rewrites the SAME file, so the name\n * must stay stable. A download never replaces anything: the browser appends\n * \" (1)\", \" (2)\", and no web API can stop it. The pile forms either way - so it\n * may as well be readable. A dated series says at a glance which one is the\n * latest and lets the user step back to an earlier one; \"backup (7).zip\" says\n * neither when, nor in what order.\n */\nexport function datedName(filename: string, when = new Date()): string {\n\tconst deux = (n: number) => String(n).padStart(2, '0');\n\tconst stamp =\n\t\t`${when.getFullYear()}-${deux(when.getMonth() + 1)}-${deux(when.getDate())}` +\n\t\t`-${deux(when.getHours())}h${deux(when.getMinutes())}`;\n\tconst dot = filename.lastIndexOf('.');\n\treturn dot <= 0\n\t\t? `${filename}-${stamp}`\n\t\t: `${filename.slice(0, dot)}-${stamp}${filename.slice(dot)}`;\n}\n\n/** Prompt the user to pick a backup file from disk. Resolves null if cancelled. */\nexport function pickFromDisk(): Promise<File | null> {\n\treturn new Promise((resolve) => {\n\t\tconst input = document.createElement('input');\n\t\tinput.type = 'file';\n\t\t// octet-stream: some providers serve a downloaded ZIP under that type.\n\t\tinput.accept = '.zip,application/zip,application/octet-stream';\n\t\tinput.onchange = () => resolve(input.files?.[0] ?? null);\n\t\t// Without this, a cancelled dialog leaves the promise pending forever.\n\t\tinput.oncancel = () => resolve(null);\n\t\t// Some browsers need the input in the DOM for the dialog to open.\n\t\tinput.style.display = 'none';\n\t\tdocument.body.appendChild(input);\n\t\tinput.click();\n\t\tinput.remove();\n\t});\n}\n"]}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { gcmSealRaw, sealDataKey, GROUP_KEYING, signBox, verifyBox, openDataKey, gcmOpenRaw } from './chunk-
|
|
2
|
-
import { mintSlot, MAX_KEY_SLOTS,
|
|
3
|
-
import {
|
|
1
|
+
import { gcmSealRaw, sealDataKey, GROUP_KEYING, signBox, verifyBox, openDataKey, gcmOpenRaw } from './chunk-F4ZZEJ5H.js';
|
|
2
|
+
import { mintSlot, MAX_KEY_SLOTS, gcmSealAad, gcmOpenAad, openExternalSlot, openSlot } from './chunk-5UYPKCDB.js';
|
|
3
|
+
import { toBase64, fromBase64 } from './chunk-WXI2NAPA.js';
|
|
4
|
+
import { SelfstoreError } from './chunk-CSYEIXNY.js';
|
|
4
5
|
|
|
5
6
|
// src/selfstore/archive.ts
|
|
6
7
|
var MANIFEST = "selfstore.json";
|
|
@@ -491,7 +492,7 @@ var BackupBuilder = class _BackupBuilder {
|
|
|
491
492
|
* the user chose into one they have to go and find. */
|
|
492
493
|
async toDisk(filename) {
|
|
493
494
|
const name = filename ?? `${this.opts.app}-${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}${BACKUP_EXTENSION}`;
|
|
494
|
-
const { pickSaveHandle, writeToHandle, downloadBlob } = await import('./local-
|
|
495
|
+
const { pickSaveHandle, writeToHandle, downloadBlob } = await import('./local-GGAMRY23.js');
|
|
495
496
|
const cible = await pickSaveHandle(name);
|
|
496
497
|
if (cible === "CANCELLED") return false;
|
|
497
498
|
const blob = await this.toBlob();
|
|
@@ -578,5 +579,5 @@ var RestoreBuilder = class {
|
|
|
578
579
|
};
|
|
579
580
|
|
|
580
581
|
export { BACKUP_EXTENSION, BACKUP_MIME, RESERVED_COLLECTION_PREFIX, asBytes, backup, changePassword, checkPasswordPolicy, exportSnapshot, importSnapshot, inspect, isEncrypted, readBoxWithSync, restore, writeBox };
|
|
581
|
-
//# sourceMappingURL=chunk-
|
|
582
|
-
//# sourceMappingURL=chunk-
|
|
582
|
+
//# sourceMappingURL=chunk-RVOBONZF.js.map
|
|
583
|
+
//# sourceMappingURL=chunk-RVOBONZF.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/selfstore/archive.ts","../src/selfstore/box.ts","../src/selfstore/types.ts","../src/selfstore/password-policy.ts","../src/selfstore/index.ts","../src/selfstore/verify.ts","../src/selfstore/fluent.ts"],"names":["utf8"],"mappings":"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","file":"chunk-RVOBONZF.js","sourcesContent":["// The inner archive: a JSON manifest (selfstore.json) plus one entry per\n// binary file, as a plain ZIP. Encrypted backups wrap these same bytes in\n// AES-GCM (box.ts). fflate is imported dynamically - only needed at\n// export/import time.\n\nimport type { Snapshot, SnapshotFile } from './types';\nimport { SelfstoreError } from './errors';\n\nconst MANIFEST = 'selfstore.json';\n// Opaque caller bookkeeping (createLocalStore's schema version + merge\n// metadata). Its own entry, so the manifest holds nothing but app data.\nconst SIDECAR = 'sync.json';\nconst ARCHIVE_VERSION = 1;\n\n// Zip-bomb guards. Backups are user data measured in MB; half a GiB per\n// entry and 1 GiB total are far above legitimate use and bound worst-case\n// inflation (many individually-legal entries must not add up to a bomb).\nexport const MAX_ENTRY_BYTES = 512 * 1024 * 1024;\nexport const MAX_TOTAL_BYTES = 1024 * 1024 * 1024;\n\nconst utf8 = { enc: new TextEncoder(), dec: new TextDecoder() };\n\ninterface FileMeta {\n\tid: string;\n\tname: string;\n\tmime: string;\n}\n\n/**\n * ZIP a set of named entries. `level` applies to the JSON entries; file\n * attachments are stored uncompressed - photos and PDFs are usually already\n * compressed, and re-deflating them was the biggest chunk of per-save\n * main-thread time for near-zero size gain. Still a plain readable ZIP.\n */\nexport async function zip(\n\tentries: Record<string, Uint8Array>,\n\tlevel: 0 | 6 = 6\n): Promise<Uint8Array> {\n\tconst { zipSync } = await import('fflate');\n\tconst input: Record<string, Uint8Array | [Uint8Array, { level: 0 }]> = {};\n\tfor (const [name, bytes] of Object.entries(entries)) {\n\t\tinput[name] = level !== 0 && name.startsWith('files/') ? [bytes, { level: 0 }] : bytes;\n\t}\n\treturn zipSync(input as Parameters<typeof zipSync>[0], { level });\n}\n\n/** Unzip to named entries. BAD_FORMAT on unreadable bytes, TOO_LARGE past the bomb guards. */\nexport async function unzip(\n\tbytes: Uint8Array,\n\tmaxEntryBytes: number = MAX_ENTRY_BYTES,\n\tmaxTotalBytes: number = MAX_TOTAL_BYTES\n): Promise<Record<string, Uint8Array>> {\n\tconst { unzipSync } = await import('fflate');\n\tlet total = 0;\n\ttry {\n\t\treturn unzipSync(bytes, {\n\t\t\tfilter: (f) => {\n\t\t\t\tif (f.originalSize > maxEntryBytes) {\n\t\t\t\t\tthrow new SelfstoreError('TOO_LARGE', `Archive entry \"${f.name}\" is too large.`);\n\t\t\t\t}\n\t\t\t\ttotal += f.originalSize;\n\t\t\t\tif (total > maxTotalBytes) {\n\t\t\t\t\tthrow new SelfstoreError('TOO_LARGE', 'Archive total size exceeds the guard.');\n\t\t\t\t}\n\t\t\t\treturn true;\n\t\t\t}\n\t\t});\n\t} catch (e) {\n\t\tif (e instanceof SelfstoreError) throw e;\n\t\tthrow new SelfstoreError('BAD_FORMAT', 'Unreadable archive.');\n\t}\n}\n\n/** Manifest + file entries for a snapshot; `sidecar` rides its own sync.json entry. */\nexport function buildEntries(snap: Snapshot, sidecar?: unknown): Record<string, Uint8Array> {\n\tconst entries: Record<string, Uint8Array> = {};\n\tentries[MANIFEST] = utf8.enc.encode(\n\t\tJSON.stringify({\n\t\t\tversion: ARCHIVE_VERSION,\n\t\t\tcollections: snap.collections,\n\t\t\tfiles: snap.files.map(({ id, name, mime }): FileMeta => ({ id, name, mime }))\n\t\t})\n\t);\n\tif (sidecar !== undefined && sidecar !== null) {\n\t\tentries[SIDECAR] = utf8.enc.encode(JSON.stringify(sidecar));\n\t}\n\tfor (const f of snap.files) entries[`files/${f.id}`] = f.bytes;\n\treturn entries;\n}\n\n/** The sync.json sidecar if present, else null. Unreadable content reads as absent. */\nexport function readSidecar(entries: Record<string, Uint8Array>): unknown {\n\tconst bytes = entries[SIDECAR];\n\tif (!bytes) return null;\n\ttry {\n\t\treturn JSON.parse(utf8.dec.decode(bytes));\n\t} catch {\n\t\treturn null;\n\t}\n}\n\n/** Rebuild a snapshot from ZIP entries (the inverse of buildEntries). */\nexport function entriesToSnapshot(entries: Record<string, Uint8Array>): Snapshot {\n\tconst manifestBytes = entries[MANIFEST];\n\tif (!manifestBytes) throw new SelfstoreError('BAD_FORMAT', 'Missing manifest.');\n\tconst manifest = JSON.parse(utf8.dec.decode(manifestBytes)) as {\n\t\tcollections?: Record<string, unknown[]>;\n\t\tfiles?: FileMeta[];\n\t};\n\tconst files: SnapshotFile[] = (manifest.files ?? []).map((m) => ({\n\t\t...m,\n\t\tbytes: entries[`files/${m.id}`] ?? new Uint8Array()\n\t}));\n\treturn { collections: manifest.collections ?? {}, files };\n}\n\n/** Pack a snapshot (plus an optional opaque sidecar) into ZIP bytes. */\nexport async function pack(snap: Snapshot, sidecar?: unknown): Promise<Uint8Array> {\n\treturn zip(buildEntries(snap, sidecar), 6);\n}\n\n/** Unpack ZIP bytes into a snapshot plus its sidecar (null when absent). */\nexport async function unpackWithSidecar(\n\tzipBytes: Uint8Array\n): Promise<{ snapshot: Snapshot; sidecar: unknown }> {\n\tconst entries = await unzip(zipBytes);\n\treturn { snapshot: entriesToSnapshot(entries), sidecar: readSidecar(entries) };\n}\n","// The portable backup container - a real ZIP in both cases, never a mystery\n// blob:\n//\n// unencrypted: meta.json + selfstore.json + files/* (browsable, format 1)\n// encrypted: meta.json + data.enc + LISEZMOI.txt (AES-256-GCM / Argon2id)\n//\n// Encrypted means the password envelope (format 3): a random data key seals\n// the inner ZIP, wrapped once per password in keys[], and the exact meta.json\n// bytes ride as GCM AAD so the slot table is tamper-evident. Format 2 is the\n// passwordless group variant. Anything else is refused.\n\nimport type { Snapshot, Header, EncodeOptions, PasswordSlot, ExternalSlot, KeySlot } from './types';\nimport {\n\tbuildEntries,\n\tentriesToSnapshot,\n\treadSidecar,\n\tpack,\n\tunpackWithSidecar,\n\tzip,\n\tunzip\n} from './archive';\nimport {\n\tmintSlot,\n\topenSlot,\n\topenExternalSlot,\n\tMAX_KEY_SLOTS,\n\tgcmSealAad,\n\tgcmOpenAad,\n\ttoBase64,\n\tfromBase64\n} from './crypto';\nimport {\n\tGROUP_KEYING,\n\tgcmSealRaw,\n\tgcmOpenRaw,\n\tsealDataKey,\n\topenDataKey,\n\tsignBox,\n\tverifyBox,\n\ttype GroupIdentity\n} from './group';\nimport { SelfstoreError } from './errors';\n\n// Genuine ZIPs get the honest extension.\nexport const BACKUP_EXTENSION = '.zip';\nexport const BACKUP_MIME = 'application/zip';\n\nconst META = 'meta.json';\nconst DATA = 'data.enc';\nconst SIG = 'sig.bin';\nconst README = 'LISEZMOI.txt';\n\n// Format identifier, which is also the discriminant: 1 plain ZIP, 2 group\n// mode, 3 authenticated password envelope (header bound as GCM AAD). These are\n// fixed wire ids; anything else is refused.\nconst BOX_FORMAT_PLAIN = 1;\nconst BOX_FORMAT_GROUP = 2;\nconst BOX_FORMAT_ENVELOPE_AUTH = 3;\n\nconst ZIP_MAGIC = [0x50, 0x4b, 0x03, 0x04]; // \"PK\\x03\\x04\"\n\nconst utf8 = { enc: new TextEncoder(), dec: new TextDecoder() };\n\n// Shipped inside encrypted backups for whoever opens the ZIP by hand. ASCII\n// only so it renders in any viewer; apps brand it via EncodeOptions.readme.\nconst DEFAULT_README =\n\t'Encrypted backup / Sauvegarde chiffree\\n\\n' +\n\t'This is a valid ZIP archive.\\n' +\n\t'\"data.enc\" holds your encrypted data (AES-256-GCM, key derived with Argon2id);\\n' +\n\t'\"meta.json\" holds the decryption parameters (cleartext).\\n' +\n\t'To read it, import this file into the application that created it\\n' +\n\t'(named in meta.json) with your password.\\n\\n' +\n\t'Ce fichier est une archive ZIP valide.\\n' +\n\t'\"data.enc\" contient vos donnees chiffrees (AES-256-GCM, cle derivee par Argon2id) ;\\n' +\n\t'\"meta.json\" contient les parametres de dechiffrement (en clair).\\n' +\n\t\"Pour la lire, importez ce fichier dans l'application qui l'a creee\\n\" +\n\t'(nommee dans meta.json) avec votre mot de passe.\\n';\n\n/** Coerce an accepted input (a picked File, a fetched Blob, raw bytes) to bytes. */\nexport async function asBytes(input: Blob | Uint8Array): Promise<Uint8Array> {\n\treturn input instanceof Uint8Array ? input : new Uint8Array(await input.arrayBuffer());\n}\n\nfunction startsWith(bytes: Uint8Array, magic: number[]): boolean {\n\treturn bytes.length >= magic.length && magic.every((b, i) => bytes[i] === b);\n}\n\n// An unknown cipher or per-slot KDF means a newer writer: UNSUPPORTED_VERSION,\n// not a misleading DECRYPT_FAILED from feeding foreign params to AES-GCM.\nfunction assertKnownCrypto(header: Header): void {\n\tif (header.encryption !== 'none' && header.encryption !== 'aes-256-gcm') {\n\t\tthrow new SelfstoreError(\n\t\t\t'UNSUPPORTED_VERSION',\n\t\t\t`Encryption \"${header.encryption}\" is newer than this reader supports.`\n\t\t);\n\t}\n\tif (header.keying !== undefined && header.keying !== GROUP_KEYING) {\n\t\tthrow new SelfstoreError(\n\t\t\t'UNSUPPORTED_VERSION',\n\t\t\t`Key envelope \"${header.keying}\" is newer than this reader supports.`\n\t\t);\n\t}\n\tfor (const slot of header.keys ?? []) {\n\t\t// External slots carry no KDF (KEK comes from the caller's secret).\n\t\tif (!slot || (slot as ExternalSlot).kind === 'external') continue;\n\t\tif ((slot as PasswordSlot).kdf?.algo !== 'argon2id') {\n\t\t\tthrow new SelfstoreError(\n\t\t\t\t'UNSUPPORTED_VERSION',\n\t\t\t\t`Key derivation \"${(slot as PasswordSlot).kdf?.algo}\" is newer than this reader supports.`\n\t\t\t);\n\t\t}\n\t}\n}\n\n/**\n * Serialize a snapshot to backup bytes. The optional `sidecar`\n * (createLocalStore's bookkeeping) rides its own sync.json entry - inside the\n * envelope when encrypting, so it never leaks in cleartext.\n */\nexport async function writeBox(\n\tsnapshot: Snapshot,\n\topts: EncodeOptions,\n\tsidecar?: unknown\n): Promise<Uint8Array> {\n\tconst header: Header = {\n\t\tformat: BOX_FORMAT_PLAIN,\n\t\tapp: opts.app,\n\t\tappVersion: opts.appVersion,\n\t\tschemaVersion: opts.schemaVersion,\n\t\tcreatedAt: new Date().toISOString(),\n\t\tencryption: 'none'\n\t};\n\n\tif (opts.group) {\n\t\tif (opts.password) {\n\t\t\tthrow new TypeError('writeBox(): `group` and `password` are mutually exclusive.');\n\t\t}\n\t\t// Fresh data key enveloped once per recipient; the author's signature\n\t\t// covers the exact meta.json + data.enc bytes, so nothing a reader\n\t\t// trusts (recipients, author claim, iv, ciphertext) can be altered.\n\t\tconst dataKey = crypto.getRandomValues(new Uint8Array(32));\n\t\tconst enc = await gcmSealRaw(dataKey, await pack(snapshot, sidecar));\n\t\tconst meta: Header = {\n\t\t\t...header,\n\t\t\tformat: BOX_FORMAT_GROUP,\n\t\t\tencryption: 'aes-256-gcm',\n\t\t\tkeying: GROUP_KEYING,\n\t\t\tauthor: opts.group.sign.pub,\n\t\t\trecipients: await sealDataKey(dataKey, opts.group.recipients),\n\t\t\tiv: enc.iv\n\t\t};\n\t\tconst metaBytes = utf8.enc.encode(JSON.stringify(meta));\n\t\treturn zip(\n\t\t\t{\n\t\t\t\t[META]: metaBytes,\n\t\t\t\t[DATA]: enc.ciphertext,\n\t\t\t\t[SIG]: await signBox(metaBytes, enc.ciphertext, opts.group.sign.priv),\n\t\t\t\t[README]: utf8.enc.encode(opts.readme || DEFAULT_README)\n\t\t\t},\n\t\t\t0\n\t\t);\n\t}\n\n\tif (opts.envelope && opts.password) {\n\t\tthrow new TypeError('writeBox(): `envelope` and `password` are mutually exclusive.');\n\t}\n\n\tif (opts.envelope || opts.password) {\n\t\t// A rewrite passes `envelope` (held data key + carried slot table) so a\n\t\t// writer that knows only one password preserves every other slot; a\n\t\t// plain `password` mints a fresh single-slot envelope, i.e. a data-key\n\t\t// rotation - that is what makes setEncryption actually revoke.\n\t\t// The iv is minted before meta.json is serialized so it sits inside the\n\t\t// AAD; seal and open then agree on the exact header bytes.\n\t\tconst dataKey = opts.envelope?.dataKey ?? crypto.getRandomValues(new Uint8Array(32));\n\t\t// Every extra secret gets its own slot around the SAME data key: either\n\t\t// one opens the file, and neither can be derived from the other. A rewrite\n\t\t// (`envelope`) already carries the slot table, so extras only apply when\n\t\t// minting a fresh envelope.\n\t\tconst slots = opts.envelope?.slots ?? [\n\t\t\tawait mintSlot(opts.password!, dataKey),\n\t\t\t...(await Promise.all((opts.extraSecrets ?? []).map((s) => mintSlot(s, dataKey))))\n\t\t];\n\t\tif (slots.length === 0 || slots.length > MAX_KEY_SLOTS) {\n\t\t\tthrow new TypeError(`writeBox(): between 1 and ${MAX_KEY_SLOTS} password slots.`);\n\t\t}\n\t\tconst iv = crypto.getRandomValues(new Uint8Array(12));\n\t\tconst meta: Header = {\n\t\t\t...header,\n\t\t\tformat: BOX_FORMAT_ENVELOPE_AUTH,\n\t\t\tencryption: 'aes-256-gcm',\n\t\t\tkeys: slots,\n\t\t\tiv: toBase64(iv)\n\t\t};\n\t\tconst metaBytes = utf8.enc.encode(JSON.stringify(meta));\n\t\tconst ciphertext = await gcmSealAad(dataKey, iv, await pack(snapshot, sidecar), metaBytes);\n\t\treturn zip(\n\t\t\t{\n\t\t\t\t[META]: metaBytes,\n\t\t\t\t[DATA]: ciphertext,\n\t\t\t\t[README]: utf8.enc.encode(opts.readme || DEFAULT_README)\n\t\t\t},\n\t\t\t0\n\t\t);\n\t}\n\n\t// The file is the browsable archive: manifest + files (+ sync.json), plus meta.json.\n\tconst entries = buildEntries(snapshot, sidecar);\n\tentries[META] = utf8.enc.encode(JSON.stringify(header));\n\treturn zip(entries, 6);\n}\n\n/** Read the cleartext metadata (app, date, whether encrypted) without decrypting. */\nexport async function readBoxMeta(bytes: Uint8Array): Promise<Header> {\n\tif (!startsWith(bytes, ZIP_MAGIC)) {\n\t\tthrow new SelfstoreError(\n\t\t\t'BAD_FORMAT',\n\t\t\t'Not a selfstore backup: expected a ZIP. Pass the exact bytes/Blob produced by ' +\n\t\t\t\t'backup().toBlob() / exportSnapshot(), not a decoded object.'\n\t\t);\n\t}\n\treturn metaOf(await unzip(bytes));\n}\n\n/**\n * Group-mode read inputs: the reader's identity and the trusted author keys\n * (manifest members' Ed25519 publics). `authors` is mandatory - a valid\n * signature over an attacker-chosen author proves nothing (SPEC.md 12.6), so\n * there is no \"decrypt without checking who signed\" mode.\n */\nexport interface GroupReadOptions {\n\tidentity: Pick<GroupIdentity, 'encPub' | 'encPriv'>;\n\tauthors: string[];\n}\n\n/**\n * Key material captured at read time: the data key that opened the file plus\n * the slot table verbatim. Carried into rewrites so a session that knows only\n * one key preserves every slot.\n */\nexport interface BoxEnvelope {\n\tdataKey: Uint8Array;\n\tslots: KeySlot[];\n}\n\n/**\n * App-supplied secret fetch for an external-key slot (the WebAuthn/hardware\n * exchange lives app-side). Return null to skip the slot (wrong key, user\n * cancelled).\n */\nexport type ExternalKeyResolver = (slot: ExternalSlot) => Promise<Uint8Array | null>;\n\n/**\n * Read a backup into a snapshot plus its sync.json sidecar (null when the\n * file carries none). Group copies need `group` and return the verified\n * author; envelope copies accept a held `dataKey` (KDF-free re-read) and\n * return the envelope for later rewrites.\n */\nexport async function readBoxWithSync(\n\tbytes: Uint8Array,\n\tpassword?: string,\n\tgroup?: GroupReadOptions,\n\tdataKey?: Uint8Array,\n\texternalResolver?: ExternalKeyResolver\n): Promise<{\n\tsnapshot: Snapshot;\n\tsidecar: unknown;\n\tauthor?: string;\n\tenvelope?: BoxEnvelope;\n\tformat: number;\n}> {\n\tconst entries = await unzip(bytes);\n\tconst meta = metaOf(entries);\n\tconst format = meta.format ?? BOX_FORMAT_PLAIN;\n\tif (\n\t\tformat !== BOX_FORMAT_PLAIN &&\n\t\tformat !== BOX_FORMAT_GROUP &&\n\t\tformat !== BOX_FORMAT_ENVELOPE_AUTH\n\t) {\n\t\tthrow new SelfstoreError(\n\t\t\t'UNSUPPORTED_VERSION',\n\t\t\t`Backup format ${format} is not supported by this reader.`\n\t\t);\n\t}\n\t// Bind security mode to format generation both ways, so neither `keying`\n\t// nor `format` can be stripped to route a box down the wrong path.\n\tif ((format === BOX_FORMAT_GROUP) !== !!meta.keying) {\n\t\tthrow new SelfstoreError(\n\t\t\t'BAD_FORMAT',\n\t\t\t'Group mode (format 2) and the keying field must agree.'\n\t\t);\n\t}\n\tif ((format === BOX_FORMAT_ENVELOPE_AUTH) !== Array.isArray(meta.keys)) {\n\t\tthrow new SelfstoreError(\n\t\t\t'BAD_FORMAT',\n\t\t\t'Password envelope (format 3) and the keys field must agree.'\n\t\t);\n\t}\n\tif (meta.keying) return { ...(await readGroupBox(entries, meta, group)), format };\n\tif (format === BOX_FORMAT_ENVELOPE_AUTH) {\n\t\treturn {\n\t\t\t...(await readEnvelopeBox(entries, meta, password, dataKey, externalResolver)),\n\t\t\tformat\n\t\t};\n\t}\n\tif (meta.encryption === 'none') {\n\t\treturn { snapshot: entriesToSnapshot(entries), sidecar: readSidecar(entries), format };\n\t}\n\t// Encrypted content in a container claiming the plain format: never written\n\t// by this library. Refuse cleanly rather than half-read.\n\tthrow new SelfstoreError('BAD_FORMAT', 'Encrypted content in a plain-format container.');\n}\n\n// Envelope read path: bounds first, the held data key as a KDF-free fast\n// path, then one trial per slot. All slots failing is DECRYPT_FAILED - wrong\n// key and tampering stay one case.\nasync function readEnvelopeBox(\n\tentries: Record<string, Uint8Array>,\n\tmeta: Header,\n\tpassword?: string,\n\tdataKey?: Uint8Array,\n\texternalResolver?: ExternalKeyResolver\n): Promise<{ snapshot: Snapshot; sidecar: unknown; envelope: BoxEnvelope }> {\n\tconst dataEnc = entries[DATA];\n\tconst metaBytes = entries[META];\n\tconst slots = meta.keys;\n\tif (\n\t\t!dataEnc ||\n\t\t!metaBytes ||\n\t\t!Array.isArray(slots) ||\n\t\tslots.length === 0 ||\n\t\ttypeof meta.iv !== 'string' ||\n\t\tmeta.encryption !== 'aes-256-gcm'\n\t) {\n\t\tthrow new SelfstoreError('BAD_FORMAT', 'Missing or malformed envelope entries or parameters.');\n\t}\n\tif (slots.length > MAX_KEY_SLOTS) {\n\t\tthrow new SelfstoreError(\n\t\t\t'UNSUPPORTED_VERSION',\n\t\t\t`Backup declares ${slots.length} key slots; this reader caps at ${MAX_KEY_SLOTS} ` +\n\t\t\t\t'(each password slot is one memory-hard trial).'\n\t\t);\n\t}\n\t// The exact meta.json bytes are bound as GCM AAD: a tampered header (an\n\t// altered slot table, a swapped KDF) fails the decrypt instead of opening.\n\tconst aad = metaBytes;\n\tconst iv = fromBase64(meta.iv);\n\t// A caller re-reading its own file skips the KDF entirely. On failure fall\n\t// through to the slot trials - another device may have rotated the key.\n\tif (dataKey) {\n\t\ttry {\n\t\t\tconst r = await unpackWithSidecar(await gcmOpenAad(dataKey, iv, dataEnc, aad));\n\t\t\treturn { ...r, envelope: { dataKey, slots } };\n\t\t} catch {\n\t\t\t/* rotated, foreign, or tampered file: the slot path decides */\n\t\t}\n\t}\n\tif (!password && !externalResolver) {\n\t\tthrow new SelfstoreError(\n\t\t\t'PASSWORD_REQUIRED',\n\t\t\t'This backup is encrypted. Provide the password, or the external key (e.g. a passkey), to open it.'\n\t\t);\n\t}\n\tfor (const slot of slots) {\n\t\tlet opened: Uint8Array | null = null;\n\t\tif ((slot as ExternalSlot).kind === 'external') {\n\t\t\tif (externalResolver) {\n\t\t\t\tconst secret = await externalResolver(slot as ExternalSlot);\n\t\t\t\tif (secret) opened = await openExternalSlot(slot as ExternalSlot, secret);\n\t\t\t}\n\t\t} else if (password) {\n\t\t\topened = await openSlot(slot as PasswordSlot, password);\n\t\t}\n\t\tif (!opened) continue;\n\t\t// Every slot wraps the same data key, so if the decrypt below fails\n\t\t// (header tamper surfaces here on format 3) trying another slot gains\n\t\t// nothing - let it throw.\n\t\tconst r = await unpackWithSidecar(await gcmOpenAad(opened, iv, dataEnc, aad));\n\t\treturn { ...r, envelope: { dataKey: opened, slots } };\n\t}\n\tthrow new SelfstoreError(\n\t\t'DECRYPT_FAILED',\n\t\t'Wrong key (no slot opened), or the backup is corrupted.'\n\t);\n}\n\n// Group read path, in trust order: shape, membership, signature, then\n// envelope + decrypt.\nasync function readGroupBox(\n\tentries: Record<string, Uint8Array>,\n\tmeta: Header,\n\tgroup?: GroupReadOptions\n): Promise<{ snapshot: Snapshot; sidecar: unknown; author: string }> {\n\tif (!group || !Array.isArray(group.authors)) {\n\t\tthrow new SelfstoreError(\n\t\t\t'IDENTITY_REQUIRED',\n\t\t\t'This copy is group-encrypted (per-member keys, no password). Open it with the member ' +\n\t\t\t\t'identity AND the trusted author list: the store does this when attached with { group }.'\n\t\t);\n\t}\n\tconst metaBytes = entries[META];\n\tconst dataEnc = entries[DATA];\n\tconst sig = entries[SIG];\n\t// Trusted fields must be well-typed, not merely truthy: malformed input\n\t// fails closed as BAD_FORMAT, never reaches the crypto as a TypeError.\n\tif (\n\t\t!metaBytes ||\n\t\t!dataEnc ||\n\t\t!sig ||\n\t\tmeta.format !== BOX_FORMAT_GROUP ||\n\t\ttypeof meta.author !== 'string' ||\n\t\tmeta.author.length === 0 ||\n\t\t!Array.isArray(meta.recipients) ||\n\t\tmeta.recipients.length === 0 ||\n\t\ttypeof meta.iv !== 'string'\n\t) {\n\t\tthrow new SelfstoreError(\n\t\t\t'BAD_FORMAT',\n\t\t\t'Missing or malformed group encryption entries or parameters.'\n\t\t);\n\t}\n\t// Membership before any crypto: a valid signature over an attacker-chosen\n\t// author proves nothing.\n\tif (!group.authors.includes(meta.author)) {\n\t\tthrow new SelfstoreError(\n\t\t\t'SIGNATURE_INVALID',\n\t\t\t'Copy author is not a trusted group member (check the membership manifest).'\n\t\t);\n\t}\n\tif (!(await verifyBox(metaBytes, dataEnc, sig, meta.author))) {\n\t\tthrow new SelfstoreError('SIGNATURE_INVALID', 'Copy signature verification failed.');\n\t}\n\tconst dataKey = await openDataKey(meta.recipients, group.identity);\n\tconst inner = await gcmOpenRaw(dataKey, meta.iv, dataEnc);\n\tconst r = await unpackWithSidecar(inner);\n\treturn { ...r, author: meta.author };\n}\n\n/** Read a backup into a snapshot, ignoring the sync sidecar. */\nexport async function readBox(bytes: Uint8Array, password?: string): Promise<Snapshot> {\n\treturn (await readBoxWithSync(bytes, password)).snapshot;\n}\n\nfunction metaOf(entries: Record<string, Uint8Array>): Header {\n\tconst metaBytes = entries[META];\n\tif (!metaBytes) throw new SelfstoreError('BAD_FORMAT', 'Missing backup metadata.');\n\tconst meta = JSON.parse(utf8.dec.decode(metaBytes)) as Header;\n\tif ((meta.format ?? BOX_FORMAT_PLAIN) > BOX_FORMAT_ENVELOPE_AUTH) {\n\t\tthrow new SelfstoreError(\n\t\t\t'UNSUPPORTED_VERSION',\n\t\t\t`Backup format v${meta.format} is newer than this reader supports.`\n\t\t);\n\t}\n\tassertKnownCrypto(meta);\n\treturn meta;\n}\n","// Public types. selfstore is schema-agnostic: named collections of opaque\n// documents plus binary files. The app owns the schema and its migrations.\n\n/** A binary attachment (image, PDF, any bytes). */\nexport interface SnapshotFile {\n\tid: string;\n\tname: string;\n\tmime: string;\n\tbytes: Uint8Array;\n}\n\n/**\n * Collection names starting with `__` are reserved for library bookkeeping\n * (createLocalStore keeps its sync metadata under `__store`). The fluent\n * `backup()` rejects them and `read()` strips them.\n */\nexport const RESERVED_COLLECTION_PREFIX = '__';\n\n/** Everything a backup carries. Collection names must not start with `__`. */\nexport interface Snapshot {\n\tcollections: Record<string, unknown[]>;\n\tfiles: SnapshotFile[];\n}\n\n/** Key-derivation parameters. Stored cleartext so older files still decode. */\nexport interface KdfParams {\n\talgo: 'argon2id';\n\tsalt: string;\n\tm: number;\n\tt: number;\n\tp: number;\n}\n\n/**\n * One wrap of the random data key under one password; any slot's password\n * opens the backup. Adding or removing a slot leaves the data key alone.\n * Changing a password rotates the data key (setEncryption) - that is the\n * operation that actually revokes access.\n */\nexport interface PasswordSlot {\n\t/** Random id so an app can list or drop a slot without knowing its\n\t * password. Cleartext, not authenticated. */\n\tid: string;\n\t/** Absent on password slots, so files written before external keys\n\t * existed still parse. */\n\tkind?: 'password';\n\tkdf: KdfParams;\n\t/** 96-bit IV of the wrap, base64. */\n\tiv: string;\n\t/** AES-256-GCM(KEK, dataKey) with tag appended: 48 bytes, base64. */\n\twrapped: string;\n}\n\n/**\n * Data key wrapped under a caller-supplied 32-byte secret (passkey PRF,\n * hardware token). The library never performs the WebAuthn or hardware\n * exchange; the app derives the secret and hands it in. KEK is\n * HKDF-SHA256 of the secret - no Argon2, the input is already\n * high-entropy. Shares the `keys[]` table with password slots, so a\n * backup can hold e.g. a passkey plus a recovery password.\n */\nexport interface ExternalSlot {\n\tid: string;\n\tkind: 'external';\n\t/** App-owned locator for re-deriving the secret (say, a WebAuthn\n\t * credential id). Cleartext, never a security input. */\n\tkeyRef: string;\n\t/** 96-bit IV of the wrap, base64. */\n\tiv: string;\n\t/** AES-256-GCM(KEK, dataKey) with tag appended: 48 bytes, base64. */\n\twrapped: string;\n}\n\nexport type KeySlot = PasswordSlot | ExternalSlot;\n\n/**\n * Group mode (format 2): the per-publication AES key wrapped for one\n * recipient. `kid` = first 8 bytes of SHA-256(recipient X25519 public),\n * base64 - a locator, not a name. `epk` is the ephemeral X25519 public;\n * `wrap` is AES-256-GCM(dataKey) under HKDF of the ECDH shared secret.\n */\nexport interface RecipientStanza {\n\tkid: string;\n\tepk: string;\n\tiv: string;\n\twrap: string;\n}\n\n/**\n * Cleartext metadata: the `meta.json` entry of the ZIP.\n *\n * app/appVersion/createdAt are cosmetic and unauthenticated - never base a\n * security decision on them. In password-envelope mode the wrapped data key\n * authenticates the payload; in group mode the author's Ed25519 signature\n * (sig.bin) covers the exact meta.json and data.enc bytes.\n */\nexport interface Header {\n\t/** Format generation: 2 = plain ZIP, 3 = group, 4 = password envelope.\n\t * Absent means 2. */\n\tformat?: number;\n\tapp: string;\n\t/** Writing app's release version, informational. */\n\tappVersion?: string;\n\t/** Writing app's data schema version (createLocalStore `version`).\n\t * Distinct from appVersion: this one drives migration. */\n\tschemaVersion?: number;\n\tcreatedAt: string;\n\tencryption: 'none' | 'aes-256-gcm';\n\t/** 96-bit IV of data.enc, base64. Present when encrypted. */\n\tiv?: string;\n\t/** Group mode: data-key envelope scheme ('x25519-hkdf-sha256'). */\n\tkeying?: string;\n\t/** Group mode: author's Ed25519 public key. Trust only after verifying\n\t * sig.bin and membership in the group manifest. */\n\tauthor?: string;\n\t/** Group mode: one data-key envelope per recipient. */\n\trecipients?: RecipientStanza[];\n\t/** Password envelope only: the data key wrapped once per password.\n\t * Present iff format === 4. */\n\tkeys?: KeySlot[];\n}\n\n/** Options for writing a backup. */\nexport interface EncodeOptions {\n\tapp: string;\n\tappVersion?: string;\n\tschemaVersion?: number;\n\t/** Omit or leave empty for an unencrypted backup (a plain, readable ZIP). */\n\tpassword?: string;\n\t/** Group mode: encrypt to these recipients (raw X25519 publics, base64)\n\t * under a fresh data key and sign with the author's Ed25519 key.\n\t * Mutually exclusive with `password`. */\n\tgroup?: {\n\t\trecipients: string[];\n\t\tsign: { pub: string; priv: string };\n\t};\n\t/** README text shipped inside an encrypted backup, for whoever opens the\n\t * ZIP by hand. Defaults to a neutral note; apps should brand it. */\n\treadme?: string;\n\t/** Rewrite path: reuse this exact envelope (held data key + slot table\n\t * from the last read) so a writer that knows only one password keeps\n\t * every other slot. Mutually exclusive with `password` and `group`. */\n\tenvelope?: { dataKey: Uint8Array; slots: KeySlot[] };\n\t/** Read the bytes back before returning them, and throw VERIFY_FAILED if\n\t * they do not hold what went in. Carried in the options rather than on the\n\t * builder so it survives every link of the chain: set on one link and lost\n\t * by the next is the silent failure this whole feature exists to prevent. */\n\tverify?: boolean;\n\t/** Extra secrets that ALSO open this backup, each getting its own key slot\n\t * around the same data key - typically a recovery code the user printed.\n\t * Only meaningful alongside `password`. */\n\textraSecrets?: string[];\n}\n","// A password strength policy the store can enforce and a UI can preview from\n// the same rules. Deliberately small: length plus character-class requirements,\n// no dictionary or breach checks (those need data the library does not ship).\n// Unicode-aware so an accented letter counts as a letter and an emoji counts as\n// one character, not two.\n\nexport interface PasswordPolicy {\n\t/** Minimum length in code points (so an emoji counts as one). Default 0. */\n\tminLength?: number;\n\t/** Require at least one lowercase letter (any script with case). */\n\trequireLowercase?: boolean;\n\t/** Require at least one uppercase letter (any script with case). */\n\trequireUppercase?: boolean;\n\t/** Require at least one digit. */\n\trequireDigit?: boolean;\n\t/** Require at least one symbol: anything that is not a letter or a number. */\n\trequireSymbol?: boolean;\n}\n\n/** A stable code for each unmet requirement, so the app maps it to its own copy\n * (like the error codes). `minLength` covers the length rule. */\nexport type PasswordRequirement = 'minLength' | 'lowercase' | 'uppercase' | 'digit' | 'symbol';\n\nexport interface PasswordCheck {\n\tok: boolean;\n\t/** The requirements the password fails, in a stable order. Empty when ok. */\n\tunmet: PasswordRequirement[];\n}\n\n/** Measure a password against a policy. Pure and synchronous: the same call\n * drives a live strength hint in the UI and the store's enforcement. An empty\n * or absent policy passes everything. */\nexport function checkPasswordPolicy(password: string, policy: PasswordPolicy): PasswordCheck {\n\tconst unmet: PasswordRequirement[] = [];\n\t// Count code points, not UTF-16 units, so a password of emoji is not\n\t// over-counted and a min-length rule stays intuitive.\n\tif ([...password].length < (policy.minLength ?? 0)) unmet.push('minLength');\n\tif (policy.requireLowercase && !/\\p{Ll}/u.test(password)) unmet.push('lowercase');\n\tif (policy.requireUppercase && !/\\p{Lu}/u.test(password)) unmet.push('uppercase');\n\tif (policy.requireDigit && !/\\p{Nd}/u.test(password)) unmet.push('digit');\n\tif (policy.requireSymbol && !/[^\\p{L}\\p{N}]/u.test(password)) unmet.push('symbol');\n\treturn { ok: unmet.length === 0, unmet };\n}\n","// The backup format: Snapshot in, portable ZIP out, and back. Plain archive\n// without a password, AES-256-GCM (over Argon2id) with one.\n//\n// const blob = await exportSnapshot(snapshot, { app: 'my-app', password });\n// const back = await importSnapshot(blob, { password });\n//\n// or fluent, same engine:\n//\n// const blob = await backup(snapshot).as('my-app').encryptedWith(password).toBlob();\n// const back = await restore(blob).withPassword(password).read();\n\nimport type { Snapshot, Header, EncodeOptions } from './types';\nimport { writeBox, readBox, readBoxMeta, asBytes } from './box';\n\nexport type {\n\tSnapshot,\n\tSnapshotFile,\n\tHeader,\n\tKdfParams,\n\tRecipientStanza,\n\tEncodeOptions,\n\tPasswordSlot,\n\tExternalSlot,\n\tKeySlot\n} from './types';\nexport { RESERVED_COLLECTION_PREFIX } from './types';\nexport {\n\tSelfstoreError,\n\tAuthExpiredError,\n\tisSelfstoreError,\n\tisAuthExpired,\n\terrorLabelKey,\n\ttype SelfstoreErrorCode\n} from './errors';\nexport { BACKUP_EXTENSION, BACKUP_MIME } from './box';\nexport {\n\tcheckPasswordPolicy,\n\ttype PasswordPolicy,\n\ttype PasswordCheck,\n\ttype PasswordRequirement\n} from './password-policy';\nexport {\n\tbackup,\n\trestore,\n\ttype BackupDraft,\n\ttype BackupBuilder,\n\ttype EncryptedBackupBuilder,\n\ttype RestoreBuilder\n} from './fluent';\nexport { verifyBackup, countsOf, type ExpectedContents } from './verify';\n\n/** Serialize a snapshot to a portable backup blob (compressed; encrypted if a password is given). */\nexport async function exportSnapshot(snapshot: Snapshot, opts: EncodeOptions): Promise<Blob> {\n\treturn new Blob([(await writeBox(snapshot, opts)) as BlobPart], { type: 'application/zip' });\n}\n\n/** Read a backup blob (or raw bytes) back into a snapshot. */\nexport async function importSnapshot(\n\tinput: Blob | Uint8Array,\n\topts: { password?: string } = {}\n): Promise<Snapshot> {\n\treturn readBox(await asBytes(input), opts.password);\n}\n\n/** Peek at a backup's cleartext metadata (app, date, whether it's encrypted) without decrypting. */\nexport async function inspect(input: Blob | Uint8Array): Promise<Header> {\n\treturn readBoxMeta(await asBytes(input));\n}\n\n/** True if the backup needs a password to import. */\nexport async function isEncrypted(input: Blob | Uint8Array): Promise<boolean> {\n\treturn (await inspect(input)).encryption !== 'none';\n}\n\n/** Re-encrypt under a new password. `from` opens the file, `to` protects the\n * result; omit `to` for an unencrypted backup. */\nexport async function changePassword(\n\tinput: Blob | Uint8Array,\n\topts: { from?: string; to?: string; readme?: string }\n): Promise<Blob> {\n\tconst bytes = await asBytes(input);\n\tconst meta = await readBoxMeta(bytes);\n\tconst snapshot = await readBox(bytes, opts.from);\n\treturn exportSnapshot(snapshot, {\n\t\tapp: meta.app,\n\t\tappVersion: meta.appVersion,\n\t\tschemaVersion: meta.schemaVersion,\n\t\tpassword: opts.to,\n\t\treadme: opts.readme\n\t});\n}\n","/**\n * Read a freshly written backup back, before anyone is told it exists.\n *\n * A backup encrypted with a key nobody can reproduce, truncated, or built from\n * an empty snapshot looks exactly like a good one: right name, right date,\n * plausible size. The difference only shows up on the day of the disaster, when\n * it is far too late to make another. The only way to know is to open it.\n *\n * The cost is one decrypt of data the app already holds - a few hundred\n * milliseconds at these sizes, once per save. Cheap enough that the honest\n * default is to do it, and cheap enough that a host doing it on every write is\n * not being wasteful.\n */\n\nimport { SelfstoreError } from './errors';\nimport { importSnapshot } from './index';\nimport type { Snapshot } from './types';\n\n/** What the caller believes it just wrote. */\nexport interface ExpectedContents {\n\t/** Per-collection record counts. Only the named collections are checked, so\n\t * a caller can pin the two that matter instead of listing everything. */\n\tcounts?: Record<string, number>;\n\t/** Number of binary files. */\n\tfiles?: number;\n}\n\n/**\n * Open `bytes` and check they hold what was meant to be written. Throws\n * VERIFY_FAILED otherwise - never returns false, because a caller that ignores\n * a boolean is exactly how a backup that lies gets announced as done.\n */\nexport async function verifyBackup(\n\tbytes: Blob | Uint8Array,\n\topts: { password?: string; expect?: ExpectedContents } = {}\n): Promise<void> {\n\tlet read: Snapshot;\n\ttry {\n\t\tread = await importSnapshot(bytes, { password: opts.password });\n\t} catch {\n\t\t// The password is the likeliest culprit and the most expensive one to\n\t\t// discover late: an archive nobody can open is not a backup.\n\t\tthrow new SelfstoreError('VERIFY_FAILED', 'The backup just written could not be read back.');\n\t}\n\tconst expect = opts.expect;\n\tif (!expect) return;\n\tfor (const [name, count] of Object.entries(expect.counts ?? {})) {\n\t\tconst got = read.collections[name]?.length ?? 0;\n\t\tif (got !== count) {\n\t\t\tthrow new SelfstoreError(\n\t\t\t\t'VERIFY_FAILED',\n\t\t\t\t`The backup just written holds ${got} \"${name}\" where ${count} were expected.`\n\t\t\t);\n\t\t}\n\t}\n\tif (expect.files !== undefined && read.files.length !== expect.files) {\n\t\tthrow new SelfstoreError(\n\t\t\t'VERIFY_FAILED',\n\t\t\t`The backup just written holds ${read.files.length} files where ${expect.files} were expected.`\n\t\t);\n\t}\n}\n\n/** Every collection's record count, for a caller that wants the whole snapshot\n * checked rather than picking which parts matter. */\nexport function countsOf(snapshot: Snapshot): ExpectedContents {\n\tconst counts: Record<string, number> = {};\n\tfor (const [name, rows] of Object.entries(snapshot.collections)) {\n\t\tcounts[name] = rows.length;\n\t}\n\treturn { counts, files: snapshot.files.length };\n}\n","// Fluent facade over the functional API. The chain is staged so an illegal\n// call order does not compile: backup() exposes only .as(), and .withReadme()\n// appears only after .encryptedWith() (the readme ships inside the encrypted\n// ZIP, so it means nothing on a plain one).\n//\n// const blob = await backup(snapshot).as('my-app', '1.2.0').encryptedWith(pw).toBlob();\n// const snap = await restore(file).withPassword(pw).read();\n\nimport type { Snapshot, Header, EncodeOptions } from './types';\nimport { RESERVED_COLLECTION_PREFIX } from './types';\nimport { writeBox, readBox, readBoxMeta, asBytes, BACKUP_MIME, BACKUP_EXTENSION } from './box';\nimport { verifyBackup, countsOf } from './verify';\n\n/** Start writing a backup. Naming the app via .as() is the required next step. */\nexport function backup(snapshot: Snapshot): BackupDraft {\n\treturn new BackupDraft(snapshot);\n}\n\n/** Start reading a backup (a picked File, a fetched Blob, or raw bytes). */\nexport function restore(input: Blob | Uint8Array): RestoreBuilder {\n\treturn new RestoreBuilder(input);\n}\n\n// Validation lives in the constructor, not the factory, so direct\n// construction cannot bypass it.\nexport class BackupDraft {\n\tconstructor(private readonly snapshot: Snapshot) {\n\t\tfor (const name of Object.keys(snapshot.collections)) {\n\t\t\tif (name.startsWith(RESERVED_COLLECTION_PREFIX)) {\n\t\t\t\tthrow new TypeError(\n\t\t\t\t\t`backup(): collection \"${name}\" uses the reserved \"${RESERVED_COLLECTION_PREFIX}\" prefix (library bookkeeping) - rename it`\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t}\n\n\t/** Name the writing app (stored cleartext in the backup's metadata). */\n\tas(app: string, appVersion?: string): BackupBuilder {\n\t\treturn new BackupBuilder(this.snapshot, { app, appVersion });\n\t}\n}\n\nexport class BackupBuilder {\n\tconstructor(\n\t\tprotected readonly snapshot: Snapshot,\n\t\tprotected readonly opts: EncodeOptions\n\t) {}\n\n\t/** Encrypt the backup (AES-256-GCM over an Argon2id-derived key). */\n\tencryptedWith(password: string): EncryptedBackupBuilder {\n\t\treturn new EncryptedBackupBuilder(this.snapshot, { ...this.opts, password });\n\t}\n\n\t/**\n\t * Read the backup back before handing it over, and throw VERIFY_FAILED if it\n\t * does not hold what went in.\n\t *\n\t * A backup encrypted with a key nobody can reproduce, truncated, or built\n\t * from an empty snapshot looks exactly like a good one: right name, right\n\t * date, plausible size. The difference only shows up on the day of the\n\t * disaster, when it is too late to make another. The only way to know is to\n\t * open it, and it costs one decrypt of data the app already holds.\n\t *\n\t * Applies to every terminal below, so a host cannot verify and then write a\n\t * different set of bytes by accident.\n\t */\n\tverified(): BackupBuilder {\n\t\treturn new BackupBuilder(this.snapshot, { ...this.opts, verify: true });\n\t}\n\n\t/** The backup as raw bytes. */\n\tasync toBytes(): Promise<Uint8Array> {\n\t\tconst bytes = await writeBox(this.snapshot, this.opts);\n\t\tif (this.opts.verify) {\n\t\t\tawait verifyBackup(bytes, { password: this.opts.password, expect: countsOf(this.snapshot) });\n\t\t}\n\t\treturn bytes;\n\t}\n\n\t/** The backup as a Blob, ready to upload or hand to a save dialog. */\n\tasync toBlob(): Promise<Blob> {\n\t\treturn new Blob([(await this.toBytes()) as BlobPart], { type: BACKUP_MIME });\n\t}\n\n\t/** Save the backup to disk (File System Access API, else a download).\n\t * Browser-only terminal; defaults to `<app>-<date>.zip`. False when the\n\t * user closed the save dialog: nothing was written, so do not tell them\n\t * they have a backup.\n\t *\n\t * Asks WHERE first, encrypts after. The save dialog needs the transient\n\t * activation of the click that led here, and building an encrypted backup\n\t * outlives it (Argon2id is deliberately slow): encrypting first made the\n\t * browser refuse the dialog and silently download instead, turning a file\n\t * the user chose into one they have to go and find. */\n\tasync toDisk(filename?: string): Promise<boolean> {\n\t\tconst name =\n\t\t\tfilename ?? `${this.opts.app}-${new Date().toISOString().slice(0, 10)}${BACKUP_EXTENSION}`;\n\t\tconst { pickSaveHandle, writeToHandle, downloadBlob } = await import('./targets/local');\n\t\tconst cible = await pickSaveHandle(name);\n\t\tif (cible === 'CANCELLED') return false;\n\t\tconst blob = await this.toBlob();\n\t\tif (cible) {\n\t\t\tawait writeToHandle(cible.handle, blob);\n\t\t\treturn true;\n\t\t}\n\t\tdownloadBlob(blob, name);\n\t\treturn true;\n\t}\n}\n\nexport class EncryptedBackupBuilder extends BackupBuilder {\n\tconstructor(snapshot: Snapshot, opts: EncodeOptions) {\n\t\tif (!opts.password) {\n\t\t\tthrow new TypeError(\n\t\t\t\t'EncryptedBackupBuilder: a password is required - use backup(snapshot).as(app).encryptedWith(password)'\n\t\t\t);\n\t\t}\n\t\tsuper(snapshot, opts);\n\t}\n\n\t/** Same guarantee, keeping the encrypted chain's own links reachable. */\n\toverride verified(): EncryptedBackupBuilder {\n\t\treturn new EncryptedBackupBuilder(this.snapshot, { ...this.opts, verify: true });\n\t}\n\n\t/** Brand the README shipped inside the encrypted ZIP. */\n\twithReadme(text: string): EncryptedBackupBuilder {\n\t\treturn new EncryptedBackupBuilder(this.snapshot, { ...this.opts, readme: text });\n\t}\n\n\t/**\n\t * A second secret that also opens this backup - a recovery code the user\n\t * printed and put away.\n\t *\n\t * A password that only lives in one person's memory is the likeliest way a\n\t * local-first backup dies: no server can reset it, so the day it is\n\t * forgotten the data is gone for good. The envelope has always held several\n\t * key slots (see `keys[]` in the header); this exposes them where a backup\n\t * is actually written. Each secret wraps the SAME data key, so either one\n\t * opens the file and neither can read the other.\n\t *\n\t * Call it more than once for more than one recovery secret. Reading needs no\n\t * change: `restore(...).withPassword(code)` already tries every slot.\n\t */\n\talsoOpenedWith(secret: string): EncryptedBackupBuilder {\n\t\tif (!secret) {\n\t\t\tthrow new TypeError('alsoOpenedWith(): a non-empty secret is required.');\n\t\t}\n\t\treturn new EncryptedBackupBuilder(this.snapshot, {\n\t\t\t...this.opts,\n\t\t\textraSecrets: [...(this.opts.extraSecrets ?? []), secret]\n\t\t});\n\t}\n}\n\nexport class RestoreBuilder {\n\tprivate password?: string;\n\n\tconstructor(private readonly input: Blob | Uint8Array) {}\n\n\t/** Accepts undefined so callers can pass straight through from an optional field. */\n\twithPassword(password?: string): this {\n\t\tthis.password = password || undefined;\n\t\treturn this;\n\t}\n\n\t/** The cleartext metadata (app, date, encryption), without decrypting. */\n\tasync meta(): Promise<Header> {\n\t\treturn readBoxMeta(await asBytes(this.input));\n\t}\n\n\t/** True if reading will require a password. */\n\tasync isEncrypted(): Promise<boolean> {\n\t\treturn (await this.meta()).encryption !== 'none';\n\t}\n\n\t/** Read the backup into a snapshot. Reserved `__*` collections (library\n\t * bookkeeping) are stripped. PASSWORD_REQUIRED when encrypted and no\n\t * password given, DECRYPT_FAILED on a wrong one. */\n\tasync read(): Promise<Snapshot> {\n\t\tconst snap = await readBox(await asBytes(this.input), this.password);\n\t\tconst collections = Object.fromEntries(\n\t\t\tObject.entries(snap.collections).filter(\n\t\t\t\t([name]) => !name.startsWith(RESERVED_COLLECTION_PREFIX)\n\t\t\t)\n\t\t);\n\t\treturn { collections, files: snap.files };\n\t}\n}\n"]}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// src/simple/bytes.ts
|
|
2
|
+
async function toBytes(input) {
|
|
3
|
+
if (input instanceof Uint8Array) return input;
|
|
4
|
+
if (input instanceof ArrayBuffer) return new Uint8Array(input);
|
|
5
|
+
return new Uint8Array(await input.arrayBuffer());
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export { toBytes };
|
|
9
|
+
//# sourceMappingURL=chunk-SJ3PKQPR.js.map
|
|
10
|
+
//# sourceMappingURL=chunk-SJ3PKQPR.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/simple/bytes.ts"],"names":[],"mappings":";AAQA,eAAsB,QAAQ,KAAA,EAAuC;AACpE,EAAA,IAAI,KAAA,YAAiB,YAAY,OAAO,KAAA;AACxC,EAAA,IAAI,KAAA,YAAiB,WAAA,EAAa,OAAO,IAAI,WAAW,KAAK,CAAA;AAC7D,EAAA,OAAO,IAAI,UAAA,CAAW,MAAM,KAAA,CAAM,aAAa,CAAA;AAChD","file":"chunk-SJ3PKQPR.js","sourcesContent":["// A leaf on purpose: the proof entry needs these two and must not pull the\n// whole store graph in with them.\n\n/** What a file body may be handed in as. */\nexport type FileBytes = Uint8Array | ArrayBuffer | Blob;\n\n/** Blob and ArrayBuffer are accepted because that is what a picker, a fetch and\n * a canvas hand back; the snapshot only ever holds Uint8Array. */\nexport async function toBytes(input: FileBytes): Promise<Uint8Array> {\n\tif (input instanceof Uint8Array) return input;\n\tif (input instanceof ArrayBuffer) return new Uint8Array(input);\n\treturn new Uint8Array(await input.arrayBuffer());\n}\n"]}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// src/selfstore/base64.ts
|
|
2
|
+
function toBase64(bytes) {
|
|
3
|
+
let bin = "";
|
|
4
|
+
for (const b of bytes) bin += String.fromCharCode(b);
|
|
5
|
+
return btoa(bin);
|
|
6
|
+
}
|
|
7
|
+
function fromBase64(s) {
|
|
8
|
+
const bin = atob(s);
|
|
9
|
+
const out = new Uint8Array(bin.length);
|
|
10
|
+
for (let i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i);
|
|
11
|
+
return out;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export { fromBase64, toBase64 };
|
|
15
|
+
//# sourceMappingURL=chunk-WXI2NAPA.js.map
|
|
16
|
+
//# sourceMappingURL=chunk-WXI2NAPA.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/selfstore/base64.ts"],"names":[],"mappings":";AAKO,SAAS,SAAS,KAAA,EAA2B;AACnD,EAAA,IAAI,GAAA,GAAM,EAAA;AACV,EAAA,KAAA,MAAW,CAAA,IAAK,KAAA,EAAO,GAAA,IAAO,MAAA,CAAO,aAAa,CAAC,CAAA;AACnD,EAAA,OAAO,KAAK,GAAG,CAAA;AAChB;AAEO,SAAS,WAAW,CAAA,EAAuB;AACjD,EAAA,MAAM,GAAA,GAAM,KAAK,CAAC,CAAA;AAClB,EAAA,MAAM,GAAA,GAAM,IAAI,UAAA,CAAW,GAAA,CAAI,MAAM,CAAA;AACrC,EAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,GAAA,CAAI,MAAA,EAAQ,CAAA,EAAA,EAAK,GAAA,CAAI,CAAC,CAAA,GAAI,GAAA,CAAI,UAAA,CAAW,CAAC,CAAA;AAC9D,EAAA,OAAO,GAAA;AACR","file":"chunk-WXI2NAPA.js","sourcesContent":["/**\n * Base64 <-> bytes, shared by the crypto layer and the KDF worker (the worker\n * bundle must not drag the whole crypto module in for two helpers).\n */\n\nexport function toBase64(bytes: Uint8Array): string {\n\tlet bin = '';\n\tfor (const b of bytes) bin += String.fromCharCode(b);\n\treturn btoa(bin);\n}\n\nexport function fromBase64(s: string): Uint8Array {\n\tconst bin = atob(s);\n\tconst out = new Uint8Array(bin.length);\n\tfor (let i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i);\n\treturn out;\n}\n"]}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { S as StoreError, B as BackupTarget, L as LocalStore } from './store-
|
|
1
|
+
import { S as StoreError, B as BackupTarget, L as LocalStore } from './store-CKGrIFQU.js';
|
|
2
2
|
import { K as KV } from './cache-ilsK3Tki.js';
|
|
3
|
-
import { a as DriveAuth } from './drive
|
|
4
|
-
import { W as WebdavConfig, S as S3Config } from './s3-
|
|
3
|
+
import { a as DriveAuth } from './drive--mndByvF.js';
|
|
4
|
+
import { W as WebdavConfig, S as S3Config } from './s3-dG1SCnV1.js';
|
|
5
5
|
import { F as FlowStore } from './machine-ALMkT3mk.js';
|
|
6
6
|
|
|
7
7
|
type ConnectKind = 'drive' | 'file' | 'webdav' | 's3';
|
package/dist/flows.d.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { b as ConnectKind, S as StoreLike, C as ConnectTargets } from './connect-
|
|
2
|
-
export { c as ConnectFlow, a as ConnectFlowOptions, d as ConnectFlowOutcome, e as ConnectResolution, f as ConnectSnapshot, g as ConnectStep, h as Connector, F as FlowHost, R as ResumeOffer, i as connectFlow } from './connect-
|
|
3
|
-
import { S as StoreError,
|
|
1
|
+
import { b as ConnectKind, S as StoreLike, C as ConnectTargets } from './connect-DoB_DN__.js';
|
|
2
|
+
export { c as ConnectFlow, a as ConnectFlowOptions, d as ConnectFlowOutcome, e as ConnectResolution, f as ConnectSnapshot, g as ConnectStep, h as Connector, F as FlowHost, R as ResumeOffer, i as connectFlow } from './connect-DoB_DN__.js';
|
|
3
|
+
import { S as StoreError, n as ReplicaState, B as BackupTarget } from './store-CKGrIFQU.js';
|
|
4
4
|
import { F as FlowStore } from './machine-ALMkT3mk.js';
|
|
5
5
|
export { w as withDeadline } from './machine-ALMkT3mk.js';
|
|
6
|
-
import { W as WebdavConfig, S as S3Config } from './s3-
|
|
6
|
+
import { W as WebdavConfig, S as S3Config } from './s3-dG1SCnV1.js';
|
|
7
7
|
import './cache-ilsK3Tki.js';
|
|
8
|
-
import './drive
|
|
8
|
+
import './drive--mndByvF.js';
|
|
9
9
|
import './sync.js';
|
|
10
10
|
|
|
11
11
|
/** The one replica this flow manages, as its id on the engine. */
|
package/dist/flows.js
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
|
-
export { connectFlow, joinFlow, shareFlow } from './chunk-
|
|
2
|
-
import { s3Connect, webdavConnect, webdavFromSession, s3FromSession } from './chunk-
|
|
3
|
-
import { connect as connect$1, fromSession as fromSession$1 } from './chunk-
|
|
4
|
-
import { makeMachine, toStoreError } from './chunk-
|
|
5
|
-
export { withDeadline } from './chunk-
|
|
6
|
-
import { connect, fromSession } from './chunk-
|
|
7
|
-
import './chunk-
|
|
8
|
-
import './chunk-
|
|
9
|
-
import './chunk-
|
|
10
|
-
import './chunk-
|
|
11
|
-
import './chunk-
|
|
1
|
+
export { connectFlow, joinFlow, shareFlow } from './chunk-2PIYIIHY.js';
|
|
2
|
+
import { s3Connect, webdavConnect, webdavFromSession, s3FromSession } from './chunk-FQ5O4PHK.js';
|
|
3
|
+
import { connect as connect$1, fromSession as fromSession$1 } from './chunk-2TIRUEXR.js';
|
|
4
|
+
import { makeMachine, toStoreError } from './chunk-2BNTCXQN.js';
|
|
5
|
+
export { withDeadline } from './chunk-2BNTCXQN.js';
|
|
6
|
+
import { connect, fromSession } from './chunk-25U6AKIX.js';
|
|
7
|
+
import './chunk-LF3CC2P3.js';
|
|
8
|
+
import './chunk-RVOBONZF.js';
|
|
9
|
+
import './chunk-F4ZZEJ5H.js';
|
|
10
|
+
import './chunk-5UYPKCDB.js';
|
|
11
|
+
import './chunk-WXI2NAPA.js';
|
|
12
|
+
import './chunk-CSYEIXNY.js';
|
|
12
13
|
|
|
13
14
|
// src/flows/replica.ts
|
|
14
15
|
var REPLICA_ID = "replica";
|
package/dist/flows.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/flows/replica.ts"],"names":["connect","config","fromSession"],"mappings":"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","file":"flows.js","sourcesContent":["// The backup-copy journey: attach ONE replica (a second destination receiving\n// the same encrypted backup as the primary home) behind a small state machine\n// the widgets can render. Reuses the connect flow's target specs, but there is\n// no password step, no conflict step and no deadline: a replica carries the\n// primary's bytes as-is, and every connector may contain a human gesture.\n\nimport type { BackupTarget } from '../persistence/target';\nimport type { KV } from '../persistence/cache';\nimport type { ReplicaState, StoreError } from '../persistence/store';\nimport { makeMachine, toStoreError, type FlowStore } from './machine';\nimport type { ConnectKind, ConnectTargets, Connector, StoreLike } from './connect';\nimport {\n\tconnect as driveConnect,\n\tfromSession as driveFromSession,\n\ttype DriveAuth\n} from '../persistence/targets/drive';\nimport {\n\tconnect as fileConnect,\n\tfromSession as fileFromSession\n} from '../persistence/targets/file';\n// WebDAV and S3 arrive on the gesture that uses them - see targets/servers.ts.\nimport {\n\twebdavConnect,\n\twebdavFromSession,\n\ts3Connect,\n\ts3FromSession\n} from '../persistence/targets/servers';\nimport type { WebdavConfig } from '../persistence/targets/webdav';\nimport type { S3Config } from '../persistence/targets/s3';\n\n/** The one replica this flow manages, as its id on the engine. */\nexport const REPLICA_ID = 'replica';\n\n// The replica's target session lives in the SAME kv as the primary's, so every\n// key is prefixed: a second driveTarget (say) must never read or clobber the\n// primary connection's session keys.\nconst SCOPE = 'replica:';\nconst RECORD_KEY = 'record';\n\n/** What survives a reboot: which provider the copy lives on. The target's own\n * session (file id, handle, server config) is persisted by the target module\n * itself, under the scoped kv. */\ninterface ReplicaRecord {\n\tkind: ConnectKind;\n}\n\nexport type ReplicaStep = 'idle' | 'choose' | 'form-webdav' | 'form-s3';\n\nexport interface ReplicaSnapshot {\n\tstep: ReplicaStep;\n\t/** The kinds the host offers, in ConnectTargets order. */\n\tkinds: ConnectKind[];\n\tbusy: boolean;\n\terror: StoreError | null;\n\t/** The live engine entry (label, lastPublishAt, lastError), null when none. */\n\treplica: ReplicaState | null;\n}\n\nexport interface ReplicaFlowOptions {\n\t/** File name of the copy on its destination. Default: the primary backup\n\t * name with `.replica` before the extension (\"App.zip\" -> \"App.replica.zip\"),\n\t * which a same-account Drive never confuses with the primary file and the\n\t * backups panel never lists as a backup of its own. */\n\tfileName?: string;\n\t/** Rebuild the target of a custom Connector kind on restore() (built-in\n\t * kinds rebuild by themselves). Without it, custom kinds are not restored. */\n\trestoreTarget?: (kind: ConnectKind) => Promise<BackupTarget | null>;\n}\n\nexport interface ReplicaFlow extends FlowStore<ReplicaSnapshot> {\n\t/** Show the destination choice (from the user's gesture). */\n\topen(): void;\n\t/** Back to idle; never an error. */\n\tcancel(): void;\n\t/** Run a destination (inside the user's click - pickers and consent popups\n\t * open from it). `webdav: true` / `s3: true` route to their form step. */\n\tpick(kind: ConnectKind): void;\n\t/** 'form-webdav' step: submit the server config. */\n\tsubmitWebdav(config: WebdavConfig): void;\n\t/** 'form-s3' step: submit the bucket config. */\n\tsubmitS3(config: S3Config): void;\n\t/** Detach the copy and forget its session. Never deletes remote data. */\n\tremove(): Promise<void>;\n\t/** Silent reboot: re-attach the copy recorded by a past session, if any.\n\t * Never throws - an unreachable destination retries at the next boot. */\n\trestore(): Promise<void>;\n\t/** Detach from the engine (the replica itself stays attached). */\n\tdispose(): void;\n}\n\nfunction scopedKv(kv: KV, prefix: string): KV {\n\treturn {\n\t\tget: <T = unknown>(key: string) => kv.get<T>(prefix + key),\n\t\tset: (key: string, value: unknown) => kv.set(prefix + key, value),\n\t\tdel: (key: string) => kv.del(prefix + key)\n\t};\n}\n\nfunction replicaFileName(backupName: string): string {\n\tconst dot = backupName.lastIndexOf('.');\n\treturn dot > 0\n\t\t? `${backupName.slice(0, dot)}.replica${backupName.slice(dot)}`\n\t\t: `${backupName}.replica`;\n}\n\nexport function replicaFlow(\n\tstore: StoreLike,\n\ttargets: ConnectTargets,\n\toptions: ReplicaFlowOptions = {}\n): ReplicaFlow {\n\tconst host = 'flowHost' in store ? store.flowHost : store;\n\tconst engine = host.engine;\n\tconst skv = scopedKv(host.kv, SCOPE);\n\tconst fileName = options.fileName ?? replicaFileName(host.backupName);\n\tconst kinds = (Object.keys(targets) as ConnectKind[]).filter(\n\t\t(k) => (k === 'drive' || k === 'file' || k === 'webdav' || k === 's3') && targets[k] != null\n\t);\n\n\tconst current = (): ReplicaState | null =>\n\t\tengine.state.replicas.find((r) => r.id === REPLICA_ID) ?? null;\n\n\tconst m = makeMachine<ReplicaSnapshot>({\n\t\tstep: 'idle',\n\t\tkinds,\n\t\tbusy: false,\n\t\terror: null,\n\t\treplica: current()\n\t});\n\n\t// Follow the engine: every publish (or publish failure) refreshes the entry.\n\tconst unsub = engine.subscribe(() => m.set({ replica: current() }));\n\n\t// Held for remove(): disconnect() lets the target forget its own session.\n\tlet attached: BackupTarget | null = null;\n\t// Its kind, so a swap can tell 'moved to another provider' from 'same\n\t// provider, another file' - only the first may disconnect. See run().\n\tlet attachedKind: ConnectKind | null = null;\n\n\tfunction connectorFor(kind: ConnectKind): Connector | null {\n\t\tconst spec = targets[kind];\n\t\tif (spec == null) return null;\n\t\tif (typeof spec === 'function') return spec;\n\t\tif (kind === 'drive') {\n\t\t\tconst auth = spec as DriveAuth;\n\t\t\treturn () => driveConnect({ auth, kv: skv, fileName });\n\t\t}\n\t\tif (kind === 'file') {\n\t\t\treturn () => fileConnect({ kv: skv, fileName });\n\t\t}\n\t\tif (kind === 's3') {\n\t\t\tconst config = spec as S3Config; // 's3: true' goes through the form step\n\t\t\treturn () => s3Connect({ kv: skv, config });\n\t\t}\n\t\tconst config = spec as WebdavConfig; // 'webdav: true' goes through the form step\n\t\treturn () => webdavConnect({ kv: skv, config });\n\t}\n\n\tasync function run(kind: ConnectKind, connector: Connector): Promise<void> {\n\t\tm.set({ busy: true, error: null });\n\t\ttry {\n\t\t\tconst target = await connector();\n\t\t\tif (!target) {\n\t\t\t\t// Cancelled picker/consent: stay where the user was, no error.\n\t\t\t\tm.set({ busy: false });\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (current()) engine.detachReplica(REPLICA_ID); // swap, never a dup-id throw\n\t\t\tconst left = attachedKind === kind ? null : attached;\n\t\t\tengine.attachReplica(target, { id: REPLICA_ID });\n\t\t\tattached = target;\n\t\t\tattachedKind = kind;\n\t\t\tawait skv.set(RECORD_KEY, { kind } satisfies ReplicaRecord);\n\t\t\t// The destination the user just left forgets its own session.\n\t\t\t// detachReplica only drops the engine entry, so without this a swap\n\t\t\t// leaves the previous provider's credentials on the device for good -\n\t\t\t// the one thing a copy the user moved away from must not keep.\n\t\t\t// Skipped when the kind is unchanged: the kv keys are per kind, the new\n\t\t\t// connect has just overwritten them, and only drive guards a late\n\t\t\t// disconnect against orphaning what the new target now owns.\n\t\t\tawait left?.disconnect().catch(() => undefined);\n\t\t\tm.set({ step: 'idle', busy: false, error: null, replica: current() });\n\t\t} catch (e) {\n\t\t\tm.set({ busy: false, error: toStoreError(e) });\n\t\t}\n\t}\n\n\tasync function builtinFromSession(kind: ConnectKind): Promise<BackupTarget | null> {\n\t\tif (kind === 'drive') {\n\t\t\tconst spec = targets.drive;\n\t\t\tif (spec == null || typeof spec === 'function') return null;\n\t\t\treturn driveFromSession({ auth: spec, kv: skv, fileName });\n\t\t}\n\t\tif (kind === 'file') return fileFromSession({ kv: skv });\n\t\tif (kind === 'webdav') return webdavFromSession({ kv: skv });\n\t\treturn s3FromSession({ kv: skv });\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\topen() {\n\t\t\tif (m.snapshot.busy) return;\n\t\t\tm.set({ step: 'choose', error: null });\n\t\t},\n\n\t\tcancel() {\n\t\t\tm.set({ step: 'idle', busy: false, error: null });\n\t\t},\n\n\t\tpick(kind) {\n\t\t\tif (m.snapshot.busy) return;\n\t\t\tconst spec = targets[kind];\n\t\t\tif (spec == null) return;\n\t\t\tif (kind === 'webdav' && spec === true) {\n\t\t\t\tm.set({ step: 'form-webdav', error: null });\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (kind === 's3' && spec === true) {\n\t\t\t\tm.set({ step: 'form-s3', error: null });\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst connector = connectorFor(kind);\n\t\t\tif (connector) void run(kind, connector);\n\t\t},\n\n\t\tsubmitWebdav(config) {\n\t\t\tif (m.snapshot.busy) return;\n\t\t\tvoid run('webdav', () => webdavConnect({ kv: skv, config }));\n\t\t},\n\n\t\tsubmitS3(config) {\n\t\t\tif (m.snapshot.busy) return;\n\t\t\tvoid run('s3', () => s3Connect({ kv: skv, config }));\n\t\t},\n\n\t\tasync remove() {\n\t\t\tconst target = attached;\n\t\t\tattached = null;\n\t\t\tattachedKind = null;\n\t\t\tif (current()) engine.detachReplica(REPLICA_ID);\n\t\t\tawait skv.del(RECORD_KEY);\n\t\t\t// Forget locally only - a BackupTarget's disconnect never deletes\n\t\t\t// remote data, so the copy stays where it is for a manual recovery.\n\t\t\tawait target?.disconnect().catch(() => undefined);\n\t\t\tm.set({ step: 'idle', busy: false, error: null, replica: current() });\n\t\t},\n\n\t\tasync restore() {\n\t\t\ttry {\n\t\t\t\tif (current()) return;\n\t\t\t\tconst record = await skv.get<ReplicaRecord>(RECORD_KEY);\n\t\t\t\tif (!record) return;\n\t\t\t\tconst target =\n\t\t\t\t\t(await options.restoreTarget?.(record.kind)) ?? (await builtinFromSession(record.kind));\n\t\t\t\tif (!target) return; // unreachable or custom kind: retry next boot\n\t\t\t\tengine.attachReplica(target, { id: REPLICA_ID });\n\t\t\t\tattached = target;\n\t\t\t\tattachedKind = record.kind;\n\t\t\t\tm.set({ replica: current() });\n\t\t\t} catch {\n\t\t\t\t// Silent reboot by contract: the record stays, the next boot retries.\n\t\t\t}\n\t\t},\n\n\t\tdispose() {\n\t\t\tunsub();\n\t\t}\n\t};\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/flows/replica.ts"],"names":["connect","config","fromSession"],"mappings":"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","file":"flows.js","sourcesContent":["// The backup-copy journey: attach ONE replica (a second destination receiving\n// the same encrypted backup as the primary home) behind a small state machine\n// the widgets can render. Reuses the connect flow's target specs, but there is\n// no password step, no conflict step and no deadline: a replica carries the\n// primary's bytes as-is, and every connector may contain a human gesture.\n\nimport type { BackupTarget } from '../persistence/target';\nimport type { KV } from '../persistence/cache';\nimport type { ReplicaState, StoreError } from '../persistence/store';\nimport { makeMachine, toStoreError, type FlowStore } from './machine';\nimport type { ConnectKind, ConnectTargets, Connector, StoreLike } from './connect';\nimport {\n\tconnect as driveConnect,\n\tfromSession as driveFromSession,\n\ttype DriveAuth\n} from '../persistence/targets/drive';\nimport {\n\tconnect as fileConnect,\n\tfromSession as fileFromSession\n} from '../persistence/targets/file';\n// WebDAV and S3 arrive on the gesture that uses them - see targets/servers.ts.\nimport {\n\twebdavConnect,\n\twebdavFromSession,\n\ts3Connect,\n\ts3FromSession\n} from '../persistence/targets/servers';\nimport type { WebdavConfig } from '../persistence/targets/webdav';\nimport type { S3Config } from '../persistence/targets/s3';\n\n/** The one replica this flow manages, as its id on the engine. */\nexport const REPLICA_ID = 'replica';\n\n// The replica's target session lives in the SAME kv as the primary's, so every\n// key is prefixed: a second driveTarget (say) must never read or clobber the\n// primary connection's session keys.\nconst SCOPE = 'replica:';\nconst RECORD_KEY = 'record';\n\n/** What survives a reboot: which provider the copy lives on. The target's own\n * session (file id, handle, server config) is persisted by the target module\n * itself, under the scoped kv. */\ninterface ReplicaRecord {\n\tkind: ConnectKind;\n}\n\nexport type ReplicaStep = 'idle' | 'choose' | 'form-webdav' | 'form-s3';\n\nexport interface ReplicaSnapshot {\n\tstep: ReplicaStep;\n\t/** The kinds the host offers, in ConnectTargets order. */\n\tkinds: ConnectKind[];\n\tbusy: boolean;\n\terror: StoreError | null;\n\t/** The live engine entry (label, lastPublishAt, lastError), null when none. */\n\treplica: ReplicaState | null;\n}\n\nexport interface ReplicaFlowOptions {\n\t/** File name of the copy on its destination. Default: the primary backup\n\t * name with `.replica` before the extension (\"App.zip\" -> \"App.replica.zip\"),\n\t * which a same-account Drive never confuses with the primary file and the\n\t * backups panel never lists as a backup of its own. */\n\tfileName?: string;\n\t/** Rebuild the target of a custom Connector kind on restore() (built-in\n\t * kinds rebuild by themselves). Without it, custom kinds are not restored. */\n\trestoreTarget?: (kind: ConnectKind) => Promise<BackupTarget | null>;\n}\n\nexport interface ReplicaFlow extends FlowStore<ReplicaSnapshot> {\n\t/** Show the destination choice (from the user's gesture). */\n\topen(): void;\n\t/** Back to idle; never an error. */\n\tcancel(): void;\n\t/** Run a destination (inside the user's click - pickers and consent popups\n\t * open from it). `webdav: true` / `s3: true` route to their form step. */\n\tpick(kind: ConnectKind): void;\n\t/** 'form-webdav' step: submit the server config. */\n\tsubmitWebdav(config: WebdavConfig): void;\n\t/** 'form-s3' step: submit the bucket config. */\n\tsubmitS3(config: S3Config): void;\n\t/** Detach the copy and forget its session. Never deletes remote data. */\n\tremove(): Promise<void>;\n\t/** Silent reboot: re-attach the copy recorded by a past session, if any.\n\t * Never throws - an unreachable destination retries at the next boot. */\n\trestore(): Promise<void>;\n\t/** Detach from the engine (the replica itself stays attached). */\n\tdispose(): void;\n}\n\nfunction scopedKv(kv: KV, prefix: string): KV {\n\treturn {\n\t\tget: <T = unknown>(key: string) => kv.get<T>(prefix + key),\n\t\tset: (key: string, value: unknown) => kv.set(prefix + key, value),\n\t\tdel: (key: string) => kv.del(prefix + key)\n\t};\n}\n\nfunction replicaFileName(backupName: string): string {\n\tconst dot = backupName.lastIndexOf('.');\n\treturn dot > 0\n\t\t? `${backupName.slice(0, dot)}.replica${backupName.slice(dot)}`\n\t\t: `${backupName}.replica`;\n}\n\nexport function replicaFlow(\n\tstore: StoreLike,\n\ttargets: ConnectTargets,\n\toptions: ReplicaFlowOptions = {}\n): ReplicaFlow {\n\tconst host = 'flowHost' in store ? store.flowHost : store;\n\tconst engine = host.engine;\n\tconst skv = scopedKv(host.kv, SCOPE);\n\tconst fileName = options.fileName ?? replicaFileName(host.backupName);\n\tconst kinds = (Object.keys(targets) as ConnectKind[]).filter(\n\t\t(k) => (k === 'drive' || k === 'file' || k === 'webdav' || k === 's3') && targets[k] != null\n\t);\n\n\tconst current = (): ReplicaState | null =>\n\t\tengine.state.replicas.find((r) => r.id === REPLICA_ID) ?? null;\n\n\tconst m = makeMachine<ReplicaSnapshot>({\n\t\tstep: 'idle',\n\t\tkinds,\n\t\tbusy: false,\n\t\terror: null,\n\t\treplica: current()\n\t});\n\n\t// Follow the engine: every publish (or publish failure) refreshes the entry.\n\tconst unsub = engine.subscribe(() => m.set({ replica: current() }));\n\n\t// Held for remove(): disconnect() lets the target forget its own session.\n\tlet attached: BackupTarget | null = null;\n\t// Its kind, so a swap can tell 'moved to another provider' from 'same\n\t// provider, another file' - only the first may disconnect. See run().\n\tlet attachedKind: ConnectKind | null = null;\n\n\tfunction connectorFor(kind: ConnectKind): Connector | null {\n\t\tconst spec = targets[kind];\n\t\tif (spec == null) return null;\n\t\tif (typeof spec === 'function') return spec;\n\t\tif (kind === 'drive') {\n\t\t\tconst auth = spec as DriveAuth;\n\t\t\treturn () => driveConnect({ auth, kv: skv, fileName });\n\t\t}\n\t\tif (kind === 'file') {\n\t\t\treturn () => fileConnect({ kv: skv, fileName });\n\t\t}\n\t\tif (kind === 's3') {\n\t\t\tconst config = spec as S3Config; // 's3: true' goes through the form step\n\t\t\treturn () => s3Connect({ kv: skv, config });\n\t\t}\n\t\tconst config = spec as WebdavConfig; // 'webdav: true' goes through the form step\n\t\treturn () => webdavConnect({ kv: skv, config });\n\t}\n\n\tasync function run(kind: ConnectKind, connector: Connector): Promise<void> {\n\t\tm.set({ busy: true, error: null });\n\t\ttry {\n\t\t\tconst target = await connector();\n\t\t\tif (!target) {\n\t\t\t\t// Cancelled picker/consent: stay where the user was, no error.\n\t\t\t\tm.set({ busy: false });\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (current()) engine.detachReplica(REPLICA_ID); // swap, never a dup-id throw\n\t\t\tconst left = attachedKind === kind ? null : attached;\n\t\t\tengine.attachReplica(target, { id: REPLICA_ID });\n\t\t\tattached = target;\n\t\t\tattachedKind = kind;\n\t\t\tawait skv.set(RECORD_KEY, { kind } satisfies ReplicaRecord);\n\t\t\t// The destination the user just left forgets its own session.\n\t\t\t// detachReplica only drops the engine entry, so without this a swap\n\t\t\t// leaves the previous provider's credentials on the device for good -\n\t\t\t// the one thing a copy the user moved away from must not keep.\n\t\t\t// Skipped when the kind is unchanged: the kv keys are per kind, the new\n\t\t\t// connect has just overwritten them, and only drive guards a late\n\t\t\t// disconnect against orphaning what the new target now owns.\n\t\t\tawait left?.disconnect().catch(() => undefined);\n\t\t\tm.set({ step: 'idle', busy: false, error: null, replica: current() });\n\t\t} catch (e) {\n\t\t\tm.set({ busy: false, error: toStoreError(e) });\n\t\t}\n\t}\n\n\tasync function builtinFromSession(kind: ConnectKind): Promise<BackupTarget | null> {\n\t\tif (kind === 'drive') {\n\t\t\tconst spec = targets.drive;\n\t\t\tif (spec == null || typeof spec === 'function') return null;\n\t\t\treturn driveFromSession({ auth: spec, kv: skv, fileName });\n\t\t}\n\t\tif (kind === 'file') return fileFromSession({ kv: skv });\n\t\tif (kind === 'webdav') return webdavFromSession({ kv: skv });\n\t\treturn s3FromSession({ kv: skv });\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\topen() {\n\t\t\tif (m.snapshot.busy) return;\n\t\t\tm.set({ step: 'choose', error: null });\n\t\t},\n\n\t\tcancel() {\n\t\t\tm.set({ step: 'idle', busy: false, error: null });\n\t\t},\n\n\t\tpick(kind) {\n\t\t\tif (m.snapshot.busy) return;\n\t\t\tconst spec = targets[kind];\n\t\t\tif (spec == null) return;\n\t\t\tif (kind === 'webdav' && spec === true) {\n\t\t\t\tm.set({ step: 'form-webdav', error: null });\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (kind === 's3' && spec === true) {\n\t\t\t\tm.set({ step: 'form-s3', error: null });\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst connector = connectorFor(kind);\n\t\t\tif (connector) void run(kind, connector);\n\t\t},\n\n\t\tsubmitWebdav(config) {\n\t\t\tif (m.snapshot.busy) return;\n\t\t\tvoid run('webdav', () => webdavConnect({ kv: skv, config }));\n\t\t},\n\n\t\tsubmitS3(config) {\n\t\t\tif (m.snapshot.busy) return;\n\t\t\tvoid run('s3', () => s3Connect({ kv: skv, config }));\n\t\t},\n\n\t\tasync remove() {\n\t\t\tconst target = attached;\n\t\t\tattached = null;\n\t\t\tattachedKind = null;\n\t\t\tif (current()) engine.detachReplica(REPLICA_ID);\n\t\t\tawait skv.del(RECORD_KEY);\n\t\t\t// Forget locally only - a BackupTarget's disconnect never deletes\n\t\t\t// remote data, so the copy stays where it is for a manual recovery.\n\t\t\tawait target?.disconnect().catch(() => undefined);\n\t\t\tm.set({ step: 'idle', busy: false, error: null, replica: current() });\n\t\t},\n\n\t\tasync restore() {\n\t\t\ttry {\n\t\t\t\tif (current()) return;\n\t\t\t\tconst record = await skv.get<ReplicaRecord>(RECORD_KEY);\n\t\t\t\tif (!record) return;\n\t\t\t\tconst target =\n\t\t\t\t\t(await options.restoreTarget?.(record.kind)) ?? (await builtinFromSession(record.kind));\n\t\t\t\tif (!target) return; // unreachable or custom kind: retry next boot\n\t\t\t\tengine.attachReplica(target, { id: REPLICA_ID });\n\t\t\t\tattached = target;\n\t\t\t\tattachedKind = record.kind;\n\t\t\t\tm.set({ replica: current() });\n\t\t\t} catch {\n\t\t\t\t// Silent reboot by contract: the record stays, the next boot retries.\n\t\t\t}\n\t\t},\n\n\t\tdispose() {\n\t\t\tunsub();\n\t\t}\n\t};\n}\n"]}
|
package/dist/groups.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { seal, isEnvelope, unseal, newDeviceKey } from './chunk-QBCU7OOP.js';
|
|
2
|
-
import { generateIdentity } from './chunk-
|
|
3
|
-
export { GROUP_KEYING, generateIdentity, groupCryptoAvailable, keyId, newGroupId, openManifest, publicIdentity, signManifest } from './chunk-
|
|
4
|
-
import { assertKdfBounds, deriveKey, freshKdfParams } from './chunk-
|
|
5
|
-
import
|
|
2
|
+
import { generateIdentity } from './chunk-F4ZZEJ5H.js';
|
|
3
|
+
export { GROUP_KEYING, generateIdentity, groupCryptoAvailable, keyId, newGroupId, openManifest, publicIdentity, signManifest } from './chunk-F4ZZEJ5H.js';
|
|
4
|
+
import { assertKdfBounds, deriveKey, freshKdfParams } from './chunk-5UYPKCDB.js';
|
|
5
|
+
import './chunk-WXI2NAPA.js';
|
|
6
|
+
import { SelfstoreError } from './chunk-CSYEIXNY.js';
|
|
6
7
|
|
|
7
8
|
// src/persistence/identity.ts
|
|
8
9
|
var VAULT_KEY = "groupVaultKey";
|