@kawaiininja/fetch 1.0.28 → 1.0.29
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.
|
@@ -15,13 +15,8 @@ export interface ApiContextValue {
|
|
|
15
15
|
onError?: (error: string, status: number | null) => void;
|
|
16
16
|
debug: boolean;
|
|
17
17
|
}
|
|
18
|
-
export
|
|
18
|
+
export declare const ApiContext: React.Context<ApiContextValue | null>;
|
|
19
|
+
export declare function ApiProvider({ config, children }: {
|
|
19
20
|
config: ApiConfig;
|
|
20
21
|
children: React.ReactNode;
|
|
21
|
-
}
|
|
22
|
-
export declare const ApiContext: React.Context<ApiContextValue | null>;
|
|
23
|
-
/**
|
|
24
|
-
* ApiProvider
|
|
25
|
-
* 🛡️ GOD-LEVEL STABILITY: Decouples helper identity from render cycles.
|
|
26
|
-
*/
|
|
27
|
-
export declare function ApiProvider({ config, children }: ApiProviderProps): import("react/jsx-runtime").JSX.Element;
|
|
22
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
@@ -1,41 +1,26 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import { createContext, useCallback, useMemo, useRef
|
|
3
|
-
export const INTERNAL_HEADER = {
|
|
4
|
-
"x-internal-service": "zevrinix-main",
|
|
5
|
-
};
|
|
2
|
+
import { createContext, useCallback, useEffect, useMemo, useRef } from "react";
|
|
3
|
+
export const INTERNAL_HEADER = { "x-internal-service": "zevrinix-main" };
|
|
6
4
|
export const ApiContext = createContext(null);
|
|
7
|
-
/**
|
|
8
|
-
* ApiProvider
|
|
9
|
-
* 🛡️ GOD-LEVEL STABILITY: Decouples helper identity from render cycles.
|
|
10
|
-
*/
|
|
11
5
|
export function ApiProvider({ config, children }) {
|
|
12
|
-
const
|
|
13
|
-
const onErrorRef = useRef(config?.onError);
|
|
6
|
+
const configRef = useRef(config);
|
|
14
7
|
useEffect(() => {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
}, [config?.baseUrl, config?.onError]);
|
|
18
|
-
/**
|
|
19
|
-
* 🛡️ PERMANENT IDENTITY URL HELPER
|
|
20
|
-
* Identity is fixed for the life of the App. No downstream loops possible.
|
|
21
|
-
*/
|
|
8
|
+
configRef.current = config;
|
|
9
|
+
}, [config]);
|
|
22
10
|
const apiUrl = useCallback((path) => {
|
|
23
|
-
const base =
|
|
11
|
+
const base = configRef.current.baseUrl;
|
|
24
12
|
if (path.startsWith("http"))
|
|
25
13
|
return path;
|
|
26
14
|
const cleanBase = base.endsWith("/") ? base.slice(0, -1) : base;
|
|
27
15
|
const cleanPath = path.startsWith("/") ? path : `/${path}`;
|
|
28
16
|
return `${cleanBase}${cleanPath}`;
|
|
29
17
|
}, []);
|
|
30
|
-
const baseUrl = config?.baseUrl || "";
|
|
31
|
-
const version = config?.version || "v1";
|
|
32
|
-
const debug = !!config?.debug;
|
|
33
18
|
const value = useMemo(() => ({
|
|
34
|
-
baseUrl,
|
|
35
|
-
version,
|
|
36
|
-
debug,
|
|
19
|
+
baseUrl: config.baseUrl,
|
|
20
|
+
version: config.version || "v1",
|
|
21
|
+
debug: !!config.debug,
|
|
37
22
|
apiUrl,
|
|
38
|
-
onError: (
|
|
39
|
-
}), [baseUrl, version, debug, apiUrl]);
|
|
23
|
+
onError: (e, s) => configRef.current.onError?.(e, s),
|
|
24
|
+
}), [config.baseUrl, config.version, config.debug, apiUrl]);
|
|
40
25
|
return _jsx(ApiContext.Provider, { value: value, children: children });
|
|
41
26
|
}
|
package/dist/hooks/useFetch.d.ts
CHANGED
|
@@ -1,7 +1,2 @@
|
|
|
1
1
|
import { ApiSurface } from "./utils";
|
|
2
|
-
export { clearNativeAuthVault } from "./useFetch.vault";
|
|
3
|
-
/**
|
|
4
|
-
* useFetch Hook
|
|
5
|
-
* 🛡️ STACK PROTECTION: Macro-task Flattening + Identity Locking
|
|
6
|
-
*/
|
|
7
2
|
export declare const useFetch: <T = any>(endpoint: string, baseOptions?: any) => ApiSurface<T>;
|
|
@@ -1,6 +1,2 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 🛡️ CORE FETCH EXECUTOR
|
|
3
|
-
* Handles the actual network request with retries and timeout.
|
|
4
|
-
*/
|
|
5
1
|
export declare const performFetch: (url: string, method: string, token: string, body?: BodyInit, headers?: HeadersInit, rest?: RequestInit, debug?: boolean, abortSignal?: AbortSignal, apiUrl?: (path: string) => string) => Promise<Response>;
|
|
6
2
|
export declare const parseResponse: (res: Response, parseAs: string) => Promise<any>;
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import { INTERNAL_HEADER } from "../context/ApiContext";
|
|
2
2
|
import { isNative } from "./platform";
|
|
3
3
|
import { initializeVault, nativeAuthVault } from "./useFetch.vault";
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
*/
|
|
4
|
+
const TIMEOUT_MS = 15000;
|
|
5
|
+
const RETRY_DELAY_MS = 3000;
|
|
6
|
+
const MAX_ATTEMPTS = 3;
|
|
8
7
|
export const performFetch = async (url, method, token, body, headers, rest, debug, abortSignal, apiUrl) => {
|
|
9
8
|
const _isNative = isNative();
|
|
10
9
|
const isInternal = url.startsWith("/") || (apiUrl && url.startsWith(apiUrl(""))) || false;
|
|
@@ -12,43 +11,31 @@ export const performFetch = async (url, method, token, body, headers, rest, debu
|
|
|
12
11
|
...(headers || {}),
|
|
13
12
|
...INTERNAL_HEADER,
|
|
14
13
|
};
|
|
15
|
-
// 🔒 NATIVE SECURITY
|
|
16
14
|
if (_isNative && isInternal) {
|
|
17
15
|
await initializeVault(debug);
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
if (
|
|
21
|
-
headersConfig["
|
|
22
|
-
if (sessionId)
|
|
23
|
-
headersConfig["X-Session-ID"] = sessionId;
|
|
16
|
+
if (nativeAuthVault.token)
|
|
17
|
+
headersConfig["Authorization"] = `Bearer ${nativeAuthVault.token}`;
|
|
18
|
+
if (nativeAuthVault.sessionId)
|
|
19
|
+
headersConfig["X-Session-ID"] = nativeAuthVault.sessionId;
|
|
24
20
|
}
|
|
25
|
-
|
|
26
|
-
if (!_isNative && isInternal && token) {
|
|
21
|
+
if (!_isNative && isInternal && token)
|
|
27
22
|
headersConfig["X-CSRF-Token"] = token;
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
};
|
|
31
|
-
/**
|
|
32
|
-
* Executes a fetch request with 3 retry attempts.
|
|
33
|
-
*/
|
|
34
|
-
async function executeWithRetry(url, method, headers, body, rest, debug, abortSignal) {
|
|
35
|
-
let attempt = 0;
|
|
36
|
-
const maxAttempts = 3;
|
|
37
|
-
let lastError = null;
|
|
38
|
-
while (attempt < maxAttempts) {
|
|
39
|
-
attempt++;
|
|
40
|
-
const TIMEOUT_MS = 15000;
|
|
23
|
+
let lastError;
|
|
24
|
+
for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {
|
|
41
25
|
const controller = new AbortController();
|
|
42
26
|
const timeoutId = setTimeout(() => controller.abort(), TIMEOUT_MS);
|
|
43
|
-
// Link to external abort signal if provided
|
|
44
27
|
const onAbort = () => controller.abort();
|
|
45
28
|
if (abortSignal)
|
|
46
29
|
abortSignal.addEventListener("abort", onAbort);
|
|
47
30
|
try {
|
|
48
|
-
const response = await
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
31
|
+
const response = await fetch(url, {
|
|
32
|
+
...rest,
|
|
33
|
+
method,
|
|
34
|
+
signal: controller.signal,
|
|
35
|
+
credentials: "include",
|
|
36
|
+
headers: headersConfig,
|
|
37
|
+
body,
|
|
38
|
+
});
|
|
52
39
|
clearTimeout(timeoutId);
|
|
53
40
|
if (abortSignal)
|
|
54
41
|
abortSignal.removeEventListener("abort", onAbort);
|
|
@@ -59,26 +46,22 @@ async function executeWithRetry(url, method, headers, body, rest, debug, abortSi
|
|
|
59
46
|
if (abortSignal)
|
|
60
47
|
abortSignal.removeEventListener("abort", onAbort);
|
|
61
48
|
lastError = error;
|
|
62
|
-
if (error.name === "AbortError" ||
|
|
63
|
-
|
|
49
|
+
if (error.name === "AbortError" || attempt === MAX_ATTEMPTS)
|
|
50
|
+
break;
|
|
64
51
|
if (debug)
|
|
65
|
-
console.warn(`[
|
|
66
|
-
|
|
67
|
-
await new Promise((r) => setTimeout(r, 1000));
|
|
52
|
+
console.warn(`[API] Attempt ${attempt} failed, retrying in 3s...`);
|
|
53
|
+
await new Promise((resolve) => setTimeout(resolve, RETRY_DELAY_MS));
|
|
68
54
|
}
|
|
69
55
|
}
|
|
70
56
|
throw lastError || new Error("Request failed after 3 attempts");
|
|
71
|
-
}
|
|
57
|
+
};
|
|
72
58
|
export const parseResponse = async (res, parseAs) => {
|
|
73
59
|
const type = res.headers.get("content-type") || "";
|
|
74
|
-
if (parseAs === "json" || (parseAs === "auto" && type.includes("application/json")))
|
|
60
|
+
if (parseAs === "json" || (parseAs === "auto" && type.includes("application/json")))
|
|
75
61
|
return res.json();
|
|
76
|
-
|
|
77
|
-
else if (parseAs === "text") {
|
|
62
|
+
if (parseAs === "text")
|
|
78
63
|
return res.text();
|
|
79
|
-
|
|
80
|
-
else if (parseAs === "blob") {
|
|
64
|
+
if (parseAs === "blob")
|
|
81
65
|
return res.blob();
|
|
82
|
-
|
|
83
|
-
return res.text();
|
|
66
|
+
return res;
|
|
84
67
|
};
|
package/dist/hooks/useFetch.js
CHANGED
|
@@ -3,74 +3,57 @@ import { useApiConfig } from "./useApiConfig";
|
|
|
3
3
|
import { useCsrf } from "./useCsrf";
|
|
4
4
|
import { parseResponse, performFetch } from "./useFetch.executor";
|
|
5
5
|
import { createApiMethods } from "./utils";
|
|
6
|
-
export { clearNativeAuthVault } from "./useFetch.vault";
|
|
7
|
-
/**
|
|
8
|
-
* useFetch Hook
|
|
9
|
-
* 🛡️ STACK PROTECTION: Macro-task Flattening + Identity Locking
|
|
10
|
-
*/
|
|
11
6
|
export const useFetch = (endpoint, baseOptions = {}) => {
|
|
12
7
|
const { apiUrl, onError, debug } = useApiConfig();
|
|
13
8
|
const { fetchCSRF } = useCsrf();
|
|
14
9
|
const abortRef = useRef(new AbortController());
|
|
15
10
|
const mounted = useRef(true);
|
|
16
11
|
const isRequestingRef = useRef(false);
|
|
17
|
-
//
|
|
18
|
-
// Removed Math.random() fallback which was causing infinite loops.
|
|
12
|
+
// 🛡️ LOCK IDENTITY: Ignore baseOptions identity shifts
|
|
19
13
|
const optionsRef = useRef(baseOptions);
|
|
20
|
-
const optionsHash = useMemo(() => {
|
|
21
|
-
try {
|
|
22
|
-
return JSON.stringify(baseOptions);
|
|
23
|
-
}
|
|
24
|
-
catch {
|
|
25
|
-
return endpoint;
|
|
26
|
-
}
|
|
27
|
-
}, [baseOptions, endpoint]);
|
|
28
14
|
useEffect(() => {
|
|
29
15
|
optionsRef.current = baseOptions;
|
|
30
|
-
}, [
|
|
16
|
+
}, [JSON.stringify(baseOptions)]);
|
|
31
17
|
const [state, setState] = useState({
|
|
32
18
|
data: null,
|
|
33
19
|
loading: false,
|
|
34
20
|
error: null,
|
|
35
21
|
status: null,
|
|
36
22
|
});
|
|
37
|
-
/**
|
|
38
|
-
* 🛡️ MACRO-TASK SETTER
|
|
39
|
-
* Using setTimeout(0) forces a physical stack clear by the JS event loop.
|
|
40
|
-
* This stops the "Stack Pile" effect in production APKs.
|
|
41
|
-
*/
|
|
42
23
|
const safeSet = useCallback((fn) => {
|
|
43
24
|
if (!mounted.current)
|
|
44
25
|
return;
|
|
26
|
+
// 🚀 STACK FLATTENER: setTimeout(0) physically clears the memory stack
|
|
45
27
|
setTimeout(() => {
|
|
46
|
-
if (mounted.current)
|
|
28
|
+
if (mounted.current)
|
|
47
29
|
setState((prev) => ({ ...prev, ...fn(prev) }));
|
|
48
|
-
}
|
|
49
30
|
}, 0);
|
|
50
31
|
}, []);
|
|
51
32
|
const request = useCallback(async (params = {}) => {
|
|
52
33
|
if (isRequestingRef.current)
|
|
53
34
|
return;
|
|
54
35
|
isRequestingRef.current = true;
|
|
36
|
+
if (debug)
|
|
37
|
+
console.log(`[API] Starting: ${endpoint}`);
|
|
55
38
|
safeSet(() => ({ loading: true, error: null }));
|
|
56
|
-
let lastStatus = null;
|
|
57
39
|
try {
|
|
58
40
|
const token = await fetchCSRF();
|
|
59
41
|
const finalUrl = params.url || (endpoint.startsWith("http") ? endpoint : apiUrl(endpoint));
|
|
60
42
|
const res = await performFetch(finalUrl, params.method || "GET", token, params.body, params.headers, { ...optionsRef.current, ...params }, debug, abortRef.current.signal, apiUrl);
|
|
61
|
-
lastStatus = res.status;
|
|
62
43
|
const parsed = await parseResponse(res, params.parseAs || "auto");
|
|
63
44
|
if (!res.ok)
|
|
64
45
|
throw new Error(parsed?.message || res.statusText);
|
|
46
|
+
if (debug)
|
|
47
|
+
console.log(`[API] Success: ${endpoint}`);
|
|
65
48
|
safeSet(() => ({ data: parsed, status: res.status }));
|
|
66
49
|
return parsed;
|
|
67
50
|
}
|
|
68
51
|
catch (err) {
|
|
69
52
|
if (err.name !== "AbortError") {
|
|
70
|
-
const msg = err.message || "Network
|
|
71
|
-
safeSet(() => ({ error: msg, status:
|
|
53
|
+
const msg = err.message || "Network error";
|
|
54
|
+
safeSet(() => ({ error: msg, status: null }));
|
|
72
55
|
if (onError)
|
|
73
|
-
onError(msg,
|
|
56
|
+
onError(msg, null);
|
|
74
57
|
throw err;
|
|
75
58
|
}
|
|
76
59
|
}
|
|
@@ -83,7 +66,7 @@ export const useFetch = (endpoint, baseOptions = {}) => {
|
|
|
83
66
|
mounted.current = true;
|
|
84
67
|
return () => {
|
|
85
68
|
mounted.current = false;
|
|
86
|
-
abortRef.current
|
|
69
|
+
abortRef.current.abort();
|
|
87
70
|
};
|
|
88
71
|
}, []);
|
|
89
72
|
const methods = useMemo(() => createApiMethods({
|