react-native-nitro-fetch 0.1.2 → 0.1.4

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 (45) hide show
  1. package/android/build.gradle +1 -0
  2. package/android/src/main/java/com/margelo/nitro/nitrofetch/AutoPrefetcher.kt +8 -27
  3. package/android/src/main/java/com/margelo/nitro/nitrofetch/FetchCache.kt +11 -1
  4. package/android/src/main/java/com/margelo/nitro/nitrofetch/NativeStorage.kt +102 -0
  5. package/android/src/main/java/com/margelo/nitro/nitrofetch/NitroFetchClient.kt +3 -3
  6. package/ios/NativeStorage.swift +61 -0
  7. package/ios/NitroAutoPrefetcher.swift +5 -41
  8. package/lib/module/NitroFetch.nitro.js +0 -3
  9. package/lib/module/NitroFetch.nitro.js.map +1 -1
  10. package/lib/module/NitroInstances.js +1 -0
  11. package/lib/module/NitroInstances.js.map +1 -1
  12. package/lib/module/fetch.js +63 -81
  13. package/lib/module/fetch.js.map +1 -1
  14. package/lib/typescript/src/NitroFetch.nitro.d.ts +8 -0
  15. package/lib/typescript/src/NitroFetch.nitro.d.ts.map +1 -1
  16. package/lib/typescript/src/NitroInstances.d.ts +2 -1
  17. package/lib/typescript/src/NitroInstances.d.ts.map +1 -1
  18. package/lib/typescript/src/fetch.d.ts.map +1 -1
  19. package/nitro.json +4 -0
  20. package/nitrogen/generated/android/c++/JHybridNativeStorageSpec.cpp +54 -0
  21. package/nitrogen/generated/android/c++/JHybridNativeStorageSpec.hpp +66 -0
  22. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrofetch/HybridNativeStorageSpec.kt +60 -0
  23. package/nitrogen/generated/android/nitrofetch+autolinking.cmake +9 -4
  24. package/nitrogen/generated/android/nitrofetchOnLoad.cpp +10 -0
  25. package/nitrogen/generated/ios/NitroFetch-Swift-Cxx-Bridge.cpp +17 -0
  26. package/nitrogen/generated/ios/NitroFetch-Swift-Cxx-Bridge.hpp +35 -0
  27. package/nitrogen/generated/ios/NitroFetch-Swift-Cxx-Umbrella.hpp +5 -0
  28. package/nitrogen/generated/ios/NitroFetchAutolinking.mm +8 -0
  29. package/nitrogen/generated/ios/NitroFetchAutolinking.swift +15 -0
  30. package/nitrogen/generated/ios/c++/HybridNativeStorageSpecSwift.cpp +11 -0
  31. package/nitrogen/generated/ios/c++/HybridNativeStorageSpecSwift.hpp +85 -0
  32. package/nitrogen/generated/ios/swift/HybridNativeStorageSpec.swift +51 -0
  33. package/nitrogen/generated/ios/swift/HybridNativeStorageSpec_cxx.swift +145 -0
  34. package/nitrogen/generated/shared/c++/HybridNativeStorageSpec.cpp +23 -0
  35. package/nitrogen/generated/shared/c++/HybridNativeStorageSpec.hpp +64 -0
  36. package/package.json +8 -24
  37. package/src/NitroFetch.nitro.ts +12 -5
  38. package/src/NitroInstances.ts +6 -2
  39. package/src/fetch.ts +151 -89
  40. package/LICENSE +0 -20
  41. package/README.md +0 -144
  42. package/src/NitroFetch.nitro.js +0 -2
  43. package/src/NitroInstances.js +0 -3
  44. package/src/fetch.js +0 -377
  45. package/src/index.js +0 -8
package/src/fetch.ts CHANGED
@@ -5,6 +5,7 @@ import type {
5
5
  NitroResponse,
6
6
  } from './NitroFetch.nitro';
7
7
  import { NitroFetch as NitroFetchSingleton } from './NitroInstances';
8
+ import { NativeStorage as NativeStorageSingleton } from './NitroInstances';
8
9
 
9
10
  // No base64: pass strings/ArrayBuffers directly
10
11
 
@@ -21,7 +22,12 @@ function headersToPairs(headers?: HeadersInit): NitroHeader[] | undefined {
21
22
  for (const entry of headers as any[]) {
22
23
  if (Array.isArray(entry) && entry.length >= 2) {
23
24
  pairs.push({ key: String(entry[0]), value: String(entry[1]) });
24
- } else if (entry && typeof entry === 'object' && 'key' in entry && 'value' in entry) {
25
+ } else if (
26
+ entry &&
27
+ typeof entry === 'object' &&
28
+ 'key' in entry &&
29
+ 'value' in entry
30
+ ) {
25
31
  pairs.push(entry as NitroHeader);
26
32
  }
27
33
  }
@@ -34,30 +40,30 @@ function headersToPairs(headers?: HeadersInit): NitroHeader[] | undefined {
34
40
  return pairs;
35
41
  }
36
42
 
37
- function normalizeBody(body: BodyInit | null | undefined): { bodyString?: string; bodyBytes?: ArrayBuffer } | undefined {
43
+ function normalizeBody(
44
+ body: BodyInit | null | undefined
45
+ ): { bodyString?: string; bodyBytes?: ArrayBuffer } | undefined {
38
46
  'worklet';
39
47
  if (body == null) return undefined;
40
48
  if (typeof body === 'string') return { bodyString: body };
41
49
  if (body instanceof URLSearchParams) return { bodyString: body.toString() };
42
- if (typeof ArrayBuffer !== 'undefined' && body instanceof ArrayBuffer) return { bodyBytes: body };
50
+ if (typeof ArrayBuffer !== 'undefined' && body instanceof ArrayBuffer)
51
+ return { bodyBytes: body };
43
52
  if (ArrayBuffer.isView(body)) {
44
53
  const view = body as ArrayBufferView;
45
54
  // Pass a copy/slice of the underlying bytes without base64
46
- //@ts-ignore
47
- return { bodyBytes: view.buffer.slice(view.byteOffset, view.byteOffset + view.byteLength) };
55
+ return {
56
+ //@ts-ignore
57
+ bodyBytes: view.buffer.slice(
58
+ view.byteOffset,
59
+ view.byteOffset + view.byteLength
60
+ ),
61
+ };
48
62
  }
49
63
  // TODO: Blob/FormData support can be added later
50
64
  throw new Error('Unsupported body type for nitro fetch');
51
65
  }
52
66
 
53
- // @ts-ignore
54
- function pairsToHeaders(pairs: NitroHeader[]): Headers {
55
- 'worklet';
56
- const h = new Headers();
57
- for (const { key, value } of pairs) h.append(key, value);
58
- return h;
59
- }
60
-
61
67
  const NitroFetchHybrid: NitroFetchModule = NitroFetchSingleton;
62
68
 
63
69
  let client: ReturnType<NitroFetchModule['createClient']> | undefined;
@@ -73,7 +79,10 @@ function ensureClient() {
73
79
  return client;
74
80
  }
75
81
 
76
- function buildNitroRequest(input: RequestInfo | URL, init?: RequestInit): NitroRequest {
82
+ function buildNitroRequest(
83
+ input: RequestInfo | URL,
84
+ init?: RequestInit
85
+ ): NitroRequest {
77
86
  'worklet';
78
87
  let url: string;
79
88
  let method: string | undefined;
@@ -102,14 +111,19 @@ function buildNitroRequest(input: RequestInfo | URL, init?: RequestInit): NitroR
102
111
  method: (method?.toUpperCase() as any) ?? 'GET',
103
112
  headers,
104
113
  bodyString: normalized?.bodyString,
105
- bodyBytes: "",//normalized?.bodyBytes,
114
+ // Only include bodyBytes when provided to avoid signaling upload data unintentionally
115
+ bodyBytes: undefined as any,
106
116
  followRedirects: true,
107
117
  };
108
118
  }
109
119
 
110
- async function nitroFetchRaw(input: RequestInfo | URL, init?: RequestInit): Promise<NitroResponse> {
120
+ async function nitroFetchRaw(
121
+ input: RequestInfo | URL,
122
+ init?: RequestInit
123
+ ): Promise<NitroResponse> {
111
124
  'worklet';
112
- const hasNative = typeof (NitroFetchHybrid as any)?.createClient === 'function';
125
+ const hasNative =
126
+ typeof (NitroFetchHybrid as any)?.createClient === 'function';
113
127
  if (!hasNative) {
114
128
  // Fallback path not supported for raw; use global fetch and synthesize minimal shape
115
129
  // @ts-ignore: global fetch exists in RN
@@ -132,59 +146,105 @@ async function nitroFetchRaw(input: RequestInfo | URL, init?: RequestInit): Prom
132
146
 
133
147
  const req = buildNitroRequest(input, init);
134
148
  ensureClient();
135
- if (!client || typeof (client as any).request !== 'function') throw new Error('NitroFetch client not available');
149
+ if (!client || typeof (client as any).request !== 'function')
150
+ throw new Error('NitroFetch client not available');
136
151
  const res: NitroResponse = await client.request(req);
137
152
  return res;
138
153
  }
139
154
 
140
- export async function nitroFetch(input: RequestInfo | URL, init?: RequestInit): Promise<Response> {
141
- 'worklet';
142
- // If native implementation is not present yet, fallback to global fetch
143
- const hasNative = typeof (NitroFetchHybrid as any)?.createClient === 'function';
144
- if (!hasNative) {
145
- // @ts-ignore: global fetch exists in RN
146
- return fetch(input as any, init);
155
+ // Simple Headers-like class that supports get() method
156
+ class NitroHeaders {
157
+ private _headers: Map<string, string>;
158
+
159
+ constructor(headers: NitroHeader[]) {
160
+ this._headers = new Map();
161
+ for (const { key, value } of headers) {
162
+ // Headers are case-insensitive, normalize to lowercase
163
+ this._headers.set(key.toLowerCase(), value);
164
+ }
165
+ }
166
+
167
+ get(name: string): string | null {
168
+ return this._headers.get(name.toLowerCase()) ?? null;
169
+ }
170
+
171
+ has(name: string): boolean {
172
+ return this._headers.has(name.toLowerCase());
173
+ }
174
+
175
+ forEach(callback: (value: string, key: string) => void): void {
176
+ this._headers.forEach(callback);
177
+ }
178
+
179
+ entries(): IterableIterator<[string, string]> {
180
+ return this._headers.entries();
181
+ }
182
+
183
+ keys(): IterableIterator<string> {
184
+ return this._headers.keys();
147
185
  }
148
186
 
187
+ values(): IterableIterator<string> {
188
+ return this._headers.values();
189
+ }
190
+ }
191
+
192
+ export async function nitroFetch(
193
+ input: RequestInfo | URL,
194
+ init?: RequestInit
195
+ ): Promise<Response> {
196
+ 'worklet';
197
+
149
198
  const res = await nitroFetchRaw(input, init);
150
199
 
151
- // Fallback lightweight Response-like object (minimal methods)
152
- const headersObj = res.headers.reduce((acc, { key, value }) => {
153
- acc[key] = value;
154
- return acc;
155
- }, {} as Record<string, string>);
200
+ const headersObj = new NitroHeaders(res.headers);
156
201
 
157
- const light: any = {
202
+ const bodyBytes = res.bodyBytes;
203
+ const bodyString = res.bodyString;
204
+
205
+ const makeLight = (): any => ({
158
206
  url: res.url,
159
207
  ok: res.ok,
160
208
  status: res.status,
161
209
  statusText: res.statusText,
162
210
  redirected: res.redirected,
163
211
  headers: headersObj,
164
- arrayBuffer: async () => res.bodyBytes,
165
- text: async () => res.bodyString,
166
- json: async () => JSON.parse(res.bodyString ?? '{}'),
167
- };
212
+ arrayBuffer: async () => bodyBytes,
213
+ text: async () => bodyString,
214
+ json: async () => JSON.parse(bodyString ?? '{}'),
215
+ clone: () => makeLight(),
216
+ });
217
+
218
+ const light: any = makeLight();
168
219
  return light as Response;
169
220
  }
170
221
 
171
222
  // Start a native prefetch. Requires a `prefetchKey` header on the request.
172
- export async function prefetch(input: RequestInfo | URL, init?: RequestInit): Promise<void> {
223
+ export async function prefetch(
224
+ input: RequestInfo | URL,
225
+ init?: RequestInit
226
+ ): Promise<void> {
173
227
  // If native implementation is not present yet, do nothing
174
- const hasNative = typeof (NitroFetchHybrid as any)?.createClient === 'function';
228
+ const hasNative =
229
+ typeof (NitroFetchHybrid as any)?.createClient === 'function';
175
230
  if (!hasNative) return;
176
231
 
177
232
  // Build NitroRequest and ensure prefetchKey header exists
178
233
  const req = buildNitroRequest(input, init);
179
- const hasKey = req.headers?.some(h => h.key.toLowerCase() === 'prefetchkey') ?? false;
234
+ const hasKey =
235
+ req.headers?.some((h) => h.key.toLowerCase() === 'prefetchkey') ?? false;
180
236
  // Also support passing prefetchKey via non-standard field on init
181
237
  const fromInit = (init as any)?.prefetchKey as string | undefined;
182
238
  if (!hasKey && fromInit) {
183
- req.headers = (req.headers ?? []).concat([{ key: 'prefetchKey', value: fromInit }]);
239
+ req.headers = (req.headers ?? []).concat([
240
+ { key: 'prefetchKey', value: fromInit },
241
+ ]);
184
242
  }
185
- const finalHasKey = req.headers?.some(h => h.key.toLowerCase() === 'prefetchkey');
243
+ const finalHasKey = req.headers?.some(
244
+ (h) => h.key.toLowerCase() === 'prefetchkey'
245
+ );
186
246
  if (!finalHasKey) {
187
- throw new Error('prefetch requires a \"prefetchKey\" header');
247
+ throw new Error('prefetch requires a "prefetchKey" header');
188
248
  }
189
249
 
190
250
  // Ensure client and call native prefetch
@@ -193,26 +253,32 @@ export async function prefetch(input: RequestInfo | URL, init?: RequestInit): Pr
193
253
  await client.prefetch(req);
194
254
  }
195
255
 
196
- // Persist a request to MMKV so native can prefetch it on app start.
197
- // Stores an array of entries under the same key Android reads: "nitrofetch_autoprefetch_queue".
256
+ // Persist a request to storage so native can prefetch it on app start.
198
257
  export async function prefetchOnAppStart(
199
258
  input: RequestInfo | URL,
200
259
  init?: RequestInit & { prefetchKey?: string }
201
260
  ): Promise<void> {
202
261
  // Resolve request and prefetchKey
203
262
  const req = buildNitroRequest(input, init);
204
- const fromHeader = req.headers?.find(h => h.key.toLowerCase() === 'prefetchkey')?.value;
263
+ const fromHeader = req.headers?.find(
264
+ (h) => h.key.toLowerCase() === 'prefetchkey'
265
+ )?.value;
205
266
  const fromInit = (init as any)?.prefetchKey as string | undefined;
206
267
  const prefetchKey = fromHeader ?? fromInit;
207
268
  if (!prefetchKey) {
208
- throw new Error('prefetchOnAppStart requires a "prefetchKey" (header or init.prefetchKey)');
269
+ throw new Error(
270
+ 'prefetchOnAppStart requires a "prefetchKey" (header or init.prefetchKey)'
271
+ );
209
272
  }
210
273
 
211
274
  // Convert headers to a plain object for storage
212
- const headersObj = (req.headers ?? []).reduce((acc, { key, value }) => {
213
- acc[String(key)] = String(value);
214
- return acc;
215
- }, {} as Record<string, string>);
275
+ const headersObj = (req.headers ?? []).reduce(
276
+ (acc, { key, value }) => {
277
+ acc[String(key)] = String(value);
278
+ return acc;
279
+ },
280
+ {} as Record<string, string>
281
+ );
216
282
 
217
283
  const entry = {
218
284
  url: req.url,
@@ -220,39 +286,40 @@ export async function prefetchOnAppStart(
220
286
  headers: headersObj,
221
287
  } as const;
222
288
 
223
- // Write or append to MMKV queue
289
+ // Write or append to storage queue
224
290
  try {
225
- // Dynamically require to keep it optional for consumers
226
- // eslint-disable-next-line @typescript-eslint/no-var-requires
227
- const { MMKV } = require('react-native-mmkv');
228
- const storage = new MMKV(); // default instance matches Android's defaultMMKV
229
291
  const KEY = 'nitrofetch_autoprefetch_queue';
230
292
  let arr: any[] = [];
231
293
  try {
232
- const raw = storage.getString(KEY);
294
+ const raw = NativeStorageSingleton.getString(
295
+ 'nitrofetch_autoprefetch_queue'
296
+ );
233
297
  if (raw) arr = JSON.parse(raw);
234
298
  if (!Array.isArray(arr)) arr = [];
235
299
  } catch {
236
300
  arr = [];
237
301
  }
302
+ if (arr.some((e) => e && e.prefetchKey === prefetchKey)) {
303
+ arr = arr.filter((e) => e && e.prefetchKey !== prefetchKey);
304
+ }
238
305
  arr.push(entry);
239
- storage.set(KEY, JSON.stringify(arr));
306
+ NativeStorageSingleton.setString(KEY, JSON.stringify(arr));
240
307
  } catch (e) {
241
- console.warn('react-native-mmkv not available; prefetchOnAppStart is a no-op', e);
308
+ console.warn('Failed to persist prefetch queue', e);
242
309
  }
243
310
  }
244
311
 
245
- // Remove one entry (by prefetchKey) from the auto-prefetch queue in MMKV.
246
- export async function removeFromAutoPrefetch(prefetchKey: string): Promise<void> {
247
- // No-op on iOS
312
+ // Remove one entry (by prefetchKey) from the auto-prefetch queue.
313
+ export async function removeFromAutoPrefetch(
314
+ prefetchKey: string
315
+ ): Promise<void> {
248
316
  try {
249
- // eslint-disable-next-line @typescript-eslint/no-var-requires
250
- const { MMKV } = require('react-native-mmkv');
251
- const storage = new MMKV();
252
317
  const KEY = 'nitrofetch_autoprefetch_queue';
253
318
  let arr: any[] = [];
254
319
  try {
255
- const raw = storage.getString(KEY);
320
+ const raw = NativeStorageSingleton.getString(
321
+ 'nitrofetch_autoprefetch_queue'
322
+ );
256
323
  if (raw) arr = JSON.parse(raw);
257
324
  if (!Array.isArray(arr)) arr = [];
258
325
  } catch {
@@ -260,34 +327,19 @@ export async function removeFromAutoPrefetch(prefetchKey: string): Promise<void>
260
327
  }
261
328
  const next = arr.filter((e) => e && e.prefetchKey !== prefetchKey);
262
329
  if (next.length === 0) {
263
- if (typeof (storage as any).delete === 'function') {
264
- (storage as any).delete(KEY);
265
- } else {
266
- storage.set(KEY, JSON.stringify([]));
267
- }
330
+ NativeStorageSingleton.removeString(KEY);
268
331
  } else if (next.length !== arr.length) {
269
- storage.set(KEY, JSON.stringify(next));
332
+ NativeStorageSingleton.setString(KEY, JSON.stringify(next));
270
333
  }
271
334
  } catch (e) {
272
- console.warn('react-native-mmkv not available; removeFromAutoPrefetch is a no-op', e);
335
+ console.warn('Failed to remove from prefetch queue', e);
273
336
  }
274
337
  }
275
338
 
276
- // Remove all entries from the auto-prefetch queue in MMKV.
339
+ // Remove all entries from the auto-prefetch queue.
277
340
  export async function removeAllFromAutoprefetch(): Promise<void> {
278
- try {
279
- // eslint-disable-next-line @typescript-eslint/no-var-requires
280
- const { MMKV } = require('react-native-mmkv');
281
- const storage = new MMKV();
282
- const KEY = 'nitrofetch_autoprefetch_queue';
283
- if (typeof (storage as any).delete === 'function') {
284
- (storage as any).delete(KEY);
285
- } else {
286
- storage.set(KEY, JSON.stringify([]));
287
- }
288
- } catch (e) {
289
- console.warn('react-native-mmkv not available; removeAllFromAutoprefetch is a no-op', e);
290
- }
341
+ const KEY = 'nitrofetch_autoprefetch_queue';
342
+ NativeStorageSingleton.setString(KEY, JSON.stringify([]));
291
343
  }
292
344
 
293
345
  // Optional off-thread processing using react-native-worklets-core
@@ -307,7 +359,6 @@ let WorkletsRef: any | undefined;
307
359
  function ensureWorkletRuntime(name = 'nitro-fetch'): any | undefined {
308
360
  console.log('ensuring worklet runtime');
309
361
  try {
310
- // eslint-disable-next-line @typescript-eslint/no-var-requires
311
362
  const { Worklets } = require('react-native-worklets-core');
312
363
  nitroRuntime = nitroRuntime ?? Worklets.createRuntime(name);
313
364
  console.log('nitroRuntime:', !!nitroRuntime);
@@ -321,7 +372,7 @@ function ensureWorkletRuntime(name = 'nitro-fetch'): any | undefined {
321
372
  function getWorklets(): any | undefined {
322
373
  try {
323
374
  if (WorkletsRef) return WorkletsRef;
324
- // eslint-disable-next-line @typescript-eslint/no-var-requires
375
+
325
376
  const { Worklets } = require('react-native-worklets-core');
326
377
  WorkletsRef = Worklets;
327
378
  return WorkletsRef;
@@ -384,8 +435,19 @@ export async function nitroFetchOnWorklet<T>(
384
435
  const redirected = res.redirected;
385
436
  const headersPairs: NitroHeader[] = res.headers;
386
437
  const bodyBytes: ArrayBuffer | undefined = undefined; // preferBytes ? res.bodyBytes : undefined;
387
- const bodyString: string | undefined = preferBytes ? undefined : res.bodyString;
388
- const payload = { url, status, statusText, ok, redirected, headers: headersPairs, bodyBytes, bodyString };
438
+ const bodyString: string | undefined = preferBytes
439
+ ? undefined
440
+ : res.bodyString;
441
+ const payload = {
442
+ url,
443
+ status,
444
+ statusText,
445
+ ok,
446
+ redirected,
447
+ headers: headersPairs,
448
+ bodyBytes,
449
+ bodyString,
450
+ };
389
451
  const out = map(payload);
390
452
  // Resolve back on JS thread
391
453
  Worklets.runOnJS(resolve)(out as any);
package/LICENSE DELETED
@@ -1,20 +0,0 @@
1
- MIT License
2
-
3
- Copyright (c) 2025 Szymon Kapala
4
- Permission is hereby granted, free of charge, to any person obtaining a copy
5
- of this software and associated documentation files (the "Software"), to deal
6
- in the Software without restriction, including without limitation the rights
7
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
- copies of the Software, and to permit persons to whom the Software is
9
- furnished to do so, subject to the following conditions:
10
-
11
- The above copyright notice and this permission notice shall be included in all
12
- copies or substantial portions of the Software.
13
-
14
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20
- SOFTWARE.
package/README.md DELETED
@@ -1,144 +0,0 @@
1
- <p align="center">
2
- <img src="./assets/logo.png" alt="Nitro Fetch Logo" width="200" />
3
- </p>
4
-
5
- # react-native-nitro-fetch
6
-
7
- Nitro-powered fetch for React Native. Android uses Chromium Cronet (via `org.chromium.net:cronet-embedded`); iOS currently falls back to the built-in fetch. Includes helpers for background prefetching and off-thread parsing with worklets.
8
-
9
- ## Project Status
10
-
11
- This library is currently in alpha. You can adopt it in production, but you may face counter-intuitive api, poor docs or bugs.
12
-
13
- ## Installation
14
-
15
- ```sh
16
- npm install react-native-nitro-fetch react-native-nitro-modules
17
- ```
18
-
19
- - `react-native-nitro-modules` is required as this library relies on Nitro Modules. Rebuild your app after installing.
20
-
21
- ## Quick Start
22
-
23
- ```ts
24
- import { fetch } from 'react-native-nitro-fetch';
25
-
26
- const res = await fetch('https://httpbin.org/get');
27
- const json = await res.json();
28
- ```
29
-
30
- ## Features
31
-
32
- - Nitro-backed `fetch`: drop-in replacement for global fetch.
33
- - Android Cronet: fast HTTP stack via `org.chromium.net:cronet-embedded` (already wired in `android/build.gradle`).
34
- - Prefetch: start a background request tied to a `prefetchKey` and serve it later.
35
- - Android auto-prefetch: enqueue requests to MMKV so they warm up on next app start.
36
- - Worklets helper: run mapping/parsing off the JS thread with `react-native-worklets-core`.
37
-
38
- ## Why Prefetch
39
-
40
- - Faster first paint of data: Prefetching lets you move network I/O earlier on the critical path so the screen can render with data sooner.
41
- - App start wins: With auto‑prefetch + MMKV, we can begin fetching immediately at process start. In our measurements on mid‑range Android devices (e.g., Samsung A16), this starts at least ~220 ms earlier than initiating the same request from JS after React is up.
42
- - UX hooks: Kick off prefetch on navigation intent (button press) and serve the result when the destination screen mounts.
43
-
44
- See `docs/prefetch.md` for patterns and examples.
45
-
46
- ## Why Cronet
47
-
48
- - Performance: Enables HTTP/2 multiplexing and QUIC/HTTP/3, reducing latency and avoiding head‑of‑line blocking.
49
- - Efficiency: Advanced connection management, TLS/ALPN, Brotli, and robust on‑disk caching.
50
- - Battle‑tested: Built on Chromium’s networking stack (the same tech behind Chrome) and widely adopted across the ecosystem, including the Flutter community.
51
-
52
- ## Philosophy
53
-
54
- - Nitro Fetch often outperforms built‑in fetch thanks to Cronet’s optimizations, but raw speed is not the primary goal.
55
- - The main goals are:
56
- - High‑quality prefetching (including auto‑prefetch on app start)
57
- - Enabling a multi‑threaded React Native architecture (e.g., off‑thread mapping with worklets)
58
- - Performance is a nice side‑effect.
59
-
60
- ## Usage Examples
61
-
62
- - Basic fetch (drop-in replacement):
63
-
64
- ```ts
65
- import { fetch } from 'react-native-nitro-fetch';
66
- const res = await fetch('https://jsonplaceholder.typicode.com/todos/1');
67
- console.log(await res.json());
68
- ```
69
-
70
- - Prefetch and consume (Android or JS fallback):
71
-
72
- ```ts
73
- import { fetch, prefetch } from 'react-native-nitro-fetch';
74
-
75
- await prefetch('https://httpbin.org/uuid', { headers: { prefetchKey: 'uuid' } });
76
- const res = await fetch('https://httpbin.org/uuid', { headers: { prefetchKey: 'uuid' } });
77
- console.log('prefetched header:', res.headers.get('nitroPrefetched'));
78
- ```
79
-
80
- - Schedule auto-prefetch on Android (requires `react-native-mmkv` in your app):
81
-
82
- ```ts
83
- import { prefetchOnAppStart } from 'react-native-nitro-fetch';
84
- await prefetchOnAppStart('https://httpbin.org/uuid', { prefetchKey: 'uuid' });
85
- ```
86
-
87
- - Off-thread parsing with worklets:
88
-
89
- ```ts
90
- import { nitroFetchOnWorklet } from 'react-native-nitro-fetch';
91
-
92
- const map = (payload: { bodyString?: string }) => {
93
- 'worklet';
94
- return JSON.parse(payload.bodyString ?? '{}');
95
- };
96
-
97
- const data = await nitroFetchOnWorklet('https://httpbin.org/get', undefined, map);
98
- ```
99
-
100
- ## Platform Notes
101
-
102
- - Android: Uses Cronet Java API; no extra setup needed beyond install and rebuild. Cronet engine is initialized once and enables HTTP/2, QUIC, Brotli, and disk cache.
103
- - iOS: Uses a native `URLSession` client for requests and prefetch (in‑memory cache). Cronet integration is still planned for future releases.
104
-
105
- ## Limitations & Alternatives
106
-
107
- - HTTP streaming: Not supported yet. For streaming responses today, use Expo’s `expo-fetch`. Streaming is on the roadmap.
108
- - WebSockets: Not supported. For high‑performance sockets and binary streams, consider `react-native-fast-io`.
109
-
110
- ## Documentation
111
-
112
- - Getting Started: `docs/getting-started.md`
113
- - API Reference: `docs/api.md`
114
- - Android Details: `docs/android.md`
115
- - iOS Details: `docs/ios.md`
116
- - Prefetch & Auto-Prefetch: `docs/prefetch.md`
117
- - Worklets: `docs/worklets.md`
118
- - Troubleshooting: `docs/troubleshooting.md`
119
- - Cronet (Android) notes: `docs/cronet-android.md`
120
- - Cronet (iOS) notes: `docs/cronet-ios.md`
121
-
122
- ## Work With Margelo
123
-
124
- Need top‑notch React Native help or custom networking solutions? Reach out to Margelo: hello@margelo.com
125
-
126
- ## Contributing
127
-
128
- - Development workflow: `CONTRIBUTING.md#development-workflow`
129
- - Sending a pull request: `CONTRIBUTING.md#sending-a-pull-request`
130
- - Code of conduct: `CODE_OF_CONDUCT.md`
131
-
132
- ## Authors
133
-
134
- - [Szymon Kapala](https://x.com/Turbo_Szymon)
135
- - [Alex Shumihin](https://x.com/pioner_dev)
136
- - [Ronald Goedeke](https://x.com/BubbleTroubl_rg)
137
-
138
- ## License
139
-
140
- MIT
141
-
142
- ---
143
-
144
- Made with [create-react-native-library](https://github.com/callstack/react-native-builder-bob)
@@ -1,2 +0,0 @@
1
- ;
2
- export {};
@@ -1,3 +0,0 @@
1
- import { NitroModules } from 'react-native-nitro-modules';
2
- // Create singletons once per JS runtime
3
- export const NitroFetch = NitroModules.createHybridObject('NitroFetch');