@dloizides/content-upload 1.0.1 → 1.2.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/CHANGELOG.md +38 -13
- package/README.md +101 -63
- package/dist/index.d.mts +101 -12
- package/dist/index.d.ts +101 -12
- package/dist/index.js +220 -24
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +213 -25
- package/dist/index.mjs.map +1 -1
- package/package.json +4 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,13 +1,38 @@
|
|
|
1
|
-
# Changelog
|
|
2
|
-
|
|
3
|
-
## 1.
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
- `
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
- `
|
|
11
|
-
- `
|
|
12
|
-
-
|
|
13
|
-
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 1.2.0
|
|
4
|
+
|
|
5
|
+
RUPEP-5 "Upload the RuPEP file from the Jobs tab" -- resumable tus upload.
|
|
6
|
+
|
|
7
|
+
- `uploadResumable({ endpoint, file, metadata?, gzip?, chunkSize?, headers?, withCredentials?, onProgress?, signal?, uploadFactory? })`
|
|
8
|
+
resolves the tus upload id (last segment of the upload URL). Built on `tus-js-client` 4 (MIT).
|
|
9
|
+
- 16 MiB chunks by default; cookies sent (`withCredentials`) unless `withCredentials: false`; `headers` carry the CSRF header.
|
|
10
|
+
- `gzip: true` compresses with `CompressionStream` as a stream and uses tus deferred length (`encoding=gzip`).
|
|
11
|
+
Where `CompressionStream` is missing the file is uploaded as-is with `encoding=identity`.
|
|
12
|
+
- Resume: a fingerprint of endpoint + size + lastModified + metadata is stored; the newest stored upload is resumed.
|
|
13
|
+
A gzip resume re-compresses from the start and skips the bytes already on the server (same engine = same bytes).
|
|
14
|
+
- `signal` abort terminates the server upload (tus termination) and rejects with `UploadErrorKind.Aborted`.
|
|
15
|
+
- Progress for gzip uploads counts source bytes read and holds at 99 until the upload finishes.
|
|
16
|
+
|
|
17
|
+
## 1.1.0
|
|
18
|
+
|
|
19
|
+
RUPEP-5 "Upload the RuPEP file from the Jobs tab".
|
|
20
|
+
|
|
21
|
+
- `uploadFormWithProgress({ url, parts, headers?, onProgress?, signal?, withCredentials?, fetchImpl?, xhrFactory? })`:
|
|
22
|
+
generic multipart POST with progress; resolves parsed JSON (`null` for an empty 2xx body).
|
|
23
|
+
Credentials default to `include`; any Content-Type header is dropped; CSRF goes in `headers`.
|
|
24
|
+
- `buildFormRequest(options)` to pin the write shape.
|
|
25
|
+
- `UploadError.body` carries the raw response body of an `Http` failure (both transports).
|
|
26
|
+
- `uploadImage` now shares the transport selection (`sendUploadRequest`); behaviour unchanged.
|
|
27
|
+
|
|
28
|
+
## 1.0.0
|
|
29
|
+
|
|
30
|
+
Initial release (KEFI-PEOPLE-1 "Organizer people editor", task T3).
|
|
31
|
+
|
|
32
|
+
- `uploadImage(file, options)`: multipart POST to a consumer-supplied endpoint; CSRF header,
|
|
33
|
+
credentials, client-side validation (empty / `maxBytes` / `allowedTypes`), abort, progress over
|
|
34
|
+
XMLHttpRequest, typed `UploadError` with `UploadErrorKind`.
|
|
35
|
+
- `buildUploadRequest` for pinning the write shape (no Content-Type on FormData).
|
|
36
|
+
- `blobFromUri(uri)`.
|
|
37
|
+
- `useImageUpload(options)` hook: `UploadStatus` idle / uploading / error / done, progress,
|
|
38
|
+
supersede-on-new-upload, abort on reset and unmount.
|
package/README.md
CHANGED
|
@@ -1,63 +1,101 @@
|
|
|
1
|
-
# @dloizides/content-upload
|
|
2
|
-
|
|
3
|
-
Image upload for the dloizides.com RN-web portals: one multipart POST to the ContentService
|
|
4
|
-
through a same-origin BFF proxy, plus a React hook with `idle / uploading / error / done` states.
|
|
5
|
-
Merged from kefi-web `uploadContent.ts` and katalogos-web `useUploadContent.ts`.
|
|
6
|
-
|
|
7
|
-
## Install
|
|
8
|
-
|
|
9
|
-
```bash
|
|
10
|
-
npm install @dloizides/content-upload
|
|
11
|
-
```
|
|
12
|
-
|
|
13
|
-
Peer dependency: `react >= 18` (only for the hook).
|
|
14
|
-
|
|
15
|
-
## Client
|
|
16
|
-
|
|
17
|
-
```ts
|
|
18
|
-
import { uploadImage, UploadError, UploadErrorKind } from '@dloizides/content-upload';
|
|
19
|
-
|
|
20
|
-
const content = await uploadImage(file, {
|
|
21
|
-
endpoint: '/bff/api/content/api/v1/content/upload', // consumer-supplied
|
|
22
|
-
maxBytes: 5 * 1024 * 1024,
|
|
23
|
-
allowedTypes: ['image/png', 'image/jpeg', 'image/webp'],
|
|
24
|
-
});
|
|
25
|
-
// content = { contentId, status, url }
|
|
26
|
-
```
|
|
27
|
-
|
|
28
|
-
The request is `multipart/form-data` with fields `File`, `Category` (default `Image`) and
|
|
29
|
-
`IsPublic` (default `true`), `credentials: 'include'` and the `X-BFF-Csrf: 1` anti-forgery header
|
|
30
|
-
(`csrf: null` disables it, `csrf: { name, value }` replaces it). **No Content-Type is ever set**:
|
|
31
|
-
the runtime adds the multipart boundary, and a JSON content type on a FormData body is a 415 (the
|
|
32
|
-
same rule `@dloizides/bff-web-client`'s `writeContentTypeGuard` enforces for axios).
|
|
33
|
-
|
|
34
|
-
Every failure throws `UploadError` with a `kind`: `Validation`, `Network`, `Http` (`status` set),
|
|
35
|
-
`Aborted`, `InvalidResponse`. Map `kind` to translated copy; `message` is developer-facing.
|
|
36
|
-
|
|
37
|
-
Transport: with `onProgress` and no `fetchImpl`, the upload uses `XMLHttpRequest` (the only
|
|
38
|
-
transport that reports upload progress, available in browsers and React Native). Otherwise it
|
|
39
|
-
uses `fetch`. `signal` aborts either.
|
|
40
|
-
|
|
41
|
-
`blobFromUri(uri)` reads a `blob:` / `data:` / RN picker `file:` URI into a `Blob`.
|
|
42
|
-
|
|
43
|
-
##
|
|
44
|
-
|
|
45
|
-
```ts
|
|
46
|
-
import {
|
|
47
|
-
|
|
48
|
-
const
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
1
|
+
# @dloizides/content-upload
|
|
2
|
+
|
|
3
|
+
Image upload for the dloizides.com RN-web portals: one multipart POST to the ContentService
|
|
4
|
+
through a same-origin BFF proxy, plus a React hook with `idle / uploading / error / done` states.
|
|
5
|
+
Merged from kefi-web `uploadContent.ts` and katalogos-web `useUploadContent.ts`.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install @dloizides/content-upload
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Peer dependency: `react >= 18` (only for the hook).
|
|
14
|
+
|
|
15
|
+
## Client
|
|
16
|
+
|
|
17
|
+
```ts
|
|
18
|
+
import { uploadImage, UploadError, UploadErrorKind } from '@dloizides/content-upload';
|
|
19
|
+
|
|
20
|
+
const content = await uploadImage(file, {
|
|
21
|
+
endpoint: '/bff/api/content/api/v1/content/upload', // consumer-supplied
|
|
22
|
+
maxBytes: 5 * 1024 * 1024,
|
|
23
|
+
allowedTypes: ['image/png', 'image/jpeg', 'image/webp'],
|
|
24
|
+
});
|
|
25
|
+
// content = { contentId, status, url }
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
The request is `multipart/form-data` with fields `File`, `Category` (default `Image`) and
|
|
29
|
+
`IsPublic` (default `true`), `credentials: 'include'` and the `X-BFF-Csrf: 1` anti-forgery header
|
|
30
|
+
(`csrf: null` disables it, `csrf: { name, value }` replaces it). **No Content-Type is ever set**:
|
|
31
|
+
the runtime adds the multipart boundary, and a JSON content type on a FormData body is a 415 (the
|
|
32
|
+
same rule `@dloizides/bff-web-client`'s `writeContentTypeGuard` enforces for axios).
|
|
33
|
+
|
|
34
|
+
Every failure throws `UploadError` with a `kind`: `Validation`, `Network`, `Http` (`status` set),
|
|
35
|
+
`Aborted`, `InvalidResponse`. Map `kind` to translated copy; `message` is developer-facing.
|
|
36
|
+
|
|
37
|
+
Transport: with `onProgress` and no `fetchImpl`, the upload uses `XMLHttpRequest` (the only
|
|
38
|
+
transport that reports upload progress, available in browsers and React Native). Otherwise it
|
|
39
|
+
uses `fetch`. `signal` aborts either.
|
|
40
|
+
|
|
41
|
+
`blobFromUri(uri)` reads a `blob:` / `data:` / RN picker `file:` URI into a `Blob`.
|
|
42
|
+
|
|
43
|
+
## Generic multipart upload with progress
|
|
44
|
+
|
|
45
|
+
```ts
|
|
46
|
+
import { uploadFormWithProgress, UploadError, UploadErrorKind } from '@dloizides/content-upload';
|
|
47
|
+
|
|
48
|
+
const result = await uploadFormWithProgress<{ jobId: string }>({
|
|
49
|
+
url: '/bff/api/aml/admin/jobs/rupep/upload',
|
|
50
|
+
parts: { file },
|
|
51
|
+
headers: { 'X-BFF-Csrf': '1' },
|
|
52
|
+
onProgress: (pct) => setPct(pct),
|
|
53
|
+
signal: controller.signal,
|
|
54
|
+
});
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Cookies are sent by default (`withCredentials: false` to opt out). A Content-Type header is
|
|
58
|
+
dropped. An `Http` failure carries `error.status` and the raw `error.body`; an empty 2xx body
|
|
59
|
+
resolves `null`.
|
|
60
|
+
|
|
61
|
+
## Hook
|
|
62
|
+
|
|
63
|
+
```ts
|
|
64
|
+
import { useImageUpload, UploadStatus } from '@dloizides/content-upload';
|
|
65
|
+
|
|
66
|
+
const { status, progress, error, result, upload, reset } = useImageUpload({
|
|
67
|
+
endpoint: '/bff/api/content/api/v1/content/upload',
|
|
68
|
+
onSuccess: (content) => setAvatarUrl(content.url),
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
await upload(file); // resolves to the content, or null on error / abort
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
- A new `upload()` or `reset()` aborts the in-flight request; its late result is dropped.
|
|
75
|
+
- Unmount aborts the in-flight request.
|
|
76
|
+
- Options are read at call time, so an inline options object is fine.
|
|
77
|
+
|
|
78
|
+
## Scripts
|
|
79
|
+
|
|
80
|
+
`npm run lint` · `npm run typecheck` · `npm run test:coverage` (100% threshold) · `npm run build`.
|
|
81
|
+
Publish only via `./publish.ps1 -Bump <patch|minor|major>`.
|
|
82
|
+
|
|
83
|
+
## Resumable upload (tus)
|
|
84
|
+
|
|
85
|
+
```ts
|
|
86
|
+
const id = await uploadResumable({
|
|
87
|
+
endpoint: '/bff/api/aml/v1/ingest/uploads',
|
|
88
|
+
file,
|
|
89
|
+
metadata: { part: 'persons' },
|
|
90
|
+
gzip: true,
|
|
91
|
+
headers: { 'X-BFF-Csrf': '1' },
|
|
92
|
+
onProgress: setPercent,
|
|
93
|
+
signal: controller.signal,
|
|
94
|
+
});
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
- tus 1.0 via `tus-js-client`; 16 MiB chunks; cookies sent by default.
|
|
98
|
+
- `gzip: true` streams through `CompressionStream` with deferred length (`encoding=gzip`); without
|
|
99
|
+
`CompressionStream` the file goes up uncompressed (`encoding=identity`). The server reads `encoding`.
|
|
100
|
+
- Interrupted uploads resume from a stored fingerprint on the next call with the same file and metadata.
|
|
101
|
+
- Aborting terminates the server-side upload.
|
package/dist/index.d.mts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { UploadOptions, PreviousUpload } from 'tus-js-client';
|
|
2
|
+
|
|
1
3
|
/** A header the BFF anti-forgery middleware requires on mutating requests. */
|
|
2
4
|
interface CsrfHeader {
|
|
3
5
|
name: string;
|
|
@@ -15,7 +17,7 @@ interface UploadedContent {
|
|
|
15
17
|
/** Receives whole-number upload progress, 0-100. */
|
|
16
18
|
type ProgressListener = (percent: number) => void;
|
|
17
19
|
/** Options for `uploadImage`. Only `endpoint` is required. */
|
|
18
|
-
interface UploadImageOptions {
|
|
20
|
+
interface UploadImageOptions extends TransportOptions {
|
|
19
21
|
/** Same-origin URL of the upload endpoint, e.g. `/bff/api/content/api/v1/content/upload`. */
|
|
20
22
|
endpoint: string;
|
|
21
23
|
/** Filename sent with the file part. Defaults to `File.name`, else `"upload"`. */
|
|
@@ -36,15 +38,19 @@ interface UploadImageOptions {
|
|
|
36
38
|
allowedTypes?: readonly string[];
|
|
37
39
|
/** Aborts the request; the promise rejects with `UploadErrorKind.Aborted`. */
|
|
38
40
|
signal?: AbortSignal;
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
|
|
41
|
+
}
|
|
42
|
+
/** Options for `uploadFormWithProgress`. Only `url` and `parts` are required. */
|
|
43
|
+
interface UploadFormOptions extends TransportOptions {
|
|
44
|
+
/** Upload endpoint, e.g. a same-origin BFF route. */
|
|
45
|
+
url: string;
|
|
46
|
+
/** Multipart fields in insertion order; a Blob/File becomes a file part. */
|
|
47
|
+
parts: Record<string, Blob | string>;
|
|
48
|
+
/** Extra headers (e.g. a CSRF header). Any Content-Type is dropped: the runtime sets the boundary. */
|
|
49
|
+
headers?: Record<string, string>;
|
|
50
|
+
/** Send cookies (BFF session). Default `true`. */
|
|
51
|
+
withCredentials?: boolean;
|
|
52
|
+
/** Aborts the request; the promise rejects with `UploadErrorKind.Aborted`. */
|
|
53
|
+
signal?: AbortSignal;
|
|
48
54
|
}
|
|
49
55
|
|
|
50
56
|
/** A fully built multipart upload, independent of the transport that sends it. */
|
|
@@ -56,6 +62,15 @@ interface UploadRequest {
|
|
|
56
62
|
credentials: RequestCredentials;
|
|
57
63
|
signal: AbortSignal | undefined;
|
|
58
64
|
}
|
|
65
|
+
/** Transport selection knobs shared by every upload entry point. */
|
|
66
|
+
interface TransportOptions {
|
|
67
|
+
/** When set (and no `fetchImpl`), the upload goes over XMLHttpRequest to report progress. */
|
|
68
|
+
onProgress?: ProgressListener;
|
|
69
|
+
/** Custom fetch (tests, SSR). Forces the fetch transport. */
|
|
70
|
+
fetchImpl?: typeof fetch;
|
|
71
|
+
/** Custom XMLHttpRequest factory (tests). */
|
|
72
|
+
xhrFactory?: () => XMLHttpRequest;
|
|
73
|
+
}
|
|
59
74
|
|
|
60
75
|
/** The Bff.AspNetCore anti-forgery convention: header presence, any value. */
|
|
61
76
|
declare const DEFAULT_CSRF_HEADER: CsrfHeader;
|
|
@@ -72,6 +87,16 @@ declare function buildUploadRequest(file: Blob, options: UploadImageOptions): Up
|
|
|
72
87
|
*/
|
|
73
88
|
declare function uploadImage(file: Blob, options: UploadImageOptions): Promise<UploadedContent>;
|
|
74
89
|
|
|
90
|
+
/** Build the transport-independent request. Exported so consumers can pin the write shape. */
|
|
91
|
+
declare function buildFormRequest(options: UploadFormOptions): UploadRequest;
|
|
92
|
+
/**
|
|
93
|
+
* POST arbitrary multipart parts with optional upload progress; resolves the parsed JSON body
|
|
94
|
+
* (`null` for an empty 2xx body).
|
|
95
|
+
*
|
|
96
|
+
* @throws {UploadError} `Http` carries `status` and the raw `body`; switch on `error.kind`.
|
|
97
|
+
*/
|
|
98
|
+
declare function uploadFormWithProgress<T = unknown>(options: UploadFormOptions): Promise<T | null>;
|
|
99
|
+
|
|
75
100
|
/**
|
|
76
101
|
* Read a local URI (`blob:`, `data:`, or an RN image-picker `file:` URI) into a Blob so it
|
|
77
102
|
* can be passed to `uploadImage`.
|
|
@@ -107,7 +132,9 @@ declare class UploadError extends Error {
|
|
|
107
132
|
readonly kind: UploadErrorKind;
|
|
108
133
|
/** HTTP status for `UploadErrorKind.Http`; undefined otherwise. */
|
|
109
134
|
readonly status: number | undefined;
|
|
110
|
-
|
|
135
|
+
/** Raw response body for `UploadErrorKind.Http`; undefined otherwise. */
|
|
136
|
+
readonly body: string | undefined;
|
|
137
|
+
constructor(kind: UploadErrorKind, message: string, status?: number, body?: string);
|
|
111
138
|
}
|
|
112
139
|
/** Normalise anything thrown into an `UploadError` (unknown throws become `Network`). */
|
|
113
140
|
declare function toUploadError(cause: unknown): UploadError;
|
|
@@ -138,4 +165,66 @@ interface UseImageUploadResult extends ImageUploadState {
|
|
|
138
165
|
*/
|
|
139
166
|
declare function useImageUpload(options: UseImageUploadOptions): UseImageUploadResult;
|
|
140
167
|
|
|
141
|
-
|
|
168
|
+
/** Value of the tus `encoding` metadata entry: how the uploaded bytes are encoded. */
|
|
169
|
+
declare const enum UploadEncoding {
|
|
170
|
+
Gzip = "gzip",
|
|
171
|
+
Identity = "identity"
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/** The read side of a byte stream, as tus-js-client accepts it. */
|
|
175
|
+
type ByteReader = Pick<ReadableStreamDefaultReader<Uint8Array>, 'read'>;
|
|
176
|
+
/** What tus-js-client uploads, and how its progress maps back to the source file. */
|
|
177
|
+
interface ResumableSource {
|
|
178
|
+
input: Blob | ByteReader;
|
|
179
|
+
encoding: UploadEncoding;
|
|
180
|
+
lengthDeferred: boolean;
|
|
181
|
+
sourceBytesRead: () => number;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/** The part of a tus-js-client `Upload` that `uploadResumable` drives. */
|
|
185
|
+
interface TusUpload {
|
|
186
|
+
url: string | null;
|
|
187
|
+
start(): void;
|
|
188
|
+
abort(shouldTerminate?: boolean): Promise<void>;
|
|
189
|
+
findPreviousUploads(): Promise<PreviousUpload[]>;
|
|
190
|
+
resumeFromPreviousUpload(previousUpload: PreviousUpload): void;
|
|
191
|
+
}
|
|
192
|
+
/** Builds the tus upload; injectable for tests. */
|
|
193
|
+
type TusUploadFactory = (input: Blob | ByteReader, options: UploadOptions) => TusUpload;
|
|
194
|
+
/** Options for `uploadResumable`. Only `endpoint` and `file` are required. */
|
|
195
|
+
interface ResumableUploadOptions {
|
|
196
|
+
/** tus creation endpoint, e.g. `/bff/api/aml/v1/ingest/uploads`. */
|
|
197
|
+
endpoint: string;
|
|
198
|
+
file: Blob;
|
|
199
|
+
/** tus Upload-Metadata; `filename` defaults to `File.name`; `encoding` is always set by the client. */
|
|
200
|
+
metadata?: Record<string, string>;
|
|
201
|
+
/** Gzip the file as a stream (deferred length); falls back to identity when `CompressionStream` is missing. */
|
|
202
|
+
gzip?: boolean;
|
|
203
|
+
/** PATCH body size in bytes. Default 16 MiB (under the BFF 30 MB body limit). */
|
|
204
|
+
chunkSize?: number;
|
|
205
|
+
/** Extra request headers, e.g. the BFF CSRF header. */
|
|
206
|
+
headers?: Record<string, string>;
|
|
207
|
+
/** Send cookies (BFF session). Default `true`. */
|
|
208
|
+
withCredentials?: boolean;
|
|
209
|
+
onProgress?: ProgressListener;
|
|
210
|
+
/** Aborts and terminates the upload; the promise rejects with `UploadErrorKind.Aborted`. */
|
|
211
|
+
signal?: AbortSignal;
|
|
212
|
+
uploadFactory?: TusUploadFactory;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
declare const DEFAULT_RESUMABLE_CHUNK_SIZE: number;
|
|
216
|
+
interface UploadCallbacks {
|
|
217
|
+
onProgress: (bytesSent: number, bytesTotal: number) => void;
|
|
218
|
+
onSuccess: () => void;
|
|
219
|
+
onError: (error: Error) => void;
|
|
220
|
+
}
|
|
221
|
+
/** Upload-Metadata sent on creation: `filename` from the File unless given, `encoding` always from the source. */
|
|
222
|
+
declare function buildResumableMetadata(file: Blob, metadata: Record<string, string> | undefined, encoding: UploadEncoding): Record<string, string>;
|
|
223
|
+
/** Stable resume key: the same endpoint, file identity and metadata resume the same tus upload. */
|
|
224
|
+
declare function resumeFingerprint(endpoint: string, file: Blob, metadata: Record<string, string>): string;
|
|
225
|
+
/** The tus-js-client options for one upload. Exported so consumers can pin the request shape. */
|
|
226
|
+
declare function buildTusOptions(options: ResumableUploadOptions, source: ResumableSource, callbacks: UploadCallbacks): UploadOptions;
|
|
227
|
+
/** tus 1.0 resumable upload (16 MiB chunks, optional streamed gzip, fingerprint resume); resolves the upload id. */
|
|
228
|
+
declare function uploadResumable(options: ResumableUploadOptions): Promise<string>;
|
|
229
|
+
|
|
230
|
+
export { type ByteReader, type CsrfHeader, DEFAULT_CSRF_HEADER, DEFAULT_RESUMABLE_CHUNK_SIZE, type ImageUploadState, type ProgressListener, type ResumableUploadOptions, type TransportOptions, type TusUpload, type TusUploadFactory, UploadEncoding, UploadError, UploadErrorKind, type UploadFormOptions, type UploadImageOptions, type UploadRequest, UploadStatus, type UploadedContent, type UseImageUploadOptions, type UseImageUploadResult, blobFromUri, buildFormRequest, buildResumableMetadata, buildTusOptions, buildUploadRequest, resumeFingerprint, toUploadError, uploadFormWithProgress, uploadImage, uploadResumable, useImageUpload };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { UploadOptions, PreviousUpload } from 'tus-js-client';
|
|
2
|
+
|
|
1
3
|
/** A header the BFF anti-forgery middleware requires on mutating requests. */
|
|
2
4
|
interface CsrfHeader {
|
|
3
5
|
name: string;
|
|
@@ -15,7 +17,7 @@ interface UploadedContent {
|
|
|
15
17
|
/** Receives whole-number upload progress, 0-100. */
|
|
16
18
|
type ProgressListener = (percent: number) => void;
|
|
17
19
|
/** Options for `uploadImage`. Only `endpoint` is required. */
|
|
18
|
-
interface UploadImageOptions {
|
|
20
|
+
interface UploadImageOptions extends TransportOptions {
|
|
19
21
|
/** Same-origin URL of the upload endpoint, e.g. `/bff/api/content/api/v1/content/upload`. */
|
|
20
22
|
endpoint: string;
|
|
21
23
|
/** Filename sent with the file part. Defaults to `File.name`, else `"upload"`. */
|
|
@@ -36,15 +38,19 @@ interface UploadImageOptions {
|
|
|
36
38
|
allowedTypes?: readonly string[];
|
|
37
39
|
/** Aborts the request; the promise rejects with `UploadErrorKind.Aborted`. */
|
|
38
40
|
signal?: AbortSignal;
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
|
|
41
|
+
}
|
|
42
|
+
/** Options for `uploadFormWithProgress`. Only `url` and `parts` are required. */
|
|
43
|
+
interface UploadFormOptions extends TransportOptions {
|
|
44
|
+
/** Upload endpoint, e.g. a same-origin BFF route. */
|
|
45
|
+
url: string;
|
|
46
|
+
/** Multipart fields in insertion order; a Blob/File becomes a file part. */
|
|
47
|
+
parts: Record<string, Blob | string>;
|
|
48
|
+
/** Extra headers (e.g. a CSRF header). Any Content-Type is dropped: the runtime sets the boundary. */
|
|
49
|
+
headers?: Record<string, string>;
|
|
50
|
+
/** Send cookies (BFF session). Default `true`. */
|
|
51
|
+
withCredentials?: boolean;
|
|
52
|
+
/** Aborts the request; the promise rejects with `UploadErrorKind.Aborted`. */
|
|
53
|
+
signal?: AbortSignal;
|
|
48
54
|
}
|
|
49
55
|
|
|
50
56
|
/** A fully built multipart upload, independent of the transport that sends it. */
|
|
@@ -56,6 +62,15 @@ interface UploadRequest {
|
|
|
56
62
|
credentials: RequestCredentials;
|
|
57
63
|
signal: AbortSignal | undefined;
|
|
58
64
|
}
|
|
65
|
+
/** Transport selection knobs shared by every upload entry point. */
|
|
66
|
+
interface TransportOptions {
|
|
67
|
+
/** When set (and no `fetchImpl`), the upload goes over XMLHttpRequest to report progress. */
|
|
68
|
+
onProgress?: ProgressListener;
|
|
69
|
+
/** Custom fetch (tests, SSR). Forces the fetch transport. */
|
|
70
|
+
fetchImpl?: typeof fetch;
|
|
71
|
+
/** Custom XMLHttpRequest factory (tests). */
|
|
72
|
+
xhrFactory?: () => XMLHttpRequest;
|
|
73
|
+
}
|
|
59
74
|
|
|
60
75
|
/** The Bff.AspNetCore anti-forgery convention: header presence, any value. */
|
|
61
76
|
declare const DEFAULT_CSRF_HEADER: CsrfHeader;
|
|
@@ -72,6 +87,16 @@ declare function buildUploadRequest(file: Blob, options: UploadImageOptions): Up
|
|
|
72
87
|
*/
|
|
73
88
|
declare function uploadImage(file: Blob, options: UploadImageOptions): Promise<UploadedContent>;
|
|
74
89
|
|
|
90
|
+
/** Build the transport-independent request. Exported so consumers can pin the write shape. */
|
|
91
|
+
declare function buildFormRequest(options: UploadFormOptions): UploadRequest;
|
|
92
|
+
/**
|
|
93
|
+
* POST arbitrary multipart parts with optional upload progress; resolves the parsed JSON body
|
|
94
|
+
* (`null` for an empty 2xx body).
|
|
95
|
+
*
|
|
96
|
+
* @throws {UploadError} `Http` carries `status` and the raw `body`; switch on `error.kind`.
|
|
97
|
+
*/
|
|
98
|
+
declare function uploadFormWithProgress<T = unknown>(options: UploadFormOptions): Promise<T | null>;
|
|
99
|
+
|
|
75
100
|
/**
|
|
76
101
|
* Read a local URI (`blob:`, `data:`, or an RN image-picker `file:` URI) into a Blob so it
|
|
77
102
|
* can be passed to `uploadImage`.
|
|
@@ -107,7 +132,9 @@ declare class UploadError extends Error {
|
|
|
107
132
|
readonly kind: UploadErrorKind;
|
|
108
133
|
/** HTTP status for `UploadErrorKind.Http`; undefined otherwise. */
|
|
109
134
|
readonly status: number | undefined;
|
|
110
|
-
|
|
135
|
+
/** Raw response body for `UploadErrorKind.Http`; undefined otherwise. */
|
|
136
|
+
readonly body: string | undefined;
|
|
137
|
+
constructor(kind: UploadErrorKind, message: string, status?: number, body?: string);
|
|
111
138
|
}
|
|
112
139
|
/** Normalise anything thrown into an `UploadError` (unknown throws become `Network`). */
|
|
113
140
|
declare function toUploadError(cause: unknown): UploadError;
|
|
@@ -138,4 +165,66 @@ interface UseImageUploadResult extends ImageUploadState {
|
|
|
138
165
|
*/
|
|
139
166
|
declare function useImageUpload(options: UseImageUploadOptions): UseImageUploadResult;
|
|
140
167
|
|
|
141
|
-
|
|
168
|
+
/** Value of the tus `encoding` metadata entry: how the uploaded bytes are encoded. */
|
|
169
|
+
declare const enum UploadEncoding {
|
|
170
|
+
Gzip = "gzip",
|
|
171
|
+
Identity = "identity"
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/** The read side of a byte stream, as tus-js-client accepts it. */
|
|
175
|
+
type ByteReader = Pick<ReadableStreamDefaultReader<Uint8Array>, 'read'>;
|
|
176
|
+
/** What tus-js-client uploads, and how its progress maps back to the source file. */
|
|
177
|
+
interface ResumableSource {
|
|
178
|
+
input: Blob | ByteReader;
|
|
179
|
+
encoding: UploadEncoding;
|
|
180
|
+
lengthDeferred: boolean;
|
|
181
|
+
sourceBytesRead: () => number;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/** The part of a tus-js-client `Upload` that `uploadResumable` drives. */
|
|
185
|
+
interface TusUpload {
|
|
186
|
+
url: string | null;
|
|
187
|
+
start(): void;
|
|
188
|
+
abort(shouldTerminate?: boolean): Promise<void>;
|
|
189
|
+
findPreviousUploads(): Promise<PreviousUpload[]>;
|
|
190
|
+
resumeFromPreviousUpload(previousUpload: PreviousUpload): void;
|
|
191
|
+
}
|
|
192
|
+
/** Builds the tus upload; injectable for tests. */
|
|
193
|
+
type TusUploadFactory = (input: Blob | ByteReader, options: UploadOptions) => TusUpload;
|
|
194
|
+
/** Options for `uploadResumable`. Only `endpoint` and `file` are required. */
|
|
195
|
+
interface ResumableUploadOptions {
|
|
196
|
+
/** tus creation endpoint, e.g. `/bff/api/aml/v1/ingest/uploads`. */
|
|
197
|
+
endpoint: string;
|
|
198
|
+
file: Blob;
|
|
199
|
+
/** tus Upload-Metadata; `filename` defaults to `File.name`; `encoding` is always set by the client. */
|
|
200
|
+
metadata?: Record<string, string>;
|
|
201
|
+
/** Gzip the file as a stream (deferred length); falls back to identity when `CompressionStream` is missing. */
|
|
202
|
+
gzip?: boolean;
|
|
203
|
+
/** PATCH body size in bytes. Default 16 MiB (under the BFF 30 MB body limit). */
|
|
204
|
+
chunkSize?: number;
|
|
205
|
+
/** Extra request headers, e.g. the BFF CSRF header. */
|
|
206
|
+
headers?: Record<string, string>;
|
|
207
|
+
/** Send cookies (BFF session). Default `true`. */
|
|
208
|
+
withCredentials?: boolean;
|
|
209
|
+
onProgress?: ProgressListener;
|
|
210
|
+
/** Aborts and terminates the upload; the promise rejects with `UploadErrorKind.Aborted`. */
|
|
211
|
+
signal?: AbortSignal;
|
|
212
|
+
uploadFactory?: TusUploadFactory;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
declare const DEFAULT_RESUMABLE_CHUNK_SIZE: number;
|
|
216
|
+
interface UploadCallbacks {
|
|
217
|
+
onProgress: (bytesSent: number, bytesTotal: number) => void;
|
|
218
|
+
onSuccess: () => void;
|
|
219
|
+
onError: (error: Error) => void;
|
|
220
|
+
}
|
|
221
|
+
/** Upload-Metadata sent on creation: `filename` from the File unless given, `encoding` always from the source. */
|
|
222
|
+
declare function buildResumableMetadata(file: Blob, metadata: Record<string, string> | undefined, encoding: UploadEncoding): Record<string, string>;
|
|
223
|
+
/** Stable resume key: the same endpoint, file identity and metadata resume the same tus upload. */
|
|
224
|
+
declare function resumeFingerprint(endpoint: string, file: Blob, metadata: Record<string, string>): string;
|
|
225
|
+
/** The tus-js-client options for one upload. Exported so consumers can pin the request shape. */
|
|
226
|
+
declare function buildTusOptions(options: ResumableUploadOptions, source: ResumableSource, callbacks: UploadCallbacks): UploadOptions;
|
|
227
|
+
/** tus 1.0 resumable upload (16 MiB chunks, optional streamed gzip, fingerprint resume); resolves the upload id. */
|
|
228
|
+
declare function uploadResumable(options: ResumableUploadOptions): Promise<string>;
|
|
229
|
+
|
|
230
|
+
export { type ByteReader, type CsrfHeader, DEFAULT_CSRF_HEADER, DEFAULT_RESUMABLE_CHUNK_SIZE, type ImageUploadState, type ProgressListener, type ResumableUploadOptions, type TransportOptions, type TusUpload, type TusUploadFactory, UploadEncoding, UploadError, UploadErrorKind, type UploadFormOptions, type UploadImageOptions, type UploadRequest, UploadStatus, type UploadedContent, type UseImageUploadOptions, type UseImageUploadResult, blobFromUri, buildFormRequest, buildResumableMetadata, buildTusOptions, buildUploadRequest, resumeFingerprint, toUploadError, uploadFormWithProgress, uploadImage, uploadResumable, useImageUpload };
|