@universal-uploader/core 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/dist/const.d.ts +19 -0
  2. package/dist/createUpload-Bv4Z2y9b.js +155 -0
  3. package/dist/createUpload-Bv4Z2y9b.js.map +1 -0
  4. package/dist/createUpload-CePIp5sg.js +123 -0
  5. package/dist/createUpload-CePIp5sg.js.map +1 -0
  6. package/dist/createUpload-DfsX80rY.js +121 -0
  7. package/dist/createUpload-DfsX80rY.js.map +1 -0
  8. package/dist/createUpload-wm9hFbGi.js +153 -0
  9. package/dist/createUpload-wm9hFbGi.js.map +1 -0
  10. package/dist/createUpload.d.ts +10 -0
  11. package/dist/helper.d.ts +74 -0
  12. package/dist/index-BS99wlKk.js +214 -0
  13. package/dist/index-BS99wlKk.js.map +1 -0
  14. package/dist/index-CZdguoWB.js +216 -0
  15. package/dist/index-CZdguoWB.js.map +1 -0
  16. package/dist/index.cjs +954 -0
  17. package/dist/index.cjs.map +1 -0
  18. package/dist/index.d.ts +144 -0
  19. package/dist/index.js +952 -0
  20. package/dist/index.js.map +1 -0
  21. package/dist/orchestrator.d.ts +6 -0
  22. package/dist/stream/helper.d.ts +28 -0
  23. package/dist/stream/index.d.ts +7 -0
  24. package/dist/stream/index.js +79 -0
  25. package/dist/stream-chunked/helper.d.ts +9 -0
  26. package/dist/stream-chunked/index.d.ts +5 -0
  27. package/dist/stream.cjs +249 -0
  28. package/dist/stream.cjs.map +1 -0
  29. package/dist/stream.d.ts +10 -0
  30. package/dist/stream.js +247 -0
  31. package/dist/stream.js.map +1 -0
  32. package/dist/types-CLZnJDsz.d.ts +111 -0
  33. package/dist/types.d.ts +127 -0
  34. package/dist/utils-B4LG-_oN.js +12 -0
  35. package/dist/utils-B4LG-_oN.js.map +1 -0
  36. package/dist/utils-MtT6SgZa.js +10 -0
  37. package/dist/utils-MtT6SgZa.js.map +1 -0
  38. package/dist/utils.d.ts +5 -0
  39. package/dist/xhr-chuncked/helper.d.ts +27 -0
  40. package/dist/xhr-chuncked/index.d.ts +7 -0
  41. package/dist/xhr-chuncked/index.js +181 -0
  42. package/dist/xhr.cjs +142 -0
  43. package/dist/xhr.cjs.map +1 -0
  44. package/dist/xhr.d.ts +10 -0
  45. package/dist/xhr.js +140 -0
  46. package/dist/xhr.js.map +1 -0
  47. package/package.json +34 -0
@@ -0,0 +1,10 @@
1
+ import { Upload, UploadResponse } from "./types";
2
+ type UploadExecutor = (args: Upload & {
3
+ refresh: () => void;
4
+ }) => Promise<UploadResponse> | UploadResponse;
5
+ /**
6
+ * Creates an upload orchestrator around a specific uploader executor.
7
+ * 특정 업로더 실행기를 중심으로 업로드 오케스트레이터를 생성합니다.
8
+ */
9
+ export declare const createUpload: (resolveUploadExecutor: (uploadArgs: Upload) => UploadExecutor) => ({ url, file, options: { method, ...options } }: Upload, retryAttempt?: number) => Promise<UploadResponse>;
10
+ export {};
@@ -0,0 +1,74 @@
1
+ import type { StreamUploaderParams } from "./types";
2
+ /**
3
+ * Metadata for chunked uploads.
4
+ * 청크 단위 업로드를 위한 메타데이터입니다.
5
+ */
6
+ export interface ChunkUploadSizesMeta {
7
+ /** Safe size of each chunk. / 안전한 각 청크의 크기. */
8
+ safeChunkSize: number;
9
+ /** Total file size. / 전체 파일 크기. */
10
+ totalFileSize: number;
11
+ /** Total number of chunks. / 전체 청크 수. */
12
+ totalChunks: number;
13
+ }
14
+ /**
15
+ * Calculates metadata for chunked uploads, such as safe chunk size and total chunks.
16
+ * 안전한 청크 크기 및 전체 청크 수와 같은 청크 업로드용 메타데이터를 계산합니다.
17
+ */
18
+ export declare const calculateSizes: ({ chunkSize, fileSize, }: {
19
+ chunkSize: number;
20
+ fileSize: number;
21
+ }) => ChunkUploadSizesMeta;
22
+ /**
23
+ * Calculates chunk progress state.
24
+ * 청크 진행 상태(isLastChunk, percentage)를 계산합니다.
25
+ */
26
+ export declare const calculateChunkProgress: ({ loaded, total, }: {
27
+ loaded: number;
28
+ total: number;
29
+ }) => {
30
+ isLastChunk: boolean;
31
+ percentage: number;
32
+ };
33
+ /**
34
+ * Calculates the resume start position from a persisted offset.
35
+ * 저장된 오프셋으로부터 재개 시작 위치를 계산합니다.
36
+ */
37
+ export declare const calculateResumePosition: ({ offset, chunkSize, }: {
38
+ offset?: number;
39
+ chunkSize: number;
40
+ }) => {
41
+ startChunkIndex: number;
42
+ startOffset: number;
43
+ };
44
+ /**
45
+ * Creates a Uint8Array buffer from a specific slice of the file.
46
+ * 파일의 특정 부분을 잘라 Uint8Array 버퍼를 생성합니다.
47
+ */
48
+ export declare const createBuffer: ({ file, offset, chunkSize, }: StreamUploaderParams & {
49
+ offset: number;
50
+ }) => Promise<Uint8Array<ArrayBuffer>>;
51
+ /**
52
+ * Returns chunk-related headers merged with custom headers.
53
+ * 청크 관련 헤더와 커스텀 헤더를 병합해 반환합니다.
54
+ */
55
+ export declare const getCustomHeaders: ({ customHeaders, chunkIndex, totalChunks, chunkSize, totalFileSize, fileName, }: {
56
+ customHeaders?: Record<string, string>;
57
+ chunkIndex?: number;
58
+ totalChunks?: number;
59
+ chunkSize?: number;
60
+ totalFileSize?: number;
61
+ fileName: string;
62
+ }) => Record<string, string>;
63
+ /**
64
+ * Initializes a RequestInit object and AbortController for streaming uploads.
65
+ * 스트리밍 업로드를 위한 RequestInit 객체 및 AbortController를 초기화합니다.
66
+ */
67
+ export declare const initializeStream: ({ body, withCredentials, customHeaders, }: {
68
+ body: ReadableStream;
69
+ withCredentials: boolean;
70
+ customHeaders: Record<string, string>;
71
+ }) => {
72
+ abortController: AbortController;
73
+ streamInit: Readonly<RequestInit>;
74
+ };
@@ -0,0 +1,214 @@
1
+ const HTTP_STATUS_SUCCESS_MIN = 200;
2
+ const HTTP_STATUS_SUCCESS_MAX_EXCLUSIVE = 300;
3
+ /**
4
+ * Checks if the given HTTP status code indicates success (2xx).
5
+ * 주어진 HTTP 상태 코드가 성공(2xx)을 나타내는지 확인합니다.
6
+ */
7
+ const isSuccessfulHttpStatus = (status) => status >= HTTP_STATUS_SUCCESS_MIN && status < HTTP_STATUS_SUCCESS_MAX_EXCLUSIVE;
8
+ /**
9
+ * Calculates metadata for chunked uploads, such as safe chunk size and total chunks.
10
+ * 안전한 청크 크기 및 전체 청크 수와 같은 청크 업로드용 메타데이터를 계산합니다.
11
+ */
12
+ const getChunkUploadMeta = ({ chunkSize, fileSize, }) => {
13
+ const safeChunkSize = Math.max(1, chunkSize);
14
+ const totalFileSize = fileSize;
15
+ const totalChunks = Math.ceil(totalFileSize / safeChunkSize);
16
+ return {
17
+ safeChunkSize,
18
+ totalFileSize,
19
+ totalChunks,
20
+ };
21
+ };
22
+ /**
23
+ * Calculates the start and end byte positions for a specific chunk.
24
+ * 특정 청크의 시작 및 종료 바이트 위치를 계산합니다.
25
+ */
26
+ const getChunkRange = ({ chunkIndex, safeChunkSize, totalFileSize, }) => {
27
+ const start = chunkIndex * safeChunkSize;
28
+ const end = Math.min(start + safeChunkSize, totalFileSize);
29
+ return {
30
+ start,
31
+ end,
32
+ };
33
+ };
34
+ /**
35
+ * Applies chunking-related headers and custom headers to the XMLHttpRequest object.
36
+ * XMLHttpRequest 객체에 청크 관련 헤더 및 커스텀 헤더를 적용합니다.
37
+ */
38
+ const applyChunkHeaders = ({ xhr, customHeaders, chunkIndex, totalChunks, safeChunkSize, totalFileSize, }) => {
39
+ xhr.setRequestHeader('X-Chunk-Index', String(chunkIndex));
40
+ xhr.setRequestHeader('X-Total-Chunks', String(totalChunks));
41
+ xhr.setRequestHeader('X-Chunk-Size', String(safeChunkSize));
42
+ xhr.setRequestHeader('X-File-Size', String(totalFileSize));
43
+ Object.entries(customHeaders).forEach(([key, value]) => {
44
+ xhr.setRequestHeader(key, value);
45
+ });
46
+ };
47
+
48
+ /**
49
+ * Handles file upload as a single request without chunking using XMLHttpRequest.
50
+ * XMLHttpRequest를 사용하여 파일을 청크 분할 없이 단일 요청으로 업로드합니다.
51
+ */
52
+ const uploadWithoutChunking = ({ url, file, refresh, customHeaders, onProgress, }) => {
53
+ const xhr = new XMLHttpRequest();
54
+ const response = new Promise((resolve, reject) => {
55
+ xhr.open('POST', url);
56
+ Object.entries(customHeaders).forEach(([key, value]) => {
57
+ xhr.setRequestHeader(key, value);
58
+ });
59
+ if (onProgress) {
60
+ xhr.upload.onprogress = (event) => {
61
+ const total = event.total || file.size;
62
+ const percentage = total === 0 ? 100 : (event.loaded / total) * 100;
63
+ onProgress({
64
+ loaded: event.loaded,
65
+ total,
66
+ percentage,
67
+ });
68
+ };
69
+ }
70
+ xhr.onload = () => {
71
+ if (isSuccessfulHttpStatus(xhr.status)) {
72
+ if (onProgress) {
73
+ onProgress({ loaded: file.size, total: file.size, percentage: 100 });
74
+ }
75
+ resolve({
76
+ ok: true,
77
+ total: file.size,
78
+ message: undefined,
79
+ status: 'success',
80
+ });
81
+ return;
82
+ }
83
+ reject(new Error(`Upload failed with status ${xhr.status}`));
84
+ };
85
+ xhr.onerror = (e) => reject(e);
86
+ xhr.onabort = () => {
87
+ reject(new DOMException('Aborted', 'AbortError'));
88
+ };
89
+ xhr.send(file);
90
+ });
91
+ return {
92
+ result: response.then((res) => ({
93
+ ok: res.ok,
94
+ total: file.size,
95
+ message: undefined,
96
+ status: 'success',
97
+ })),
98
+ actions: {
99
+ abort: () => xhr.abort(),
100
+ refresh: () => {
101
+ xhr.abort();
102
+ refresh();
103
+ },
104
+ },
105
+ };
106
+ };
107
+ /**
108
+ * Uploads a file by splitting it into sequential chunks using XMLHttpRequest.
109
+ * XMLHttpRequest를 사용하여 파일을 여러 개의 청크로 나누어 순차적으로 업로드합니다.
110
+ */
111
+ const uploadWithXhrChuncked = async ({ url, file, refresh, options: { chunkSize, customHeaders = {}, onProgress }, }) => {
112
+ const response = {
113
+ result: Promise.resolve({ ok: false, total: 0, message: undefined, status: 'idle' }),
114
+ actions: {
115
+ abort: () => null,
116
+ refresh: () => null,
117
+ },
118
+ };
119
+ if (!chunkSize || chunkSize <= 0) {
120
+ return uploadWithoutChunking({ url, file, refresh, customHeaders, onProgress });
121
+ }
122
+ const { safeChunkSize, totalFileSize, totalChunks } = getChunkUploadMeta({
123
+ chunkSize,
124
+ fileSize: file.size,
125
+ });
126
+ if (totalFileSize === 0) {
127
+ if (onProgress) {
128
+ onProgress({ loaded: 0, total: 0, percentage: 100 });
129
+ }
130
+ response.result = Promise.resolve({
131
+ ok: true,
132
+ total: 0,
133
+ message: undefined,
134
+ status: 'success',
135
+ });
136
+ return response;
137
+ }
138
+ /**
139
+ * Orchestrates the sequential upload of all chunks.
140
+ * 모든 청크의 순차적 업로드를 조율합니다.
141
+ */
142
+ const chunkUpload = async () => {
143
+ let uploadResult = {
144
+ ok: false,
145
+ total: 0,
146
+ message: undefined,
147
+ status: 'uploading',
148
+ };
149
+ for (let chunkIndex = 0; chunkIndex < totalChunks; chunkIndex += 1) {
150
+ const { start, end } = getChunkRange({
151
+ chunkIndex,
152
+ safeChunkSize,
153
+ totalFileSize,
154
+ });
155
+ const uploadPromise = new Promise((resolve, reject) => {
156
+ const xhr = new XMLHttpRequest();
157
+ xhr.open('POST', url);
158
+ applyChunkHeaders({
159
+ xhr,
160
+ customHeaders,
161
+ chunkIndex,
162
+ totalChunks,
163
+ safeChunkSize,
164
+ totalFileSize,
165
+ });
166
+ xhr.onload = () => {
167
+ if (isSuccessfulHttpStatus(xhr.status)) {
168
+ const isLastChunk = end >= totalFileSize;
169
+ const percentage = isLastChunk ? 100 : (end / totalFileSize) * 100;
170
+ if (onProgress) {
171
+ onProgress({
172
+ loaded: end,
173
+ total: totalFileSize,
174
+ percentage: totalFileSize === 0 ? 100 : percentage,
175
+ });
176
+ }
177
+ resolve({
178
+ ok: true,
179
+ total: totalFileSize,
180
+ message: undefined,
181
+ status: isLastChunk ? 'success' : 'uploading',
182
+ });
183
+ return;
184
+ }
185
+ reject(new Error(`Chunk upload failed with status ${xhr.status}`));
186
+ };
187
+ xhr.onerror = (e) => reject(e);
188
+ xhr.onabort = () => {
189
+ const abortError = new DOMException('Aborted', 'AbortError');
190
+ reject(abortError);
191
+ };
192
+ const chunk = file.slice(start, end);
193
+ xhr.send(chunk);
194
+ // Update actions to refer to the current XHR request.
195
+ // 현재 XHR 요청을 참조하도록 액션을 업데이트합니다.
196
+ response.actions.abort = () => xhr.abort();
197
+ response.actions.refresh = () => {
198
+ xhr.abort();
199
+ refresh();
200
+ };
201
+ });
202
+ // Sequential execution using await within a loop for chunked transfer.
203
+ // 청크 전송을 위해 루프 내에서 await를 사용하여 순차적으로 실행합니다.
204
+ // eslint-disable-next-line no-await-in-loop -- chunked mode intentionally uploads sequentially
205
+ uploadResult = await uploadPromise;
206
+ }
207
+ return uploadResult;
208
+ };
209
+ response.result = chunkUpload();
210
+ return response;
211
+ };
212
+
213
+ export { uploadWithXhrChuncked as u };
214
+ //# sourceMappingURL=index-BS99wlKk.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index-BS99wlKk.js","sources":["../src/xhr-chuncked/helper.ts","../src/xhr-chuncked/index.ts"],"sourcesContent":["interface ChunkUploadMeta {\n safeChunkSize: number;\n totalFileSize: number;\n totalChunks: number;\n}\n\nconst HTTP_STATUS_SUCCESS_MIN = 200;\nconst HTTP_STATUS_SUCCESS_MAX_EXCLUSIVE = 300;\n\n/**\n * Checks if the given HTTP status code indicates success (2xx).\n * 주어진 HTTP 상태 코드가 성공(2xx)을 나타내는지 확인합니다.\n */\nexport const isSuccessfulHttpStatus = (status: number) =>\n status >= HTTP_STATUS_SUCCESS_MIN && status < HTTP_STATUS_SUCCESS_MAX_EXCLUSIVE;\n\n/**\n * Calculates metadata for chunked uploads, such as safe chunk size and total chunks.\n * 안전한 청크 크기 및 전체 청크 수와 같은 청크 업로드용 메타데이터를 계산합니다.\n */\nexport const getChunkUploadMeta = ({\n chunkSize,\n fileSize,\n}: {\n chunkSize: number;\n fileSize: number;\n}): ChunkUploadMeta => {\n const safeChunkSize = Math.max(1, chunkSize);\n const totalFileSize = fileSize;\n const totalChunks = Math.ceil(totalFileSize / safeChunkSize);\n\n return {\n safeChunkSize,\n totalFileSize,\n totalChunks,\n };\n};\n\n/**\n * Calculates the start and end byte positions for a specific chunk.\n * 특정 청크의 시작 및 종료 바이트 위치를 계산합니다.\n */\nexport const getChunkRange = ({\n chunkIndex,\n safeChunkSize,\n totalFileSize,\n}: {\n chunkIndex: number;\n safeChunkSize: number;\n totalFileSize: number;\n}) => {\n const start = chunkIndex * safeChunkSize;\n const end = Math.min(start + safeChunkSize, totalFileSize);\n\n return {\n start,\n end,\n };\n};\n\n/**\n * Applies chunking-related headers and custom headers to the XMLHttpRequest object.\n * XMLHttpRequest 객체에 청크 관련 헤더 및 커스텀 헤더를 적용합니다.\n */\nexport const applyChunkHeaders = ({\n xhr,\n customHeaders,\n chunkIndex,\n totalChunks,\n safeChunkSize,\n totalFileSize,\n}: {\n xhr: XMLHttpRequest;\n customHeaders: Record<string, string>;\n chunkIndex: number;\n totalChunks: number;\n safeChunkSize: number;\n totalFileSize: number;\n}) => {\n xhr.setRequestHeader('X-Chunk-Index', String(chunkIndex));\n xhr.setRequestHeader('X-Total-Chunks', String(totalChunks));\n xhr.setRequestHeader('X-Chunk-Size', String(safeChunkSize));\n xhr.setRequestHeader('X-File-Size', String(totalFileSize));\n\n Object.entries(customHeaders).forEach(([key, value]) => {\n xhr.setRequestHeader(key, value);\n });\n};\n","import { UploadParams, UploadResponse, OnProgressParams, UploadResult } from '../index';\nimport {\n isSuccessfulHttpStatus,\n getChunkUploadMeta,\n getChunkRange,\n applyChunkHeaders,\n} from './helper';\n\n/**\n * Handles file upload as a single request without chunking using XMLHttpRequest.\n * XMLHttpRequest를 사용하여 파일을 청크 분할 없이 단일 요청으로 업로드합니다.\n */\nconst uploadWithoutChunking = ({\n url,\n file,\n refresh,\n customHeaders,\n onProgress,\n}: {\n url: string;\n file: File;\n refresh: () => void;\n customHeaders: Record<string, string>;\n onProgress?: (args: OnProgressParams) => void;\n}): UploadResponse => {\n const xhr = new XMLHttpRequest();\n\n const response = new Promise<UploadResult>((resolve, reject) => {\n xhr.open('POST', url);\n\n Object.entries(customHeaders).forEach(([key, value]) => {\n xhr.setRequestHeader(key, value);\n });\n\n if (onProgress) {\n xhr.upload.onprogress = (event) => {\n const total = event.total || file.size;\n const percentage = total === 0 ? 100 : (event.loaded / total) * 100;\n\n onProgress({\n loaded: event.loaded,\n total,\n percentage,\n });\n };\n }\n\n xhr.onload = () => {\n if (isSuccessfulHttpStatus(xhr.status)) {\n if (onProgress) {\n onProgress({ loaded: file.size, total: file.size, percentage: 100 });\n }\n\n resolve({\n ok: true,\n total: file.size,\n message: undefined,\n status: 'success',\n });\n\n return;\n }\n\n reject(new Error(`Upload failed with status ${xhr.status}`));\n };\n\n xhr.onerror = (e) => reject(e);\n xhr.onabort = () => {\n reject(new DOMException('Aborted', 'AbortError'));\n };\n xhr.send(file);\n });\n\n return {\n result: response.then((res) => ({\n ok: res.ok,\n total: file.size,\n message: undefined,\n status: 'success',\n })),\n actions: {\n abort: () => xhr.abort(),\n refresh: () => {\n xhr.abort();\n refresh();\n },\n },\n };\n};\n\n/**\n * Uploads a file by splitting it into sequential chunks using XMLHttpRequest.\n * XMLHttpRequest를 사용하여 파일을 여러 개의 청크로 나누어 순차적으로 업로드합니다.\n */\nconst uploadWithXhrChuncked = async ({\n url,\n file,\n refresh,\n options: { chunkSize, customHeaders = {}, onProgress },\n}: UploadParams & { refresh: () => void }): Promise<UploadResponse> => {\n const response: UploadResponse = {\n result: Promise.resolve({ ok: false, total: 0, message: undefined, status: 'idle' }),\n actions: {\n abort: () => null,\n refresh: () => null,\n },\n };\n\n if (!chunkSize || chunkSize <= 0) {\n return uploadWithoutChunking({ url, file, refresh, customHeaders, onProgress });\n }\n\n const { safeChunkSize, totalFileSize, totalChunks } = getChunkUploadMeta({\n chunkSize,\n fileSize: file.size,\n });\n\n if (totalFileSize === 0) {\n if (onProgress) {\n onProgress({ loaded: 0, total: 0, percentage: 100 });\n }\n\n response.result = Promise.resolve({\n ok: true,\n total: 0,\n message: undefined,\n status: 'success',\n });\n\n return response;\n }\n\n /**\n * Orchestrates the sequential upload of all chunks.\n * 모든 청크의 순차적 업로드를 조율합니다.\n */\n const chunkUpload = async (): Promise<Readonly<UploadResult>> => {\n let uploadResult: Readonly<UploadResult> = {\n ok: false,\n total: 0,\n message: undefined,\n status: 'uploading',\n };\n\n for (let chunkIndex = 0; chunkIndex < totalChunks; chunkIndex += 1) {\n const { start, end } = getChunkRange({\n chunkIndex,\n safeChunkSize,\n totalFileSize,\n });\n\n const uploadPromise = new Promise<Readonly<UploadResult>>((resolve, reject) => {\n const xhr = new XMLHttpRequest();\n\n xhr.open('POST', url);\n\n applyChunkHeaders({\n xhr,\n customHeaders,\n chunkIndex,\n totalChunks,\n safeChunkSize,\n totalFileSize,\n });\n\n xhr.onload = () => {\n if (isSuccessfulHttpStatus(xhr.status)) {\n const isLastChunk = end >= totalFileSize;\n const percentage = isLastChunk ? 100 : (end / totalFileSize) * 100;\n\n if (onProgress) {\n onProgress({\n loaded: end,\n total: totalFileSize,\n percentage: totalFileSize === 0 ? 100 : percentage,\n });\n }\n\n resolve({\n ok: true,\n total: totalFileSize,\n message: undefined,\n status: isLastChunk ? 'success' : 'uploading',\n });\n\n return;\n }\n\n reject(new Error(`Chunk upload failed with status ${xhr.status}`));\n };\n\n xhr.onerror = (e) => reject(e);\n xhr.onabort = () => {\n const abortError = new DOMException('Aborted', 'AbortError');\n reject(abortError);\n };\n\n const chunk = file.slice(start, end);\n xhr.send(chunk);\n\n // Update actions to refer to the current XHR request.\n // 현재 XHR 요청을 참조하도록 액션을 업데이트합니다.\n response.actions.abort = () => xhr.abort();\n response.actions.refresh = () => {\n xhr.abort();\n refresh();\n };\n });\n\n // Sequential execution using await within a loop for chunked transfer.\n // 청크 전송을 위해 루프 내에서 await를 사용하여 순차적으로 실행합니다.\n // eslint-disable-next-line no-await-in-loop -- chunked mode intentionally uploads sequentially\n uploadResult = await uploadPromise;\n }\n\n return uploadResult;\n };\n\n response.result = chunkUpload();\n\n return response;\n};\n\nexport default uploadWithXhrChuncked;\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,216 @@
1
+ 'use strict';
2
+
3
+ const HTTP_STATUS_SUCCESS_MIN = 200;
4
+ const HTTP_STATUS_SUCCESS_MAX_EXCLUSIVE = 300;
5
+ /**
6
+ * Checks if the given HTTP status code indicates success (2xx).
7
+ * 주어진 HTTP 상태 코드가 성공(2xx)을 나타내는지 확인합니다.
8
+ */
9
+ const isSuccessfulHttpStatus = (status) => status >= HTTP_STATUS_SUCCESS_MIN && status < HTTP_STATUS_SUCCESS_MAX_EXCLUSIVE;
10
+ /**
11
+ * Calculates metadata for chunked uploads, such as safe chunk size and total chunks.
12
+ * 안전한 청크 크기 및 전체 청크 수와 같은 청크 업로드용 메타데이터를 계산합니다.
13
+ */
14
+ const getChunkUploadMeta = ({ chunkSize, fileSize, }) => {
15
+ const safeChunkSize = Math.max(1, chunkSize);
16
+ const totalFileSize = fileSize;
17
+ const totalChunks = Math.ceil(totalFileSize / safeChunkSize);
18
+ return {
19
+ safeChunkSize,
20
+ totalFileSize,
21
+ totalChunks,
22
+ };
23
+ };
24
+ /**
25
+ * Calculates the start and end byte positions for a specific chunk.
26
+ * 특정 청크의 시작 및 종료 바이트 위치를 계산합니다.
27
+ */
28
+ const getChunkRange = ({ chunkIndex, safeChunkSize, totalFileSize, }) => {
29
+ const start = chunkIndex * safeChunkSize;
30
+ const end = Math.min(start + safeChunkSize, totalFileSize);
31
+ return {
32
+ start,
33
+ end,
34
+ };
35
+ };
36
+ /**
37
+ * Applies chunking-related headers and custom headers to the XMLHttpRequest object.
38
+ * XMLHttpRequest 객체에 청크 관련 헤더 및 커스텀 헤더를 적용합니다.
39
+ */
40
+ const applyChunkHeaders = ({ xhr, customHeaders, chunkIndex, totalChunks, safeChunkSize, totalFileSize, }) => {
41
+ xhr.setRequestHeader('X-Chunk-Index', String(chunkIndex));
42
+ xhr.setRequestHeader('X-Total-Chunks', String(totalChunks));
43
+ xhr.setRequestHeader('X-Chunk-Size', String(safeChunkSize));
44
+ xhr.setRequestHeader('X-File-Size', String(totalFileSize));
45
+ Object.entries(customHeaders).forEach(([key, value]) => {
46
+ xhr.setRequestHeader(key, value);
47
+ });
48
+ };
49
+
50
+ /**
51
+ * Handles file upload as a single request without chunking using XMLHttpRequest.
52
+ * XMLHttpRequest를 사용하여 파일을 청크 분할 없이 단일 요청으로 업로드합니다.
53
+ */
54
+ const uploadWithoutChunking = ({ url, file, refresh, customHeaders, onProgress, }) => {
55
+ const xhr = new XMLHttpRequest();
56
+ const response = new Promise((resolve, reject) => {
57
+ xhr.open('POST', url);
58
+ Object.entries(customHeaders).forEach(([key, value]) => {
59
+ xhr.setRequestHeader(key, value);
60
+ });
61
+ if (onProgress) {
62
+ xhr.upload.onprogress = (event) => {
63
+ const total = event.total || file.size;
64
+ const percentage = total === 0 ? 100 : (event.loaded / total) * 100;
65
+ onProgress({
66
+ loaded: event.loaded,
67
+ total,
68
+ percentage,
69
+ });
70
+ };
71
+ }
72
+ xhr.onload = () => {
73
+ if (isSuccessfulHttpStatus(xhr.status)) {
74
+ if (onProgress) {
75
+ onProgress({ loaded: file.size, total: file.size, percentage: 100 });
76
+ }
77
+ resolve({
78
+ ok: true,
79
+ total: file.size,
80
+ message: undefined,
81
+ status: 'success',
82
+ });
83
+ return;
84
+ }
85
+ reject(new Error(`Upload failed with status ${xhr.status}`));
86
+ };
87
+ xhr.onerror = (e) => reject(e);
88
+ xhr.onabort = () => {
89
+ reject(new DOMException('Aborted', 'AbortError'));
90
+ };
91
+ xhr.send(file);
92
+ });
93
+ return {
94
+ result: response.then((res) => ({
95
+ ok: res.ok,
96
+ total: file.size,
97
+ message: undefined,
98
+ status: 'success',
99
+ })),
100
+ actions: {
101
+ abort: () => xhr.abort(),
102
+ refresh: () => {
103
+ xhr.abort();
104
+ refresh();
105
+ },
106
+ },
107
+ };
108
+ };
109
+ /**
110
+ * Uploads a file by splitting it into sequential chunks using XMLHttpRequest.
111
+ * XMLHttpRequest를 사용하여 파일을 여러 개의 청크로 나누어 순차적으로 업로드합니다.
112
+ */
113
+ const uploadWithXhrChuncked = async ({ url, file, refresh, options: { chunkSize, customHeaders = {}, onProgress }, }) => {
114
+ const response = {
115
+ result: Promise.resolve({ ok: false, total: 0, message: undefined, status: 'idle' }),
116
+ actions: {
117
+ abort: () => null,
118
+ refresh: () => null,
119
+ },
120
+ };
121
+ if (!chunkSize || chunkSize <= 0) {
122
+ return uploadWithoutChunking({ url, file, refresh, customHeaders, onProgress });
123
+ }
124
+ const { safeChunkSize, totalFileSize, totalChunks } = getChunkUploadMeta({
125
+ chunkSize,
126
+ fileSize: file.size,
127
+ });
128
+ if (totalFileSize === 0) {
129
+ if (onProgress) {
130
+ onProgress({ loaded: 0, total: 0, percentage: 100 });
131
+ }
132
+ response.result = Promise.resolve({
133
+ ok: true,
134
+ total: 0,
135
+ message: undefined,
136
+ status: 'success',
137
+ });
138
+ return response;
139
+ }
140
+ /**
141
+ * Orchestrates the sequential upload of all chunks.
142
+ * 모든 청크의 순차적 업로드를 조율합니다.
143
+ */
144
+ const chunkUpload = async () => {
145
+ let uploadResult = {
146
+ ok: false,
147
+ total: 0,
148
+ message: undefined,
149
+ status: 'uploading',
150
+ };
151
+ for (let chunkIndex = 0; chunkIndex < totalChunks; chunkIndex += 1) {
152
+ const { start, end } = getChunkRange({
153
+ chunkIndex,
154
+ safeChunkSize,
155
+ totalFileSize,
156
+ });
157
+ const uploadPromise = new Promise((resolve, reject) => {
158
+ const xhr = new XMLHttpRequest();
159
+ xhr.open('POST', url);
160
+ applyChunkHeaders({
161
+ xhr,
162
+ customHeaders,
163
+ chunkIndex,
164
+ totalChunks,
165
+ safeChunkSize,
166
+ totalFileSize,
167
+ });
168
+ xhr.onload = () => {
169
+ if (isSuccessfulHttpStatus(xhr.status)) {
170
+ const isLastChunk = end >= totalFileSize;
171
+ const percentage = isLastChunk ? 100 : (end / totalFileSize) * 100;
172
+ if (onProgress) {
173
+ onProgress({
174
+ loaded: end,
175
+ total: totalFileSize,
176
+ percentage: totalFileSize === 0 ? 100 : percentage,
177
+ });
178
+ }
179
+ resolve({
180
+ ok: true,
181
+ total: totalFileSize,
182
+ message: undefined,
183
+ status: isLastChunk ? 'success' : 'uploading',
184
+ });
185
+ return;
186
+ }
187
+ reject(new Error(`Chunk upload failed with status ${xhr.status}`));
188
+ };
189
+ xhr.onerror = (e) => reject(e);
190
+ xhr.onabort = () => {
191
+ const abortError = new DOMException('Aborted', 'AbortError');
192
+ reject(abortError);
193
+ };
194
+ const chunk = file.slice(start, end);
195
+ xhr.send(chunk);
196
+ // Update actions to refer to the current XHR request.
197
+ // 현재 XHR 요청을 참조하도록 액션을 업데이트합니다.
198
+ response.actions.abort = () => xhr.abort();
199
+ response.actions.refresh = () => {
200
+ xhr.abort();
201
+ refresh();
202
+ };
203
+ });
204
+ // Sequential execution using await within a loop for chunked transfer.
205
+ // 청크 전송을 위해 루프 내에서 await를 사용하여 순차적으로 실행합니다.
206
+ // eslint-disable-next-line no-await-in-loop -- chunked mode intentionally uploads sequentially
207
+ uploadResult = await uploadPromise;
208
+ }
209
+ return uploadResult;
210
+ };
211
+ response.result = chunkUpload();
212
+ return response;
213
+ };
214
+
215
+ exports.uploadWithXhrChuncked = uploadWithXhrChuncked;
216
+ //# sourceMappingURL=index-CZdguoWB.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index-CZdguoWB.js","sources":["../src/xhr-chuncked/helper.ts","../src/xhr-chuncked/index.ts"],"sourcesContent":["interface ChunkUploadMeta {\n safeChunkSize: number;\n totalFileSize: number;\n totalChunks: number;\n}\n\nconst HTTP_STATUS_SUCCESS_MIN = 200;\nconst HTTP_STATUS_SUCCESS_MAX_EXCLUSIVE = 300;\n\n/**\n * Checks if the given HTTP status code indicates success (2xx).\n * 주어진 HTTP 상태 코드가 성공(2xx)을 나타내는지 확인합니다.\n */\nexport const isSuccessfulHttpStatus = (status: number) =>\n status >= HTTP_STATUS_SUCCESS_MIN && status < HTTP_STATUS_SUCCESS_MAX_EXCLUSIVE;\n\n/**\n * Calculates metadata for chunked uploads, such as safe chunk size and total chunks.\n * 안전한 청크 크기 및 전체 청크 수와 같은 청크 업로드용 메타데이터를 계산합니다.\n */\nexport const getChunkUploadMeta = ({\n chunkSize,\n fileSize,\n}: {\n chunkSize: number;\n fileSize: number;\n}): ChunkUploadMeta => {\n const safeChunkSize = Math.max(1, chunkSize);\n const totalFileSize = fileSize;\n const totalChunks = Math.ceil(totalFileSize / safeChunkSize);\n\n return {\n safeChunkSize,\n totalFileSize,\n totalChunks,\n };\n};\n\n/**\n * Calculates the start and end byte positions for a specific chunk.\n * 특정 청크의 시작 및 종료 바이트 위치를 계산합니다.\n */\nexport const getChunkRange = ({\n chunkIndex,\n safeChunkSize,\n totalFileSize,\n}: {\n chunkIndex: number;\n safeChunkSize: number;\n totalFileSize: number;\n}) => {\n const start = chunkIndex * safeChunkSize;\n const end = Math.min(start + safeChunkSize, totalFileSize);\n\n return {\n start,\n end,\n };\n};\n\n/**\n * Applies chunking-related headers and custom headers to the XMLHttpRequest object.\n * XMLHttpRequest 객체에 청크 관련 헤더 및 커스텀 헤더를 적용합니다.\n */\nexport const applyChunkHeaders = ({\n xhr,\n customHeaders,\n chunkIndex,\n totalChunks,\n safeChunkSize,\n totalFileSize,\n}: {\n xhr: XMLHttpRequest;\n customHeaders: Record<string, string>;\n chunkIndex: number;\n totalChunks: number;\n safeChunkSize: number;\n totalFileSize: number;\n}) => {\n xhr.setRequestHeader('X-Chunk-Index', String(chunkIndex));\n xhr.setRequestHeader('X-Total-Chunks', String(totalChunks));\n xhr.setRequestHeader('X-Chunk-Size', String(safeChunkSize));\n xhr.setRequestHeader('X-File-Size', String(totalFileSize));\n\n Object.entries(customHeaders).forEach(([key, value]) => {\n xhr.setRequestHeader(key, value);\n });\n};\n","import { UploadParams, UploadResponse, OnProgressParams, UploadResult } from '../index';\nimport {\n isSuccessfulHttpStatus,\n getChunkUploadMeta,\n getChunkRange,\n applyChunkHeaders,\n} from './helper';\n\n/**\n * Handles file upload as a single request without chunking using XMLHttpRequest.\n * XMLHttpRequest를 사용하여 파일을 청크 분할 없이 단일 요청으로 업로드합니다.\n */\nconst uploadWithoutChunking = ({\n url,\n file,\n refresh,\n customHeaders,\n onProgress,\n}: {\n url: string;\n file: File;\n refresh: () => void;\n customHeaders: Record<string, string>;\n onProgress?: (args: OnProgressParams) => void;\n}): UploadResponse => {\n const xhr = new XMLHttpRequest();\n\n const response = new Promise<UploadResult>((resolve, reject) => {\n xhr.open('POST', url);\n\n Object.entries(customHeaders).forEach(([key, value]) => {\n xhr.setRequestHeader(key, value);\n });\n\n if (onProgress) {\n xhr.upload.onprogress = (event) => {\n const total = event.total || file.size;\n const percentage = total === 0 ? 100 : (event.loaded / total) * 100;\n\n onProgress({\n loaded: event.loaded,\n total,\n percentage,\n });\n };\n }\n\n xhr.onload = () => {\n if (isSuccessfulHttpStatus(xhr.status)) {\n if (onProgress) {\n onProgress({ loaded: file.size, total: file.size, percentage: 100 });\n }\n\n resolve({\n ok: true,\n total: file.size,\n message: undefined,\n status: 'success',\n });\n\n return;\n }\n\n reject(new Error(`Upload failed with status ${xhr.status}`));\n };\n\n xhr.onerror = (e) => reject(e);\n xhr.onabort = () => {\n reject(new DOMException('Aborted', 'AbortError'));\n };\n xhr.send(file);\n });\n\n return {\n result: response.then((res) => ({\n ok: res.ok,\n total: file.size,\n message: undefined,\n status: 'success',\n })),\n actions: {\n abort: () => xhr.abort(),\n refresh: () => {\n xhr.abort();\n refresh();\n },\n },\n };\n};\n\n/**\n * Uploads a file by splitting it into sequential chunks using XMLHttpRequest.\n * XMLHttpRequest를 사용하여 파일을 여러 개의 청크로 나누어 순차적으로 업로드합니다.\n */\nconst uploadWithXhrChuncked = async ({\n url,\n file,\n refresh,\n options: { chunkSize, customHeaders = {}, onProgress },\n}: UploadParams & { refresh: () => void }): Promise<UploadResponse> => {\n const response: UploadResponse = {\n result: Promise.resolve({ ok: false, total: 0, message: undefined, status: 'idle' }),\n actions: {\n abort: () => null,\n refresh: () => null,\n },\n };\n\n if (!chunkSize || chunkSize <= 0) {\n return uploadWithoutChunking({ url, file, refresh, customHeaders, onProgress });\n }\n\n const { safeChunkSize, totalFileSize, totalChunks } = getChunkUploadMeta({\n chunkSize,\n fileSize: file.size,\n });\n\n if (totalFileSize === 0) {\n if (onProgress) {\n onProgress({ loaded: 0, total: 0, percentage: 100 });\n }\n\n response.result = Promise.resolve({\n ok: true,\n total: 0,\n message: undefined,\n status: 'success',\n });\n\n return response;\n }\n\n /**\n * Orchestrates the sequential upload of all chunks.\n * 모든 청크의 순차적 업로드를 조율합니다.\n */\n const chunkUpload = async (): Promise<Readonly<UploadResult>> => {\n let uploadResult: Readonly<UploadResult> = {\n ok: false,\n total: 0,\n message: undefined,\n status: 'uploading',\n };\n\n for (let chunkIndex = 0; chunkIndex < totalChunks; chunkIndex += 1) {\n const { start, end } = getChunkRange({\n chunkIndex,\n safeChunkSize,\n totalFileSize,\n });\n\n const uploadPromise = new Promise<Readonly<UploadResult>>((resolve, reject) => {\n const xhr = new XMLHttpRequest();\n\n xhr.open('POST', url);\n\n applyChunkHeaders({\n xhr,\n customHeaders,\n chunkIndex,\n totalChunks,\n safeChunkSize,\n totalFileSize,\n });\n\n xhr.onload = () => {\n if (isSuccessfulHttpStatus(xhr.status)) {\n const isLastChunk = end >= totalFileSize;\n const percentage = isLastChunk ? 100 : (end / totalFileSize) * 100;\n\n if (onProgress) {\n onProgress({\n loaded: end,\n total: totalFileSize,\n percentage: totalFileSize === 0 ? 100 : percentage,\n });\n }\n\n resolve({\n ok: true,\n total: totalFileSize,\n message: undefined,\n status: isLastChunk ? 'success' : 'uploading',\n });\n\n return;\n }\n\n reject(new Error(`Chunk upload failed with status ${xhr.status}`));\n };\n\n xhr.onerror = (e) => reject(e);\n xhr.onabort = () => {\n const abortError = new DOMException('Aborted', 'AbortError');\n reject(abortError);\n };\n\n const chunk = file.slice(start, end);\n xhr.send(chunk);\n\n // Update actions to refer to the current XHR request.\n // 현재 XHR 요청을 참조하도록 액션을 업데이트합니다.\n response.actions.abort = () => xhr.abort();\n response.actions.refresh = () => {\n xhr.abort();\n refresh();\n };\n });\n\n // Sequential execution using await within a loop for chunked transfer.\n // 청크 전송을 위해 루프 내에서 await를 사용하여 순차적으로 실행합니다.\n // eslint-disable-next-line no-await-in-loop -- chunked mode intentionally uploads sequentially\n uploadResult = await uploadPromise;\n }\n\n return uploadResult;\n };\n\n response.result = chunkUpload();\n\n return response;\n};\n\nexport default uploadWithXhrChuncked;\n"],"names":[],"mappings":"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"}