@dloizides/content-upload 1.0.1 → 1.1.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 +11 -0
- package/README.md +81 -63
- package/dist/index.d.mts +37 -12
- package/dist/index.d.ts +37 -12
- package/dist/index.js +79 -24
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +78 -25
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.1.0
|
|
4
|
+
|
|
5
|
+
RUPEP-5 "Upload the RuPEP file from the Jobs tab".
|
|
6
|
+
|
|
7
|
+
- `uploadFormWithProgress({ url, parts, headers?, onProgress?, signal?, withCredentials?, fetchImpl?, xhrFactory? })`:
|
|
8
|
+
generic multipart POST with progress; resolves parsed JSON (`null` for an empty 2xx body).
|
|
9
|
+
Credentials default to `include`; any Content-Type header is dropped; CSRF goes in `headers`.
|
|
10
|
+
- `buildFormRequest(options)` to pin the write shape.
|
|
11
|
+
- `UploadError.body` carries the raw response body of an `Http` failure (both transports).
|
|
12
|
+
- `uploadImage` now shares the transport selection (`sendUploadRequest`); behaviour unchanged.
|
|
13
|
+
|
|
3
14
|
## 1.0.0
|
|
4
15
|
|
|
5
16
|
Initial release (KEFI-PEOPLE-1 "Organizer people editor", task T3).
|
package/README.md
CHANGED
|
@@ -1,63 +1,81 @@
|
|
|
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>`.
|
package/dist/index.d.mts
CHANGED
|
@@ -15,7 +15,7 @@ interface UploadedContent {
|
|
|
15
15
|
/** Receives whole-number upload progress, 0-100. */
|
|
16
16
|
type ProgressListener = (percent: number) => void;
|
|
17
17
|
/** Options for `uploadImage`. Only `endpoint` is required. */
|
|
18
|
-
interface UploadImageOptions {
|
|
18
|
+
interface UploadImageOptions extends TransportOptions {
|
|
19
19
|
/** Same-origin URL of the upload endpoint, e.g. `/bff/api/content/api/v1/content/upload`. */
|
|
20
20
|
endpoint: string;
|
|
21
21
|
/** Filename sent with the file part. Defaults to `File.name`, else `"upload"`. */
|
|
@@ -36,15 +36,19 @@ interface UploadImageOptions {
|
|
|
36
36
|
allowedTypes?: readonly string[];
|
|
37
37
|
/** Aborts the request; the promise rejects with `UploadErrorKind.Aborted`. */
|
|
38
38
|
signal?: AbortSignal;
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
|
|
39
|
+
}
|
|
40
|
+
/** Options for `uploadFormWithProgress`. Only `url` and `parts` are required. */
|
|
41
|
+
interface UploadFormOptions extends TransportOptions {
|
|
42
|
+
/** Upload endpoint, e.g. a same-origin BFF route. */
|
|
43
|
+
url: string;
|
|
44
|
+
/** Multipart fields in insertion order; a Blob/File becomes a file part. */
|
|
45
|
+
parts: Record<string, Blob | string>;
|
|
46
|
+
/** Extra headers (e.g. a CSRF header). Any Content-Type is dropped: the runtime sets the boundary. */
|
|
47
|
+
headers?: Record<string, string>;
|
|
48
|
+
/** Send cookies (BFF session). Default `true`. */
|
|
49
|
+
withCredentials?: boolean;
|
|
50
|
+
/** Aborts the request; the promise rejects with `UploadErrorKind.Aborted`. */
|
|
51
|
+
signal?: AbortSignal;
|
|
48
52
|
}
|
|
49
53
|
|
|
50
54
|
/** A fully built multipart upload, independent of the transport that sends it. */
|
|
@@ -56,6 +60,15 @@ interface UploadRequest {
|
|
|
56
60
|
credentials: RequestCredentials;
|
|
57
61
|
signal: AbortSignal | undefined;
|
|
58
62
|
}
|
|
63
|
+
/** Transport selection knobs shared by every upload entry point. */
|
|
64
|
+
interface TransportOptions {
|
|
65
|
+
/** When set (and no `fetchImpl`), the upload goes over XMLHttpRequest to report progress. */
|
|
66
|
+
onProgress?: ProgressListener;
|
|
67
|
+
/** Custom fetch (tests, SSR). Forces the fetch transport. */
|
|
68
|
+
fetchImpl?: typeof fetch;
|
|
69
|
+
/** Custom XMLHttpRequest factory (tests). */
|
|
70
|
+
xhrFactory?: () => XMLHttpRequest;
|
|
71
|
+
}
|
|
59
72
|
|
|
60
73
|
/** The Bff.AspNetCore anti-forgery convention: header presence, any value. */
|
|
61
74
|
declare const DEFAULT_CSRF_HEADER: CsrfHeader;
|
|
@@ -72,6 +85,16 @@ declare function buildUploadRequest(file: Blob, options: UploadImageOptions): Up
|
|
|
72
85
|
*/
|
|
73
86
|
declare function uploadImage(file: Blob, options: UploadImageOptions): Promise<UploadedContent>;
|
|
74
87
|
|
|
88
|
+
/** Build the transport-independent request. Exported so consumers can pin the write shape. */
|
|
89
|
+
declare function buildFormRequest(options: UploadFormOptions): UploadRequest;
|
|
90
|
+
/**
|
|
91
|
+
* POST arbitrary multipart parts with optional upload progress; resolves the parsed JSON body
|
|
92
|
+
* (`null` for an empty 2xx body).
|
|
93
|
+
*
|
|
94
|
+
* @throws {UploadError} `Http` carries `status` and the raw `body`; switch on `error.kind`.
|
|
95
|
+
*/
|
|
96
|
+
declare function uploadFormWithProgress<T = unknown>(options: UploadFormOptions): Promise<T | null>;
|
|
97
|
+
|
|
75
98
|
/**
|
|
76
99
|
* Read a local URI (`blob:`, `data:`, or an RN image-picker `file:` URI) into a Blob so it
|
|
77
100
|
* can be passed to `uploadImage`.
|
|
@@ -107,7 +130,9 @@ declare class UploadError extends Error {
|
|
|
107
130
|
readonly kind: UploadErrorKind;
|
|
108
131
|
/** HTTP status for `UploadErrorKind.Http`; undefined otherwise. */
|
|
109
132
|
readonly status: number | undefined;
|
|
110
|
-
|
|
133
|
+
/** Raw response body for `UploadErrorKind.Http`; undefined otherwise. */
|
|
134
|
+
readonly body: string | undefined;
|
|
135
|
+
constructor(kind: UploadErrorKind, message: string, status?: number, body?: string);
|
|
111
136
|
}
|
|
112
137
|
/** Normalise anything thrown into an `UploadError` (unknown throws become `Network`). */
|
|
113
138
|
declare function toUploadError(cause: unknown): UploadError;
|
|
@@ -138,4 +163,4 @@ interface UseImageUploadResult extends ImageUploadState {
|
|
|
138
163
|
*/
|
|
139
164
|
declare function useImageUpload(options: UseImageUploadOptions): UseImageUploadResult;
|
|
140
165
|
|
|
141
|
-
export { type CsrfHeader, DEFAULT_CSRF_HEADER, type ImageUploadState, type ProgressListener, UploadError, UploadErrorKind, type UploadImageOptions, type UploadRequest, UploadStatus, type UploadedContent, type UseImageUploadOptions, type UseImageUploadResult, blobFromUri, buildUploadRequest, toUploadError, uploadImage, useImageUpload };
|
|
166
|
+
export { type CsrfHeader, DEFAULT_CSRF_HEADER, type ImageUploadState, type ProgressListener, type TransportOptions, UploadError, UploadErrorKind, type UploadFormOptions, type UploadImageOptions, type UploadRequest, UploadStatus, type UploadedContent, type UseImageUploadOptions, type UseImageUploadResult, blobFromUri, buildFormRequest, buildUploadRequest, toUploadError, uploadFormWithProgress, uploadImage, useImageUpload };
|
package/dist/index.d.ts
CHANGED
|
@@ -15,7 +15,7 @@ interface UploadedContent {
|
|
|
15
15
|
/** Receives whole-number upload progress, 0-100. */
|
|
16
16
|
type ProgressListener = (percent: number) => void;
|
|
17
17
|
/** Options for `uploadImage`. Only `endpoint` is required. */
|
|
18
|
-
interface UploadImageOptions {
|
|
18
|
+
interface UploadImageOptions extends TransportOptions {
|
|
19
19
|
/** Same-origin URL of the upload endpoint, e.g. `/bff/api/content/api/v1/content/upload`. */
|
|
20
20
|
endpoint: string;
|
|
21
21
|
/** Filename sent with the file part. Defaults to `File.name`, else `"upload"`. */
|
|
@@ -36,15 +36,19 @@ interface UploadImageOptions {
|
|
|
36
36
|
allowedTypes?: readonly string[];
|
|
37
37
|
/** Aborts the request; the promise rejects with `UploadErrorKind.Aborted`. */
|
|
38
38
|
signal?: AbortSignal;
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
|
|
39
|
+
}
|
|
40
|
+
/** Options for `uploadFormWithProgress`. Only `url` and `parts` are required. */
|
|
41
|
+
interface UploadFormOptions extends TransportOptions {
|
|
42
|
+
/** Upload endpoint, e.g. a same-origin BFF route. */
|
|
43
|
+
url: string;
|
|
44
|
+
/** Multipart fields in insertion order; a Blob/File becomes a file part. */
|
|
45
|
+
parts: Record<string, Blob | string>;
|
|
46
|
+
/** Extra headers (e.g. a CSRF header). Any Content-Type is dropped: the runtime sets the boundary. */
|
|
47
|
+
headers?: Record<string, string>;
|
|
48
|
+
/** Send cookies (BFF session). Default `true`. */
|
|
49
|
+
withCredentials?: boolean;
|
|
50
|
+
/** Aborts the request; the promise rejects with `UploadErrorKind.Aborted`. */
|
|
51
|
+
signal?: AbortSignal;
|
|
48
52
|
}
|
|
49
53
|
|
|
50
54
|
/** A fully built multipart upload, independent of the transport that sends it. */
|
|
@@ -56,6 +60,15 @@ interface UploadRequest {
|
|
|
56
60
|
credentials: RequestCredentials;
|
|
57
61
|
signal: AbortSignal | undefined;
|
|
58
62
|
}
|
|
63
|
+
/** Transport selection knobs shared by every upload entry point. */
|
|
64
|
+
interface TransportOptions {
|
|
65
|
+
/** When set (and no `fetchImpl`), the upload goes over XMLHttpRequest to report progress. */
|
|
66
|
+
onProgress?: ProgressListener;
|
|
67
|
+
/** Custom fetch (tests, SSR). Forces the fetch transport. */
|
|
68
|
+
fetchImpl?: typeof fetch;
|
|
69
|
+
/** Custom XMLHttpRequest factory (tests). */
|
|
70
|
+
xhrFactory?: () => XMLHttpRequest;
|
|
71
|
+
}
|
|
59
72
|
|
|
60
73
|
/** The Bff.AspNetCore anti-forgery convention: header presence, any value. */
|
|
61
74
|
declare const DEFAULT_CSRF_HEADER: CsrfHeader;
|
|
@@ -72,6 +85,16 @@ declare function buildUploadRequest(file: Blob, options: UploadImageOptions): Up
|
|
|
72
85
|
*/
|
|
73
86
|
declare function uploadImage(file: Blob, options: UploadImageOptions): Promise<UploadedContent>;
|
|
74
87
|
|
|
88
|
+
/** Build the transport-independent request. Exported so consumers can pin the write shape. */
|
|
89
|
+
declare function buildFormRequest(options: UploadFormOptions): UploadRequest;
|
|
90
|
+
/**
|
|
91
|
+
* POST arbitrary multipart parts with optional upload progress; resolves the parsed JSON body
|
|
92
|
+
* (`null` for an empty 2xx body).
|
|
93
|
+
*
|
|
94
|
+
* @throws {UploadError} `Http` carries `status` and the raw `body`; switch on `error.kind`.
|
|
95
|
+
*/
|
|
96
|
+
declare function uploadFormWithProgress<T = unknown>(options: UploadFormOptions): Promise<T | null>;
|
|
97
|
+
|
|
75
98
|
/**
|
|
76
99
|
* Read a local URI (`blob:`, `data:`, or an RN image-picker `file:` URI) into a Blob so it
|
|
77
100
|
* can be passed to `uploadImage`.
|
|
@@ -107,7 +130,9 @@ declare class UploadError extends Error {
|
|
|
107
130
|
readonly kind: UploadErrorKind;
|
|
108
131
|
/** HTTP status for `UploadErrorKind.Http`; undefined otherwise. */
|
|
109
132
|
readonly status: number | undefined;
|
|
110
|
-
|
|
133
|
+
/** Raw response body for `UploadErrorKind.Http`; undefined otherwise. */
|
|
134
|
+
readonly body: string | undefined;
|
|
135
|
+
constructor(kind: UploadErrorKind, message: string, status?: number, body?: string);
|
|
111
136
|
}
|
|
112
137
|
/** Normalise anything thrown into an `UploadError` (unknown throws become `Network`). */
|
|
113
138
|
declare function toUploadError(cause: unknown): UploadError;
|
|
@@ -138,4 +163,4 @@ interface UseImageUploadResult extends ImageUploadState {
|
|
|
138
163
|
*/
|
|
139
164
|
declare function useImageUpload(options: UseImageUploadOptions): UseImageUploadResult;
|
|
140
165
|
|
|
141
|
-
export { type CsrfHeader, DEFAULT_CSRF_HEADER, type ImageUploadState, type ProgressListener, UploadError, UploadErrorKind, type UploadImageOptions, type UploadRequest, UploadStatus, type UploadedContent, type UseImageUploadOptions, type UseImageUploadResult, blobFromUri, buildUploadRequest, toUploadError, uploadImage, useImageUpload };
|
|
166
|
+
export { type CsrfHeader, DEFAULT_CSRF_HEADER, type ImageUploadState, type ProgressListener, type TransportOptions, UploadError, UploadErrorKind, type UploadFormOptions, type UploadImageOptions, type UploadRequest, UploadStatus, type UploadedContent, type UseImageUploadOptions, type UseImageUploadResult, blobFromUri, buildFormRequest, buildUploadRequest, toUploadError, uploadFormWithProgress, uploadImage, useImageUpload };
|
package/dist/index.js
CHANGED
|
@@ -14,11 +14,12 @@ var UploadErrorKind = /* @__PURE__ */ ((UploadErrorKind2) => {
|
|
|
14
14
|
|
|
15
15
|
// src/UploadError.ts
|
|
16
16
|
var UploadError = class extends Error {
|
|
17
|
-
constructor(kind, message, status) {
|
|
17
|
+
constructor(kind, message, status, body) {
|
|
18
18
|
super(message);
|
|
19
19
|
this.name = "UploadError";
|
|
20
20
|
this.kind = kind;
|
|
21
21
|
this.status = status;
|
|
22
|
+
this.body = body;
|
|
22
23
|
}
|
|
23
24
|
};
|
|
24
25
|
function toUploadError(cause) {
|
|
@@ -62,8 +63,15 @@ var PERCENT_COMPLETE = 100;
|
|
|
62
63
|
function isOk(status) {
|
|
63
64
|
return status >= HTTP_OK_MIN && status < HTTP_OK_MAX_EXCLUSIVE;
|
|
64
65
|
}
|
|
65
|
-
function httpError(status) {
|
|
66
|
-
return new UploadError("http" /* Http */, `Upload failed (${String(status)}).`, status);
|
|
66
|
+
function httpError(status, body) {
|
|
67
|
+
return new UploadError("http" /* Http */, `Upload failed (${String(status)}).`, status, body);
|
|
68
|
+
}
|
|
69
|
+
async function readBodySafely(response) {
|
|
70
|
+
try {
|
|
71
|
+
return await response.text();
|
|
72
|
+
} catch {
|
|
73
|
+
return "";
|
|
74
|
+
}
|
|
67
75
|
}
|
|
68
76
|
function abortedError() {
|
|
69
77
|
return new UploadError("aborted" /* Aborted */, "Upload was cancelled.");
|
|
@@ -86,7 +94,7 @@ async function sendWithFetch(request, fetchImpl) {
|
|
|
86
94
|
throw new UploadError("network" /* Network */, detail);
|
|
87
95
|
}
|
|
88
96
|
if (!isOk(response.status)) {
|
|
89
|
-
throw httpError(response.status);
|
|
97
|
+
throw httpError(response.status, await readBodySafely(response));
|
|
90
98
|
}
|
|
91
99
|
return response.text();
|
|
92
100
|
}
|
|
@@ -108,7 +116,7 @@ function sendWithXhr(request, createXhr, onProgress) {
|
|
|
108
116
|
if (isOk(xhr.status)) {
|
|
109
117
|
resolve(xhr.responseText);
|
|
110
118
|
} else {
|
|
111
|
-
reject(httpError(xhr.status));
|
|
119
|
+
reject(httpError(xhr.status, xhr.responseText));
|
|
112
120
|
}
|
|
113
121
|
});
|
|
114
122
|
xhr.addEventListener("error", () => reject(new UploadError("network" /* Network */, "Upload failed (network error).")));
|
|
@@ -120,6 +128,24 @@ function sendWithXhr(request, createXhr, onProgress) {
|
|
|
120
128
|
xhr.send(request.form);
|
|
121
129
|
});
|
|
122
130
|
}
|
|
131
|
+
function resolveXhrFactory(options) {
|
|
132
|
+
if (options.fetchImpl !== void 0) {
|
|
133
|
+
return void 0;
|
|
134
|
+
}
|
|
135
|
+
if (options.xhrFactory !== void 0) {
|
|
136
|
+
return options.xhrFactory;
|
|
137
|
+
}
|
|
138
|
+
return typeof XMLHttpRequest === "undefined" ? void 0 : () => new XMLHttpRequest();
|
|
139
|
+
}
|
|
140
|
+
async function sendUploadRequest(request, options) {
|
|
141
|
+
const { onProgress } = options;
|
|
142
|
+
const createXhr = onProgress === void 0 ? void 0 : resolveXhrFactory(options);
|
|
143
|
+
if (createXhr !== void 0 && onProgress !== void 0) {
|
|
144
|
+
return sendWithXhr(request, createXhr, onProgress);
|
|
145
|
+
}
|
|
146
|
+
const fetchImpl = options.fetchImpl ?? globalThis.fetch.bind(globalThis);
|
|
147
|
+
return sendWithFetch(request, fetchImpl);
|
|
148
|
+
}
|
|
123
149
|
|
|
124
150
|
// src/uploadImage.ts
|
|
125
151
|
var DEFAULT_CSRF_HEADER = { name: "X-BFF-Csrf", value: "1" };
|
|
@@ -172,31 +198,58 @@ function buildUploadRequest(file, options) {
|
|
|
172
198
|
signal: options.signal
|
|
173
199
|
};
|
|
174
200
|
}
|
|
175
|
-
function resolveXhrFactory(options) {
|
|
176
|
-
if (options.fetchImpl !== void 0) {
|
|
177
|
-
return void 0;
|
|
178
|
-
}
|
|
179
|
-
if (options.xhrFactory !== void 0) {
|
|
180
|
-
return options.xhrFactory;
|
|
181
|
-
}
|
|
182
|
-
return typeof XMLHttpRequest === "undefined" ? void 0 : () => new XMLHttpRequest();
|
|
183
|
-
}
|
|
184
|
-
async function send(request, options) {
|
|
185
|
-
const { onProgress } = options;
|
|
186
|
-
const createXhr = onProgress === void 0 ? void 0 : resolveXhrFactory(options);
|
|
187
|
-
if (createXhr !== void 0 && onProgress !== void 0) {
|
|
188
|
-
return sendWithXhr(request, createXhr, onProgress);
|
|
189
|
-
}
|
|
190
|
-
const fetchImpl = options.fetchImpl ?? globalThis.fetch.bind(globalThis);
|
|
191
|
-
return sendWithFetch(request, fetchImpl);
|
|
192
|
-
}
|
|
193
201
|
async function uploadImage(file, options) {
|
|
194
202
|
validate(file, options);
|
|
195
203
|
const request = buildUploadRequest(file, options);
|
|
196
|
-
const text = await
|
|
204
|
+
const text = await sendUploadRequest(request, options);
|
|
197
205
|
return parseUploadedContent(text, options.requireUrl ?? options.isPublic ?? true);
|
|
198
206
|
}
|
|
199
207
|
|
|
208
|
+
// src/uploadFormWithProgress.ts
|
|
209
|
+
var CONTENT_TYPE_HEADER = "content-type";
|
|
210
|
+
var ACCEPT_HEADER = "accept";
|
|
211
|
+
function buildHeaders2(extra) {
|
|
212
|
+
const headers = {};
|
|
213
|
+
Object.entries(extra ?? {}).forEach(([name, value]) => {
|
|
214
|
+
if (name.toLowerCase() !== CONTENT_TYPE_HEADER) {
|
|
215
|
+
headers[name] = value;
|
|
216
|
+
}
|
|
217
|
+
});
|
|
218
|
+
const hasAccept = Object.keys(headers).some((name) => name.toLowerCase() === ACCEPT_HEADER);
|
|
219
|
+
if (!hasAccept) {
|
|
220
|
+
headers.Accept = "application/json";
|
|
221
|
+
}
|
|
222
|
+
return headers;
|
|
223
|
+
}
|
|
224
|
+
function buildForm(parts) {
|
|
225
|
+
const form = new FormData();
|
|
226
|
+
Object.entries(parts).forEach(([name, value]) => form.append(name, value));
|
|
227
|
+
return form;
|
|
228
|
+
}
|
|
229
|
+
function buildFormRequest(options) {
|
|
230
|
+
return {
|
|
231
|
+
url: options.url,
|
|
232
|
+
headers: buildHeaders2(options.headers),
|
|
233
|
+
form: buildForm(options.parts),
|
|
234
|
+
credentials: options.withCredentials === false ? "same-origin" : "include",
|
|
235
|
+
signal: options.signal
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
function parseJson(text) {
|
|
239
|
+
if (text.trim() === "") {
|
|
240
|
+
return null;
|
|
241
|
+
}
|
|
242
|
+
try {
|
|
243
|
+
return JSON.parse(text);
|
|
244
|
+
} catch {
|
|
245
|
+
throw new UploadError("invalid-response" /* InvalidResponse */, "Upload response was not valid JSON.");
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
async function uploadFormWithProgress(options) {
|
|
249
|
+
const text = await sendUploadRequest(buildFormRequest(options), options);
|
|
250
|
+
return parseJson(text);
|
|
251
|
+
}
|
|
252
|
+
|
|
200
253
|
// src/blobFromUri.ts
|
|
201
254
|
async function blobFromUri(uri, fetchImpl) {
|
|
202
255
|
const doFetch = fetchImpl ?? globalThis.fetch.bind(globalThis);
|
|
@@ -298,8 +351,10 @@ exports.UploadError = UploadError;
|
|
|
298
351
|
exports.UploadErrorKind = UploadErrorKind;
|
|
299
352
|
exports.UploadStatus = UploadStatus;
|
|
300
353
|
exports.blobFromUri = blobFromUri;
|
|
354
|
+
exports.buildFormRequest = buildFormRequest;
|
|
301
355
|
exports.buildUploadRequest = buildUploadRequest;
|
|
302
356
|
exports.toUploadError = toUploadError;
|
|
357
|
+
exports.uploadFormWithProgress = uploadFormWithProgress;
|
|
303
358
|
exports.uploadImage = uploadImage;
|
|
304
359
|
exports.useImageUpload = useImageUpload;
|
|
305
360
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/UploadErrorKind.ts","../src/UploadError.ts","../src/parseUploadedContent.ts","../src/transports.ts","../src/uploadImage.ts","../src/blobFromUri.ts","../src/UploadStatus.ts","../src/useImageUpload.ts"],"names":["UploadErrorKind","UploadStatus","useReducer","useRef","useCallback","useEffect"],"mappings":"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","file":"index.js","sourcesContent":["/** Why an upload failed. Consumers map this to translated copy; `message` is developer-facing only. */\nexport const enum UploadErrorKind {\n /** Rejected client-side before any request: empty file, too large, disallowed MIME type. */\n Validation = 'validation',\n /** The request never produced an HTTP response (offline, DNS, CORS). */\n Network = 'network',\n /** The server answered outside 2xx; `status` carries the code. */\n Http = 'http',\n /** The caller aborted (AbortSignal / hook reset). */\n Aborted = 'aborted',\n /** 2xx, but the body was not `{ contentId, status?, url? }` (or lacked a required url). */\n InvalidResponse = 'invalid-response',\n}\n","import { UploadErrorKind } from './UploadErrorKind';\n\n/** The single error type thrown by `uploadImage` and surfaced by `useImageUpload`. */\nexport class UploadError extends Error {\n readonly kind: UploadErrorKind;\n /** HTTP status for `UploadErrorKind.Http`; undefined otherwise. */\n readonly status: number | undefined;\n\n constructor(kind: UploadErrorKind, message: string, status?: number) {\n super(message);\n this.name = 'UploadError';\n this.kind = kind;\n this.status = status;\n }\n}\n\n/** Normalise anything thrown into an `UploadError` (unknown throws become `Network`). */\nexport function toUploadError(cause: unknown): UploadError {\n if (cause instanceof UploadError) {\n return cause;\n }\n const message = cause instanceof Error ? cause.message : String(cause);\n return new UploadError(UploadErrorKind.Network, message);\n}\n","import { UploadError } from './UploadError';\nimport { UploadErrorKind } from './UploadErrorKind';\n\nimport type { UploadedContent } from './types';\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction invalid(message: string): UploadError {\n return new UploadError(UploadErrorKind.InvalidResponse, message);\n}\n\n/** Parse the upload endpoint's JSON body into `UploadedContent`, or throw `InvalidResponse`. */\nexport function parseUploadedContent(text: string, requireUrl: boolean): UploadedContent {\n let payload: unknown;\n try {\n payload = JSON.parse(text);\n } catch {\n throw invalid('Upload response was not JSON.');\n }\n // `status` is optional: ContentService sends it, the Dloizides.Content.Upload.AspNetCore\n // proxy (`ContentUploadResponse(ContentId, Url)`) does not. Present-but-not-a-string is still malformed.\n const hasBadStatus = isRecord(payload) && payload.status !== undefined && typeof payload.status !== 'string';\n if (!isRecord(payload) || typeof payload.contentId !== 'string' || hasBadStatus) {\n throw invalid('Upload response shape was invalid.');\n }\n const status = typeof payload.status === 'string' ? payload.status : undefined;\n const url = typeof payload.url === 'string' && payload.url !== '' ? payload.url : undefined;\n if (requireUrl && url === undefined) {\n throw invalid('Upload response did not include a public URL.');\n }\n return { contentId: payload.contentId, status, url };\n}\n","import { UploadError } from './UploadError';\nimport { UploadErrorKind } from './UploadErrorKind';\n\nimport type { ProgressListener } from './types';\n\nconst HTTP_OK_MIN = 200;\nconst HTTP_OK_MAX_EXCLUSIVE = 300;\nexport const PERCENT_COMPLETE = 100;\n\n/** A fully built multipart upload, independent of the transport that sends it. */\nexport interface UploadRequest {\n url: string;\n /** Never contains Content-Type: the runtime sets the multipart boundary for a FormData body. */\n headers: Record<string, string>;\n form: FormData;\n credentials: RequestCredentials;\n signal: AbortSignal | undefined;\n}\n\nfunction isOk(status: number): boolean {\n return status >= HTTP_OK_MIN && status < HTTP_OK_MAX_EXCLUSIVE;\n}\n\nfunction httpError(status: number): UploadError {\n return new UploadError(UploadErrorKind.Http, `Upload failed (${String(status)}).`, status);\n}\n\nfunction abortedError(): UploadError {\n return new UploadError(UploadErrorKind.Aborted, 'Upload was cancelled.');\n}\n\n/** POST with fetch; resolves to the raw response text of a 2xx. */\nexport async function sendWithFetch(request: UploadRequest, fetchImpl: typeof fetch): Promise<string> {\n let response: Response;\n try {\n response = await fetchImpl(request.url, {\n method: 'POST',\n credentials: request.credentials,\n headers: request.headers,\n body: request.form,\n signal: request.signal,\n });\n } catch (cause) {\n if (request.signal?.aborted === true) {\n throw abortedError();\n }\n const detail = cause instanceof Error ? cause.message : String(cause);\n throw new UploadError(UploadErrorKind.Network, detail);\n }\n if (!isOk(response.status)) {\n throw httpError(response.status);\n }\n return response.text();\n}\n\nfunction reportProgress(event: ProgressEvent, onProgress: ProgressListener): void {\n if (event.lengthComputable && event.total > 0) {\n onProgress(Math.round((event.loaded / event.total) * PERCENT_COMPLETE));\n }\n}\n\n/** POST with XMLHttpRequest so upload progress can be observed. */\nexport function sendWithXhr(\n request: UploadRequest,\n createXhr: () => XMLHttpRequest,\n onProgress: ProgressListener,\n): Promise<string> {\n return new Promise((resolve, reject) => {\n const { signal } = request;\n if (signal?.aborted === true) {\n reject(abortedError());\n return;\n }\n const xhr = createXhr();\n xhr.upload.addEventListener('progress', (event) => reportProgress(event, onProgress));\n xhr.addEventListener('load', () => {\n if (isOk(xhr.status)) {\n resolve(xhr.responseText);\n } else {\n reject(httpError(xhr.status));\n }\n });\n xhr.addEventListener('error', () => reject(new UploadError(UploadErrorKind.Network, 'Upload failed (network error).')));\n xhr.addEventListener('abort', () => reject(abortedError()));\n signal?.addEventListener('abort', () => xhr.abort(), { once: true });\n xhr.open('POST', request.url);\n xhr.withCredentials = request.credentials === 'include';\n Object.entries(request.headers).forEach(([name, value]) => xhr.setRequestHeader(name, value));\n xhr.send(request.form);\n });\n}\n","import { parseUploadedContent } from './parseUploadedContent';\nimport { sendWithFetch, sendWithXhr } from './transports';\nimport { UploadError } from './UploadError';\nimport { UploadErrorKind } from './UploadErrorKind';\n\nimport type { UploadRequest } from './transports';\nimport type { CsrfHeader, UploadedContent, UploadImageOptions } from './types';\n\n/** The Bff.AspNetCore anti-forgery convention: header presence, any value. */\nexport const DEFAULT_CSRF_HEADER: CsrfHeader = { name: 'X-BFF-Csrf', value: '1' };\n\nconst DEFAULT_CATEGORY = 'Image';\nconst DEFAULT_FILE_NAME = 'upload';\nconst FILE_FIELD = 'File';\nconst CATEGORY_FIELD = 'Category';\nconst IS_PUBLIC_FIELD = 'IsPublic';\n\nfunction validationError(message: string): UploadError {\n return new UploadError(UploadErrorKind.Validation, message);\n}\n\n/** Client-side checks that must fail before a byte leaves the device. */\nfunction validate(file: Blob, options: UploadImageOptions): void {\n if (file.size === 0) {\n throw validationError('File is empty.');\n }\n if (options.maxBytes !== undefined && file.size > options.maxBytes) {\n throw validationError(`File is ${String(file.size)} bytes; the limit is ${String(options.maxBytes)}.`);\n }\n if (options.allowedTypes !== undefined && !options.allowedTypes.includes(file.type)) {\n throw validationError(`File type \"${file.type}\" is not allowed.`);\n }\n}\n\nfunction resolveFileName(file: Blob, explicit: string | undefined): string {\n if (explicit !== undefined && explicit !== '') {\n return explicit;\n }\n if ('name' in file && typeof file.name === 'string' && file.name !== '') {\n return file.name;\n }\n return DEFAULT_FILE_NAME;\n}\n\nfunction buildHeaders(csrf: CsrfHeader | null | undefined): Record<string, string> {\n const headers: Record<string, string> = { Accept: 'application/json' };\n const header = csrf === undefined ? DEFAULT_CSRF_HEADER : csrf;\n if (header !== null) {\n headers[header.name] = header.value;\n }\n return headers;\n}\n\n/** Build the transport-independent request. Exported so consumers can pin the write shape. */\nexport function buildUploadRequest(file: Blob, options: UploadImageOptions): UploadRequest {\n const form = new FormData();\n form.append(FILE_FIELD, file, resolveFileName(file, options.fileName));\n form.append(CATEGORY_FIELD, options.category ?? DEFAULT_CATEGORY);\n form.append(IS_PUBLIC_FIELD, String(options.isPublic ?? true));\n return {\n url: options.endpoint,\n headers: buildHeaders(options.csrf),\n form,\n credentials: options.credentials ?? 'include',\n signal: options.signal,\n };\n}\n\nfunction resolveXhrFactory(options: UploadImageOptions): (() => XMLHttpRequest) | undefined {\n if (options.fetchImpl !== undefined) {\n return undefined;\n }\n if (options.xhrFactory !== undefined) {\n return options.xhrFactory;\n }\n return typeof XMLHttpRequest === 'undefined' ? undefined : (): XMLHttpRequest => new XMLHttpRequest();\n}\n\nasync function send(request: UploadRequest, options: UploadImageOptions): Promise<string> {\n const { onProgress } = options;\n const createXhr = onProgress === undefined ? undefined : resolveXhrFactory(options);\n if (createXhr !== undefined && onProgress !== undefined) {\n return sendWithXhr(request, createXhr, onProgress);\n }\n const fetchImpl = options.fetchImpl ?? globalThis.fetch.bind(globalThis);\n return sendWithFetch(request, fetchImpl);\n}\n\n/**\n * Upload one image as multipart/form-data (`File`, `Category`, `IsPublic`) to a\n * consumer-supplied endpoint and return the parsed `{ contentId, status?, url }`.\n *\n * Never sets Content-Type: the runtime adds the multipart boundary, and a JSON content type\n * on a FormData body is a 415 (see `@dloizides/bff-web-client` writeContentTypeGuard).\n *\n * @throws {UploadError} for every failure; switch on `error.kind`.\n */\nexport async function uploadImage(file: Blob, options: UploadImageOptions): Promise<UploadedContent> {\n validate(file, options);\n const request = buildUploadRequest(file, options);\n const text = await send(request, options);\n return parseUploadedContent(text, options.requireUrl ?? options.isPublic ?? true);\n}\n","import { UploadError } from './UploadError';\nimport { UploadErrorKind } from './UploadErrorKind';\n\n/**\n * Read a local URI (`blob:`, `data:`, or an RN image-picker `file:` URI) into a Blob so it\n * can be passed to `uploadImage`.\n *\n * @throws {UploadError} `Validation` when the URI cannot be read.\n */\nexport async function blobFromUri(uri: string, fetchImpl?: typeof fetch): Promise<Blob> {\n const doFetch = fetchImpl ?? globalThis.fetch.bind(globalThis);\n let response: Response;\n try {\n response = await doFetch(uri);\n } catch (cause) {\n const detail = cause instanceof Error ? cause.message : String(cause);\n throw new UploadError(UploadErrorKind.Validation, `Could not read file: ${detail}`);\n }\n if (!response.ok) {\n throw new UploadError(UploadErrorKind.Validation, `Could not read file (${String(response.status)}).`);\n }\n return response.blob();\n}\n","/** Lifecycle of a single `useImageUpload` slot. */\nexport const enum UploadStatus {\n Idle = 'idle',\n Uploading = 'uploading',\n Error = 'error',\n Done = 'done',\n}\n","import { useCallback, useEffect, useReducer, useRef } from 'react';\n\nimport { PERCENT_COMPLETE } from './transports';\nimport { toUploadError } from './UploadError';\nimport { uploadImage } from './uploadImage';\nimport { UploadStatus } from './UploadStatus';\n\nimport type { UploadedContent, UploadImageOptions } from './types';\nimport type { UploadError } from './UploadError';\n\n/** Options for `useImageUpload`: everything `uploadImage` takes except the per-call wiring. */\nexport interface UseImageUploadOptions extends Omit<UploadImageOptions, 'signal' | 'onProgress'> {\n onSuccess?: (content: UploadedContent) => void;\n onError?: (error: UploadError) => void;\n}\n\nexport interface ImageUploadState {\n status: UploadStatus;\n /** 0-100. Stays 0 while uploading when the transport cannot report progress (fetch). */\n progress: number;\n error: UploadError | null;\n result: UploadedContent | null;\n}\n\nexport interface UseImageUploadResult extends ImageUploadState {\n /** Start an upload (aborting any in-flight one). Resolves to the content, or null on error/abort. */\n upload: (file: Blob, fileName?: string) => Promise<UploadedContent | null>;\n /** Abort any in-flight upload and return to idle. */\n reset: () => void;\n}\n\nconst IDLE: ImageUploadState = { status: UploadStatus.Idle, progress: 0, error: null, result: null };\nconst UPLOADING: ImageUploadState = { status: UploadStatus.Uploading, progress: 0, error: null, result: null };\n\ntype Action =\n | { type: 'start' }\n | { type: 'progress'; percent: number }\n | { type: 'done'; result: UploadedContent }\n | { type: 'fail'; error: UploadError }\n | { type: 'reset' };\n\nfunction reducer(state: ImageUploadState, action: Action): ImageUploadState {\n switch (action.type) {\n case 'start':\n return UPLOADING;\n case 'progress':\n return { ...state, progress: action.percent };\n case 'done':\n return { status: UploadStatus.Done, progress: PERCENT_COMPLETE, error: null, result: action.result };\n case 'fail':\n return { ...state, status: UploadStatus.Error, error: action.error };\n default:\n return IDLE;\n }\n}\n\n/**\n * One image-upload slot: idle -> uploading -> done | error. Progress is reported when the\n * XMLHttpRequest transport is available (browser, React Native) and no `fetchImpl` is set.\n * A newer `upload()` or `reset()` supersedes the previous request; its late result is dropped.\n * Unmount aborts the in-flight request.\n */\nexport function useImageUpload(options: UseImageUploadOptions): UseImageUploadResult {\n const [state, dispatch] = useReducer(reducer, IDLE);\n const optionsRef = useRef(options);\n optionsRef.current = options;\n const controllerRef = useRef<AbortController | null>(null);\n const requestIdRef = useRef(0);\n\n const abortInFlight = useCallback((): void => {\n controllerRef.current?.abort();\n controllerRef.current = null;\n requestIdRef.current += 1;\n }, []);\n\n useEffect(() => abortInFlight, [abortInFlight]);\n\n const upload = useCallback(async (file: Blob, fileName?: string): Promise<UploadedContent | null> => {\n abortInFlight();\n const controller = new AbortController();\n controllerRef.current = controller;\n const requestId = requestIdRef.current;\n const isCurrent = (): boolean => requestId === requestIdRef.current;\n const { onSuccess, onError, ...uploadOptions } = optionsRef.current;\n dispatch({ type: 'start' });\n try {\n const result = await uploadImage(file, {\n ...uploadOptions,\n fileName: fileName ?? uploadOptions.fileName,\n signal: controller.signal,\n onProgress: (percent) => {\n if (isCurrent()) {\n dispatch({ type: 'progress', percent });\n }\n },\n });\n if (!isCurrent()) {\n return null;\n }\n dispatch({ type: 'done', result });\n onSuccess?.(result);\n return result;\n } catch (cause) {\n if (!isCurrent()) {\n return null;\n }\n const error = toUploadError(cause);\n dispatch({ type: 'fail', error });\n onError?.(error);\n return null;\n }\n }, [abortInFlight]);\n\n const reset = useCallback((): void => {\n abortInFlight();\n dispatch({ type: 'reset' });\n }, [abortInFlight]);\n\n return { ...state, upload, reset };\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/UploadErrorKind.ts","../src/UploadError.ts","../src/parseUploadedContent.ts","../src/transports.ts","../src/uploadImage.ts","../src/uploadFormWithProgress.ts","../src/blobFromUri.ts","../src/UploadStatus.ts","../src/useImageUpload.ts"],"names":["UploadErrorKind","buildHeaders","UploadStatus","useReducer","useRef","useCallback","useEffect"],"mappings":"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","file":"index.js","sourcesContent":["/** Why an upload failed. Consumers map this to translated copy; `message` is developer-facing only. */\nexport const enum UploadErrorKind {\n /** Rejected client-side before any request: empty file, too large, disallowed MIME type. */\n Validation = 'validation',\n /** The request never produced an HTTP response (offline, DNS, CORS). */\n Network = 'network',\n /** The server answered outside 2xx; `status` carries the code. */\n Http = 'http',\n /** The caller aborted (AbortSignal / hook reset). */\n Aborted = 'aborted',\n /** 2xx, but the body was not `{ contentId, status?, url? }` (or lacked a required url). */\n InvalidResponse = 'invalid-response',\n}\n","import { UploadErrorKind } from './UploadErrorKind';\r\n\r\n/** The single error type thrown by `uploadImage` and surfaced by `useImageUpload`. */\r\nexport class UploadError extends Error {\r\n readonly kind: UploadErrorKind;\r\n /** HTTP status for `UploadErrorKind.Http`; undefined otherwise. */\r\n readonly status: number | undefined;\r\n /** Raw response body for `UploadErrorKind.Http`; undefined otherwise. */\r\n readonly body: string | undefined;\r\n\r\n constructor(kind: UploadErrorKind, message: string, status?: number, body?: string) {\r\n super(message);\r\n this.name = 'UploadError';\r\n this.kind = kind;\r\n this.status = status;\r\n this.body = body;\r\n }\r\n}\r\n\r\n/** Normalise anything thrown into an `UploadError` (unknown throws become `Network`). */\r\nexport function toUploadError(cause: unknown): UploadError {\r\n if (cause instanceof UploadError) {\r\n return cause;\r\n }\r\n const message = cause instanceof Error ? cause.message : String(cause);\r\n return new UploadError(UploadErrorKind.Network, message);\r\n}\r\n","import { UploadError } from './UploadError';\nimport { UploadErrorKind } from './UploadErrorKind';\n\nimport type { UploadedContent } from './types';\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction invalid(message: string): UploadError {\n return new UploadError(UploadErrorKind.InvalidResponse, message);\n}\n\n/** Parse the upload endpoint's JSON body into `UploadedContent`, or throw `InvalidResponse`. */\nexport function parseUploadedContent(text: string, requireUrl: boolean): UploadedContent {\n let payload: unknown;\n try {\n payload = JSON.parse(text);\n } catch {\n throw invalid('Upload response was not JSON.');\n }\n // `status` is optional: ContentService sends it, the Dloizides.Content.Upload.AspNetCore\n // proxy (`ContentUploadResponse(ContentId, Url)`) does not. Present-but-not-a-string is still malformed.\n const hasBadStatus = isRecord(payload) && payload.status !== undefined && typeof payload.status !== 'string';\n if (!isRecord(payload) || typeof payload.contentId !== 'string' || hasBadStatus) {\n throw invalid('Upload response shape was invalid.');\n }\n const status = typeof payload.status === 'string' ? payload.status : undefined;\n const url = typeof payload.url === 'string' && payload.url !== '' ? payload.url : undefined;\n if (requireUrl && url === undefined) {\n throw invalid('Upload response did not include a public URL.');\n }\n return { contentId: payload.contentId, status, url };\n}\n","import { UploadError } from './UploadError';\r\nimport { UploadErrorKind } from './UploadErrorKind';\r\n\r\nimport type { ProgressListener } from './types';\r\n\r\nconst HTTP_OK_MIN = 200;\r\nconst HTTP_OK_MAX_EXCLUSIVE = 300;\r\nexport const PERCENT_COMPLETE = 100;\r\n\r\n/** A fully built multipart upload, independent of the transport that sends it. */\r\nexport interface UploadRequest {\r\n url: string;\r\n /** Never contains Content-Type: the runtime sets the multipart boundary for a FormData body. */\r\n headers: Record<string, string>;\r\n form: FormData;\r\n credentials: RequestCredentials;\r\n signal: AbortSignal | undefined;\r\n}\r\n\r\nfunction isOk(status: number): boolean {\r\n return status >= HTTP_OK_MIN && status < HTTP_OK_MAX_EXCLUSIVE;\r\n}\r\n\r\nfunction httpError(status: number, body: string): UploadError {\r\n return new UploadError(UploadErrorKind.Http, `Upload failed (${String(status)}).`, status, body);\r\n}\r\n\r\nasync function readBodySafely(response: Response): Promise<string> {\r\n try {\r\n return await response.text();\r\n } catch {\r\n return '';\r\n }\r\n}\r\n\r\nfunction abortedError(): UploadError {\r\n return new UploadError(UploadErrorKind.Aborted, 'Upload was cancelled.');\r\n}\r\n\r\n/** POST with fetch; resolves to the raw response text of a 2xx. */\r\nexport async function sendWithFetch(request: UploadRequest, fetchImpl: typeof fetch): Promise<string> {\r\n let response: Response;\r\n try {\r\n response = await fetchImpl(request.url, {\r\n method: 'POST',\r\n credentials: request.credentials,\r\n headers: request.headers,\r\n body: request.form,\r\n signal: request.signal,\r\n });\r\n } catch (cause) {\r\n if (request.signal?.aborted === true) {\r\n throw abortedError();\r\n }\r\n const detail = cause instanceof Error ? cause.message : String(cause);\r\n throw new UploadError(UploadErrorKind.Network, detail);\r\n }\r\n if (!isOk(response.status)) {\r\n throw httpError(response.status, await readBodySafely(response));\r\n }\r\n return response.text();\r\n}\r\n\r\nfunction reportProgress(event: ProgressEvent, onProgress: ProgressListener): void {\r\n if (event.lengthComputable && event.total > 0) {\r\n onProgress(Math.round((event.loaded / event.total) * PERCENT_COMPLETE));\r\n }\r\n}\r\n\r\n/** POST with XMLHttpRequest so upload progress can be observed. */\r\nexport function sendWithXhr(\r\n request: UploadRequest,\r\n createXhr: () => XMLHttpRequest,\r\n onProgress: ProgressListener,\r\n): Promise<string> {\r\n return new Promise((resolve, reject) => {\r\n const { signal } = request;\r\n if (signal?.aborted === true) {\r\n reject(abortedError());\r\n return;\r\n }\r\n const xhr = createXhr();\r\n xhr.upload.addEventListener('progress', (event) => reportProgress(event, onProgress));\r\n xhr.addEventListener('load', () => {\r\n if (isOk(xhr.status)) {\r\n resolve(xhr.responseText);\r\n } else {\r\n reject(httpError(xhr.status, xhr.responseText));\r\n }\r\n });\r\n xhr.addEventListener('error', () => reject(new UploadError(UploadErrorKind.Network, 'Upload failed (network error).')));\r\n xhr.addEventListener('abort', () => reject(abortedError()));\r\n signal?.addEventListener('abort', () => xhr.abort(), { once: true });\r\n xhr.open('POST', request.url);\r\n xhr.withCredentials = request.credentials === 'include';\r\n Object.entries(request.headers).forEach(([name, value]) => xhr.setRequestHeader(name, value));\r\n xhr.send(request.form);\r\n });\r\n}\r\n\r\n/** Transport selection knobs shared by every upload entry point. */\r\nexport interface TransportOptions {\r\n /** When set (and no `fetchImpl`), the upload goes over XMLHttpRequest to report progress. */\r\n onProgress?: ProgressListener;\r\n /** Custom fetch (tests, SSR). Forces the fetch transport. */\r\n fetchImpl?: typeof fetch;\r\n /** Custom XMLHttpRequest factory (tests). */\r\n xhrFactory?: () => XMLHttpRequest;\r\n}\r\n\r\nfunction resolveXhrFactory(options: TransportOptions): (() => XMLHttpRequest) | undefined {\r\n if (options.fetchImpl !== undefined) {\r\n return undefined;\r\n }\r\n if (options.xhrFactory !== undefined) {\r\n return options.xhrFactory;\r\n }\r\n return typeof XMLHttpRequest === 'undefined' ? undefined : (): XMLHttpRequest => new XMLHttpRequest();\r\n}\r\n\r\n/** Send over XMLHttpRequest when progress is wanted and available, else over fetch. */\r\nexport async function sendUploadRequest(request: UploadRequest, options: TransportOptions): Promise<string> {\r\n const { onProgress } = options;\r\n const createXhr = onProgress === undefined ? undefined : resolveXhrFactory(options);\r\n if (createXhr !== undefined && onProgress !== undefined) {\r\n return sendWithXhr(request, createXhr, onProgress);\r\n }\r\n const fetchImpl = options.fetchImpl ?? globalThis.fetch.bind(globalThis);\r\n return sendWithFetch(request, fetchImpl);\r\n}\r\n","import { parseUploadedContent } from './parseUploadedContent';\r\nimport { sendUploadRequest } from './transports';\r\nimport { UploadError } from './UploadError';\r\nimport { UploadErrorKind } from './UploadErrorKind';\r\n\r\nimport type { UploadRequest } from './transports';\r\nimport type { CsrfHeader, UploadedContent, UploadImageOptions } from './types';\r\n\r\n/** The Bff.AspNetCore anti-forgery convention: header presence, any value. */\r\nexport const DEFAULT_CSRF_HEADER: CsrfHeader = { name: 'X-BFF-Csrf', value: '1' };\r\n\r\nconst DEFAULT_CATEGORY = 'Image';\r\nconst DEFAULT_FILE_NAME = 'upload';\r\nconst FILE_FIELD = 'File';\r\nconst CATEGORY_FIELD = 'Category';\r\nconst IS_PUBLIC_FIELD = 'IsPublic';\r\n\r\nfunction validationError(message: string): UploadError {\r\n return new UploadError(UploadErrorKind.Validation, message);\r\n}\r\n\r\n/** Client-side checks that must fail before a byte leaves the device. */\r\nfunction validate(file: Blob, options: UploadImageOptions): void {\r\n if (file.size === 0) {\r\n throw validationError('File is empty.');\r\n }\r\n if (options.maxBytes !== undefined && file.size > options.maxBytes) {\r\n throw validationError(`File is ${String(file.size)} bytes; the limit is ${String(options.maxBytes)}.`);\r\n }\r\n if (options.allowedTypes !== undefined && !options.allowedTypes.includes(file.type)) {\r\n throw validationError(`File type \"${file.type}\" is not allowed.`);\r\n }\r\n}\r\n\r\nfunction resolveFileName(file: Blob, explicit: string | undefined): string {\r\n if (explicit !== undefined && explicit !== '') {\r\n return explicit;\r\n }\r\n if ('name' in file && typeof file.name === 'string' && file.name !== '') {\r\n return file.name;\r\n }\r\n return DEFAULT_FILE_NAME;\r\n}\r\n\r\nfunction buildHeaders(csrf: CsrfHeader | null | undefined): Record<string, string> {\r\n const headers: Record<string, string> = { Accept: 'application/json' };\r\n const header = csrf === undefined ? DEFAULT_CSRF_HEADER : csrf;\r\n if (header !== null) {\r\n headers[header.name] = header.value;\r\n }\r\n return headers;\r\n}\r\n\r\n/** Build the transport-independent request. Exported so consumers can pin the write shape. */\r\nexport function buildUploadRequest(file: Blob, options: UploadImageOptions): UploadRequest {\r\n const form = new FormData();\r\n form.append(FILE_FIELD, file, resolveFileName(file, options.fileName));\r\n form.append(CATEGORY_FIELD, options.category ?? DEFAULT_CATEGORY);\r\n form.append(IS_PUBLIC_FIELD, String(options.isPublic ?? true));\r\n return {\r\n url: options.endpoint,\r\n headers: buildHeaders(options.csrf),\r\n form,\r\n credentials: options.credentials ?? 'include',\r\n signal: options.signal,\r\n };\r\n}\r\n\r\n/**\r\n * Upload one image as multipart/form-data (`File`, `Category`, `IsPublic`) to a\r\n * consumer-supplied endpoint and return the parsed `{ contentId, status?, url }`.\r\n *\r\n * Never sets Content-Type: the runtime adds the multipart boundary, and a JSON content type\r\n * on a FormData body is a 415 (see `@dloizides/bff-web-client` writeContentTypeGuard).\r\n *\r\n * @throws {UploadError} for every failure; switch on `error.kind`.\r\n */\r\nexport async function uploadImage(file: Blob, options: UploadImageOptions): Promise<UploadedContent> {\r\n validate(file, options);\r\n const request = buildUploadRequest(file, options);\r\n const text = await sendUploadRequest(request, options);\r\n return parseUploadedContent(text, options.requireUrl ?? options.isPublic ?? true);\r\n}\r\n","import { sendUploadRequest } from './transports';\nimport { UploadError } from './UploadError';\nimport { UploadErrorKind } from './UploadErrorKind';\n\nimport type { UploadRequest } from './transports';\nimport type { UploadFormOptions } from './types';\n\nconst CONTENT_TYPE_HEADER = 'content-type';\nconst ACCEPT_HEADER = 'accept';\n\nfunction buildHeaders(extra: Record<string, string> | undefined): Record<string, string> {\n const headers: Record<string, string> = {};\n Object.entries(extra ?? {}).forEach(([name, value]) => {\n if (name.toLowerCase() !== CONTENT_TYPE_HEADER) {headers[name] = value;}\n });\n const hasAccept = Object.keys(headers).some((name) => name.toLowerCase() === ACCEPT_HEADER);\n if (!hasAccept) {headers.Accept = 'application/json';}\n return headers;\n}\n\nfunction buildForm(parts: Record<string, Blob | string>): FormData {\n const form = new FormData();\n Object.entries(parts).forEach(([name, value]) => form.append(name, value));\n return form;\n}\n\n/** Build the transport-independent request. Exported so consumers can pin the write shape. */\nexport function buildFormRequest(options: UploadFormOptions): UploadRequest {\n return {\n url: options.url,\n headers: buildHeaders(options.headers),\n form: buildForm(options.parts),\n credentials: options.withCredentials === false ? 'same-origin' : 'include',\n signal: options.signal,\n };\n}\n\nfunction parseJson<T>(text: string): T | null {\n if (text.trim() === '') {return null;}\n try {\n return JSON.parse(text) as T;\n } catch {\n throw new UploadError(UploadErrorKind.InvalidResponse, 'Upload response was not valid JSON.');\n }\n}\n\n/**\n * POST arbitrary multipart parts with optional upload progress; resolves the parsed JSON body\n * (`null` for an empty 2xx body).\n *\n * @throws {UploadError} `Http` carries `status` and the raw `body`; switch on `error.kind`.\n */\nexport async function uploadFormWithProgress<T = unknown>(options: UploadFormOptions): Promise<T | null> {\n const text = await sendUploadRequest(buildFormRequest(options), options);\n return parseJson<T>(text);\n}\n","import { UploadError } from './UploadError';\nimport { UploadErrorKind } from './UploadErrorKind';\n\n/**\n * Read a local URI (`blob:`, `data:`, or an RN image-picker `file:` URI) into a Blob so it\n * can be passed to `uploadImage`.\n *\n * @throws {UploadError} `Validation` when the URI cannot be read.\n */\nexport async function blobFromUri(uri: string, fetchImpl?: typeof fetch): Promise<Blob> {\n const doFetch = fetchImpl ?? globalThis.fetch.bind(globalThis);\n let response: Response;\n try {\n response = await doFetch(uri);\n } catch (cause) {\n const detail = cause instanceof Error ? cause.message : String(cause);\n throw new UploadError(UploadErrorKind.Validation, `Could not read file: ${detail}`);\n }\n if (!response.ok) {\n throw new UploadError(UploadErrorKind.Validation, `Could not read file (${String(response.status)}).`);\n }\n return response.blob();\n}\n","/** Lifecycle of a single `useImageUpload` slot. */\nexport const enum UploadStatus {\n Idle = 'idle',\n Uploading = 'uploading',\n Error = 'error',\n Done = 'done',\n}\n","import { useCallback, useEffect, useReducer, useRef } from 'react';\n\nimport { PERCENT_COMPLETE } from './transports';\nimport { toUploadError } from './UploadError';\nimport { uploadImage } from './uploadImage';\nimport { UploadStatus } from './UploadStatus';\n\nimport type { UploadedContent, UploadImageOptions } from './types';\nimport type { UploadError } from './UploadError';\n\n/** Options for `useImageUpload`: everything `uploadImage` takes except the per-call wiring. */\nexport interface UseImageUploadOptions extends Omit<UploadImageOptions, 'signal' | 'onProgress'> {\n onSuccess?: (content: UploadedContent) => void;\n onError?: (error: UploadError) => void;\n}\n\nexport interface ImageUploadState {\n status: UploadStatus;\n /** 0-100. Stays 0 while uploading when the transport cannot report progress (fetch). */\n progress: number;\n error: UploadError | null;\n result: UploadedContent | null;\n}\n\nexport interface UseImageUploadResult extends ImageUploadState {\n /** Start an upload (aborting any in-flight one). Resolves to the content, or null on error/abort. */\n upload: (file: Blob, fileName?: string) => Promise<UploadedContent | null>;\n /** Abort any in-flight upload and return to idle. */\n reset: () => void;\n}\n\nconst IDLE: ImageUploadState = { status: UploadStatus.Idle, progress: 0, error: null, result: null };\nconst UPLOADING: ImageUploadState = { status: UploadStatus.Uploading, progress: 0, error: null, result: null };\n\ntype Action =\n | { type: 'start' }\n | { type: 'progress'; percent: number }\n | { type: 'done'; result: UploadedContent }\n | { type: 'fail'; error: UploadError }\n | { type: 'reset' };\n\nfunction reducer(state: ImageUploadState, action: Action): ImageUploadState {\n switch (action.type) {\n case 'start':\n return UPLOADING;\n case 'progress':\n return { ...state, progress: action.percent };\n case 'done':\n return { status: UploadStatus.Done, progress: PERCENT_COMPLETE, error: null, result: action.result };\n case 'fail':\n return { ...state, status: UploadStatus.Error, error: action.error };\n default:\n return IDLE;\n }\n}\n\n/**\n * One image-upload slot: idle -> uploading -> done | error. Progress is reported when the\n * XMLHttpRequest transport is available (browser, React Native) and no `fetchImpl` is set.\n * A newer `upload()` or `reset()` supersedes the previous request; its late result is dropped.\n * Unmount aborts the in-flight request.\n */\nexport function useImageUpload(options: UseImageUploadOptions): UseImageUploadResult {\n const [state, dispatch] = useReducer(reducer, IDLE);\n const optionsRef = useRef(options);\n optionsRef.current = options;\n const controllerRef = useRef<AbortController | null>(null);\n const requestIdRef = useRef(0);\n\n const abortInFlight = useCallback((): void => {\n controllerRef.current?.abort();\n controllerRef.current = null;\n requestIdRef.current += 1;\n }, []);\n\n useEffect(() => abortInFlight, [abortInFlight]);\n\n const upload = useCallback(async (file: Blob, fileName?: string): Promise<UploadedContent | null> => {\n abortInFlight();\n const controller = new AbortController();\n controllerRef.current = controller;\n const requestId = requestIdRef.current;\n const isCurrent = (): boolean => requestId === requestIdRef.current;\n const { onSuccess, onError, ...uploadOptions } = optionsRef.current;\n dispatch({ type: 'start' });\n try {\n const result = await uploadImage(file, {\n ...uploadOptions,\n fileName: fileName ?? uploadOptions.fileName,\n signal: controller.signal,\n onProgress: (percent) => {\n if (isCurrent()) {\n dispatch({ type: 'progress', percent });\n }\n },\n });\n if (!isCurrent()) {\n return null;\n }\n dispatch({ type: 'done', result });\n onSuccess?.(result);\n return result;\n } catch (cause) {\n if (!isCurrent()) {\n return null;\n }\n const error = toUploadError(cause);\n dispatch({ type: 'fail', error });\n onError?.(error);\n return null;\n }\n }, [abortInFlight]);\n\n const reset = useCallback((): void => {\n abortInFlight();\n dispatch({ type: 'reset' });\n }, [abortInFlight]);\n\n return { ...state, upload, reset };\n}\n"]}
|
package/dist/index.mjs
CHANGED
|
@@ -12,11 +12,12 @@ var UploadErrorKind = /* @__PURE__ */ ((UploadErrorKind2) => {
|
|
|
12
12
|
|
|
13
13
|
// src/UploadError.ts
|
|
14
14
|
var UploadError = class extends Error {
|
|
15
|
-
constructor(kind, message, status) {
|
|
15
|
+
constructor(kind, message, status, body) {
|
|
16
16
|
super(message);
|
|
17
17
|
this.name = "UploadError";
|
|
18
18
|
this.kind = kind;
|
|
19
19
|
this.status = status;
|
|
20
|
+
this.body = body;
|
|
20
21
|
}
|
|
21
22
|
};
|
|
22
23
|
function toUploadError(cause) {
|
|
@@ -60,8 +61,15 @@ var PERCENT_COMPLETE = 100;
|
|
|
60
61
|
function isOk(status) {
|
|
61
62
|
return status >= HTTP_OK_MIN && status < HTTP_OK_MAX_EXCLUSIVE;
|
|
62
63
|
}
|
|
63
|
-
function httpError(status) {
|
|
64
|
-
return new UploadError("http" /* Http */, `Upload failed (${String(status)}).`, status);
|
|
64
|
+
function httpError(status, body) {
|
|
65
|
+
return new UploadError("http" /* Http */, `Upload failed (${String(status)}).`, status, body);
|
|
66
|
+
}
|
|
67
|
+
async function readBodySafely(response) {
|
|
68
|
+
try {
|
|
69
|
+
return await response.text();
|
|
70
|
+
} catch {
|
|
71
|
+
return "";
|
|
72
|
+
}
|
|
65
73
|
}
|
|
66
74
|
function abortedError() {
|
|
67
75
|
return new UploadError("aborted" /* Aborted */, "Upload was cancelled.");
|
|
@@ -84,7 +92,7 @@ async function sendWithFetch(request, fetchImpl) {
|
|
|
84
92
|
throw new UploadError("network" /* Network */, detail);
|
|
85
93
|
}
|
|
86
94
|
if (!isOk(response.status)) {
|
|
87
|
-
throw httpError(response.status);
|
|
95
|
+
throw httpError(response.status, await readBodySafely(response));
|
|
88
96
|
}
|
|
89
97
|
return response.text();
|
|
90
98
|
}
|
|
@@ -106,7 +114,7 @@ function sendWithXhr(request, createXhr, onProgress) {
|
|
|
106
114
|
if (isOk(xhr.status)) {
|
|
107
115
|
resolve(xhr.responseText);
|
|
108
116
|
} else {
|
|
109
|
-
reject(httpError(xhr.status));
|
|
117
|
+
reject(httpError(xhr.status, xhr.responseText));
|
|
110
118
|
}
|
|
111
119
|
});
|
|
112
120
|
xhr.addEventListener("error", () => reject(new UploadError("network" /* Network */, "Upload failed (network error).")));
|
|
@@ -118,6 +126,24 @@ function sendWithXhr(request, createXhr, onProgress) {
|
|
|
118
126
|
xhr.send(request.form);
|
|
119
127
|
});
|
|
120
128
|
}
|
|
129
|
+
function resolveXhrFactory(options) {
|
|
130
|
+
if (options.fetchImpl !== void 0) {
|
|
131
|
+
return void 0;
|
|
132
|
+
}
|
|
133
|
+
if (options.xhrFactory !== void 0) {
|
|
134
|
+
return options.xhrFactory;
|
|
135
|
+
}
|
|
136
|
+
return typeof XMLHttpRequest === "undefined" ? void 0 : () => new XMLHttpRequest();
|
|
137
|
+
}
|
|
138
|
+
async function sendUploadRequest(request, options) {
|
|
139
|
+
const { onProgress } = options;
|
|
140
|
+
const createXhr = onProgress === void 0 ? void 0 : resolveXhrFactory(options);
|
|
141
|
+
if (createXhr !== void 0 && onProgress !== void 0) {
|
|
142
|
+
return sendWithXhr(request, createXhr, onProgress);
|
|
143
|
+
}
|
|
144
|
+
const fetchImpl = options.fetchImpl ?? globalThis.fetch.bind(globalThis);
|
|
145
|
+
return sendWithFetch(request, fetchImpl);
|
|
146
|
+
}
|
|
121
147
|
|
|
122
148
|
// src/uploadImage.ts
|
|
123
149
|
var DEFAULT_CSRF_HEADER = { name: "X-BFF-Csrf", value: "1" };
|
|
@@ -170,31 +196,58 @@ function buildUploadRequest(file, options) {
|
|
|
170
196
|
signal: options.signal
|
|
171
197
|
};
|
|
172
198
|
}
|
|
173
|
-
function resolveXhrFactory(options) {
|
|
174
|
-
if (options.fetchImpl !== void 0) {
|
|
175
|
-
return void 0;
|
|
176
|
-
}
|
|
177
|
-
if (options.xhrFactory !== void 0) {
|
|
178
|
-
return options.xhrFactory;
|
|
179
|
-
}
|
|
180
|
-
return typeof XMLHttpRequest === "undefined" ? void 0 : () => new XMLHttpRequest();
|
|
181
|
-
}
|
|
182
|
-
async function send(request, options) {
|
|
183
|
-
const { onProgress } = options;
|
|
184
|
-
const createXhr = onProgress === void 0 ? void 0 : resolveXhrFactory(options);
|
|
185
|
-
if (createXhr !== void 0 && onProgress !== void 0) {
|
|
186
|
-
return sendWithXhr(request, createXhr, onProgress);
|
|
187
|
-
}
|
|
188
|
-
const fetchImpl = options.fetchImpl ?? globalThis.fetch.bind(globalThis);
|
|
189
|
-
return sendWithFetch(request, fetchImpl);
|
|
190
|
-
}
|
|
191
199
|
async function uploadImage(file, options) {
|
|
192
200
|
validate(file, options);
|
|
193
201
|
const request = buildUploadRequest(file, options);
|
|
194
|
-
const text = await
|
|
202
|
+
const text = await sendUploadRequest(request, options);
|
|
195
203
|
return parseUploadedContent(text, options.requireUrl ?? options.isPublic ?? true);
|
|
196
204
|
}
|
|
197
205
|
|
|
206
|
+
// src/uploadFormWithProgress.ts
|
|
207
|
+
var CONTENT_TYPE_HEADER = "content-type";
|
|
208
|
+
var ACCEPT_HEADER = "accept";
|
|
209
|
+
function buildHeaders2(extra) {
|
|
210
|
+
const headers = {};
|
|
211
|
+
Object.entries(extra ?? {}).forEach(([name, value]) => {
|
|
212
|
+
if (name.toLowerCase() !== CONTENT_TYPE_HEADER) {
|
|
213
|
+
headers[name] = value;
|
|
214
|
+
}
|
|
215
|
+
});
|
|
216
|
+
const hasAccept = Object.keys(headers).some((name) => name.toLowerCase() === ACCEPT_HEADER);
|
|
217
|
+
if (!hasAccept) {
|
|
218
|
+
headers.Accept = "application/json";
|
|
219
|
+
}
|
|
220
|
+
return headers;
|
|
221
|
+
}
|
|
222
|
+
function buildForm(parts) {
|
|
223
|
+
const form = new FormData();
|
|
224
|
+
Object.entries(parts).forEach(([name, value]) => form.append(name, value));
|
|
225
|
+
return form;
|
|
226
|
+
}
|
|
227
|
+
function buildFormRequest(options) {
|
|
228
|
+
return {
|
|
229
|
+
url: options.url,
|
|
230
|
+
headers: buildHeaders2(options.headers),
|
|
231
|
+
form: buildForm(options.parts),
|
|
232
|
+
credentials: options.withCredentials === false ? "same-origin" : "include",
|
|
233
|
+
signal: options.signal
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
function parseJson(text) {
|
|
237
|
+
if (text.trim() === "") {
|
|
238
|
+
return null;
|
|
239
|
+
}
|
|
240
|
+
try {
|
|
241
|
+
return JSON.parse(text);
|
|
242
|
+
} catch {
|
|
243
|
+
throw new UploadError("invalid-response" /* InvalidResponse */, "Upload response was not valid JSON.");
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
async function uploadFormWithProgress(options) {
|
|
247
|
+
const text = await sendUploadRequest(buildFormRequest(options), options);
|
|
248
|
+
return parseJson(text);
|
|
249
|
+
}
|
|
250
|
+
|
|
198
251
|
// src/blobFromUri.ts
|
|
199
252
|
async function blobFromUri(uri, fetchImpl) {
|
|
200
253
|
const doFetch = fetchImpl ?? globalThis.fetch.bind(globalThis);
|
|
@@ -291,6 +344,6 @@ function useImageUpload(options) {
|
|
|
291
344
|
return { ...state, upload, reset };
|
|
292
345
|
}
|
|
293
346
|
|
|
294
|
-
export { DEFAULT_CSRF_HEADER, UploadError, UploadErrorKind, UploadStatus, blobFromUri, buildUploadRequest, toUploadError, uploadImage, useImageUpload };
|
|
347
|
+
export { DEFAULT_CSRF_HEADER, UploadError, UploadErrorKind, UploadStatus, blobFromUri, buildFormRequest, buildUploadRequest, toUploadError, uploadFormWithProgress, uploadImage, useImageUpload };
|
|
295
348
|
//# sourceMappingURL=index.mjs.map
|
|
296
349
|
//# sourceMappingURL=index.mjs.map
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/UploadErrorKind.ts","../src/UploadError.ts","../src/parseUploadedContent.ts","../src/transports.ts","../src/uploadImage.ts","../src/blobFromUri.ts","../src/UploadStatus.ts","../src/useImageUpload.ts"],"names":["UploadErrorKind","UploadStatus"],"mappings":"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","file":"index.mjs","sourcesContent":["/** Why an upload failed. Consumers map this to translated copy; `message` is developer-facing only. */\nexport const enum UploadErrorKind {\n /** Rejected client-side before any request: empty file, too large, disallowed MIME type. */\n Validation = 'validation',\n /** The request never produced an HTTP response (offline, DNS, CORS). */\n Network = 'network',\n /** The server answered outside 2xx; `status` carries the code. */\n Http = 'http',\n /** The caller aborted (AbortSignal / hook reset). */\n Aborted = 'aborted',\n /** 2xx, but the body was not `{ contentId, status?, url? }` (or lacked a required url). */\n InvalidResponse = 'invalid-response',\n}\n","import { UploadErrorKind } from './UploadErrorKind';\n\n/** The single error type thrown by `uploadImage` and surfaced by `useImageUpload`. */\nexport class UploadError extends Error {\n readonly kind: UploadErrorKind;\n /** HTTP status for `UploadErrorKind.Http`; undefined otherwise. */\n readonly status: number | undefined;\n\n constructor(kind: UploadErrorKind, message: string, status?: number) {\n super(message);\n this.name = 'UploadError';\n this.kind = kind;\n this.status = status;\n }\n}\n\n/** Normalise anything thrown into an `UploadError` (unknown throws become `Network`). */\nexport function toUploadError(cause: unknown): UploadError {\n if (cause instanceof UploadError) {\n return cause;\n }\n const message = cause instanceof Error ? cause.message : String(cause);\n return new UploadError(UploadErrorKind.Network, message);\n}\n","import { UploadError } from './UploadError';\nimport { UploadErrorKind } from './UploadErrorKind';\n\nimport type { UploadedContent } from './types';\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction invalid(message: string): UploadError {\n return new UploadError(UploadErrorKind.InvalidResponse, message);\n}\n\n/** Parse the upload endpoint's JSON body into `UploadedContent`, or throw `InvalidResponse`. */\nexport function parseUploadedContent(text: string, requireUrl: boolean): UploadedContent {\n let payload: unknown;\n try {\n payload = JSON.parse(text);\n } catch {\n throw invalid('Upload response was not JSON.');\n }\n // `status` is optional: ContentService sends it, the Dloizides.Content.Upload.AspNetCore\n // proxy (`ContentUploadResponse(ContentId, Url)`) does not. Present-but-not-a-string is still malformed.\n const hasBadStatus = isRecord(payload) && payload.status !== undefined && typeof payload.status !== 'string';\n if (!isRecord(payload) || typeof payload.contentId !== 'string' || hasBadStatus) {\n throw invalid('Upload response shape was invalid.');\n }\n const status = typeof payload.status === 'string' ? payload.status : undefined;\n const url = typeof payload.url === 'string' && payload.url !== '' ? payload.url : undefined;\n if (requireUrl && url === undefined) {\n throw invalid('Upload response did not include a public URL.');\n }\n return { contentId: payload.contentId, status, url };\n}\n","import { UploadError } from './UploadError';\nimport { UploadErrorKind } from './UploadErrorKind';\n\nimport type { ProgressListener } from './types';\n\nconst HTTP_OK_MIN = 200;\nconst HTTP_OK_MAX_EXCLUSIVE = 300;\nexport const PERCENT_COMPLETE = 100;\n\n/** A fully built multipart upload, independent of the transport that sends it. */\nexport interface UploadRequest {\n url: string;\n /** Never contains Content-Type: the runtime sets the multipart boundary for a FormData body. */\n headers: Record<string, string>;\n form: FormData;\n credentials: RequestCredentials;\n signal: AbortSignal | undefined;\n}\n\nfunction isOk(status: number): boolean {\n return status >= HTTP_OK_MIN && status < HTTP_OK_MAX_EXCLUSIVE;\n}\n\nfunction httpError(status: number): UploadError {\n return new UploadError(UploadErrorKind.Http, `Upload failed (${String(status)}).`, status);\n}\n\nfunction abortedError(): UploadError {\n return new UploadError(UploadErrorKind.Aborted, 'Upload was cancelled.');\n}\n\n/** POST with fetch; resolves to the raw response text of a 2xx. */\nexport async function sendWithFetch(request: UploadRequest, fetchImpl: typeof fetch): Promise<string> {\n let response: Response;\n try {\n response = await fetchImpl(request.url, {\n method: 'POST',\n credentials: request.credentials,\n headers: request.headers,\n body: request.form,\n signal: request.signal,\n });\n } catch (cause) {\n if (request.signal?.aborted === true) {\n throw abortedError();\n }\n const detail = cause instanceof Error ? cause.message : String(cause);\n throw new UploadError(UploadErrorKind.Network, detail);\n }\n if (!isOk(response.status)) {\n throw httpError(response.status);\n }\n return response.text();\n}\n\nfunction reportProgress(event: ProgressEvent, onProgress: ProgressListener): void {\n if (event.lengthComputable && event.total > 0) {\n onProgress(Math.round((event.loaded / event.total) * PERCENT_COMPLETE));\n }\n}\n\n/** POST with XMLHttpRequest so upload progress can be observed. */\nexport function sendWithXhr(\n request: UploadRequest,\n createXhr: () => XMLHttpRequest,\n onProgress: ProgressListener,\n): Promise<string> {\n return new Promise((resolve, reject) => {\n const { signal } = request;\n if (signal?.aborted === true) {\n reject(abortedError());\n return;\n }\n const xhr = createXhr();\n xhr.upload.addEventListener('progress', (event) => reportProgress(event, onProgress));\n xhr.addEventListener('load', () => {\n if (isOk(xhr.status)) {\n resolve(xhr.responseText);\n } else {\n reject(httpError(xhr.status));\n }\n });\n xhr.addEventListener('error', () => reject(new UploadError(UploadErrorKind.Network, 'Upload failed (network error).')));\n xhr.addEventListener('abort', () => reject(abortedError()));\n signal?.addEventListener('abort', () => xhr.abort(), { once: true });\n xhr.open('POST', request.url);\n xhr.withCredentials = request.credentials === 'include';\n Object.entries(request.headers).forEach(([name, value]) => xhr.setRequestHeader(name, value));\n xhr.send(request.form);\n });\n}\n","import { parseUploadedContent } from './parseUploadedContent';\nimport { sendWithFetch, sendWithXhr } from './transports';\nimport { UploadError } from './UploadError';\nimport { UploadErrorKind } from './UploadErrorKind';\n\nimport type { UploadRequest } from './transports';\nimport type { CsrfHeader, UploadedContent, UploadImageOptions } from './types';\n\n/** The Bff.AspNetCore anti-forgery convention: header presence, any value. */\nexport const DEFAULT_CSRF_HEADER: CsrfHeader = { name: 'X-BFF-Csrf', value: '1' };\n\nconst DEFAULT_CATEGORY = 'Image';\nconst DEFAULT_FILE_NAME = 'upload';\nconst FILE_FIELD = 'File';\nconst CATEGORY_FIELD = 'Category';\nconst IS_PUBLIC_FIELD = 'IsPublic';\n\nfunction validationError(message: string): UploadError {\n return new UploadError(UploadErrorKind.Validation, message);\n}\n\n/** Client-side checks that must fail before a byte leaves the device. */\nfunction validate(file: Blob, options: UploadImageOptions): void {\n if (file.size === 0) {\n throw validationError('File is empty.');\n }\n if (options.maxBytes !== undefined && file.size > options.maxBytes) {\n throw validationError(`File is ${String(file.size)} bytes; the limit is ${String(options.maxBytes)}.`);\n }\n if (options.allowedTypes !== undefined && !options.allowedTypes.includes(file.type)) {\n throw validationError(`File type \"${file.type}\" is not allowed.`);\n }\n}\n\nfunction resolveFileName(file: Blob, explicit: string | undefined): string {\n if (explicit !== undefined && explicit !== '') {\n return explicit;\n }\n if ('name' in file && typeof file.name === 'string' && file.name !== '') {\n return file.name;\n }\n return DEFAULT_FILE_NAME;\n}\n\nfunction buildHeaders(csrf: CsrfHeader | null | undefined): Record<string, string> {\n const headers: Record<string, string> = { Accept: 'application/json' };\n const header = csrf === undefined ? DEFAULT_CSRF_HEADER : csrf;\n if (header !== null) {\n headers[header.name] = header.value;\n }\n return headers;\n}\n\n/** Build the transport-independent request. Exported so consumers can pin the write shape. */\nexport function buildUploadRequest(file: Blob, options: UploadImageOptions): UploadRequest {\n const form = new FormData();\n form.append(FILE_FIELD, file, resolveFileName(file, options.fileName));\n form.append(CATEGORY_FIELD, options.category ?? DEFAULT_CATEGORY);\n form.append(IS_PUBLIC_FIELD, String(options.isPublic ?? true));\n return {\n url: options.endpoint,\n headers: buildHeaders(options.csrf),\n form,\n credentials: options.credentials ?? 'include',\n signal: options.signal,\n };\n}\n\nfunction resolveXhrFactory(options: UploadImageOptions): (() => XMLHttpRequest) | undefined {\n if (options.fetchImpl !== undefined) {\n return undefined;\n }\n if (options.xhrFactory !== undefined) {\n return options.xhrFactory;\n }\n return typeof XMLHttpRequest === 'undefined' ? undefined : (): XMLHttpRequest => new XMLHttpRequest();\n}\n\nasync function send(request: UploadRequest, options: UploadImageOptions): Promise<string> {\n const { onProgress } = options;\n const createXhr = onProgress === undefined ? undefined : resolveXhrFactory(options);\n if (createXhr !== undefined && onProgress !== undefined) {\n return sendWithXhr(request, createXhr, onProgress);\n }\n const fetchImpl = options.fetchImpl ?? globalThis.fetch.bind(globalThis);\n return sendWithFetch(request, fetchImpl);\n}\n\n/**\n * Upload one image as multipart/form-data (`File`, `Category`, `IsPublic`) to a\n * consumer-supplied endpoint and return the parsed `{ contentId, status?, url }`.\n *\n * Never sets Content-Type: the runtime adds the multipart boundary, and a JSON content type\n * on a FormData body is a 415 (see `@dloizides/bff-web-client` writeContentTypeGuard).\n *\n * @throws {UploadError} for every failure; switch on `error.kind`.\n */\nexport async function uploadImage(file: Blob, options: UploadImageOptions): Promise<UploadedContent> {\n validate(file, options);\n const request = buildUploadRequest(file, options);\n const text = await send(request, options);\n return parseUploadedContent(text, options.requireUrl ?? options.isPublic ?? true);\n}\n","import { UploadError } from './UploadError';\nimport { UploadErrorKind } from './UploadErrorKind';\n\n/**\n * Read a local URI (`blob:`, `data:`, or an RN image-picker `file:` URI) into a Blob so it\n * can be passed to `uploadImage`.\n *\n * @throws {UploadError} `Validation` when the URI cannot be read.\n */\nexport async function blobFromUri(uri: string, fetchImpl?: typeof fetch): Promise<Blob> {\n const doFetch = fetchImpl ?? globalThis.fetch.bind(globalThis);\n let response: Response;\n try {\n response = await doFetch(uri);\n } catch (cause) {\n const detail = cause instanceof Error ? cause.message : String(cause);\n throw new UploadError(UploadErrorKind.Validation, `Could not read file: ${detail}`);\n }\n if (!response.ok) {\n throw new UploadError(UploadErrorKind.Validation, `Could not read file (${String(response.status)}).`);\n }\n return response.blob();\n}\n","/** Lifecycle of a single `useImageUpload` slot. */\nexport const enum UploadStatus {\n Idle = 'idle',\n Uploading = 'uploading',\n Error = 'error',\n Done = 'done',\n}\n","import { useCallback, useEffect, useReducer, useRef } from 'react';\n\nimport { PERCENT_COMPLETE } from './transports';\nimport { toUploadError } from './UploadError';\nimport { uploadImage } from './uploadImage';\nimport { UploadStatus } from './UploadStatus';\n\nimport type { UploadedContent, UploadImageOptions } from './types';\nimport type { UploadError } from './UploadError';\n\n/** Options for `useImageUpload`: everything `uploadImage` takes except the per-call wiring. */\nexport interface UseImageUploadOptions extends Omit<UploadImageOptions, 'signal' | 'onProgress'> {\n onSuccess?: (content: UploadedContent) => void;\n onError?: (error: UploadError) => void;\n}\n\nexport interface ImageUploadState {\n status: UploadStatus;\n /** 0-100. Stays 0 while uploading when the transport cannot report progress (fetch). */\n progress: number;\n error: UploadError | null;\n result: UploadedContent | null;\n}\n\nexport interface UseImageUploadResult extends ImageUploadState {\n /** Start an upload (aborting any in-flight one). Resolves to the content, or null on error/abort. */\n upload: (file: Blob, fileName?: string) => Promise<UploadedContent | null>;\n /** Abort any in-flight upload and return to idle. */\n reset: () => void;\n}\n\nconst IDLE: ImageUploadState = { status: UploadStatus.Idle, progress: 0, error: null, result: null };\nconst UPLOADING: ImageUploadState = { status: UploadStatus.Uploading, progress: 0, error: null, result: null };\n\ntype Action =\n | { type: 'start' }\n | { type: 'progress'; percent: number }\n | { type: 'done'; result: UploadedContent }\n | { type: 'fail'; error: UploadError }\n | { type: 'reset' };\n\nfunction reducer(state: ImageUploadState, action: Action): ImageUploadState {\n switch (action.type) {\n case 'start':\n return UPLOADING;\n case 'progress':\n return { ...state, progress: action.percent };\n case 'done':\n return { status: UploadStatus.Done, progress: PERCENT_COMPLETE, error: null, result: action.result };\n case 'fail':\n return { ...state, status: UploadStatus.Error, error: action.error };\n default:\n return IDLE;\n }\n}\n\n/**\n * One image-upload slot: idle -> uploading -> done | error. Progress is reported when the\n * XMLHttpRequest transport is available (browser, React Native) and no `fetchImpl` is set.\n * A newer `upload()` or `reset()` supersedes the previous request; its late result is dropped.\n * Unmount aborts the in-flight request.\n */\nexport function useImageUpload(options: UseImageUploadOptions): UseImageUploadResult {\n const [state, dispatch] = useReducer(reducer, IDLE);\n const optionsRef = useRef(options);\n optionsRef.current = options;\n const controllerRef = useRef<AbortController | null>(null);\n const requestIdRef = useRef(0);\n\n const abortInFlight = useCallback((): void => {\n controllerRef.current?.abort();\n controllerRef.current = null;\n requestIdRef.current += 1;\n }, []);\n\n useEffect(() => abortInFlight, [abortInFlight]);\n\n const upload = useCallback(async (file: Blob, fileName?: string): Promise<UploadedContent | null> => {\n abortInFlight();\n const controller = new AbortController();\n controllerRef.current = controller;\n const requestId = requestIdRef.current;\n const isCurrent = (): boolean => requestId === requestIdRef.current;\n const { onSuccess, onError, ...uploadOptions } = optionsRef.current;\n dispatch({ type: 'start' });\n try {\n const result = await uploadImage(file, {\n ...uploadOptions,\n fileName: fileName ?? uploadOptions.fileName,\n signal: controller.signal,\n onProgress: (percent) => {\n if (isCurrent()) {\n dispatch({ type: 'progress', percent });\n }\n },\n });\n if (!isCurrent()) {\n return null;\n }\n dispatch({ type: 'done', result });\n onSuccess?.(result);\n return result;\n } catch (cause) {\n if (!isCurrent()) {\n return null;\n }\n const error = toUploadError(cause);\n dispatch({ type: 'fail', error });\n onError?.(error);\n return null;\n }\n }, [abortInFlight]);\n\n const reset = useCallback((): void => {\n abortInFlight();\n dispatch({ type: 'reset' });\n }, [abortInFlight]);\n\n return { ...state, upload, reset };\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/UploadErrorKind.ts","../src/UploadError.ts","../src/parseUploadedContent.ts","../src/transports.ts","../src/uploadImage.ts","../src/uploadFormWithProgress.ts","../src/blobFromUri.ts","../src/UploadStatus.ts","../src/useImageUpload.ts"],"names":["UploadErrorKind","buildHeaders","UploadStatus"],"mappings":"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","file":"index.mjs","sourcesContent":["/** Why an upload failed. Consumers map this to translated copy; `message` is developer-facing only. */\nexport const enum UploadErrorKind {\n /** Rejected client-side before any request: empty file, too large, disallowed MIME type. */\n Validation = 'validation',\n /** The request never produced an HTTP response (offline, DNS, CORS). */\n Network = 'network',\n /** The server answered outside 2xx; `status` carries the code. */\n Http = 'http',\n /** The caller aborted (AbortSignal / hook reset). */\n Aborted = 'aborted',\n /** 2xx, but the body was not `{ contentId, status?, url? }` (or lacked a required url). */\n InvalidResponse = 'invalid-response',\n}\n","import { UploadErrorKind } from './UploadErrorKind';\r\n\r\n/** The single error type thrown by `uploadImage` and surfaced by `useImageUpload`. */\r\nexport class UploadError extends Error {\r\n readonly kind: UploadErrorKind;\r\n /** HTTP status for `UploadErrorKind.Http`; undefined otherwise. */\r\n readonly status: number | undefined;\r\n /** Raw response body for `UploadErrorKind.Http`; undefined otherwise. */\r\n readonly body: string | undefined;\r\n\r\n constructor(kind: UploadErrorKind, message: string, status?: number, body?: string) {\r\n super(message);\r\n this.name = 'UploadError';\r\n this.kind = kind;\r\n this.status = status;\r\n this.body = body;\r\n }\r\n}\r\n\r\n/** Normalise anything thrown into an `UploadError` (unknown throws become `Network`). */\r\nexport function toUploadError(cause: unknown): UploadError {\r\n if (cause instanceof UploadError) {\r\n return cause;\r\n }\r\n const message = cause instanceof Error ? cause.message : String(cause);\r\n return new UploadError(UploadErrorKind.Network, message);\r\n}\r\n","import { UploadError } from './UploadError';\nimport { UploadErrorKind } from './UploadErrorKind';\n\nimport type { UploadedContent } from './types';\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction invalid(message: string): UploadError {\n return new UploadError(UploadErrorKind.InvalidResponse, message);\n}\n\n/** Parse the upload endpoint's JSON body into `UploadedContent`, or throw `InvalidResponse`. */\nexport function parseUploadedContent(text: string, requireUrl: boolean): UploadedContent {\n let payload: unknown;\n try {\n payload = JSON.parse(text);\n } catch {\n throw invalid('Upload response was not JSON.');\n }\n // `status` is optional: ContentService sends it, the Dloizides.Content.Upload.AspNetCore\n // proxy (`ContentUploadResponse(ContentId, Url)`) does not. Present-but-not-a-string is still malformed.\n const hasBadStatus = isRecord(payload) && payload.status !== undefined && typeof payload.status !== 'string';\n if (!isRecord(payload) || typeof payload.contentId !== 'string' || hasBadStatus) {\n throw invalid('Upload response shape was invalid.');\n }\n const status = typeof payload.status === 'string' ? payload.status : undefined;\n const url = typeof payload.url === 'string' && payload.url !== '' ? payload.url : undefined;\n if (requireUrl && url === undefined) {\n throw invalid('Upload response did not include a public URL.');\n }\n return { contentId: payload.contentId, status, url };\n}\n","import { UploadError } from './UploadError';\r\nimport { UploadErrorKind } from './UploadErrorKind';\r\n\r\nimport type { ProgressListener } from './types';\r\n\r\nconst HTTP_OK_MIN = 200;\r\nconst HTTP_OK_MAX_EXCLUSIVE = 300;\r\nexport const PERCENT_COMPLETE = 100;\r\n\r\n/** A fully built multipart upload, independent of the transport that sends it. */\r\nexport interface UploadRequest {\r\n url: string;\r\n /** Never contains Content-Type: the runtime sets the multipart boundary for a FormData body. */\r\n headers: Record<string, string>;\r\n form: FormData;\r\n credentials: RequestCredentials;\r\n signal: AbortSignal | undefined;\r\n}\r\n\r\nfunction isOk(status: number): boolean {\r\n return status >= HTTP_OK_MIN && status < HTTP_OK_MAX_EXCLUSIVE;\r\n}\r\n\r\nfunction httpError(status: number, body: string): UploadError {\r\n return new UploadError(UploadErrorKind.Http, `Upload failed (${String(status)}).`, status, body);\r\n}\r\n\r\nasync function readBodySafely(response: Response): Promise<string> {\r\n try {\r\n return await response.text();\r\n } catch {\r\n return '';\r\n }\r\n}\r\n\r\nfunction abortedError(): UploadError {\r\n return new UploadError(UploadErrorKind.Aborted, 'Upload was cancelled.');\r\n}\r\n\r\n/** POST with fetch; resolves to the raw response text of a 2xx. */\r\nexport async function sendWithFetch(request: UploadRequest, fetchImpl: typeof fetch): Promise<string> {\r\n let response: Response;\r\n try {\r\n response = await fetchImpl(request.url, {\r\n method: 'POST',\r\n credentials: request.credentials,\r\n headers: request.headers,\r\n body: request.form,\r\n signal: request.signal,\r\n });\r\n } catch (cause) {\r\n if (request.signal?.aborted === true) {\r\n throw abortedError();\r\n }\r\n const detail = cause instanceof Error ? cause.message : String(cause);\r\n throw new UploadError(UploadErrorKind.Network, detail);\r\n }\r\n if (!isOk(response.status)) {\r\n throw httpError(response.status, await readBodySafely(response));\r\n }\r\n return response.text();\r\n}\r\n\r\nfunction reportProgress(event: ProgressEvent, onProgress: ProgressListener): void {\r\n if (event.lengthComputable && event.total > 0) {\r\n onProgress(Math.round((event.loaded / event.total) * PERCENT_COMPLETE));\r\n }\r\n}\r\n\r\n/** POST with XMLHttpRequest so upload progress can be observed. */\r\nexport function sendWithXhr(\r\n request: UploadRequest,\r\n createXhr: () => XMLHttpRequest,\r\n onProgress: ProgressListener,\r\n): Promise<string> {\r\n return new Promise((resolve, reject) => {\r\n const { signal } = request;\r\n if (signal?.aborted === true) {\r\n reject(abortedError());\r\n return;\r\n }\r\n const xhr = createXhr();\r\n xhr.upload.addEventListener('progress', (event) => reportProgress(event, onProgress));\r\n xhr.addEventListener('load', () => {\r\n if (isOk(xhr.status)) {\r\n resolve(xhr.responseText);\r\n } else {\r\n reject(httpError(xhr.status, xhr.responseText));\r\n }\r\n });\r\n xhr.addEventListener('error', () => reject(new UploadError(UploadErrorKind.Network, 'Upload failed (network error).')));\r\n xhr.addEventListener('abort', () => reject(abortedError()));\r\n signal?.addEventListener('abort', () => xhr.abort(), { once: true });\r\n xhr.open('POST', request.url);\r\n xhr.withCredentials = request.credentials === 'include';\r\n Object.entries(request.headers).forEach(([name, value]) => xhr.setRequestHeader(name, value));\r\n xhr.send(request.form);\r\n });\r\n}\r\n\r\n/** Transport selection knobs shared by every upload entry point. */\r\nexport interface TransportOptions {\r\n /** When set (and no `fetchImpl`), the upload goes over XMLHttpRequest to report progress. */\r\n onProgress?: ProgressListener;\r\n /** Custom fetch (tests, SSR). Forces the fetch transport. */\r\n fetchImpl?: typeof fetch;\r\n /** Custom XMLHttpRequest factory (tests). */\r\n xhrFactory?: () => XMLHttpRequest;\r\n}\r\n\r\nfunction resolveXhrFactory(options: TransportOptions): (() => XMLHttpRequest) | undefined {\r\n if (options.fetchImpl !== undefined) {\r\n return undefined;\r\n }\r\n if (options.xhrFactory !== undefined) {\r\n return options.xhrFactory;\r\n }\r\n return typeof XMLHttpRequest === 'undefined' ? undefined : (): XMLHttpRequest => new XMLHttpRequest();\r\n}\r\n\r\n/** Send over XMLHttpRequest when progress is wanted and available, else over fetch. */\r\nexport async function sendUploadRequest(request: UploadRequest, options: TransportOptions): Promise<string> {\r\n const { onProgress } = options;\r\n const createXhr = onProgress === undefined ? undefined : resolveXhrFactory(options);\r\n if (createXhr !== undefined && onProgress !== undefined) {\r\n return sendWithXhr(request, createXhr, onProgress);\r\n }\r\n const fetchImpl = options.fetchImpl ?? globalThis.fetch.bind(globalThis);\r\n return sendWithFetch(request, fetchImpl);\r\n}\r\n","import { parseUploadedContent } from './parseUploadedContent';\r\nimport { sendUploadRequest } from './transports';\r\nimport { UploadError } from './UploadError';\r\nimport { UploadErrorKind } from './UploadErrorKind';\r\n\r\nimport type { UploadRequest } from './transports';\r\nimport type { CsrfHeader, UploadedContent, UploadImageOptions } from './types';\r\n\r\n/** The Bff.AspNetCore anti-forgery convention: header presence, any value. */\r\nexport const DEFAULT_CSRF_HEADER: CsrfHeader = { name: 'X-BFF-Csrf', value: '1' };\r\n\r\nconst DEFAULT_CATEGORY = 'Image';\r\nconst DEFAULT_FILE_NAME = 'upload';\r\nconst FILE_FIELD = 'File';\r\nconst CATEGORY_FIELD = 'Category';\r\nconst IS_PUBLIC_FIELD = 'IsPublic';\r\n\r\nfunction validationError(message: string): UploadError {\r\n return new UploadError(UploadErrorKind.Validation, message);\r\n}\r\n\r\n/** Client-side checks that must fail before a byte leaves the device. */\r\nfunction validate(file: Blob, options: UploadImageOptions): void {\r\n if (file.size === 0) {\r\n throw validationError('File is empty.');\r\n }\r\n if (options.maxBytes !== undefined && file.size > options.maxBytes) {\r\n throw validationError(`File is ${String(file.size)} bytes; the limit is ${String(options.maxBytes)}.`);\r\n }\r\n if (options.allowedTypes !== undefined && !options.allowedTypes.includes(file.type)) {\r\n throw validationError(`File type \"${file.type}\" is not allowed.`);\r\n }\r\n}\r\n\r\nfunction resolveFileName(file: Blob, explicit: string | undefined): string {\r\n if (explicit !== undefined && explicit !== '') {\r\n return explicit;\r\n }\r\n if ('name' in file && typeof file.name === 'string' && file.name !== '') {\r\n return file.name;\r\n }\r\n return DEFAULT_FILE_NAME;\r\n}\r\n\r\nfunction buildHeaders(csrf: CsrfHeader | null | undefined): Record<string, string> {\r\n const headers: Record<string, string> = { Accept: 'application/json' };\r\n const header = csrf === undefined ? DEFAULT_CSRF_HEADER : csrf;\r\n if (header !== null) {\r\n headers[header.name] = header.value;\r\n }\r\n return headers;\r\n}\r\n\r\n/** Build the transport-independent request. Exported so consumers can pin the write shape. */\r\nexport function buildUploadRequest(file: Blob, options: UploadImageOptions): UploadRequest {\r\n const form = new FormData();\r\n form.append(FILE_FIELD, file, resolveFileName(file, options.fileName));\r\n form.append(CATEGORY_FIELD, options.category ?? DEFAULT_CATEGORY);\r\n form.append(IS_PUBLIC_FIELD, String(options.isPublic ?? true));\r\n return {\r\n url: options.endpoint,\r\n headers: buildHeaders(options.csrf),\r\n form,\r\n credentials: options.credentials ?? 'include',\r\n signal: options.signal,\r\n };\r\n}\r\n\r\n/**\r\n * Upload one image as multipart/form-data (`File`, `Category`, `IsPublic`) to a\r\n * consumer-supplied endpoint and return the parsed `{ contentId, status?, url }`.\r\n *\r\n * Never sets Content-Type: the runtime adds the multipart boundary, and a JSON content type\r\n * on a FormData body is a 415 (see `@dloizides/bff-web-client` writeContentTypeGuard).\r\n *\r\n * @throws {UploadError} for every failure; switch on `error.kind`.\r\n */\r\nexport async function uploadImage(file: Blob, options: UploadImageOptions): Promise<UploadedContent> {\r\n validate(file, options);\r\n const request = buildUploadRequest(file, options);\r\n const text = await sendUploadRequest(request, options);\r\n return parseUploadedContent(text, options.requireUrl ?? options.isPublic ?? true);\r\n}\r\n","import { sendUploadRequest } from './transports';\nimport { UploadError } from './UploadError';\nimport { UploadErrorKind } from './UploadErrorKind';\n\nimport type { UploadRequest } from './transports';\nimport type { UploadFormOptions } from './types';\n\nconst CONTENT_TYPE_HEADER = 'content-type';\nconst ACCEPT_HEADER = 'accept';\n\nfunction buildHeaders(extra: Record<string, string> | undefined): Record<string, string> {\n const headers: Record<string, string> = {};\n Object.entries(extra ?? {}).forEach(([name, value]) => {\n if (name.toLowerCase() !== CONTENT_TYPE_HEADER) {headers[name] = value;}\n });\n const hasAccept = Object.keys(headers).some((name) => name.toLowerCase() === ACCEPT_HEADER);\n if (!hasAccept) {headers.Accept = 'application/json';}\n return headers;\n}\n\nfunction buildForm(parts: Record<string, Blob | string>): FormData {\n const form = new FormData();\n Object.entries(parts).forEach(([name, value]) => form.append(name, value));\n return form;\n}\n\n/** Build the transport-independent request. Exported so consumers can pin the write shape. */\nexport function buildFormRequest(options: UploadFormOptions): UploadRequest {\n return {\n url: options.url,\n headers: buildHeaders(options.headers),\n form: buildForm(options.parts),\n credentials: options.withCredentials === false ? 'same-origin' : 'include',\n signal: options.signal,\n };\n}\n\nfunction parseJson<T>(text: string): T | null {\n if (text.trim() === '') {return null;}\n try {\n return JSON.parse(text) as T;\n } catch {\n throw new UploadError(UploadErrorKind.InvalidResponse, 'Upload response was not valid JSON.');\n }\n}\n\n/**\n * POST arbitrary multipart parts with optional upload progress; resolves the parsed JSON body\n * (`null` for an empty 2xx body).\n *\n * @throws {UploadError} `Http` carries `status` and the raw `body`; switch on `error.kind`.\n */\nexport async function uploadFormWithProgress<T = unknown>(options: UploadFormOptions): Promise<T | null> {\n const text = await sendUploadRequest(buildFormRequest(options), options);\n return parseJson<T>(text);\n}\n","import { UploadError } from './UploadError';\nimport { UploadErrorKind } from './UploadErrorKind';\n\n/**\n * Read a local URI (`blob:`, `data:`, or an RN image-picker `file:` URI) into a Blob so it\n * can be passed to `uploadImage`.\n *\n * @throws {UploadError} `Validation` when the URI cannot be read.\n */\nexport async function blobFromUri(uri: string, fetchImpl?: typeof fetch): Promise<Blob> {\n const doFetch = fetchImpl ?? globalThis.fetch.bind(globalThis);\n let response: Response;\n try {\n response = await doFetch(uri);\n } catch (cause) {\n const detail = cause instanceof Error ? cause.message : String(cause);\n throw new UploadError(UploadErrorKind.Validation, `Could not read file: ${detail}`);\n }\n if (!response.ok) {\n throw new UploadError(UploadErrorKind.Validation, `Could not read file (${String(response.status)}).`);\n }\n return response.blob();\n}\n","/** Lifecycle of a single `useImageUpload` slot. */\nexport const enum UploadStatus {\n Idle = 'idle',\n Uploading = 'uploading',\n Error = 'error',\n Done = 'done',\n}\n","import { useCallback, useEffect, useReducer, useRef } from 'react';\n\nimport { PERCENT_COMPLETE } from './transports';\nimport { toUploadError } from './UploadError';\nimport { uploadImage } from './uploadImage';\nimport { UploadStatus } from './UploadStatus';\n\nimport type { UploadedContent, UploadImageOptions } from './types';\nimport type { UploadError } from './UploadError';\n\n/** Options for `useImageUpload`: everything `uploadImage` takes except the per-call wiring. */\nexport interface UseImageUploadOptions extends Omit<UploadImageOptions, 'signal' | 'onProgress'> {\n onSuccess?: (content: UploadedContent) => void;\n onError?: (error: UploadError) => void;\n}\n\nexport interface ImageUploadState {\n status: UploadStatus;\n /** 0-100. Stays 0 while uploading when the transport cannot report progress (fetch). */\n progress: number;\n error: UploadError | null;\n result: UploadedContent | null;\n}\n\nexport interface UseImageUploadResult extends ImageUploadState {\n /** Start an upload (aborting any in-flight one). Resolves to the content, or null on error/abort. */\n upload: (file: Blob, fileName?: string) => Promise<UploadedContent | null>;\n /** Abort any in-flight upload and return to idle. */\n reset: () => void;\n}\n\nconst IDLE: ImageUploadState = { status: UploadStatus.Idle, progress: 0, error: null, result: null };\nconst UPLOADING: ImageUploadState = { status: UploadStatus.Uploading, progress: 0, error: null, result: null };\n\ntype Action =\n | { type: 'start' }\n | { type: 'progress'; percent: number }\n | { type: 'done'; result: UploadedContent }\n | { type: 'fail'; error: UploadError }\n | { type: 'reset' };\n\nfunction reducer(state: ImageUploadState, action: Action): ImageUploadState {\n switch (action.type) {\n case 'start':\n return UPLOADING;\n case 'progress':\n return { ...state, progress: action.percent };\n case 'done':\n return { status: UploadStatus.Done, progress: PERCENT_COMPLETE, error: null, result: action.result };\n case 'fail':\n return { ...state, status: UploadStatus.Error, error: action.error };\n default:\n return IDLE;\n }\n}\n\n/**\n * One image-upload slot: idle -> uploading -> done | error. Progress is reported when the\n * XMLHttpRequest transport is available (browser, React Native) and no `fetchImpl` is set.\n * A newer `upload()` or `reset()` supersedes the previous request; its late result is dropped.\n * Unmount aborts the in-flight request.\n */\nexport function useImageUpload(options: UseImageUploadOptions): UseImageUploadResult {\n const [state, dispatch] = useReducer(reducer, IDLE);\n const optionsRef = useRef(options);\n optionsRef.current = options;\n const controllerRef = useRef<AbortController | null>(null);\n const requestIdRef = useRef(0);\n\n const abortInFlight = useCallback((): void => {\n controllerRef.current?.abort();\n controllerRef.current = null;\n requestIdRef.current += 1;\n }, []);\n\n useEffect(() => abortInFlight, [abortInFlight]);\n\n const upload = useCallback(async (file: Blob, fileName?: string): Promise<UploadedContent | null> => {\n abortInFlight();\n const controller = new AbortController();\n controllerRef.current = controller;\n const requestId = requestIdRef.current;\n const isCurrent = (): boolean => requestId === requestIdRef.current;\n const { onSuccess, onError, ...uploadOptions } = optionsRef.current;\n dispatch({ type: 'start' });\n try {\n const result = await uploadImage(file, {\n ...uploadOptions,\n fileName: fileName ?? uploadOptions.fileName,\n signal: controller.signal,\n onProgress: (percent) => {\n if (isCurrent()) {\n dispatch({ type: 'progress', percent });\n }\n },\n });\n if (!isCurrent()) {\n return null;\n }\n dispatch({ type: 'done', result });\n onSuccess?.(result);\n return result;\n } catch (cause) {\n if (!isCurrent()) {\n return null;\n }\n const error = toUploadError(cause);\n dispatch({ type: 'fail', error });\n onError?.(error);\n return null;\n }\n }, [abortInFlight]);\n\n const reset = useCallback((): void => {\n abortInFlight();\n dispatch({ type: 'reset' });\n }, [abortInFlight]);\n\n return { ...state, upload, reset };\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dloizides/content-upload",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "Framework-light image upload client for the dloizides.com ContentService (multipart POST through a same-origin BFF proxy, CSRF header, abort, optional progress) plus a useImageUpload React hook with idle/uploading/error/done states. The endpoint is supplied by the consumer.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"upload",
|