@universal-uploader/react 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.
- package/dist/index.cjs +237 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +39 -0
- package/dist/index.js +235 -0
- package/dist/index.js.map +1 -0
- package/dist/types.d.ts +12 -0
- package/dist/useUniversalUpload.d.ts +25 -0
- package/dist/useUniversalUpload.js +158 -0
- package/package.json +35 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var uploadCore = require('@universal-uploader/core');
|
|
4
|
+
var react = require('react');
|
|
5
|
+
|
|
6
|
+
const INITIAL_UPLOAD_RESULT = {
|
|
7
|
+
ok: false,
|
|
8
|
+
total: 0,
|
|
9
|
+
message: undefined,
|
|
10
|
+
status: "idle",
|
|
11
|
+
};
|
|
12
|
+
const FALLBACK_UPLOAD_OPTIONS = {};
|
|
13
|
+
/**
|
|
14
|
+
* A custom React hook for handling universal file uploads with streaming support and fallbacks.
|
|
15
|
+
* 스트리밍 지원 및 폴백 기능을 갖춘 범용 파일 업로드를 처리하기 위한 커스텀 React 훅입니다.
|
|
16
|
+
*/
|
|
17
|
+
function useUniversalUpload({ url, options, }) {
|
|
18
|
+
const [status, setStatus] = react.useState("idle");
|
|
19
|
+
const [error, setError] = react.useState(null);
|
|
20
|
+
const [result, setResult] = react.useState(INITIAL_UPLOAD_RESULT);
|
|
21
|
+
/**
|
|
22
|
+
* The previous request abort function.
|
|
23
|
+
* 이전의 요청 중단 함수입니다.
|
|
24
|
+
*/
|
|
25
|
+
const prevReqAbortRef = react.useRef({
|
|
26
|
+
abort: () => null,
|
|
27
|
+
refresh: () => null,
|
|
28
|
+
pause: () => null,
|
|
29
|
+
resume: () => null,
|
|
30
|
+
});
|
|
31
|
+
/**
|
|
32
|
+
* The latest upload request ID.
|
|
33
|
+
* 가장 최근의 업로드 요청 ID입니다.
|
|
34
|
+
*/
|
|
35
|
+
const latestUploadRequestIdRef = react.useRef(0);
|
|
36
|
+
/**
|
|
37
|
+
* The options object.
|
|
38
|
+
* deps에 포함되지 않는 옵션 객체입니다.
|
|
39
|
+
*/
|
|
40
|
+
const optionRef = react.useRef(options ?? FALLBACK_UPLOAD_OPTIONS);
|
|
41
|
+
// eslint-disable-next-line react-hooks/refs
|
|
42
|
+
optionRef.current = options ?? FALLBACK_UPLOAD_OPTIONS;
|
|
43
|
+
/**
|
|
44
|
+
* Updates the upload result and status state.
|
|
45
|
+
* 업로드 결과 및 상태 스테이트를 업데이트합니다.
|
|
46
|
+
*/
|
|
47
|
+
const updateUploadResult = react.useCallback((uploadResult) => {
|
|
48
|
+
setResult(uploadResult);
|
|
49
|
+
setStatus(uploadResult.status);
|
|
50
|
+
if (uploadResult.status === "error") {
|
|
51
|
+
setError((prevError) => prevError ?? new Error(uploadResult.message || "Upload failed"));
|
|
52
|
+
}
|
|
53
|
+
}, []);
|
|
54
|
+
/**
|
|
55
|
+
* Core upload logic that manages request lifecycle and status updates.
|
|
56
|
+
* 요청 수명 주기 및 상태 업데이트를 관리하는 핵심 업로드 로직입니다.
|
|
57
|
+
*/
|
|
58
|
+
const upload = react.useCallback(async (file) => {
|
|
59
|
+
/**
|
|
60
|
+
* Increment the latest upload request ID.
|
|
61
|
+
* 가장 최근의 업로드 요청 ID를 증가시킵니다.
|
|
62
|
+
*
|
|
63
|
+
* Set the latest upload request ID to the current value.
|
|
64
|
+
* 가장 최근의 업로드 요청 ID를 현재 값으로 설정합니다.
|
|
65
|
+
*
|
|
66
|
+
* Check if the current request is the latest upload request.
|
|
67
|
+
* 현재 요청이 가장 최근의 업로드 요청인지 확인합니다.
|
|
68
|
+
*/
|
|
69
|
+
latestUploadRequestIdRef.current += 1;
|
|
70
|
+
const latestUploadRequestId = latestUploadRequestIdRef.current;
|
|
71
|
+
const isLatestUploadRequest = () => latestUploadRequestIdRef.current === latestUploadRequestId;
|
|
72
|
+
/**
|
|
73
|
+
* If the previous request is still in progress, abort it.
|
|
74
|
+
* 이전의 요청이 아직 진행 중이라면 중단합니다.
|
|
75
|
+
*/
|
|
76
|
+
prevReqAbortRef.current.abort();
|
|
77
|
+
/**
|
|
78
|
+
* Reset external progress UI before a new upload starts.
|
|
79
|
+
* 새 업로드가 시작되기 전에 외부 progress UI를 초기화합니다.
|
|
80
|
+
*/
|
|
81
|
+
optionRef.current?.onProgress?.({ loaded: 0, total: 0, percentage: 0 });
|
|
82
|
+
setStatus("uploading");
|
|
83
|
+
setError(null);
|
|
84
|
+
setResult(INITIAL_UPLOAD_RESULT);
|
|
85
|
+
const { current: $options } = optionRef;
|
|
86
|
+
const { result: uploadResultPromise, actions: uploadActions } = await uploadCore({
|
|
87
|
+
url,
|
|
88
|
+
file,
|
|
89
|
+
options: {
|
|
90
|
+
...$options,
|
|
91
|
+
onComplete: () => {
|
|
92
|
+
$options.onComplete?.();
|
|
93
|
+
if (isLatestUploadRequest()) {
|
|
94
|
+
setStatus("success");
|
|
95
|
+
setResult((prevResult) => ({
|
|
96
|
+
...prevResult,
|
|
97
|
+
ok: true,
|
|
98
|
+
status: "success",
|
|
99
|
+
}));
|
|
100
|
+
}
|
|
101
|
+
},
|
|
102
|
+
onError: (e) => {
|
|
103
|
+
$options.onError?.(e);
|
|
104
|
+
if (isLatestUploadRequest()) {
|
|
105
|
+
setError(e);
|
|
106
|
+
setStatus("error");
|
|
107
|
+
setResult((prevResult) => ({
|
|
108
|
+
...prevResult,
|
|
109
|
+
ok: false,
|
|
110
|
+
status: "error",
|
|
111
|
+
}));
|
|
112
|
+
}
|
|
113
|
+
},
|
|
114
|
+
onAbort: (e) => {
|
|
115
|
+
$options.onAbort?.(e);
|
|
116
|
+
if (isLatestUploadRequest()) {
|
|
117
|
+
setStatus("aborted");
|
|
118
|
+
setResult((prevResult) => ({
|
|
119
|
+
...prevResult,
|
|
120
|
+
ok: false,
|
|
121
|
+
status: "aborted",
|
|
122
|
+
}));
|
|
123
|
+
}
|
|
124
|
+
},
|
|
125
|
+
onProgress: (args) => {
|
|
126
|
+
$options.onProgress?.(args);
|
|
127
|
+
if (isLatestUploadRequest()) {
|
|
128
|
+
setStatus("uploading");
|
|
129
|
+
setResult({
|
|
130
|
+
ok: false,
|
|
131
|
+
total: args.total,
|
|
132
|
+
status: "uploading",
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
},
|
|
136
|
+
onRetry: () => {
|
|
137
|
+
$options.onRetry?.();
|
|
138
|
+
if (isLatestUploadRequest()) {
|
|
139
|
+
setStatus("uploading");
|
|
140
|
+
setResult(INITIAL_UPLOAD_RESULT);
|
|
141
|
+
setError(null);
|
|
142
|
+
}
|
|
143
|
+
},
|
|
144
|
+
onPause: () => {
|
|
145
|
+
$options.onPause?.();
|
|
146
|
+
if (isLatestUploadRequest()) {
|
|
147
|
+
setStatus("paused");
|
|
148
|
+
setResult((prevResult) => ({
|
|
149
|
+
...prevResult,
|
|
150
|
+
ok: false,
|
|
151
|
+
status: "paused",
|
|
152
|
+
}));
|
|
153
|
+
}
|
|
154
|
+
},
|
|
155
|
+
onResume: () => {
|
|
156
|
+
$options.onResume?.();
|
|
157
|
+
if (isLatestUploadRequest()) {
|
|
158
|
+
setStatus("uploading");
|
|
159
|
+
setResult((prevResult) => ({
|
|
160
|
+
...prevResult,
|
|
161
|
+
status: "uploading",
|
|
162
|
+
}));
|
|
163
|
+
}
|
|
164
|
+
},
|
|
165
|
+
},
|
|
166
|
+
});
|
|
167
|
+
/**
|
|
168
|
+
* If the current request is not the latest upload request, abort the request.
|
|
169
|
+
* 현재 요청이 가장 최근의 업로드 요청이 아니라면 요청을 중단합니다.
|
|
170
|
+
*/
|
|
171
|
+
if (!isLatestUploadRequest()) {
|
|
172
|
+
uploadActions.abort();
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
// Set the previous request abort function to the current abort function.
|
|
176
|
+
// 이전의 요청 중단 함수를 현재의 요청 중단 함수로 설정합니다.
|
|
177
|
+
prevReqAbortRef.current = uploadActions;
|
|
178
|
+
const uploadResult = await uploadResultPromise;
|
|
179
|
+
/**
|
|
180
|
+
* If the current request is not the latest upload request, do not update the result status.
|
|
181
|
+
* 현재 요청이 가장 최근의 업로드 요청이 아니라면 결과 상태 업데이트를 하지 않고 반환합니다.
|
|
182
|
+
*/
|
|
183
|
+
if (!isLatestUploadRequest()) {
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
updateUploadResult(uploadResult);
|
|
187
|
+
}, [url, updateUploadResult]);
|
|
188
|
+
/**
|
|
189
|
+
* Executes the upload with error handling and optional re-throwing.
|
|
190
|
+
* 에러 핸들링 및 선택적 re-throw 기능을 포함하여 업로드를 실행합니다.
|
|
191
|
+
*/
|
|
192
|
+
const uploadSafely = react.useCallback(async (file) => {
|
|
193
|
+
try {
|
|
194
|
+
await upload(file);
|
|
195
|
+
}
|
|
196
|
+
catch (e) {
|
|
197
|
+
setError(e);
|
|
198
|
+
setStatus("error");
|
|
199
|
+
setResult({ ...INITIAL_UPLOAD_RESULT, status: "error" });
|
|
200
|
+
const { current: $options } = optionRef;
|
|
201
|
+
const shouldThrow = typeof $options.throwOnError === "function"
|
|
202
|
+
? $options.throwOnError(e)
|
|
203
|
+
: Boolean($options.throwOnError);
|
|
204
|
+
if (shouldThrow) {
|
|
205
|
+
throw e;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}, [upload]);
|
|
209
|
+
react.useEffect(() => {
|
|
210
|
+
return () => {
|
|
211
|
+
prevReqAbortRef.current.abort();
|
|
212
|
+
};
|
|
213
|
+
}, []);
|
|
214
|
+
react.useEffect(() => {
|
|
215
|
+
optionRef.current?.onUrlChange?.(url);
|
|
216
|
+
}, [url]);
|
|
217
|
+
react.useEffect(() => {
|
|
218
|
+
optionRef.current?.onMethodChange?.(optionRef.current.method);
|
|
219
|
+
}, [options?.method]);
|
|
220
|
+
return {
|
|
221
|
+
upload: uploadSafely,
|
|
222
|
+
/**
|
|
223
|
+
* Alias for upload — restarts upload after abort, error, or success.
|
|
224
|
+
* upload의 별칭 — 중단, 실패, 성공 이후 업로드를 다시 시작할 때 사용합니다.
|
|
225
|
+
*/
|
|
226
|
+
retry: uploadSafely,
|
|
227
|
+
result,
|
|
228
|
+
status,
|
|
229
|
+
error,
|
|
230
|
+
abort: () => prevReqAbortRef.current.abort(),
|
|
231
|
+
pause: () => prevReqAbortRef.current.pause(),
|
|
232
|
+
resume: () => prevReqAbortRef.current.resume(),
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
exports.useUniversalUpload = useUniversalUpload;
|
|
237
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.cjs","sources":["../src/useUniversalUpload.tsx"],"sourcesContent":["import uploadCore, {\n UploadResult,\n UploadStatus,\n UploadActions,\n} from \"@universal-uploader/core\";\nimport { UploadHookOptions } from \"./types\";\nimport { useCallback, useEffect, useRef, useState } from \"react\";\n\nconst INITIAL_UPLOAD_RESULT: Readonly<UploadResult> = {\n ok: false,\n total: 0,\n message: undefined,\n status: \"idle\",\n};\n\nconst FALLBACK_UPLOAD_OPTIONS: Readonly<UploadHookOptions> = {};\n\ninterface UseUniversalUploadArgs {\n url: string;\n options?: UploadHookOptions;\n}\n\n/**\n * A custom React hook for handling universal file uploads with streaming support and fallbacks.\n * 스트리밍 지원 및 폴백 기능을 갖춘 범용 파일 업로드를 처리하기 위한 커스텀 React 훅입니다.\n */\nexport default function useUniversalUpload({\n url,\n options,\n}: UseUniversalUploadArgs) {\n const [status, setStatus] = useState<UploadStatus>(\"idle\");\n const [error, setError] = useState<Error | null>(null);\n const [result, setResult] = useState<UploadResult>(INITIAL_UPLOAD_RESULT);\n\n /**\n * The previous request abort function.\n * 이전의 요청 중단 함수입니다.\n */\n const prevReqAbortRef = useRef<UploadActions>({\n abort: () => null,\n refresh: () => null,\n pause: () => null,\n resume: () => null,\n });\n\n /**\n * The latest upload request ID.\n * 가장 최근의 업로드 요청 ID입니다.\n */\n const latestUploadRequestIdRef = useRef(0);\n\n /**\n * The options object.\n * deps에 포함되지 않는 옵션 객체입니다.\n */\n const optionRef = useRef<UploadHookOptions>(\n options ?? FALLBACK_UPLOAD_OPTIONS,\n );\n // eslint-disable-next-line react-hooks/refs\n optionRef.current = options ?? FALLBACK_UPLOAD_OPTIONS;\n\n /**\n * Updates the upload result and status state.\n * 업로드 결과 및 상태 스테이트를 업데이트합니다.\n */\n const updateUploadResult = useCallback((uploadResult: UploadResult) => {\n setResult(uploadResult);\n setStatus(uploadResult.status);\n\n if (uploadResult.status === \"error\") {\n setError(\n (prevError) =>\n prevError ?? new Error(uploadResult.message || \"Upload failed\"),\n );\n }\n }, []);\n\n /**\n * Core upload logic that manages request lifecycle and status updates.\n * 요청 수명 주기 및 상태 업데이트를 관리하는 핵심 업로드 로직입니다.\n */\n const upload = useCallback(\n async (file: File) => {\n /**\n * Increment the latest upload request ID.\n * 가장 최근의 업로드 요청 ID를 증가시킵니다.\n *\n * Set the latest upload request ID to the current value.\n * 가장 최근의 업로드 요청 ID를 현재 값으로 설정합니다.\n *\n * Check if the current request is the latest upload request.\n * 현재 요청이 가장 최근의 업로드 요청인지 확인합니다.\n */\n latestUploadRequestIdRef.current += 1;\n const latestUploadRequestId = latestUploadRequestIdRef.current;\n const isLatestUploadRequest = () =>\n latestUploadRequestIdRef.current === latestUploadRequestId;\n\n /**\n * If the previous request is still in progress, abort it.\n * 이전의 요청이 아직 진행 중이라면 중단합니다.\n */\n prevReqAbortRef.current.abort();\n\n /**\n * Reset external progress UI before a new upload starts.\n * 새 업로드가 시작되기 전에 외부 progress UI를 초기화합니다.\n */\n optionRef.current?.onProgress?.({ loaded: 0, total: 0, percentage: 0 });\n\n setStatus(\"uploading\");\n setError(null);\n setResult(INITIAL_UPLOAD_RESULT);\n\n const { current: $options } = optionRef;\n\n const { result: uploadResultPromise, actions: uploadActions } =\n await uploadCore({\n url,\n file,\n options: {\n ...$options,\n onComplete: () => {\n $options.onComplete?.();\n if (isLatestUploadRequest()) {\n setStatus(\"success\");\n setResult((prevResult) => ({\n ...prevResult,\n ok: true,\n status: \"success\",\n }));\n }\n },\n onError: (e) => {\n $options.onError?.(e);\n if (isLatestUploadRequest()) {\n setError(e);\n setStatus(\"error\");\n setResult((prevResult) => ({\n ...prevResult,\n ok: false,\n status: \"error\",\n }));\n }\n },\n onAbort: (e) => {\n $options.onAbort?.(e);\n if (isLatestUploadRequest()) {\n setStatus(\"aborted\");\n setResult((prevResult) => ({\n ...prevResult,\n ok: false,\n status: \"aborted\",\n }));\n }\n },\n onProgress: (args) => {\n $options.onProgress?.(args);\n if (isLatestUploadRequest()) {\n setStatus(\"uploading\");\n setResult({\n ok: false,\n total: args.total,\n status: \"uploading\",\n });\n }\n },\n onRetry: () => {\n $options.onRetry?.();\n if (isLatestUploadRequest()) {\n setStatus(\"uploading\");\n setResult(INITIAL_UPLOAD_RESULT);\n setError(null);\n }\n },\n onPause: () => {\n $options.onPause?.();\n if (isLatestUploadRequest()) {\n setStatus(\"paused\");\n setResult((prevResult) => ({\n ...prevResult,\n ok: false,\n status: \"paused\",\n }));\n }\n },\n onResume: () => {\n $options.onResume?.();\n if (isLatestUploadRequest()) {\n setStatus(\"uploading\");\n setResult((prevResult) => ({\n ...prevResult,\n status: \"uploading\",\n }));\n }\n },\n },\n });\n\n /**\n * If the current request is not the latest upload request, abort the request.\n * 현재 요청이 가장 최근의 업로드 요청이 아니라면 요청을 중단합니다.\n */\n if (!isLatestUploadRequest()) {\n uploadActions.abort();\n return;\n }\n\n // Set the previous request abort function to the current abort function.\n // 이전의 요청 중단 함수를 현재의 요청 중단 함수로 설정합니다.\n prevReqAbortRef.current = uploadActions;\n\n const uploadResult = await uploadResultPromise;\n\n /**\n * If the current request is not the latest upload request, do not update the result status.\n * 현재 요청이 가장 최근의 업로드 요청이 아니라면 결과 상태 업데이트를 하지 않고 반환합니다.\n */\n if (!isLatestUploadRequest()) {\n return;\n }\n\n updateUploadResult(uploadResult);\n },\n [url, updateUploadResult],\n );\n\n /**\n * Executes the upload with error handling and optional re-throwing.\n * 에러 핸들링 및 선택적 re-throw 기능을 포함하여 업로드를 실행합니다.\n */\n const uploadSafely = useCallback(\n async (file: File) => {\n try {\n await upload(file);\n } catch (e) {\n setError(e as Error);\n setStatus(\"error\");\n setResult({ ...INITIAL_UPLOAD_RESULT, status: \"error\" });\n\n const { current: $options } = optionRef;\n\n const shouldThrow =\n typeof $options.throwOnError === \"function\"\n ? $options.throwOnError(e)\n : Boolean($options.throwOnError);\n\n if (shouldThrow) {\n throw e;\n }\n }\n },\n [upload],\n );\n\n useEffect(() => {\n return () => {\n prevReqAbortRef.current.abort();\n };\n }, []);\n\n useEffect(() => {\n optionRef.current?.onUrlChange?.(url);\n }, [url]);\n\n useEffect(() => {\n optionRef.current?.onMethodChange?.(optionRef.current.method);\n }, [options?.method]);\n\n return {\n upload: uploadSafely,\n /**\n * Alias for upload — restarts upload after abort, error, or success.\n * upload의 별칭 — 중단, 실패, 성공 이후 업로드를 다시 시작할 때 사용합니다.\n */\n retry: uploadSafely,\n result,\n status,\n error,\n abort: () => prevReqAbortRef.current.abort(),\n pause: () => prevReqAbortRef.current.pause(),\n resume: () => prevReqAbortRef.current.resume(),\n };\n}\n"],"names":["useState","useRef","useCallback","useEffect"],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { UploadOptions, UploadMethod as UploadMethod$1, UploadResult, UploadStatus } from '@universal-uploader/core';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Configuration options for the React hook.
|
|
5
|
+
* React 훅을 위한 구성 옵션입니다.
|
|
6
|
+
*/
|
|
7
|
+
interface UploadHookOptions extends UploadOptions {
|
|
8
|
+
/** Callback when the target URL changes. / URL이 변경될 때 호출되는 콜백. */
|
|
9
|
+
onUrlChange?: (url: string) => void;
|
|
10
|
+
/** Callback when the upload method changes. / 업로드 방식이 변경될 때 호출되는 콜백. */
|
|
11
|
+
onMethodChange?: (method: UploadOptions["method"]) => void;
|
|
12
|
+
}
|
|
13
|
+
type UploadMethod = UploadMethod$1;
|
|
14
|
+
|
|
15
|
+
interface UseUniversalUploadArgs {
|
|
16
|
+
url: string;
|
|
17
|
+
options?: UploadHookOptions;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* A custom React hook for handling universal file uploads with streaming support and fallbacks.
|
|
21
|
+
* 스트리밍 지원 및 폴백 기능을 갖춘 범용 파일 업로드를 처리하기 위한 커스텀 React 훅입니다.
|
|
22
|
+
*/
|
|
23
|
+
declare function useUniversalUpload({ url, options, }: UseUniversalUploadArgs): {
|
|
24
|
+
upload: (file: File) => Promise<void>;
|
|
25
|
+
/**
|
|
26
|
+
* Alias for upload — restarts upload after abort, error, or success.
|
|
27
|
+
* upload의 별칭 — 중단, 실패, 성공 이후 업로드를 다시 시작할 때 사용합니다.
|
|
28
|
+
*/
|
|
29
|
+
retry: (file: File) => Promise<void>;
|
|
30
|
+
result: UploadResult;
|
|
31
|
+
status: UploadStatus;
|
|
32
|
+
error: Error | null;
|
|
33
|
+
abort: () => void;
|
|
34
|
+
pause: () => void;
|
|
35
|
+
resume: () => void;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export { useUniversalUpload };
|
|
39
|
+
export type { UploadHookOptions, UploadMethod };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
import uploadCore from '@universal-uploader/core';
|
|
2
|
+
import { useState, useRef, useCallback, useEffect } from 'react';
|
|
3
|
+
|
|
4
|
+
const INITIAL_UPLOAD_RESULT = {
|
|
5
|
+
ok: false,
|
|
6
|
+
total: 0,
|
|
7
|
+
message: undefined,
|
|
8
|
+
status: "idle",
|
|
9
|
+
};
|
|
10
|
+
const FALLBACK_UPLOAD_OPTIONS = {};
|
|
11
|
+
/**
|
|
12
|
+
* A custom React hook for handling universal file uploads with streaming support and fallbacks.
|
|
13
|
+
* 스트리밍 지원 및 폴백 기능을 갖춘 범용 파일 업로드를 처리하기 위한 커스텀 React 훅입니다.
|
|
14
|
+
*/
|
|
15
|
+
function useUniversalUpload({ url, options, }) {
|
|
16
|
+
const [status, setStatus] = useState("idle");
|
|
17
|
+
const [error, setError] = useState(null);
|
|
18
|
+
const [result, setResult] = useState(INITIAL_UPLOAD_RESULT);
|
|
19
|
+
/**
|
|
20
|
+
* The previous request abort function.
|
|
21
|
+
* 이전의 요청 중단 함수입니다.
|
|
22
|
+
*/
|
|
23
|
+
const prevReqAbortRef = useRef({
|
|
24
|
+
abort: () => null,
|
|
25
|
+
refresh: () => null,
|
|
26
|
+
pause: () => null,
|
|
27
|
+
resume: () => null,
|
|
28
|
+
});
|
|
29
|
+
/**
|
|
30
|
+
* The latest upload request ID.
|
|
31
|
+
* 가장 최근의 업로드 요청 ID입니다.
|
|
32
|
+
*/
|
|
33
|
+
const latestUploadRequestIdRef = useRef(0);
|
|
34
|
+
/**
|
|
35
|
+
* The options object.
|
|
36
|
+
* deps에 포함되지 않는 옵션 객체입니다.
|
|
37
|
+
*/
|
|
38
|
+
const optionRef = useRef(options ?? FALLBACK_UPLOAD_OPTIONS);
|
|
39
|
+
// eslint-disable-next-line react-hooks/refs
|
|
40
|
+
optionRef.current = options ?? FALLBACK_UPLOAD_OPTIONS;
|
|
41
|
+
/**
|
|
42
|
+
* Updates the upload result and status state.
|
|
43
|
+
* 업로드 결과 및 상태 스테이트를 업데이트합니다.
|
|
44
|
+
*/
|
|
45
|
+
const updateUploadResult = useCallback((uploadResult) => {
|
|
46
|
+
setResult(uploadResult);
|
|
47
|
+
setStatus(uploadResult.status);
|
|
48
|
+
if (uploadResult.status === "error") {
|
|
49
|
+
setError((prevError) => prevError ?? new Error(uploadResult.message || "Upload failed"));
|
|
50
|
+
}
|
|
51
|
+
}, []);
|
|
52
|
+
/**
|
|
53
|
+
* Core upload logic that manages request lifecycle and status updates.
|
|
54
|
+
* 요청 수명 주기 및 상태 업데이트를 관리하는 핵심 업로드 로직입니다.
|
|
55
|
+
*/
|
|
56
|
+
const upload = useCallback(async (file) => {
|
|
57
|
+
/**
|
|
58
|
+
* Increment the latest upload request ID.
|
|
59
|
+
* 가장 최근의 업로드 요청 ID를 증가시킵니다.
|
|
60
|
+
*
|
|
61
|
+
* Set the latest upload request ID to the current value.
|
|
62
|
+
* 가장 최근의 업로드 요청 ID를 현재 값으로 설정합니다.
|
|
63
|
+
*
|
|
64
|
+
* Check if the current request is the latest upload request.
|
|
65
|
+
* 현재 요청이 가장 최근의 업로드 요청인지 확인합니다.
|
|
66
|
+
*/
|
|
67
|
+
latestUploadRequestIdRef.current += 1;
|
|
68
|
+
const latestUploadRequestId = latestUploadRequestIdRef.current;
|
|
69
|
+
const isLatestUploadRequest = () => latestUploadRequestIdRef.current === latestUploadRequestId;
|
|
70
|
+
/**
|
|
71
|
+
* If the previous request is still in progress, abort it.
|
|
72
|
+
* 이전의 요청이 아직 진행 중이라면 중단합니다.
|
|
73
|
+
*/
|
|
74
|
+
prevReqAbortRef.current.abort();
|
|
75
|
+
/**
|
|
76
|
+
* Reset external progress UI before a new upload starts.
|
|
77
|
+
* 새 업로드가 시작되기 전에 외부 progress UI를 초기화합니다.
|
|
78
|
+
*/
|
|
79
|
+
optionRef.current?.onProgress?.({ loaded: 0, total: 0, percentage: 0 });
|
|
80
|
+
setStatus("uploading");
|
|
81
|
+
setError(null);
|
|
82
|
+
setResult(INITIAL_UPLOAD_RESULT);
|
|
83
|
+
const { current: $options } = optionRef;
|
|
84
|
+
const { result: uploadResultPromise, actions: uploadActions } = await uploadCore({
|
|
85
|
+
url,
|
|
86
|
+
file,
|
|
87
|
+
options: {
|
|
88
|
+
...$options,
|
|
89
|
+
onComplete: () => {
|
|
90
|
+
$options.onComplete?.();
|
|
91
|
+
if (isLatestUploadRequest()) {
|
|
92
|
+
setStatus("success");
|
|
93
|
+
setResult((prevResult) => ({
|
|
94
|
+
...prevResult,
|
|
95
|
+
ok: true,
|
|
96
|
+
status: "success",
|
|
97
|
+
}));
|
|
98
|
+
}
|
|
99
|
+
},
|
|
100
|
+
onError: (e) => {
|
|
101
|
+
$options.onError?.(e);
|
|
102
|
+
if (isLatestUploadRequest()) {
|
|
103
|
+
setError(e);
|
|
104
|
+
setStatus("error");
|
|
105
|
+
setResult((prevResult) => ({
|
|
106
|
+
...prevResult,
|
|
107
|
+
ok: false,
|
|
108
|
+
status: "error",
|
|
109
|
+
}));
|
|
110
|
+
}
|
|
111
|
+
},
|
|
112
|
+
onAbort: (e) => {
|
|
113
|
+
$options.onAbort?.(e);
|
|
114
|
+
if (isLatestUploadRequest()) {
|
|
115
|
+
setStatus("aborted");
|
|
116
|
+
setResult((prevResult) => ({
|
|
117
|
+
...prevResult,
|
|
118
|
+
ok: false,
|
|
119
|
+
status: "aborted",
|
|
120
|
+
}));
|
|
121
|
+
}
|
|
122
|
+
},
|
|
123
|
+
onProgress: (args) => {
|
|
124
|
+
$options.onProgress?.(args);
|
|
125
|
+
if (isLatestUploadRequest()) {
|
|
126
|
+
setStatus("uploading");
|
|
127
|
+
setResult({
|
|
128
|
+
ok: false,
|
|
129
|
+
total: args.total,
|
|
130
|
+
status: "uploading",
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
},
|
|
134
|
+
onRetry: () => {
|
|
135
|
+
$options.onRetry?.();
|
|
136
|
+
if (isLatestUploadRequest()) {
|
|
137
|
+
setStatus("uploading");
|
|
138
|
+
setResult(INITIAL_UPLOAD_RESULT);
|
|
139
|
+
setError(null);
|
|
140
|
+
}
|
|
141
|
+
},
|
|
142
|
+
onPause: () => {
|
|
143
|
+
$options.onPause?.();
|
|
144
|
+
if (isLatestUploadRequest()) {
|
|
145
|
+
setStatus("paused");
|
|
146
|
+
setResult((prevResult) => ({
|
|
147
|
+
...prevResult,
|
|
148
|
+
ok: false,
|
|
149
|
+
status: "paused",
|
|
150
|
+
}));
|
|
151
|
+
}
|
|
152
|
+
},
|
|
153
|
+
onResume: () => {
|
|
154
|
+
$options.onResume?.();
|
|
155
|
+
if (isLatestUploadRequest()) {
|
|
156
|
+
setStatus("uploading");
|
|
157
|
+
setResult((prevResult) => ({
|
|
158
|
+
...prevResult,
|
|
159
|
+
status: "uploading",
|
|
160
|
+
}));
|
|
161
|
+
}
|
|
162
|
+
},
|
|
163
|
+
},
|
|
164
|
+
});
|
|
165
|
+
/**
|
|
166
|
+
* If the current request is not the latest upload request, abort the request.
|
|
167
|
+
* 현재 요청이 가장 최근의 업로드 요청이 아니라면 요청을 중단합니다.
|
|
168
|
+
*/
|
|
169
|
+
if (!isLatestUploadRequest()) {
|
|
170
|
+
uploadActions.abort();
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
// Set the previous request abort function to the current abort function.
|
|
174
|
+
// 이전의 요청 중단 함수를 현재의 요청 중단 함수로 설정합니다.
|
|
175
|
+
prevReqAbortRef.current = uploadActions;
|
|
176
|
+
const uploadResult = await uploadResultPromise;
|
|
177
|
+
/**
|
|
178
|
+
* If the current request is not the latest upload request, do not update the result status.
|
|
179
|
+
* 현재 요청이 가장 최근의 업로드 요청이 아니라면 결과 상태 업데이트를 하지 않고 반환합니다.
|
|
180
|
+
*/
|
|
181
|
+
if (!isLatestUploadRequest()) {
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
updateUploadResult(uploadResult);
|
|
185
|
+
}, [url, updateUploadResult]);
|
|
186
|
+
/**
|
|
187
|
+
* Executes the upload with error handling and optional re-throwing.
|
|
188
|
+
* 에러 핸들링 및 선택적 re-throw 기능을 포함하여 업로드를 실행합니다.
|
|
189
|
+
*/
|
|
190
|
+
const uploadSafely = useCallback(async (file) => {
|
|
191
|
+
try {
|
|
192
|
+
await upload(file);
|
|
193
|
+
}
|
|
194
|
+
catch (e) {
|
|
195
|
+
setError(e);
|
|
196
|
+
setStatus("error");
|
|
197
|
+
setResult({ ...INITIAL_UPLOAD_RESULT, status: "error" });
|
|
198
|
+
const { current: $options } = optionRef;
|
|
199
|
+
const shouldThrow = typeof $options.throwOnError === "function"
|
|
200
|
+
? $options.throwOnError(e)
|
|
201
|
+
: Boolean($options.throwOnError);
|
|
202
|
+
if (shouldThrow) {
|
|
203
|
+
throw e;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}, [upload]);
|
|
207
|
+
useEffect(() => {
|
|
208
|
+
return () => {
|
|
209
|
+
prevReqAbortRef.current.abort();
|
|
210
|
+
};
|
|
211
|
+
}, []);
|
|
212
|
+
useEffect(() => {
|
|
213
|
+
optionRef.current?.onUrlChange?.(url);
|
|
214
|
+
}, [url]);
|
|
215
|
+
useEffect(() => {
|
|
216
|
+
optionRef.current?.onMethodChange?.(optionRef.current.method);
|
|
217
|
+
}, [options?.method]);
|
|
218
|
+
return {
|
|
219
|
+
upload: uploadSafely,
|
|
220
|
+
/**
|
|
221
|
+
* Alias for upload — restarts upload after abort, error, or success.
|
|
222
|
+
* upload의 별칭 — 중단, 실패, 성공 이후 업로드를 다시 시작할 때 사용합니다.
|
|
223
|
+
*/
|
|
224
|
+
retry: uploadSafely,
|
|
225
|
+
result,
|
|
226
|
+
status,
|
|
227
|
+
error,
|
|
228
|
+
abort: () => prevReqAbortRef.current.abort(),
|
|
229
|
+
pause: () => prevReqAbortRef.current.pause(),
|
|
230
|
+
resume: () => prevReqAbortRef.current.resume(),
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
export { useUniversalUpload };
|
|
235
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../src/useUniversalUpload.tsx"],"sourcesContent":["import uploadCore, {\n UploadResult,\n UploadStatus,\n UploadActions,\n} from \"@universal-uploader/core\";\nimport { UploadHookOptions } from \"./types\";\nimport { useCallback, useEffect, useRef, useState } from \"react\";\n\nconst INITIAL_UPLOAD_RESULT: Readonly<UploadResult> = {\n ok: false,\n total: 0,\n message: undefined,\n status: \"idle\",\n};\n\nconst FALLBACK_UPLOAD_OPTIONS: Readonly<UploadHookOptions> = {};\n\ninterface UseUniversalUploadArgs {\n url: string;\n options?: UploadHookOptions;\n}\n\n/**\n * A custom React hook for handling universal file uploads with streaming support and fallbacks.\n * 스트리밍 지원 및 폴백 기능을 갖춘 범용 파일 업로드를 처리하기 위한 커스텀 React 훅입니다.\n */\nexport default function useUniversalUpload({\n url,\n options,\n}: UseUniversalUploadArgs) {\n const [status, setStatus] = useState<UploadStatus>(\"idle\");\n const [error, setError] = useState<Error | null>(null);\n const [result, setResult] = useState<UploadResult>(INITIAL_UPLOAD_RESULT);\n\n /**\n * The previous request abort function.\n * 이전의 요청 중단 함수입니다.\n */\n const prevReqAbortRef = useRef<UploadActions>({\n abort: () => null,\n refresh: () => null,\n pause: () => null,\n resume: () => null,\n });\n\n /**\n * The latest upload request ID.\n * 가장 최근의 업로드 요청 ID입니다.\n */\n const latestUploadRequestIdRef = useRef(0);\n\n /**\n * The options object.\n * deps에 포함되지 않는 옵션 객체입니다.\n */\n const optionRef = useRef<UploadHookOptions>(\n options ?? FALLBACK_UPLOAD_OPTIONS,\n );\n // eslint-disable-next-line react-hooks/refs\n optionRef.current = options ?? FALLBACK_UPLOAD_OPTIONS;\n\n /**\n * Updates the upload result and status state.\n * 업로드 결과 및 상태 스테이트를 업데이트합니다.\n */\n const updateUploadResult = useCallback((uploadResult: UploadResult) => {\n setResult(uploadResult);\n setStatus(uploadResult.status);\n\n if (uploadResult.status === \"error\") {\n setError(\n (prevError) =>\n prevError ?? new Error(uploadResult.message || \"Upload failed\"),\n );\n }\n }, []);\n\n /**\n * Core upload logic that manages request lifecycle and status updates.\n * 요청 수명 주기 및 상태 업데이트를 관리하는 핵심 업로드 로직입니다.\n */\n const upload = useCallback(\n async (file: File) => {\n /**\n * Increment the latest upload request ID.\n * 가장 최근의 업로드 요청 ID를 증가시킵니다.\n *\n * Set the latest upload request ID to the current value.\n * 가장 최근의 업로드 요청 ID를 현재 값으로 설정합니다.\n *\n * Check if the current request is the latest upload request.\n * 현재 요청이 가장 최근의 업로드 요청인지 확인합니다.\n */\n latestUploadRequestIdRef.current += 1;\n const latestUploadRequestId = latestUploadRequestIdRef.current;\n const isLatestUploadRequest = () =>\n latestUploadRequestIdRef.current === latestUploadRequestId;\n\n /**\n * If the previous request is still in progress, abort it.\n * 이전의 요청이 아직 진행 중이라면 중단합니다.\n */\n prevReqAbortRef.current.abort();\n\n /**\n * Reset external progress UI before a new upload starts.\n * 새 업로드가 시작되기 전에 외부 progress UI를 초기화합니다.\n */\n optionRef.current?.onProgress?.({ loaded: 0, total: 0, percentage: 0 });\n\n setStatus(\"uploading\");\n setError(null);\n setResult(INITIAL_UPLOAD_RESULT);\n\n const { current: $options } = optionRef;\n\n const { result: uploadResultPromise, actions: uploadActions } =\n await uploadCore({\n url,\n file,\n options: {\n ...$options,\n onComplete: () => {\n $options.onComplete?.();\n if (isLatestUploadRequest()) {\n setStatus(\"success\");\n setResult((prevResult) => ({\n ...prevResult,\n ok: true,\n status: \"success\",\n }));\n }\n },\n onError: (e) => {\n $options.onError?.(e);\n if (isLatestUploadRequest()) {\n setError(e);\n setStatus(\"error\");\n setResult((prevResult) => ({\n ...prevResult,\n ok: false,\n status: \"error\",\n }));\n }\n },\n onAbort: (e) => {\n $options.onAbort?.(e);\n if (isLatestUploadRequest()) {\n setStatus(\"aborted\");\n setResult((prevResult) => ({\n ...prevResult,\n ok: false,\n status: \"aborted\",\n }));\n }\n },\n onProgress: (args) => {\n $options.onProgress?.(args);\n if (isLatestUploadRequest()) {\n setStatus(\"uploading\");\n setResult({\n ok: false,\n total: args.total,\n status: \"uploading\",\n });\n }\n },\n onRetry: () => {\n $options.onRetry?.();\n if (isLatestUploadRequest()) {\n setStatus(\"uploading\");\n setResult(INITIAL_UPLOAD_RESULT);\n setError(null);\n }\n },\n onPause: () => {\n $options.onPause?.();\n if (isLatestUploadRequest()) {\n setStatus(\"paused\");\n setResult((prevResult) => ({\n ...prevResult,\n ok: false,\n status: \"paused\",\n }));\n }\n },\n onResume: () => {\n $options.onResume?.();\n if (isLatestUploadRequest()) {\n setStatus(\"uploading\");\n setResult((prevResult) => ({\n ...prevResult,\n status: \"uploading\",\n }));\n }\n },\n },\n });\n\n /**\n * If the current request is not the latest upload request, abort the request.\n * 현재 요청이 가장 최근의 업로드 요청이 아니라면 요청을 중단합니다.\n */\n if (!isLatestUploadRequest()) {\n uploadActions.abort();\n return;\n }\n\n // Set the previous request abort function to the current abort function.\n // 이전의 요청 중단 함수를 현재의 요청 중단 함수로 설정합니다.\n prevReqAbortRef.current = uploadActions;\n\n const uploadResult = await uploadResultPromise;\n\n /**\n * If the current request is not the latest upload request, do not update the result status.\n * 현재 요청이 가장 최근의 업로드 요청이 아니라면 결과 상태 업데이트를 하지 않고 반환합니다.\n */\n if (!isLatestUploadRequest()) {\n return;\n }\n\n updateUploadResult(uploadResult);\n },\n [url, updateUploadResult],\n );\n\n /**\n * Executes the upload with error handling and optional re-throwing.\n * 에러 핸들링 및 선택적 re-throw 기능을 포함하여 업로드를 실행합니다.\n */\n const uploadSafely = useCallback(\n async (file: File) => {\n try {\n await upload(file);\n } catch (e) {\n setError(e as Error);\n setStatus(\"error\");\n setResult({ ...INITIAL_UPLOAD_RESULT, status: \"error\" });\n\n const { current: $options } = optionRef;\n\n const shouldThrow =\n typeof $options.throwOnError === \"function\"\n ? $options.throwOnError(e)\n : Boolean($options.throwOnError);\n\n if (shouldThrow) {\n throw e;\n }\n }\n },\n [upload],\n );\n\n useEffect(() => {\n return () => {\n prevReqAbortRef.current.abort();\n };\n }, []);\n\n useEffect(() => {\n optionRef.current?.onUrlChange?.(url);\n }, [url]);\n\n useEffect(() => {\n optionRef.current?.onMethodChange?.(optionRef.current.method);\n }, [options?.method]);\n\n return {\n upload: uploadSafely,\n /**\n * Alias for upload — restarts upload after abort, error, or success.\n * upload의 별칭 — 중단, 실패, 성공 이후 업로드를 다시 시작할 때 사용합니다.\n */\n retry: uploadSafely,\n result,\n status,\n error,\n abort: () => prevReqAbortRef.current.abort(),\n pause: () => prevReqAbortRef.current.pause(),\n resume: () => prevReqAbortRef.current.resume(),\n };\n}\n"],"names":[],"mappings":"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"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { UploadOptions, UploadMethod as UploadMethodType } from "@universal-uploader/core";
|
|
2
|
+
/**
|
|
3
|
+
* Configuration options for the React hook.
|
|
4
|
+
* React 훅을 위한 구성 옵션입니다.
|
|
5
|
+
*/
|
|
6
|
+
export interface UploadHookOptions extends UploadOptions {
|
|
7
|
+
/** Callback when the target URL changes. / URL이 변경될 때 호출되는 콜백. */
|
|
8
|
+
onUrlChange?: (url: string) => void;
|
|
9
|
+
/** Callback when the upload method changes. / 업로드 방식이 변경될 때 호출되는 콜백. */
|
|
10
|
+
onMethodChange?: (method: UploadOptions["method"]) => void;
|
|
11
|
+
}
|
|
12
|
+
export type UploadMethod = UploadMethodType;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { UploadResult, UploadStatus } from "@universal-uploader/core";
|
|
2
|
+
import { UploadHookOptions } from "./types";
|
|
3
|
+
interface UseUniversalUploadArgs {
|
|
4
|
+
url: string;
|
|
5
|
+
options?: UploadHookOptions;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* A custom React hook for handling universal file uploads with streaming support and fallbacks.
|
|
9
|
+
* 스트리밍 지원 및 폴백 기능을 갖춘 범용 파일 업로드를 처리하기 위한 커스텀 React 훅입니다.
|
|
10
|
+
*/
|
|
11
|
+
export default function useUniversalUpload({ url, options, }: UseUniversalUploadArgs): {
|
|
12
|
+
upload: (file: File) => Promise<void>;
|
|
13
|
+
/**
|
|
14
|
+
* Alias for upload — restarts upload after abort, error, or success.
|
|
15
|
+
* upload의 별칭 — 중단, 실패, 성공 이후 업로드를 다시 시작할 때 사용합니다.
|
|
16
|
+
*/
|
|
17
|
+
retry: (file: File) => Promise<void>;
|
|
18
|
+
result: UploadResult;
|
|
19
|
+
status: UploadStatus;
|
|
20
|
+
error: Error | null;
|
|
21
|
+
abort: () => void;
|
|
22
|
+
pause: () => void;
|
|
23
|
+
resume: () => void;
|
|
24
|
+
};
|
|
25
|
+
export {};
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import uploadCore from '@usu/core';
|
|
2
|
+
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
3
|
+
const INITIAL_UPLOAD_RESULT = {
|
|
4
|
+
ok: false,
|
|
5
|
+
total: 0,
|
|
6
|
+
message: undefined,
|
|
7
|
+
status: 'idle',
|
|
8
|
+
};
|
|
9
|
+
export default function useUniversalUpload({ url, file, options }) {
|
|
10
|
+
const [status, setStatus] = useState('idle');
|
|
11
|
+
const [error, setError] = useState(null);
|
|
12
|
+
const [result, setResult] = useState(INITIAL_UPLOAD_RESULT);
|
|
13
|
+
/**
|
|
14
|
+
* The previous request abort function.
|
|
15
|
+
* 이전의 요청 중단 함수입니다.
|
|
16
|
+
*/
|
|
17
|
+
const prevReqAbortRef = useRef({ abort: () => null, refresh: () => null });
|
|
18
|
+
/**
|
|
19
|
+
* The latest upload request ID.
|
|
20
|
+
* 가장 최근의 업로드 요청 ID입니다.
|
|
21
|
+
*/
|
|
22
|
+
const latestUploadRequestIdRef = useRef(0);
|
|
23
|
+
/**
|
|
24
|
+
* The options object.
|
|
25
|
+
* deps에 포함되지 않는 옵션 객체입니다.
|
|
26
|
+
*/
|
|
27
|
+
const optionRef = useRef(options);
|
|
28
|
+
// eslint-disable-next-line react-hooks/refs
|
|
29
|
+
optionRef.current = options;
|
|
30
|
+
const updateUploadResult = useCallback((uploadResult) => {
|
|
31
|
+
setResult(uploadResult);
|
|
32
|
+
setStatus(uploadResult.status);
|
|
33
|
+
if (uploadResult.status === 'error') {
|
|
34
|
+
setError((prevError) => prevError ?? new Error(uploadResult.message || 'Upload failed'));
|
|
35
|
+
}
|
|
36
|
+
}, []);
|
|
37
|
+
const upload = useCallback(async () => {
|
|
38
|
+
/**
|
|
39
|
+
* Increment the latest upload request ID.
|
|
40
|
+
* 가장 최근의 업로드 요청 ID를 증가시킵니다.
|
|
41
|
+
*
|
|
42
|
+
* Set the latest upload request ID to the current value.
|
|
43
|
+
* 가장 최근의 업로드 요청 ID를 현재 값으로 설정합니다.
|
|
44
|
+
*
|
|
45
|
+
* Check if the current request is the latest upload request.
|
|
46
|
+
* 현재 요청이 가장 최근의 업로드 요청인지 확인합니다.
|
|
47
|
+
*/
|
|
48
|
+
latestUploadRequestIdRef.current += 1;
|
|
49
|
+
const latestUploadRequestId = latestUploadRequestIdRef.current;
|
|
50
|
+
const isLatestUploadRequest = () => latestUploadRequestIdRef.current === latestUploadRequestId;
|
|
51
|
+
/**
|
|
52
|
+
* If the previous request is still in progress, abort it.
|
|
53
|
+
* 이전의 요청이 아직 진행 중이라면 중단합니다.
|
|
54
|
+
*/
|
|
55
|
+
prevReqAbortRef.current.abort();
|
|
56
|
+
setStatus('uploading');
|
|
57
|
+
setError(null);
|
|
58
|
+
setResult(INITIAL_UPLOAD_RESULT);
|
|
59
|
+
const { current: $options } = optionRef;
|
|
60
|
+
const { result: uploadResultPromise, actions: uploadActions } = await uploadCore({
|
|
61
|
+
url,
|
|
62
|
+
file,
|
|
63
|
+
options: {
|
|
64
|
+
...$options,
|
|
65
|
+
onComplete: () => {
|
|
66
|
+
$options.onComplete?.();
|
|
67
|
+
if (isLatestUploadRequest()) {
|
|
68
|
+
setStatus('success');
|
|
69
|
+
setResult((prevResult) => ({ ...prevResult, ok: true, status: 'success' }));
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
onError: (e) => {
|
|
73
|
+
$options.onError?.(e);
|
|
74
|
+
if (isLatestUploadRequest()) {
|
|
75
|
+
setError(e);
|
|
76
|
+
setStatus('error');
|
|
77
|
+
setResult((prevResult) => ({ ...prevResult, ok: false, status: 'error' }));
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
onAbort: (e) => {
|
|
81
|
+
$options.onAbort?.(e);
|
|
82
|
+
if (isLatestUploadRequest()) {
|
|
83
|
+
setStatus('aborted');
|
|
84
|
+
setResult((prevResult) => ({ ...prevResult, ok: false, status: 'aborted' }));
|
|
85
|
+
}
|
|
86
|
+
},
|
|
87
|
+
onProgress: (args) => {
|
|
88
|
+
$options.onProgress?.(args);
|
|
89
|
+
if (isLatestUploadRequest()) {
|
|
90
|
+
setStatus('uploading');
|
|
91
|
+
setResult({ ok: false, total: args.percentage, status: 'uploading' });
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
onRetry: () => {
|
|
95
|
+
$options.onRetry?.();
|
|
96
|
+
if (isLatestUploadRequest()) {
|
|
97
|
+
setStatus('uploading');
|
|
98
|
+
setResult(INITIAL_UPLOAD_RESULT);
|
|
99
|
+
setError(null);
|
|
100
|
+
}
|
|
101
|
+
},
|
|
102
|
+
},
|
|
103
|
+
});
|
|
104
|
+
/**
|
|
105
|
+
* If the current request is not the latest upload request, abort the request.
|
|
106
|
+
* 현재 요청이 가장 최근의 업로드 요청이 아니라면 요청을 중단합니다.
|
|
107
|
+
*/
|
|
108
|
+
if (!isLatestUploadRequest()) {
|
|
109
|
+
uploadActions.abort();
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
// Set the previous request abort function to the current abort function.
|
|
113
|
+
// 이전의 요청 중단 함수를 현재의 요청 중단 함수로 설정합니다.
|
|
114
|
+
prevReqAbortRef.current = uploadActions;
|
|
115
|
+
const uploadResult = await uploadResultPromise;
|
|
116
|
+
/**
|
|
117
|
+
* If the current request is not the latest upload request, do not update the result status.
|
|
118
|
+
* 현재 요청이 가장 최근의 업로드 요청이 아니라면 결과 상태 업데이트를 하지 않고 반환합니다.
|
|
119
|
+
*/
|
|
120
|
+
if (!isLatestUploadRequest()) {
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
updateUploadResult(uploadResult);
|
|
124
|
+
}, [url, file, updateUploadResult]);
|
|
125
|
+
const uploadSafely = useCallback(async () => {
|
|
126
|
+
try {
|
|
127
|
+
await upload();
|
|
128
|
+
}
|
|
129
|
+
catch (e) {
|
|
130
|
+
setError(e);
|
|
131
|
+
setStatus('error');
|
|
132
|
+
setResult({ ...INITIAL_UPLOAD_RESULT, status: 'error' });
|
|
133
|
+
const { current: $options } = optionRef;
|
|
134
|
+
const shouldThrow = typeof $options.throwOnError === 'function'
|
|
135
|
+
? $options.throwOnError(e)
|
|
136
|
+
: Boolean($options.throwOnError);
|
|
137
|
+
if (shouldThrow) {
|
|
138
|
+
throw e;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}, [upload]);
|
|
142
|
+
useEffect(() => {
|
|
143
|
+
return () => {
|
|
144
|
+
prevReqAbortRef.current.abort();
|
|
145
|
+
};
|
|
146
|
+
}, []);
|
|
147
|
+
return {
|
|
148
|
+
upload: uploadSafely,
|
|
149
|
+
result,
|
|
150
|
+
status,
|
|
151
|
+
error,
|
|
152
|
+
abort: () => prevReqAbortRef.current.abort(),
|
|
153
|
+
refresh: async () => {
|
|
154
|
+
prevReqAbortRef.current.abort();
|
|
155
|
+
await uploadSafely();
|
|
156
|
+
},
|
|
157
|
+
};
|
|
158
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@universal-uploader/react",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "React hook wrapper for Universal Stream Uploader.",
|
|
6
|
+
"main": "dist/index.cjs",
|
|
7
|
+
"module": "dist/index.js",
|
|
8
|
+
"types": "dist/index.d.ts",
|
|
9
|
+
"files": [
|
|
10
|
+
"dist"
|
|
11
|
+
],
|
|
12
|
+
"peerDependencies": {
|
|
13
|
+
"react": "^19.2.6"
|
|
14
|
+
},
|
|
15
|
+
"dependencies": {
|
|
16
|
+
"@universal-uploader/core": "1.0.0"
|
|
17
|
+
},
|
|
18
|
+
"devDependencies": {
|
|
19
|
+
"@types/react": "^19.2.14",
|
|
20
|
+
"@types/react-dom": "^19.2.3"
|
|
21
|
+
},
|
|
22
|
+
"publishConfig": {
|
|
23
|
+
"access": "public"
|
|
24
|
+
},
|
|
25
|
+
"repository": {
|
|
26
|
+
"type": "git",
|
|
27
|
+
"url": "https://github.com/jdy8739/universal-uploader.git",
|
|
28
|
+
"directory": "packages/react"
|
|
29
|
+
},
|
|
30
|
+
"license": "MIT",
|
|
31
|
+
"scripts": {
|
|
32
|
+
"build": "rollup -c rollup.config.mjs",
|
|
33
|
+
"type-check": "tsc --noEmit"
|
|
34
|
+
}
|
|
35
|
+
}
|