@ibanzajoe/uploader 1.5.0 → 1.7.0
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 +16 -5
- package/dist/{chunk-7LTEX76J.js → chunk-MAPCHOFK.js} +77 -7
- package/dist/chunk-MAPCHOFK.js.map +1 -0
- package/dist/{client-DW6DcS6o.d.cts → client-DxYeqjzt.d.cts} +122 -4
- package/dist/{client-DW6DcS6o.d.ts → client-DxYeqjzt.d.ts} +122 -4
- package/dist/core.cjs +76 -6
- package/dist/core.cjs.map +1 -1
- package/dist/core.d.cts +1 -1
- package/dist/core.d.ts +1 -1
- package/dist/core.js +1 -1
- package/dist/index.cjs +169 -11
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +40 -7
- package/dist/index.d.ts +40 -7
- package/dist/index.js +92 -6
- package/dist/index.js.map +1 -1
- package/dist/styles.css +42 -0
- package/package.json +1 -1
- package/dist/chunk-7LTEX76J.js.map +0 -1
package/README.md
CHANGED
|
@@ -26,7 +26,7 @@ function App() {
|
|
|
26
26
|
<>
|
|
27
27
|
<button onClick={() => setOpen(true)}>Upload</button>
|
|
28
28
|
<PickerOverlay
|
|
29
|
-
apikey="
|
|
29
|
+
apikey="pk_your_publishable_key"
|
|
30
30
|
apiUrl="https://your-api.example.com"
|
|
31
31
|
open={open}
|
|
32
32
|
onClose={() => setOpen(false)}
|
|
@@ -47,7 +47,7 @@ React dependency.
|
|
|
47
47
|
import { UploaderClient } from '@ibanzajoe/uploader/core'
|
|
48
48
|
|
|
49
49
|
const client = new UploaderClient({
|
|
50
|
-
apikey: '
|
|
50
|
+
apikey: 'pk_your_publishable_key', // required — publishable, safe in client code
|
|
51
51
|
apiUrl: 'https://your-api.example.com',
|
|
52
52
|
})
|
|
53
53
|
|
|
@@ -117,7 +117,7 @@ Full-screen modal picker with drag-and-drop, progress, and error UI.
|
|
|
117
117
|
|
|
118
118
|
| Prop | Type | Description |
|
|
119
119
|
|---|---|---|
|
|
120
|
-
| `apikey` | `string` |
|
|
120
|
+
| `apikey` | `string` | Publishable key (`pk_…`) — identifies your account, not a credential. **Required.** |
|
|
121
121
|
| `apiUrl` | `string` | Base URL of the API server |
|
|
122
122
|
| `open` | `boolean` | Whether the modal is open. **Required.** |
|
|
123
123
|
| `onClose` | `() => void` | Called when the modal should close (ESC, backdrop, cancel) |
|
|
@@ -281,6 +281,11 @@ flows through the exact same pipeline as a dropped or browsed file — it lands
|
|
|
281
281
|
the queue with a preview, can be cropped/rotated in the in-picker editor, and is
|
|
282
282
|
then uploaded normally.
|
|
283
283
|
|
|
284
|
+
**Front or rear camera.** On phones and tablets the picker opens the **rear**
|
|
285
|
+
camera; on desktops it opens the front one. Where the device exposes more than
|
|
286
|
+
one camera, a flip control in the corner of the viewfinder switches between them,
|
|
287
|
+
so the default is only the starting side. Set `cameraFacingMode` to pin it.
|
|
288
|
+
|
|
284
289
|
```tsx
|
|
285
290
|
<PickerOverlay
|
|
286
291
|
apikey="pk_…"
|
|
@@ -290,7 +295,8 @@ then uploaded normally.
|
|
|
290
295
|
accept: ['image/*'],
|
|
291
296
|
// Sources offered to the user. Omit to offer everything supported.
|
|
292
297
|
fromSources: ['local_file_system', 'camera'],
|
|
293
|
-
//
|
|
298
|
+
// Omit for the per-device default (rear on mobile, front on desktop).
|
|
299
|
+
// 'user' = front/selfie (mirrored) · 'environment' = rear camera
|
|
294
300
|
cameraFacingMode: 'environment',
|
|
295
301
|
}}
|
|
296
302
|
/>
|
|
@@ -301,8 +307,13 @@ Notes:
|
|
|
301
307
|
permission. Permission/no-camera errors are surfaced inline with a retry.
|
|
302
308
|
- Pass `fromSources: ['local_file_system']` to hide the camera even where it is
|
|
303
309
|
supported; omit `fromSources` to offer it by default.
|
|
310
|
+
- The flip control appears only when `enumerateDevices()` reports two or more
|
|
311
|
+
video inputs (checked after permission is granted, when the list is accurate).
|
|
312
|
+
- The front camera is mirrored in both the preview and the captured still; the
|
|
313
|
+
rear camera is not.
|
|
304
314
|
- The `<CameraCapture>` component and the `isCameraSupported()` /
|
|
305
|
-
`shouldOfferCamera()`
|
|
315
|
+
`shouldOfferCamera()` / `isMobileDevice()` / `defaultCameraFacingMode()`
|
|
316
|
+
helpers are exported for fully custom pickers.
|
|
306
317
|
|
|
307
318
|
## Signed policies
|
|
308
319
|
|
|
@@ -136,6 +136,7 @@ function xhrPut(url, body, opts) {
|
|
|
136
136
|
});
|
|
137
137
|
}
|
|
138
138
|
var UploaderClient = class {
|
|
139
|
+
/** The publishable key (`pk_…`) — a project identifier, not a credential. */
|
|
139
140
|
apikey;
|
|
140
141
|
apiUrl;
|
|
141
142
|
security;
|
|
@@ -150,7 +151,7 @@ var UploaderClient = class {
|
|
|
150
151
|
#capsPromise = null;
|
|
151
152
|
constructor(options) {
|
|
152
153
|
this.apikey = options.apikey;
|
|
153
|
-
this.apiUrl = (options.apiUrl ?? "https://api.
|
|
154
|
+
this.apiUrl = (options.apiUrl ?? "https://api.postila.app").replace(/\/$/, "");
|
|
154
155
|
this.security = options.security;
|
|
155
156
|
this.#directUploadOption = options.directUpload;
|
|
156
157
|
this.#deliveryProtection = options.deliveryProtection;
|
|
@@ -262,15 +263,18 @@ var UploaderClient = class {
|
|
|
262
263
|
// ─── Auth headers ──────────────────────────────────────────────────────────
|
|
263
264
|
/**
|
|
264
265
|
* Build the auth headers shared by all requests.
|
|
265
|
-
* Attaches the API key and, when present, the signed policy pair.
|
|
266
|
+
* Attaches the API key and, when present, the signed policy pair. A per-call
|
|
267
|
+
* `security` overrides the client-level one (the file-management methods
|
|
268
|
+
* need policies with different `call` grants than uploads).
|
|
266
269
|
*/
|
|
267
|
-
#authHeaders() {
|
|
270
|
+
#authHeaders(security) {
|
|
268
271
|
const headers = {
|
|
269
272
|
"X-Uploader-Key": this.apikey
|
|
270
273
|
};
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
headers["X-Uploader-
|
|
274
|
+
const pair = security ?? this.security;
|
|
275
|
+
if (pair) {
|
|
276
|
+
headers["X-Uploader-Policy"] = pair.policy;
|
|
277
|
+
headers["X-Uploader-Signature"] = pair.signature;
|
|
274
278
|
}
|
|
275
279
|
return headers;
|
|
276
280
|
}
|
|
@@ -390,6 +394,72 @@ var UploaderClient = class {
|
|
|
390
394
|
}
|
|
391
395
|
return this.#uploadMultipart(file, plan.parts, opts);
|
|
392
396
|
}
|
|
397
|
+
/**
|
|
398
|
+
* List the account's files (paginated, 50 per page).
|
|
399
|
+
*
|
|
400
|
+
* Accounts with required signed policies must supply a policy whose `call`
|
|
401
|
+
* includes `'list'` — a handle-bound `'read'` policy from a delivery URL is
|
|
402
|
+
* deliberately not enough to enumerate the account.
|
|
403
|
+
*
|
|
404
|
+
* @throws {UploaderError} with code NETWORK_ERROR | SERVER_ERROR |
|
|
405
|
+
* CLIENT_ERROR | INVALID_RESPONSE
|
|
406
|
+
*/
|
|
407
|
+
async listFiles(query = {}, opts = {}) {
|
|
408
|
+
const params = new URLSearchParams();
|
|
409
|
+
for (const [k, v] of Object.entries(query)) {
|
|
410
|
+
if (v !== void 0) params.set(k, String(v));
|
|
411
|
+
}
|
|
412
|
+
const qs = params.toString();
|
|
413
|
+
const res = await fetchWithRetry(
|
|
414
|
+
`${this.apiUrl}/api/files${qs ? `?${qs}` : ""}`,
|
|
415
|
+
{ headers: this.#authHeaders(opts.security) },
|
|
416
|
+
opts.signal
|
|
417
|
+
);
|
|
418
|
+
const body = await res.json().catch(() => null);
|
|
419
|
+
if (!body || !Array.isArray(body.files)) {
|
|
420
|
+
throw new UploaderError("INVALID_RESPONSE", "Unexpected response shape from file list API");
|
|
421
|
+
}
|
|
422
|
+
return body;
|
|
423
|
+
}
|
|
424
|
+
/**
|
|
425
|
+
* Fetch one file's record by its public handle.
|
|
426
|
+
*
|
|
427
|
+
* Accounts with required signed policies must supply a policy whose `call`
|
|
428
|
+
* includes `'read'` (handle-bound policies must match this handle).
|
|
429
|
+
*
|
|
430
|
+
* @throws {UploaderError} — CLIENT_ERROR with statusCode 404 when the handle
|
|
431
|
+
* does not exist (or belongs to another account).
|
|
432
|
+
*/
|
|
433
|
+
async getFile(handle, opts = {}) {
|
|
434
|
+
const res = await fetchWithRetry(
|
|
435
|
+
`${this.apiUrl}/api/files/${encodeURIComponent(handle)}`,
|
|
436
|
+
{ headers: this.#authHeaders(opts.security) },
|
|
437
|
+
opts.signal
|
|
438
|
+
);
|
|
439
|
+
const body = await res.json().catch(() => null);
|
|
440
|
+
if (!body || typeof body.handle !== "string") {
|
|
441
|
+
throw new UploaderError("INVALID_RESPONSE", "Unexpected response shape from file API");
|
|
442
|
+
}
|
|
443
|
+
return body;
|
|
444
|
+
}
|
|
445
|
+
/**
|
|
446
|
+
* Delete a file by its public handle.
|
|
447
|
+
*
|
|
448
|
+
* The file stops being served immediately (soft-delete + edge cache purge);
|
|
449
|
+
* the stored bytes and derivatives are removed by a background cleanup.
|
|
450
|
+
* Accounts with required signed policies must supply a policy whose `call`
|
|
451
|
+
* includes `'remove'` (handle-bound policies must match this handle).
|
|
452
|
+
*
|
|
453
|
+
* @throws {UploaderError} — CLIENT_ERROR with statusCode 404 when the handle
|
|
454
|
+
* does not exist (or belongs to another account).
|
|
455
|
+
*/
|
|
456
|
+
async deleteFile(handle, opts = {}) {
|
|
457
|
+
await fetchWithRetry(
|
|
458
|
+
`${this.apiUrl}/api/files/${encodeURIComponent(handle)}`,
|
|
459
|
+
{ method: "DELETE", headers: this.#authHeaders(opts.security) },
|
|
460
|
+
opts.signal
|
|
461
|
+
);
|
|
462
|
+
}
|
|
393
463
|
/**
|
|
394
464
|
* Upload multiple files with concurrency limiting.
|
|
395
465
|
*
|
|
@@ -431,4 +501,4 @@ export {
|
|
|
431
501
|
planChunks,
|
|
432
502
|
UploaderClient
|
|
433
503
|
};
|
|
434
|
-
//# sourceMappingURL=chunk-
|
|
504
|
+
//# sourceMappingURL=chunk-MAPCHOFK.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/core/errors.ts","../src/core/chunk.ts","../src/core/client.ts"],"sourcesContent":["/**\n * Typed errors for UploaderClient.\n *\n * UploaderError is thrown by upload / uploadAll on any non-retried failure.\n * The caller can narrow on `err.code` for structured handling.\n */\n\nexport type UploaderErrorCode =\n | 'ABORTED' // AbortSignal fired\n | 'NETWORK_ERROR' // fetch() threw (no response)\n | 'SERVER_ERROR' // 5xx after all retries exhausted\n | 'CLIENT_ERROR' // 4xx (not retried)\n | 'INVALID_RESPONSE' // response body did not match expected shape\n\nexport class UploaderError extends Error {\n readonly code: UploaderErrorCode\n /** HTTP status code when available (undefined for ABORTED / NETWORK_ERROR). */\n readonly statusCode?: number\n\n constructor(code: UploaderErrorCode, message: string, statusCode?: number) {\n super(message)\n this.name = 'UploaderError'\n this.code = code\n this.statusCode = statusCode\n }\n}\n","/**\n * Chunk planner for multipart uploads.\n *\n * Decides single-shot vs multipart by comparing the file size against\n * MULTIPART_THRESHOLD. For multipart files, slices the Blob into parts of\n * `chunkSize` bytes.\n */\n\n/** Files ≤ this size use single-shot POST /api/store. */\nexport const MULTIPART_THRESHOLD = 5 * 1024 * 1024 // 5 MB\n\n/** Default part size for multipart uploads. */\nexport const DEFAULT_CHUNK_SIZE = 5 * 1024 * 1024 // 5 MB\n\nexport type ChunkPlan =\n | { mode: 'single' }\n | { mode: 'multipart'; parts: Blob[]; partSize: number }\n\n/**\n * Build a chunk plan for a file.\n *\n * @param file The File or Blob to upload.\n * @param chunkSize Desired part size in bytes (default DEFAULT_CHUNK_SIZE).\n * @returns A plan describing whether to use single-shot or multipart.\n */\nexport function planChunks(file: File | Blob, chunkSize = DEFAULT_CHUNK_SIZE): ChunkPlan {\n if (file.size <= MULTIPART_THRESHOLD) {\n return { mode: 'single' }\n }\n\n const parts: Blob[] = []\n let offset = 0\n while (offset < file.size) {\n parts.push(file.slice(offset, offset + chunkSize))\n offset += chunkSize\n }\n return { mode: 'multipart', parts, partSize: chunkSize }\n}\n","/**\n * UploaderClient — headless upload client.\n *\n * Chooses single-shot (POST /api/store, multipart/form-data) vs multipart\n * (start/part/complete) based on file size relative to MULTIPART_THRESHOLD\n * (5 MB). Parts are retried individually with exponential backoff. Progress\n * is emitted as a 0–100 integer. AbortSignal cancels in-flight work.\n */\n\nimport type {\n FileResult,\n UploadStartResponse,\n UploadPartResponse,\n UploaderClientOptions,\n UploadAllOptions,\n UploadOptions,\n UsageWarning,\n SecurityPair,\n StoredFileRecord,\n ListFilesQuery,\n ListFilesResult,\n FileManageOptions,\n} from './types.js'\nimport { UploaderError } from './errors.js'\nimport { planChunks } from './chunk.js'\n\n// ─── Constants ────────────────────────────────────────────────────────────────\n\nconst MAX_RETRIES = 3\nconst RETRY_BASE_MS = 200\n\n/**\n * Single presigned PUT ceiling (mirrors the server's Phase-A limit). Files above\n * this fall back to the proxied multipart flow.\n */\nconst MAX_DIRECT_PUT_BYTES = 5 * 1024 * 1024 * 1024 // 5 GB\n\n/**\n * Skip the advisory client checksum above this size — SubtleCrypto has no\n * streaming API, so hashing would load the whole file into memory a second time.\n * The checksum is advisory only (the server can't verify it anyway).\n */\nconst SHA256_MAX_BYTES = 64 * 1024 * 1024 // 64 MB\n\n// ─── Internal helpers ─────────────────────────────────────────────────────────\n\n/** Sleep for `ms` milliseconds, resolving early if signal fires. */\nfunction sleep(ms: number, signal?: AbortSignal): Promise<void> {\n return new Promise<void>((resolve, reject) => {\n if (signal?.aborted) {\n reject(new UploaderError('ABORTED', 'Upload aborted'))\n return\n }\n const timer = setTimeout(resolve, ms)\n signal?.addEventListener('abort', () => {\n clearTimeout(timer)\n reject(new UploaderError('ABORTED', 'Upload aborted'))\n }, { once: true })\n })\n}\n\n/** Throw UploaderError(ABORTED) if signal has already fired. */\nfunction checkAbort(signal?: AbortSignal): void {\n if (signal?.aborted) {\n throw new UploaderError('ABORTED', 'Upload aborted')\n }\n}\n\n/**\n * Fetch with retry on network errors and 5xx responses.\n * 4xx responses are not retried — they surface immediately as CLIENT_ERROR.\n */\nasync function fetchWithRetry(\n url: string,\n init: RequestInit,\n signal?: AbortSignal,\n maxRetries = MAX_RETRIES,\n): Promise<Response> {\n let lastErr: unknown\n for (let attempt = 0; attempt < maxRetries; attempt++) {\n checkAbort(signal)\n try {\n const res = await fetch(url, { ...init, signal })\n if (res.status >= 400 && res.status < 500) {\n // Client error — do not retry\n const body = await res.text().catch(() => '')\n throw new UploaderError(\n 'CLIENT_ERROR',\n `HTTP ${res.status}: ${body}`,\n res.status,\n )\n }\n if (res.status >= 500) {\n // Server error — retry with backoff\n lastErr = new UploaderError(\n 'SERVER_ERROR',\n `HTTP ${res.status}`,\n res.status,\n )\n if (attempt < maxRetries - 1) {\n await sleep(RETRY_BASE_MS * 2 ** attempt, signal)\n }\n continue\n }\n return res\n } catch (err) {\n if (err instanceof UploaderError) {\n if (err.code === 'CLIENT_ERROR' || err.code === 'ABORTED') throw err\n lastErr = err\n } else {\n // fetch() threw (network failure, CORS, etc.)\n lastErr = new UploaderError(\n 'NETWORK_ERROR',\n err instanceof Error ? err.message : String(err),\n )\n }\n if (attempt < maxRetries - 1) {\n await sleep(RETRY_BASE_MS * 2 ** attempt, signal)\n }\n }\n }\n throw lastErr\n}\n\n/**\n * Best-effort SHA-256 (hex) of a Blob for the advisory checksum sent at confirm.\n * Returns undefined when SubtleCrypto is unavailable (insecure context / older\n * runtime) or the file is large — never throws. The server treats this as a hint\n * only, so skipping it is safe.\n */\nasync function sha256Hex(blob: Blob): Promise<string | undefined> {\n try {\n const c = (globalThis as { crypto?: Crypto }).crypto\n if (!c?.subtle || blob.size > SHA256_MAX_BYTES) return undefined\n const digest = await c.subtle.digest('SHA-256', await blob.arrayBuffer())\n return Array.from(new Uint8Array(digest))\n .map((b) => b.toString(16).padStart(2, '0'))\n .join('')\n } catch {\n return undefined\n }\n}\n\n/**\n * PUT a body straight to a presigned bucket URL via XMLHttpRequest.\n *\n * XHR (not fetch) so real upload progress is reported (fetch cannot). The\n * Content-Type header MUST equal the value the server signed, or S3/R2 reject\n * the signature. Bytes go browser → bucket; our server never sees them.\n */\nfunction xhrPut(\n url: string,\n body: Blob,\n opts: { contentType: string; onProgress?: (percent: number) => void; signal?: AbortSignal },\n): Promise<void> {\n return new Promise<void>((resolve, reject) => {\n if (opts.signal?.aborted) {\n reject(new UploaderError('ABORTED', 'Upload aborted'))\n return\n }\n const xhr = new XMLHttpRequest()\n xhr.open('PUT', url)\n xhr.setRequestHeader('Content-Type', opts.contentType)\n\n if (opts.onProgress) {\n xhr.upload.onprogress = (e: ProgressEvent) => {\n if (e.lengthComputable) {\n // Reserve the last 5% for the confirm round-trip.\n opts.onProgress!(Math.round((e.loaded / e.total) * 95))\n }\n }\n }\n xhr.onload = () => {\n if (xhr.status >= 200 && xhr.status < 300) {\n resolve()\n } else {\n const code = xhr.status >= 400 && xhr.status < 500 ? 'CLIENT_ERROR' : 'SERVER_ERROR'\n reject(new UploaderError(code, `Bucket PUT failed: HTTP ${xhr.status}`, xhr.status))\n }\n }\n xhr.onerror = () => reject(new UploaderError('NETWORK_ERROR', 'Bucket PUT network error'))\n xhr.onabort = () => reject(new UploaderError('ABORTED', 'Upload aborted'))\n\n if (opts.signal) {\n opts.signal.addEventListener('abort', () => xhr.abort(), { once: true })\n }\n xhr.send(body)\n })\n}\n\n/**\n * Shape returned by POST /api/uploads/presign.\n *\n * Deliberately no `storageKey`: the server stopped returning it (docs/22\n * §5.3a) because it addresses the object on the raw CDN domain, bypassing\n * delivery protection. Nothing here ever read it — the upload PUTs to\n * `putUrl` and confirms by `handle`.\n */\ntype PresignResponse = { handle: string; putUrl: string; contentType: string }\n\n// ─── UploaderClient ────────────────────────────────────────────────────────────\n\nexport class UploaderClient {\n /** The publishable key (`pk_…`) — a project identifier, not a credential. */\n readonly apikey: string\n readonly apiUrl: string\n readonly security: UploaderClientOptions['security']\n readonly #directUploadOption?: boolean\n /** Client-level default per-file delivery protection (docs/13). */\n readonly #deliveryProtection?: UploaderClientOptions['deliveryProtection']\n /** Client-level default per-file allowed origins (docs/13). */\n readonly #allowedOrigins?: string[]\n /** Optional consumer hook for non-disruptive usage warnings (soft bandwidth cap). */\n readonly #onUsageWarning?: UploaderClientOptions['onUsageWarning']\n\n /** Memoized capability probe — one request per client, shared across uploads. */\n #capsPromise: Promise<{ directUpload: boolean }> | null = null\n\n constructor(options: UploaderClientOptions) {\n this.apikey = options.apikey\n this.apiUrl = (options.apiUrl ?? 'https://api.postila.app').replace(/\\/$/, '')\n this.security = options.security\n this.#directUploadOption = options.directUpload\n this.#deliveryProtection = options.deliveryProtection\n this.#allowedOrigins = options.allowedOrigins\n this.#onUsageWarning = options.onUsageWarning\n }\n\n /**\n * Surface a non-disruptive usage warning carried on an upload response. Routes\n * to the consumer's `onUsageWarning` hook if provided, else `console.warn`.\n * Tolerates arbitrary JSON: only a well-formed `{ usageWarning }` triggers it.\n */\n #emitUsageWarning(body: unknown): void {\n if (typeof body !== 'object' || body === null) return\n const w = (body as { usageWarning?: unknown }).usageWarning\n if (typeof w !== 'object' || w === null) return\n const warning = w as UsageWarning\n if (warning.type !== 'bandwidth') return\n if (this.#onUsageWarning) this.#onUsageWarning(warning)\n else console.warn(`[uploader] ${warning.message}`)\n }\n\n /**\n * Resolve the effective per-file protection for one upload: a per-upload value\n * overrides the client-level default (docs/13). Returns an object carrying ONLY\n * the keys that are set, so callers spread it into the request body and unset\n * fields are omitted entirely — an old server ignores them and the file inherits\n * the account mode (`null`).\n */\n #resolveProtection(opts: UploadOptions): {\n deliveryProtection?: UploaderClientOptions['deliveryProtection']\n allowedOrigins?: string[]\n } {\n const out: { deliveryProtection?: UploaderClientOptions['deliveryProtection']; allowedOrigins?: string[] } = {}\n const mode = opts.deliveryProtection ?? this.#deliveryProtection\n if (mode !== undefined) out.deliveryProtection = mode\n const origins = opts.allowedOrigins ?? this.#allowedOrigins\n if (origins !== undefined) out.allowedOrigins = origins\n return out\n }\n\n // ─── Capability negotiation ─────────────────────────────────────────────────\n\n /**\n * Probe GET /api/capabilities once per client and cache the result. Fails OPEN\n * to the proxied flow ({ directUpload: false }) on any error/timeout, so a\n * flaky probe never blocks uploads and old servers (404) are handled.\n */\n #getCapabilities(): Promise<{ directUpload: boolean }> {\n // Explicit opt-out — never probe, never use direct upload.\n if (this.#directUploadOption === false) {\n return Promise.resolve({ directUpload: false })\n }\n if (!this.#capsPromise) {\n this.#capsPromise = fetch(`${this.apiUrl}/api/capabilities`, {\n headers: this.#authHeaders(),\n })\n .then(async (res) => {\n if (!res.ok) return { directUpload: false }\n const body = (await res.json().catch(() => ({}))) as Record<string, unknown>\n return { directUpload: body['directUpload'] === true }\n })\n .catch(() => ({ directUpload: false }))\n }\n return this.#capsPromise\n }\n\n // ─── Direct-to-bucket upload ────────────────────────────────────────────────\n\n /**\n * Presign → PUT-to-bucket → confirm. Bytes go browser → bucket directly; our\n * server only signs and records. Used when the account has the directUpload\n * capability and the file fits a single PUT.\n */\n async #uploadDirect(file: File | Blob, opts: UploadOptions): Promise<FileResult> {\n const { onProgress, filename, signal } = opts\n const name = filename ?? (file instanceof File ? file.name : 'upload')\n // Must match the Content-Type we send on the PUT (the server signs it).\n const contentType = file instanceof File && file.type ? file.type : 'application/octet-stream'\n\n onProgress?.(0)\n checkAbort(signal)\n\n const protection = this.#resolveProtection(opts)\n const presign = async (): Promise<PresignResponse> => {\n const res = await fetchWithRetry(\n `${this.apiUrl}/api/uploads/presign`,\n {\n method: 'POST',\n headers: { ...this.#authHeaders(), 'Content-Type': 'application/json' },\n body: JSON.stringify({ filename: name, contentType, size: file.size, ...protection }),\n },\n signal,\n )\n return (await res.json()) as PresignResponse\n }\n\n let signed = await presign()\n\n // Advisory checksum (best-effort; unverifiable server-side).\n const checksum = await sha256Hex(file)\n\n // PUT straight to the bucket. A 403 means the short-lived signature lapsed\n // (or a stale key) — re-presign once and retry before giving up.\n try {\n await xhrPut(signed.putUrl, file, { contentType: signed.contentType, onProgress, signal })\n } catch (err) {\n if (err instanceof UploaderError && err.statusCode === 403) {\n signed = await presign()\n await xhrPut(signed.putUrl, file, { contentType: signed.contentType, onProgress, signal })\n } else {\n throw err\n }\n }\n\n // Confirm — server verifies the object exists, records usage, enqueues.\n checkAbort(signal)\n const confirmRes = await fetchWithRetry(\n `${this.apiUrl}/api/uploads/confirm`,\n {\n method: 'POST',\n headers: { ...this.#authHeaders(), 'Content-Type': 'application/json' },\n body: JSON.stringify({ handle: signed.handle, checksum }),\n },\n signal,\n )\n const body = (await confirmRes.json()) as unknown\n onProgress?.(100)\n // Non-disruptive soft-cap heads-up (bandwidth). Emitted before returning so a\n // consumer sees it even if they ignore the return value; never throws.\n this.#emitUsageWarning(body)\n return this.#parseFileResult(body)\n }\n\n // ─── Auth headers ──────────────────────────────────────────────────────────\n\n /**\n * Build the auth headers shared by all requests.\n * Attaches the API key and, when present, the signed policy pair. A per-call\n * `security` overrides the client-level one (the file-management methods\n * need policies with different `call` grants than uploads).\n */\n #authHeaders(security?: SecurityPair): Record<string, string> {\n const headers: Record<string, string> = {\n 'X-Uploader-Key': this.apikey,\n }\n const pair = security ?? this.security\n if (pair) {\n headers['X-Uploader-Policy'] = pair.policy\n headers['X-Uploader-Signature'] = pair.signature\n }\n return headers\n }\n\n // ─── Single-shot upload ────────────────────────────────────────────────────\n\n /**\n * POST /api/store — multipart/form-data for files ≤ MULTIPART_THRESHOLD.\n */\n async #uploadSingleShot(\n file: File | Blob,\n opts: UploadOptions,\n ): Promise<FileResult> {\n const { onProgress, filename, signal } = opts\n\n onProgress?.(0)\n checkAbort(signal)\n\n const form = new FormData()\n form.append('file', file, filename ?? (file instanceof File ? file.name : 'upload'))\n if (filename) form.append('filename', filename)\n // Per-file delivery protection (docs/13) — sent as form fields; allowedOrigins\n // is JSON-encoded to match the server's parse. Omitted when unset (inherit).\n const protection = this.#resolveProtection(opts)\n if (protection.deliveryProtection) form.append('deliveryProtection', protection.deliveryProtection)\n if (protection.allowedOrigins) form.append('allowedOrigins', JSON.stringify(protection.allowedOrigins))\n\n const res = await fetchWithRetry(\n `${this.apiUrl}/api/store`,\n {\n method: 'POST',\n headers: this.#authHeaders(),\n body: form,\n },\n signal,\n )\n\n const body = await res.json() as unknown\n onProgress?.(100)\n return this.#parseFileResult(body)\n }\n\n // ─── Multipart upload ──────────────────────────────────────────────────────\n\n /**\n * start → parts (parallel with progress) → complete for files > MULTIPART_THRESHOLD.\n */\n async #uploadMultipart(\n file: File | Blob,\n parts: Blob[],\n opts: UploadOptions,\n ): Promise<FileResult> {\n const { onProgress, filename, signal } = opts\n const name = filename ?? (file instanceof File ? file.name : 'upload')\n const mime = file instanceof File ? file.type : 'application/octet-stream'\n\n onProgress?.(0)\n checkAbort(signal)\n\n // 1. Start\n const protection = this.#resolveProtection(opts)\n const startRes = await fetchWithRetry(\n `${this.apiUrl}/api/upload/start`,\n {\n method: 'POST',\n headers: { ...this.#authHeaders(), 'Content-Type': 'application/json' },\n body: JSON.stringify({ filename: name, mimetype: mime, size: file.size, ...protection }),\n },\n signal,\n )\n const { uploadId } = await startRes.json() as UploadStartResponse\n\n // 2. Upload parts sequentially (retried individually)\n const etags: { partNumber: number; etag: string }[] = []\n let uploadedBytes = 0\n\n for (let i = 0; i < parts.length; i++) {\n checkAbort(signal)\n const partBlob = parts[i]!\n const partNumber = i + 1\n\n const partForm = new FormData()\n partForm.append('uploadId', uploadId)\n partForm.append('partNumber', String(partNumber))\n partForm.append('part', partBlob)\n\n const partRes = await fetchWithRetry(\n `${this.apiUrl}/api/upload/part`,\n {\n method: 'POST',\n headers: this.#authHeaders(),\n body: partForm,\n },\n signal,\n )\n const { etag } = await partRes.json() as UploadPartResponse\n\n etags.push({ partNumber, etag })\n uploadedBytes += partBlob.size\n onProgress?.(Math.round((uploadedBytes / file.size) * 95)) // reserve 5% for complete\n }\n\n // 3. Complete\n checkAbort(signal)\n const completeRes = await fetchWithRetry(\n `${this.apiUrl}/api/upload/complete`,\n {\n method: 'POST',\n headers: { ...this.#authHeaders(), 'Content-Type': 'application/json' },\n body: JSON.stringify({ uploadId, parts: etags }),\n },\n signal,\n )\n const body = await completeRes.json() as unknown\n onProgress?.(100)\n return this.#parseFileResult(body)\n }\n\n // ─── Response parser ───────────────────────────────────────────────────────\n\n #parseFileResult(body: unknown): FileResult {\n if (\n typeof body !== 'object' ||\n body === null ||\n typeof (body as Record<string, unknown>)['handle'] !== 'string' ||\n typeof (body as Record<string, unknown>)['url'] !== 'string'\n ) {\n throw new UploaderError('INVALID_RESPONSE', 'Unexpected response shape from upload API')\n }\n return body as FileResult\n }\n\n // ─── Public API ───────────────────────────────────────────────────────────\n\n /**\n * Upload a single file.\n *\n * Automatically selects single-shot vs multipart upload based on file size.\n * Emits progress via `opts.onProgress` (0–100). Respects `opts.signal` for\n * cancellation. Retries network errors and 5xx responses up to 3 times with\n * exponential backoff; 4xx errors are surfaced immediately.\n *\n * @throws {UploaderError} with code ABORTED | NETWORK_ERROR | SERVER_ERROR |\n * CLIENT_ERROR | INVALID_RESPONSE\n */\n async upload(\n file: File | Blob,\n opts: UploadOptions = {},\n ): Promise<FileResult> {\n // Prefer direct-to-bucket when the account supports it and the file fits a\n // single PUT. The probe fails open, so an old server or a disabled account\n // transparently uses the proxied flow below.\n const caps = await this.#getCapabilities()\n if (caps.directUpload && file.size <= MAX_DIRECT_PUT_BYTES) {\n return this.#uploadDirect(file, opts)\n }\n\n const plan = planChunks(file, opts.chunkSize)\n if (plan.mode === 'single') {\n return this.#uploadSingleShot(file, opts)\n }\n return this.#uploadMultipart(file, plan.parts, opts)\n }\n\n /**\n * List the account's files (paginated, 50 per page).\n *\n * Accounts with required signed policies must supply a policy whose `call`\n * includes `'list'` — a handle-bound `'read'` policy from a delivery URL is\n * deliberately not enough to enumerate the account.\n *\n * @throws {UploaderError} with code NETWORK_ERROR | SERVER_ERROR |\n * CLIENT_ERROR | INVALID_RESPONSE\n */\n async listFiles(query: ListFilesQuery = {}, opts: FileManageOptions = {}): Promise<ListFilesResult> {\n const params = new URLSearchParams()\n for (const [k, v] of Object.entries(query)) {\n if (v !== undefined) params.set(k, String(v))\n }\n const qs = params.toString()\n const res = await fetchWithRetry(\n `${this.apiUrl}/api/files${qs ? `?${qs}` : ''}`,\n { headers: this.#authHeaders(opts.security) },\n opts.signal,\n )\n const body = (await res.json().catch(() => null)) as ListFilesResult | null\n if (!body || !Array.isArray(body.files)) {\n throw new UploaderError('INVALID_RESPONSE', 'Unexpected response shape from file list API')\n }\n return body\n }\n\n /**\n * Fetch one file's record by its public handle.\n *\n * Accounts with required signed policies must supply a policy whose `call`\n * includes `'read'` (handle-bound policies must match this handle).\n *\n * @throws {UploaderError} — CLIENT_ERROR with statusCode 404 when the handle\n * does not exist (or belongs to another account).\n */\n async getFile(handle: string, opts: FileManageOptions = {}): Promise<StoredFileRecord> {\n const res = await fetchWithRetry(\n `${this.apiUrl}/api/files/${encodeURIComponent(handle)}`,\n { headers: this.#authHeaders(opts.security) },\n opts.signal,\n )\n const body = (await res.json().catch(() => null)) as StoredFileRecord | null\n if (!body || typeof body.handle !== 'string') {\n throw new UploaderError('INVALID_RESPONSE', 'Unexpected response shape from file API')\n }\n return body\n }\n\n /**\n * Delete a file by its public handle.\n *\n * The file stops being served immediately (soft-delete + edge cache purge);\n * the stored bytes and derivatives are removed by a background cleanup.\n * Accounts with required signed policies must supply a policy whose `call`\n * includes `'remove'` (handle-bound policies must match this handle).\n *\n * @throws {UploaderError} — CLIENT_ERROR with statusCode 404 when the handle\n * does not exist (or belongs to another account).\n */\n async deleteFile(handle: string, opts: FileManageOptions = {}): Promise<void> {\n await fetchWithRetry(\n `${this.apiUrl}/api/files/${encodeURIComponent(handle)}`,\n { method: 'DELETE', headers: this.#authHeaders(opts.security) },\n opts.signal,\n )\n }\n\n /**\n * Upload multiple files with concurrency limiting.\n *\n * Resolves once all uploads settle (fulfilled or rejected). The returned\n * array preserves input order. `opts.concurrency` caps simultaneous\n * in-flight uploads (default 3). Each file shares the same opts\n * (including onProgress — the callback fires per-file, not aggregate).\n *\n * @returns Array of PromiseSettledResult in input order.\n */\n async uploadAll(\n files: Array<File | Blob>,\n opts: UploadAllOptions = {},\n ): Promise<PromiseSettledResult<FileResult>[]> {\n const concurrency = opts.concurrency ?? 3\n const results: PromiseSettledResult<FileResult>[] = new Array(files.length)\n\n let index = 0\n\n async function worker(client: UploaderClient): Promise<void> {\n while (index < files.length) {\n const i = index++\n const file = files[i]!\n try {\n results[i] = { status: 'fulfilled', value: await client.upload(file, opts) }\n } catch (err) {\n results[i] = { status: 'rejected', reason: err }\n }\n }\n }\n\n const workers = Array.from({ length: Math.min(concurrency, files.length) }, () =>\n worker(this),\n )\n await Promise.all(workers)\n return results\n }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -62,9 +62,26 @@ type UsageWarning = {
|
|
|
62
62
|
*/
|
|
63
63
|
type DeliveryProtection = 'public' | 'hotlink' | 'signed';
|
|
64
64
|
type UploaderClientOptions = {
|
|
65
|
-
/**
|
|
65
|
+
/**
|
|
66
|
+
* Your **publishable key** (`pk_…`) — a project identifier, safe to ship in
|
|
67
|
+
* client code.
|
|
68
|
+
*
|
|
69
|
+
* Deliberately NOT called "authentication": it identifies which account an
|
|
70
|
+
* upload belongs to, and anyone who views your page can read it. If uploads
|
|
71
|
+
* to your account must be authorized, enable required signed policies for
|
|
72
|
+
* the account and mint a policy server-side (`security` below) — that is the
|
|
73
|
+
* credential. Treating the publishable key as a secret is the mistake this
|
|
74
|
+
* name exists to prevent.
|
|
75
|
+
*
|
|
76
|
+
* (The option is spelled `apikey`, lowercase, for backwards compatibility —
|
|
77
|
+
* renaming it would break every existing integration.)
|
|
78
|
+
*/
|
|
66
79
|
apikey: string;
|
|
67
|
-
/**
|
|
80
|
+
/**
|
|
81
|
+
* Base URL of the upload API. Defaults to the hosted endpoint
|
|
82
|
+
* (`https://api.postila.app`). Pass `''` to use same-origin relative URLs
|
|
83
|
+
* (e.g. behind a dev proxy).
|
|
84
|
+
*/
|
|
68
85
|
apiUrl?: string;
|
|
69
86
|
/** Optional signed security policy. */
|
|
70
87
|
security?: {
|
|
@@ -103,7 +120,11 @@ type UploaderClientOptions = {
|
|
|
103
120
|
type UploadOptions = {
|
|
104
121
|
/** Progress callback, value 0–100. */
|
|
105
122
|
onProgress?: (percent: number) => void;
|
|
106
|
-
/**
|
|
123
|
+
/**
|
|
124
|
+
* @deprecated Ignored — the server assigns storage keys and no upload route
|
|
125
|
+
* reads a path prefix. Kept only for type compatibility; passing it has no
|
|
126
|
+
* effect on where the file is stored.
|
|
127
|
+
*/
|
|
107
128
|
path?: string;
|
|
108
129
|
/** Override the file's stored name. */
|
|
109
130
|
filename?: string;
|
|
@@ -131,6 +152,69 @@ type UploadAllOptions = UploadOptions & {
|
|
|
131
152
|
/** Maximum concurrent uploads. Defaults to 3. */
|
|
132
153
|
concurrency?: number;
|
|
133
154
|
};
|
|
155
|
+
/** A signed policy + signature pair, as minted by your backend. */
|
|
156
|
+
type SecurityPair = {
|
|
157
|
+
policy: string;
|
|
158
|
+
signature: string;
|
|
159
|
+
};
|
|
160
|
+
/** Lifecycle status of a stored file. */
|
|
161
|
+
type StoredFileStatus = 'uploading' | 'stored' | 'processing' | 'ready' | 'flagged' | 'quarantined' | 'deleted';
|
|
162
|
+
/**
|
|
163
|
+
* One stored file's record, as returned by the file-management API
|
|
164
|
+
* (GET /api/files). Richer than the upload-time FileResult: carries lifecycle
|
|
165
|
+
* status, timestamps, image dimensions, and tags.
|
|
166
|
+
*/
|
|
167
|
+
type StoredFileRecord = {
|
|
168
|
+
id: string;
|
|
169
|
+
handle: string;
|
|
170
|
+
filename: string;
|
|
171
|
+
mimetype: string;
|
|
172
|
+
size: number;
|
|
173
|
+
status: StoredFileStatus;
|
|
174
|
+
url: string;
|
|
175
|
+
createdAt: string;
|
|
176
|
+
updatedAt: string;
|
|
177
|
+
width?: number | null;
|
|
178
|
+
height?: number | null;
|
|
179
|
+
tags?: string[];
|
|
180
|
+
};
|
|
181
|
+
/** Query filters for listFiles. All optional; pages are fixed-size (50). */
|
|
182
|
+
type ListFilesQuery = {
|
|
183
|
+
/** Full-text search over filename (and extracted text). */
|
|
184
|
+
q?: string;
|
|
185
|
+
/** Mimetype prefix filter, e.g. "image" or "image/png". */
|
|
186
|
+
mimetype?: string;
|
|
187
|
+
status?: StoredFileStatus;
|
|
188
|
+
tag?: string;
|
|
189
|
+
sizeMin?: number;
|
|
190
|
+
sizeMax?: number;
|
|
191
|
+
/** ISO date bounds on creation time. */
|
|
192
|
+
dateFrom?: string;
|
|
193
|
+
dateTo?: string;
|
|
194
|
+
/** Sort key; prefix with '-' for descending, e.g. '-created_at'. */
|
|
195
|
+
sort?: 'created_at' | '-created_at' | 'size' | '-size' | 'filename' | '-filename';
|
|
196
|
+
/** 1-based page number. */
|
|
197
|
+
page?: number;
|
|
198
|
+
};
|
|
199
|
+
/** Paginated result of listFiles. */
|
|
200
|
+
type ListFilesResult = {
|
|
201
|
+
files: StoredFileRecord[];
|
|
202
|
+
total: number;
|
|
203
|
+
page: number;
|
|
204
|
+
pageSize: number;
|
|
205
|
+
};
|
|
206
|
+
/**
|
|
207
|
+
* Per-call options for the file-management methods.
|
|
208
|
+
*
|
|
209
|
+
* `security` overrides the client-level policy for this one call — useful
|
|
210
|
+
* because these operations need different policy `call` grants than uploads:
|
|
211
|
+
* listFiles needs `list`, getFile needs `read`, deleteFile needs `remove`
|
|
212
|
+
* (accounts without required signed policies need none of this).
|
|
213
|
+
*/
|
|
214
|
+
type FileManageOptions = {
|
|
215
|
+
security?: SecurityPair;
|
|
216
|
+
signal?: AbortSignal;
|
|
217
|
+
};
|
|
134
218
|
|
|
135
219
|
/**
|
|
136
220
|
* UploaderClient — headless upload client.
|
|
@@ -143,6 +227,7 @@ type UploadAllOptions = UploadOptions & {
|
|
|
143
227
|
|
|
144
228
|
declare class UploaderClient {
|
|
145
229
|
#private;
|
|
230
|
+
/** The publishable key (`pk_…`) — a project identifier, not a credential. */
|
|
146
231
|
readonly apikey: string;
|
|
147
232
|
readonly apiUrl: string;
|
|
148
233
|
readonly security: UploaderClientOptions['security'];
|
|
@@ -159,6 +244,39 @@ declare class UploaderClient {
|
|
|
159
244
|
* CLIENT_ERROR | INVALID_RESPONSE
|
|
160
245
|
*/
|
|
161
246
|
upload(file: File | Blob, opts?: UploadOptions): Promise<FileResult>;
|
|
247
|
+
/**
|
|
248
|
+
* List the account's files (paginated, 50 per page).
|
|
249
|
+
*
|
|
250
|
+
* Accounts with required signed policies must supply a policy whose `call`
|
|
251
|
+
* includes `'list'` — a handle-bound `'read'` policy from a delivery URL is
|
|
252
|
+
* deliberately not enough to enumerate the account.
|
|
253
|
+
*
|
|
254
|
+
* @throws {UploaderError} with code NETWORK_ERROR | SERVER_ERROR |
|
|
255
|
+
* CLIENT_ERROR | INVALID_RESPONSE
|
|
256
|
+
*/
|
|
257
|
+
listFiles(query?: ListFilesQuery, opts?: FileManageOptions): Promise<ListFilesResult>;
|
|
258
|
+
/**
|
|
259
|
+
* Fetch one file's record by its public handle.
|
|
260
|
+
*
|
|
261
|
+
* Accounts with required signed policies must supply a policy whose `call`
|
|
262
|
+
* includes `'read'` (handle-bound policies must match this handle).
|
|
263
|
+
*
|
|
264
|
+
* @throws {UploaderError} — CLIENT_ERROR with statusCode 404 when the handle
|
|
265
|
+
* does not exist (or belongs to another account).
|
|
266
|
+
*/
|
|
267
|
+
getFile(handle: string, opts?: FileManageOptions): Promise<StoredFileRecord>;
|
|
268
|
+
/**
|
|
269
|
+
* Delete a file by its public handle.
|
|
270
|
+
*
|
|
271
|
+
* The file stops being served immediately (soft-delete + edge cache purge);
|
|
272
|
+
* the stored bytes and derivatives are removed by a background cleanup.
|
|
273
|
+
* Accounts with required signed policies must supply a policy whose `call`
|
|
274
|
+
* includes `'remove'` (handle-bound policies must match this handle).
|
|
275
|
+
*
|
|
276
|
+
* @throws {UploaderError} — CLIENT_ERROR with statusCode 404 when the handle
|
|
277
|
+
* does not exist (or belongs to another account).
|
|
278
|
+
*/
|
|
279
|
+
deleteFile(handle: string, opts?: FileManageOptions): Promise<void>;
|
|
162
280
|
/**
|
|
163
281
|
* Upload multiple files with concurrency limiting.
|
|
164
282
|
*
|
|
@@ -172,4 +290,4 @@ declare class UploaderClient {
|
|
|
172
290
|
uploadAll(files: Array<File | Blob>, opts?: UploadAllOptions): Promise<PromiseSettledResult<FileResult>[]>;
|
|
173
291
|
}
|
|
174
292
|
|
|
175
|
-
export { type DeliveryProtection as D, type
|
|
293
|
+
export { type DeliveryProtection as D, type FileManageOptions as F, type ListFilesQuery as L, type PickerResponse as P, type SecurityPair as S, type UploadAllOptions as U, type FileResult as a, type ListFilesResult as b, type PolicySpec as c, type StoredFileRecord as d, type StoredFileStatus as e, type UploadOptions as f, UploaderClient as g, type UploaderClientOptions as h, type UsageWarning as i };
|
|
@@ -62,9 +62,26 @@ type UsageWarning = {
|
|
|
62
62
|
*/
|
|
63
63
|
type DeliveryProtection = 'public' | 'hotlink' | 'signed';
|
|
64
64
|
type UploaderClientOptions = {
|
|
65
|
-
/**
|
|
65
|
+
/**
|
|
66
|
+
* Your **publishable key** (`pk_…`) — a project identifier, safe to ship in
|
|
67
|
+
* client code.
|
|
68
|
+
*
|
|
69
|
+
* Deliberately NOT called "authentication": it identifies which account an
|
|
70
|
+
* upload belongs to, and anyone who views your page can read it. If uploads
|
|
71
|
+
* to your account must be authorized, enable required signed policies for
|
|
72
|
+
* the account and mint a policy server-side (`security` below) — that is the
|
|
73
|
+
* credential. Treating the publishable key as a secret is the mistake this
|
|
74
|
+
* name exists to prevent.
|
|
75
|
+
*
|
|
76
|
+
* (The option is spelled `apikey`, lowercase, for backwards compatibility —
|
|
77
|
+
* renaming it would break every existing integration.)
|
|
78
|
+
*/
|
|
66
79
|
apikey: string;
|
|
67
|
-
/**
|
|
80
|
+
/**
|
|
81
|
+
* Base URL of the upload API. Defaults to the hosted endpoint
|
|
82
|
+
* (`https://api.postila.app`). Pass `''` to use same-origin relative URLs
|
|
83
|
+
* (e.g. behind a dev proxy).
|
|
84
|
+
*/
|
|
68
85
|
apiUrl?: string;
|
|
69
86
|
/** Optional signed security policy. */
|
|
70
87
|
security?: {
|
|
@@ -103,7 +120,11 @@ type UploaderClientOptions = {
|
|
|
103
120
|
type UploadOptions = {
|
|
104
121
|
/** Progress callback, value 0–100. */
|
|
105
122
|
onProgress?: (percent: number) => void;
|
|
106
|
-
/**
|
|
123
|
+
/**
|
|
124
|
+
* @deprecated Ignored — the server assigns storage keys and no upload route
|
|
125
|
+
* reads a path prefix. Kept only for type compatibility; passing it has no
|
|
126
|
+
* effect on where the file is stored.
|
|
127
|
+
*/
|
|
107
128
|
path?: string;
|
|
108
129
|
/** Override the file's stored name. */
|
|
109
130
|
filename?: string;
|
|
@@ -131,6 +152,69 @@ type UploadAllOptions = UploadOptions & {
|
|
|
131
152
|
/** Maximum concurrent uploads. Defaults to 3. */
|
|
132
153
|
concurrency?: number;
|
|
133
154
|
};
|
|
155
|
+
/** A signed policy + signature pair, as minted by your backend. */
|
|
156
|
+
type SecurityPair = {
|
|
157
|
+
policy: string;
|
|
158
|
+
signature: string;
|
|
159
|
+
};
|
|
160
|
+
/** Lifecycle status of a stored file. */
|
|
161
|
+
type StoredFileStatus = 'uploading' | 'stored' | 'processing' | 'ready' | 'flagged' | 'quarantined' | 'deleted';
|
|
162
|
+
/**
|
|
163
|
+
* One stored file's record, as returned by the file-management API
|
|
164
|
+
* (GET /api/files). Richer than the upload-time FileResult: carries lifecycle
|
|
165
|
+
* status, timestamps, image dimensions, and tags.
|
|
166
|
+
*/
|
|
167
|
+
type StoredFileRecord = {
|
|
168
|
+
id: string;
|
|
169
|
+
handle: string;
|
|
170
|
+
filename: string;
|
|
171
|
+
mimetype: string;
|
|
172
|
+
size: number;
|
|
173
|
+
status: StoredFileStatus;
|
|
174
|
+
url: string;
|
|
175
|
+
createdAt: string;
|
|
176
|
+
updatedAt: string;
|
|
177
|
+
width?: number | null;
|
|
178
|
+
height?: number | null;
|
|
179
|
+
tags?: string[];
|
|
180
|
+
};
|
|
181
|
+
/** Query filters for listFiles. All optional; pages are fixed-size (50). */
|
|
182
|
+
type ListFilesQuery = {
|
|
183
|
+
/** Full-text search over filename (and extracted text). */
|
|
184
|
+
q?: string;
|
|
185
|
+
/** Mimetype prefix filter, e.g. "image" or "image/png". */
|
|
186
|
+
mimetype?: string;
|
|
187
|
+
status?: StoredFileStatus;
|
|
188
|
+
tag?: string;
|
|
189
|
+
sizeMin?: number;
|
|
190
|
+
sizeMax?: number;
|
|
191
|
+
/** ISO date bounds on creation time. */
|
|
192
|
+
dateFrom?: string;
|
|
193
|
+
dateTo?: string;
|
|
194
|
+
/** Sort key; prefix with '-' for descending, e.g. '-created_at'. */
|
|
195
|
+
sort?: 'created_at' | '-created_at' | 'size' | '-size' | 'filename' | '-filename';
|
|
196
|
+
/** 1-based page number. */
|
|
197
|
+
page?: number;
|
|
198
|
+
};
|
|
199
|
+
/** Paginated result of listFiles. */
|
|
200
|
+
type ListFilesResult = {
|
|
201
|
+
files: StoredFileRecord[];
|
|
202
|
+
total: number;
|
|
203
|
+
page: number;
|
|
204
|
+
pageSize: number;
|
|
205
|
+
};
|
|
206
|
+
/**
|
|
207
|
+
* Per-call options for the file-management methods.
|
|
208
|
+
*
|
|
209
|
+
* `security` overrides the client-level policy for this one call — useful
|
|
210
|
+
* because these operations need different policy `call` grants than uploads:
|
|
211
|
+
* listFiles needs `list`, getFile needs `read`, deleteFile needs `remove`
|
|
212
|
+
* (accounts without required signed policies need none of this).
|
|
213
|
+
*/
|
|
214
|
+
type FileManageOptions = {
|
|
215
|
+
security?: SecurityPair;
|
|
216
|
+
signal?: AbortSignal;
|
|
217
|
+
};
|
|
134
218
|
|
|
135
219
|
/**
|
|
136
220
|
* UploaderClient — headless upload client.
|
|
@@ -143,6 +227,7 @@ type UploadAllOptions = UploadOptions & {
|
|
|
143
227
|
|
|
144
228
|
declare class UploaderClient {
|
|
145
229
|
#private;
|
|
230
|
+
/** The publishable key (`pk_…`) — a project identifier, not a credential. */
|
|
146
231
|
readonly apikey: string;
|
|
147
232
|
readonly apiUrl: string;
|
|
148
233
|
readonly security: UploaderClientOptions['security'];
|
|
@@ -159,6 +244,39 @@ declare class UploaderClient {
|
|
|
159
244
|
* CLIENT_ERROR | INVALID_RESPONSE
|
|
160
245
|
*/
|
|
161
246
|
upload(file: File | Blob, opts?: UploadOptions): Promise<FileResult>;
|
|
247
|
+
/**
|
|
248
|
+
* List the account's files (paginated, 50 per page).
|
|
249
|
+
*
|
|
250
|
+
* Accounts with required signed policies must supply a policy whose `call`
|
|
251
|
+
* includes `'list'` — a handle-bound `'read'` policy from a delivery URL is
|
|
252
|
+
* deliberately not enough to enumerate the account.
|
|
253
|
+
*
|
|
254
|
+
* @throws {UploaderError} with code NETWORK_ERROR | SERVER_ERROR |
|
|
255
|
+
* CLIENT_ERROR | INVALID_RESPONSE
|
|
256
|
+
*/
|
|
257
|
+
listFiles(query?: ListFilesQuery, opts?: FileManageOptions): Promise<ListFilesResult>;
|
|
258
|
+
/**
|
|
259
|
+
* Fetch one file's record by its public handle.
|
|
260
|
+
*
|
|
261
|
+
* Accounts with required signed policies must supply a policy whose `call`
|
|
262
|
+
* includes `'read'` (handle-bound policies must match this handle).
|
|
263
|
+
*
|
|
264
|
+
* @throws {UploaderError} — CLIENT_ERROR with statusCode 404 when the handle
|
|
265
|
+
* does not exist (or belongs to another account).
|
|
266
|
+
*/
|
|
267
|
+
getFile(handle: string, opts?: FileManageOptions): Promise<StoredFileRecord>;
|
|
268
|
+
/**
|
|
269
|
+
* Delete a file by its public handle.
|
|
270
|
+
*
|
|
271
|
+
* The file stops being served immediately (soft-delete + edge cache purge);
|
|
272
|
+
* the stored bytes and derivatives are removed by a background cleanup.
|
|
273
|
+
* Accounts with required signed policies must supply a policy whose `call`
|
|
274
|
+
* includes `'remove'` (handle-bound policies must match this handle).
|
|
275
|
+
*
|
|
276
|
+
* @throws {UploaderError} — CLIENT_ERROR with statusCode 404 when the handle
|
|
277
|
+
* does not exist (or belongs to another account).
|
|
278
|
+
*/
|
|
279
|
+
deleteFile(handle: string, opts?: FileManageOptions): Promise<void>;
|
|
162
280
|
/**
|
|
163
281
|
* Upload multiple files with concurrency limiting.
|
|
164
282
|
*
|
|
@@ -172,4 +290,4 @@ declare class UploaderClient {
|
|
|
172
290
|
uploadAll(files: Array<File | Blob>, opts?: UploadAllOptions): Promise<PromiseSettledResult<FileResult>[]>;
|
|
173
291
|
}
|
|
174
292
|
|
|
175
|
-
export { type DeliveryProtection as D, type
|
|
293
|
+
export { type DeliveryProtection as D, type FileManageOptions as F, type ListFilesQuery as L, type PickerResponse as P, type SecurityPair as S, type UploadAllOptions as U, type FileResult as a, type ListFilesResult as b, type PolicySpec as c, type StoredFileRecord as d, type StoredFileStatus as e, type UploadOptions as f, UploaderClient as g, type UploaderClientOptions as h, type UsageWarning as i };
|