@kawaiininja/fetch 1.0.11 โ 1.0.13
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/context/ApiContext.js +24 -20
- package/dist/hooks/useCsrf.js +2 -2
- package/dist/hooks/useFetch.js +11 -26
- package/package.json +1 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import { createContext, useMemo } from "react";
|
|
2
|
+
import { createContext, useCallback, useMemo } from "react";
|
|
3
3
|
/**
|
|
4
4
|
* Internal service header for API requests
|
|
5
5
|
*/
|
|
@@ -14,24 +14,28 @@ export const ApiContext = createContext(null);
|
|
|
14
14
|
* Constructs full API URLs from relative paths using the baseUrl.
|
|
15
15
|
*/
|
|
16
16
|
export function ApiProvider({ config, children }) {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
17
|
+
// Ensure valid config structure
|
|
18
|
+
const baseUrl = config?.baseUrl || "";
|
|
19
|
+
const version = config?.version || "v1";
|
|
20
|
+
const disableCsrf = !!config?.disableCsrf;
|
|
21
|
+
const onError = config?.onError;
|
|
22
|
+
/**
|
|
23
|
+
* ๐ก๏ธ HYPER-STABLE URL CONSTRUCTOR
|
|
24
|
+
* Memoized independently of the context value to prevent downstream loop invalidation.
|
|
25
|
+
*/
|
|
26
|
+
const apiUrl = useCallback((path) => {
|
|
27
|
+
if (path.startsWith("http"))
|
|
28
|
+
return path;
|
|
29
|
+
const cleanBase = baseUrl.endsWith("/") ? baseUrl.slice(0, -1) : baseUrl;
|
|
30
|
+
const cleanPath = path.startsWith("/") ? path : `/${path}`;
|
|
31
|
+
return `${cleanBase}${cleanPath}`;
|
|
32
|
+
}, [baseUrl]);
|
|
33
|
+
const value = useMemo(() => ({
|
|
34
|
+
baseUrl,
|
|
35
|
+
version,
|
|
36
|
+
disableCsrf,
|
|
37
|
+
onError,
|
|
38
|
+
apiUrl,
|
|
39
|
+
}), [baseUrl, version, disableCsrf, onError, apiUrl]);
|
|
36
40
|
return _jsx(ApiContext.Provider, { value: value, children: children });
|
|
37
41
|
}
|
package/dist/hooks/useCsrf.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { SecureStoragePlugin } from "capacitor-secure-storage-plugin";
|
|
2
|
-
import { useCallback, useRef } from "react";
|
|
2
|
+
import { useCallback, useMemo, useRef } from "react";
|
|
3
3
|
import { INTERNAL_HEADER } from "../context/ApiContext";
|
|
4
4
|
import { isNative } from "./platform";
|
|
5
5
|
import { useApiConfig } from "./useApiConfig";
|
|
@@ -85,5 +85,5 @@ export function useCsrf() {
|
|
|
85
85
|
}
|
|
86
86
|
return token;
|
|
87
87
|
}, [apiUrl]);
|
|
88
|
-
return { fetchCSRF, clearCsrf };
|
|
88
|
+
return useMemo(() => ({ fetchCSRF, clearCsrf }), [fetchCSRF, clearCsrf]);
|
|
89
89
|
}
|
package/dist/hooks/useFetch.js
CHANGED
|
@@ -44,7 +44,6 @@ export const useFetch = (endpoint, baseOptions = {}) => {
|
|
|
44
44
|
const abortRef = useRef(new AbortController());
|
|
45
45
|
const mounted = useRef(true);
|
|
46
46
|
const optionsRef = useRef(baseOptions);
|
|
47
|
-
const requestInProgress = useRef(false);
|
|
48
47
|
const [state, setState] = useState({
|
|
49
48
|
data: null,
|
|
50
49
|
loading: false,
|
|
@@ -76,7 +75,7 @@ export const useFetch = (endpoint, baseOptions = {}) => {
|
|
|
76
75
|
if (_isNative && isInternal) {
|
|
77
76
|
// Mobile relies on manual headers ("Active Courier")
|
|
78
77
|
// ๐ก๏ธ S-RANK UPGRADE: Use Session Memory Vault (In-Memory Singleton)
|
|
79
|
-
console.log(`[useFetch] [Native] Strategy: Active Courier for ${url}`);
|
|
78
|
+
// console.log(`[useFetch] [Native] Strategy: Active Courier for ${url}`);
|
|
80
79
|
if (!nativeAuthVault.loaded) {
|
|
81
80
|
// If an initialization is already in progress, await it
|
|
82
81
|
if (!nativeAuthVault.initPromise) {
|
|
@@ -125,20 +124,15 @@ export const useFetch = (endpoint, baseOptions = {}) => {
|
|
|
125
124
|
// ๐ SECURITY STRATEGY: WEB (BROWSER)
|
|
126
125
|
if (!_isNative && isInternal) {
|
|
127
126
|
// Web relies on Cookies ("Passive Courier") for Auth
|
|
128
|
-
// BUT we MUST attach the CSRF Token to prevent attacks
|
|
129
127
|
if (token) {
|
|
130
128
|
headersConfig["X-CSRF-Token"] = token;
|
|
131
|
-
// console.log("[useFetch] [Web] CSRF token attached.");
|
|
132
129
|
}
|
|
133
|
-
// On Web, we DO NOT attach 'Authorization' or 'Session-ID' from localStorage
|
|
134
|
-
// This minimizes XSS risk. The HttpOnly cookie handles it.
|
|
135
130
|
}
|
|
136
131
|
return fetch(url, {
|
|
137
132
|
...optionsRef.current,
|
|
138
133
|
...rest,
|
|
139
134
|
method,
|
|
140
135
|
signal: abortRef.current.signal,
|
|
141
|
-
// ๐ช Web: "include" sends cookies. Mobile: ignored/useless but harmless.
|
|
142
136
|
credentials: "include",
|
|
143
137
|
headers: headersConfig,
|
|
144
138
|
body,
|
|
@@ -158,35 +152,28 @@ export const useFetch = (endpoint, baseOptions = {}) => {
|
|
|
158
152
|
}
|
|
159
153
|
return res.text();
|
|
160
154
|
};
|
|
155
|
+
// ๐ STABLE REQUEST METHOD
|
|
161
156
|
const request = useCallback(async (params = {}) => {
|
|
162
|
-
const { url =
|
|
157
|
+
const { url = endpoint, method = "GET", body, headers, parseAs = "auto", ...rest } = params;
|
|
163
158
|
let finalUrl = url.startsWith("http") ? url : apiUrl(url);
|
|
164
159
|
if (!finalUrl)
|
|
165
160
|
return;
|
|
166
|
-
// ๐ก๏ธ RECURSION GUARD: Prevent overlapping requests from the same hook instance
|
|
167
|
-
if (requestInProgress.current) {
|
|
168
|
-
console.warn(`[useFetch] Request already in progress for ${finalUrl}`);
|
|
169
|
-
return;
|
|
170
|
-
}
|
|
171
|
-
requestInProgress.current = true;
|
|
172
161
|
safeSet(() => ({ loading: true, error: null }));
|
|
173
162
|
let lastStatus = null;
|
|
174
163
|
try {
|
|
175
|
-
const _isNative = isNative();
|
|
176
164
|
let csrfToken = "";
|
|
177
|
-
|
|
178
|
-
if (!disableCsrf && !_isNative) {
|
|
165
|
+
if (!disableCsrf) {
|
|
179
166
|
csrfToken = await fetchCSRF();
|
|
180
167
|
}
|
|
181
168
|
let res = await performFetch(finalUrl, method, csrfToken, body, headers, rest);
|
|
182
169
|
lastStatus = res.status;
|
|
183
|
-
// ๐ Auto-retry on CSRF error
|
|
184
|
-
if (!disableCsrf &&
|
|
170
|
+
// ๐ Auto-retry on CSRF error (One-time only)
|
|
171
|
+
if (!disableCsrf && res.status === 403) {
|
|
185
172
|
try {
|
|
186
173
|
const errorBody = await res.clone().json();
|
|
187
174
|
if (errorBody?.code === "CSRF_ERROR" ||
|
|
188
175
|
errorBody?.message === "Invalid or missing CSRF token") {
|
|
189
|
-
clearCsrf();
|
|
176
|
+
await clearCsrf();
|
|
190
177
|
csrfToken = await fetchCSRF();
|
|
191
178
|
res = await performFetch(finalUrl, method, csrfToken, body, headers, rest);
|
|
192
179
|
lastStatus = res.status;
|
|
@@ -206,25 +193,23 @@ export const useFetch = (endpoint, baseOptions = {}) => {
|
|
|
206
193
|
catch (err) {
|
|
207
194
|
if (err.name !== "AbortError") {
|
|
208
195
|
safeSet(() => ({ error: err.message, status: lastStatus }));
|
|
209
|
-
|
|
210
|
-
if (onError && !isNative()) {
|
|
196
|
+
if (onError)
|
|
211
197
|
onError(err.message, lastStatus);
|
|
212
|
-
}
|
|
213
198
|
throw err;
|
|
214
199
|
}
|
|
215
200
|
}
|
|
216
201
|
finally {
|
|
217
|
-
requestInProgress.current = false;
|
|
218
202
|
safeSet(() => ({ loading: false }));
|
|
219
203
|
}
|
|
220
|
-
}, [
|
|
204
|
+
}, [endpoint, fetchCSRF, clearCsrf, apiUrl, safeSet, disableCsrf, onError]);
|
|
205
|
+
// ๐งน LIFECYCLE
|
|
221
206
|
useEffect(() => {
|
|
222
|
-
abortRef.current = new AbortController();
|
|
223
207
|
mounted.current = true;
|
|
224
208
|
return () => {
|
|
225
209
|
mounted.current = false;
|
|
226
210
|
abortRef.current?.abort();
|
|
227
211
|
};
|
|
228
212
|
}, []);
|
|
213
|
+
// ๐น STABLE API SURFACE
|
|
229
214
|
return useMemo(() => createApiSurface({ state, request, baseUrl: resolvedUrl, safeSet }), [state, request, resolvedUrl, safeSet]);
|
|
230
215
|
};
|