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.
- package/android/build.gradle +1 -0
- package/android/src/main/java/com/margelo/nitro/nitrofetch/AutoPrefetcher.kt +8 -27
- package/android/src/main/java/com/margelo/nitro/nitrofetch/FetchCache.kt +11 -1
- package/android/src/main/java/com/margelo/nitro/nitrofetch/NativeStorage.kt +102 -0
- package/android/src/main/java/com/margelo/nitro/nitrofetch/NitroFetchClient.kt +3 -3
- package/ios/NativeStorage.swift +61 -0
- package/ios/NitroAutoPrefetcher.swift +5 -41
- package/lib/module/NitroFetch.nitro.js +0 -3
- package/lib/module/NitroFetch.nitro.js.map +1 -1
- package/lib/module/NitroInstances.js +1 -0
- package/lib/module/NitroInstances.js.map +1 -1
- package/lib/module/fetch.js +63 -81
- package/lib/module/fetch.js.map +1 -1
- package/lib/typescript/src/NitroFetch.nitro.d.ts +8 -0
- package/lib/typescript/src/NitroFetch.nitro.d.ts.map +1 -1
- package/lib/typescript/src/NitroInstances.d.ts +2 -1
- package/lib/typescript/src/NitroInstances.d.ts.map +1 -1
- package/lib/typescript/src/fetch.d.ts.map +1 -1
- package/nitro.json +4 -0
- package/nitrogen/generated/android/c++/JHybridNativeStorageSpec.cpp +54 -0
- package/nitrogen/generated/android/c++/JHybridNativeStorageSpec.hpp +66 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrofetch/HybridNativeStorageSpec.kt +60 -0
- package/nitrogen/generated/android/nitrofetch+autolinking.cmake +9 -4
- package/nitrogen/generated/android/nitrofetchOnLoad.cpp +10 -0
- package/nitrogen/generated/ios/NitroFetch-Swift-Cxx-Bridge.cpp +17 -0
- package/nitrogen/generated/ios/NitroFetch-Swift-Cxx-Bridge.hpp +35 -0
- package/nitrogen/generated/ios/NitroFetch-Swift-Cxx-Umbrella.hpp +5 -0
- package/nitrogen/generated/ios/NitroFetchAutolinking.mm +8 -0
- package/nitrogen/generated/ios/NitroFetchAutolinking.swift +15 -0
- package/nitrogen/generated/ios/c++/HybridNativeStorageSpecSwift.cpp +11 -0
- package/nitrogen/generated/ios/c++/HybridNativeStorageSpecSwift.hpp +85 -0
- package/nitrogen/generated/ios/swift/HybridNativeStorageSpec.swift +51 -0
- package/nitrogen/generated/ios/swift/HybridNativeStorageSpec_cxx.swift +145 -0
- package/nitrogen/generated/shared/c++/HybridNativeStorageSpec.cpp +23 -0
- package/nitrogen/generated/shared/c++/HybridNativeStorageSpec.hpp +64 -0
- package/package.json +8 -24
- package/src/NitroFetch.nitro.ts +12 -5
- package/src/NitroInstances.ts +6 -2
- package/src/fetch.ts +151 -89
- package/LICENSE +0 -20
- package/README.md +0 -144
- package/src/NitroFetch.nitro.js +0 -2
- package/src/NitroInstances.js +0 -3
- package/src/fetch.js +0 -377
- 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 (
|
|
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(
|
|
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)
|
|
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
|
-
|
|
47
|
-
|
|
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(
|
|
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
|
|
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(
|
|
120
|
+
async function nitroFetchRaw(
|
|
121
|
+
input: RequestInfo | URL,
|
|
122
|
+
init?: RequestInit
|
|
123
|
+
): Promise<NitroResponse> {
|
|
111
124
|
'worklet';
|
|
112
|
-
const hasNative =
|
|
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')
|
|
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
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
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
|
-
|
|
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
|
|
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 () =>
|
|
165
|
-
text: async () =>
|
|
166
|
-
json: async () => JSON.parse(
|
|
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(
|
|
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 =
|
|
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 =
|
|
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([
|
|
239
|
+
req.headers = (req.headers ?? []).concat([
|
|
240
|
+
{ key: 'prefetchKey', value: fromInit },
|
|
241
|
+
]);
|
|
184
242
|
}
|
|
185
|
-
const finalHasKey = req.headers?.some(
|
|
243
|
+
const finalHasKey = req.headers?.some(
|
|
244
|
+
(h) => h.key.toLowerCase() === 'prefetchkey'
|
|
245
|
+
);
|
|
186
246
|
if (!finalHasKey) {
|
|
187
|
-
throw new Error('prefetch requires a
|
|
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
|
|
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(
|
|
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(
|
|
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(
|
|
213
|
-
acc
|
|
214
|
-
|
|
215
|
-
|
|
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
|
|
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 =
|
|
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
|
-
|
|
306
|
+
NativeStorageSingleton.setString(KEY, JSON.stringify(arr));
|
|
240
307
|
} catch (e) {
|
|
241
|
-
console.warn('
|
|
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
|
|
246
|
-
export async function removeFromAutoPrefetch(
|
|
247
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
332
|
+
NativeStorageSingleton.setString(KEY, JSON.stringify(next));
|
|
270
333
|
}
|
|
271
334
|
} catch (e) {
|
|
272
|
-
console.warn('
|
|
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
|
|
339
|
+
// Remove all entries from the auto-prefetch queue.
|
|
277
340
|
export async function removeAllFromAutoprefetch(): Promise<void> {
|
|
278
|
-
|
|
279
|
-
|
|
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
|
-
|
|
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
|
|
388
|
-
|
|
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)
|
package/src/NitroFetch.nitro.js
DELETED
package/src/NitroInstances.js
DELETED