@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 interface ApiProviderProps {
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, useEffect } from "react";
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 baseUrlRef = useRef(config?.baseUrl || "");
13
- const onErrorRef = useRef(config?.onError);
6
+ const configRef = useRef(config);
14
7
  useEffect(() => {
15
- baseUrlRef.current = config?.baseUrl || "";
16
- onErrorRef.current = config?.onError;
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 = baseUrlRef.current;
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: (err, status) => onErrorRef.current?.(err, status),
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
  }
@@ -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
- * 🛡️ CORE FETCH EXECUTOR
6
- * Handles the actual network request with retries and timeout.
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
- const authToken = nativeAuthVault.token;
19
- const sessionId = nativeAuthVault.sessionId;
20
- if (authToken)
21
- headersConfig["Authorization"] = `Bearer ${authToken}`;
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
- // 🔒 WEB SECURITY
26
- if (!_isNative && isInternal && token) {
21
+ if (!_isNative && isInternal && token)
27
22
  headersConfig["X-CSRF-Token"] = token;
28
- }
29
- return executeWithRetry(url, method, headersConfig, body, rest, debug, abortSignal);
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 Promise.race([
49
- fetch(url, { ...rest, method, signal: controller.signal, credentials: "include", headers, body }),
50
- new Promise((_, reject) => setTimeout(() => reject(new Error(`Timeout of ${TIMEOUT_MS}ms exceeded`)), TIMEOUT_MS)),
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" || abortSignal?.aborted)
63
- throw error;
49
+ if (error.name === "AbortError" || attempt === MAX_ATTEMPTS)
50
+ break;
64
51
  if (debug)
65
- console.warn(`[useFetch] Attempt ${attempt} failed: ${error.message}.`);
66
- if (attempt < maxAttempts)
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
  };
@@ -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
- // 🔒 Hold baseOptions in a Ref to prevent render-cycle re-triggers
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
- }, [optionsHash]);
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 Error";
71
- safeSet(() => ({ error: msg, status: lastStatus }));
53
+ const msg = err.message || "Network error";
54
+ safeSet(() => ({ error: msg, status: null }));
72
55
  if (onError)
73
- onError(msg, lastStatus);
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?.abort();
69
+ abortRef.current.abort();
87
70
  };
88
71
  }, []);
89
72
  const methods = useMemo(() => createApiMethods({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kawaiininja/fetch",
3
- "version": "1.0.28",
3
+ "version": "1.0.29",
4
4
  "description": "Core fetch utility for Onyx Framework",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",