react-native-nitro-fetch 1.3.0 → 1.3.1

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.
Files changed (48) hide show
  1. package/android/src/main/java/com/margelo/nitro/nitrofetch/AutoPrefetcher.kt +114 -31
  2. package/android/src/main/java/com/margelo/nitro/nitrofetch/NitroFetchClient.kt +4 -4
  3. package/ios/NitroAutoPrefetcher.h +21 -0
  4. package/ios/NitroAutoPrefetcher.swift +149 -27
  5. package/ios/NitroFetchClient.swift +4 -3
  6. package/lib/module/CurlGenerator.js.map +1 -1
  7. package/lib/module/HermesProfiler.js.map +2 -1
  8. package/lib/module/NetworkInspector.js +0 -4
  9. package/lib/module/NetworkInspector.js.map +1 -1
  10. package/lib/module/NitroCronet.nitro.js.map +1 -1
  11. package/lib/module/NitroFetch.nitro.js.map +1 -0
  12. package/lib/module/NitroInstances.js.map +1 -1
  13. package/lib/module/Response.js +1 -3
  14. package/lib/module/Response.js.map +2 -1
  15. package/lib/module/fetch.js +23 -2
  16. package/lib/module/fetch.js.map +1 -1
  17. package/lib/module/index.js +1 -1
  18. package/lib/module/index.js.map +2 -1
  19. package/lib/module/index.web.js +1 -2
  20. package/lib/module/utf8.js.map +2 -1
  21. package/lib/typescript/src/NitroFetch.nitro.d.ts +1 -0
  22. package/lib/typescript/src/NitroFetch.nitro.d.ts.map +1 -1
  23. package/lib/typescript/src/fetch.d.ts +4 -1
  24. package/lib/typescript/src/fetch.d.ts.map +1 -1
  25. package/lib/typescript/src/index.d.ts +1 -1
  26. package/lib/typescript/src/index.d.ts.map +1 -1
  27. package/nitrogen/generated/android/c++/JNitroRequest.hpp +5 -1
  28. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrofetch/NitroRequest.kt +5 -2
  29. package/nitrogen/generated/ios/swift/NitroRequest.swift +19 -1
  30. package/nitrogen/generated/shared/c++/NitroRequest.hpp +5 -1
  31. package/package.json +1 -1
  32. package/src/CurlGenerator.js +31 -0
  33. package/src/Headers.js +127 -0
  34. package/src/HermesProfiler.js +22 -0
  35. package/src/NetworkInspector.js +183 -0
  36. package/src/NitroCronet.nitro.js +1 -0
  37. package/src/NitroFetch.nitro.js +1 -0
  38. package/src/NitroFetch.nitro.ts +3 -0
  39. package/src/NitroInstances.js +6 -0
  40. package/src/Request.js +173 -0
  41. package/src/Response.js +258 -0
  42. package/src/fetch.js +772 -0
  43. package/src/fetch.ts +43 -4
  44. package/src/index.js +10 -0
  45. package/src/index.tsx +1 -0
  46. package/src/index.web.js +104 -0
  47. package/src/tokenRefresh.js +104 -0
  48. package/src/utf8.js +40 -0
package/src/fetch.ts CHANGED
@@ -167,7 +167,11 @@ function ensureClient() {
167
167
 
168
168
  function buildNitroRequest(
169
169
  input: RequestInfo | URL,
170
- init?: RequestInit & { redirect?: RequestRedirect; cache?: RequestCache }
170
+ init?: RequestInit & {
171
+ redirect?: RequestRedirect;
172
+ cache?: RequestCache;
173
+ prefetchCacheTtlMs?: number;
174
+ }
171
175
  ): NitroRequestNative {
172
176
  'worklet';
173
177
  let url: string;
@@ -216,6 +220,11 @@ function buildNitroRequest(
216
220
  // Determine followRedirects based on redirect option
217
221
  const followRedirects = redirectOption === 'follow';
218
222
 
223
+ const prefetchCacheTtlMs =
224
+ typeof init?.prefetchCacheTtlMs === 'number'
225
+ ? init.prefetchCacheTtlMs
226
+ : undefined;
227
+
219
228
  return {
220
229
  url,
221
230
  method: (method?.toUpperCase() as any) ?? 'GET',
@@ -224,6 +233,7 @@ function buildNitroRequest(
224
233
  bodyBytes: undefined as any,
225
234
  bodyFormData: normalized?.bodyFormData,
226
235
  followRedirects,
236
+ prefetchCacheTtlMs,
227
237
  };
228
238
  }
229
239
 
@@ -324,7 +334,7 @@ function normalizeBodyPure(
324
334
  // Pure JS version of buildNitroRequest that doesnt use anything that breaks worklets
325
335
  export function buildNitroRequestPure(
326
336
  input: RequestInfo | URL,
327
- init?: RequestInit
337
+ init?: RequestInit & { prefetchCacheTtlMs?: number }
328
338
  ): NitroRequestNative {
329
339
  'worklet';
330
340
  let url: string;
@@ -356,6 +366,11 @@ export function buildNitroRequestPure(
356
366
  const headers = headersToPairsPure(headersInit);
357
367
  const normalized = normalizeBodyPure(body);
358
368
 
369
+ const prefetchCacheTtlMs =
370
+ typeof init?.prefetchCacheTtlMs === 'number'
371
+ ? init.prefetchCacheTtlMs
372
+ : undefined;
373
+
359
374
  return {
360
375
  url,
361
376
  method: (method?.toUpperCase() as any) ?? 'GET',
@@ -364,6 +379,7 @@ export function buildNitroRequestPure(
364
379
  // Only include bodyBytes when provided to avoid signaling upload data unintentionally
365
380
  bodyBytes: undefined as any,
366
381
  followRedirects: true,
382
+ prefetchCacheTtlMs,
367
383
  };
368
384
  }
369
385
 
@@ -769,11 +785,21 @@ export async function prefetchOnAppStart(
769
785
  {} as Record<string, string>
770
786
  );
771
787
 
772
- const entry = {
788
+ const entry: Record<string, any> = {
773
789
  url: req.url,
774
790
  prefetchKey,
775
791
  headers: headersObj,
776
- } as const;
792
+ };
793
+ if (req.method && req.method !== 'GET') entry.method = req.method;
794
+ if (req.bodyString !== undefined) entry.bodyString = req.bodyString;
795
+ if (typeof req.bodyBytes === 'string' && req.bodyBytes.length > 0)
796
+ entry.bodyBytes = req.bodyBytes;
797
+ if (req.bodyFormData && req.bodyFormData.length > 0)
798
+ entry.bodyFormData = req.bodyFormData;
799
+ if (typeof req.timeoutMs === 'number') entry.timeoutMs = req.timeoutMs;
800
+ if (req.followRedirects === false) entry.followRedirects = false;
801
+ if (typeof req.prefetchCacheTtlMs === 'number')
802
+ entry.prefetchCacheTtlMs = req.prefetchCacheTtlMs;
777
803
 
778
804
  // Write or append to storage queue
779
805
  try {
@@ -831,6 +857,19 @@ export async function removeAllFromAutoprefetch(): Promise<void> {
831
857
  NativeStorageSingleton.setString(KEY, JSON.stringify([]));
832
858
  }
833
859
 
860
+ export function __readAutoPrefetchQueue(): Array<Record<string, any>> {
861
+ try {
862
+ const raw = NativeStorageSingleton.getString(
863
+ 'nitrofetch_autoprefetch_queue'
864
+ );
865
+ if (!raw) return [];
866
+ const parsed = JSON.parse(raw);
867
+ return Array.isArray(parsed) ? parsed : [];
868
+ } catch {
869
+ return [];
870
+ }
871
+ }
872
+
834
873
  // Optional off-thread processing using react-native-worklets
835
874
 
836
875
  export type NitroWorkletMapper<T> = (payload: {
package/src/index.js ADDED
@@ -0,0 +1,10 @@
1
+ export { nitroFetch as fetch, nitroFetchOnWorklet, prefetch, prefetchOnAppStart, removeFromAutoPrefetch, removeAllFromAutoprefetch, __readAutoPrefetchQueue, } from './fetch';
2
+ export { NitroHeaders as Headers } from './Headers';
3
+ export { NitroResponse as Response } from './Response';
4
+ export { NitroRequest as Request } from './Request';
5
+ export { NitroFetch } from './NitroInstances';
6
+ export { registerTokenRefresh, clearTokenRefresh, callRefreshEndpoint, getStoredTokenRefreshConfig, getNestedField, applyTemplate, } from './tokenRefresh';
7
+ export { NetworkInspector } from './NetworkInspector';
8
+ export { generateCurl } from './CurlGenerator';
9
+ export { profileFetch } from './HermesProfiler';
10
+ import './fetch';
package/src/index.tsx CHANGED
@@ -5,6 +5,7 @@ export {
5
5
  prefetchOnAppStart,
6
6
  removeFromAutoPrefetch,
7
7
  removeAllFromAutoprefetch,
8
+ __readAutoPrefetchQueue,
8
9
  } from './fetch';
9
10
  export type { NitroFormDataPart } from './fetch';
10
11
  export type {
@@ -0,0 +1,104 @@
1
+ // Web entry for react-native-nitro-fetch.
2
+ // Native (Cronet/URLSession) is unavailable on the web, so we delegate to the
3
+ // browser's built-in fetch and stub native-only APIs with console.warn.
4
+ import { NitroRequest as NitroRequestClass } from './Request';
5
+ export { NitroHeaders as Headers } from './Headers';
6
+ export { NitroResponse as Response } from './Response';
7
+ export { NitroRequest as Request } from './Request';
8
+ export { NetworkInspector } from './NetworkInspector';
9
+ export { generateCurl } from './CurlGenerator';
10
+ export { profileFetch } from './HermesProfiler';
11
+ export async function fetch(input, init) {
12
+ let resolvedInput = input;
13
+ let resolvedInit = init;
14
+ if (input instanceof NitroRequestClass) {
15
+ const method = (init?.method ?? input.method).toUpperCase();
16
+ const hasBodyMethod = method !== 'GET' && method !== 'HEAD';
17
+ let body = init?.body;
18
+ if (body === undefined && hasBodyMethod) {
19
+ const bytes = await input.arrayBuffer().catch(() => undefined);
20
+ body = bytes && bytes.byteLength > 0 ? bytes : null;
21
+ }
22
+ resolvedInput = input.url;
23
+ resolvedInit = {
24
+ ...init,
25
+ method,
26
+ headers: (init?.headers ??
27
+ input.headers),
28
+ body,
29
+ redirect: init?.redirect ?? input.redirect,
30
+ cache: init?.cache ?? input.cache,
31
+ signal: init?.signal ?? input.signal,
32
+ };
33
+ }
34
+ return globalThis.fetch(resolvedInput, resolvedInit);
35
+ }
36
+ export async function nitroFetchOnWorklet(input, init, mapWorklet, _options) {
37
+ console.warn('nitroFetchOnWorklet: worklets are not available on web; running on the JS thread');
38
+ const res = await globalThis.fetch(input, init);
39
+ const bodyBytes = await res.clone().arrayBuffer();
40
+ let bodyString;
41
+ try {
42
+ bodyString = await res.clone().text();
43
+ }
44
+ catch {
45
+ bodyString = undefined;
46
+ }
47
+ const headers = [];
48
+ res.headers.forEach((v, k) => headers.push({ key: k, value: v }));
49
+ return mapWorklet({
50
+ url: res.url,
51
+ status: res.status,
52
+ statusText: res.statusText,
53
+ ok: res.ok,
54
+ redirected: res.redirected ?? false,
55
+ headers,
56
+ bodyBytes,
57
+ bodyString,
58
+ });
59
+ }
60
+ export async function prefetch(_input, _init) {
61
+ console.warn('prefetch is not available on web');
62
+ }
63
+ export async function prefetchOnAppStart(_input, _init) {
64
+ console.warn('prefetchOnAppStart is not available on web');
65
+ }
66
+ export async function removeFromAutoPrefetch(_prefetchKey) {
67
+ console.warn('removeFromAutoPrefetch is not available on web');
68
+ }
69
+ export async function removeAllFromAutoprefetch() {
70
+ console.warn('removeAllFromAutoprefetch is not available on web');
71
+ }
72
+ export const NitroFetch = new Proxy({}, {
73
+ get(_target, prop) {
74
+ console.warn(`NitroFetch.${String(prop)} is not available on web`);
75
+ return undefined;
76
+ },
77
+ });
78
+ export function registerTokenRefresh(_options) {
79
+ console.warn('registerTokenRefresh is not available on web');
80
+ }
81
+ export function clearTokenRefresh(_target) {
82
+ console.warn('clearTokenRefresh is not available on web');
83
+ }
84
+ export async function callRefreshEndpoint(_config) {
85
+ console.warn('callRefreshEndpoint is not available on web');
86
+ return {};
87
+ }
88
+ export function getStoredTokenRefreshConfig(_target) {
89
+ console.warn('getStoredTokenRefreshConfig is not available on web');
90
+ return null;
91
+ }
92
+ export function getNestedField(obj, dotPath) {
93
+ const parts = dotPath.split('.');
94
+ let current = obj;
95
+ for (const part of parts) {
96
+ if (current == null || typeof current !== 'object')
97
+ return undefined;
98
+ current = current[part];
99
+ }
100
+ return current != null ? String(current) : undefined;
101
+ }
102
+ export function applyTemplate(template, value) {
103
+ return template.replace(/\{\{value\}\}/g, value);
104
+ }
@@ -0,0 +1,104 @@
1
+ import { NativeStorage as NativeStorageSingleton } from './NitroInstances';
2
+ // Storage keys
3
+ const KEY_WS = 'nitro_token_refresh_websocket';
4
+ const KEY_FETCH = 'nitro_token_refresh_fetch';
5
+ const KEY_WS_CACHE = 'nitro_token_refresh_ws_cache';
6
+ const KEY_FETCH_CACHE = 'nitro_token_refresh_fetch_cache';
7
+ // — Helpers —
8
+ /**
9
+ * Resolve a dot-notation path inside a parsed JSON object.
10
+ */
11
+ export function getNestedField(obj, dotPath) {
12
+ const parts = dotPath.split('.');
13
+ let current = obj;
14
+ for (const part of parts) {
15
+ if (current == null || typeof current !== 'object')
16
+ return undefined;
17
+ current = current[part];
18
+ }
19
+ return current != null ? String(current) : undefined;
20
+ }
21
+ export function applyTemplate(template, value) {
22
+ return template.replace(/\{\{value\}\}/g, value);
23
+ }
24
+ function applyCompositeTemplate(template, values) {
25
+ return template.replace(/\{\{(\w+)\}\}/g, (_, key) => values[key] ?? '');
26
+ }
27
+ export async function callRefreshEndpoint(config) {
28
+ const method = config.method ?? 'POST';
29
+ const response = await fetch(config.url, {
30
+ method,
31
+ headers: config.headers,
32
+ body: config.body,
33
+ });
34
+ if (!response.ok) {
35
+ throw new Error(`Token refresh failed: ${response.status} ${response.statusText}`);
36
+ }
37
+ const headers = {};
38
+ if (config.responseType === 'text') {
39
+ const text = await response.text();
40
+ if (config.textHeader) {
41
+ headers[config.textHeader] = config.textTemplate
42
+ ? applyTemplate(config.textTemplate, text)
43
+ : text;
44
+ }
45
+ return headers;
46
+ }
47
+ // Default: json
48
+ const json = await response.json();
49
+ if (config.mappings) {
50
+ for (const mapping of config.mappings) {
51
+ const value = getNestedField(json, mapping.jsonPath);
52
+ if (value != null) {
53
+ headers[mapping.header] = mapping.valueTemplate
54
+ ? applyTemplate(mapping.valueTemplate, value)
55
+ : value;
56
+ }
57
+ }
58
+ }
59
+ if (config.compositeHeaders) {
60
+ for (const comp of config.compositeHeaders) {
61
+ const values = {};
62
+ for (const [placeholder, jsonPath] of Object.entries(comp.paths)) {
63
+ const val = getNestedField(json, jsonPath);
64
+ if (val != null)
65
+ values[placeholder] = val;
66
+ }
67
+ headers[comp.header] = applyCompositeTemplate(comp.template, values);
68
+ }
69
+ }
70
+ return headers;
71
+ }
72
+ export function registerTokenRefresh(options) {
73
+ const { target, ...config } = options;
74
+ const raw = JSON.stringify(config);
75
+ if (target === 'websocket' || target === 'all') {
76
+ NativeStorageSingleton.setSecureString(KEY_WS, raw);
77
+ }
78
+ if (target === 'fetch' || target === 'all') {
79
+ NativeStorageSingleton.setSecureString(KEY_FETCH, raw);
80
+ }
81
+ }
82
+ export function clearTokenRefresh(target) {
83
+ const t = target ?? 'all';
84
+ if (t === 'websocket' || t === 'all') {
85
+ NativeStorageSingleton.removeSecureString(KEY_WS);
86
+ NativeStorageSingleton.removeSecureString(KEY_WS_CACHE);
87
+ }
88
+ if (t === 'fetch' || t === 'all') {
89
+ NativeStorageSingleton.removeSecureString(KEY_FETCH);
90
+ NativeStorageSingleton.removeSecureString(KEY_FETCH_CACHE);
91
+ }
92
+ }
93
+ export function getStoredTokenRefreshConfig(target) {
94
+ const key = target === 'websocket' ? KEY_WS : KEY_FETCH;
95
+ try {
96
+ const raw = NativeStorageSingleton.getSecureString(key);
97
+ if (!raw)
98
+ return null;
99
+ return JSON.parse(raw);
100
+ }
101
+ catch {
102
+ return null;
103
+ }
104
+ }
package/src/utf8.js ADDED
@@ -0,0 +1,40 @@
1
+ let _TextEncoder;
2
+ let _TextDecoder;
3
+ const NITRO_TEXT_DECODER_PKG = 'react-native-nitro-text-decoder';
4
+ function loadOptionalTextCodec() {
5
+ try {
6
+ // Hide require from the bundler so the package stays truly optional.
7
+ // eslint-disable-next-line no-new-func
8
+ const dynamicRequire = new Function('mod', 'return require(mod);');
9
+ return dynamicRequire(NITRO_TEXT_DECODER_PKG);
10
+ }
11
+ catch {
12
+ return {};
13
+ }
14
+ }
15
+ if (typeof TextEncoder !== 'undefined') {
16
+ _TextEncoder = TextEncoder;
17
+ }
18
+ else {
19
+ _TextEncoder = loadOptionalTextCodec().TextEncoder;
20
+ }
21
+ if (typeof TextDecoder !== 'undefined') {
22
+ _TextDecoder = TextDecoder;
23
+ }
24
+ else {
25
+ _TextDecoder = loadOptionalTextCodec().TextDecoder;
26
+ }
27
+ export function stringToUTF8(str) {
28
+ if (!_TextEncoder) {
29
+ console.warn('stringToUTF8: TextEncoder not available. Install react-native-nitro-text-decoder.');
30
+ return new Uint8Array(0);
31
+ }
32
+ return new _TextEncoder().encode(str);
33
+ }
34
+ export function utf8ToString(bytes) {
35
+ if (!_TextDecoder) {
36
+ console.warn('utf8ToString: TextDecoder not available. Install react-native-nitro-text-decoder.');
37
+ return '';
38
+ }
39
+ return new _TextDecoder().decode(bytes);
40
+ }