react-native-nitro-fetch 0.1.3 → 0.1.5
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 -1
- package/android/src/main/java/com/margelo/nitro/nitrofetch/AutoPrefetcher.kt +7 -28
- 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 +53 -29
- package/ios/NativeStorage.swift +61 -0
- package/ios/NitroAutoPrefetcher.swift +5 -41
- package/ios/NitroFetchClient.swift +25 -0
- package/lib/module/NitroFetch.nitro.js +0 -3
- package/lib/module/NitroFetch.nitro.js.map +1 -1
- package/lib/module/NitroInstances.js +2 -0
- package/lib/module/NitroInstances.js.map +1 -1
- package/lib/module/fetch.js +79 -125
- package/lib/module/fetch.js.map +1 -1
- package/lib/typescript/src/NitroFetch.nitro.d.ts +9 -0
- package/lib/typescript/src/NitroFetch.nitro.d.ts.map +1 -1
- package/lib/typescript/src/NitroInstances.d.ts +3 -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/c++/JHybridNitroFetchClientSpec.cpp +5 -0
- package/nitrogen/generated/android/c++/JHybridNitroFetchClientSpec.hpp +1 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrofetch/HybridNativeStorageSpec.kt +60 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrofetch/HybridNitroFetchClientSpec.kt +4 -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 +44 -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/c++/HybridNitroFetchClientSpecSwift.hpp +8 -0
- package/nitrogen/generated/ios/swift/HybridNativeStorageSpec.swift +51 -0
- package/nitrogen/generated/ios/swift/HybridNativeStorageSpec_cxx.swift +145 -0
- package/nitrogen/generated/ios/swift/HybridNitroFetchClientSpec.swift +1 -0
- package/nitrogen/generated/ios/swift/HybridNitroFetchClientSpec_cxx.swift +12 -0
- package/nitrogen/generated/shared/c++/HybridNativeStorageSpec.cpp +23 -0
- package/nitrogen/generated/shared/c++/HybridNativeStorageSpec.hpp +64 -0
- package/nitrogen/generated/shared/c++/HybridNitroFetchClientSpec.cpp +1 -0
- package/nitrogen/generated/shared/c++/HybridNitroFetchClientSpec.hpp +1 -0
- package/package.json +8 -24
- package/src/NitroFetch.nitro.ts +15 -5
- package/src/NitroInstances.ts +7 -1
- package/src/fetch.ts +158 -122
- package/LICENSE +0 -20
- package/README.md +0 -151
package/src/fetch.ts
CHANGED
|
@@ -4,7 +4,11 @@ import type {
|
|
|
4
4
|
NitroRequest,
|
|
5
5
|
NitroResponse,
|
|
6
6
|
} from './NitroFetch.nitro';
|
|
7
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
boxedNitroFetch,
|
|
9
|
+
NitroFetch as NitroFetchSingleton,
|
|
10
|
+
} from './NitroInstances';
|
|
11
|
+
import { NativeStorage as NativeStorageSingleton } from './NitroInstances';
|
|
8
12
|
|
|
9
13
|
// No base64: pass strings/ArrayBuffers directly
|
|
10
14
|
|
|
@@ -21,7 +25,12 @@ function headersToPairs(headers?: HeadersInit): NitroHeader[] | undefined {
|
|
|
21
25
|
for (const entry of headers as any[]) {
|
|
22
26
|
if (Array.isArray(entry) && entry.length >= 2) {
|
|
23
27
|
pairs.push({ key: String(entry[0]), value: String(entry[1]) });
|
|
24
|
-
} else if (
|
|
28
|
+
} else if (
|
|
29
|
+
entry &&
|
|
30
|
+
typeof entry === 'object' &&
|
|
31
|
+
'key' in entry &&
|
|
32
|
+
'value' in entry
|
|
33
|
+
) {
|
|
25
34
|
pairs.push(entry as NitroHeader);
|
|
26
35
|
}
|
|
27
36
|
}
|
|
@@ -34,30 +43,30 @@ function headersToPairs(headers?: HeadersInit): NitroHeader[] | undefined {
|
|
|
34
43
|
return pairs;
|
|
35
44
|
}
|
|
36
45
|
|
|
37
|
-
function normalizeBody(
|
|
46
|
+
function normalizeBody(
|
|
47
|
+
body: BodyInit | null | undefined
|
|
48
|
+
): { bodyString?: string; bodyBytes?: ArrayBuffer } | undefined {
|
|
38
49
|
'worklet';
|
|
39
50
|
if (body == null) return undefined;
|
|
40
51
|
if (typeof body === 'string') return { bodyString: body };
|
|
41
52
|
if (body instanceof URLSearchParams) return { bodyString: body.toString() };
|
|
42
|
-
if (typeof ArrayBuffer !== 'undefined' && body instanceof ArrayBuffer)
|
|
53
|
+
if (typeof ArrayBuffer !== 'undefined' && body instanceof ArrayBuffer)
|
|
54
|
+
return { bodyBytes: body };
|
|
43
55
|
if (ArrayBuffer.isView(body)) {
|
|
44
56
|
const view = body as ArrayBufferView;
|
|
45
57
|
// Pass a copy/slice of the underlying bytes without base64
|
|
46
|
-
|
|
47
|
-
|
|
58
|
+
return {
|
|
59
|
+
//@ts-ignore
|
|
60
|
+
bodyBytes: view.buffer.slice(
|
|
61
|
+
view.byteOffset,
|
|
62
|
+
view.byteOffset + view.byteLength
|
|
63
|
+
),
|
|
64
|
+
};
|
|
48
65
|
}
|
|
49
66
|
// TODO: Blob/FormData support can be added later
|
|
50
67
|
throw new Error('Unsupported body type for nitro fetch');
|
|
51
68
|
}
|
|
52
69
|
|
|
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
70
|
const NitroFetchHybrid: NitroFetchModule = NitroFetchSingleton;
|
|
62
71
|
|
|
63
72
|
let client: ReturnType<NitroFetchModule['createClient']> | undefined;
|
|
@@ -73,7 +82,10 @@ function ensureClient() {
|
|
|
73
82
|
return client;
|
|
74
83
|
}
|
|
75
84
|
|
|
76
|
-
function buildNitroRequest(
|
|
85
|
+
function buildNitroRequest(
|
|
86
|
+
input: RequestInfo | URL,
|
|
87
|
+
init?: RequestInit
|
|
88
|
+
): NitroRequest {
|
|
77
89
|
'worklet';
|
|
78
90
|
let url: string;
|
|
79
91
|
let method: string | undefined;
|
|
@@ -102,14 +114,18 @@ function buildNitroRequest(input: RequestInfo | URL, init?: RequestInit): NitroR
|
|
|
102
114
|
method: (method?.toUpperCase() as any) ?? 'GET',
|
|
103
115
|
headers,
|
|
104
116
|
bodyString: normalized?.bodyString,
|
|
105
|
-
bodyBytes
|
|
117
|
+
// Only include bodyBytes when provided to avoid signaling upload data unintentionally
|
|
118
|
+
bodyBytes: undefined as any,
|
|
106
119
|
followRedirects: true,
|
|
107
120
|
};
|
|
108
121
|
}
|
|
109
122
|
|
|
110
|
-
async function nitroFetchRaw(
|
|
111
|
-
|
|
112
|
-
|
|
123
|
+
async function nitroFetchRaw(
|
|
124
|
+
input: RequestInfo | URL,
|
|
125
|
+
init?: RequestInit
|
|
126
|
+
): Promise<NitroResponse> {
|
|
127
|
+
const hasNative =
|
|
128
|
+
typeof (NitroFetchHybrid as any)?.createClient === 'function';
|
|
113
129
|
if (!hasNative) {
|
|
114
130
|
// Fallback path not supported for raw; use global fetch and synthesize minimal shape
|
|
115
131
|
// @ts-ignore: global fetch exists in RN
|
|
@@ -132,59 +148,103 @@ async function nitroFetchRaw(input: RequestInfo | URL, init?: RequestInit): Prom
|
|
|
132
148
|
|
|
133
149
|
const req = buildNitroRequest(input, init);
|
|
134
150
|
ensureClient();
|
|
135
|
-
if (!client || typeof (client as any).request !== 'function')
|
|
151
|
+
if (!client || typeof (client as any).request !== 'function')
|
|
152
|
+
throw new Error('NitroFetch client not available');
|
|
136
153
|
const res: NitroResponse = await client.request(req);
|
|
137
154
|
return res;
|
|
138
155
|
}
|
|
139
156
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
157
|
+
// Simple Headers-like class that supports get() method
|
|
158
|
+
class NitroHeaders {
|
|
159
|
+
private _headers: Map<string, string>;
|
|
160
|
+
|
|
161
|
+
constructor(headers: NitroHeader[]) {
|
|
162
|
+
this._headers = new Map();
|
|
163
|
+
for (const { key, value } of headers) {
|
|
164
|
+
// Headers are case-insensitive, normalize to lowercase
|
|
165
|
+
this._headers.set(key.toLowerCase(), value);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
get(name: string): string | null {
|
|
170
|
+
return this._headers.get(name.toLowerCase()) ?? null;
|
|
147
171
|
}
|
|
148
172
|
|
|
173
|
+
has(name: string): boolean {
|
|
174
|
+
return this._headers.has(name.toLowerCase());
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
forEach(callback: (value: string, key: string) => void): void {
|
|
178
|
+
this._headers.forEach(callback);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
entries(): IterableIterator<[string, string]> {
|
|
182
|
+
return this._headers.entries();
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
keys(): IterableIterator<string> {
|
|
186
|
+
return this._headers.keys();
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
values(): IterableIterator<string> {
|
|
190
|
+
return this._headers.values();
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
export async function nitroFetch(
|
|
195
|
+
input: RequestInfo | URL,
|
|
196
|
+
init?: RequestInit
|
|
197
|
+
): Promise<Response> {
|
|
149
198
|
const res = await nitroFetchRaw(input, init);
|
|
150
199
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
}, {} as Record<string, string>);
|
|
200
|
+
const headersObj = new NitroHeaders(res.headers);
|
|
201
|
+
|
|
202
|
+
const bodyBytes = res.bodyBytes;
|
|
203
|
+
const bodyString = res.bodyString;
|
|
156
204
|
|
|
157
|
-
const
|
|
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,42 +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
|
}
|
|
238
|
-
if (arr.some(e => e && e.prefetchKey === prefetchKey)) {
|
|
239
|
-
arr = arr.filter(e => e && e.prefetchKey !== prefetchKey);
|
|
302
|
+
if (arr.some((e) => e && e.prefetchKey === prefetchKey)) {
|
|
303
|
+
arr = arr.filter((e) => e && e.prefetchKey !== prefetchKey);
|
|
240
304
|
}
|
|
241
305
|
arr.push(entry);
|
|
242
|
-
|
|
306
|
+
NativeStorageSingleton.setString(KEY, JSON.stringify(arr));
|
|
243
307
|
} catch (e) {
|
|
244
|
-
console.warn('
|
|
308
|
+
console.warn('Failed to persist prefetch queue', e);
|
|
245
309
|
}
|
|
246
310
|
}
|
|
247
311
|
|
|
248
|
-
// Remove one entry (by prefetchKey) from the auto-prefetch queue
|
|
249
|
-
export async function removeFromAutoPrefetch(
|
|
250
|
-
|
|
312
|
+
// Remove one entry (by prefetchKey) from the auto-prefetch queue.
|
|
313
|
+
export async function removeFromAutoPrefetch(
|
|
314
|
+
prefetchKey: string
|
|
315
|
+
): Promise<void> {
|
|
251
316
|
try {
|
|
252
|
-
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
253
|
-
const { MMKV } = require('react-native-mmkv');
|
|
254
|
-
const storage = new MMKV();
|
|
255
317
|
const KEY = 'nitrofetch_autoprefetch_queue';
|
|
256
318
|
let arr: any[] = [];
|
|
257
319
|
try {
|
|
258
|
-
const raw =
|
|
320
|
+
const raw = NativeStorageSingleton.getString(
|
|
321
|
+
'nitrofetch_autoprefetch_queue'
|
|
322
|
+
);
|
|
259
323
|
if (raw) arr = JSON.parse(raw);
|
|
260
324
|
if (!Array.isArray(arr)) arr = [];
|
|
261
325
|
} catch {
|
|
@@ -263,34 +327,19 @@ export async function removeFromAutoPrefetch(prefetchKey: string): Promise<void>
|
|
|
263
327
|
}
|
|
264
328
|
const next = arr.filter((e) => e && e.prefetchKey !== prefetchKey);
|
|
265
329
|
if (next.length === 0) {
|
|
266
|
-
|
|
267
|
-
(storage as any).delete(KEY);
|
|
268
|
-
} else {
|
|
269
|
-
storage.set(KEY, JSON.stringify([]));
|
|
270
|
-
}
|
|
330
|
+
NativeStorageSingleton.removeString(KEY);
|
|
271
331
|
} else if (next.length !== arr.length) {
|
|
272
|
-
|
|
332
|
+
NativeStorageSingleton.setString(KEY, JSON.stringify(next));
|
|
273
333
|
}
|
|
274
334
|
} catch (e) {
|
|
275
|
-
console.warn('
|
|
335
|
+
console.warn('Failed to remove from prefetch queue', e);
|
|
276
336
|
}
|
|
277
337
|
}
|
|
278
338
|
|
|
279
|
-
// Remove all entries from the auto-prefetch queue
|
|
339
|
+
// Remove all entries from the auto-prefetch queue.
|
|
280
340
|
export async function removeAllFromAutoprefetch(): Promise<void> {
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
const { MMKV } = require('react-native-mmkv');
|
|
284
|
-
const storage = new MMKV();
|
|
285
|
-
const KEY = 'nitrofetch_autoprefetch_queue';
|
|
286
|
-
if (typeof (storage as any).delete === 'function') {
|
|
287
|
-
(storage as any).delete(KEY);
|
|
288
|
-
} else {
|
|
289
|
-
storage.set(KEY, JSON.stringify([]));
|
|
290
|
-
}
|
|
291
|
-
} catch (e) {
|
|
292
|
-
console.warn('react-native-mmkv not available; removeAllFromAutoprefetch is a no-op', e);
|
|
293
|
-
}
|
|
341
|
+
const KEY = 'nitrofetch_autoprefetch_queue';
|
|
342
|
+
NativeStorageSingleton.setString(KEY, JSON.stringify([]));
|
|
294
343
|
}
|
|
295
344
|
|
|
296
345
|
// Optional off-thread processing using react-native-worklets-core
|
|
@@ -310,9 +359,8 @@ let WorkletsRef: any | undefined;
|
|
|
310
359
|
function ensureWorkletRuntime(name = 'nitro-fetch'): any | undefined {
|
|
311
360
|
console.log('ensuring worklet runtime');
|
|
312
361
|
try {
|
|
313
|
-
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
314
362
|
const { Worklets } = require('react-native-worklets-core');
|
|
315
|
-
nitroRuntime = nitroRuntime ?? Worklets.
|
|
363
|
+
nitroRuntime = nitroRuntime ?? Worklets.createContext(name);
|
|
316
364
|
console.log('nitroRuntime:', !!nitroRuntime);
|
|
317
365
|
return nitroRuntime;
|
|
318
366
|
} catch {
|
|
@@ -324,7 +372,7 @@ function ensureWorkletRuntime(name = 'nitro-fetch'): any | undefined {
|
|
|
324
372
|
function getWorklets(): any | undefined {
|
|
325
373
|
try {
|
|
326
374
|
if (WorkletsRef) return WorkletsRef;
|
|
327
|
-
|
|
375
|
+
|
|
328
376
|
const { Worklets } = require('react-native-worklets-core');
|
|
329
377
|
WorkletsRef = Worklets;
|
|
330
378
|
return WorkletsRef;
|
|
@@ -355,7 +403,7 @@ export async function nitroFetchOnWorklet<T>(
|
|
|
355
403
|
}
|
|
356
404
|
|
|
357
405
|
// Fallback: if runtime is not available, do the work on JS
|
|
358
|
-
if (!rt || !Worklets || typeof rt.
|
|
406
|
+
if (!rt || !Worklets || typeof rt.runAsync !== 'function') {
|
|
359
407
|
console.warn('nitroFetchOnWorklet: no runtime, mapping on JS thread');
|
|
360
408
|
const res = await nitroFetchRaw(input, init);
|
|
361
409
|
const payload = {
|
|
@@ -370,36 +418,24 @@ export async function nitroFetchOnWorklet<T>(
|
|
|
370
418
|
} as const;
|
|
371
419
|
return mapWorklet(payload as any);
|
|
372
420
|
}
|
|
421
|
+
console.log('nitroFetchOnWorklet: running on worklet thread');
|
|
422
|
+
return await rt.runAsync(() => {
|
|
423
|
+
'worklet';
|
|
424
|
+
const unboxedNitroFetch = boxedNitroFetch.unbox();
|
|
425
|
+
const unboxedClient = unboxedNitroFetch.createClient();
|
|
426
|
+
const res = unboxedClient.requestSync(buildNitroRequest(input, init));
|
|
427
|
+
const payload = {
|
|
428
|
+
url: res.url,
|
|
429
|
+
status: res.status,
|
|
430
|
+
statusText: res.statusText,
|
|
431
|
+
ok: res.ok,
|
|
432
|
+
redirected: res.redirected,
|
|
433
|
+
headers: res.headers,
|
|
434
|
+
bodyBytes: preferBytes ? res.bodyBytes : undefined,
|
|
435
|
+
bodyString: preferBytes ? undefined : res.bodyString,
|
|
436
|
+
} as const;
|
|
373
437
|
|
|
374
|
-
|
|
375
|
-
try {
|
|
376
|
-
console.log('nitroFetchOnWorklet: about to call rt.run');
|
|
377
|
-
rt.run(async (map: NitroWorkletMapper<T>) => {
|
|
378
|
-
'worklet';
|
|
379
|
-
try {
|
|
380
|
-
console.log('nitroFetchOnWorklet: running fetch on worklet thread');
|
|
381
|
-
const res = await nitroFetchRaw(input, init);
|
|
382
|
-
console.log('nitroFetchOnWorklet: fetch completed');
|
|
383
|
-
const url = res.url;
|
|
384
|
-
const status = res.status;
|
|
385
|
-
const statusText = res.statusText;
|
|
386
|
-
const ok = res.ok;
|
|
387
|
-
const redirected = res.redirected;
|
|
388
|
-
const headersPairs: NitroHeader[] = res.headers;
|
|
389
|
-
const bodyBytes: ArrayBuffer | undefined = undefined; // preferBytes ? res.bodyBytes : undefined;
|
|
390
|
-
const bodyString: string | undefined = preferBytes ? undefined : res.bodyString;
|
|
391
|
-
const payload = { url, status, statusText, ok, redirected, headers: headersPairs, bodyBytes, bodyString };
|
|
392
|
-
const out = map(payload);
|
|
393
|
-
// Resolve back on JS thread
|
|
394
|
-
Worklets.runOnJS(resolve)(out as any);
|
|
395
|
-
} catch (e) {
|
|
396
|
-
Worklets.runOnJS(reject)(e as any);
|
|
397
|
-
}
|
|
398
|
-
}, mapWorklet as any);
|
|
399
|
-
} catch (e) {
|
|
400
|
-
console.error('nitroFetchOnWorklet: rt.run failed', e);
|
|
401
|
-
reject(e);
|
|
402
|
-
}
|
|
438
|
+
return mapWorklet(payload as any);
|
|
403
439
|
});
|
|
404
440
|
}
|
|
405
441
|
|
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,151 +0,0 @@
|
|
|
1
|
-
<a href="https://margelo.com">
|
|
2
|
-
<picture>
|
|
3
|
-
<source media="(prefers-color-scheme: dark)" srcset="./docs/static/img/banner-nitro-modules-dark.png" />
|
|
4
|
-
<source media="(prefers-color-scheme: light)" srcset="./docs/static/img/banner-nitro-modules-light.png" />
|
|
5
|
-
<img alt="Nitro Modules" src="./docs/static/img/banner-nitro-modules-light.png" />
|
|
6
|
-
</picture>
|
|
7
|
-
</a>
|
|
8
|
-
|
|
9
|
-
<br />
|
|
10
|
-
|
|
11
|
-
**react-native-nitro-fetch** is a general purpose network fetching library for React Native. It can be used as a drop-in replacement for the built-in `fetch(...)` method, as well as provide additional features like prefetching and workletized mappers.
|
|
12
|
-
|
|
13
|
-
## Features
|
|
14
|
-
|
|
15
|
-
- 🔧 Drop-in replacement for the built-in `fetch(...)` method
|
|
16
|
-
- ⚡️ Fast HTTP stack using [Cronet](https://chromium.googlesource.com/chromium/src/+/lkgr/components/cronet/README.md) on Android, and [URLSession](https://developer.apple.com/documentation/Foundation/URLSession) on iOS
|
|
17
|
-
- 💪 Supports [HTTP/2](https://en.wikipedia.org/wiki/HTTP/2), [QUIC](https://www.chromium.org/quic/), [Brotli](https://github.com/google/brotli), and disk cache
|
|
18
|
-
- ⏰ Prefetching on app-startup for even faster initialization
|
|
19
|
-
- 🧵 Worklet support for parallel data mapping without blocking the JS Thread
|
|
20
|
-
- 🔥 Powered by [Nitro Modules](https://github.com/mrousavy/nitro)
|
|
21
|
-
|
|
22
|
-
## Installation
|
|
23
|
-
|
|
24
|
-
```sh
|
|
25
|
-
npm i react-native-nitro-fetch react-native-nitro-modules
|
|
26
|
-
```
|
|
27
|
-
|
|
28
|
-
> [Nitro Modules](https://github.com/mrousavy/nitro) requires react-native 0.75+ or higher
|
|
29
|
-
|
|
30
|
-
## Usage
|
|
31
|
-
|
|
32
|
-
To simply fetch data, import the `fetch(...)` method from `react-native-nitro-fetch`:
|
|
33
|
-
|
|
34
|
-
```ts
|
|
35
|
-
import { fetch } from 'react-native-nitro-fetch'
|
|
36
|
-
|
|
37
|
-
const res = await fetch('https://httpbin.org/get')
|
|
38
|
-
const json = await res.json()
|
|
39
|
-
```
|
|
40
|
-
|
|
41
|
-
This can be used as a drop-in-replacement for the built-in `fetch(...)` method.
|
|
42
|
-
|
|
43
|
-
### Prefetching in JS
|
|
44
|
-
|
|
45
|
-
You can prefetch a URL in JS, which keeps the result cached for the next actual `fetch(...)` call - this can be used shortly before navigating to a new screen to have results hot & ready:
|
|
46
|
-
|
|
47
|
-
```ts
|
|
48
|
-
import { prefetch } from 'react-native-nitro-fetch'
|
|
49
|
-
|
|
50
|
-
await prefetch('https://httpbin.org/uuid', {
|
|
51
|
-
headers: { prefetchKey: 'uuid' }
|
|
52
|
-
})
|
|
53
|
-
```
|
|
54
|
-
|
|
55
|
-
Then, on the new screen that was navigated to:
|
|
56
|
-
|
|
57
|
-
```ts
|
|
58
|
-
import { fetch } from 'react-native-nitro-fetch'
|
|
59
|
-
|
|
60
|
-
const res = await fetch('https://httpbin.org/uuid', {
|
|
61
|
-
headers: { prefetchKey: 'uuid' }
|
|
62
|
-
})
|
|
63
|
-
console.log('prefetched header:', res.headers.get('nitroPrefetched'))
|
|
64
|
-
```
|
|
65
|
-
|
|
66
|
-
### Prefetching for the next app launch
|
|
67
|
-
|
|
68
|
-
Prefetching data on app launch (or _process start_) will make it hot & ready once your JS code actually runs. Call `prefetchOnAppStart(...)` to enqueue a prefetch for the **next** app start:
|
|
69
|
-
|
|
70
|
-
```ts
|
|
71
|
-
import { prefetchOnAppStart } from 'react-native-nitro-fetch'
|
|
72
|
-
|
|
73
|
-
await prefetchOnAppStart('https://httpbin.org/uuid', {
|
|
74
|
-
prefetchKey: 'uuid'
|
|
75
|
-
})
|
|
76
|
-
```
|
|
77
|
-
|
|
78
|
-
Then, once the app opens the next time, a call to `fetch(...)` might resolve faster since it will contain already cached results:
|
|
79
|
-
|
|
80
|
-
```ts
|
|
81
|
-
import { fetch } from 'react-native-nitro-fetch'
|
|
82
|
-
|
|
83
|
-
const res = await fetch('https://httpbin.org/uuid', {
|
|
84
|
-
headers: { prefetchKey: 'uuid' }
|
|
85
|
-
})
|
|
86
|
-
console.log('prefetched header:', res.headers.get('nitroPrefetched'))
|
|
87
|
-
```
|
|
88
|
-
|
|
89
|
-
In our tests, prefetching alone yielded a **~220 ms** faster TTI (time-to-interactive) time! 🤯
|
|
90
|
-
|
|
91
|
-
### Worklet Mapping
|
|
92
|
-
|
|
93
|
-
Since Nitro Fetch is a [Nitro Module](https://nitro.margelo.com), it can be used from Worklets.
|
|
94
|
-
This can be useful to parse data without blocking the main JS-Thread:
|
|
95
|
-
|
|
96
|
-
```ts
|
|
97
|
-
import { nitroFetchOnWorklet } from 'react-native-nitro-fetch'
|
|
98
|
-
|
|
99
|
-
const data = await nitroFetchOnWorklet(
|
|
100
|
-
'https://httpbin.org/get',
|
|
101
|
-
undefined,
|
|
102
|
-
(payload) => {
|
|
103
|
-
'worklet'
|
|
104
|
-
return JSON.parse(payload.bodyString ?? '{}')
|
|
105
|
-
}
|
|
106
|
-
)
|
|
107
|
-
```
|
|
108
|
-
|
|
109
|
-
## Project Status
|
|
110
|
-
|
|
111
|
-
Nitro Fetch is currently in an alpha stage. You can adopt it in production, but keep in mind that the library and it's API is subject to change.
|
|
112
|
-
|
|
113
|
-
## Limitations & Alternatives
|
|
114
|
-
|
|
115
|
-
- [HTTP streaming](https://developer.mozilla.org/en-US/docs/Web/API/Streams_API) is not yet supported. As an alternative, use Expo's [expo-fetch](https://docs.expo.dev/versions/latest/sdk/expo/). Streaming is on the roadmap.
|
|
116
|
-
- [WebSockets](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket) are not supported. For high‑performance sockets and binary streams, consider using [react-native-fast-io](https://github.com/callstackincubator/react-native-fast-io) by our friends at Callstack.
|
|
117
|
-
|
|
118
|
-
## Documentation
|
|
119
|
-
|
|
120
|
-
- [Getting Started](docs/getting-started.md)
|
|
121
|
-
- [API Reference](docs/api.md)
|
|
122
|
-
- [Android Details](docs/android.md)
|
|
123
|
-
- [iOS Details](docs/ios.md)
|
|
124
|
-
- [Prefetch & Auto-Prefetch](docs/prefetch.md)
|
|
125
|
-
- [Worklets](docs/worklets.md)
|
|
126
|
-
- [Troubleshooting](docs/troubleshooting.md)
|
|
127
|
-
- [Cronet (Android) notes](docs/cronet-android.md)
|
|
128
|
-
- [Cronet (iOS) notes](docs/cronet-ios.md)
|
|
129
|
-
|
|
130
|
-
## Margelo
|
|
131
|
-
|
|
132
|
-
Nitro Fetch is built with ❤️ by Margelo.
|
|
133
|
-
We build fast and beautiful apps. Contact us at [margelo.com](https://margelo.com) for high-end consultancy services.
|
|
134
|
-
|
|
135
|
-
## Contributing
|
|
136
|
-
|
|
137
|
-
- Development workflow: `CONTRIBUTING.md#development-workflow`
|
|
138
|
-
- Sending a pull request: `CONTRIBUTING.md#sending-a-pull-request`
|
|
139
|
-
- Code of conduct: `CODE_OF_CONDUCT.md`
|
|
140
|
-
|
|
141
|
-
## Authors
|
|
142
|
-
|
|
143
|
-
- [Szymon Kapala](https://github.com/Szymon20000)
|
|
144
|
-
- [Alex Shumihin](https://github.com/pioner92)
|
|
145
|
-
- [Ronald Goedeke](https://github.com/ronickg)
|
|
146
|
-
- [Marc Rousavy](https://github.com/mrousavy)
|
|
147
|
-
|
|
148
|
-
## License
|
|
149
|
-
|
|
150
|
-
MIT
|
|
151
|
-
|