@broberg/s3 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -61,6 +61,7 @@ Preact: `import { useUpload } from "@broberg/s3/preact"` → `{ status, progress
61
61
  | Key outside the user's `prefix/` (incl. `..`, `%2e`, `//`) → | **403**, every action, upload and download |
62
62
  | Larger than `maxBytes` → | **413** at start; and at complete the real size is read from the bucket — a lie is discarded |
63
63
  | Type not in `allowedTypes` → | **415** |
64
+ | Not `content-type: application/json` (a cross-site form post) → | **415**, nothing happens |
64
65
  | Not configured → | **503** |
65
66
  | Signed URL lifetime | ≤ **15 min**, one object (and one part) |
66
67
  | The secret key | never in any response (tested) |
package/dist/index.cjs CHANGED
@@ -213,6 +213,8 @@ function createS3Handler(signer, opts) {
213
213
  const who = await opts.authorize(req);
214
214
  if (!who) throw new HttpError(401, "unauthorized", "sign in first");
215
215
  const prefix = checkPrefix(who.prefix);
216
+ if (!(req.headers.get("content-type") ?? "").toLowerCase().startsWith("application/json"))
217
+ throw new HttpError(415, "json_required", "content-type must be application/json");
216
218
  let body;
217
219
  try {
218
220
  body = await req.json();
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/server.ts","../src/handler.ts"],"names":["AwsClient","names"],"mappings":"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","file":"index.cjs","sourcesContent":["// F111.1 — the server half of @broberg/s3. It holds the storage key and only ever\n// hands the browser SHORT-LIVED, ONE-OBJECT signatures. Bytes never pass through\n// the site's own server: the browser talks to the bucket directly.\nimport { AwsClient } from \"aws4fetch\";\n\n/** Where a bucket lives and how to sign for it. R2 in the EU is the default. */\nexport interface S3Config {\n bucket: string;\n accessKeyId: string;\n secretAccessKey: string;\n /** Cloudflare R2 account id → https://<id>.eu.r2.cloudflarestorage.com */\n accountId?: string;\n /** R2 jurisdiction. Defaults to \"eu\"; a non-EU R2 bucket must say \"default\" explicitly. */\n jurisdiction?: \"eu\" | \"default\";\n /** Any other S3-compatible endpoint origin (Tigris, AWS, MinIO), e.g. \"https://fly.storage.tigris.dev\". */\n endpoint?: string;\n /** Signing region. \"auto\" for R2 and Tigris (default); the bucket's region for AWS. */\n region?: string;\n /** Injected for tests; defaults to the global fetch. */\n fetch?: typeof fetch;\n}\n\nexport interface UploadedPart { partNumber: number; etag: string; size?: number }\n\nexport const MIN_PART_SIZE = 8 * 1024 * 1024;\nexport const DEFAULT_PART_SIZE = 16 * 1024 * 1024;\nexport const MAX_PARTS = 10_000;\n/** The longest a signed URL may live. A leaked link is useless after this. */\nexport const MAX_EXPIRES_SECONDS = 900;\nconst MiB = 1024 * 1024;\n\n/** Part size for `size` bytes: at least `preferred` (and 8 MiB), and few enough parts for S3's 10,000 cap. */\nexport function choosePartSize(size: number, preferred = DEFAULT_PART_SIZE): number {\n const need = Math.ceil(size / MAX_PARTS);\n return Math.ceil(Math.max(preferred, MIN_PART_SIZE, need) / MiB) * MiB;\n}\n\nconst encodeKey = (key: string) => key.split(\"/\").map(encodeURIComponent).join(\"/\");\nconst xmlText = (xml: string, tag: string) => xml.match(new RegExp(`<${tag}>([^<]*)</${tag}>`))?.[1];\nconst xmlEscape = (s: string) => s.replace(/&/g, \"&amp;\").replace(/</g, \"&lt;\").replace(/>/g, \"&gt;\").replace(/\"/g, \"&quot;\");\nconst xmlUnescape = (s: string) => s.replace(/&quot;/g, '\"').replace(/&lt;/g, \"<\").replace(/&gt;/g, \">\").replace(/&amp;/g, \"&\");\n\nexport interface S3Signer {\n /** Begin a multipart upload. */\n start(input: { key: string; contentType?: string }): Promise<{ uploadId: string }>;\n /** A presigned PUT for one part of one upload. */\n signPart(input: { key: string; uploadId: string; partNumber: number; expiresIn?: number }): Promise<string>;\n /** Parts the bucket already holds for this upload (the truth a resume starts from). */\n listParts(input: { key: string; uploadId: string }): Promise<UploadedPart[]>;\n complete(input: { key: string; uploadId: string; parts: UploadedPart[] }): Promise<void>;\n abort(input: { key: string; uploadId: string }): Promise<void>;\n /** A presigned GET; `filename` makes the browser save it under that name. */\n signDownload(input: { key: string; expiresIn?: number; filename?: string }): Promise<string>;\n /** Size of a stored object, or null when it does not exist. */\n head(key: string): Promise<number | null>;\n}\n\nexport class S3Error extends Error {\n constructor(message: string, readonly status: number, readonly s3Code?: string) {\n super(message);\n this.name = \"S3Error\";\n }\n}\n\nfunction originOf(cfg: S3Config): string {\n if (cfg.endpoint) return cfg.endpoint.replace(/\\/+$/, \"\");\n if (!cfg.accountId) throw new Error(\"@broberg/s3: set accountId (R2) or endpoint (other S3-compatible storage)\");\n const j = cfg.jurisdiction ?? \"eu\";\n return j === \"eu\" ? `https://${cfg.accountId}.eu.r2.cloudflarestorage.com` : `https://${cfg.accountId}.r2.cloudflarestorage.com`;\n}\n\nexport function createS3Signer(cfg: S3Config): S3Signer {\n for (const f of [\"bucket\", \"accessKeyId\", \"secretAccessKey\"] as const)\n if (!cfg[f]) throw new Error(`@broberg/s3: ${f} is required`);\n const origin = originOf(cfg);\n const doFetch = cfg.fetch ?? ((r: Request) => fetch(r));\n const aws = new AwsClient({\n accessKeyId: cfg.accessKeyId,\n secretAccessKey: cfg.secretAccessKey,\n region: cfg.region ?? \"auto\",\n service: \"s3\",\n });\n const objectUrl = (key: string) => new URL(`${origin}/${encodeURIComponent(cfg.bucket)}/${encodeKey(key)}`);\n const clampExpiry = (s?: number) => Math.max(1, Math.min(s ?? MAX_EXPIRES_SECONDS, MAX_EXPIRES_SECONDS));\n\n async function call(method: string, url: URL, body?: string, headers?: Record<string, string>): Promise<string> {\n const signed = await aws.sign(url.toString(), { method, body, headers });\n const res = await doFetch(signed);\n const text = await res.text();\n // S3 can answer 200 with an <Error> body (CompleteMultipartUpload does).\n if (!res.ok || /<Error>/.test(text)) {\n const code = xmlText(text, \"Code\");\n throw new S3Error(`@broberg/s3: ${method} ${url.pathname} → ${res.status}${code ? ` ${code}` : \"\"}`, res.ok ? 502 : res.status, code);\n }\n return text;\n }\n\n async function presign(method: \"PUT\" | \"GET\", url: URL, expiresIn?: number): Promise<string> {\n url.searchParams.set(\"X-Amz-Expires\", String(clampExpiry(expiresIn)));\n const signed = await aws.sign(url.toString(), { method, aws: { signQuery: true } });\n return signed.url;\n }\n\n return {\n async start({ key, contentType }) {\n const url = objectUrl(key);\n url.searchParams.set(\"uploads\", \"\");\n const xml = await call(\"POST\", url, undefined, contentType ? { \"content-type\": contentType } : undefined);\n const uploadId = xmlText(xml, \"UploadId\");\n if (!uploadId) throw new S3Error(\"@broberg/s3: CreateMultipartUpload returned no UploadId\", 502);\n return { uploadId: xmlUnescape(uploadId) };\n },\n async signPart({ key, uploadId, partNumber, expiresIn }) {\n if (!Number.isInteger(partNumber) || partNumber < 1 || partNumber > MAX_PARTS)\n throw new S3Error(`@broberg/s3: partNumber must be 1..${MAX_PARTS}`, 400);\n const url = objectUrl(key);\n url.searchParams.set(\"partNumber\", String(partNumber));\n url.searchParams.set(\"uploadId\", uploadId);\n return presign(\"PUT\", url, expiresIn);\n },\n async listParts({ key, uploadId }) {\n const parts: UploadedPart[] = [];\n let marker: string | undefined;\n for (;;) {\n const url = objectUrl(key);\n url.searchParams.set(\"uploadId\", uploadId);\n if (marker) url.searchParams.set(\"part-number-marker\", marker);\n const xml = await call(\"GET\", url);\n for (const m of xml.matchAll(/<Part>([\\s\\S]*?)<\\/Part>/g)) {\n parts.push({\n partNumber: Number(xmlText(m[1]!, \"PartNumber\")),\n etag: xmlUnescape(xmlText(m[1]!, \"ETag\") ?? \"\"),\n size: Number(xmlText(m[1]!, \"Size\")),\n });\n }\n if (xmlText(xml, \"IsTruncated\") !== \"true\") break;\n marker = xmlText(xml, \"NextPartNumberMarker\");\n if (!marker) break;\n }\n return parts;\n },\n async complete({ key, uploadId, parts }) {\n const sorted = [...parts].sort((a, b) => a.partNumber - b.partNumber);\n const body =\n \"<CompleteMultipartUpload>\" +\n sorted.map((p) => `<Part><PartNumber>${p.partNumber}</PartNumber><ETag>${xmlEscape(p.etag)}</ETag></Part>`).join(\"\") +\n \"</CompleteMultipartUpload>\";\n const url = objectUrl(key);\n url.searchParams.set(\"uploadId\", uploadId);\n await call(\"POST\", url, body, { \"content-type\": \"application/xml\" });\n },\n async abort({ key, uploadId }) {\n const url = objectUrl(key);\n url.searchParams.set(\"uploadId\", uploadId);\n await call(\"DELETE\", url);\n },\n async signDownload({ key, expiresIn, filename }) {\n const url = objectUrl(key);\n if (filename) {\n const ascii = filename.replace(/[^\\x20-\\x7e]|[\"\\\\]/g, \"_\");\n url.searchParams.set(\"response-content-disposition\", `attachment; filename=\"${ascii}\"; filename*=UTF-8''${encodeURIComponent(filename)}`);\n }\n return presign(\"GET\", url, expiresIn);\n },\n async head(key) {\n const signed = await aws.sign(objectUrl(key).toString(), { method: \"HEAD\" });\n const res = await doFetch(signed);\n if (res.status === 404) return null;\n if (!res.ok) throw new S3Error(`@broberg/s3: HEAD → ${res.status}`, res.status);\n return Number(res.headers.get(\"content-length\"));\n },\n };\n}\n\ntype Env = Record<string, string | undefined>;\n\n/**\n * Builds a signer from the environment, or returns null when NOTHING is configured\n * (ship dark: the handler then answers 503). A HALF-configured environment throws\n * at boot instead — that is a mistake, not a choice.\n *\n * R2: R2_ACCOUNT_ID, R2_ACCESS_KEY_ID, R2_SECRET_ACCESS_KEY, R2_BUCKET [, R2_JURISDICTION]\n * S3: S3_ENDPOINT, S3_ACCESS_KEY_ID, S3_SECRET_ACCESS_KEY, S3_BUCKET [, S3_REGION]\n */\nexport function createS3SignerFromEnv(env: Env = (globalThis as { process?: { env: Env } }).process?.env ?? {}): S3Signer | null {\n const r2 = [\"R2_ACCOUNT_ID\", \"R2_ACCESS_KEY_ID\", \"R2_SECRET_ACCESS_KEY\", \"R2_BUCKET\"];\n const s3 = [\"S3_ENDPOINT\", \"S3_ACCESS_KEY_ID\", \"S3_SECRET_ACCESS_KEY\", \"S3_BUCKET\"];\n const pick = (names: string[]) => names.filter((n) => env[n]);\n const r2Set = pick(r2), s3Set = pick(s3);\n if (r2Set.length === 0 && s3Set.length === 0) return null;\n const [names, set] = r2Set.length >= s3Set.length ? [r2, r2Set] : [s3, s3Set];\n if (set.length !== names.length)\n throw new Error(`@broberg/s3: incomplete storage config — missing ${names.filter((n) => !env[n]).join(\", \")}`);\n if (names === r2) {\n const j = env.R2_JURISDICTION;\n if (j && j !== \"eu\" && j !== \"default\") throw new Error(`@broberg/s3: R2_JURISDICTION must be \"eu\" or \"default\"`);\n return createS3Signer({\n accountId: env.R2_ACCOUNT_ID, accessKeyId: env.R2_ACCESS_KEY_ID!, secretAccessKey: env.R2_SECRET_ACCESS_KEY!,\n bucket: env.R2_BUCKET!, jurisdiction: (j as \"eu\" | \"default\" | undefined) ?? \"eu\",\n });\n }\n return createS3Signer({\n endpoint: env.S3_ENDPOINT, accessKeyId: env.S3_ACCESS_KEY_ID!, secretAccessKey: env.S3_SECRET_ACCESS_KEY!,\n bucket: env.S3_BUCKET!, region: env.S3_REGION,\n });\n}\n","// F111.1 — ONE fetch handler (Request → Response) for Hono/Bun and Next.\n// Every action asks the SITE's authorize() first and keeps the user inside the\n// object prefix it returns. The storage key never appears in a response.\nimport {\n MAX_EXPIRES_SECONDS, MAX_PARTS, S3Error, choosePartSize,\n type S3Signer, type UploadedPart,\n} from \"./server\";\n\nexport interface Authorized {\n /** Every object this user may touch lives under `<prefix>/`. E.g. \"users/<bid-sub>\". */\n prefix: string;\n}\n\nexport interface S3HandlerOptions {\n /** The site's own check (BID session + allowlist). Return null to refuse. */\n authorize(req: Request): Promise<Authorized | null> | Authorized | null;\n /** Largest object accepted, in bytes. Default 20 GiB. Enforced at start (declared) AND at complete (actual). */\n maxBytes?: number;\n /** Allowed content types, exact (\"video/mp4\") or wildcard (\"video/*\"). Default: any. Declared type, not sniffed. */\n allowedTypes?: string[];\n /** Preferred part size in bytes (min 8 MiB). Default 16 MiB. */\n partSize?: number;\n /** Signed-URL lifetime in seconds, max 900. */\n expiresIn?: number;\n /** Object key for a new upload. Default `<prefix>/<yyyy-mm-dd>/<random>-<safe filename>`. */\n keyFor?(input: { prefix: string; filename: string; contentType: string }): string;\n}\n\nconst GiB = 1024 ** 3;\nconst MAX_SIGN_BATCH = 100;\n\nclass HttpError extends Error {\n constructor(readonly status: number, readonly code: string, message: string) { super(message); }\n}\n\nconst json = (status: number, body: unknown) =>\n new Response(JSON.stringify(body), { status, headers: { \"content-type\": \"application/json\", \"cache-control\": \"no-store\" } });\n\nexport const safeFilename = (name: string) =>\n (name.normalize(\"NFKC\").replace(/[\\\\/\\u0000-\\u001f\\u007f]/g, \"_\").replace(/^\\.+/, \"\").replace(/[^\\p{L}\\p{N}._ -]/gu, \"_\").trim().slice(0, 120)) || \"file\";\n\n/** A prefix must be plain path segments: no \"..\", no empty segments, no encoding tricks. */\nfunction checkPrefix(prefix: unknown): string {\n if (typeof prefix !== \"string\") throw new HttpError(500, \"bad_prefix\", \"authorize() returned no prefix\");\n const p = prefix.replace(/^\\/+|\\/+$/g, \"\");\n if (!p || p.split(\"/\").some((s) => !s || s === \".\" || s === \"..\") || /[%\\\\\\u0000-\\u001f]/.test(p))\n throw new HttpError(500, \"bad_prefix\", \"authorize() returned an unsafe prefix\");\n return p;\n}\n\n/** The key must sit under `<prefix>/` and be free of traversal, encoding and control characters. */\nfunction checkKey(key: unknown, prefix: string): string {\n if (typeof key !== \"string\" || key.length > 1024) throw new HttpError(400, \"bad_key\", \"key is required\");\n const segs = key.split(\"/\");\n if (/[%\\\\\\u0000-\\u001f\\u007f]/.test(key) || segs.some((s) => !s || s === \".\" || s === \"..\") || !key.startsWith(prefix + \"/\"))\n throw new HttpError(403, \"forbidden_key\", \"that object is not yours\");\n return key;\n}\n\nconst checkUploadId = (id: unknown): string => {\n if (typeof id !== \"string\" || !id || id.length > 1024 || /[\\u0000-\\u001f]/.test(id))\n throw new HttpError(400, \"bad_upload_id\", \"uploadId is required\");\n return id;\n};\n\nfunction typeAllowed(type: string, allowed?: string[]): boolean {\n if (!allowed?.length) return true;\n const t = type.toLowerCase();\n return allowed.some((a) => { const x = a.toLowerCase(); return x.endsWith(\"/*\") ? t.startsWith(x.slice(0, -1)) : t === x; });\n}\n\nconst randomId = () => {\n const b = new Uint8Array(9);\n crypto.getRandomValues(b);\n return Array.from(b, (x) => x.toString(16).padStart(2, \"0\")).join(\"\");\n};\n\nconst defaultKeyFor = ({ prefix, filename }: { prefix: string; filename: string }) =>\n `${prefix}/${new Date().toISOString().slice(0, 10)}/${randomId()}-${safeFilename(filename)}`;\n\nexport function createS3Handler(signer: S3Signer | null, opts: S3HandlerOptions): (req: Request) => Promise<Response> {\n const maxBytes = opts.maxBytes ?? 20 * GiB;\n const expiresIn = Math.min(opts.expiresIn ?? MAX_EXPIRES_SECONDS, MAX_EXPIRES_SECONDS);\n\n return async (req) => {\n try {\n if (req.method !== \"POST\") throw new HttpError(405, \"method_not_allowed\", \"POST only\");\n if (!signer) throw new HttpError(503, \"not_configured\", \"file storage is not configured on this server\");\n const who = await opts.authorize(req);\n if (!who) throw new HttpError(401, \"unauthorized\", \"sign in first\");\n const prefix = checkPrefix(who.prefix);\n\n let body: Record<string, unknown>;\n try { body = (await req.json()) as Record<string, unknown>; } catch { throw new HttpError(400, \"bad_json\", \"body must be JSON\"); }\n if (!body || typeof body !== \"object\") throw new HttpError(400, \"bad_json\", \"body must be a JSON object\");\n\n switch (body.action) {\n case \"start\": {\n const filename = typeof body.filename === \"string\" ? body.filename : \"\";\n const size = body.size;\n const contentType = typeof body.type === \"string\" && body.type ? body.type : \"application/octet-stream\";\n if (!filename) throw new HttpError(400, \"bad_filename\", \"filename is required\");\n if (typeof size !== \"number\" || !Number.isFinite(size) || size < 1) throw new HttpError(400, \"bad_size\", \"size must be a positive number\");\n if (size > maxBytes) throw new HttpError(413, \"too_large\", `file is larger than ${maxBytes} bytes`);\n if (!typeAllowed(contentType, opts.allowedTypes)) throw new HttpError(415, \"type_not_allowed\", `${contentType} is not accepted here`);\n const key = checkKey((opts.keyFor ?? defaultKeyFor)({ prefix, filename, contentType }), prefix);\n const partSize = choosePartSize(size, opts.partSize);\n const { uploadId } = await signer.start({ key, contentType });\n return json(200, { key, uploadId, partSize, partCount: Math.ceil(size / partSize) });\n }\n case \"sign\": {\n const key = checkKey(body.key, prefix);\n const uploadId = checkUploadId(body.uploadId);\n const nums = body.partNumbers;\n if (!Array.isArray(nums) || nums.length < 1 || nums.length > MAX_SIGN_BATCH ||\n !nums.every((n) => Number.isInteger(n) && n >= 1 && n <= MAX_PARTS))\n throw new HttpError(400, \"bad_parts\", `partNumbers: 1..${MAX_SIGN_BATCH} integers in 1..${MAX_PARTS}`);\n const urls = await Promise.all((nums as number[]).map(async (partNumber) => ({\n partNumber, url: await signer.signPart({ key, uploadId, partNumber, expiresIn }),\n })));\n return json(200, { urls, expiresIn });\n }\n case \"list\": {\n const key = checkKey(body.key, prefix);\n const parts = await signer.listParts({ key, uploadId: checkUploadId(body.uploadId) });\n return json(200, { parts });\n }\n case \"complete\": {\n const key = checkKey(body.key, prefix);\n const uploadId = checkUploadId(body.uploadId);\n const sent = body.parts;\n if (!Array.isArray(sent) || sent.length < 1 || sent.length > MAX_PARTS)\n throw new HttpError(400, \"bad_parts\", \"parts are required\");\n // The bucket is the truth: sizes come from listParts, never from the browser.\n const stored = await signer.listParts({ key, uploadId });\n const total = stored.reduce((s, p) => s + (p.size ?? 0), 0);\n if (total > maxBytes) {\n await signer.abort({ key, uploadId });\n throw new HttpError(413, \"too_large\", `upload exceeded ${maxBytes} bytes and was discarded`);\n }\n const parts: UploadedPart[] = (sent as Array<{ partNumber?: unknown; etag?: unknown }>).map((p) => {\n if (!Number.isInteger(p?.partNumber) || typeof p?.etag !== \"string\") throw new HttpError(400, \"bad_parts\", \"each part needs partNumber and etag\");\n return { partNumber: p.partNumber as number, etag: p.etag };\n });\n await signer.complete({ key, uploadId, parts });\n return json(200, { key, size: total });\n }\n case \"abort\": {\n const key = checkKey(body.key, prefix);\n await signer.abort({ key, uploadId: checkUploadId(body.uploadId) });\n return json(200, { aborted: true });\n }\n case \"download\": {\n const key = checkKey(body.key, prefix);\n const filename = typeof body.filename === \"string\" && body.filename ? safeFilename(body.filename) : undefined;\n const url = await signer.signDownload({ key, expiresIn, filename });\n return json(200, { url, expiresIn });\n }\n default:\n throw new HttpError(400, \"unknown_action\", \"action must be start, sign, list, complete, abort or download\");\n }\n } catch (e) {\n if (e instanceof HttpError) return json(e.status, { error: { code: e.code, message: e.message } });\n if (e instanceof S3Error) {\n const status = e.s3Code === \"NoSuchUpload\" ? 404 : e.s3Code === \"NoSuchKey\" ? 404 : 502;\n return json(status, { error: { code: e.s3Code ?? \"storage_error\", message: status === 404 ? \"upload or object not found\" : \"storage refused the request\" } });\n }\n // Never echo an unknown error: it could carry configuration.\n return json(500, { error: { code: \"internal\", message: \"upload service error\" } });\n }\n };\n}\n"]}
1
+ {"version":3,"sources":["../src/server.ts","../src/handler.ts"],"names":["AwsClient","names"],"mappings":";;;;;AAwBO,IAAM,aAAA,GAAgB,IAAI,IAAA,GAAO;AACjC,IAAM,iBAAA,GAAoB,KAAK,IAAA,GAAO;AACtC,IAAM,SAAA,GAAY;AAElB,IAAM,mBAAA,GAAsB;AACnC,IAAM,MAAM,IAAA,GAAO,IAAA;AAGZ,SAAS,cAAA,CAAe,IAAA,EAAc,SAAA,GAAY,iBAAA,EAA2B;AAClF,EAAA,MAAM,IAAA,GAAO,IAAA,CAAK,IAAA,CAAK,IAAA,GAAO,SAAS,CAAA;AACvC,EAAA,OAAO,IAAA,CAAK,KAAK,IAAA,CAAK,GAAA,CAAI,WAAW,aAAA,EAAe,IAAI,CAAA,GAAI,GAAG,CAAA,GAAI,GAAA;AACrE;AAEA,IAAM,SAAA,GAAY,CAAC,GAAA,KAAgB,GAAA,CAAI,KAAA,CAAM,GAAG,CAAA,CAAE,GAAA,CAAI,kBAAkB,CAAA,CAAE,IAAA,CAAK,GAAG,CAAA;AAClF,IAAM,OAAA,GAAU,CAAC,GAAA,EAAa,GAAA,KAAgB,IAAI,KAAA,CAAM,IAAI,MAAA,CAAO,CAAA,CAAA,EAAI,GAAG,CAAA,UAAA,EAAa,GAAG,CAAA,CAAA,CAAG,CAAC,IAAI,CAAC,CAAA;AACnG,IAAM,YAAY,CAAC,CAAA,KAAc,EAAE,OAAA,CAAQ,IAAA,EAAM,OAAO,CAAA,CAAE,OAAA,CAAQ,IAAA,EAAM,MAAM,EAAE,OAAA,CAAQ,IAAA,EAAM,MAAM,CAAA,CAAE,OAAA,CAAQ,MAAM,QAAQ,CAAA;AAC5H,IAAM,cAAc,CAAC,CAAA,KAAc,EAAE,OAAA,CAAQ,SAAA,EAAW,GAAG,CAAA,CAAE,OAAA,CAAQ,OAAA,EAAS,GAAG,EAAE,OAAA,CAAQ,OAAA,EAAS,GAAG,CAAA,CAAE,OAAA,CAAQ,UAAU,GAAG,CAAA;AAiBvH,IAAM,OAAA,GAAN,cAAsB,KAAA,CAAM;AAAA,EACjC,WAAA,CAAY,OAAA,EAA0B,MAAA,EAAyB,MAAA,EAAiB;AAC9E,IAAA,KAAA,CAAM,OAAO,CAAA;AADuB,IAAA,IAAA,CAAA,MAAA,GAAA,MAAA;AAAyB,IAAA,IAAA,CAAA,MAAA,GAAA,MAAA;AAE7D,IAAA,IAAA,CAAK,IAAA,GAAO,SAAA;AAAA,EACd;AAAA,EAHsC,MAAA;AAAA,EAAyB,MAAA;AAIjE;AAEA,SAAS,SAAS,GAAA,EAAuB;AACvC,EAAA,IAAI,IAAI,QAAA,EAAU,OAAO,IAAI,QAAA,CAAS,OAAA,CAAQ,QAAQ,EAAE,CAAA;AACxD,EAAA,IAAI,CAAC,GAAA,CAAI,SAAA,EAAW,MAAM,IAAI,MAAM,2EAA2E,CAAA;AAC/G,EAAA,MAAM,CAAA,GAAI,IAAI,YAAA,IAAgB,IAAA;AAC9B,EAAA,OAAO,CAAA,KAAM,OAAO,CAAA,QAAA,EAAW,GAAA,CAAI,SAAS,CAAA,4BAAA,CAAA,GAAiC,CAAA,QAAA,EAAW,IAAI,SAAS,CAAA,yBAAA,CAAA;AACvG;AAEO,SAAS,eAAe,GAAA,EAAyB;AACtD,EAAA,KAAA,MAAW,CAAA,IAAK,CAAC,QAAA,EAAU,aAAA,EAAe,iBAAiB,CAAA;AACzD,IAAA,IAAI,CAAC,IAAI,CAAC,CAAA,QAAS,IAAI,KAAA,CAAM,CAAA,aAAA,EAAgB,CAAC,CAAA,YAAA,CAAc,CAAA;AAC9D,EAAA,MAAM,MAAA,GAAS,SAAS,GAAG,CAAA;AAC3B,EAAA,MAAM,UAAU,GAAA,CAAI,KAAA,KAAU,CAAC,CAAA,KAAe,MAAM,CAAC,CAAA,CAAA;AACrD,EAAA,MAAM,GAAA,GAAM,IAAIA,mBAAA,CAAU;AAAA,IACxB,aAAa,GAAA,CAAI,WAAA;AAAA,IACjB,iBAAiB,GAAA,CAAI,eAAA;AAAA,IACrB,MAAA,EAAQ,IAAI,MAAA,IAAU,MAAA;AAAA,IACtB,OAAA,EAAS;AAAA,GACV,CAAA;AACD,EAAA,MAAM,YAAY,CAAC,GAAA,KAAgB,IAAI,GAAA,CAAI,GAAG,MAAM,CAAA,CAAA,EAAI,kBAAA,CAAmB,GAAA,CAAI,MAAM,CAAC,CAAA,CAAA,EAAI,SAAA,CAAU,GAAG,CAAC,CAAA,CAAE,CAAA;AAC1G,EAAA,MAAM,WAAA,GAAc,CAAC,CAAA,KAAe,IAAA,CAAK,GAAA,CAAI,CAAA,EAAG,IAAA,CAAK,GAAA,CAAI,CAAA,IAAK,mBAAA,EAAqB,mBAAmB,CAAC,CAAA;AAEvG,EAAA,eAAe,IAAA,CAAK,MAAA,EAAgB,GAAA,EAAU,IAAA,EAAe,OAAA,EAAmD;AAC9G,IAAA,MAAM,MAAA,GAAS,MAAM,GAAA,CAAI,IAAA,CAAK,GAAA,CAAI,QAAA,EAAS,EAAG,EAAE,MAAA,EAAQ,IAAA,EAAM,OAAA,EAAS,CAAA;AACvE,IAAA,MAAM,GAAA,GAAM,MAAM,OAAA,CAAQ,MAAM,CAAA;AAChC,IAAA,MAAM,IAAA,GAAO,MAAM,GAAA,CAAI,IAAA,EAAK;AAE5B,IAAA,IAAI,CAAC,GAAA,CAAI,EAAA,IAAM,SAAA,CAAU,IAAA,CAAK,IAAI,CAAA,EAAG;AACnC,MAAA,MAAM,IAAA,GAAO,OAAA,CAAQ,IAAA,EAAM,MAAM,CAAA;AACjC,MAAA,MAAM,IAAI,QAAQ,CAAA,aAAA,EAAgB,MAAM,IAAI,GAAA,CAAI,QAAQ,CAAA,QAAA,EAAM,GAAA,CAAI,MAAM,CAAA,EAAG,OAAO,CAAA,CAAA,EAAI,IAAI,KAAK,EAAE,CAAA,CAAA,EAAI,IAAI,EAAA,GAAK,GAAA,GAAM,GAAA,CAAI,MAAA,EAAQ,IAAI,CAAA;AAAA,IACtI;AACA,IAAA,OAAO,IAAA;AAAA,EACT;AAEA,EAAA,eAAe,OAAA,CAAQ,MAAA,EAAuB,GAAA,EAAU,SAAA,EAAqC;AAC3F,IAAA,GAAA,CAAI,aAAa,GAAA,CAAI,eAAA,EAAiB,OAAO,WAAA,CAAY,SAAS,CAAC,CAAC,CAAA;AACpE,IAAA,MAAM,MAAA,GAAS,MAAM,GAAA,CAAI,IAAA,CAAK,IAAI,QAAA,EAAS,EAAG,EAAE,MAAA,EAAQ,GAAA,EAAK,EAAE,SAAA,EAAW,IAAA,IAAQ,CAAA;AAClF,IAAA,OAAO,MAAA,CAAO,GAAA;AAAA,EAChB;AAEA,EAAA,OAAO;AAAA,IACL,MAAM,KAAA,CAAM,EAAE,GAAA,EAAK,aAAY,EAAG;AAChC,MAAA,MAAM,GAAA,GAAM,UAAU,GAAG,CAAA;AACzB,MAAA,GAAA,CAAI,YAAA,CAAa,GAAA,CAAI,SAAA,EAAW,EAAE,CAAA;AAClC,MAAA,MAAM,GAAA,GAAM,MAAM,IAAA,CAAK,MAAA,EAAQ,GAAA,EAAK,MAAA,EAAW,WAAA,GAAc,EAAE,cAAA,EAAgB,WAAA,EAAY,GAAI,MAAS,CAAA;AACxG,MAAA,MAAM,QAAA,GAAW,OAAA,CAAQ,GAAA,EAAK,UAAU,CAAA;AACxC,MAAA,IAAI,CAAC,QAAA,EAAU,MAAM,IAAI,OAAA,CAAQ,2DAA2D,GAAG,CAAA;AAC/F,MAAA,OAAO,EAAE,QAAA,EAAU,WAAA,CAAY,QAAQ,CAAA,EAAE;AAAA,IAC3C,CAAA;AAAA,IACA,MAAM,QAAA,CAAS,EAAE,KAAK,QAAA,EAAU,UAAA,EAAY,WAAU,EAAG;AACvD,MAAA,IAAI,CAAC,MAAA,CAAO,SAAA,CAAU,UAAU,CAAA,IAAK,UAAA,GAAa,KAAK,UAAA,GAAa,SAAA;AAClE,QAAA,MAAM,IAAI,OAAA,CAAQ,CAAA,mCAAA,EAAsC,SAAS,IAAI,GAAG,CAAA;AAC1E,MAAA,MAAM,GAAA,GAAM,UAAU,GAAG,CAAA;AACzB,MAAA,GAAA,CAAI,YAAA,CAAa,GAAA,CAAI,YAAA,EAAc,MAAA,CAAO,UAAU,CAAC,CAAA;AACrD,MAAA,GAAA,CAAI,YAAA,CAAa,GAAA,CAAI,UAAA,EAAY,QAAQ,CAAA;AACzC,MAAA,OAAO,OAAA,CAAQ,KAAA,EAAO,GAAA,EAAK,SAAS,CAAA;AAAA,IACtC,CAAA;AAAA,IACA,MAAM,SAAA,CAAU,EAAE,GAAA,EAAK,UAAS,EAAG;AACjC,MAAA,MAAM,QAAwB,EAAC;AAC/B,MAAA,IAAI,MAAA;AACJ,MAAA,WAAS;AACP,QAAA,MAAM,GAAA,GAAM,UAAU,GAAG,CAAA;AACzB,QAAA,GAAA,CAAI,YAAA,CAAa,GAAA,CAAI,UAAA,EAAY,QAAQ,CAAA;AACzC,QAAA,IAAI,MAAA,EAAQ,GAAA,CAAI,YAAA,CAAa,GAAA,CAAI,sBAAsB,MAAM,CAAA;AAC7D,QAAA,MAAM,GAAA,GAAM,MAAM,IAAA,CAAK,KAAA,EAAO,GAAG,CAAA;AACjC,QAAA,KAAA,MAAW,CAAA,IAAK,GAAA,CAAI,QAAA,CAAS,2BAA2B,CAAA,EAAG;AACzD,UAAA,KAAA,CAAM,IAAA,CAAK;AAAA,YACT,YAAY,MAAA,CAAO,OAAA,CAAQ,EAAE,CAAC,CAAA,EAAI,YAAY,CAAC,CAAA;AAAA,YAC/C,IAAA,EAAM,YAAY,OAAA,CAAQ,CAAA,CAAE,CAAC,CAAA,EAAI,MAAM,KAAK,EAAE,CAAA;AAAA,YAC9C,MAAM,MAAA,CAAO,OAAA,CAAQ,EAAE,CAAC,CAAA,EAAI,MAAM,CAAC;AAAA,WACpC,CAAA;AAAA,QACH;AACA,QAAA,IAAI,OAAA,CAAQ,GAAA,EAAK,aAAa,CAAA,KAAM,MAAA,EAAQ;AAC5C,QAAA,MAAA,GAAS,OAAA,CAAQ,KAAK,sBAAsB,CAAA;AAC5C,QAAA,IAAI,CAAC,MAAA,EAAQ;AAAA,MACf;AACA,MAAA,OAAO,KAAA;AAAA,IACT,CAAA;AAAA,IACA,MAAM,QAAA,CAAS,EAAE,GAAA,EAAK,QAAA,EAAU,OAAM,EAAG;AACvC,MAAA,MAAM,MAAA,GAAS,CAAC,GAAG,KAAK,CAAA,CAAE,IAAA,CAAK,CAAC,CAAA,EAAG,CAAA,KAAM,CAAA,CAAE,UAAA,GAAa,CAAA,CAAE,UAAU,CAAA;AACpE,MAAA,MAAM,OACJ,2BAAA,GACA,MAAA,CAAO,IAAI,CAAC,CAAA,KAAM,qBAAqB,CAAA,CAAE,UAAU,CAAA,mBAAA,EAAsB,SAAA,CAAU,EAAE,IAAI,CAAC,gBAAgB,CAAA,CAAE,IAAA,CAAK,EAAE,CAAA,GACnH,4BAAA;AACF,MAAA,MAAM,GAAA,GAAM,UAAU,GAAG,CAAA;AACzB,MAAA,GAAA,CAAI,YAAA,CAAa,GAAA,CAAI,UAAA,EAAY,QAAQ,CAAA;AACzC,MAAA,MAAM,KAAK,MAAA,EAAQ,GAAA,EAAK,MAAM,EAAE,cAAA,EAAgB,mBAAmB,CAAA;AAAA,IACrE,CAAA;AAAA,IACA,MAAM,KAAA,CAAM,EAAE,GAAA,EAAK,UAAS,EAAG;AAC7B,MAAA,MAAM,GAAA,GAAM,UAAU,GAAG,CAAA;AACzB,MAAA,GAAA,CAAI,YAAA,CAAa,GAAA,CAAI,UAAA,EAAY,QAAQ,CAAA;AACzC,MAAA,MAAM,IAAA,CAAK,UAAU,GAAG,CAAA;AAAA,IAC1B,CAAA;AAAA,IACA,MAAM,YAAA,CAAa,EAAE,GAAA,EAAK,SAAA,EAAW,UAAS,EAAG;AAC/C,MAAA,MAAM,GAAA,GAAM,UAAU,GAAG,CAAA;AACzB,MAAA,IAAI,QAAA,EAAU;AACZ,QAAA,MAAM,KAAA,GAAQ,QAAA,CAAS,OAAA,CAAQ,qBAAA,EAAuB,GAAG,CAAA;AACzD,QAAA,GAAA,CAAI,YAAA,CAAa,IAAI,8BAAA,EAAgC,CAAA,sBAAA,EAAyB,KAAK,CAAA,oBAAA,EAAuB,kBAAA,CAAmB,QAAQ,CAAC,CAAA,CAAE,CAAA;AAAA,MAC1I;AACA,MAAA,OAAO,OAAA,CAAQ,KAAA,EAAO,GAAA,EAAK,SAAS,CAAA;AAAA,IACtC,CAAA;AAAA,IACA,MAAM,KAAK,GAAA,EAAK;AACd,MAAA,MAAM,MAAA,GAAS,MAAM,GAAA,CAAI,IAAA,CAAK,SAAA,CAAU,GAAG,CAAA,CAAE,QAAA,EAAS,EAAG,EAAE,MAAA,EAAQ,MAAA,EAAQ,CAAA;AAC3E,MAAA,MAAM,GAAA,GAAM,MAAM,OAAA,CAAQ,MAAM,CAAA;AAChC,MAAA,IAAI,GAAA,CAAI,MAAA,KAAW,GAAA,EAAK,OAAO,IAAA;AAC/B,MAAA,IAAI,CAAC,GAAA,CAAI,EAAA,EAAI,MAAM,IAAI,OAAA,CAAQ,CAAA,yBAAA,EAAuB,GAAA,CAAI,MAAM,CAAA,CAAA,EAAI,GAAA,CAAI,MAAM,CAAA;AAC9E,MAAA,OAAO,MAAA,CAAO,GAAA,CAAI,OAAA,CAAQ,GAAA,CAAI,gBAAgB,CAAC,CAAA;AAAA,IACjD;AAAA,GACF;AACF;AAYO,SAAS,sBAAsB,GAAA,GAAY,UAAA,CAA0C,OAAA,EAAS,GAAA,IAAO,EAAC,EAAoB;AAC/H,EAAA,MAAM,EAAA,GAAK,CAAC,eAAA,EAAiB,kBAAA,EAAoB,wBAAwB,WAAW,CAAA;AACpF,EAAA,MAAM,EAAA,GAAK,CAAC,aAAA,EAAe,kBAAA,EAAoB,wBAAwB,WAAW,CAAA;AAClF,EAAA,MAAM,IAAA,GAAO,CAACC,MAAAA,KAAoBA,MAAAA,CAAM,OAAO,CAAC,CAAA,KAAM,GAAA,CAAI,CAAC,CAAC,CAAA;AAC5D,EAAA,MAAM,QAAQ,IAAA,CAAK,EAAE,CAAA,EAAG,KAAA,GAAQ,KAAK,EAAE,CAAA;AACvC,EAAA,IAAI,MAAM,MAAA,KAAW,CAAA,IAAK,KAAA,CAAM,MAAA,KAAW,GAAG,OAAO,IAAA;AACrD,EAAA,MAAM,CAAC,KAAA,EAAO,GAAG,CAAA,GAAI,MAAM,MAAA,IAAU,KAAA,CAAM,MAAA,GAAS,CAAC,EAAA,EAAI,KAAK,CAAA,GAAI,CAAC,IAAI,KAAK,CAAA;AAC5E,EAAA,IAAI,GAAA,CAAI,WAAW,KAAA,CAAM,MAAA;AACvB,IAAA,MAAM,IAAI,KAAA,CAAM,CAAA,sDAAA,EAAoD,KAAA,CAAM,OAAO,CAAC,CAAA,KAAM,CAAC,GAAA,CAAI,CAAC,CAAC,CAAA,CAAE,IAAA,CAAK,IAAI,CAAC,CAAA,CAAE,CAAA;AAC/G,EAAA,IAAI,UAAU,EAAA,EAAI;AAChB,IAAA,MAAM,IAAI,GAAA,CAAI,eAAA;AACd,IAAA,IAAI,CAAA,IAAK,MAAM,IAAA,IAAQ,CAAA,KAAM,WAAW,MAAM,IAAI,MAAM,CAAA,sDAAA,CAAwD,CAAA;AAChH,IAAA,OAAO,cAAA,CAAe;AAAA,MACpB,WAAW,GAAA,CAAI,aAAA;AAAA,MAAe,aAAa,GAAA,CAAI,gBAAA;AAAA,MAAmB,iBAAiB,GAAA,CAAI,oBAAA;AAAA,MACvF,QAAQ,GAAA,CAAI,SAAA;AAAA,MAAY,cAAe,CAAA,IAAsC;AAAA,KAC9E,CAAA;AAAA,EACH;AACA,EAAA,OAAO,cAAA,CAAe;AAAA,IACpB,UAAU,GAAA,CAAI,WAAA;AAAA,IAAa,aAAa,GAAA,CAAI,gBAAA;AAAA,IAAmB,iBAAiB,GAAA,CAAI,oBAAA;AAAA,IACpF,QAAQ,GAAA,CAAI,SAAA;AAAA,IAAY,QAAQ,GAAA,CAAI;AAAA,GACrC,CAAA;AACH;;;ACjLA,IAAM,MAAM,IAAA,IAAQ,CAAA;AACpB,IAAM,cAAA,GAAiB,GAAA;AAEvB,IAAM,SAAA,GAAN,cAAwB,KAAA,CAAM;AAAA,EAC5B,WAAA,CAAqB,MAAA,EAAyB,IAAA,EAAc,OAAA,EAAiB;AAAE,IAAA,KAAA,CAAM,OAAO,CAAA;AAAvE,IAAA,IAAA,CAAA,MAAA,GAAA,MAAA;AAAyB,IAAA,IAAA,CAAA,IAAA,GAAA,IAAA;AAAA,EAAiD;AAAA,EAA1E,MAAA;AAAA,EAAyB,IAAA;AAChD,CAAA;AAEA,IAAM,OAAO,CAAC,MAAA,EAAgB,SAC5B,IAAI,QAAA,CAAS,KAAK,SAAA,CAAU,IAAI,GAAG,EAAE,MAAA,EAAQ,SAAS,EAAE,cAAA,EAAgB,oBAAoB,eAAA,EAAiB,UAAA,IAAc,CAAA;AAEtH,IAAM,YAAA,GAAe,CAAC,IAAA,KAC1B,IAAA,CAAK,SAAA,CAAU,MAAM,CAAA,CAAE,OAAA,CAAQ,2BAAA,EAA6B,GAAG,CAAA,CAAE,OAAA,CAAQ,QAAQ,EAAE,CAAA,CAAE,OAAA,CAAQ,qBAAA,EAAuB,GAAG,CAAA,CAAE,MAAK,CAAE,KAAA,CAAM,CAAA,EAAG,GAAG,CAAA,IAAM;AAGrJ,SAAS,YAAY,MAAA,EAAyB;AAC5C,EAAA,IAAI,OAAO,WAAW,QAAA,EAAU,MAAM,IAAI,SAAA,CAAU,GAAA,EAAK,cAAc,gCAAgC,CAAA;AACvG,EAAA,MAAM,CAAA,GAAI,MAAA,CAAO,OAAA,CAAQ,YAAA,EAAc,EAAE,CAAA;AACzC,EAAA,IAAI,CAAC,CAAA,IAAK,CAAA,CAAE,MAAM,GAAG,CAAA,CAAE,KAAK,CAAC,CAAA,KAAM,CAAC,CAAA,IAAK,MAAM,GAAA,IAAO,CAAA,KAAM,IAAI,CAAA,IAAK,oBAAA,CAAqB,KAAK,CAAC,CAAA;AAC9F,IAAA,MAAM,IAAI,SAAA,CAAU,GAAA,EAAK,YAAA,EAAc,uCAAuC,CAAA;AAChF,EAAA,OAAO,CAAA;AACT;AAGA,SAAS,QAAA,CAAS,KAAc,MAAA,EAAwB;AACtD,EAAA,IAAI,OAAO,GAAA,KAAQ,QAAA,IAAY,GAAA,CAAI,MAAA,GAAS,IAAA,EAAM,MAAM,IAAI,SAAA,CAAU,GAAA,EAAK,SAAA,EAAW,iBAAiB,CAAA;AACvG,EAAA,MAAM,IAAA,GAAO,GAAA,CAAI,KAAA,CAAM,GAAG,CAAA;AAC1B,EAAA,IAAI,2BAA2B,IAAA,CAAK,GAAG,KAAK,IAAA,CAAK,IAAA,CAAK,CAAC,CAAA,KAAM,CAAC,KAAK,CAAA,KAAM,GAAA,IAAO,MAAM,IAAI,CAAA,IAAK,CAAC,GAAA,CAAI,UAAA,CAAW,SAAS,GAAG,CAAA;AACzH,IAAA,MAAM,IAAI,SAAA,CAAU,GAAA,EAAK,eAAA,EAAiB,0BAA0B,CAAA;AACtE,EAAA,OAAO,GAAA;AACT;AAEA,IAAM,aAAA,GAAgB,CAAC,EAAA,KAAwB;AAC7C,EAAA,IAAI,OAAO,EAAA,KAAO,QAAA,IAAY,CAAC,EAAA,IAAM,GAAG,MAAA,GAAS,IAAA,IAAQ,iBAAA,CAAkB,IAAA,CAAK,EAAE,CAAA;AAChF,IAAA,MAAM,IAAI,SAAA,CAAU,GAAA,EAAK,eAAA,EAAiB,sBAAsB,CAAA;AAClE,EAAA,OAAO,EAAA;AACT,CAAA;AAEA,SAAS,WAAA,CAAY,MAAc,OAAA,EAA6B;AAC9D,EAAA,IAAI,CAAC,OAAA,EAAS,MAAA,EAAQ,OAAO,IAAA;AAC7B,EAAA,MAAM,CAAA,GAAI,KAAK,WAAA,EAAY;AAC3B,EAAA,OAAO,OAAA,CAAQ,IAAA,CAAK,CAAC,CAAA,KAAM;AAAE,IAAA,MAAM,CAAA,GAAI,EAAE,WAAA,EAAY;AAAG,IAAA,OAAO,CAAA,CAAE,QAAA,CAAS,IAAI,CAAA,GAAI,CAAA,CAAE,UAAA,CAAW,CAAA,CAAE,KAAA,CAAM,CAAA,EAAG,EAAE,CAAC,CAAA,GAAI,CAAA,KAAM,CAAA;AAAA,EAAG,CAAC,CAAA;AAC7H;AAEA,IAAM,WAAW,MAAM;AACrB,EAAA,MAAM,CAAA,GAAI,IAAI,UAAA,CAAW,CAAC,CAAA;AAC1B,EAAA,MAAA,CAAO,gBAAgB,CAAC,CAAA;AACxB,EAAA,OAAO,KAAA,CAAM,IAAA,CAAK,CAAA,EAAG,CAAC,MAAM,CAAA,CAAE,QAAA,CAAS,EAAE,CAAA,CAAE,SAAS,CAAA,EAAG,GAAG,CAAC,CAAA,CAAE,KAAK,EAAE,CAAA;AACtE,CAAA;AAEA,IAAM,aAAA,GAAgB,CAAC,EAAE,MAAA,EAAQ,QAAA,OAC/B,CAAA,EAAG,MAAM,CAAA,CAAA,EAAA,iBAAI,IAAI,IAAA,EAAK,EAAE,aAAY,CAAE,KAAA,CAAM,CAAA,EAAG,EAAE,CAAC,CAAA,CAAA,EAAI,UAAU,CAAA,CAAA,EAAI,YAAA,CAAa,QAAQ,CAAC,CAAA,CAAA;AAErF,SAAS,eAAA,CAAgB,QAAyB,IAAA,EAA6D;AACpH,EAAA,MAAM,QAAA,GAAW,IAAA,CAAK,QAAA,IAAY,EAAA,GAAK,GAAA;AACvC,EAAA,MAAM,YAAY,IAAA,CAAK,GAAA,CAAI,IAAA,CAAK,SAAA,IAAa,qBAAqB,mBAAmB,CAAA;AAErF,EAAA,OAAO,OAAO,GAAA,KAAQ;AACpB,IAAA,IAAI;AACF,MAAA,IAAI,GAAA,CAAI,WAAW,MAAA,EAAQ,MAAM,IAAI,SAAA,CAAU,GAAA,EAAK,sBAAsB,WAAW,CAAA;AACrF,MAAA,IAAI,CAAC,MAAA,EAAQ,MAAM,IAAI,SAAA,CAAU,GAAA,EAAK,kBAAkB,+CAA+C,CAAA;AACvG,MAAA,MAAM,GAAA,GAAM,MAAM,IAAA,CAAK,SAAA,CAAU,GAAG,CAAA;AACpC,MAAA,IAAI,CAAC,GAAA,EAAK,MAAM,IAAI,SAAA,CAAU,GAAA,EAAK,gBAAgB,eAAe,CAAA;AAClE,MAAA,MAAM,MAAA,GAAS,WAAA,CAAY,GAAA,CAAI,MAAM,CAAA;AAIrC,MAAA,IAAI,CAAA,CAAE,GAAA,CAAI,OAAA,CAAQ,GAAA,CAAI,cAAc,KAAK,EAAA,EAAI,WAAA,EAAY,CAAE,UAAA,CAAW,kBAAkB,CAAA;AACtF,QAAA,MAAM,IAAI,SAAA,CAAU,GAAA,EAAK,eAAA,EAAiB,uCAAuC,CAAA;AACnF,MAAA,IAAI,IAAA;AACJ,MAAA,IAAI;AAAE,QAAA,IAAA,GAAQ,MAAM,IAAI,IAAA,EAAK;AAAA,MAA+B,CAAA,CAAA,MAAQ;AAAE,QAAA,MAAM,IAAI,SAAA,CAAU,GAAA,EAAK,UAAA,EAAY,mBAAmB,CAAA;AAAA,MAAG;AACjI,MAAA,IAAI,CAAC,IAAA,IAAQ,OAAO,IAAA,KAAS,QAAA,QAAgB,IAAI,SAAA,CAAU,GAAA,EAAK,UAAA,EAAY,4BAA4B,CAAA;AAExG,MAAA,QAAQ,KAAK,MAAA;AAAQ,QACnB,KAAK,OAAA,EAAS;AACZ,UAAA,MAAM,WAAW,OAAO,IAAA,CAAK,QAAA,KAAa,QAAA,GAAW,KAAK,QAAA,GAAW,EAAA;AACrE,UAAA,MAAM,OAAO,IAAA,CAAK,IAAA;AAClB,UAAA,MAAM,WAAA,GAAc,OAAO,IAAA,CAAK,IAAA,KAAS,YAAY,IAAA,CAAK,IAAA,GAAO,KAAK,IAAA,GAAO,0BAAA;AAC7E,UAAA,IAAI,CAAC,QAAA,EAAU,MAAM,IAAI,SAAA,CAAU,GAAA,EAAK,gBAAgB,sBAAsB,CAAA;AAC9E,UAAA,IAAI,OAAO,IAAA,KAAS,QAAA,IAAY,CAAC,OAAO,QAAA,CAAS,IAAI,CAAA,IAAK,IAAA,GAAO,GAAG,MAAM,IAAI,SAAA,CAAU,GAAA,EAAK,YAAY,gCAAgC,CAAA;AACzI,UAAA,IAAI,IAAA,GAAO,UAAU,MAAM,IAAI,UAAU,GAAA,EAAK,WAAA,EAAa,CAAA,oBAAA,EAAuB,QAAQ,CAAA,MAAA,CAAQ,CAAA;AAClG,UAAA,IAAI,CAAC,WAAA,CAAY,WAAA,EAAa,IAAA,CAAK,YAAY,CAAA,EAAG,MAAM,IAAI,SAAA,CAAU,GAAA,EAAK,kBAAA,EAAoB,CAAA,EAAG,WAAW,CAAA,qBAAA,CAAuB,CAAA;AACpI,UAAA,MAAM,GAAA,GAAM,QAAA,CAAA,CAAU,IAAA,CAAK,MAAA,IAAU,aAAA,EAAe,EAAE,MAAA,EAAQ,QAAA,EAAU,WAAA,EAAa,CAAA,EAAG,MAAM,CAAA;AAC9F,UAAA,MAAM,QAAA,GAAW,cAAA,CAAe,IAAA,EAAM,IAAA,CAAK,QAAQ,CAAA;AACnD,UAAA,MAAM,EAAE,UAAS,GAAI,MAAM,OAAO,KAAA,CAAM,EAAE,GAAA,EAAK,WAAA,EAAa,CAAA;AAC5D,UAAA,OAAO,IAAA,CAAK,GAAA,EAAK,EAAE,GAAA,EAAK,QAAA,EAAU,QAAA,EAAU,SAAA,EAAW,IAAA,CAAK,IAAA,CAAK,IAAA,GAAO,QAAQ,CAAA,EAAG,CAAA;AAAA,QACrF;AAAA,QACA,KAAK,MAAA,EAAQ;AACX,UAAA,MAAM,GAAA,GAAM,QAAA,CAAS,IAAA,CAAK,GAAA,EAAK,MAAM,CAAA;AACrC,UAAA,MAAM,QAAA,GAAW,aAAA,CAAc,IAAA,CAAK,QAAQ,CAAA;AAC5C,UAAA,MAAM,OAAO,IAAA,CAAK,WAAA;AAClB,UAAA,IAAI,CAAC,MAAM,OAAA,CAAQ,IAAI,KAAK,IAAA,CAAK,MAAA,GAAS,CAAA,IAAK,IAAA,CAAK,MAAA,GAAS,cAAA,IACzD,CAAC,IAAA,CAAK,KAAA,CAAM,CAAC,CAAA,KAAM,MAAA,CAAO,SAAA,CAAU,CAAC,CAAA,IAAK,CAAA,IAAK,CAAA,IAAK,CAAA,IAAK,SAAS,CAAA;AACpE,YAAA,MAAM,IAAI,UAAU,GAAA,EAAK,WAAA,EAAa,mBAAmB,cAAc,CAAA,gBAAA,EAAmB,SAAS,CAAA,CAAE,CAAA;AACvG,UAAA,MAAM,OAAO,MAAM,OAAA,CAAQ,IAAK,IAAA,CAAkB,GAAA,CAAI,OAAO,UAAA,MAAgB;AAAA,YAC3E,UAAA;AAAA,YAAY,GAAA,EAAK,MAAM,MAAA,CAAO,QAAA,CAAS,EAAE,GAAA,EAAK,QAAA,EAAU,UAAA,EAAY,SAAA,EAAW;AAAA,YAC/E,CAAC,CAAA;AACH,UAAA,OAAO,IAAA,CAAK,GAAA,EAAK,EAAE,IAAA,EAAM,WAAW,CAAA;AAAA,QACtC;AAAA,QACA,KAAK,MAAA,EAAQ;AACX,UAAA,MAAM,GAAA,GAAM,QAAA,CAAS,IAAA,CAAK,GAAA,EAAK,MAAM,CAAA;AACrC,UAAA,MAAM,KAAA,GAAQ,MAAM,MAAA,CAAO,SAAA,CAAU,EAAE,GAAA,EAAK,QAAA,EAAU,aAAA,CAAc,IAAA,CAAK,QAAQ,CAAA,EAAG,CAAA;AACpF,UAAA,OAAO,IAAA,CAAK,GAAA,EAAK,EAAE,KAAA,EAAO,CAAA;AAAA,QAC5B;AAAA,QACA,KAAK,UAAA,EAAY;AACf,UAAA,MAAM,GAAA,GAAM,QAAA,CAAS,IAAA,CAAK,GAAA,EAAK,MAAM,CAAA;AACrC,UAAA,MAAM,QAAA,GAAW,aAAA,CAAc,IAAA,CAAK,QAAQ,CAAA;AAC5C,UAAA,MAAM,OAAO,IAAA,CAAK,KAAA;AAClB,UAAA,IAAI,CAAC,MAAM,OAAA,CAAQ,IAAI,KAAK,IAAA,CAAK,MAAA,GAAS,CAAA,IAAK,IAAA,CAAK,MAAA,GAAS,SAAA;AAC3D,YAAA,MAAM,IAAI,SAAA,CAAU,GAAA,EAAK,WAAA,EAAa,oBAAoB,CAAA;AAE5D,UAAA,MAAM,SAAS,MAAM,MAAA,CAAO,UAAU,EAAE,GAAA,EAAK,UAAU,CAAA;AACvD,UAAA,MAAM,KAAA,GAAQ,MAAA,CAAO,MAAA,CAAO,CAAC,CAAA,EAAG,MAAM,CAAA,IAAK,CAAA,CAAE,IAAA,IAAQ,CAAA,CAAA,EAAI,CAAC,CAAA;AAC1D,UAAA,IAAI,QAAQ,QAAA,EAAU;AACpB,YAAA,MAAM,MAAA,CAAO,KAAA,CAAM,EAAE,GAAA,EAAK,UAAU,CAAA;AACpC,YAAA,MAAM,IAAI,SAAA,CAAU,GAAA,EAAK,WAAA,EAAa,CAAA,gBAAA,EAAmB,QAAQ,CAAA,wBAAA,CAA0B,CAAA;AAAA,UAC7F;AACA,UAAA,MAAM,KAAA,GAAyB,IAAA,CAAyD,GAAA,CAAI,CAAC,CAAA,KAAM;AACjG,YAAA,IAAI,CAAC,MAAA,CAAO,SAAA,CAAU,CAAA,EAAG,UAAU,CAAA,IAAK,OAAO,CAAA,EAAG,IAAA,KAAS,UAAU,MAAM,IAAI,SAAA,CAAU,GAAA,EAAK,aAAa,qCAAqC,CAAA;AAChJ,YAAA,OAAO,EAAE,UAAA,EAAY,CAAA,CAAE,UAAA,EAAsB,IAAA,EAAM,EAAE,IAAA,EAAK;AAAA,UAC5D,CAAC,CAAA;AACD,UAAA,MAAM,OAAO,QAAA,CAAS,EAAE,GAAA,EAAK,QAAA,EAAU,OAAO,CAAA;AAC9C,UAAA,OAAO,KAAK,GAAA,EAAK,EAAE,GAAA,EAAK,IAAA,EAAM,OAAO,CAAA;AAAA,QACvC;AAAA,QACA,KAAK,OAAA,EAAS;AACZ,UAAA,MAAM,GAAA,GAAM,QAAA,CAAS,IAAA,CAAK,GAAA,EAAK,MAAM,CAAA;AACrC,UAAA,MAAM,MAAA,CAAO,MAAM,EAAE,GAAA,EAAK,UAAU,aAAA,CAAc,IAAA,CAAK,QAAQ,CAAA,EAAG,CAAA;AAClE,UAAA,OAAO,IAAA,CAAK,GAAA,EAAK,EAAE,OAAA,EAAS,MAAM,CAAA;AAAA,QACpC;AAAA,QACA,KAAK,UAAA,EAAY;AACf,UAAA,MAAM,GAAA,GAAM,QAAA,CAAS,IAAA,CAAK,GAAA,EAAK,MAAM,CAAA;AACrC,UAAA,MAAM,QAAA,GAAW,OAAO,IAAA,CAAK,QAAA,KAAa,QAAA,IAAY,KAAK,QAAA,GAAW,YAAA,CAAa,IAAA,CAAK,QAAQ,CAAA,GAAI,KAAA,CAAA;AACpG,UAAA,MAAM,GAAA,GAAM,MAAM,MAAA,CAAO,YAAA,CAAa,EAAE,GAAA,EAAK,SAAA,EAAW,UAAU,CAAA;AAClE,UAAA,OAAO,IAAA,CAAK,GAAA,EAAK,EAAE,GAAA,EAAK,WAAW,CAAA;AAAA,QACrC;AAAA,QACA;AACE,UAAA,MAAM,IAAI,SAAA,CAAU,GAAA,EAAK,gBAAA,EAAkB,+DAA+D,CAAA;AAAA;AAC9G,IACF,SAAS,CAAA,EAAG;AACV,MAAA,IAAI,aAAa,SAAA,EAAW,OAAO,IAAA,CAAK,CAAA,CAAE,QAAQ,EAAE,KAAA,EAAO,EAAE,IAAA,EAAM,EAAE,IAAA,EAAM,OAAA,EAAS,CAAA,CAAE,OAAA,IAAW,CAAA;AACjG,MAAA,IAAI,aAAa,OAAA,EAAS;AACxB,QAAA,MAAM,MAAA,GAAS,EAAE,MAAA,KAAW,cAAA,GAAiB,MAAM,CAAA,CAAE,MAAA,KAAW,cAAc,GAAA,GAAM,GAAA;AACpF,QAAA,OAAO,IAAA,CAAK,MAAA,EAAQ,EAAE,KAAA,EAAO,EAAE,IAAA,EAAM,CAAA,CAAE,MAAA,IAAU,eAAA,EAAiB,SAAS,MAAA,KAAW,GAAA,GAAM,4BAAA,GAA+B,6BAAA,IAAiC,CAAA;AAAA,MAC9J;AAEA,MAAA,OAAO,IAAA,CAAK,GAAA,EAAK,EAAE,KAAA,EAAO,EAAE,MAAM,UAAA,EAAY,OAAA,EAAS,sBAAA,EAAuB,EAAG,CAAA;AAAA,IACnF;AAAA,EACF,CAAA;AACF","file":"index.cjs","sourcesContent":["// F111.1 — the server half of @broberg/s3. It holds the storage key and only ever\n// hands the browser SHORT-LIVED, ONE-OBJECT signatures. Bytes never pass through\n// the site's own server: the browser talks to the bucket directly.\nimport { AwsClient } from \"aws4fetch\";\n\n/** Where a bucket lives and how to sign for it. R2 in the EU is the default. */\nexport interface S3Config {\n bucket: string;\n accessKeyId: string;\n secretAccessKey: string;\n /** Cloudflare R2 account id → https://<id>.eu.r2.cloudflarestorage.com */\n accountId?: string;\n /** R2 jurisdiction. Defaults to \"eu\"; a non-EU R2 bucket must say \"default\" explicitly. */\n jurisdiction?: \"eu\" | \"default\";\n /** Any other S3-compatible endpoint origin (Tigris, AWS, MinIO), e.g. \"https://fly.storage.tigris.dev\". */\n endpoint?: string;\n /** Signing region. \"auto\" for R2 and Tigris (default); the bucket's region for AWS. */\n region?: string;\n /** Injected for tests; defaults to the global fetch. */\n fetch?: typeof fetch;\n}\n\nexport interface UploadedPart { partNumber: number; etag: string; size?: number }\n\nexport const MIN_PART_SIZE = 8 * 1024 * 1024;\nexport const DEFAULT_PART_SIZE = 16 * 1024 * 1024;\nexport const MAX_PARTS = 10_000;\n/** The longest a signed URL may live. A leaked link is useless after this. */\nexport const MAX_EXPIRES_SECONDS = 900;\nconst MiB = 1024 * 1024;\n\n/** Part size for `size` bytes: at least `preferred` (and 8 MiB), and few enough parts for S3's 10,000 cap. */\nexport function choosePartSize(size: number, preferred = DEFAULT_PART_SIZE): number {\n const need = Math.ceil(size / MAX_PARTS);\n return Math.ceil(Math.max(preferred, MIN_PART_SIZE, need) / MiB) * MiB;\n}\n\nconst encodeKey = (key: string) => key.split(\"/\").map(encodeURIComponent).join(\"/\");\nconst xmlText = (xml: string, tag: string) => xml.match(new RegExp(`<${tag}>([^<]*)</${tag}>`))?.[1];\nconst xmlEscape = (s: string) => s.replace(/&/g, \"&amp;\").replace(/</g, \"&lt;\").replace(/>/g, \"&gt;\").replace(/\"/g, \"&quot;\");\nconst xmlUnescape = (s: string) => s.replace(/&quot;/g, '\"').replace(/&lt;/g, \"<\").replace(/&gt;/g, \">\").replace(/&amp;/g, \"&\");\n\nexport interface S3Signer {\n /** Begin a multipart upload. */\n start(input: { key: string; contentType?: string }): Promise<{ uploadId: string }>;\n /** A presigned PUT for one part of one upload. */\n signPart(input: { key: string; uploadId: string; partNumber: number; expiresIn?: number }): Promise<string>;\n /** Parts the bucket already holds for this upload (the truth a resume starts from). */\n listParts(input: { key: string; uploadId: string }): Promise<UploadedPart[]>;\n complete(input: { key: string; uploadId: string; parts: UploadedPart[] }): Promise<void>;\n abort(input: { key: string; uploadId: string }): Promise<void>;\n /** A presigned GET; `filename` makes the browser save it under that name. */\n signDownload(input: { key: string; expiresIn?: number; filename?: string }): Promise<string>;\n /** Size of a stored object, or null when it does not exist. */\n head(key: string): Promise<number | null>;\n}\n\nexport class S3Error extends Error {\n constructor(message: string, readonly status: number, readonly s3Code?: string) {\n super(message);\n this.name = \"S3Error\";\n }\n}\n\nfunction originOf(cfg: S3Config): string {\n if (cfg.endpoint) return cfg.endpoint.replace(/\\/+$/, \"\");\n if (!cfg.accountId) throw new Error(\"@broberg/s3: set accountId (R2) or endpoint (other S3-compatible storage)\");\n const j = cfg.jurisdiction ?? \"eu\";\n return j === \"eu\" ? `https://${cfg.accountId}.eu.r2.cloudflarestorage.com` : `https://${cfg.accountId}.r2.cloudflarestorage.com`;\n}\n\nexport function createS3Signer(cfg: S3Config): S3Signer {\n for (const f of [\"bucket\", \"accessKeyId\", \"secretAccessKey\"] as const)\n if (!cfg[f]) throw new Error(`@broberg/s3: ${f} is required`);\n const origin = originOf(cfg);\n const doFetch = cfg.fetch ?? ((r: Request) => fetch(r));\n const aws = new AwsClient({\n accessKeyId: cfg.accessKeyId,\n secretAccessKey: cfg.secretAccessKey,\n region: cfg.region ?? \"auto\",\n service: \"s3\",\n });\n const objectUrl = (key: string) => new URL(`${origin}/${encodeURIComponent(cfg.bucket)}/${encodeKey(key)}`);\n const clampExpiry = (s?: number) => Math.max(1, Math.min(s ?? MAX_EXPIRES_SECONDS, MAX_EXPIRES_SECONDS));\n\n async function call(method: string, url: URL, body?: string, headers?: Record<string, string>): Promise<string> {\n const signed = await aws.sign(url.toString(), { method, body, headers });\n const res = await doFetch(signed);\n const text = await res.text();\n // S3 can answer 200 with an <Error> body (CompleteMultipartUpload does).\n if (!res.ok || /<Error>/.test(text)) {\n const code = xmlText(text, \"Code\");\n throw new S3Error(`@broberg/s3: ${method} ${url.pathname} → ${res.status}${code ? ` ${code}` : \"\"}`, res.ok ? 502 : res.status, code);\n }\n return text;\n }\n\n async function presign(method: \"PUT\" | \"GET\", url: URL, expiresIn?: number): Promise<string> {\n url.searchParams.set(\"X-Amz-Expires\", String(clampExpiry(expiresIn)));\n const signed = await aws.sign(url.toString(), { method, aws: { signQuery: true } });\n return signed.url;\n }\n\n return {\n async start({ key, contentType }) {\n const url = objectUrl(key);\n url.searchParams.set(\"uploads\", \"\");\n const xml = await call(\"POST\", url, undefined, contentType ? { \"content-type\": contentType } : undefined);\n const uploadId = xmlText(xml, \"UploadId\");\n if (!uploadId) throw new S3Error(\"@broberg/s3: CreateMultipartUpload returned no UploadId\", 502);\n return { uploadId: xmlUnescape(uploadId) };\n },\n async signPart({ key, uploadId, partNumber, expiresIn }) {\n if (!Number.isInteger(partNumber) || partNumber < 1 || partNumber > MAX_PARTS)\n throw new S3Error(`@broberg/s3: partNumber must be 1..${MAX_PARTS}`, 400);\n const url = objectUrl(key);\n url.searchParams.set(\"partNumber\", String(partNumber));\n url.searchParams.set(\"uploadId\", uploadId);\n return presign(\"PUT\", url, expiresIn);\n },\n async listParts({ key, uploadId }) {\n const parts: UploadedPart[] = [];\n let marker: string | undefined;\n for (;;) {\n const url = objectUrl(key);\n url.searchParams.set(\"uploadId\", uploadId);\n if (marker) url.searchParams.set(\"part-number-marker\", marker);\n const xml = await call(\"GET\", url);\n for (const m of xml.matchAll(/<Part>([\\s\\S]*?)<\\/Part>/g)) {\n parts.push({\n partNumber: Number(xmlText(m[1]!, \"PartNumber\")),\n etag: xmlUnescape(xmlText(m[1]!, \"ETag\") ?? \"\"),\n size: Number(xmlText(m[1]!, \"Size\")),\n });\n }\n if (xmlText(xml, \"IsTruncated\") !== \"true\") break;\n marker = xmlText(xml, \"NextPartNumberMarker\");\n if (!marker) break;\n }\n return parts;\n },\n async complete({ key, uploadId, parts }) {\n const sorted = [...parts].sort((a, b) => a.partNumber - b.partNumber);\n const body =\n \"<CompleteMultipartUpload>\" +\n sorted.map((p) => `<Part><PartNumber>${p.partNumber}</PartNumber><ETag>${xmlEscape(p.etag)}</ETag></Part>`).join(\"\") +\n \"</CompleteMultipartUpload>\";\n const url = objectUrl(key);\n url.searchParams.set(\"uploadId\", uploadId);\n await call(\"POST\", url, body, { \"content-type\": \"application/xml\" });\n },\n async abort({ key, uploadId }) {\n const url = objectUrl(key);\n url.searchParams.set(\"uploadId\", uploadId);\n await call(\"DELETE\", url);\n },\n async signDownload({ key, expiresIn, filename }) {\n const url = objectUrl(key);\n if (filename) {\n const ascii = filename.replace(/[^\\x20-\\x7e]|[\"\\\\]/g, \"_\");\n url.searchParams.set(\"response-content-disposition\", `attachment; filename=\"${ascii}\"; filename*=UTF-8''${encodeURIComponent(filename)}`);\n }\n return presign(\"GET\", url, expiresIn);\n },\n async head(key) {\n const signed = await aws.sign(objectUrl(key).toString(), { method: \"HEAD\" });\n const res = await doFetch(signed);\n if (res.status === 404) return null;\n if (!res.ok) throw new S3Error(`@broberg/s3: HEAD → ${res.status}`, res.status);\n return Number(res.headers.get(\"content-length\"));\n },\n };\n}\n\ntype Env = Record<string, string | undefined>;\n\n/**\n * Builds a signer from the environment, or returns null when NOTHING is configured\n * (ship dark: the handler then answers 503). A HALF-configured environment throws\n * at boot instead — that is a mistake, not a choice.\n *\n * R2: R2_ACCOUNT_ID, R2_ACCESS_KEY_ID, R2_SECRET_ACCESS_KEY, R2_BUCKET [, R2_JURISDICTION]\n * S3: S3_ENDPOINT, S3_ACCESS_KEY_ID, S3_SECRET_ACCESS_KEY, S3_BUCKET [, S3_REGION]\n */\nexport function createS3SignerFromEnv(env: Env = (globalThis as { process?: { env: Env } }).process?.env ?? {}): S3Signer | null {\n const r2 = [\"R2_ACCOUNT_ID\", \"R2_ACCESS_KEY_ID\", \"R2_SECRET_ACCESS_KEY\", \"R2_BUCKET\"];\n const s3 = [\"S3_ENDPOINT\", \"S3_ACCESS_KEY_ID\", \"S3_SECRET_ACCESS_KEY\", \"S3_BUCKET\"];\n const pick = (names: string[]) => names.filter((n) => env[n]);\n const r2Set = pick(r2), s3Set = pick(s3);\n if (r2Set.length === 0 && s3Set.length === 0) return null;\n const [names, set] = r2Set.length >= s3Set.length ? [r2, r2Set] : [s3, s3Set];\n if (set.length !== names.length)\n throw new Error(`@broberg/s3: incomplete storage config — missing ${names.filter((n) => !env[n]).join(\", \")}`);\n if (names === r2) {\n const j = env.R2_JURISDICTION;\n if (j && j !== \"eu\" && j !== \"default\") throw new Error(`@broberg/s3: R2_JURISDICTION must be \"eu\" or \"default\"`);\n return createS3Signer({\n accountId: env.R2_ACCOUNT_ID, accessKeyId: env.R2_ACCESS_KEY_ID!, secretAccessKey: env.R2_SECRET_ACCESS_KEY!,\n bucket: env.R2_BUCKET!, jurisdiction: (j as \"eu\" | \"default\" | undefined) ?? \"eu\",\n });\n }\n return createS3Signer({\n endpoint: env.S3_ENDPOINT, accessKeyId: env.S3_ACCESS_KEY_ID!, secretAccessKey: env.S3_SECRET_ACCESS_KEY!,\n bucket: env.S3_BUCKET!, region: env.S3_REGION,\n });\n}\n","// F111.1 — ONE fetch handler (Request → Response) for Hono/Bun and Next.\n// Every action asks the SITE's authorize() first and keeps the user inside the\n// object prefix it returns. The storage key never appears in a response.\nimport {\n MAX_EXPIRES_SECONDS, MAX_PARTS, S3Error, choosePartSize,\n type S3Signer, type UploadedPart,\n} from \"./server\";\n\nexport interface Authorized {\n /** Every object this user may touch lives under `<prefix>/`. E.g. \"users/<bid-sub>\". */\n prefix: string;\n}\n\nexport interface S3HandlerOptions {\n /** The site's own check (BID session + allowlist). Return null to refuse. */\n authorize(req: Request): Promise<Authorized | null> | Authorized | null;\n /** Largest object accepted, in bytes. Default 20 GiB. Enforced at start (declared) AND at complete (actual). */\n maxBytes?: number;\n /** Allowed content types, exact (\"video/mp4\") or wildcard (\"video/*\"). Default: any. Declared type, not sniffed. */\n allowedTypes?: string[];\n /** Preferred part size in bytes (min 8 MiB). Default 16 MiB. */\n partSize?: number;\n /** Signed-URL lifetime in seconds, max 900. */\n expiresIn?: number;\n /** Object key for a new upload. Default `<prefix>/<yyyy-mm-dd>/<random>-<safe filename>`. */\n keyFor?(input: { prefix: string; filename: string; contentType: string }): string;\n}\n\nconst GiB = 1024 ** 3;\nconst MAX_SIGN_BATCH = 100;\n\nclass HttpError extends Error {\n constructor(readonly status: number, readonly code: string, message: string) { super(message); }\n}\n\nconst json = (status: number, body: unknown) =>\n new Response(JSON.stringify(body), { status, headers: { \"content-type\": \"application/json\", \"cache-control\": \"no-store\" } });\n\nexport const safeFilename = (name: string) =>\n (name.normalize(\"NFKC\").replace(/[\\\\/\\u0000-\\u001f\\u007f]/g, \"_\").replace(/^\\.+/, \"\").replace(/[^\\p{L}\\p{N}._ -]/gu, \"_\").trim().slice(0, 120)) || \"file\";\n\n/** A prefix must be plain path segments: no \"..\", no empty segments, no encoding tricks. */\nfunction checkPrefix(prefix: unknown): string {\n if (typeof prefix !== \"string\") throw new HttpError(500, \"bad_prefix\", \"authorize() returned no prefix\");\n const p = prefix.replace(/^\\/+|\\/+$/g, \"\");\n if (!p || p.split(\"/\").some((s) => !s || s === \".\" || s === \"..\") || /[%\\\\\\u0000-\\u001f]/.test(p))\n throw new HttpError(500, \"bad_prefix\", \"authorize() returned an unsafe prefix\");\n return p;\n}\n\n/** The key must sit under `<prefix>/` and be free of traversal, encoding and control characters. */\nfunction checkKey(key: unknown, prefix: string): string {\n if (typeof key !== \"string\" || key.length > 1024) throw new HttpError(400, \"bad_key\", \"key is required\");\n const segs = key.split(\"/\");\n if (/[%\\\\\\u0000-\\u001f\\u007f]/.test(key) || segs.some((s) => !s || s === \".\" || s === \"..\") || !key.startsWith(prefix + \"/\"))\n throw new HttpError(403, \"forbidden_key\", \"that object is not yours\");\n return key;\n}\n\nconst checkUploadId = (id: unknown): string => {\n if (typeof id !== \"string\" || !id || id.length > 1024 || /[\\u0000-\\u001f]/.test(id))\n throw new HttpError(400, \"bad_upload_id\", \"uploadId is required\");\n return id;\n};\n\nfunction typeAllowed(type: string, allowed?: string[]): boolean {\n if (!allowed?.length) return true;\n const t = type.toLowerCase();\n return allowed.some((a) => { const x = a.toLowerCase(); return x.endsWith(\"/*\") ? t.startsWith(x.slice(0, -1)) : t === x; });\n}\n\nconst randomId = () => {\n const b = new Uint8Array(9);\n crypto.getRandomValues(b);\n return Array.from(b, (x) => x.toString(16).padStart(2, \"0\")).join(\"\");\n};\n\nconst defaultKeyFor = ({ prefix, filename }: { prefix: string; filename: string }) =>\n `${prefix}/${new Date().toISOString().slice(0, 10)}/${randomId()}-${safeFilename(filename)}`;\n\nexport function createS3Handler(signer: S3Signer | null, opts: S3HandlerOptions): (req: Request) => Promise<Response> {\n const maxBytes = opts.maxBytes ?? 20 * GiB;\n const expiresIn = Math.min(opts.expiresIn ?? MAX_EXPIRES_SECONDS, MAX_EXPIRES_SECONDS);\n\n return async (req) => {\n try {\n if (req.method !== \"POST\") throw new HttpError(405, \"method_not_allowed\", \"POST only\");\n if (!signer) throw new HttpError(503, \"not_configured\", \"file storage is not configured on this server\");\n const who = await opts.authorize(req);\n if (!who) throw new HttpError(401, \"unauthorized\", \"sign in first\");\n const prefix = checkPrefix(who.prefix);\n\n // JSON only: a cross-site <form> can POST text/plain with the user's cookies,\n // but cannot set application/json without a CORS preflight this route never grants.\n if (!(req.headers.get(\"content-type\") ?? \"\").toLowerCase().startsWith(\"application/json\"))\n throw new HttpError(415, \"json_required\", \"content-type must be application/json\");\n let body: Record<string, unknown>;\n try { body = (await req.json()) as Record<string, unknown>; } catch { throw new HttpError(400, \"bad_json\", \"body must be JSON\"); }\n if (!body || typeof body !== \"object\") throw new HttpError(400, \"bad_json\", \"body must be a JSON object\");\n\n switch (body.action) {\n case \"start\": {\n const filename = typeof body.filename === \"string\" ? body.filename : \"\";\n const size = body.size;\n const contentType = typeof body.type === \"string\" && body.type ? body.type : \"application/octet-stream\";\n if (!filename) throw new HttpError(400, \"bad_filename\", \"filename is required\");\n if (typeof size !== \"number\" || !Number.isFinite(size) || size < 1) throw new HttpError(400, \"bad_size\", \"size must be a positive number\");\n if (size > maxBytes) throw new HttpError(413, \"too_large\", `file is larger than ${maxBytes} bytes`);\n if (!typeAllowed(contentType, opts.allowedTypes)) throw new HttpError(415, \"type_not_allowed\", `${contentType} is not accepted here`);\n const key = checkKey((opts.keyFor ?? defaultKeyFor)({ prefix, filename, contentType }), prefix);\n const partSize = choosePartSize(size, opts.partSize);\n const { uploadId } = await signer.start({ key, contentType });\n return json(200, { key, uploadId, partSize, partCount: Math.ceil(size / partSize) });\n }\n case \"sign\": {\n const key = checkKey(body.key, prefix);\n const uploadId = checkUploadId(body.uploadId);\n const nums = body.partNumbers;\n if (!Array.isArray(nums) || nums.length < 1 || nums.length > MAX_SIGN_BATCH ||\n !nums.every((n) => Number.isInteger(n) && n >= 1 && n <= MAX_PARTS))\n throw new HttpError(400, \"bad_parts\", `partNumbers: 1..${MAX_SIGN_BATCH} integers in 1..${MAX_PARTS}`);\n const urls = await Promise.all((nums as number[]).map(async (partNumber) => ({\n partNumber, url: await signer.signPart({ key, uploadId, partNumber, expiresIn }),\n })));\n return json(200, { urls, expiresIn });\n }\n case \"list\": {\n const key = checkKey(body.key, prefix);\n const parts = await signer.listParts({ key, uploadId: checkUploadId(body.uploadId) });\n return json(200, { parts });\n }\n case \"complete\": {\n const key = checkKey(body.key, prefix);\n const uploadId = checkUploadId(body.uploadId);\n const sent = body.parts;\n if (!Array.isArray(sent) || sent.length < 1 || sent.length > MAX_PARTS)\n throw new HttpError(400, \"bad_parts\", \"parts are required\");\n // The bucket is the truth: sizes come from listParts, never from the browser.\n const stored = await signer.listParts({ key, uploadId });\n const total = stored.reduce((s, p) => s + (p.size ?? 0), 0);\n if (total > maxBytes) {\n await signer.abort({ key, uploadId });\n throw new HttpError(413, \"too_large\", `upload exceeded ${maxBytes} bytes and was discarded`);\n }\n const parts: UploadedPart[] = (sent as Array<{ partNumber?: unknown; etag?: unknown }>).map((p) => {\n if (!Number.isInteger(p?.partNumber) || typeof p?.etag !== \"string\") throw new HttpError(400, \"bad_parts\", \"each part needs partNumber and etag\");\n return { partNumber: p.partNumber as number, etag: p.etag };\n });\n await signer.complete({ key, uploadId, parts });\n return json(200, { key, size: total });\n }\n case \"abort\": {\n const key = checkKey(body.key, prefix);\n await signer.abort({ key, uploadId: checkUploadId(body.uploadId) });\n return json(200, { aborted: true });\n }\n case \"download\": {\n const key = checkKey(body.key, prefix);\n const filename = typeof body.filename === \"string\" && body.filename ? safeFilename(body.filename) : undefined;\n const url = await signer.signDownload({ key, expiresIn, filename });\n return json(200, { url, expiresIn });\n }\n default:\n throw new HttpError(400, \"unknown_action\", \"action must be start, sign, list, complete, abort or download\");\n }\n } catch (e) {\n if (e instanceof HttpError) return json(e.status, { error: { code: e.code, message: e.message } });\n if (e instanceof S3Error) {\n const status = e.s3Code === \"NoSuchUpload\" ? 404 : e.s3Code === \"NoSuchKey\" ? 404 : 502;\n return json(status, { error: { code: e.s3Code ?? \"storage_error\", message: status === 404 ? \"upload or object not found\" : \"storage refused the request\" } });\n }\n // Never echo an unknown error: it could carry configuration.\n return json(500, { error: { code: \"internal\", message: \"upload service error\" } });\n }\n };\n}\n"]}
package/dist/index.js CHANGED
@@ -211,6 +211,8 @@ function createS3Handler(signer, opts) {
211
211
  const who = await opts.authorize(req);
212
212
  if (!who) throw new HttpError(401, "unauthorized", "sign in first");
213
213
  const prefix = checkPrefix(who.prefix);
214
+ if (!(req.headers.get("content-type") ?? "").toLowerCase().startsWith("application/json"))
215
+ throw new HttpError(415, "json_required", "content-type must be application/json");
214
216
  let body;
215
217
  try {
216
218
  body = await req.json();
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/server.ts","../src/handler.ts"],"names":["names"],"mappings":"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","file":"index.js","sourcesContent":["// F111.1 — the server half of @broberg/s3. It holds the storage key and only ever\n// hands the browser SHORT-LIVED, ONE-OBJECT signatures. Bytes never pass through\n// the site's own server: the browser talks to the bucket directly.\nimport { AwsClient } from \"aws4fetch\";\n\n/** Where a bucket lives and how to sign for it. R2 in the EU is the default. */\nexport interface S3Config {\n bucket: string;\n accessKeyId: string;\n secretAccessKey: string;\n /** Cloudflare R2 account id → https://<id>.eu.r2.cloudflarestorage.com */\n accountId?: string;\n /** R2 jurisdiction. Defaults to \"eu\"; a non-EU R2 bucket must say \"default\" explicitly. */\n jurisdiction?: \"eu\" | \"default\";\n /** Any other S3-compatible endpoint origin (Tigris, AWS, MinIO), e.g. \"https://fly.storage.tigris.dev\". */\n endpoint?: string;\n /** Signing region. \"auto\" for R2 and Tigris (default); the bucket's region for AWS. */\n region?: string;\n /** Injected for tests; defaults to the global fetch. */\n fetch?: typeof fetch;\n}\n\nexport interface UploadedPart { partNumber: number; etag: string; size?: number }\n\nexport const MIN_PART_SIZE = 8 * 1024 * 1024;\nexport const DEFAULT_PART_SIZE = 16 * 1024 * 1024;\nexport const MAX_PARTS = 10_000;\n/** The longest a signed URL may live. A leaked link is useless after this. */\nexport const MAX_EXPIRES_SECONDS = 900;\nconst MiB = 1024 * 1024;\n\n/** Part size for `size` bytes: at least `preferred` (and 8 MiB), and few enough parts for S3's 10,000 cap. */\nexport function choosePartSize(size: number, preferred = DEFAULT_PART_SIZE): number {\n const need = Math.ceil(size / MAX_PARTS);\n return Math.ceil(Math.max(preferred, MIN_PART_SIZE, need) / MiB) * MiB;\n}\n\nconst encodeKey = (key: string) => key.split(\"/\").map(encodeURIComponent).join(\"/\");\nconst xmlText = (xml: string, tag: string) => xml.match(new RegExp(`<${tag}>([^<]*)</${tag}>`))?.[1];\nconst xmlEscape = (s: string) => s.replace(/&/g, \"&amp;\").replace(/</g, \"&lt;\").replace(/>/g, \"&gt;\").replace(/\"/g, \"&quot;\");\nconst xmlUnescape = (s: string) => s.replace(/&quot;/g, '\"').replace(/&lt;/g, \"<\").replace(/&gt;/g, \">\").replace(/&amp;/g, \"&\");\n\nexport interface S3Signer {\n /** Begin a multipart upload. */\n start(input: { key: string; contentType?: string }): Promise<{ uploadId: string }>;\n /** A presigned PUT for one part of one upload. */\n signPart(input: { key: string; uploadId: string; partNumber: number; expiresIn?: number }): Promise<string>;\n /** Parts the bucket already holds for this upload (the truth a resume starts from). */\n listParts(input: { key: string; uploadId: string }): Promise<UploadedPart[]>;\n complete(input: { key: string; uploadId: string; parts: UploadedPart[] }): Promise<void>;\n abort(input: { key: string; uploadId: string }): Promise<void>;\n /** A presigned GET; `filename` makes the browser save it under that name. */\n signDownload(input: { key: string; expiresIn?: number; filename?: string }): Promise<string>;\n /** Size of a stored object, or null when it does not exist. */\n head(key: string): Promise<number | null>;\n}\n\nexport class S3Error extends Error {\n constructor(message: string, readonly status: number, readonly s3Code?: string) {\n super(message);\n this.name = \"S3Error\";\n }\n}\n\nfunction originOf(cfg: S3Config): string {\n if (cfg.endpoint) return cfg.endpoint.replace(/\\/+$/, \"\");\n if (!cfg.accountId) throw new Error(\"@broberg/s3: set accountId (R2) or endpoint (other S3-compatible storage)\");\n const j = cfg.jurisdiction ?? \"eu\";\n return j === \"eu\" ? `https://${cfg.accountId}.eu.r2.cloudflarestorage.com` : `https://${cfg.accountId}.r2.cloudflarestorage.com`;\n}\n\nexport function createS3Signer(cfg: S3Config): S3Signer {\n for (const f of [\"bucket\", \"accessKeyId\", \"secretAccessKey\"] as const)\n if (!cfg[f]) throw new Error(`@broberg/s3: ${f} is required`);\n const origin = originOf(cfg);\n const doFetch = cfg.fetch ?? ((r: Request) => fetch(r));\n const aws = new AwsClient({\n accessKeyId: cfg.accessKeyId,\n secretAccessKey: cfg.secretAccessKey,\n region: cfg.region ?? \"auto\",\n service: \"s3\",\n });\n const objectUrl = (key: string) => new URL(`${origin}/${encodeURIComponent(cfg.bucket)}/${encodeKey(key)}`);\n const clampExpiry = (s?: number) => Math.max(1, Math.min(s ?? MAX_EXPIRES_SECONDS, MAX_EXPIRES_SECONDS));\n\n async function call(method: string, url: URL, body?: string, headers?: Record<string, string>): Promise<string> {\n const signed = await aws.sign(url.toString(), { method, body, headers });\n const res = await doFetch(signed);\n const text = await res.text();\n // S3 can answer 200 with an <Error> body (CompleteMultipartUpload does).\n if (!res.ok || /<Error>/.test(text)) {\n const code = xmlText(text, \"Code\");\n throw new S3Error(`@broberg/s3: ${method} ${url.pathname} → ${res.status}${code ? ` ${code}` : \"\"}`, res.ok ? 502 : res.status, code);\n }\n return text;\n }\n\n async function presign(method: \"PUT\" | \"GET\", url: URL, expiresIn?: number): Promise<string> {\n url.searchParams.set(\"X-Amz-Expires\", String(clampExpiry(expiresIn)));\n const signed = await aws.sign(url.toString(), { method, aws: { signQuery: true } });\n return signed.url;\n }\n\n return {\n async start({ key, contentType }) {\n const url = objectUrl(key);\n url.searchParams.set(\"uploads\", \"\");\n const xml = await call(\"POST\", url, undefined, contentType ? { \"content-type\": contentType } : undefined);\n const uploadId = xmlText(xml, \"UploadId\");\n if (!uploadId) throw new S3Error(\"@broberg/s3: CreateMultipartUpload returned no UploadId\", 502);\n return { uploadId: xmlUnescape(uploadId) };\n },\n async signPart({ key, uploadId, partNumber, expiresIn }) {\n if (!Number.isInteger(partNumber) || partNumber < 1 || partNumber > MAX_PARTS)\n throw new S3Error(`@broberg/s3: partNumber must be 1..${MAX_PARTS}`, 400);\n const url = objectUrl(key);\n url.searchParams.set(\"partNumber\", String(partNumber));\n url.searchParams.set(\"uploadId\", uploadId);\n return presign(\"PUT\", url, expiresIn);\n },\n async listParts({ key, uploadId }) {\n const parts: UploadedPart[] = [];\n let marker: string | undefined;\n for (;;) {\n const url = objectUrl(key);\n url.searchParams.set(\"uploadId\", uploadId);\n if (marker) url.searchParams.set(\"part-number-marker\", marker);\n const xml = await call(\"GET\", url);\n for (const m of xml.matchAll(/<Part>([\\s\\S]*?)<\\/Part>/g)) {\n parts.push({\n partNumber: Number(xmlText(m[1]!, \"PartNumber\")),\n etag: xmlUnescape(xmlText(m[1]!, \"ETag\") ?? \"\"),\n size: Number(xmlText(m[1]!, \"Size\")),\n });\n }\n if (xmlText(xml, \"IsTruncated\") !== \"true\") break;\n marker = xmlText(xml, \"NextPartNumberMarker\");\n if (!marker) break;\n }\n return parts;\n },\n async complete({ key, uploadId, parts }) {\n const sorted = [...parts].sort((a, b) => a.partNumber - b.partNumber);\n const body =\n \"<CompleteMultipartUpload>\" +\n sorted.map((p) => `<Part><PartNumber>${p.partNumber}</PartNumber><ETag>${xmlEscape(p.etag)}</ETag></Part>`).join(\"\") +\n \"</CompleteMultipartUpload>\";\n const url = objectUrl(key);\n url.searchParams.set(\"uploadId\", uploadId);\n await call(\"POST\", url, body, { \"content-type\": \"application/xml\" });\n },\n async abort({ key, uploadId }) {\n const url = objectUrl(key);\n url.searchParams.set(\"uploadId\", uploadId);\n await call(\"DELETE\", url);\n },\n async signDownload({ key, expiresIn, filename }) {\n const url = objectUrl(key);\n if (filename) {\n const ascii = filename.replace(/[^\\x20-\\x7e]|[\"\\\\]/g, \"_\");\n url.searchParams.set(\"response-content-disposition\", `attachment; filename=\"${ascii}\"; filename*=UTF-8''${encodeURIComponent(filename)}`);\n }\n return presign(\"GET\", url, expiresIn);\n },\n async head(key) {\n const signed = await aws.sign(objectUrl(key).toString(), { method: \"HEAD\" });\n const res = await doFetch(signed);\n if (res.status === 404) return null;\n if (!res.ok) throw new S3Error(`@broberg/s3: HEAD → ${res.status}`, res.status);\n return Number(res.headers.get(\"content-length\"));\n },\n };\n}\n\ntype Env = Record<string, string | undefined>;\n\n/**\n * Builds a signer from the environment, or returns null when NOTHING is configured\n * (ship dark: the handler then answers 503). A HALF-configured environment throws\n * at boot instead — that is a mistake, not a choice.\n *\n * R2: R2_ACCOUNT_ID, R2_ACCESS_KEY_ID, R2_SECRET_ACCESS_KEY, R2_BUCKET [, R2_JURISDICTION]\n * S3: S3_ENDPOINT, S3_ACCESS_KEY_ID, S3_SECRET_ACCESS_KEY, S3_BUCKET [, S3_REGION]\n */\nexport function createS3SignerFromEnv(env: Env = (globalThis as { process?: { env: Env } }).process?.env ?? {}): S3Signer | null {\n const r2 = [\"R2_ACCOUNT_ID\", \"R2_ACCESS_KEY_ID\", \"R2_SECRET_ACCESS_KEY\", \"R2_BUCKET\"];\n const s3 = [\"S3_ENDPOINT\", \"S3_ACCESS_KEY_ID\", \"S3_SECRET_ACCESS_KEY\", \"S3_BUCKET\"];\n const pick = (names: string[]) => names.filter((n) => env[n]);\n const r2Set = pick(r2), s3Set = pick(s3);\n if (r2Set.length === 0 && s3Set.length === 0) return null;\n const [names, set] = r2Set.length >= s3Set.length ? [r2, r2Set] : [s3, s3Set];\n if (set.length !== names.length)\n throw new Error(`@broberg/s3: incomplete storage config — missing ${names.filter((n) => !env[n]).join(\", \")}`);\n if (names === r2) {\n const j = env.R2_JURISDICTION;\n if (j && j !== \"eu\" && j !== \"default\") throw new Error(`@broberg/s3: R2_JURISDICTION must be \"eu\" or \"default\"`);\n return createS3Signer({\n accountId: env.R2_ACCOUNT_ID, accessKeyId: env.R2_ACCESS_KEY_ID!, secretAccessKey: env.R2_SECRET_ACCESS_KEY!,\n bucket: env.R2_BUCKET!, jurisdiction: (j as \"eu\" | \"default\" | undefined) ?? \"eu\",\n });\n }\n return createS3Signer({\n endpoint: env.S3_ENDPOINT, accessKeyId: env.S3_ACCESS_KEY_ID!, secretAccessKey: env.S3_SECRET_ACCESS_KEY!,\n bucket: env.S3_BUCKET!, region: env.S3_REGION,\n });\n}\n","// F111.1 — ONE fetch handler (Request → Response) for Hono/Bun and Next.\n// Every action asks the SITE's authorize() first and keeps the user inside the\n// object prefix it returns. The storage key never appears in a response.\nimport {\n MAX_EXPIRES_SECONDS, MAX_PARTS, S3Error, choosePartSize,\n type S3Signer, type UploadedPart,\n} from \"./server\";\n\nexport interface Authorized {\n /** Every object this user may touch lives under `<prefix>/`. E.g. \"users/<bid-sub>\". */\n prefix: string;\n}\n\nexport interface S3HandlerOptions {\n /** The site's own check (BID session + allowlist). Return null to refuse. */\n authorize(req: Request): Promise<Authorized | null> | Authorized | null;\n /** Largest object accepted, in bytes. Default 20 GiB. Enforced at start (declared) AND at complete (actual). */\n maxBytes?: number;\n /** Allowed content types, exact (\"video/mp4\") or wildcard (\"video/*\"). Default: any. Declared type, not sniffed. */\n allowedTypes?: string[];\n /** Preferred part size in bytes (min 8 MiB). Default 16 MiB. */\n partSize?: number;\n /** Signed-URL lifetime in seconds, max 900. */\n expiresIn?: number;\n /** Object key for a new upload. Default `<prefix>/<yyyy-mm-dd>/<random>-<safe filename>`. */\n keyFor?(input: { prefix: string; filename: string; contentType: string }): string;\n}\n\nconst GiB = 1024 ** 3;\nconst MAX_SIGN_BATCH = 100;\n\nclass HttpError extends Error {\n constructor(readonly status: number, readonly code: string, message: string) { super(message); }\n}\n\nconst json = (status: number, body: unknown) =>\n new Response(JSON.stringify(body), { status, headers: { \"content-type\": \"application/json\", \"cache-control\": \"no-store\" } });\n\nexport const safeFilename = (name: string) =>\n (name.normalize(\"NFKC\").replace(/[\\\\/\\u0000-\\u001f\\u007f]/g, \"_\").replace(/^\\.+/, \"\").replace(/[^\\p{L}\\p{N}._ -]/gu, \"_\").trim().slice(0, 120)) || \"file\";\n\n/** A prefix must be plain path segments: no \"..\", no empty segments, no encoding tricks. */\nfunction checkPrefix(prefix: unknown): string {\n if (typeof prefix !== \"string\") throw new HttpError(500, \"bad_prefix\", \"authorize() returned no prefix\");\n const p = prefix.replace(/^\\/+|\\/+$/g, \"\");\n if (!p || p.split(\"/\").some((s) => !s || s === \".\" || s === \"..\") || /[%\\\\\\u0000-\\u001f]/.test(p))\n throw new HttpError(500, \"bad_prefix\", \"authorize() returned an unsafe prefix\");\n return p;\n}\n\n/** The key must sit under `<prefix>/` and be free of traversal, encoding and control characters. */\nfunction checkKey(key: unknown, prefix: string): string {\n if (typeof key !== \"string\" || key.length > 1024) throw new HttpError(400, \"bad_key\", \"key is required\");\n const segs = key.split(\"/\");\n if (/[%\\\\\\u0000-\\u001f\\u007f]/.test(key) || segs.some((s) => !s || s === \".\" || s === \"..\") || !key.startsWith(prefix + \"/\"))\n throw new HttpError(403, \"forbidden_key\", \"that object is not yours\");\n return key;\n}\n\nconst checkUploadId = (id: unknown): string => {\n if (typeof id !== \"string\" || !id || id.length > 1024 || /[\\u0000-\\u001f]/.test(id))\n throw new HttpError(400, \"bad_upload_id\", \"uploadId is required\");\n return id;\n};\n\nfunction typeAllowed(type: string, allowed?: string[]): boolean {\n if (!allowed?.length) return true;\n const t = type.toLowerCase();\n return allowed.some((a) => { const x = a.toLowerCase(); return x.endsWith(\"/*\") ? t.startsWith(x.slice(0, -1)) : t === x; });\n}\n\nconst randomId = () => {\n const b = new Uint8Array(9);\n crypto.getRandomValues(b);\n return Array.from(b, (x) => x.toString(16).padStart(2, \"0\")).join(\"\");\n};\n\nconst defaultKeyFor = ({ prefix, filename }: { prefix: string; filename: string }) =>\n `${prefix}/${new Date().toISOString().slice(0, 10)}/${randomId()}-${safeFilename(filename)}`;\n\nexport function createS3Handler(signer: S3Signer | null, opts: S3HandlerOptions): (req: Request) => Promise<Response> {\n const maxBytes = opts.maxBytes ?? 20 * GiB;\n const expiresIn = Math.min(opts.expiresIn ?? MAX_EXPIRES_SECONDS, MAX_EXPIRES_SECONDS);\n\n return async (req) => {\n try {\n if (req.method !== \"POST\") throw new HttpError(405, \"method_not_allowed\", \"POST only\");\n if (!signer) throw new HttpError(503, \"not_configured\", \"file storage is not configured on this server\");\n const who = await opts.authorize(req);\n if (!who) throw new HttpError(401, \"unauthorized\", \"sign in first\");\n const prefix = checkPrefix(who.prefix);\n\n let body: Record<string, unknown>;\n try { body = (await req.json()) as Record<string, unknown>; } catch { throw new HttpError(400, \"bad_json\", \"body must be JSON\"); }\n if (!body || typeof body !== \"object\") throw new HttpError(400, \"bad_json\", \"body must be a JSON object\");\n\n switch (body.action) {\n case \"start\": {\n const filename = typeof body.filename === \"string\" ? body.filename : \"\";\n const size = body.size;\n const contentType = typeof body.type === \"string\" && body.type ? body.type : \"application/octet-stream\";\n if (!filename) throw new HttpError(400, \"bad_filename\", \"filename is required\");\n if (typeof size !== \"number\" || !Number.isFinite(size) || size < 1) throw new HttpError(400, \"bad_size\", \"size must be a positive number\");\n if (size > maxBytes) throw new HttpError(413, \"too_large\", `file is larger than ${maxBytes} bytes`);\n if (!typeAllowed(contentType, opts.allowedTypes)) throw new HttpError(415, \"type_not_allowed\", `${contentType} is not accepted here`);\n const key = checkKey((opts.keyFor ?? defaultKeyFor)({ prefix, filename, contentType }), prefix);\n const partSize = choosePartSize(size, opts.partSize);\n const { uploadId } = await signer.start({ key, contentType });\n return json(200, { key, uploadId, partSize, partCount: Math.ceil(size / partSize) });\n }\n case \"sign\": {\n const key = checkKey(body.key, prefix);\n const uploadId = checkUploadId(body.uploadId);\n const nums = body.partNumbers;\n if (!Array.isArray(nums) || nums.length < 1 || nums.length > MAX_SIGN_BATCH ||\n !nums.every((n) => Number.isInteger(n) && n >= 1 && n <= MAX_PARTS))\n throw new HttpError(400, \"bad_parts\", `partNumbers: 1..${MAX_SIGN_BATCH} integers in 1..${MAX_PARTS}`);\n const urls = await Promise.all((nums as number[]).map(async (partNumber) => ({\n partNumber, url: await signer.signPart({ key, uploadId, partNumber, expiresIn }),\n })));\n return json(200, { urls, expiresIn });\n }\n case \"list\": {\n const key = checkKey(body.key, prefix);\n const parts = await signer.listParts({ key, uploadId: checkUploadId(body.uploadId) });\n return json(200, { parts });\n }\n case \"complete\": {\n const key = checkKey(body.key, prefix);\n const uploadId = checkUploadId(body.uploadId);\n const sent = body.parts;\n if (!Array.isArray(sent) || sent.length < 1 || sent.length > MAX_PARTS)\n throw new HttpError(400, \"bad_parts\", \"parts are required\");\n // The bucket is the truth: sizes come from listParts, never from the browser.\n const stored = await signer.listParts({ key, uploadId });\n const total = stored.reduce((s, p) => s + (p.size ?? 0), 0);\n if (total > maxBytes) {\n await signer.abort({ key, uploadId });\n throw new HttpError(413, \"too_large\", `upload exceeded ${maxBytes} bytes and was discarded`);\n }\n const parts: UploadedPart[] = (sent as Array<{ partNumber?: unknown; etag?: unknown }>).map((p) => {\n if (!Number.isInteger(p?.partNumber) || typeof p?.etag !== \"string\") throw new HttpError(400, \"bad_parts\", \"each part needs partNumber and etag\");\n return { partNumber: p.partNumber as number, etag: p.etag };\n });\n await signer.complete({ key, uploadId, parts });\n return json(200, { key, size: total });\n }\n case \"abort\": {\n const key = checkKey(body.key, prefix);\n await signer.abort({ key, uploadId: checkUploadId(body.uploadId) });\n return json(200, { aborted: true });\n }\n case \"download\": {\n const key = checkKey(body.key, prefix);\n const filename = typeof body.filename === \"string\" && body.filename ? safeFilename(body.filename) : undefined;\n const url = await signer.signDownload({ key, expiresIn, filename });\n return json(200, { url, expiresIn });\n }\n default:\n throw new HttpError(400, \"unknown_action\", \"action must be start, sign, list, complete, abort or download\");\n }\n } catch (e) {\n if (e instanceof HttpError) return json(e.status, { error: { code: e.code, message: e.message } });\n if (e instanceof S3Error) {\n const status = e.s3Code === \"NoSuchUpload\" ? 404 : e.s3Code === \"NoSuchKey\" ? 404 : 502;\n return json(status, { error: { code: e.s3Code ?? \"storage_error\", message: status === 404 ? \"upload or object not found\" : \"storage refused the request\" } });\n }\n // Never echo an unknown error: it could carry configuration.\n return json(500, { error: { code: \"internal\", message: \"upload service error\" } });\n }\n };\n}\n"]}
1
+ {"version":3,"sources":["../src/server.ts","../src/handler.ts"],"names":["names"],"mappings":"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","file":"index.js","sourcesContent":["// F111.1 — the server half of @broberg/s3. It holds the storage key and only ever\n// hands the browser SHORT-LIVED, ONE-OBJECT signatures. Bytes never pass through\n// the site's own server: the browser talks to the bucket directly.\nimport { AwsClient } from \"aws4fetch\";\n\n/** Where a bucket lives and how to sign for it. R2 in the EU is the default. */\nexport interface S3Config {\n bucket: string;\n accessKeyId: string;\n secretAccessKey: string;\n /** Cloudflare R2 account id → https://<id>.eu.r2.cloudflarestorage.com */\n accountId?: string;\n /** R2 jurisdiction. Defaults to \"eu\"; a non-EU R2 bucket must say \"default\" explicitly. */\n jurisdiction?: \"eu\" | \"default\";\n /** Any other S3-compatible endpoint origin (Tigris, AWS, MinIO), e.g. \"https://fly.storage.tigris.dev\". */\n endpoint?: string;\n /** Signing region. \"auto\" for R2 and Tigris (default); the bucket's region for AWS. */\n region?: string;\n /** Injected for tests; defaults to the global fetch. */\n fetch?: typeof fetch;\n}\n\nexport interface UploadedPart { partNumber: number; etag: string; size?: number }\n\nexport const MIN_PART_SIZE = 8 * 1024 * 1024;\nexport const DEFAULT_PART_SIZE = 16 * 1024 * 1024;\nexport const MAX_PARTS = 10_000;\n/** The longest a signed URL may live. A leaked link is useless after this. */\nexport const MAX_EXPIRES_SECONDS = 900;\nconst MiB = 1024 * 1024;\n\n/** Part size for `size` bytes: at least `preferred` (and 8 MiB), and few enough parts for S3's 10,000 cap. */\nexport function choosePartSize(size: number, preferred = DEFAULT_PART_SIZE): number {\n const need = Math.ceil(size / MAX_PARTS);\n return Math.ceil(Math.max(preferred, MIN_PART_SIZE, need) / MiB) * MiB;\n}\n\nconst encodeKey = (key: string) => key.split(\"/\").map(encodeURIComponent).join(\"/\");\nconst xmlText = (xml: string, tag: string) => xml.match(new RegExp(`<${tag}>([^<]*)</${tag}>`))?.[1];\nconst xmlEscape = (s: string) => s.replace(/&/g, \"&amp;\").replace(/</g, \"&lt;\").replace(/>/g, \"&gt;\").replace(/\"/g, \"&quot;\");\nconst xmlUnescape = (s: string) => s.replace(/&quot;/g, '\"').replace(/&lt;/g, \"<\").replace(/&gt;/g, \">\").replace(/&amp;/g, \"&\");\n\nexport interface S3Signer {\n /** Begin a multipart upload. */\n start(input: { key: string; contentType?: string }): Promise<{ uploadId: string }>;\n /** A presigned PUT for one part of one upload. */\n signPart(input: { key: string; uploadId: string; partNumber: number; expiresIn?: number }): Promise<string>;\n /** Parts the bucket already holds for this upload (the truth a resume starts from). */\n listParts(input: { key: string; uploadId: string }): Promise<UploadedPart[]>;\n complete(input: { key: string; uploadId: string; parts: UploadedPart[] }): Promise<void>;\n abort(input: { key: string; uploadId: string }): Promise<void>;\n /** A presigned GET; `filename` makes the browser save it under that name. */\n signDownload(input: { key: string; expiresIn?: number; filename?: string }): Promise<string>;\n /** Size of a stored object, or null when it does not exist. */\n head(key: string): Promise<number | null>;\n}\n\nexport class S3Error extends Error {\n constructor(message: string, readonly status: number, readonly s3Code?: string) {\n super(message);\n this.name = \"S3Error\";\n }\n}\n\nfunction originOf(cfg: S3Config): string {\n if (cfg.endpoint) return cfg.endpoint.replace(/\\/+$/, \"\");\n if (!cfg.accountId) throw new Error(\"@broberg/s3: set accountId (R2) or endpoint (other S3-compatible storage)\");\n const j = cfg.jurisdiction ?? \"eu\";\n return j === \"eu\" ? `https://${cfg.accountId}.eu.r2.cloudflarestorage.com` : `https://${cfg.accountId}.r2.cloudflarestorage.com`;\n}\n\nexport function createS3Signer(cfg: S3Config): S3Signer {\n for (const f of [\"bucket\", \"accessKeyId\", \"secretAccessKey\"] as const)\n if (!cfg[f]) throw new Error(`@broberg/s3: ${f} is required`);\n const origin = originOf(cfg);\n const doFetch = cfg.fetch ?? ((r: Request) => fetch(r));\n const aws = new AwsClient({\n accessKeyId: cfg.accessKeyId,\n secretAccessKey: cfg.secretAccessKey,\n region: cfg.region ?? \"auto\",\n service: \"s3\",\n });\n const objectUrl = (key: string) => new URL(`${origin}/${encodeURIComponent(cfg.bucket)}/${encodeKey(key)}`);\n const clampExpiry = (s?: number) => Math.max(1, Math.min(s ?? MAX_EXPIRES_SECONDS, MAX_EXPIRES_SECONDS));\n\n async function call(method: string, url: URL, body?: string, headers?: Record<string, string>): Promise<string> {\n const signed = await aws.sign(url.toString(), { method, body, headers });\n const res = await doFetch(signed);\n const text = await res.text();\n // S3 can answer 200 with an <Error> body (CompleteMultipartUpload does).\n if (!res.ok || /<Error>/.test(text)) {\n const code = xmlText(text, \"Code\");\n throw new S3Error(`@broberg/s3: ${method} ${url.pathname} → ${res.status}${code ? ` ${code}` : \"\"}`, res.ok ? 502 : res.status, code);\n }\n return text;\n }\n\n async function presign(method: \"PUT\" | \"GET\", url: URL, expiresIn?: number): Promise<string> {\n url.searchParams.set(\"X-Amz-Expires\", String(clampExpiry(expiresIn)));\n const signed = await aws.sign(url.toString(), { method, aws: { signQuery: true } });\n return signed.url;\n }\n\n return {\n async start({ key, contentType }) {\n const url = objectUrl(key);\n url.searchParams.set(\"uploads\", \"\");\n const xml = await call(\"POST\", url, undefined, contentType ? { \"content-type\": contentType } : undefined);\n const uploadId = xmlText(xml, \"UploadId\");\n if (!uploadId) throw new S3Error(\"@broberg/s3: CreateMultipartUpload returned no UploadId\", 502);\n return { uploadId: xmlUnescape(uploadId) };\n },\n async signPart({ key, uploadId, partNumber, expiresIn }) {\n if (!Number.isInteger(partNumber) || partNumber < 1 || partNumber > MAX_PARTS)\n throw new S3Error(`@broberg/s3: partNumber must be 1..${MAX_PARTS}`, 400);\n const url = objectUrl(key);\n url.searchParams.set(\"partNumber\", String(partNumber));\n url.searchParams.set(\"uploadId\", uploadId);\n return presign(\"PUT\", url, expiresIn);\n },\n async listParts({ key, uploadId }) {\n const parts: UploadedPart[] = [];\n let marker: string | undefined;\n for (;;) {\n const url = objectUrl(key);\n url.searchParams.set(\"uploadId\", uploadId);\n if (marker) url.searchParams.set(\"part-number-marker\", marker);\n const xml = await call(\"GET\", url);\n for (const m of xml.matchAll(/<Part>([\\s\\S]*?)<\\/Part>/g)) {\n parts.push({\n partNumber: Number(xmlText(m[1]!, \"PartNumber\")),\n etag: xmlUnescape(xmlText(m[1]!, \"ETag\") ?? \"\"),\n size: Number(xmlText(m[1]!, \"Size\")),\n });\n }\n if (xmlText(xml, \"IsTruncated\") !== \"true\") break;\n marker = xmlText(xml, \"NextPartNumberMarker\");\n if (!marker) break;\n }\n return parts;\n },\n async complete({ key, uploadId, parts }) {\n const sorted = [...parts].sort((a, b) => a.partNumber - b.partNumber);\n const body =\n \"<CompleteMultipartUpload>\" +\n sorted.map((p) => `<Part><PartNumber>${p.partNumber}</PartNumber><ETag>${xmlEscape(p.etag)}</ETag></Part>`).join(\"\") +\n \"</CompleteMultipartUpload>\";\n const url = objectUrl(key);\n url.searchParams.set(\"uploadId\", uploadId);\n await call(\"POST\", url, body, { \"content-type\": \"application/xml\" });\n },\n async abort({ key, uploadId }) {\n const url = objectUrl(key);\n url.searchParams.set(\"uploadId\", uploadId);\n await call(\"DELETE\", url);\n },\n async signDownload({ key, expiresIn, filename }) {\n const url = objectUrl(key);\n if (filename) {\n const ascii = filename.replace(/[^\\x20-\\x7e]|[\"\\\\]/g, \"_\");\n url.searchParams.set(\"response-content-disposition\", `attachment; filename=\"${ascii}\"; filename*=UTF-8''${encodeURIComponent(filename)}`);\n }\n return presign(\"GET\", url, expiresIn);\n },\n async head(key) {\n const signed = await aws.sign(objectUrl(key).toString(), { method: \"HEAD\" });\n const res = await doFetch(signed);\n if (res.status === 404) return null;\n if (!res.ok) throw new S3Error(`@broberg/s3: HEAD → ${res.status}`, res.status);\n return Number(res.headers.get(\"content-length\"));\n },\n };\n}\n\ntype Env = Record<string, string | undefined>;\n\n/**\n * Builds a signer from the environment, or returns null when NOTHING is configured\n * (ship dark: the handler then answers 503). A HALF-configured environment throws\n * at boot instead — that is a mistake, not a choice.\n *\n * R2: R2_ACCOUNT_ID, R2_ACCESS_KEY_ID, R2_SECRET_ACCESS_KEY, R2_BUCKET [, R2_JURISDICTION]\n * S3: S3_ENDPOINT, S3_ACCESS_KEY_ID, S3_SECRET_ACCESS_KEY, S3_BUCKET [, S3_REGION]\n */\nexport function createS3SignerFromEnv(env: Env = (globalThis as { process?: { env: Env } }).process?.env ?? {}): S3Signer | null {\n const r2 = [\"R2_ACCOUNT_ID\", \"R2_ACCESS_KEY_ID\", \"R2_SECRET_ACCESS_KEY\", \"R2_BUCKET\"];\n const s3 = [\"S3_ENDPOINT\", \"S3_ACCESS_KEY_ID\", \"S3_SECRET_ACCESS_KEY\", \"S3_BUCKET\"];\n const pick = (names: string[]) => names.filter((n) => env[n]);\n const r2Set = pick(r2), s3Set = pick(s3);\n if (r2Set.length === 0 && s3Set.length === 0) return null;\n const [names, set] = r2Set.length >= s3Set.length ? [r2, r2Set] : [s3, s3Set];\n if (set.length !== names.length)\n throw new Error(`@broberg/s3: incomplete storage config — missing ${names.filter((n) => !env[n]).join(\", \")}`);\n if (names === r2) {\n const j = env.R2_JURISDICTION;\n if (j && j !== \"eu\" && j !== \"default\") throw new Error(`@broberg/s3: R2_JURISDICTION must be \"eu\" or \"default\"`);\n return createS3Signer({\n accountId: env.R2_ACCOUNT_ID, accessKeyId: env.R2_ACCESS_KEY_ID!, secretAccessKey: env.R2_SECRET_ACCESS_KEY!,\n bucket: env.R2_BUCKET!, jurisdiction: (j as \"eu\" | \"default\" | undefined) ?? \"eu\",\n });\n }\n return createS3Signer({\n endpoint: env.S3_ENDPOINT, accessKeyId: env.S3_ACCESS_KEY_ID!, secretAccessKey: env.S3_SECRET_ACCESS_KEY!,\n bucket: env.S3_BUCKET!, region: env.S3_REGION,\n });\n}\n","// F111.1 — ONE fetch handler (Request → Response) for Hono/Bun and Next.\n// Every action asks the SITE's authorize() first and keeps the user inside the\n// object prefix it returns. The storage key never appears in a response.\nimport {\n MAX_EXPIRES_SECONDS, MAX_PARTS, S3Error, choosePartSize,\n type S3Signer, type UploadedPart,\n} from \"./server\";\n\nexport interface Authorized {\n /** Every object this user may touch lives under `<prefix>/`. E.g. \"users/<bid-sub>\". */\n prefix: string;\n}\n\nexport interface S3HandlerOptions {\n /** The site's own check (BID session + allowlist). Return null to refuse. */\n authorize(req: Request): Promise<Authorized | null> | Authorized | null;\n /** Largest object accepted, in bytes. Default 20 GiB. Enforced at start (declared) AND at complete (actual). */\n maxBytes?: number;\n /** Allowed content types, exact (\"video/mp4\") or wildcard (\"video/*\"). Default: any. Declared type, not sniffed. */\n allowedTypes?: string[];\n /** Preferred part size in bytes (min 8 MiB). Default 16 MiB. */\n partSize?: number;\n /** Signed-URL lifetime in seconds, max 900. */\n expiresIn?: number;\n /** Object key for a new upload. Default `<prefix>/<yyyy-mm-dd>/<random>-<safe filename>`. */\n keyFor?(input: { prefix: string; filename: string; contentType: string }): string;\n}\n\nconst GiB = 1024 ** 3;\nconst MAX_SIGN_BATCH = 100;\n\nclass HttpError extends Error {\n constructor(readonly status: number, readonly code: string, message: string) { super(message); }\n}\n\nconst json = (status: number, body: unknown) =>\n new Response(JSON.stringify(body), { status, headers: { \"content-type\": \"application/json\", \"cache-control\": \"no-store\" } });\n\nexport const safeFilename = (name: string) =>\n (name.normalize(\"NFKC\").replace(/[\\\\/\\u0000-\\u001f\\u007f]/g, \"_\").replace(/^\\.+/, \"\").replace(/[^\\p{L}\\p{N}._ -]/gu, \"_\").trim().slice(0, 120)) || \"file\";\n\n/** A prefix must be plain path segments: no \"..\", no empty segments, no encoding tricks. */\nfunction checkPrefix(prefix: unknown): string {\n if (typeof prefix !== \"string\") throw new HttpError(500, \"bad_prefix\", \"authorize() returned no prefix\");\n const p = prefix.replace(/^\\/+|\\/+$/g, \"\");\n if (!p || p.split(\"/\").some((s) => !s || s === \".\" || s === \"..\") || /[%\\\\\\u0000-\\u001f]/.test(p))\n throw new HttpError(500, \"bad_prefix\", \"authorize() returned an unsafe prefix\");\n return p;\n}\n\n/** The key must sit under `<prefix>/` and be free of traversal, encoding and control characters. */\nfunction checkKey(key: unknown, prefix: string): string {\n if (typeof key !== \"string\" || key.length > 1024) throw new HttpError(400, \"bad_key\", \"key is required\");\n const segs = key.split(\"/\");\n if (/[%\\\\\\u0000-\\u001f\\u007f]/.test(key) || segs.some((s) => !s || s === \".\" || s === \"..\") || !key.startsWith(prefix + \"/\"))\n throw new HttpError(403, \"forbidden_key\", \"that object is not yours\");\n return key;\n}\n\nconst checkUploadId = (id: unknown): string => {\n if (typeof id !== \"string\" || !id || id.length > 1024 || /[\\u0000-\\u001f]/.test(id))\n throw new HttpError(400, \"bad_upload_id\", \"uploadId is required\");\n return id;\n};\n\nfunction typeAllowed(type: string, allowed?: string[]): boolean {\n if (!allowed?.length) return true;\n const t = type.toLowerCase();\n return allowed.some((a) => { const x = a.toLowerCase(); return x.endsWith(\"/*\") ? t.startsWith(x.slice(0, -1)) : t === x; });\n}\n\nconst randomId = () => {\n const b = new Uint8Array(9);\n crypto.getRandomValues(b);\n return Array.from(b, (x) => x.toString(16).padStart(2, \"0\")).join(\"\");\n};\n\nconst defaultKeyFor = ({ prefix, filename }: { prefix: string; filename: string }) =>\n `${prefix}/${new Date().toISOString().slice(0, 10)}/${randomId()}-${safeFilename(filename)}`;\n\nexport function createS3Handler(signer: S3Signer | null, opts: S3HandlerOptions): (req: Request) => Promise<Response> {\n const maxBytes = opts.maxBytes ?? 20 * GiB;\n const expiresIn = Math.min(opts.expiresIn ?? MAX_EXPIRES_SECONDS, MAX_EXPIRES_SECONDS);\n\n return async (req) => {\n try {\n if (req.method !== \"POST\") throw new HttpError(405, \"method_not_allowed\", \"POST only\");\n if (!signer) throw new HttpError(503, \"not_configured\", \"file storage is not configured on this server\");\n const who = await opts.authorize(req);\n if (!who) throw new HttpError(401, \"unauthorized\", \"sign in first\");\n const prefix = checkPrefix(who.prefix);\n\n // JSON only: a cross-site <form> can POST text/plain with the user's cookies,\n // but cannot set application/json without a CORS preflight this route never grants.\n if (!(req.headers.get(\"content-type\") ?? \"\").toLowerCase().startsWith(\"application/json\"))\n throw new HttpError(415, \"json_required\", \"content-type must be application/json\");\n let body: Record<string, unknown>;\n try { body = (await req.json()) as Record<string, unknown>; } catch { throw new HttpError(400, \"bad_json\", \"body must be JSON\"); }\n if (!body || typeof body !== \"object\") throw new HttpError(400, \"bad_json\", \"body must be a JSON object\");\n\n switch (body.action) {\n case \"start\": {\n const filename = typeof body.filename === \"string\" ? body.filename : \"\";\n const size = body.size;\n const contentType = typeof body.type === \"string\" && body.type ? body.type : \"application/octet-stream\";\n if (!filename) throw new HttpError(400, \"bad_filename\", \"filename is required\");\n if (typeof size !== \"number\" || !Number.isFinite(size) || size < 1) throw new HttpError(400, \"bad_size\", \"size must be a positive number\");\n if (size > maxBytes) throw new HttpError(413, \"too_large\", `file is larger than ${maxBytes} bytes`);\n if (!typeAllowed(contentType, opts.allowedTypes)) throw new HttpError(415, \"type_not_allowed\", `${contentType} is not accepted here`);\n const key = checkKey((opts.keyFor ?? defaultKeyFor)({ prefix, filename, contentType }), prefix);\n const partSize = choosePartSize(size, opts.partSize);\n const { uploadId } = await signer.start({ key, contentType });\n return json(200, { key, uploadId, partSize, partCount: Math.ceil(size / partSize) });\n }\n case \"sign\": {\n const key = checkKey(body.key, prefix);\n const uploadId = checkUploadId(body.uploadId);\n const nums = body.partNumbers;\n if (!Array.isArray(nums) || nums.length < 1 || nums.length > MAX_SIGN_BATCH ||\n !nums.every((n) => Number.isInteger(n) && n >= 1 && n <= MAX_PARTS))\n throw new HttpError(400, \"bad_parts\", `partNumbers: 1..${MAX_SIGN_BATCH} integers in 1..${MAX_PARTS}`);\n const urls = await Promise.all((nums as number[]).map(async (partNumber) => ({\n partNumber, url: await signer.signPart({ key, uploadId, partNumber, expiresIn }),\n })));\n return json(200, { urls, expiresIn });\n }\n case \"list\": {\n const key = checkKey(body.key, prefix);\n const parts = await signer.listParts({ key, uploadId: checkUploadId(body.uploadId) });\n return json(200, { parts });\n }\n case \"complete\": {\n const key = checkKey(body.key, prefix);\n const uploadId = checkUploadId(body.uploadId);\n const sent = body.parts;\n if (!Array.isArray(sent) || sent.length < 1 || sent.length > MAX_PARTS)\n throw new HttpError(400, \"bad_parts\", \"parts are required\");\n // The bucket is the truth: sizes come from listParts, never from the browser.\n const stored = await signer.listParts({ key, uploadId });\n const total = stored.reduce((s, p) => s + (p.size ?? 0), 0);\n if (total > maxBytes) {\n await signer.abort({ key, uploadId });\n throw new HttpError(413, \"too_large\", `upload exceeded ${maxBytes} bytes and was discarded`);\n }\n const parts: UploadedPart[] = (sent as Array<{ partNumber?: unknown; etag?: unknown }>).map((p) => {\n if (!Number.isInteger(p?.partNumber) || typeof p?.etag !== \"string\") throw new HttpError(400, \"bad_parts\", \"each part needs partNumber and etag\");\n return { partNumber: p.partNumber as number, etag: p.etag };\n });\n await signer.complete({ key, uploadId, parts });\n return json(200, { key, size: total });\n }\n case \"abort\": {\n const key = checkKey(body.key, prefix);\n await signer.abort({ key, uploadId: checkUploadId(body.uploadId) });\n return json(200, { aborted: true });\n }\n case \"download\": {\n const key = checkKey(body.key, prefix);\n const filename = typeof body.filename === \"string\" && body.filename ? safeFilename(body.filename) : undefined;\n const url = await signer.signDownload({ key, expiresIn, filename });\n return json(200, { url, expiresIn });\n }\n default:\n throw new HttpError(400, \"unknown_action\", \"action must be start, sign, list, complete, abort or download\");\n }\n } catch (e) {\n if (e instanceof HttpError) return json(e.status, { error: { code: e.code, message: e.message } });\n if (e instanceof S3Error) {\n const status = e.s3Code === \"NoSuchUpload\" ? 404 : e.s3Code === \"NoSuchKey\" ? 404 : 502;\n return json(status, { error: { code: e.s3Code ?? \"storage_error\", message: status === 404 ? \"upload or object not found\" : \"storage refused the request\" } });\n }\n // Never echo an unknown error: it could carry configuration.\n return json(500, { error: { code: \"internal\", message: \"upload service error\" } });\n }\n };\n}\n"]}
package/package.json CHANGED
@@ -1,18 +1,33 @@
1
1
  {
2
2
  "name": "@broberg/s3",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Upload and download files of any size DIRECTLY between the browser and S3-compatible storage (Cloudflare R2 in the EU by default; Tigris, AWS, MinIO by endpoint) — the site's server only issues short-lived, one-object signatures, so bytes never pass through it. Multipart upload with parallel parts, retry, progress (%, speed, time left) and resume after a dropped connection; presigned download links. One fetch handler (Request → Response) for Hono/Bun and Next, guarded by the site's own authorize() hook and a per-user object prefix. Preact hook, no visual component.",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
7
  "sideEffects": false,
8
- "files": ["dist", "README.md"],
8
+ "files": [
9
+ "dist",
10
+ "README.md"
11
+ ],
9
12
  "main": "./dist/index.cjs",
10
13
  "module": "./dist/index.js",
11
14
  "types": "./dist/index.d.ts",
12
15
  "exports": {
13
- ".": { "types": "./dist/index.d.ts", "import": "./dist/index.js", "require": "./dist/index.cjs" },
14
- "./client": { "types": "./dist/client.d.ts", "import": "./dist/client.js", "require": "./dist/client.cjs" },
15
- "./preact": { "types": "./dist/preact.d.ts", "import": "./dist/preact.js", "require": "./dist/preact.cjs" },
16
+ ".": {
17
+ "types": "./dist/index.d.ts",
18
+ "import": "./dist/index.js",
19
+ "require": "./dist/index.cjs"
20
+ },
21
+ "./client": {
22
+ "types": "./dist/client.d.ts",
23
+ "import": "./dist/client.js",
24
+ "require": "./dist/client.cjs"
25
+ },
26
+ "./preact": {
27
+ "types": "./dist/preact.d.ts",
28
+ "import": "./dist/preact.js",
29
+ "require": "./dist/preact.cjs"
30
+ },
16
31
  "./package.json": "./package.json"
17
32
  },
18
33
  "scripts": {
@@ -27,16 +42,39 @@
27
42
  "preact": ">=10"
28
43
  },
29
44
  "peerDependenciesMeta": {
30
- "preact": { "optional": true }
45
+ "preact": {
46
+ "optional": true
47
+ }
31
48
  },
32
49
  "devDependencies": {
33
50
  "@types/node": "^22.0.0",
51
+ "happy-dom": "^15.0.0",
34
52
  "preact": "^10.24.0",
35
53
  "tsup": "^8.3.0",
36
54
  "typescript": "^5.6.0",
37
55
  "vitest": "^2.1.0"
38
56
  },
39
- "keywords": ["s3", "r2", "cloudflare", "tigris", "upload", "download", "multipart", "presigned", "large files", "resumable", "store filer", "upload af video", "broberg"],
40
- "repository": { "type": "git", "url": "https://github.com/broberg-ai/components", "directory": "packages/s3" },
41
- "publishConfig": { "access": "public" }
57
+ "keywords": [
58
+ "s3",
59
+ "r2",
60
+ "cloudflare",
61
+ "tigris",
62
+ "upload",
63
+ "download",
64
+ "multipart",
65
+ "presigned",
66
+ "large files",
67
+ "resumable",
68
+ "store filer",
69
+ "upload af video",
70
+ "broberg"
71
+ ],
72
+ "repository": {
73
+ "type": "git",
74
+ "url": "https://github.com/broberg-ai/components",
75
+ "directory": "packages/s3"
76
+ },
77
+ "publishConfig": {
78
+ "access": "public"
79
+ }
42
80
  }