@dloizides/content-upload 1.2.0 → 1.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +11 -1
- package/README.md +3 -1
- package/dist/index.d.mts +10 -3
- package/dist/index.d.ts +10 -3
- package/dist/index.js +21 -7
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +21 -8
- 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.2.1
|
|
4
|
+
|
|
5
|
+
RUPEP-5 "Upload the RuPEP file from the Jobs tab" -- review fix: no cross-reload resume for gzip.
|
|
6
|
+
|
|
7
|
+
- A gzip (deferred-length) upload no longer stores a resume fingerprint and never looks up previous uploads.
|
|
8
|
+
A reload restarts `CompressionStream` at byte 0 and its output is not guaranteed byte-identical across
|
|
9
|
+
browsers or versions, so appending to the old server upload could corrupt the file. In-session chunk
|
|
10
|
+
retries (tus `retryDelays`) still apply.
|
|
11
|
+
- Identity uploads keep cross-reload resume (fingerprint stored, newest previous upload resumed).
|
|
12
|
+
- New `isResumableAcrossReload(gzip?, runtime?)` so a UI can say whether a reload will resume or restart.
|
|
13
|
+
|
|
3
14
|
## 1.2.0
|
|
4
15
|
|
|
5
16
|
RUPEP-5 "Upload the RuPEP file from the Jobs tab" -- resumable tus upload.
|
|
@@ -10,7 +21,6 @@ RUPEP-5 "Upload the RuPEP file from the Jobs tab" -- resumable tus upload.
|
|
|
10
21
|
- `gzip: true` compresses with `CompressionStream` as a stream and uses tus deferred length (`encoding=gzip`).
|
|
11
22
|
Where `CompressionStream` is missing the file is uploaded as-is with `encoding=identity`.
|
|
12
23
|
- Resume: a fingerprint of endpoint + size + lastModified + metadata is stored; the newest stored upload is resumed.
|
|
13
|
-
A gzip resume re-compresses from the start and skips the bytes already on the server (same engine = same bytes).
|
|
14
24
|
- `signal` abort terminates the server upload (tus termination) and rejects with `UploadErrorKind.Aborted`.
|
|
15
25
|
- Progress for gzip uploads counts source bytes read and holds at 99 until the upload finishes.
|
|
16
26
|
|
package/README.md
CHANGED
|
@@ -97,5 +97,7 @@ const id = await uploadResumable({
|
|
|
97
97
|
- tus 1.0 via `tus-js-client`; 16 MiB chunks; cookies sent by default.
|
|
98
98
|
- `gzip: true` streams through `CompressionStream` with deferred length (`encoding=gzip`); without
|
|
99
99
|
`CompressionStream` the file goes up uncompressed (`encoding=identity`). The server reads `encoding`.
|
|
100
|
-
-
|
|
100
|
+
- Identity uploads interrupted by a reload resume from a stored fingerprint on the next call with the same
|
|
101
|
+
file and metadata. Gzip uploads do not (re-compressed bytes are not guaranteed identical); they retry failed
|
|
102
|
+
chunks in-session only. `isResumableAcrossReload(gzip)` tells a UI which applies.
|
|
101
103
|
- Aborting terminates the server-side upload.
|
package/dist/index.d.mts
CHANGED
|
@@ -173,6 +173,11 @@ declare const enum UploadEncoding {
|
|
|
173
173
|
|
|
174
174
|
/** The read side of a byte stream, as tus-js-client accepts it. */
|
|
175
175
|
type ByteReader = Pick<ReadableStreamDefaultReader<Uint8Array>, 'read'>;
|
|
176
|
+
/** The stream constructors gzip needs; `CompressionStream` is absent on older runtimes. */
|
|
177
|
+
interface StreamRuntime {
|
|
178
|
+
CompressionStream?: typeof CompressionStream;
|
|
179
|
+
TransformStream?: typeof TransformStream;
|
|
180
|
+
}
|
|
176
181
|
/** What tus-js-client uploads, and how its progress maps back to the source file. */
|
|
177
182
|
interface ResumableSource {
|
|
178
183
|
input: Blob | ByteReader;
|
|
@@ -180,6 +185,8 @@ interface ResumableSource {
|
|
|
180
185
|
lengthDeferred: boolean;
|
|
181
186
|
sourceBytesRead: () => number;
|
|
182
187
|
}
|
|
188
|
+
/** True when an interrupted upload can resume after a page reload: identity only, never a gzip stream. */
|
|
189
|
+
declare function isResumableAcrossReload(gzip?: boolean, runtime?: StreamRuntime): boolean;
|
|
183
190
|
|
|
184
191
|
/** The part of a tus-js-client `Upload` that `uploadResumable` drives. */
|
|
185
192
|
interface TusUpload {
|
|
@@ -198,7 +205,7 @@ interface ResumableUploadOptions {
|
|
|
198
205
|
file: Blob;
|
|
199
206
|
/** tus Upload-Metadata; `filename` defaults to `File.name`; `encoding` is always set by the client. */
|
|
200
207
|
metadata?: Record<string, string>;
|
|
201
|
-
/** Gzip
|
|
208
|
+
/** Gzip as a stream (deferred length; no resume after reload); identity when `CompressionStream` is missing. */
|
|
202
209
|
gzip?: boolean;
|
|
203
210
|
/** PATCH body size in bytes. Default 16 MiB (under the BFF 30 MB body limit). */
|
|
204
211
|
chunkSize?: number;
|
|
@@ -224,7 +231,7 @@ declare function buildResumableMetadata(file: Blob, metadata: Record<string, str
|
|
|
224
231
|
declare function resumeFingerprint(endpoint: string, file: Blob, metadata: Record<string, string>): string;
|
|
225
232
|
/** The tus-js-client options for one upload. Exported so consumers can pin the request shape. */
|
|
226
233
|
declare function buildTusOptions(options: ResumableUploadOptions, source: ResumableSource, callbacks: UploadCallbacks): UploadOptions;
|
|
227
|
-
/** tus 1.0
|
|
234
|
+
/** tus 1.0 upload (16 MiB chunks, optional streamed gzip); identity resumes across reloads, gzip only in-session. */
|
|
228
235
|
declare function uploadResumable(options: ResumableUploadOptions): Promise<string>;
|
|
229
236
|
|
|
230
|
-
export { type ByteReader, type CsrfHeader, DEFAULT_CSRF_HEADER, DEFAULT_RESUMABLE_CHUNK_SIZE, type ImageUploadState, type ProgressListener, type ResumableUploadOptions, type TransportOptions, type TusUpload, type TusUploadFactory, UploadEncoding, UploadError, UploadErrorKind, type UploadFormOptions, type UploadImageOptions, type UploadRequest, UploadStatus, type UploadedContent, type UseImageUploadOptions, type UseImageUploadResult, blobFromUri, buildFormRequest, buildResumableMetadata, buildTusOptions, buildUploadRequest, resumeFingerprint, toUploadError, uploadFormWithProgress, uploadImage, uploadResumable, useImageUpload };
|
|
237
|
+
export { type ByteReader, type CsrfHeader, DEFAULT_CSRF_HEADER, DEFAULT_RESUMABLE_CHUNK_SIZE, type ImageUploadState, type ProgressListener, type ResumableUploadOptions, type StreamRuntime, type TransportOptions, type TusUpload, type TusUploadFactory, UploadEncoding, UploadError, UploadErrorKind, type UploadFormOptions, type UploadImageOptions, type UploadRequest, UploadStatus, type UploadedContent, type UseImageUploadOptions, type UseImageUploadResult, blobFromUri, buildFormRequest, buildResumableMetadata, buildTusOptions, buildUploadRequest, isResumableAcrossReload, resumeFingerprint, toUploadError, uploadFormWithProgress, uploadImage, uploadResumable, useImageUpload };
|
package/dist/index.d.ts
CHANGED
|
@@ -173,6 +173,11 @@ declare const enum UploadEncoding {
|
|
|
173
173
|
|
|
174
174
|
/** The read side of a byte stream, as tus-js-client accepts it. */
|
|
175
175
|
type ByteReader = Pick<ReadableStreamDefaultReader<Uint8Array>, 'read'>;
|
|
176
|
+
/** The stream constructors gzip needs; `CompressionStream` is absent on older runtimes. */
|
|
177
|
+
interface StreamRuntime {
|
|
178
|
+
CompressionStream?: typeof CompressionStream;
|
|
179
|
+
TransformStream?: typeof TransformStream;
|
|
180
|
+
}
|
|
176
181
|
/** What tus-js-client uploads, and how its progress maps back to the source file. */
|
|
177
182
|
interface ResumableSource {
|
|
178
183
|
input: Blob | ByteReader;
|
|
@@ -180,6 +185,8 @@ interface ResumableSource {
|
|
|
180
185
|
lengthDeferred: boolean;
|
|
181
186
|
sourceBytesRead: () => number;
|
|
182
187
|
}
|
|
188
|
+
/** True when an interrupted upload can resume after a page reload: identity only, never a gzip stream. */
|
|
189
|
+
declare function isResumableAcrossReload(gzip?: boolean, runtime?: StreamRuntime): boolean;
|
|
183
190
|
|
|
184
191
|
/** The part of a tus-js-client `Upload` that `uploadResumable` drives. */
|
|
185
192
|
interface TusUpload {
|
|
@@ -198,7 +205,7 @@ interface ResumableUploadOptions {
|
|
|
198
205
|
file: Blob;
|
|
199
206
|
/** tus Upload-Metadata; `filename` defaults to `File.name`; `encoding` is always set by the client. */
|
|
200
207
|
metadata?: Record<string, string>;
|
|
201
|
-
/** Gzip
|
|
208
|
+
/** Gzip as a stream (deferred length; no resume after reload); identity when `CompressionStream` is missing. */
|
|
202
209
|
gzip?: boolean;
|
|
203
210
|
/** PATCH body size in bytes. Default 16 MiB (under the BFF 30 MB body limit). */
|
|
204
211
|
chunkSize?: number;
|
|
@@ -224,7 +231,7 @@ declare function buildResumableMetadata(file: Blob, metadata: Record<string, str
|
|
|
224
231
|
declare function resumeFingerprint(endpoint: string, file: Blob, metadata: Record<string, string>): string;
|
|
225
232
|
/** The tus-js-client options for one upload. Exported so consumers can pin the request shape. */
|
|
226
233
|
declare function buildTusOptions(options: ResumableUploadOptions, source: ResumableSource, callbacks: UploadCallbacks): UploadOptions;
|
|
227
|
-
/** tus 1.0
|
|
234
|
+
/** tus 1.0 upload (16 MiB chunks, optional streamed gzip); identity resumes across reloads, gzip only in-session. */
|
|
228
235
|
declare function uploadResumable(options: ResumableUploadOptions): Promise<string>;
|
|
229
236
|
|
|
230
|
-
export { type ByteReader, type CsrfHeader, DEFAULT_CSRF_HEADER, DEFAULT_RESUMABLE_CHUNK_SIZE, type ImageUploadState, type ProgressListener, type ResumableUploadOptions, type TransportOptions, type TusUpload, type TusUploadFactory, UploadEncoding, UploadError, UploadErrorKind, type UploadFormOptions, type UploadImageOptions, type UploadRequest, UploadStatus, type UploadedContent, type UseImageUploadOptions, type UseImageUploadResult, blobFromUri, buildFormRequest, buildResumableMetadata, buildTusOptions, buildUploadRequest, resumeFingerprint, toUploadError, uploadFormWithProgress, uploadImage, uploadResumable, useImageUpload };
|
|
237
|
+
export { type ByteReader, type CsrfHeader, DEFAULT_CSRF_HEADER, DEFAULT_RESUMABLE_CHUNK_SIZE, type ImageUploadState, type ProgressListener, type ResumableUploadOptions, type StreamRuntime, type TransportOptions, type TusUpload, type TusUploadFactory, UploadEncoding, UploadError, UploadErrorKind, type UploadFormOptions, type UploadImageOptions, type UploadRequest, UploadStatus, type UploadedContent, type UseImageUploadOptions, type UseImageUploadResult, blobFromUri, buildFormRequest, buildResumableMetadata, buildTusOptions, buildUploadRequest, isResumableAcrossReload, resumeFingerprint, toUploadError, uploadFormWithProgress, uploadImage, uploadResumable, useImageUpload };
|
package/dist/index.js
CHANGED
|
@@ -360,12 +360,23 @@ var MAX_PERCENT_BEFORE_SUCCESS = 99;
|
|
|
360
360
|
function identitySource(file) {
|
|
361
361
|
return { input: file, encoding: "identity" /* Identity */, lengthDeferred: false, sourceBytesRead: () => 0 };
|
|
362
362
|
}
|
|
363
|
-
function
|
|
363
|
+
function gzipStreams(gzip, runtime) {
|
|
364
364
|
const Compression = runtime.CompressionStream;
|
|
365
365
|
const Transform = runtime.TransformStream;
|
|
366
366
|
if (!gzip || Compression === void 0 || Transform === void 0) {
|
|
367
|
+
return void 0;
|
|
368
|
+
}
|
|
369
|
+
return { Compression, Transform };
|
|
370
|
+
}
|
|
371
|
+
function isResumableAcrossReload(gzip = false, runtime = globalThis) {
|
|
372
|
+
return gzipStreams(gzip, runtime) === void 0;
|
|
373
|
+
}
|
|
374
|
+
function openResumableSource(file, gzip, runtime = globalThis) {
|
|
375
|
+
const streams = gzipStreams(gzip, runtime);
|
|
376
|
+
if (streams === void 0) {
|
|
367
377
|
return identitySource(file);
|
|
368
378
|
}
|
|
379
|
+
const { Compression, Transform } = streams;
|
|
369
380
|
let read = 0;
|
|
370
381
|
const counter = new Transform({
|
|
371
382
|
transform(chunk, controller) {
|
|
@@ -414,7 +425,7 @@ function buildTusOptions(options, source, callbacks) {
|
|
|
414
425
|
headers: { ...options.headers },
|
|
415
426
|
uploadLengthDeferred: source.lengthDeferred,
|
|
416
427
|
fingerprint: () => Promise.resolve(fingerprint),
|
|
417
|
-
storeFingerprintForResuming:
|
|
428
|
+
storeFingerprintForResuming: !source.lengthDeferred,
|
|
418
429
|
removeFingerprintOnSuccess: true,
|
|
419
430
|
onBeforeRequest: options.withCredentials === false ? void 0 : sendCookies,
|
|
420
431
|
...callbacks
|
|
@@ -439,10 +450,12 @@ function toResumableError(error) {
|
|
|
439
450
|
function newestPreviousUpload(previous) {
|
|
440
451
|
return [...previous].sort((a, b) => b.creationTime.localeCompare(a.creationTime))[0];
|
|
441
452
|
}
|
|
442
|
-
async function startOrResume(upload) {
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
453
|
+
async function startOrResume(upload, source) {
|
|
454
|
+
if (!source.lengthDeferred) {
|
|
455
|
+
const previous = newestPreviousUpload(await upload.findPreviousUploads());
|
|
456
|
+
if (previous !== void 0) {
|
|
457
|
+
upload.resumeFromPreviousUpload(previous);
|
|
458
|
+
}
|
|
446
459
|
}
|
|
447
460
|
upload.start();
|
|
448
461
|
}
|
|
@@ -477,7 +490,7 @@ function uploadResumable(options) {
|
|
|
477
490
|
upload.abort(true).catch(() => void 0);
|
|
478
491
|
}
|
|
479
492
|
options.signal?.addEventListener("abort", onAbort, { once: true });
|
|
480
|
-
startOrResume(upload).catch((error) => settle(() => reject(toUploadError(error))));
|
|
493
|
+
startOrResume(upload, source).catch((error) => settle(() => reject(toUploadError(error))));
|
|
481
494
|
});
|
|
482
495
|
}
|
|
483
496
|
|
|
@@ -492,6 +505,7 @@ exports.buildFormRequest = buildFormRequest;
|
|
|
492
505
|
exports.buildResumableMetadata = buildResumableMetadata;
|
|
493
506
|
exports.buildTusOptions = buildTusOptions;
|
|
494
507
|
exports.buildUploadRequest = buildUploadRequest;
|
|
508
|
+
exports.isResumableAcrossReload = isResumableAcrossReload;
|
|
495
509
|
exports.resumeFingerprint = resumeFingerprint;
|
|
496
510
|
exports.toUploadError = toUploadError;
|
|
497
511
|
exports.uploadFormWithProgress = uploadFormWithProgress;
|
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/uploadFormWithProgress.ts","../src/blobFromUri.ts","../src/UploadStatus.ts","../src/useImageUpload.ts","../src/UploadEncoding.ts","../src/resumableSource.ts","../src/uploadResumable.ts"],"names":["UploadErrorKind","buildHeaders","UploadStatus","useReducer","useRef","useCallback","useEffect","UploadEncoding","Upload","abortedError"],"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","/** Value of the tus `encoding` metadata entry: how the uploaded bytes are encoded. */\nexport const enum UploadEncoding {\n Gzip = 'gzip',\n Identity = 'identity',\n}\n","import { UploadEncoding } from './UploadEncoding';\n\nconst PERCENT_SCALE = 100;\nconst MAX_PERCENT_BEFORE_SUCCESS = 99;\n\n/** The read side of a byte stream, as tus-js-client accepts it. */\nexport type ByteReader = Pick<ReadableStreamDefaultReader<Uint8Array>, 'read'>;\n\n/** The stream constructors gzip needs; `CompressionStream` is absent on older runtimes. */\nexport interface StreamRuntime {\n CompressionStream?: typeof CompressionStream;\n TransformStream?: typeof TransformStream;\n}\n\n/** What tus-js-client uploads, and how its progress maps back to the source file. */\nexport interface ResumableSource {\n input: Blob | ByteReader;\n encoding: UploadEncoding;\n lengthDeferred: boolean;\n sourceBytesRead: () => number;\n}\n\nfunction identitySource(file: Blob): ResumableSource {\n return { input: file, encoding: UploadEncoding.Identity, lengthDeferred: false, sourceBytesRead: () => 0 };\n}\n\n/** Gzip the file as a stream when asked and supported; otherwise upload it as-is (identity). */\nexport function openResumableSource(file: Blob, gzip: boolean, runtime: StreamRuntime = globalThis): ResumableSource {\n const Compression = runtime.CompressionStream;\n const Transform = runtime.TransformStream;\n if (!gzip || Compression === undefined || Transform === undefined) {return identitySource(file);}\n let read = 0;\n const counter = new Transform<Uint8Array<ArrayBuffer>, Uint8Array<ArrayBuffer>>({\n transform(chunk, controller): void {\n read += chunk.byteLength;\n controller.enqueue(chunk);\n },\n });\n const reader = file.stream().pipeThrough(counter).pipeThrough(new Compression('gzip')).getReader();\n return { input: reader, encoding: UploadEncoding.Gzip, lengthDeferred: true, sourceBytesRead: () => read };\n}\n\n/** Whole-number percent; a deferred-length (gzip) upload counts source bytes read and holds at 99 until success. */\nexport function resumablePercent(source: ResumableSource, fileSize: number, bytesSent: number, bytesTotal: number): number {\n if (!source.lengthDeferred) {return Math.floor((bytesSent * PERCENT_SCALE) / Math.max(bytesTotal, 1));}\n const percent = Math.floor((source.sourceBytesRead() * PERCENT_SCALE) / Math.max(fileSize, 1));\n return Math.min(percent, MAX_PERCENT_BEFORE_SUCCESS);\n}\n","import { Upload } from 'tus-js-client';\n\nimport { openResumableSource, resumablePercent } from './resumableSource';\nimport { PERCENT_COMPLETE } from './transports';\nimport { UploadError, toUploadError } from './UploadError';\nimport { UploadErrorKind } from './UploadErrorKind';\n\nimport type { HttpRequest, PreviousUpload, UploadOptions } from 'tus-js-client';\nimport type { ResumableSource } from './resumableSource';\nimport type { ResumableUploadOptions, TusUpload, TusUploadFactory } from './resumableTypes';\nimport type { UploadEncoding } from './UploadEncoding';\n\nconst MEBIBYTE = 1024 * 1024;\nconst DEFAULT_CHUNK_MEBIBYTES = 16;\nexport const DEFAULT_RESUMABLE_CHUNK_SIZE = DEFAULT_CHUNK_MEBIBYTES * MEBIBYTE;\n\nexport const createTusUpload: TusUploadFactory = (input, options) => new Upload(input, options);\n\ninterface UploadCallbacks {\n onProgress: (bytesSent: number, bytesTotal: number) => void;\n onSuccess: () => void;\n onError: (error: Error) => void;\n}\n\ninterface TusResponse {\n getStatus(): number;\n getBody(): string;\n}\n\n/** Upload-Metadata sent on creation: `filename` from the File unless given, `encoding` always from the source. */\nexport function buildResumableMetadata(file: Blob, metadata: Record<string, string> | undefined, encoding: UploadEncoding): Record<string, string> {\n const defaults: Record<string, string> = file instanceof File ? { filename: file.name } : {};\n return { ...defaults, ...metadata, encoding };\n}\n\n/** Stable resume key: the same endpoint, file identity and metadata resume the same tus upload. */\nexport function resumeFingerprint(endpoint: string, file: Blob, metadata: Record<string, string>): string {\n const lastModified = file instanceof File ? file.lastModified : 0;\n const entries = Object.entries(metadata).sort(([a], [b]) => a.localeCompare(b)).map(([key, value]) => `${key}=${value}`);\n return ['content-upload', endpoint, String(file.size), String(lastModified), entries.join('&')].join('|');\n}\n\nfunction sendCookies(request: HttpRequest): void {\n const underlying = request.getUnderlyingObject() as { withCredentials?: boolean } | null;\n if (underlying !== null && typeof underlying === 'object') {underlying.withCredentials = true;}\n}\n\n/** The tus-js-client options for one upload. Exported so consumers can pin the request shape. */\nexport function buildTusOptions(options: ResumableUploadOptions, source: ResumableSource, callbacks: UploadCallbacks): UploadOptions {\n const metadata = buildResumableMetadata(options.file, options.metadata, source.encoding);\n const fingerprint = resumeFingerprint(options.endpoint, options.file, metadata);\n return {\n endpoint: options.endpoint,\n chunkSize: options.chunkSize ?? DEFAULT_RESUMABLE_CHUNK_SIZE,\n metadata,\n headers: { ...options.headers },\n uploadLengthDeferred: source.lengthDeferred,\n fingerprint: () => Promise.resolve(fingerprint),\n storeFingerprintForResuming: true,\n removeFingerprintOnSuccess: true,\n onBeforeRequest: options.withCredentials === false ? undefined : sendCookies,\n ...callbacks,\n };\n}\n\n/** The upload id is the last path segment of the tus upload URL (the BFF-rewritten Location). */\nexport function uploadIdFromUrl(url: string | null): string {\n const path = (url ?? '').split('?', 1).join('');\n const id = path.split('/').filter((segment) => segment !== '').pop();\n if (id === undefined) {throw new UploadError(UploadErrorKind.InvalidResponse, 'tus upload finished without an upload URL.');}\n return id;\n}\n\n/** Map a tus-js-client failure to `UploadError`: a server answer is `Http`, anything else `Network`. */\nexport function toResumableError(error: Error): UploadError {\n const response = (error as { originalResponse?: TusResponse | null }).originalResponse;\n if (response === undefined || response === null) {return toUploadError(error);}\n const status = response.getStatus();\n return new UploadError(UploadErrorKind.Http, `Upload failed (${String(status)}).`, status, response.getBody());\n}\n\n/** The previous upload with the newest `creationTime`, if any. */\nexport function newestPreviousUpload(previous: PreviousUpload[]): PreviousUpload | undefined {\n return [...previous].sort((a, b) => b.creationTime.localeCompare(a.creationTime))[0];\n}\n\nasync function startOrResume(upload: TusUpload): Promise<void> {\n const previous = newestPreviousUpload(await upload.findPreviousUploads());\n if (previous !== undefined) {upload.resumeFromPreviousUpload(previous);}\n upload.start();\n}\n\nfunction abortedError(): UploadError {\n return new UploadError(UploadErrorKind.Aborted, 'Upload was cancelled.');\n}\n\n/** tus 1.0 resumable upload (16 MiB chunks, optional streamed gzip, fingerprint resume); resolves the upload id. */\nexport function uploadResumable(options: ResumableUploadOptions): Promise<string> {\n if (options.signal?.aborted === true) {return Promise.reject(abortedError());}\n const source = openResumableSource(options.file, options.gzip === true);\n const factory = options.uploadFactory ?? createTusUpload;\n return new Promise<string>((resolve, reject) => {\n const settle = (finish: () => void): void => {\n options.signal?.removeEventListener('abort', onAbort);\n finish();\n };\n const upload = factory(source.input, buildTusOptions(options, source, {\n onProgress: (sent, total) => options.onProgress?.(resumablePercent(source, options.file.size, sent, total)),\n onSuccess: () => settle(() => {\n options.onProgress?.(PERCENT_COMPLETE);\n try { resolve(uploadIdFromUrl(upload.url)); } catch (error) { reject(error); }\n }),\n onError: (error) => settle(() => reject(toResumableError(error))),\n }));\n function onAbort(): void {\n settle(() => reject(abortedError()));\n upload.abort(true).catch(() => undefined);\n }\n options.signal?.addEventListener('abort', onAbort, { once: true });\n startOrResume(upload).catch((error: unknown) => settle(() => reject(toUploadError(error))));\n });\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","../src/UploadEncoding.ts","../src/resumableSource.ts","../src/uploadResumable.ts"],"names":["UploadErrorKind","buildHeaders","UploadStatus","useReducer","useRef","useCallback","useEffect","UploadEncoding","Upload","abortedError"],"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","/** Value of the tus `encoding` metadata entry: how the uploaded bytes are encoded. */\nexport const enum UploadEncoding {\n Gzip = 'gzip',\n Identity = 'identity',\n}\n","import { UploadEncoding } from './UploadEncoding';\n\nconst PERCENT_SCALE = 100;\nconst MAX_PERCENT_BEFORE_SUCCESS = 99;\n\n/** The read side of a byte stream, as tus-js-client accepts it. */\nexport type ByteReader = Pick<ReadableStreamDefaultReader<Uint8Array>, 'read'>;\n\n/** The stream constructors gzip needs; `CompressionStream` is absent on older runtimes. */\nexport interface StreamRuntime {\n CompressionStream?: typeof CompressionStream;\n TransformStream?: typeof TransformStream;\n}\n\n/** What tus-js-client uploads, and how its progress maps back to the source file. */\nexport interface ResumableSource {\n input: Blob | ByteReader;\n encoding: UploadEncoding;\n lengthDeferred: boolean;\n sourceBytesRead: () => number;\n}\n\nfunction identitySource(file: Blob): ResumableSource {\n return { input: file, encoding: UploadEncoding.Identity, lengthDeferred: false, sourceBytesRead: () => 0 };\n}\n\ninterface GzipStreams {\n Compression: typeof CompressionStream;\n Transform: typeof TransformStream;\n}\n\nfunction gzipStreams(gzip: boolean, runtime: StreamRuntime): GzipStreams | undefined {\n const Compression = runtime.CompressionStream;\n const Transform = runtime.TransformStream;\n if (!gzip || Compression === undefined || Transform === undefined) {return undefined;}\n return { Compression, Transform };\n}\n\n/** True when an interrupted upload can resume after a page reload: identity only, never a gzip stream. */\nexport function isResumableAcrossReload(gzip = false, runtime: StreamRuntime = globalThis): boolean {\n return gzipStreams(gzip, runtime) === undefined;\n}\n\n/** Gzip the file as a stream when asked and supported; otherwise upload it as-is (identity). */\nexport function openResumableSource(file: Blob, gzip: boolean, runtime: StreamRuntime = globalThis): ResumableSource {\n const streams = gzipStreams(gzip, runtime);\n if (streams === undefined) {return identitySource(file);}\n const { Compression, Transform } = streams;\n let read = 0;\n const counter = new Transform<Uint8Array<ArrayBuffer>, Uint8Array<ArrayBuffer>>({\n transform(chunk, controller): void {\n read += chunk.byteLength;\n controller.enqueue(chunk);\n },\n });\n const reader = file.stream().pipeThrough(counter).pipeThrough(new Compression('gzip')).getReader();\n return { input: reader, encoding: UploadEncoding.Gzip, lengthDeferred: true, sourceBytesRead: () => read };\n}\n\n/** Whole-number percent; a deferred-length (gzip) upload counts source bytes read and holds at 99 until success. */\nexport function resumablePercent(source: ResumableSource, fileSize: number, bytesSent: number, bytesTotal: number): number {\n if (!source.lengthDeferred) {return Math.floor((bytesSent * PERCENT_SCALE) / Math.max(bytesTotal, 1));}\n const percent = Math.floor((source.sourceBytesRead() * PERCENT_SCALE) / Math.max(fileSize, 1));\n return Math.min(percent, MAX_PERCENT_BEFORE_SUCCESS);\n}\n","import { Upload } from 'tus-js-client';\n\nimport { openResumableSource, resumablePercent } from './resumableSource';\nimport { PERCENT_COMPLETE } from './transports';\nimport { UploadError, toUploadError } from './UploadError';\nimport { UploadErrorKind } from './UploadErrorKind';\n\nimport type { HttpRequest, PreviousUpload, UploadOptions } from 'tus-js-client';\nimport type { ResumableSource } from './resumableSource';\nimport type { ResumableUploadOptions, TusUpload, TusUploadFactory } from './resumableTypes';\nimport type { UploadEncoding } from './UploadEncoding';\n\nconst MEBIBYTE = 1024 * 1024;\nconst DEFAULT_CHUNK_MEBIBYTES = 16;\nexport const DEFAULT_RESUMABLE_CHUNK_SIZE = DEFAULT_CHUNK_MEBIBYTES * MEBIBYTE;\n\nexport const createTusUpload: TusUploadFactory = (input, options) => new Upload(input, options);\n\ninterface UploadCallbacks {\n onProgress: (bytesSent: number, bytesTotal: number) => void;\n onSuccess: () => void;\n onError: (error: Error) => void;\n}\n\ninterface TusResponse {\n getStatus(): number;\n getBody(): string;\n}\n\n/** Upload-Metadata sent on creation: `filename` from the File unless given, `encoding` always from the source. */\nexport function buildResumableMetadata(file: Blob, metadata: Record<string, string> | undefined, encoding: UploadEncoding): Record<string, string> {\n const defaults: Record<string, string> = file instanceof File ? { filename: file.name } : {};\n return { ...defaults, ...metadata, encoding };\n}\n\n/** Stable resume key: the same endpoint, file identity and metadata resume the same tus upload. */\nexport function resumeFingerprint(endpoint: string, file: Blob, metadata: Record<string, string>): string {\n const lastModified = file instanceof File ? file.lastModified : 0;\n const entries = Object.entries(metadata).sort(([a], [b]) => a.localeCompare(b)).map(([key, value]) => `${key}=${value}`);\n return ['content-upload', endpoint, String(file.size), String(lastModified), entries.join('&')].join('|');\n}\n\nfunction sendCookies(request: HttpRequest): void {\n const underlying = request.getUnderlyingObject() as { withCredentials?: boolean } | null;\n if (underlying !== null && typeof underlying === 'object') {underlying.withCredentials = true;}\n}\n\n/** The tus-js-client options for one upload. Exported so consumers can pin the request shape. */\nexport function buildTusOptions(options: ResumableUploadOptions, source: ResumableSource, callbacks: UploadCallbacks): UploadOptions {\n const metadata = buildResumableMetadata(options.file, options.metadata, source.encoding);\n const fingerprint = resumeFingerprint(options.endpoint, options.file, metadata);\n return {\n endpoint: options.endpoint,\n chunkSize: options.chunkSize ?? DEFAULT_RESUMABLE_CHUNK_SIZE,\n metadata,\n headers: { ...options.headers },\n uploadLengthDeferred: source.lengthDeferred,\n fingerprint: () => Promise.resolve(fingerprint),\n storeFingerprintForResuming: !source.lengthDeferred,\n removeFingerprintOnSuccess: true,\n onBeforeRequest: options.withCredentials === false ? undefined : sendCookies,\n ...callbacks,\n };\n}\n\n/** The upload id is the last path segment of the tus upload URL (the BFF-rewritten Location). */\nexport function uploadIdFromUrl(url: string | null): string {\n const path = (url ?? '').split('?', 1).join('');\n const id = path.split('/').filter((segment) => segment !== '').pop();\n if (id === undefined) {throw new UploadError(UploadErrorKind.InvalidResponse, 'tus upload finished without an upload URL.');}\n return id;\n}\n\n/** Map a tus-js-client failure to `UploadError`: a server answer is `Http`, anything else `Network`. */\nexport function toResumableError(error: Error): UploadError {\n const response = (error as { originalResponse?: TusResponse | null }).originalResponse;\n if (response === undefined || response === null) {return toUploadError(error);}\n const status = response.getStatus();\n return new UploadError(UploadErrorKind.Http, `Upload failed (${String(status)}).`, status, response.getBody());\n}\n\n/** The previous upload with the newest `creationTime`, if any. */\nexport function newestPreviousUpload(previous: PreviousUpload[]): PreviousUpload | undefined {\n return [...previous].sort((a, b) => b.creationTime.localeCompare(a.creationTime))[0];\n}\n\nasync function startOrResume(upload: TusUpload, source: ResumableSource): Promise<void> {\n if (!source.lengthDeferred) {\n const previous = newestPreviousUpload(await upload.findPreviousUploads());\n if (previous !== undefined) {upload.resumeFromPreviousUpload(previous);}\n }\n upload.start();\n}\n\nfunction abortedError(): UploadError {\n return new UploadError(UploadErrorKind.Aborted, 'Upload was cancelled.');\n}\n\n/** tus 1.0 upload (16 MiB chunks, optional streamed gzip); identity resumes across reloads, gzip only in-session. */\nexport function uploadResumable(options: ResumableUploadOptions): Promise<string> {\n if (options.signal?.aborted === true) {return Promise.reject(abortedError());}\n const source = openResumableSource(options.file, options.gzip === true);\n const factory = options.uploadFactory ?? createTusUpload;\n return new Promise<string>((resolve, reject) => {\n const settle = (finish: () => void): void => {\n options.signal?.removeEventListener('abort', onAbort);\n finish();\n };\n const upload = factory(source.input, buildTusOptions(options, source, {\n onProgress: (sent, total) => options.onProgress?.(resumablePercent(source, options.file.size, sent, total)),\n onSuccess: () => settle(() => {\n options.onProgress?.(PERCENT_COMPLETE);\n try { resolve(uploadIdFromUrl(upload.url)); } catch (error) { reject(error); }\n }),\n onError: (error) => settle(() => reject(toResumableError(error))),\n }));\n function onAbort(): void {\n settle(() => reject(abortedError()));\n upload.abort(true).catch(() => undefined);\n }\n options.signal?.addEventListener('abort', onAbort, { once: true });\n startOrResume(upload, source).catch((error: unknown) => settle(() => reject(toUploadError(error))));\n });\n}\n"]}
|
package/dist/index.mjs
CHANGED
|
@@ -358,12 +358,23 @@ var MAX_PERCENT_BEFORE_SUCCESS = 99;
|
|
|
358
358
|
function identitySource(file) {
|
|
359
359
|
return { input: file, encoding: "identity" /* Identity */, lengthDeferred: false, sourceBytesRead: () => 0 };
|
|
360
360
|
}
|
|
361
|
-
function
|
|
361
|
+
function gzipStreams(gzip, runtime) {
|
|
362
362
|
const Compression = runtime.CompressionStream;
|
|
363
363
|
const Transform = runtime.TransformStream;
|
|
364
364
|
if (!gzip || Compression === void 0 || Transform === void 0) {
|
|
365
|
+
return void 0;
|
|
366
|
+
}
|
|
367
|
+
return { Compression, Transform };
|
|
368
|
+
}
|
|
369
|
+
function isResumableAcrossReload(gzip = false, runtime = globalThis) {
|
|
370
|
+
return gzipStreams(gzip, runtime) === void 0;
|
|
371
|
+
}
|
|
372
|
+
function openResumableSource(file, gzip, runtime = globalThis) {
|
|
373
|
+
const streams = gzipStreams(gzip, runtime);
|
|
374
|
+
if (streams === void 0) {
|
|
365
375
|
return identitySource(file);
|
|
366
376
|
}
|
|
377
|
+
const { Compression, Transform } = streams;
|
|
367
378
|
let read = 0;
|
|
368
379
|
const counter = new Transform({
|
|
369
380
|
transform(chunk, controller) {
|
|
@@ -412,7 +423,7 @@ function buildTusOptions(options, source, callbacks) {
|
|
|
412
423
|
headers: { ...options.headers },
|
|
413
424
|
uploadLengthDeferred: source.lengthDeferred,
|
|
414
425
|
fingerprint: () => Promise.resolve(fingerprint),
|
|
415
|
-
storeFingerprintForResuming:
|
|
426
|
+
storeFingerprintForResuming: !source.lengthDeferred,
|
|
416
427
|
removeFingerprintOnSuccess: true,
|
|
417
428
|
onBeforeRequest: options.withCredentials === false ? void 0 : sendCookies,
|
|
418
429
|
...callbacks
|
|
@@ -437,10 +448,12 @@ function toResumableError(error) {
|
|
|
437
448
|
function newestPreviousUpload(previous) {
|
|
438
449
|
return [...previous].sort((a, b) => b.creationTime.localeCompare(a.creationTime))[0];
|
|
439
450
|
}
|
|
440
|
-
async function startOrResume(upload) {
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
451
|
+
async function startOrResume(upload, source) {
|
|
452
|
+
if (!source.lengthDeferred) {
|
|
453
|
+
const previous = newestPreviousUpload(await upload.findPreviousUploads());
|
|
454
|
+
if (previous !== void 0) {
|
|
455
|
+
upload.resumeFromPreviousUpload(previous);
|
|
456
|
+
}
|
|
444
457
|
}
|
|
445
458
|
upload.start();
|
|
446
459
|
}
|
|
@@ -475,10 +488,10 @@ function uploadResumable(options) {
|
|
|
475
488
|
upload.abort(true).catch(() => void 0);
|
|
476
489
|
}
|
|
477
490
|
options.signal?.addEventListener("abort", onAbort, { once: true });
|
|
478
|
-
startOrResume(upload).catch((error) => settle(() => reject(toUploadError(error))));
|
|
491
|
+
startOrResume(upload, source).catch((error) => settle(() => reject(toUploadError(error))));
|
|
479
492
|
});
|
|
480
493
|
}
|
|
481
494
|
|
|
482
|
-
export { DEFAULT_CSRF_HEADER, DEFAULT_RESUMABLE_CHUNK_SIZE, UploadEncoding, UploadError, UploadErrorKind, UploadStatus, blobFromUri, buildFormRequest, buildResumableMetadata, buildTusOptions, buildUploadRequest, resumeFingerprint, toUploadError, uploadFormWithProgress, uploadImage, uploadResumable, useImageUpload };
|
|
495
|
+
export { DEFAULT_CSRF_HEADER, DEFAULT_RESUMABLE_CHUNK_SIZE, UploadEncoding, UploadError, UploadErrorKind, UploadStatus, blobFromUri, buildFormRequest, buildResumableMetadata, buildTusOptions, buildUploadRequest, isResumableAcrossReload, resumeFingerprint, toUploadError, uploadFormWithProgress, uploadImage, uploadResumable, useImageUpload };
|
|
483
496
|
//# sourceMappingURL=index.mjs.map
|
|
484
497
|
//# 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/uploadFormWithProgress.ts","../src/blobFromUri.ts","../src/UploadStatus.ts","../src/useImageUpload.ts","../src/UploadEncoding.ts","../src/resumableSource.ts","../src/uploadResumable.ts"],"names":["UploadErrorKind","buildHeaders","UploadStatus","UploadEncoding","abortedError"],"mappings":";;;;AACO,IAAW,eAAA,qBAAAA,gBAAAA,KAAX;AAEL,EAAAA,iBAAA,YAAA,CAAA,GAAa,YAAA;AAEb,EAAAA,iBAAA,SAAA,CAAA,GAAU,SAAA;AAEV,EAAAA,iBAAA,MAAA,CAAA,GAAO,MAAA;AAEP,EAAAA,iBAAA,SAAA,CAAA,GAAU,SAAA;AAEV,EAAAA,iBAAA,iBAAA,CAAA,GAAkB,kBAAA;AAVF,EAAA,OAAAA,gBAAAA;AAAA,CAAA,EAAA,eAAA,IAAA,EAAA;;;ACEX,IAAM,WAAA,GAAN,cAA0B,KAAA,CAAM;AAAA,EAOrC,WAAA,CAAY,IAAA,EAAuB,OAAA,EAAiB,MAAA,EAAiB,IAAA,EAAe;AAClF,IAAA,KAAA,CAAM,OAAO,CAAA;AACb,IAAA,IAAA,CAAK,IAAA,GAAO,aAAA;AACZ,IAAA,IAAA,CAAK,IAAA,GAAO,IAAA;AACZ,IAAA,IAAA,CAAK,MAAA,GAAS,MAAA;AACd,IAAA,IAAA,CAAK,IAAA,GAAO,IAAA;AAAA,EACd;AACF;AAGO,SAAS,cAAc,KAAA,EAA6B;AACzD,EAAA,IAAI,iBAAiB,WAAA,EAAa;AAChC,IAAA,OAAO,KAAA;AAAA,EACT;AACA,EAAA,MAAM,UAAU,KAAA,YAAiB,KAAA,GAAQ,KAAA,CAAM,OAAA,GAAU,OAAO,KAAK,CAAA;AACrE,EAAA,OAAO,IAAI,qCAAqC,OAAO,CAAA;AACzD;;;ACrBA,SAAS,SAAS,KAAA,EAAkD;AAClE,EAAA,OAAO,OAAO,UAAU,QAAA,IAAY,KAAA,KAAU,QAAQ,CAAC,KAAA,CAAM,QAAQ,KAAK,CAAA;AAC5E;AAEA,SAAS,QAAQ,OAAA,EAA8B;AAC7C,EAAA,OAAO,IAAI,sDAA6C,OAAO,CAAA;AACjE;AAGO,SAAS,oBAAA,CAAqB,MAAc,UAAA,EAAsC;AACvF,EAAA,IAAI,OAAA;AACJ,EAAA,IAAI;AACF,IAAA,OAAA,GAAU,IAAA,CAAK,MAAM,IAAI,CAAA;AAAA,EAC3B,CAAA,CAAA,MAAQ;AACN,IAAA,MAAM,QAAQ,+BAA+B,CAAA;AAAA,EAC/C;AAGA,EAAA,MAAM,YAAA,GAAe,SAAS,OAAO,CAAA,IAAK,QAAQ,MAAA,KAAW,MAAA,IAAa,OAAO,OAAA,CAAQ,MAAA,KAAW,QAAA;AACpG,EAAA,IAAI,CAAC,SAAS,OAAO,CAAA,IAAK,OAAO,OAAA,CAAQ,SAAA,KAAc,YAAY,YAAA,EAAc;AAC/E,IAAA,MAAM,QAAQ,oCAAoC,CAAA;AAAA,EACpD;AACA,EAAA,MAAM,SAAS,OAAO,OAAA,CAAQ,MAAA,KAAW,QAAA,GAAW,QAAQ,MAAA,GAAS,MAAA;AACrE,EAAA,MAAM,GAAA,GAAM,OAAO,OAAA,CAAQ,GAAA,KAAQ,YAAY,OAAA,CAAQ,GAAA,KAAQ,EAAA,GAAK,OAAA,CAAQ,GAAA,GAAM,MAAA;AAClF,EAAA,IAAI,UAAA,IAAc,QAAQ,MAAA,EAAW;AACnC,IAAA,MAAM,QAAQ,+CAA+C,CAAA;AAAA,EAC/D;AACA,EAAA,OAAO,EAAE,SAAA,EAAW,OAAA,CAAQ,SAAA,EAAW,QAAQ,GAAA,EAAI;AACrD;;;AC5BA,IAAM,WAAA,GAAc,GAAA;AACpB,IAAM,qBAAA,GAAwB,GAAA;AACvB,IAAM,gBAAA,GAAmB,GAAA;AAYhC,SAAS,KAAK,MAAA,EAAyB;AACrC,EAAA,OAAO,MAAA,IAAU,eAAe,MAAA,GAAS,qBAAA;AAC3C;AAEA,SAAS,SAAA,CAAU,QAAgB,IAAA,EAA2B;AAC5D,EAAA,OAAO,IAAI,+BAAkC,CAAA,eAAA,EAAkB,MAAA,CAAO,MAAM,CAAC,CAAA,EAAA,CAAA,EAAM,QAAQ,IAAI,CAAA;AACjG;AAEA,eAAe,eAAe,QAAA,EAAqC;AACjE,EAAA,IAAI;AACF,IAAA,OAAO,MAAM,SAAS,IAAA,EAAK;AAAA,EAC7B,CAAA,CAAA,MAAQ;AACN,IAAA,OAAO,EAAA;AAAA,EACT;AACF;AAEA,SAAS,YAAA,GAA4B;AACnC,EAAA,OAAO,IAAI,qCAAqC,uBAAuB,CAAA;AACzE;AAGA,eAAsB,aAAA,CAAc,SAAwB,SAAA,EAA0C;AACpG,EAAA,IAAI,QAAA;AACJ,EAAA,IAAI;AACF,IAAA,QAAA,GAAW,MAAM,SAAA,CAAU,OAAA,CAAQ,GAAA,EAAK;AAAA,MACtC,MAAA,EAAQ,MAAA;AAAA,MACR,aAAa,OAAA,CAAQ,WAAA;AAAA,MACrB,SAAS,OAAA,CAAQ,OAAA;AAAA,MACjB,MAAM,OAAA,CAAQ,IAAA;AAAA,MACd,QAAQ,OAAA,CAAQ;AAAA,KACjB,CAAA;AAAA,EACH,SAAS,KAAA,EAAO;AACd,IAAA,IAAI,OAAA,CAAQ,MAAA,EAAQ,OAAA,KAAY,IAAA,EAAM;AACpC,MAAA,MAAM,YAAA,EAAa;AAAA,IACrB;AACA,IAAA,MAAM,SAAS,KAAA,YAAiB,KAAA,GAAQ,KAAA,CAAM,OAAA,GAAU,OAAO,KAAK,CAAA;AACpE,IAAA,MAAM,IAAI,qCAAqC,MAAM,CAAA;AAAA,EACvD;AACA,EAAA,IAAI,CAAC,IAAA,CAAK,QAAA,CAAS,MAAM,CAAA,EAAG;AAC1B,IAAA,MAAM,UAAU,QAAA,CAAS,MAAA,EAAQ,MAAM,cAAA,CAAe,QAAQ,CAAC,CAAA;AAAA,EACjE;AACA,EAAA,OAAO,SAAS,IAAA,EAAK;AACvB;AAEA,SAAS,cAAA,CAAe,OAAsB,UAAA,EAAoC;AAChF,EAAA,IAAI,KAAA,CAAM,gBAAA,IAAoB,KAAA,CAAM,KAAA,GAAQ,CAAA,EAAG;AAC7C,IAAA,UAAA,CAAW,KAAK,KAAA,CAAO,KAAA,CAAM,SAAS,KAAA,CAAM,KAAA,GAAS,gBAAgB,CAAC,CAAA;AAAA,EACxE;AACF;AAGO,SAAS,WAAA,CACd,OAAA,EACA,SAAA,EACA,UAAA,EACiB;AACjB,EAAA,OAAO,IAAI,OAAA,CAAQ,CAAC,OAAA,EAAS,MAAA,KAAW;AACtC,IAAA,MAAM,EAAE,QAAO,GAAI,OAAA;AACnB,IAAA,IAAI,MAAA,EAAQ,YAAY,IAAA,EAAM;AAC5B,MAAA,MAAA,CAAO,cAAc,CAAA;AACrB,MAAA;AAAA,IACF;AACA,IAAA,MAAM,MAAM,SAAA,EAAU;AACtB,IAAA,GAAA,CAAI,MAAA,CAAO,iBAAiB,UAAA,EAAY,CAAC,UAAU,cAAA,CAAe,KAAA,EAAO,UAAU,CAAC,CAAA;AACpF,IAAA,GAAA,CAAI,gBAAA,CAAiB,QAAQ,MAAM;AACjC,MAAA,IAAI,IAAA,CAAK,GAAA,CAAI,MAAM,CAAA,EAAG;AACpB,QAAA,OAAA,CAAQ,IAAI,YAAY,CAAA;AAAA,MAC1B,CAAA,MAAO;AACL,QAAA,MAAA,CAAO,SAAA,CAAU,GAAA,CAAI,MAAA,EAAQ,GAAA,CAAI,YAAY,CAAC,CAAA;AAAA,MAChD;AAAA,IACF,CAAC,CAAA;AACD,IAAA,GAAA,CAAI,gBAAA,CAAiB,SAAS,MAAM,MAAA,CAAO,IAAI,WAAA,CAAA,SAAA,gBAAqC,gCAAgC,CAAC,CAAC,CAAA;AACtH,IAAA,GAAA,CAAI,iBAAiB,OAAA,EAAS,MAAM,MAAA,CAAO,YAAA,EAAc,CAAC,CAAA;AAC1D,IAAA,MAAA,EAAQ,gBAAA,CAAiB,SAAS,MAAM,GAAA,CAAI,OAAM,EAAG,EAAE,IAAA,EAAM,IAAA,EAAM,CAAA;AACnE,IAAA,GAAA,CAAI,IAAA,CAAK,MAAA,EAAQ,OAAA,CAAQ,GAAG,CAAA;AAC5B,IAAA,GAAA,CAAI,eAAA,GAAkB,QAAQ,WAAA,KAAgB,SAAA;AAC9C,IAAA,MAAA,CAAO,OAAA,CAAQ,OAAA,CAAQ,OAAO,CAAA,CAAE,QAAQ,CAAC,CAAC,IAAA,EAAM,KAAK,CAAA,KAAM,GAAA,CAAI,gBAAA,CAAiB,IAAA,EAAM,KAAK,CAAC,CAAA;AAC5F,IAAA,GAAA,CAAI,IAAA,CAAK,QAAQ,IAAI,CAAA;AAAA,EACvB,CAAC,CAAA;AACH;AAYA,SAAS,kBAAkB,OAAA,EAA+D;AACxF,EAAA,IAAI,OAAA,CAAQ,cAAc,MAAA,EAAW;AACnC,IAAA,OAAO,MAAA;AAAA,EACT;AACA,EAAA,IAAI,OAAA,CAAQ,eAAe,MAAA,EAAW;AACpC,IAAA,OAAO,OAAA,CAAQ,UAAA;AAAA,EACjB;AACA,EAAA,OAAO,OAAO,cAAA,KAAmB,WAAA,GAAc,MAAA,GAAY,MAAsB,IAAI,cAAA,EAAe;AACtG;AAGA,eAAsB,iBAAA,CAAkB,SAAwB,OAAA,EAA4C;AAC1G,EAAA,MAAM,EAAE,YAAW,GAAI,OAAA;AACvB,EAAA,MAAM,SAAA,GAAY,UAAA,KAAe,MAAA,GAAY,MAAA,GAAY,kBAAkB,OAAO,CAAA;AAClF,EAAA,IAAI,SAAA,KAAc,MAAA,IAAa,UAAA,KAAe,MAAA,EAAW;AACvD,IAAA,OAAO,WAAA,CAAY,OAAA,EAAS,SAAA,EAAW,UAAU,CAAA;AAAA,EACnD;AACA,EAAA,MAAM,YAAY,OAAA,CAAQ,SAAA,IAAa,UAAA,CAAW,KAAA,CAAM,KAAK,UAAU,CAAA;AACvE,EAAA,OAAO,aAAA,CAAc,SAAS,SAAS,CAAA;AACzC;;;ACxHO,IAAM,mBAAA,GAAkC,EAAE,IAAA,EAAM,YAAA,EAAc,OAAO,GAAA;AAE5E,IAAM,gBAAA,GAAmB,OAAA;AACzB,IAAM,iBAAA,GAAoB,QAAA;AAC1B,IAAM,UAAA,GAAa,MAAA;AACnB,IAAM,cAAA,GAAiB,UAAA;AACvB,IAAM,eAAA,GAAkB,UAAA;AAExB,SAAS,gBAAgB,OAAA,EAA8B;AACrD,EAAA,OAAO,IAAI,2CAAwC,OAAO,CAAA;AAC5D;AAGA,SAAS,QAAA,CAAS,MAAY,OAAA,EAAmC;AAC/D,EAAA,IAAI,IAAA,CAAK,SAAS,CAAA,EAAG;AACnB,IAAA,MAAM,gBAAgB,gBAAgB,CAAA;AAAA,EACxC;AACA,EAAA,IAAI,QAAQ,QAAA,KAAa,MAAA,IAAa,IAAA,CAAK,IAAA,GAAO,QAAQ,QAAA,EAAU;AAClE,IAAA,MAAM,eAAA,CAAgB,CAAA,QAAA,EAAW,MAAA,CAAO,IAAA,CAAK,IAAI,CAAC,CAAA,qBAAA,EAAwB,MAAA,CAAO,OAAA,CAAQ,QAAQ,CAAC,CAAA,CAAA,CAAG,CAAA;AAAA,EACvG;AACA,EAAA,IAAI,OAAA,CAAQ,iBAAiB,MAAA,IAAa,CAAC,QAAQ,YAAA,CAAa,QAAA,CAAS,IAAA,CAAK,IAAI,CAAA,EAAG;AACnF,IAAA,MAAM,eAAA,CAAgB,CAAA,WAAA,EAAc,IAAA,CAAK,IAAI,CAAA,iBAAA,CAAmB,CAAA;AAAA,EAClE;AACF;AAEA,SAAS,eAAA,CAAgB,MAAY,QAAA,EAAsC;AACzE,EAAA,IAAI,QAAA,KAAa,MAAA,IAAa,QAAA,KAAa,EAAA,EAAI;AAC7C,IAAA,OAAO,QAAA;AAAA,EACT;AACA,EAAA,IAAI,MAAA,IAAU,QAAQ,OAAO,IAAA,CAAK,SAAS,QAAA,IAAY,IAAA,CAAK,SAAS,EAAA,EAAI;AACvE,IAAA,OAAO,IAAA,CAAK,IAAA;AAAA,EACd;AACA,EAAA,OAAO,iBAAA;AACT;AAEA,SAAS,aAAa,IAAA,EAA6D;AACjF,EAAA,MAAM,OAAA,GAAkC,EAAE,MAAA,EAAQ,kBAAA,EAAmB;AACrE,EAAA,MAAM,MAAA,GAAS,IAAA,KAAS,MAAA,GAAY,mBAAA,GAAsB,IAAA;AAC1D,EAAA,IAAI,WAAW,IAAA,EAAM;AACnB,IAAA,OAAA,CAAQ,MAAA,CAAO,IAAI,CAAA,GAAI,MAAA,CAAO,KAAA;AAAA,EAChC;AACA,EAAA,OAAO,OAAA;AACT;AAGO,SAAS,kBAAA,CAAmB,MAAY,OAAA,EAA4C;AACzF,EAAA,MAAM,IAAA,GAAO,IAAI,QAAA,EAAS;AAC1B,EAAA,IAAA,CAAK,OAAO,UAAA,EAAY,IAAA,EAAM,gBAAgB,IAAA,EAAM,OAAA,CAAQ,QAAQ,CAAC,CAAA;AACrE,EAAA,IAAA,CAAK,MAAA,CAAO,cAAA,EAAgB,OAAA,CAAQ,QAAA,IAAY,gBAAgB,CAAA;AAChE,EAAA,IAAA,CAAK,OAAO,eAAA,EAAiB,MAAA,CAAO,OAAA,CAAQ,QAAA,IAAY,IAAI,CAAC,CAAA;AAC7D,EAAA,OAAO;AAAA,IACL,KAAK,OAAA,CAAQ,QAAA;AAAA,IACb,OAAA,EAAS,YAAA,CAAa,OAAA,CAAQ,IAAI,CAAA;AAAA,IAClC,IAAA;AAAA,IACA,WAAA,EAAa,QAAQ,WAAA,IAAe,SAAA;AAAA,IACpC,QAAQ,OAAA,CAAQ;AAAA,GAClB;AACF;AAWA,eAAsB,WAAA,CAAY,MAAY,OAAA,EAAuD;AACnG,EAAA,QAAA,CAAS,MAAM,OAAO,CAAA;AACtB,EAAA,MAAM,OAAA,GAAU,kBAAA,CAAmB,IAAA,EAAM,OAAO,CAAA;AAChD,EAAA,MAAM,IAAA,GAAO,MAAM,iBAAA,CAAkB,OAAA,EAAS,OAAO,CAAA;AACrD,EAAA,OAAO,qBAAqB,IAAA,EAAM,OAAA,CAAQ,UAAA,IAAc,OAAA,CAAQ,YAAY,IAAI,CAAA;AAClF;;;AC3EA,IAAM,mBAAA,GAAsB,cAAA;AAC5B,IAAM,aAAA,GAAgB,QAAA;AAEtB,SAASC,cAAa,KAAA,EAAmE;AACvF,EAAA,MAAM,UAAkC,EAAC;AACzC,EAAA,MAAA,CAAO,OAAA,CAAQ,KAAA,IAAS,EAAE,CAAA,CAAE,QAAQ,CAAC,CAAC,IAAA,EAAM,KAAK,CAAA,KAAM;AACrD,IAAA,IAAI,IAAA,CAAK,WAAA,EAAY,KAAM,mBAAA,EAAqB;AAAC,MAAA,OAAA,CAAQ,IAAI,CAAA,GAAI,KAAA;AAAA,IAAM;AAAA,EACzE,CAAC,CAAA;AACD,EAAA,MAAM,SAAA,GAAY,MAAA,CAAO,IAAA,CAAK,OAAO,CAAA,CAAE,IAAA,CAAK,CAAC,IAAA,KAAS,IAAA,CAAK,WAAA,EAAY,KAAM,aAAa,CAAA;AAC1F,EAAA,IAAI,CAAC,SAAA,EAAW;AAAC,IAAA,OAAA,CAAQ,MAAA,GAAS,kBAAA;AAAA,EAAmB;AACrD,EAAA,OAAO,OAAA;AACT;AAEA,SAAS,UAAU,KAAA,EAAgD;AACjE,EAAA,MAAM,IAAA,GAAO,IAAI,QAAA,EAAS;AAC1B,EAAA,MAAA,CAAO,OAAA,CAAQ,KAAK,CAAA,CAAE,OAAA,CAAQ,CAAC,CAAC,IAAA,EAAM,KAAK,CAAA,KAAM,IAAA,CAAK,MAAA,CAAO,IAAA,EAAM,KAAK,CAAC,CAAA;AACzE,EAAA,OAAO,IAAA;AACT;AAGO,SAAS,iBAAiB,OAAA,EAA2C;AAC1E,EAAA,OAAO;AAAA,IACL,KAAK,OAAA,CAAQ,GAAA;AAAA,IACb,OAAA,EAASA,aAAAA,CAAa,OAAA,CAAQ,OAAO,CAAA;AAAA,IACrC,IAAA,EAAM,SAAA,CAAU,OAAA,CAAQ,KAAK,CAAA;AAAA,IAC7B,WAAA,EAAa,OAAA,CAAQ,eAAA,KAAoB,KAAA,GAAQ,aAAA,GAAgB,SAAA;AAAA,IACjE,QAAQ,OAAA,CAAQ;AAAA,GAClB;AACF;AAEA,SAAS,UAAa,IAAA,EAAwB;AAC5C,EAAA,IAAI,IAAA,CAAK,IAAA,EAAK,KAAM,EAAA,EAAI;AAAC,IAAA,OAAO,IAAA;AAAA,EAAK;AACrC,EAAA,IAAI;AACF,IAAA,OAAO,IAAA,CAAK,MAAM,IAAI,CAAA;AAAA,EACxB,CAAA,CAAA,MAAQ;AACN,IAAA,MAAM,IAAI,sDAA6C,qCAAqC,CAAA;AAAA,EAC9F;AACF;AAQA,eAAsB,uBAAoC,OAAA,EAA+C;AACvG,EAAA,MAAM,OAAO,MAAM,iBAAA,CAAkB,gBAAA,CAAiB,OAAO,GAAG,OAAO,CAAA;AACvE,EAAA,OAAO,UAAa,IAAI,CAAA;AAC1B;;;AC9CA,eAAsB,WAAA,CAAY,KAAa,SAAA,EAAyC;AACtF,EAAA,MAAM,OAAA,GAAU,SAAA,IAAa,UAAA,CAAW,KAAA,CAAM,KAAK,UAAU,CAAA;AAC7D,EAAA,IAAI,QAAA;AACJ,EAAA,IAAI;AACF,IAAA,QAAA,GAAW,MAAM,QAAQ,GAAG,CAAA;AAAA,EAC9B,SAAS,KAAA,EAAO;AACd,IAAA,MAAM,SAAS,KAAA,YAAiB,KAAA,GAAQ,KAAA,CAAM,OAAA,GAAU,OAAO,KAAK,CAAA;AACpE,IAAA,MAAM,IAAI,WAAA,CAAA,YAAA,mBAAwC,CAAA,qBAAA,EAAwB,MAAM,CAAA,CAAE,CAAA;AAAA,EACpF;AACA,EAAA,IAAI,CAAC,SAAS,EAAA,EAAI;AAChB,IAAA,MAAM,IAAI,WAAA,CAAA,YAAA,mBAAwC,CAAA,qBAAA,EAAwB,OAAO,QAAA,CAAS,MAAM,CAAC,CAAA,EAAA,CAAI,CAAA;AAAA,EACvG;AACA,EAAA,OAAO,SAAS,IAAA,EAAK;AACvB;;;ACrBO,IAAW,YAAA,qBAAAC,aAAAA,KAAX;AACL,EAAAA,cAAA,MAAA,CAAA,GAAO,MAAA;AACP,EAAAA,cAAA,WAAA,CAAA,GAAY,WAAA;AACZ,EAAAA,cAAA,OAAA,CAAA,GAAQ,OAAA;AACR,EAAAA,cAAA,MAAA,CAAA,GAAO,MAAA;AAJS,EAAA,OAAAA,aAAAA;AAAA,CAAA,EAAA,YAAA,IAAA,EAAA;;;AC8BlB,IAAM,IAAA,GAAyB,EAAE,MAAA,EAAA,MAAA,aAA2B,QAAA,EAAU,GAAG,KAAA,EAAO,IAAA,EAAM,QAAQ,IAAA,EAAK;AACnG,IAAM,SAAA,GAA8B,EAAE,MAAA,EAAA,WAAA,kBAAgC,QAAA,EAAU,GAAG,KAAA,EAAO,IAAA,EAAM,QAAQ,IAAA,EAAK;AAS7G,SAAS,OAAA,CAAQ,OAAyB,MAAA,EAAkC;AAC1E,EAAA,QAAQ,OAAO,IAAA;AAAM,IACnB,KAAK,OAAA;AACH,MAAA,OAAO,SAAA;AAAA,IACT,KAAK,UAAA;AACH,MAAA,OAAO,EAAE,GAAG,KAAA,EAAO,QAAA,EAAU,OAAO,OAAA,EAAQ;AAAA,IAC9C,KAAK,MAAA;AACH,MAAA,OAAO,EAAE,2BAA2B,QAAA,EAAU,gBAAA,EAAkB,OAAO,IAAA,EAAM,MAAA,EAAQ,OAAO,MAAA,EAAO;AAAA,IACrG,KAAK,MAAA;AACH,MAAA,OAAO,EAAE,GAAG,KAAA,EAAO,MAAA,EAAA,OAAA,cAA4B,KAAA,EAAO,OAAO,KAAA,EAAM;AAAA,IACrE;AACE,MAAA,OAAO,IAAA;AAAA;AAEb;AAQO,SAAS,eAAe,OAAA,EAAsD;AACnF,EAAA,MAAM,CAAC,KAAA,EAAO,QAAQ,CAAA,GAAI,UAAA,CAAW,SAAS,IAAI,CAAA;AAClD,EAAA,MAAM,UAAA,GAAa,OAAO,OAAO,CAAA;AACjC,EAAA,UAAA,CAAW,OAAA,GAAU,OAAA;AACrB,EAAA,MAAM,aAAA,GAAgB,OAA+B,IAAI,CAAA;AACzD,EAAA,MAAM,YAAA,GAAe,OAAO,CAAC,CAAA;AAE7B,EAAA,MAAM,aAAA,GAAgB,YAAY,MAAY;AAC5C,IAAA,aAAA,CAAc,SAAS,KAAA,EAAM;AAC7B,IAAA,aAAA,CAAc,OAAA,GAAU,IAAA;AACxB,IAAA,YAAA,CAAa,OAAA,IAAW,CAAA;AAAA,EAC1B,CAAA,EAAG,EAAE,CAAA;AAEL,EAAA,SAAA,CAAU,MAAM,aAAA,EAAe,CAAC,aAAa,CAAC,CAAA;AAE9C,EAAA,MAAM,MAAA,GAAS,WAAA,CAAY,OAAO,IAAA,EAAY,QAAA,KAAuD;AACnG,IAAA,aAAA,EAAc;AACd,IAAA,MAAM,UAAA,GAAa,IAAI,eAAA,EAAgB;AACvC,IAAA,aAAA,CAAc,OAAA,GAAU,UAAA;AACxB,IAAA,MAAM,YAAY,YAAA,CAAa,OAAA;AAC/B,IAAA,MAAM,SAAA,GAAY,MAAe,SAAA,KAAc,YAAA,CAAa,OAAA;AAC5D,IAAA,MAAM,EAAE,SAAA,EAAW,OAAA,EAAS,GAAG,aAAA,KAAkB,UAAA,CAAW,OAAA;AAC5D,IAAA,QAAA,CAAS,EAAE,IAAA,EAAM,OAAA,EAAS,CAAA;AAC1B,IAAA,IAAI;AACF,MAAA,MAAM,MAAA,GAAS,MAAM,WAAA,CAAY,IAAA,EAAM;AAAA,QACrC,GAAG,aAAA;AAAA,QACH,QAAA,EAAU,YAAY,aAAA,CAAc,QAAA;AAAA,QACpC,QAAQ,UAAA,CAAW,MAAA;AAAA,QACnB,UAAA,EAAY,CAAC,OAAA,KAAY;AACvB,UAAA,IAAI,WAAU,EAAG;AACf,YAAA,QAAA,CAAS,EAAE,IAAA,EAAM,UAAA,EAAY,OAAA,EAAS,CAAA;AAAA,UACxC;AAAA,QACF;AAAA,OACD,CAAA;AACD,MAAA,IAAI,CAAC,WAAU,EAAG;AAChB,QAAA,OAAO,IAAA;AAAA,MACT;AACA,MAAA,QAAA,CAAS,EAAE,IAAA,EAAM,MAAA,EAAQ,MAAA,EAAQ,CAAA;AACjC,MAAA,SAAA,GAAY,MAAM,CAAA;AAClB,MAAA,OAAO,MAAA;AAAA,IACT,SAAS,KAAA,EAAO;AACd,MAAA,IAAI,CAAC,WAAU,EAAG;AAChB,QAAA,OAAO,IAAA;AAAA,MACT;AACA,MAAA,MAAM,KAAA,GAAQ,cAAc,KAAK,CAAA;AACjC,MAAA,QAAA,CAAS,EAAE,IAAA,EAAM,MAAA,EAAQ,KAAA,EAAO,CAAA;AAChC,MAAA,OAAA,GAAU,KAAK,CAAA;AACf,MAAA,OAAO,IAAA;AAAA,IACT;AAAA,EACF,CAAA,EAAG,CAAC,aAAa,CAAC,CAAA;AAElB,EAAA,MAAM,KAAA,GAAQ,YAAY,MAAY;AACpC,IAAA,aAAA,EAAc;AACd,IAAA,QAAA,CAAS,EAAE,IAAA,EAAM,OAAA,EAAS,CAAA;AAAA,EAC5B,CAAA,EAAG,CAAC,aAAa,CAAC,CAAA;AAElB,EAAA,OAAO,EAAE,GAAG,KAAA,EAAO,MAAA,EAAQ,KAAA,EAAM;AACnC;;;ACtHO,IAAW,cAAA,qBAAAC,eAAAA,KAAX;AACL,EAAAA,gBAAA,MAAA,CAAA,GAAO,MAAA;AACP,EAAAA,gBAAA,UAAA,CAAA,GAAW,UAAA;AAFK,EAAA,OAAAA,eAAAA;AAAA,CAAA,EAAA,cAAA,IAAA,EAAA;;;ACClB,IAAM,aAAA,GAAgB,GAAA;AACtB,IAAM,0BAAA,GAA6B,EAAA;AAmBnC,SAAS,eAAe,IAAA,EAA6B;AACnD,EAAA,OAAO,EAAE,OAAO,IAAA,EAAM,QAAA,EAAA,UAAA,iBAAmC,gBAAgB,KAAA,EAAO,eAAA,EAAiB,MAAM,CAAA,EAAE;AAC3G;AAGO,SAAS,mBAAA,CAAoB,IAAA,EAAY,IAAA,EAAe,OAAA,GAAyB,UAAA,EAA6B;AACnH,EAAA,MAAM,cAAc,OAAA,CAAQ,iBAAA;AAC5B,EAAA,MAAM,YAAY,OAAA,CAAQ,eAAA;AAC1B,EAAA,IAAI,CAAC,IAAA,IAAQ,WAAA,KAAgB,MAAA,IAAa,cAAc,MAAA,EAAW;AAAC,IAAA,OAAO,eAAe,IAAI,CAAA;AAAA,EAAE;AAChG,EAAA,IAAI,IAAA,GAAO,CAAA;AACX,EAAA,MAAM,OAAA,GAAU,IAAI,SAAA,CAA4D;AAAA,IAC9E,SAAA,CAAU,OAAO,UAAA,EAAkB;AACjC,MAAA,IAAA,IAAQ,KAAA,CAAM,UAAA;AACd,MAAA,UAAA,CAAW,QAAQ,KAAK,CAAA;AAAA,IAC1B;AAAA,GACD,CAAA;AACD,EAAA,MAAM,MAAA,GAAS,IAAA,CAAK,MAAA,EAAO,CAAE,WAAA,CAAY,OAAO,CAAA,CAAE,WAAA,CAAY,IAAI,WAAA,CAAY,MAAM,CAAC,EAAE,SAAA,EAAU;AACjG,EAAA,OAAO,EAAE,OAAO,MAAA,EAAQ,QAAA,EAAA,MAAA,aAA+B,gBAAgB,IAAA,EAAM,eAAA,EAAiB,MAAM,IAAA,EAAK;AAC3G;AAGO,SAAS,gBAAA,CAAiB,MAAA,EAAyB,QAAA,EAAkB,SAAA,EAAmB,UAAA,EAA4B;AACzH,EAAA,IAAI,CAAC,OAAO,cAAA,EAAgB;AAAC,IAAA,OAAO,IAAA,CAAK,MAAO,SAAA,GAAY,aAAA,GAAiB,KAAK,GAAA,CAAI,UAAA,EAAY,CAAC,CAAC,CAAA;AAAA,EAAE;AACtG,EAAA,MAAM,OAAA,GAAU,IAAA,CAAK,KAAA,CAAO,MAAA,CAAO,eAAA,EAAgB,GAAI,aAAA,GAAiB,IAAA,CAAK,GAAA,CAAI,QAAA,EAAU,CAAC,CAAC,CAAA;AAC7F,EAAA,OAAO,IAAA,CAAK,GAAA,CAAI,OAAA,EAAS,0BAA0B,CAAA;AACrD;;;ACnCA,IAAM,WAAW,IAAA,GAAO,IAAA;AACxB,IAAM,uBAAA,GAA0B,EAAA;AACzB,IAAM,+BAA+B,uBAAA,GAA0B;AAE/D,IAAM,kBAAoC,CAAC,KAAA,EAAO,YAAY,IAAI,MAAA,CAAO,OAAO,OAAO,CAAA;AAcvF,SAAS,sBAAA,CAAuB,IAAA,EAAY,QAAA,EAA8C,QAAA,EAAkD;AACjJ,EAAA,MAAM,QAAA,GAAmC,gBAAgB,IAAA,GAAO,EAAE,UAAU,IAAA,CAAK,IAAA,KAAS,EAAC;AAC3F,EAAA,OAAO,EAAE,GAAG,QAAA,EAAU,GAAG,UAAU,QAAA,EAAS;AAC9C;AAGO,SAAS,iBAAA,CAAkB,QAAA,EAAkB,IAAA,EAAY,QAAA,EAA0C;AACxG,EAAA,MAAM,YAAA,GAAe,IAAA,YAAgB,IAAA,GAAO,IAAA,CAAK,YAAA,GAAe,CAAA;AAChE,EAAA,MAAM,OAAA,GAAU,MAAA,CAAO,OAAA,CAAQ,QAAQ,CAAA,CAAE,IAAA,CAAK,CAAC,CAAC,CAAC,CAAA,EAAG,CAAC,CAAC,CAAA,KAAM,CAAA,CAAE,aAAA,CAAc,CAAC,CAAC,CAAA,CAAE,GAAA,CAAI,CAAC,CAAC,GAAA,EAAK,KAAK,CAAA,KAAM,CAAA,EAAG,GAAG,CAAA,CAAA,EAAI,KAAK,CAAA,CAAE,CAAA;AACvH,EAAA,OAAO,CAAC,gBAAA,EAAkB,QAAA,EAAU,MAAA,CAAO,IAAA,CAAK,IAAI,CAAA,EAAG,MAAA,CAAO,YAAY,CAAA,EAAG,QAAQ,IAAA,CAAK,GAAG,CAAC,CAAA,CAAE,KAAK,GAAG,CAAA;AAC1G;AAEA,SAAS,YAAY,OAAA,EAA4B;AAC/C,EAAA,MAAM,UAAA,GAAa,QAAQ,mBAAA,EAAoB;AAC/C,EAAA,IAAI,UAAA,KAAe,IAAA,IAAQ,OAAO,UAAA,KAAe,QAAA,EAAU;AAAC,IAAA,UAAA,CAAW,eAAA,GAAkB,IAAA;AAAA,EAAK;AAChG;AAGO,SAAS,eAAA,CAAgB,OAAA,EAAiC,MAAA,EAAyB,SAAA,EAA2C;AACnI,EAAA,MAAM,WAAW,sBAAA,CAAuB,OAAA,CAAQ,MAAM,OAAA,CAAQ,QAAA,EAAU,OAAO,QAAQ,CAAA;AACvF,EAAA,MAAM,cAAc,iBAAA,CAAkB,OAAA,CAAQ,QAAA,EAAU,OAAA,CAAQ,MAAM,QAAQ,CAAA;AAC9E,EAAA,OAAO;AAAA,IACL,UAAU,OAAA,CAAQ,QAAA;AAAA,IAClB,SAAA,EAAW,QAAQ,SAAA,IAAa,4BAAA;AAAA,IAChC,QAAA;AAAA,IACA,OAAA,EAAS,EAAE,GAAG,OAAA,CAAQ,OAAA,EAAQ;AAAA,IAC9B,sBAAsB,MAAA,CAAO,cAAA;AAAA,IAC7B,WAAA,EAAa,MAAM,OAAA,CAAQ,OAAA,CAAQ,WAAW,CAAA;AAAA,IAC9C,2BAAA,EAA6B,IAAA;AAAA,IAC7B,0BAAA,EAA4B,IAAA;AAAA,IAC5B,eAAA,EAAiB,OAAA,CAAQ,eAAA,KAAoB,KAAA,GAAQ,MAAA,GAAY,WAAA;AAAA,IACjE,GAAG;AAAA,GACL;AACF;AAGO,SAAS,gBAAgB,GAAA,EAA4B;AAC1D,EAAA,MAAM,IAAA,GAAA,CAAQ,OAAO,EAAA,EAAI,KAAA,CAAM,KAAK,CAAC,CAAA,CAAE,KAAK,EAAE,CAAA;AAC9C,EAAA,MAAM,EAAA,GAAK,IAAA,CAAK,KAAA,CAAM,GAAG,CAAA,CAAE,MAAA,CAAO,CAAC,OAAA,KAAY,OAAA,KAAY,EAAE,CAAA,CAAE,GAAA,EAAI;AACnE,EAAA,IAAI,OAAO,MAAA,EAAW;AAAC,IAAA,MAAM,IAAI,sDAA6C,4CAA4C,CAAA;AAAA,EAAE;AAC5H,EAAA,OAAO,EAAA;AACT;AAGO,SAAS,iBAAiB,KAAA,EAA2B;AAC1D,EAAA,MAAM,WAAY,KAAA,CAAoD,gBAAA;AACtE,EAAA,IAAI,QAAA,KAAa,MAAA,IAAa,QAAA,KAAa,IAAA,EAAM;AAAC,IAAA,OAAO,cAAc,KAAK,CAAA;AAAA,EAAE;AAC9E,EAAA,MAAM,MAAA,GAAS,SAAS,SAAA,EAAU;AAClC,EAAA,OAAO,IAAI,WAAA,CAAA,MAAA,aAAkC,CAAA,eAAA,EAAkB,MAAA,CAAO,MAAM,CAAC,CAAA,EAAA,CAAA,EAAM,MAAA,EAAQ,QAAA,CAAS,OAAA,EAAS,CAAA;AAC/G;AAGO,SAAS,qBAAqB,QAAA,EAAwD;AAC3F,EAAA,OAAO,CAAC,GAAG,QAAQ,CAAA,CAAE,KAAK,CAAC,CAAA,EAAG,CAAA,KAAM,CAAA,CAAE,aAAa,aAAA,CAAc,CAAA,CAAE,YAAY,CAAC,EAAE,CAAC,CAAA;AACrF;AAEA,eAAe,cAAc,MAAA,EAAkC;AAC7D,EAAA,MAAM,QAAA,GAAW,oBAAA,CAAqB,MAAM,MAAA,CAAO,qBAAqB,CAAA;AACxE,EAAA,IAAI,aAAa,MAAA,EAAW;AAAC,IAAA,MAAA,CAAO,yBAAyB,QAAQ,CAAA;AAAA,EAAE;AACvE,EAAA,MAAA,CAAO,KAAA,EAAM;AACf;AAEA,SAASC,aAAAA,GAA4B;AACnC,EAAA,OAAO,IAAI,qCAAqC,uBAAuB,CAAA;AACzE;AAGO,SAAS,gBAAgB,OAAA,EAAkD;AAChF,EAAA,IAAI,OAAA,CAAQ,MAAA,EAAQ,OAAA,KAAY,IAAA,EAAM;AAAC,IAAA,OAAO,OAAA,CAAQ,MAAA,CAAOA,aAAAA,EAAc,CAAA;AAAA,EAAE;AAC7E,EAAA,MAAM,SAAS,mBAAA,CAAoB,OAAA,CAAQ,IAAA,EAAM,OAAA,CAAQ,SAAS,IAAI,CAAA;AACtE,EAAA,MAAM,OAAA,GAAU,QAAQ,aAAA,IAAiB,eAAA;AACzC,EAAA,OAAO,IAAI,OAAA,CAAgB,CAAC,OAAA,EAAS,MAAA,KAAW;AAC9C,IAAA,MAAM,MAAA,GAAS,CAAC,MAAA,KAA6B;AAC3C,MAAA,OAAA,CAAQ,MAAA,EAAQ,mBAAA,CAAoB,OAAA,EAAS,OAAO,CAAA;AACpD,MAAA,MAAA,EAAO;AAAA,IACT,CAAA;AACA,IAAA,MAAM,SAAS,OAAA,CAAQ,MAAA,CAAO,KAAA,EAAO,eAAA,CAAgB,SAAS,MAAA,EAAQ;AAAA,MACpE,UAAA,EAAY,CAAC,IAAA,EAAM,KAAA,KAAU,OAAA,CAAQ,UAAA,GAAa,gBAAA,CAAiB,MAAA,EAAQ,OAAA,CAAQ,IAAA,CAAK,IAAA,EAAM,IAAA,EAAM,KAAK,CAAC,CAAA;AAAA,MAC1G,SAAA,EAAW,MAAM,MAAA,CAAO,MAAM;AAC5B,QAAA,OAAA,CAAQ,aAAa,gBAAgB,CAAA;AACrC,QAAA,IAAI;AAAE,UAAA,OAAA,CAAQ,eAAA,CAAgB,MAAA,CAAO,GAAG,CAAC,CAAA;AAAA,QAAG,SAAS,KAAA,EAAO;AAAE,UAAA,MAAA,CAAO,KAAK,CAAA;AAAA,QAAG;AAAA,MAC/E,CAAC,CAAA;AAAA,MACD,OAAA,EAAS,CAAC,KAAA,KAAU,MAAA,CAAO,MAAM,MAAA,CAAO,gBAAA,CAAiB,KAAK,CAAC,CAAC;AAAA,KACjE,CAAC,CAAA;AACF,IAAA,SAAS,OAAA,GAAgB;AACvB,MAAA,MAAA,CAAO,MAAM,MAAA,CAAOA,aAAAA,EAAc,CAAC,CAAA;AACnC,MAAA,MAAA,CAAO,KAAA,CAAM,IAAI,CAAA,CAAE,KAAA,CAAM,MAAM,MAAS,CAAA;AAAA,IAC1C;AACA,IAAA,OAAA,CAAQ,QAAQ,gBAAA,CAAiB,OAAA,EAAS,SAAS,EAAE,IAAA,EAAM,MAAM,CAAA;AACjE,IAAA,aAAA,CAAc,MAAM,CAAA,CAAE,KAAA,CAAM,CAAC,KAAA,KAAmB,MAAA,CAAO,MAAM,MAAA,CAAO,aAAA,CAAc,KAAK,CAAC,CAAC,CAAC,CAAA;AAAA,EAC5F,CAAC,CAAA;AACH","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","/** Value of the tus `encoding` metadata entry: how the uploaded bytes are encoded. */\nexport const enum UploadEncoding {\n Gzip = 'gzip',\n Identity = 'identity',\n}\n","import { UploadEncoding } from './UploadEncoding';\n\nconst PERCENT_SCALE = 100;\nconst MAX_PERCENT_BEFORE_SUCCESS = 99;\n\n/** The read side of a byte stream, as tus-js-client accepts it. */\nexport type ByteReader = Pick<ReadableStreamDefaultReader<Uint8Array>, 'read'>;\n\n/** The stream constructors gzip needs; `CompressionStream` is absent on older runtimes. */\nexport interface StreamRuntime {\n CompressionStream?: typeof CompressionStream;\n TransformStream?: typeof TransformStream;\n}\n\n/** What tus-js-client uploads, and how its progress maps back to the source file. */\nexport interface ResumableSource {\n input: Blob | ByteReader;\n encoding: UploadEncoding;\n lengthDeferred: boolean;\n sourceBytesRead: () => number;\n}\n\nfunction identitySource(file: Blob): ResumableSource {\n return { input: file, encoding: UploadEncoding.Identity, lengthDeferred: false, sourceBytesRead: () => 0 };\n}\n\n/** Gzip the file as a stream when asked and supported; otherwise upload it as-is (identity). */\nexport function openResumableSource(file: Blob, gzip: boolean, runtime: StreamRuntime = globalThis): ResumableSource {\n const Compression = runtime.CompressionStream;\n const Transform = runtime.TransformStream;\n if (!gzip || Compression === undefined || Transform === undefined) {return identitySource(file);}\n let read = 0;\n const counter = new Transform<Uint8Array<ArrayBuffer>, Uint8Array<ArrayBuffer>>({\n transform(chunk, controller): void {\n read += chunk.byteLength;\n controller.enqueue(chunk);\n },\n });\n const reader = file.stream().pipeThrough(counter).pipeThrough(new Compression('gzip')).getReader();\n return { input: reader, encoding: UploadEncoding.Gzip, lengthDeferred: true, sourceBytesRead: () => read };\n}\n\n/** Whole-number percent; a deferred-length (gzip) upload counts source bytes read and holds at 99 until success. */\nexport function resumablePercent(source: ResumableSource, fileSize: number, bytesSent: number, bytesTotal: number): number {\n if (!source.lengthDeferred) {return Math.floor((bytesSent * PERCENT_SCALE) / Math.max(bytesTotal, 1));}\n const percent = Math.floor((source.sourceBytesRead() * PERCENT_SCALE) / Math.max(fileSize, 1));\n return Math.min(percent, MAX_PERCENT_BEFORE_SUCCESS);\n}\n","import { Upload } from 'tus-js-client';\n\nimport { openResumableSource, resumablePercent } from './resumableSource';\nimport { PERCENT_COMPLETE } from './transports';\nimport { UploadError, toUploadError } from './UploadError';\nimport { UploadErrorKind } from './UploadErrorKind';\n\nimport type { HttpRequest, PreviousUpload, UploadOptions } from 'tus-js-client';\nimport type { ResumableSource } from './resumableSource';\nimport type { ResumableUploadOptions, TusUpload, TusUploadFactory } from './resumableTypes';\nimport type { UploadEncoding } from './UploadEncoding';\n\nconst MEBIBYTE = 1024 * 1024;\nconst DEFAULT_CHUNK_MEBIBYTES = 16;\nexport const DEFAULT_RESUMABLE_CHUNK_SIZE = DEFAULT_CHUNK_MEBIBYTES * MEBIBYTE;\n\nexport const createTusUpload: TusUploadFactory = (input, options) => new Upload(input, options);\n\ninterface UploadCallbacks {\n onProgress: (bytesSent: number, bytesTotal: number) => void;\n onSuccess: () => void;\n onError: (error: Error) => void;\n}\n\ninterface TusResponse {\n getStatus(): number;\n getBody(): string;\n}\n\n/** Upload-Metadata sent on creation: `filename` from the File unless given, `encoding` always from the source. */\nexport function buildResumableMetadata(file: Blob, metadata: Record<string, string> | undefined, encoding: UploadEncoding): Record<string, string> {\n const defaults: Record<string, string> = file instanceof File ? { filename: file.name } : {};\n return { ...defaults, ...metadata, encoding };\n}\n\n/** Stable resume key: the same endpoint, file identity and metadata resume the same tus upload. */\nexport function resumeFingerprint(endpoint: string, file: Blob, metadata: Record<string, string>): string {\n const lastModified = file instanceof File ? file.lastModified : 0;\n const entries = Object.entries(metadata).sort(([a], [b]) => a.localeCompare(b)).map(([key, value]) => `${key}=${value}`);\n return ['content-upload', endpoint, String(file.size), String(lastModified), entries.join('&')].join('|');\n}\n\nfunction sendCookies(request: HttpRequest): void {\n const underlying = request.getUnderlyingObject() as { withCredentials?: boolean } | null;\n if (underlying !== null && typeof underlying === 'object') {underlying.withCredentials = true;}\n}\n\n/** The tus-js-client options for one upload. Exported so consumers can pin the request shape. */\nexport function buildTusOptions(options: ResumableUploadOptions, source: ResumableSource, callbacks: UploadCallbacks): UploadOptions {\n const metadata = buildResumableMetadata(options.file, options.metadata, source.encoding);\n const fingerprint = resumeFingerprint(options.endpoint, options.file, metadata);\n return {\n endpoint: options.endpoint,\n chunkSize: options.chunkSize ?? DEFAULT_RESUMABLE_CHUNK_SIZE,\n metadata,\n headers: { ...options.headers },\n uploadLengthDeferred: source.lengthDeferred,\n fingerprint: () => Promise.resolve(fingerprint),\n storeFingerprintForResuming: true,\n removeFingerprintOnSuccess: true,\n onBeforeRequest: options.withCredentials === false ? undefined : sendCookies,\n ...callbacks,\n };\n}\n\n/** The upload id is the last path segment of the tus upload URL (the BFF-rewritten Location). */\nexport function uploadIdFromUrl(url: string | null): string {\n const path = (url ?? '').split('?', 1).join('');\n const id = path.split('/').filter((segment) => segment !== '').pop();\n if (id === undefined) {throw new UploadError(UploadErrorKind.InvalidResponse, 'tus upload finished without an upload URL.');}\n return id;\n}\n\n/** Map a tus-js-client failure to `UploadError`: a server answer is `Http`, anything else `Network`. */\nexport function toResumableError(error: Error): UploadError {\n const response = (error as { originalResponse?: TusResponse | null }).originalResponse;\n if (response === undefined || response === null) {return toUploadError(error);}\n const status = response.getStatus();\n return new UploadError(UploadErrorKind.Http, `Upload failed (${String(status)}).`, status, response.getBody());\n}\n\n/** The previous upload with the newest `creationTime`, if any. */\nexport function newestPreviousUpload(previous: PreviousUpload[]): PreviousUpload | undefined {\n return [...previous].sort((a, b) => b.creationTime.localeCompare(a.creationTime))[0];\n}\n\nasync function startOrResume(upload: TusUpload): Promise<void> {\n const previous = newestPreviousUpload(await upload.findPreviousUploads());\n if (previous !== undefined) {upload.resumeFromPreviousUpload(previous);}\n upload.start();\n}\n\nfunction abortedError(): UploadError {\n return new UploadError(UploadErrorKind.Aborted, 'Upload was cancelled.');\n}\n\n/** tus 1.0 resumable upload (16 MiB chunks, optional streamed gzip, fingerprint resume); resolves the upload id. */\nexport function uploadResumable(options: ResumableUploadOptions): Promise<string> {\n if (options.signal?.aborted === true) {return Promise.reject(abortedError());}\n const source = openResumableSource(options.file, options.gzip === true);\n const factory = options.uploadFactory ?? createTusUpload;\n return new Promise<string>((resolve, reject) => {\n const settle = (finish: () => void): void => {\n options.signal?.removeEventListener('abort', onAbort);\n finish();\n };\n const upload = factory(source.input, buildTusOptions(options, source, {\n onProgress: (sent, total) => options.onProgress?.(resumablePercent(source, options.file.size, sent, total)),\n onSuccess: () => settle(() => {\n options.onProgress?.(PERCENT_COMPLETE);\n try { resolve(uploadIdFromUrl(upload.url)); } catch (error) { reject(error); }\n }),\n onError: (error) => settle(() => reject(toResumableError(error))),\n }));\n function onAbort(): void {\n settle(() => reject(abortedError()));\n upload.abort(true).catch(() => undefined);\n }\n options.signal?.addEventListener('abort', onAbort, { once: true });\n startOrResume(upload).catch((error: unknown) => settle(() => reject(toUploadError(error))));\n });\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","../src/UploadEncoding.ts","../src/resumableSource.ts","../src/uploadResumable.ts"],"names":["UploadErrorKind","buildHeaders","UploadStatus","UploadEncoding","abortedError"],"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","/** Value of the tus `encoding` metadata entry: how the uploaded bytes are encoded. */\nexport const enum UploadEncoding {\n Gzip = 'gzip',\n Identity = 'identity',\n}\n","import { UploadEncoding } from './UploadEncoding';\n\nconst PERCENT_SCALE = 100;\nconst MAX_PERCENT_BEFORE_SUCCESS = 99;\n\n/** The read side of a byte stream, as tus-js-client accepts it. */\nexport type ByteReader = Pick<ReadableStreamDefaultReader<Uint8Array>, 'read'>;\n\n/** The stream constructors gzip needs; `CompressionStream` is absent on older runtimes. */\nexport interface StreamRuntime {\n CompressionStream?: typeof CompressionStream;\n TransformStream?: typeof TransformStream;\n}\n\n/** What tus-js-client uploads, and how its progress maps back to the source file. */\nexport interface ResumableSource {\n input: Blob | ByteReader;\n encoding: UploadEncoding;\n lengthDeferred: boolean;\n sourceBytesRead: () => number;\n}\n\nfunction identitySource(file: Blob): ResumableSource {\n return { input: file, encoding: UploadEncoding.Identity, lengthDeferred: false, sourceBytesRead: () => 0 };\n}\n\ninterface GzipStreams {\n Compression: typeof CompressionStream;\n Transform: typeof TransformStream;\n}\n\nfunction gzipStreams(gzip: boolean, runtime: StreamRuntime): GzipStreams | undefined {\n const Compression = runtime.CompressionStream;\n const Transform = runtime.TransformStream;\n if (!gzip || Compression === undefined || Transform === undefined) {return undefined;}\n return { Compression, Transform };\n}\n\n/** True when an interrupted upload can resume after a page reload: identity only, never a gzip stream. */\nexport function isResumableAcrossReload(gzip = false, runtime: StreamRuntime = globalThis): boolean {\n return gzipStreams(gzip, runtime) === undefined;\n}\n\n/** Gzip the file as a stream when asked and supported; otherwise upload it as-is (identity). */\nexport function openResumableSource(file: Blob, gzip: boolean, runtime: StreamRuntime = globalThis): ResumableSource {\n const streams = gzipStreams(gzip, runtime);\n if (streams === undefined) {return identitySource(file);}\n const { Compression, Transform } = streams;\n let read = 0;\n const counter = new Transform<Uint8Array<ArrayBuffer>, Uint8Array<ArrayBuffer>>({\n transform(chunk, controller): void {\n read += chunk.byteLength;\n controller.enqueue(chunk);\n },\n });\n const reader = file.stream().pipeThrough(counter).pipeThrough(new Compression('gzip')).getReader();\n return { input: reader, encoding: UploadEncoding.Gzip, lengthDeferred: true, sourceBytesRead: () => read };\n}\n\n/** Whole-number percent; a deferred-length (gzip) upload counts source bytes read and holds at 99 until success. */\nexport function resumablePercent(source: ResumableSource, fileSize: number, bytesSent: number, bytesTotal: number): number {\n if (!source.lengthDeferred) {return Math.floor((bytesSent * PERCENT_SCALE) / Math.max(bytesTotal, 1));}\n const percent = Math.floor((source.sourceBytesRead() * PERCENT_SCALE) / Math.max(fileSize, 1));\n return Math.min(percent, MAX_PERCENT_BEFORE_SUCCESS);\n}\n","import { Upload } from 'tus-js-client';\n\nimport { openResumableSource, resumablePercent } from './resumableSource';\nimport { PERCENT_COMPLETE } from './transports';\nimport { UploadError, toUploadError } from './UploadError';\nimport { UploadErrorKind } from './UploadErrorKind';\n\nimport type { HttpRequest, PreviousUpload, UploadOptions } from 'tus-js-client';\nimport type { ResumableSource } from './resumableSource';\nimport type { ResumableUploadOptions, TusUpload, TusUploadFactory } from './resumableTypes';\nimport type { UploadEncoding } from './UploadEncoding';\n\nconst MEBIBYTE = 1024 * 1024;\nconst DEFAULT_CHUNK_MEBIBYTES = 16;\nexport const DEFAULT_RESUMABLE_CHUNK_SIZE = DEFAULT_CHUNK_MEBIBYTES * MEBIBYTE;\n\nexport const createTusUpload: TusUploadFactory = (input, options) => new Upload(input, options);\n\ninterface UploadCallbacks {\n onProgress: (bytesSent: number, bytesTotal: number) => void;\n onSuccess: () => void;\n onError: (error: Error) => void;\n}\n\ninterface TusResponse {\n getStatus(): number;\n getBody(): string;\n}\n\n/** Upload-Metadata sent on creation: `filename` from the File unless given, `encoding` always from the source. */\nexport function buildResumableMetadata(file: Blob, metadata: Record<string, string> | undefined, encoding: UploadEncoding): Record<string, string> {\n const defaults: Record<string, string> = file instanceof File ? { filename: file.name } : {};\n return { ...defaults, ...metadata, encoding };\n}\n\n/** Stable resume key: the same endpoint, file identity and metadata resume the same tus upload. */\nexport function resumeFingerprint(endpoint: string, file: Blob, metadata: Record<string, string>): string {\n const lastModified = file instanceof File ? file.lastModified : 0;\n const entries = Object.entries(metadata).sort(([a], [b]) => a.localeCompare(b)).map(([key, value]) => `${key}=${value}`);\n return ['content-upload', endpoint, String(file.size), String(lastModified), entries.join('&')].join('|');\n}\n\nfunction sendCookies(request: HttpRequest): void {\n const underlying = request.getUnderlyingObject() as { withCredentials?: boolean } | null;\n if (underlying !== null && typeof underlying === 'object') {underlying.withCredentials = true;}\n}\n\n/** The tus-js-client options for one upload. Exported so consumers can pin the request shape. */\nexport function buildTusOptions(options: ResumableUploadOptions, source: ResumableSource, callbacks: UploadCallbacks): UploadOptions {\n const metadata = buildResumableMetadata(options.file, options.metadata, source.encoding);\n const fingerprint = resumeFingerprint(options.endpoint, options.file, metadata);\n return {\n endpoint: options.endpoint,\n chunkSize: options.chunkSize ?? DEFAULT_RESUMABLE_CHUNK_SIZE,\n metadata,\n headers: { ...options.headers },\n uploadLengthDeferred: source.lengthDeferred,\n fingerprint: () => Promise.resolve(fingerprint),\n storeFingerprintForResuming: !source.lengthDeferred,\n removeFingerprintOnSuccess: true,\n onBeforeRequest: options.withCredentials === false ? undefined : sendCookies,\n ...callbacks,\n };\n}\n\n/** The upload id is the last path segment of the tus upload URL (the BFF-rewritten Location). */\nexport function uploadIdFromUrl(url: string | null): string {\n const path = (url ?? '').split('?', 1).join('');\n const id = path.split('/').filter((segment) => segment !== '').pop();\n if (id === undefined) {throw new UploadError(UploadErrorKind.InvalidResponse, 'tus upload finished without an upload URL.');}\n return id;\n}\n\n/** Map a tus-js-client failure to `UploadError`: a server answer is `Http`, anything else `Network`. */\nexport function toResumableError(error: Error): UploadError {\n const response = (error as { originalResponse?: TusResponse | null }).originalResponse;\n if (response === undefined || response === null) {return toUploadError(error);}\n const status = response.getStatus();\n return new UploadError(UploadErrorKind.Http, `Upload failed (${String(status)}).`, status, response.getBody());\n}\n\n/** The previous upload with the newest `creationTime`, if any. */\nexport function newestPreviousUpload(previous: PreviousUpload[]): PreviousUpload | undefined {\n return [...previous].sort((a, b) => b.creationTime.localeCompare(a.creationTime))[0];\n}\n\nasync function startOrResume(upload: TusUpload, source: ResumableSource): Promise<void> {\n if (!source.lengthDeferred) {\n const previous = newestPreviousUpload(await upload.findPreviousUploads());\n if (previous !== undefined) {upload.resumeFromPreviousUpload(previous);}\n }\n upload.start();\n}\n\nfunction abortedError(): UploadError {\n return new UploadError(UploadErrorKind.Aborted, 'Upload was cancelled.');\n}\n\n/** tus 1.0 upload (16 MiB chunks, optional streamed gzip); identity resumes across reloads, gzip only in-session. */\nexport function uploadResumable(options: ResumableUploadOptions): Promise<string> {\n if (options.signal?.aborted === true) {return Promise.reject(abortedError());}\n const source = openResumableSource(options.file, options.gzip === true);\n const factory = options.uploadFactory ?? createTusUpload;\n return new Promise<string>((resolve, reject) => {\n const settle = (finish: () => void): void => {\n options.signal?.removeEventListener('abort', onAbort);\n finish();\n };\n const upload = factory(source.input, buildTusOptions(options, source, {\n onProgress: (sent, total) => options.onProgress?.(resumablePercent(source, options.file.size, sent, total)),\n onSuccess: () => settle(() => {\n options.onProgress?.(PERCENT_COMPLETE);\n try { resolve(uploadIdFromUrl(upload.url)); } catch (error) { reject(error); }\n }),\n onError: (error) => settle(() => reject(toResumableError(error))),\n }));\n function onAbort(): void {\n settle(() => reject(abortedError()));\n upload.abort(true).catch(() => undefined);\n }\n options.signal?.addEventListener('abort', onAbort, { once: true });\n startOrResume(upload, source).catch((error: unknown) => settle(() => reject(toUploadError(error))));\n });\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dloizides/content-upload",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.1",
|
|
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",
|