@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.
@@ -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
- const value = useMemo(() => {
18
- // Ensure valid config structure
19
- const baseUrl = config?.baseUrl || "";
20
- const version = config?.version || "v1";
21
- return {
22
- baseUrl,
23
- version,
24
- disableCsrf: !!config?.disableCsrf,
25
- onError: config?.onError,
26
- // Helper to construct URLs
27
- apiUrl: (path) => {
28
- const cleanBase = baseUrl.endsWith("/")
29
- ? baseUrl.slice(0, -1)
30
- : baseUrl;
31
- const cleanPath = path.startsWith("/") ? path : `/${path}`;
32
- return `${cleanBase}${cleanPath}`;
33
- },
34
- };
35
- }, [config]);
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
  }
@@ -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
  }
@@ -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 = resolvedUrl, method = "GET", body, headers, parseAs = "auto", ...rest } = params;
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
- // ๐Ÿ”’ CSRF is only required for Web (Browser) strategy
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 && !isNative() && res.status === 403) {
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
- // ๐Ÿ“ฃ Global Error Broadcast (Disabled on Native)
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
- }, [resolvedUrl, fetchCSRF, clearCsrf, apiUrl, safeSet]);
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
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kawaiininja/fetch",
3
- "version": "1.0.11",
3
+ "version": "1.0.13",
4
4
  "description": "Core fetch utility for Onyx Framework",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",