react-native-nitro-fetch 1.4.0 → 1.4.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.
- package/README.md +11 -0
- package/lib/module/CurlGenerator.js.map +2 -1
- package/lib/module/Headers.js.map +2 -2
- package/lib/module/HermesProfiler.js.map +1 -1
- package/lib/module/NetworkInspector.js.map +2 -2
- package/lib/module/NitroInstances.js.map +2 -2
- package/lib/module/Response.js.map +1 -2
- package/lib/module/index.js.map +2 -1
- package/lib/module/index.web.js +1 -2
- package/lib/module/index.web.js.map +2 -1
- package/lib/module/utf8.js.map +2 -1
- package/package.json +5 -5
- package/src/CurlGenerator.js +23 -26
- package/src/Headers.js +108 -116
- package/src/HermesProfiler.js +16 -18
- package/src/NetworkInspector.js +171 -179
- package/src/NitroInstances.js +2 -1
- package/src/Request.js +167 -164
- package/src/Response.js +244 -242
- package/src/fetch.js +830 -820
- package/src/index.js +17 -2
- package/src/index.web.js +69 -67
- package/src/tokenRefresh.js +75 -77
- package/src/utf8.js +28 -27
package/src/fetch.js
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import 'web-streams-polyfill/polyfill';
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
NitroFetch as NitroFetchSingleton,
|
|
4
|
+
NitroCronetSingleton,
|
|
5
|
+
} from './NitroInstances';
|
|
3
6
|
import { NativeStorage as NativeStorageSingleton } from './NitroInstances';
|
|
4
7
|
import { NitroHeaders } from './Headers';
|
|
5
8
|
import { NitroResponse } from './Response';
|
|
@@ -7,916 +10,923 @@ import { NitroRequest as NitroRequestClass } from './Request';
|
|
|
7
10
|
import { NetworkInspector } from './NetworkInspector';
|
|
8
11
|
// No base64: pass strings/ArrayBuffers directly
|
|
9
12
|
function headersToPairs(headers) {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
if
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
}
|
|
31
|
-
return pairs;
|
|
13
|
+
'worklet';
|
|
14
|
+
if (!headers) return undefined;
|
|
15
|
+
const pairs = [];
|
|
16
|
+
if (headers instanceof Headers) {
|
|
17
|
+
headers.forEach((v, k) => pairs.push({ key: k, value: v }));
|
|
18
|
+
return pairs;
|
|
19
|
+
}
|
|
20
|
+
if (Array.isArray(headers)) {
|
|
21
|
+
// Convert tuple pairs to objects if needed
|
|
22
|
+
for (const entry of headers) {
|
|
23
|
+
if (Array.isArray(entry) && entry.length >= 2) {
|
|
24
|
+
pairs.push({ key: String(entry[0]), value: String(entry[1]) });
|
|
25
|
+
} else if (
|
|
26
|
+
entry &&
|
|
27
|
+
typeof entry === 'object' &&
|
|
28
|
+
'key' in entry &&
|
|
29
|
+
'value' in entry
|
|
30
|
+
) {
|
|
31
|
+
pairs.push(entry);
|
|
32
|
+
}
|
|
32
33
|
}
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
}
|
|
54
|
-
return pairs;
|
|
34
|
+
return pairs;
|
|
35
|
+
}
|
|
36
|
+
// Check if it's a plain object (Record<string, string>) first
|
|
37
|
+
// Plain objects don't have forEach, so check for its absence
|
|
38
|
+
if (typeof headers === 'object' && headers !== null) {
|
|
39
|
+
// Check if it's a Headers instance by checking for forEach method
|
|
40
|
+
const hasForEach = typeof headers.forEach === 'function';
|
|
41
|
+
if (hasForEach) {
|
|
42
|
+
// Headers-like object (duck typing)
|
|
43
|
+
headers.forEach((v, k) => pairs.push({ key: k, value: v }));
|
|
44
|
+
return pairs;
|
|
45
|
+
} else {
|
|
46
|
+
// Plain object (Record<string, string>)
|
|
47
|
+
// Use Object.keys to iterate since Object.entries might not work in worklets
|
|
48
|
+
const keys = Object.keys(headers);
|
|
49
|
+
for (let i = 0; i < keys.length; i++) {
|
|
50
|
+
const k = keys[i];
|
|
51
|
+
const v = headers[k];
|
|
52
|
+
if (v !== undefined) {
|
|
53
|
+
pairs.push({ key: k, value: String(v) });
|
|
55
54
|
}
|
|
55
|
+
}
|
|
56
|
+
return pairs;
|
|
56
57
|
}
|
|
57
|
-
|
|
58
|
+
}
|
|
59
|
+
return pairs;
|
|
58
60
|
}
|
|
59
61
|
function serializeFormData(fd) {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
return parts;
|
|
62
|
+
const parts = [];
|
|
63
|
+
if (typeof fd.getParts === 'function') {
|
|
64
|
+
const rnParts = fd.getParts();
|
|
65
|
+
for (const part of rnParts) {
|
|
66
|
+
if (part.string !== undefined) {
|
|
67
|
+
parts.push({ name: part.fieldName, value: String(part.string) });
|
|
68
|
+
} else if (part.uri) {
|
|
69
|
+
parts.push({
|
|
70
|
+
name: part.fieldName,
|
|
71
|
+
fileUri: part.uri,
|
|
72
|
+
fileName: part.fileName ?? part.name ?? 'file',
|
|
73
|
+
mimeType: part.type ?? 'application/octet-stream',
|
|
74
|
+
});
|
|
75
|
+
}
|
|
77
76
|
}
|
|
78
|
-
fd.forEach((value, key) => {
|
|
79
|
-
if (typeof value === 'string') {
|
|
80
|
-
parts.push({ name: key, value });
|
|
81
|
-
}
|
|
82
|
-
else if (value && typeof value === 'object') {
|
|
83
|
-
parts.push({
|
|
84
|
-
name: key,
|
|
85
|
-
fileUri: value.uri ?? value.fileUri,
|
|
86
|
-
fileName: value.name ?? value.fileName ?? 'file',
|
|
87
|
-
mimeType: value.type ?? value.mimeType ?? 'application/octet-stream',
|
|
88
|
-
});
|
|
89
|
-
}
|
|
90
|
-
});
|
|
91
77
|
return parts;
|
|
78
|
+
}
|
|
79
|
+
fd.forEach((value, key) => {
|
|
80
|
+
if (typeof value === 'string') {
|
|
81
|
+
parts.push({ name: key, value });
|
|
82
|
+
} else if (value && typeof value === 'object') {
|
|
83
|
+
parts.push({
|
|
84
|
+
name: key,
|
|
85
|
+
fileUri: value.uri ?? value.fileUri,
|
|
86
|
+
fileName: value.name ?? value.fileName ?? 'file',
|
|
87
|
+
mimeType: value.type ?? value.mimeType ?? 'application/octet-stream',
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
return parts;
|
|
92
92
|
}
|
|
93
93
|
function isFormData(body) {
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
94
|
+
if (typeof FormData !== 'undefined' && body instanceof FormData) return true;
|
|
95
|
+
if (
|
|
96
|
+
body &&
|
|
97
|
+
typeof body === 'object' &&
|
|
98
|
+
typeof body.append === 'function' &&
|
|
99
|
+
typeof body.getParts === 'function'
|
|
100
|
+
) {
|
|
101
|
+
return true;
|
|
102
|
+
}
|
|
103
|
+
return false;
|
|
103
104
|
}
|
|
104
105
|
function normalizeBody(body) {
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
106
|
+
'worklet';
|
|
107
|
+
if (body == null) return undefined;
|
|
108
|
+
if (typeof body === 'string') return { bodyString: body };
|
|
109
|
+
if (isFormData(body)) {
|
|
110
|
+
return { bodyFormData: serializeFormData(body) };
|
|
111
|
+
}
|
|
112
|
+
if (body instanceof URLSearchParams) return { bodyString: body.toString() };
|
|
113
|
+
if (typeof ArrayBuffer !== 'undefined' && body instanceof ArrayBuffer)
|
|
114
|
+
return { bodyBytes: body };
|
|
115
|
+
if (ArrayBuffer.isView(body)) {
|
|
116
|
+
const view = body;
|
|
117
|
+
return {
|
|
118
|
+
//@ts-ignore
|
|
119
|
+
bodyBytes: view.buffer.slice(
|
|
120
|
+
view.byteOffset,
|
|
121
|
+
view.byteOffset + view.byteLength
|
|
122
|
+
),
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
throw new Error('Unsupported body type for nitro fetch');
|
|
125
126
|
}
|
|
126
127
|
const NitroFetchHybrid = NitroFetchSingleton;
|
|
127
128
|
let client;
|
|
128
129
|
function ensureClient() {
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
}
|
|
138
|
-
return client;
|
|
130
|
+
if (client) return client;
|
|
131
|
+
try {
|
|
132
|
+
client = NitroFetchHybrid.createClient();
|
|
133
|
+
} catch (err) {
|
|
134
|
+
console.error('Failed to create NitroFetch client', err);
|
|
135
|
+
// native not ready; keep undefined
|
|
136
|
+
}
|
|
137
|
+
return client;
|
|
139
138
|
}
|
|
140
139
|
function buildNitroRequest(input, init) {
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
bodyBytes: undefined,
|
|
195
|
-
bodyFormData: normalized?.bodyFormData,
|
|
196
|
-
followRedirects,
|
|
197
|
-
prefetchCacheTtlMs,
|
|
198
|
-
};
|
|
140
|
+
'worklet';
|
|
141
|
+
let url;
|
|
142
|
+
let method;
|
|
143
|
+
let headersInit;
|
|
144
|
+
let body;
|
|
145
|
+
let redirectOption = init?.redirect ?? 'follow';
|
|
146
|
+
let cacheOption = init?.cache;
|
|
147
|
+
if (input instanceof NitroRequestClass) {
|
|
148
|
+
url = input.url;
|
|
149
|
+
method = init?.method ?? input.method;
|
|
150
|
+
headersInit = init?.headers ?? input.headers;
|
|
151
|
+
body = init?.body ?? input.body ?? null;
|
|
152
|
+
if (!init?.redirect) redirectOption = input.redirect;
|
|
153
|
+
if (!init?.cache) cacheOption = input.cache;
|
|
154
|
+
} else if (typeof input === 'string' || input instanceof URL) {
|
|
155
|
+
url = String(input);
|
|
156
|
+
method = init?.method;
|
|
157
|
+
headersInit = init?.headers;
|
|
158
|
+
body = init?.body ?? null;
|
|
159
|
+
} else {
|
|
160
|
+
// Standard Request object
|
|
161
|
+
url = input.url;
|
|
162
|
+
method = input.method;
|
|
163
|
+
headersInit = input.headers;
|
|
164
|
+
body = init?.body ?? null;
|
|
165
|
+
}
|
|
166
|
+
const headers = headersToPairs(headersInit) ?? [];
|
|
167
|
+
const normalized = normalizeBody(body);
|
|
168
|
+
// Inject cache-control headers based on cache option
|
|
169
|
+
if (cacheOption === 'no-store') {
|
|
170
|
+
headers.push({ key: 'Cache-Control', value: 'no-store' });
|
|
171
|
+
} else if (cacheOption === 'no-cache') {
|
|
172
|
+
headers.push({ key: 'Cache-Control', value: 'no-cache' });
|
|
173
|
+
} else if (cacheOption === 'reload') {
|
|
174
|
+
headers.push({ key: 'Cache-Control', value: 'no-cache' });
|
|
175
|
+
headers.push({ key: 'Pragma', value: 'no-cache' });
|
|
176
|
+
}
|
|
177
|
+
// Determine followRedirects based on redirect option
|
|
178
|
+
const followRedirects = redirectOption === 'follow';
|
|
179
|
+
const prefetchCacheTtlMs =
|
|
180
|
+
typeof init?.prefetchCacheTtlMs === 'number'
|
|
181
|
+
? init.prefetchCacheTtlMs
|
|
182
|
+
: undefined;
|
|
183
|
+
return {
|
|
184
|
+
url,
|
|
185
|
+
method: method?.toUpperCase() ?? 'GET',
|
|
186
|
+
headers: headers.length > 0 ? headers : undefined,
|
|
187
|
+
bodyString: normalized?.bodyString,
|
|
188
|
+
bodyBytes: undefined,
|
|
189
|
+
bodyFormData: normalized?.bodyFormData,
|
|
190
|
+
followRedirects,
|
|
191
|
+
prefetchCacheTtlMs,
|
|
192
|
+
};
|
|
199
193
|
}
|
|
200
194
|
// Pure JS version of buildNitroRequest that doesnt use anything that breaks worklets. TODO: Merge this to use Same logic for Worklets and normal Fetch
|
|
201
195
|
function headersToPairsPure(headers) {
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
if
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
}
|
|
219
|
-
return pairs;
|
|
196
|
+
'worklet';
|
|
197
|
+
if (!headers) return undefined;
|
|
198
|
+
const pairs = [];
|
|
199
|
+
if (Array.isArray(headers)) {
|
|
200
|
+
// Convert tuple pairs to objects if needed
|
|
201
|
+
for (const entry of headers) {
|
|
202
|
+
if (Array.isArray(entry) && entry.length >= 2) {
|
|
203
|
+
pairs.push({ key: String(entry[0]), value: String(entry[1]) });
|
|
204
|
+
} else if (
|
|
205
|
+
entry &&
|
|
206
|
+
typeof entry === 'object' &&
|
|
207
|
+
'key' in entry &&
|
|
208
|
+
'value' in entry
|
|
209
|
+
) {
|
|
210
|
+
pairs.push(entry);
|
|
211
|
+
}
|
|
220
212
|
}
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
}
|
|
242
|
-
return pairs;
|
|
213
|
+
return pairs;
|
|
214
|
+
}
|
|
215
|
+
// Check if it's a plain object (Record<string, string>) first
|
|
216
|
+
// Plain objects don't have forEach, so check for its absence
|
|
217
|
+
if (typeof headers === 'object' && headers !== null) {
|
|
218
|
+
// Check if it's a Headers instance by checking for forEach method
|
|
219
|
+
const hasForEach = typeof headers.forEach === 'function';
|
|
220
|
+
if (hasForEach) {
|
|
221
|
+
// Headers-like object (duck typing)
|
|
222
|
+
headers.forEach((v, k) => pairs.push({ key: k, value: v }));
|
|
223
|
+
return pairs;
|
|
224
|
+
} else {
|
|
225
|
+
// Plain object (Record<string, string>)
|
|
226
|
+
// Use Object.keys to iterate since Object.entries might not work in worklets
|
|
227
|
+
const keys = Object.keys(headers);
|
|
228
|
+
for (let i = 0; i < keys.length; i++) {
|
|
229
|
+
const k = keys[i];
|
|
230
|
+
const v = headers[k];
|
|
231
|
+
if (v !== undefined) {
|
|
232
|
+
pairs.push({ key: k, value: String(v) });
|
|
243
233
|
}
|
|
234
|
+
}
|
|
235
|
+
return pairs;
|
|
244
236
|
}
|
|
245
|
-
|
|
237
|
+
}
|
|
238
|
+
return pairs;
|
|
246
239
|
}
|
|
247
240
|
// Pure JS version of buildNitroRequest that doesnt use anything that breaks worklets
|
|
248
241
|
function normalizeBodyPure(body) {
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
242
|
+
'worklet';
|
|
243
|
+
if (body == null) return undefined;
|
|
244
|
+
if (typeof body === 'string') return { bodyString: body };
|
|
245
|
+
// Check for URLSearchParams (duck typing)
|
|
246
|
+
// It should be an object, have a toString method, and typically append/delete methods
|
|
247
|
+
// But mainly we care about toString() returning the query string
|
|
248
|
+
if (
|
|
249
|
+
typeof body === 'object' &&
|
|
250
|
+
body !== null &&
|
|
251
|
+
typeof body.toString === 'function' &&
|
|
252
|
+
Object.prototype.toString.call(body) === '[object URLSearchParams]'
|
|
253
|
+
) {
|
|
254
|
+
return { bodyString: body.toString() };
|
|
255
|
+
}
|
|
256
|
+
// Check for ArrayBuffer (using toString tag to avoid instanceof)
|
|
257
|
+
if (
|
|
258
|
+
typeof ArrayBuffer !== 'undefined' &&
|
|
259
|
+
Object.prototype.toString.call(body) === '[object ArrayBuffer]'
|
|
260
|
+
) {
|
|
261
|
+
return { bodyBytes: body };
|
|
262
|
+
}
|
|
263
|
+
if (ArrayBuffer.isView(body)) {
|
|
264
|
+
const view = body;
|
|
265
|
+
return {
|
|
266
|
+
//@ts-ignore
|
|
267
|
+
bodyBytes: view.buffer.slice(
|
|
268
|
+
view.byteOffset,
|
|
269
|
+
view.byteOffset + view.byteLength
|
|
270
|
+
),
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
throw new Error(
|
|
274
|
+
'Unsupported body type for nitro fetch worklet (FormData is not available in worklets)'
|
|
275
|
+
);
|
|
276
276
|
}
|
|
277
277
|
// Pure JS version of buildNitroRequest that doesnt use anything that breaks worklets
|
|
278
278
|
export function buildNitroRequestPure(input, init) {
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
279
|
+
'worklet';
|
|
280
|
+
let url;
|
|
281
|
+
let method;
|
|
282
|
+
let headersInit;
|
|
283
|
+
let body;
|
|
284
|
+
// Check if input is URL-like without instanceof
|
|
285
|
+
const isUrlObject =
|
|
286
|
+
typeof input === 'object' &&
|
|
287
|
+
input !== null &&
|
|
288
|
+
Object.prototype.toString.call(input) === '[object URL]';
|
|
289
|
+
if (typeof input === 'string' || isUrlObject) {
|
|
290
|
+
url = String(input);
|
|
291
|
+
method = init?.method;
|
|
292
|
+
headersInit = init?.headers;
|
|
293
|
+
body = init?.body ?? null;
|
|
294
|
+
} else {
|
|
295
|
+
// Request object
|
|
296
|
+
const req = input;
|
|
297
|
+
url = req.url;
|
|
298
|
+
method = req.method;
|
|
299
|
+
headersInit = req.headers;
|
|
300
|
+
// Clone body if needed – Request objects in RN typically allow direct access
|
|
301
|
+
body = init?.body ?? null;
|
|
302
|
+
}
|
|
303
|
+
const headers = headersToPairsPure(headersInit);
|
|
304
|
+
const normalized = normalizeBodyPure(body);
|
|
305
|
+
const prefetchCacheTtlMs =
|
|
306
|
+
typeof init?.prefetchCacheTtlMs === 'number'
|
|
307
|
+
? init.prefetchCacheTtlMs
|
|
308
|
+
: undefined;
|
|
309
|
+
return {
|
|
310
|
+
url,
|
|
311
|
+
method: method?.toUpperCase() ?? 'GET',
|
|
312
|
+
headers,
|
|
313
|
+
bodyString: normalized?.bodyString,
|
|
314
|
+
// Only include bodyBytes when provided to avoid signaling upload data unintentionally
|
|
315
|
+
bodyBytes: undefined,
|
|
316
|
+
followRedirects: true,
|
|
317
|
+
prefetchCacheTtlMs,
|
|
318
|
+
};
|
|
318
319
|
}
|
|
319
320
|
function createAbortError() {
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
321
|
+
const err = new Error('The operation was aborted.');
|
|
322
|
+
err.name = 'AbortError';
|
|
323
|
+
return err;
|
|
323
324
|
}
|
|
324
325
|
async function resolveRequestBody(input, init) {
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
if (text.length === 0)
|
|
340
|
-
return init;
|
|
341
|
-
return { ...(init ?? {}), body: text };
|
|
342
|
-
}
|
|
343
|
-
catch {
|
|
344
|
-
return init;
|
|
345
|
-
}
|
|
326
|
+
if (typeof input === 'string' || input instanceof URL) return init;
|
|
327
|
+
if (input instanceof NitroRequestClass) return init;
|
|
328
|
+
if (init?.body != null) return init;
|
|
329
|
+
const req = input;
|
|
330
|
+
if (typeof req.clone !== 'function') return init;
|
|
331
|
+
const method = (init?.method ?? req.method ?? 'GET').toUpperCase();
|
|
332
|
+
if (method === 'GET' || method === 'HEAD') return init;
|
|
333
|
+
try {
|
|
334
|
+
const text = await req.clone().text();
|
|
335
|
+
if (text.length === 0) return init;
|
|
336
|
+
return { ...(init ?? {}), body: text };
|
|
337
|
+
} catch {
|
|
338
|
+
return init;
|
|
339
|
+
}
|
|
346
340
|
}
|
|
347
341
|
async function resolveBlobBody(init) {
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
}
|
|
370
|
-
|
|
342
|
+
if (!init?.body) return init;
|
|
343
|
+
if (typeof Blob !== 'undefined' && init.body instanceof Blob) {
|
|
344
|
+
const blob = init.body;
|
|
345
|
+
const text = await new Promise((resolve, reject) => {
|
|
346
|
+
const reader = new FileReader();
|
|
347
|
+
reader.onload = () => resolve(reader.result);
|
|
348
|
+
reader.onerror = () => reject(reader.error);
|
|
349
|
+
reader.readAsText(blob);
|
|
350
|
+
});
|
|
351
|
+
// Auto-set Content-Type from Blob.type if not already provided
|
|
352
|
+
let headers = init.headers;
|
|
353
|
+
if (blob.type) {
|
|
354
|
+
const pairs = headersToPairs(headers) ?? [];
|
|
355
|
+
const hasContentType = pairs.some(
|
|
356
|
+
(h) => h.key.toLowerCase() === 'content-type'
|
|
357
|
+
);
|
|
358
|
+
if (!hasContentType) {
|
|
359
|
+
pairs.push({ key: 'Content-Type', value: blob.type });
|
|
360
|
+
headers = pairs.map((h) => [h.key, h.value]);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
return { ...init, body: text, headers };
|
|
364
|
+
}
|
|
365
|
+
return init;
|
|
371
366
|
}
|
|
372
367
|
// http(s) -> native client; anything else is a local resource (hot path).
|
|
373
368
|
function isHttpUrl(url) {
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
return false; // not 'h'/'H'
|
|
379
|
-
return /^https?:/i.test(url);
|
|
369
|
+
if (url.startsWith('http://') || url.startsWith('https://')) return true;
|
|
370
|
+
const c = url.charCodeAt(0);
|
|
371
|
+
if (c !== 104 && c !== 72) return false; // not 'h'/'H'
|
|
372
|
+
return /^https?:/i.test(url);
|
|
380
373
|
}
|
|
381
374
|
function getUrlString(input) {
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
const u = input?.url;
|
|
387
|
-
return typeof u === 'string' ? u : String(input);
|
|
375
|
+
if (typeof input === 'string') return input;
|
|
376
|
+
if (input instanceof URL) return input.toString();
|
|
377
|
+
const u = input?.url;
|
|
378
|
+
return typeof u === 'string' ? u : String(input);
|
|
388
379
|
}
|
|
389
380
|
function base64ToBytes(b64) {
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
out[i] = bin.charCodeAt(i);
|
|
396
|
-
return out;
|
|
397
|
-
}
|
|
398
|
-
// base64 fallback for runtimes without a global atob.
|
|
399
|
-
/* eslint-disable no-bitwise */
|
|
400
|
-
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
|
|
401
|
-
const clean = b64.replace(/[^A-Za-z0-9+/]/g, '');
|
|
402
|
-
const out = new Uint8Array(Math.floor((clean.length * 3) / 4));
|
|
403
|
-
let p = 0;
|
|
404
|
-
let buf = 0;
|
|
405
|
-
let bits = 0;
|
|
406
|
-
for (let i = 0; i < clean.length; i++) {
|
|
407
|
-
buf = (buf << 6) | chars.indexOf(clean[i]);
|
|
408
|
-
bits += 6;
|
|
409
|
-
if (bits >= 8) {
|
|
410
|
-
bits -= 8;
|
|
411
|
-
out[p++] = (buf >> bits) & 0xff;
|
|
412
|
-
}
|
|
413
|
-
}
|
|
381
|
+
const decode = globalThis.atob;
|
|
382
|
+
if (typeof decode === 'function') {
|
|
383
|
+
const bin = decode(b64);
|
|
384
|
+
const out = new Uint8Array(bin.length);
|
|
385
|
+
for (let i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i);
|
|
414
386
|
return out;
|
|
415
|
-
|
|
387
|
+
}
|
|
388
|
+
// base64 fallback for runtimes without a global atob.
|
|
389
|
+
/* eslint-disable no-bitwise */
|
|
390
|
+
const chars =
|
|
391
|
+
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
|
|
392
|
+
const clean = b64.replace(/[^A-Za-z0-9+/]/g, '');
|
|
393
|
+
const out = new Uint8Array(Math.floor((clean.length * 3) / 4));
|
|
394
|
+
let p = 0;
|
|
395
|
+
let buf = 0;
|
|
396
|
+
let bits = 0;
|
|
397
|
+
for (let i = 0; i < clean.length; i++) {
|
|
398
|
+
buf = (buf << 6) | chars.indexOf(clean[i]);
|
|
399
|
+
bits += 6;
|
|
400
|
+
if (bits >= 8) {
|
|
401
|
+
bits -= 8;
|
|
402
|
+
out[p++] = (buf >> bits) & 0xff;
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
return out;
|
|
406
|
+
/* eslint-enable no-bitwise */
|
|
416
407
|
}
|
|
417
408
|
// Cached: our nitro-text-decoder if the app bundles it (aliased require keeps it optional, not a dep), else a global TextDecoder.
|
|
418
409
|
let _decoder;
|
|
419
410
|
function resolveTextDecoder() {
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
.TextDecoder;
|
|
435
|
-
if (typeof GlobalTextDecoder === 'function') {
|
|
436
|
-
_decoder = new GlobalTextDecoder('utf-8', { fatal: true });
|
|
437
|
-
return _decoder;
|
|
438
|
-
}
|
|
439
|
-
_decoder = null;
|
|
411
|
+
if (_decoder !== undefined) return _decoder;
|
|
412
|
+
try {
|
|
413
|
+
const dynamicRequire = require;
|
|
414
|
+
const mod = dynamicRequire('react-native-nitro-text-decoder');
|
|
415
|
+
if (mod && typeof mod.TextDecoder === 'function') {
|
|
416
|
+
_decoder = new mod.TextDecoder('utf-8', { fatal: true });
|
|
417
|
+
return _decoder;
|
|
418
|
+
}
|
|
419
|
+
} catch {
|
|
420
|
+
// optional, not bundled
|
|
421
|
+
}
|
|
422
|
+
const GlobalTextDecoder = globalThis.TextDecoder;
|
|
423
|
+
if (typeof GlobalTextDecoder === 'function') {
|
|
424
|
+
_decoder = new GlobalTextDecoder('utf-8', { fatal: true });
|
|
440
425
|
return _decoder;
|
|
426
|
+
}
|
|
427
|
+
_decoder = null;
|
|
428
|
+
return _decoder;
|
|
441
429
|
}
|
|
442
430
|
// data: text via a TextDecoder; null (bytes-only) + a one-time warn if none.
|
|
443
431
|
let _warnedNoTextDecoder = false;
|
|
444
432
|
function decodeUtf8(bytes) {
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
433
|
+
const decoder = resolveTextDecoder();
|
|
434
|
+
if (decoder) {
|
|
435
|
+
try {
|
|
436
|
+
return decoder.decode(bytes);
|
|
437
|
+
} catch {
|
|
438
|
+
return null; // invalid UTF-8 -> keep bytes only
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
if (!_warnedNoTextDecoder) {
|
|
442
|
+
_warnedNoTextDecoder = true;
|
|
443
|
+
console.warn(
|
|
444
|
+
'[nitro-fetch] Reading a data: URL as text needs a TextDecoder. Install ' +
|
|
445
|
+
'react-native-nitro-text-decoder or expose a global TextDecoder. The ' +
|
|
446
|
+
'body is still available via response.arrayBuffer()/bytes().'
|
|
447
|
+
);
|
|
448
|
+
}
|
|
449
|
+
return null;
|
|
461
450
|
}
|
|
462
451
|
// Decode a data: URL into a synthetic 200 response, entirely in JS.
|
|
463
452
|
function decodeDataUrl(url) {
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
length
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
bodyBytes,
|
|
503
|
-
};
|
|
453
|
+
const comma = url.indexOf(',');
|
|
454
|
+
if (comma < 0) throw new TypeError('Failed to fetch: invalid data: URL');
|
|
455
|
+
const meta = url.slice(5, comma); // strip leading "data:"
|
|
456
|
+
const rawData = url.slice(comma + 1);
|
|
457
|
+
const isBase64 = /;base64\s*$/i.test(meta);
|
|
458
|
+
const mediaType =
|
|
459
|
+
(isBase64 ? meta.replace(/;base64\s*$/i, '') : meta).trim() ||
|
|
460
|
+
'text/plain;charset=US-ASCII';
|
|
461
|
+
let bodyString;
|
|
462
|
+
let bodyBytes;
|
|
463
|
+
let length;
|
|
464
|
+
if (isBase64) {
|
|
465
|
+
const bytes = base64ToBytes(rawData);
|
|
466
|
+
length = bytes.byteLength;
|
|
467
|
+
// bytes for arrayBuffer/bytes; string for text/json when a decoder exists.
|
|
468
|
+
bodyBytes = bytes.buffer;
|
|
469
|
+
const decoded = decodeUtf8(bytes);
|
|
470
|
+
if (decoded != null) bodyString = decoded;
|
|
471
|
+
} else {
|
|
472
|
+
bodyString = decodeURIComponent(rawData);
|
|
473
|
+
length =
|
|
474
|
+
typeof TextEncoder !== 'undefined'
|
|
475
|
+
? new TextEncoder().encode(bodyString).length
|
|
476
|
+
: bodyString.length;
|
|
477
|
+
}
|
|
478
|
+
return {
|
|
479
|
+
url,
|
|
480
|
+
status: 200,
|
|
481
|
+
statusText: 'OK',
|
|
482
|
+
ok: true,
|
|
483
|
+
redirected: false,
|
|
484
|
+
headers: [
|
|
485
|
+
{ key: 'Content-Type', value: mediaType },
|
|
486
|
+
{ key: 'Content-Length', value: String(length) },
|
|
487
|
+
],
|
|
488
|
+
bodyString,
|
|
489
|
+
bodyBytes,
|
|
490
|
+
};
|
|
504
491
|
}
|
|
505
492
|
// Non-http(s): decode data: in JS, reject blob:, read file/content/path natively.
|
|
506
493
|
async function fetchLocalResource(req) {
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
494
|
+
const url = req.url;
|
|
495
|
+
if (url.startsWith('data:')) return decodeDataUrl(url);
|
|
496
|
+
if (url.startsWith('blob:')) {
|
|
497
|
+
throw new TypeError(
|
|
498
|
+
'nitro-fetch cannot read blob: URLs (the React Native blob registry is not ' +
|
|
499
|
+
'reachable from native). Read blobs with the platform fetch/FileReader instead.'
|
|
500
|
+
);
|
|
501
|
+
}
|
|
502
|
+
ensureClient();
|
|
503
|
+
if (!client || typeof client.request !== 'function') {
|
|
504
|
+
throw new Error('NitroFetch client not available');
|
|
505
|
+
}
|
|
506
|
+
return client.request(req);
|
|
519
507
|
}
|
|
520
508
|
async function nitroFetchRaw(input, init) {
|
|
521
|
-
|
|
522
|
-
|
|
509
|
+
const signal = init?.signal;
|
|
510
|
+
// Fast-abort: reject synchronously before any bridge work.
|
|
511
|
+
if (signal?.aborted) {
|
|
512
|
+
throw createAbortError();
|
|
513
|
+
}
|
|
514
|
+
// Extract body from standard Request when init.body is absent (ky/undici pattern)
|
|
515
|
+
init = await resolveRequestBody(input, init);
|
|
516
|
+
// Resolve Blob body to string before passing to sync buildNitroRequest
|
|
517
|
+
init = await resolveBlobBody(init);
|
|
518
|
+
const hasNative = typeof NitroFetchHybrid?.createClient === 'function';
|
|
519
|
+
if (!hasNative) {
|
|
520
|
+
// Fallback path not supported for raw; use global fetch and synthesize minimal shape
|
|
521
|
+
// @ts-ignore: global fetch exists in RN
|
|
522
|
+
const res = await fetch(input, init);
|
|
523
|
+
const url = res.url ?? String(input);
|
|
524
|
+
const bytes = await res.arrayBuffer();
|
|
525
|
+
const headers = [];
|
|
526
|
+
res.headers.forEach((v, k) => headers.push({ key: k, value: v }));
|
|
527
|
+
return {
|
|
528
|
+
url,
|
|
529
|
+
status: res.status,
|
|
530
|
+
statusText: res.statusText,
|
|
531
|
+
ok: res.ok,
|
|
532
|
+
redirected: res.redirected ?? false,
|
|
533
|
+
headers,
|
|
534
|
+
bodyBytes: bytes,
|
|
535
|
+
bodyString: undefined,
|
|
536
|
+
}; // bleee
|
|
537
|
+
}
|
|
538
|
+
const req = buildNitroRequest(input, init);
|
|
539
|
+
// Route non-http(s) (data:/file://content://scheme-less) off the HTTP client.
|
|
540
|
+
if (!isHttpUrl(req.url)) {
|
|
541
|
+
return fetchLocalResource(req);
|
|
542
|
+
}
|
|
543
|
+
// Inspector: record start (zero cost when disabled — single boolean check)
|
|
544
|
+
let inspectorId;
|
|
545
|
+
if (NetworkInspector.isEnabled()) {
|
|
546
|
+
inspectorId = String(Date.now()) + '-' + String(Math.random()).slice(2, 8);
|
|
547
|
+
NetworkInspector._recordStart(
|
|
548
|
+
inspectorId,
|
|
549
|
+
req.url,
|
|
550
|
+
req.method ?? 'GET',
|
|
551
|
+
req.headers ?? [],
|
|
552
|
+
req.bodyString
|
|
553
|
+
);
|
|
554
|
+
}
|
|
555
|
+
// Only allocate a requestId when a signal is present — zero overhead otherwise.
|
|
556
|
+
const requestId = signal ? String(Math.random()) : undefined;
|
|
557
|
+
if (requestId) req.requestId = requestId;
|
|
558
|
+
ensureClient();
|
|
559
|
+
if (!client || typeof client.request !== 'function')
|
|
560
|
+
throw new Error('NitroFetch client not available');
|
|
561
|
+
// Wire up the abort listener with { once: true } so it auto-removes
|
|
562
|
+
// after firing, avoiding a dangling reference to the client closure.
|
|
563
|
+
let abortListener;
|
|
564
|
+
if (signal && requestId) {
|
|
565
|
+
abortListener = () => {
|
|
566
|
+
try {
|
|
567
|
+
client.cancelRequest(requestId);
|
|
568
|
+
} catch {
|
|
569
|
+
// Client may already be torn down — swallow.
|
|
570
|
+
}
|
|
571
|
+
};
|
|
572
|
+
signal.addEventListener('abort', abortListener, { once: true });
|
|
573
|
+
}
|
|
574
|
+
try {
|
|
575
|
+
const res = await client.request(req);
|
|
576
|
+
if (inspectorId) {
|
|
577
|
+
NetworkInspector._recordEnd(
|
|
578
|
+
inspectorId,
|
|
579
|
+
res.status,
|
|
580
|
+
res.statusText,
|
|
581
|
+
res.headers ?? [],
|
|
582
|
+
res.bodyString?.length ?? 0,
|
|
583
|
+
undefined,
|
|
584
|
+
res.bodyString ?? undefined
|
|
585
|
+
);
|
|
586
|
+
}
|
|
587
|
+
return res;
|
|
588
|
+
} catch (e) {
|
|
589
|
+
if (inspectorId) {
|
|
590
|
+
NetworkInspector._recordEnd(inspectorId, 0, '', [], 0, String(e));
|
|
591
|
+
}
|
|
592
|
+
// If the signal was aborted (either before or during the request),
|
|
593
|
+
// surface a spec-compliant AbortError regardless of what native threw.
|
|
523
594
|
if (signal?.aborted) {
|
|
524
|
-
|
|
595
|
+
throw createAbortError();
|
|
525
596
|
}
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
//
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
// Fallback path not supported for raw; use global fetch and synthesize minimal shape
|
|
533
|
-
// @ts-ignore: global fetch exists in RN
|
|
534
|
-
const res = await fetch(input, init);
|
|
535
|
-
const url = res.url ?? String(input);
|
|
536
|
-
const bytes = await res.arrayBuffer();
|
|
537
|
-
const headers = [];
|
|
538
|
-
res.headers.forEach((v, k) => headers.push({ key: k, value: v }));
|
|
539
|
-
return {
|
|
540
|
-
url,
|
|
541
|
-
status: res.status,
|
|
542
|
-
statusText: res.statusText,
|
|
543
|
-
ok: res.ok,
|
|
544
|
-
redirected: res.redirected ?? false,
|
|
545
|
-
headers,
|
|
546
|
-
bodyBytes: bytes,
|
|
547
|
-
bodyString: undefined,
|
|
548
|
-
}; // bleee
|
|
549
|
-
}
|
|
550
|
-
const req = buildNitroRequest(input, init);
|
|
551
|
-
// Route non-http(s) (data:/file://content://scheme-less) off the HTTP client.
|
|
552
|
-
if (!isHttpUrl(req.url)) {
|
|
553
|
-
return fetchLocalResource(req);
|
|
554
|
-
}
|
|
555
|
-
// Inspector: record start (zero cost when disabled — single boolean check)
|
|
556
|
-
let inspectorId;
|
|
557
|
-
if (NetworkInspector.isEnabled()) {
|
|
558
|
-
inspectorId = String(Date.now()) + '-' + String(Math.random()).slice(2, 8);
|
|
559
|
-
NetworkInspector._recordStart(inspectorId, req.url, req.method ?? 'GET', req.headers ?? [], req.bodyString);
|
|
560
|
-
}
|
|
561
|
-
// Only allocate a requestId when a signal is present — zero overhead otherwise.
|
|
562
|
-
const requestId = signal ? String(Math.random()) : undefined;
|
|
563
|
-
if (requestId)
|
|
564
|
-
req.requestId = requestId;
|
|
565
|
-
ensureClient();
|
|
566
|
-
if (!client || typeof client.request !== 'function')
|
|
567
|
-
throw new Error('NitroFetch client not available');
|
|
568
|
-
// Wire up the abort listener with { once: true } so it auto-removes
|
|
569
|
-
// after firing, avoiding a dangling reference to the client closure.
|
|
570
|
-
let abortListener;
|
|
571
|
-
if (signal && requestId) {
|
|
572
|
-
abortListener = () => {
|
|
573
|
-
try {
|
|
574
|
-
client.cancelRequest(requestId);
|
|
575
|
-
}
|
|
576
|
-
catch {
|
|
577
|
-
// Client may already be torn down — swallow.
|
|
578
|
-
}
|
|
579
|
-
};
|
|
580
|
-
signal.addEventListener('abort', abortListener, { once: true });
|
|
581
|
-
}
|
|
582
|
-
try {
|
|
583
|
-
const res = await client.request(req);
|
|
584
|
-
if (inspectorId) {
|
|
585
|
-
NetworkInspector._recordEnd(inspectorId, res.status, res.statusText, res.headers ?? [], res.bodyString?.length ?? 0, undefined, res.bodyString ?? undefined);
|
|
586
|
-
}
|
|
587
|
-
return res;
|
|
588
|
-
}
|
|
589
|
-
catch (e) {
|
|
590
|
-
if (inspectorId) {
|
|
591
|
-
NetworkInspector._recordEnd(inspectorId, 0, '', [], 0, String(e));
|
|
592
|
-
}
|
|
593
|
-
// If the signal was aborted (either before or during the request),
|
|
594
|
-
// surface a spec-compliant AbortError regardless of what native threw.
|
|
595
|
-
if (signal?.aborted) {
|
|
596
|
-
throw createAbortError();
|
|
597
|
-
}
|
|
598
|
-
throw e;
|
|
599
|
-
}
|
|
600
|
-
finally {
|
|
601
|
-
// Idempotent cleanup — removeEventListener is a no-op if the listener
|
|
602
|
-
// already fired (thanks to { once: true }) or was never added.
|
|
603
|
-
if (signal && abortListener) {
|
|
604
|
-
signal.removeEventListener('abort', abortListener);
|
|
605
|
-
}
|
|
597
|
+
throw e;
|
|
598
|
+
} finally {
|
|
599
|
+
// Idempotent cleanup — removeEventListener is a no-op if the listener
|
|
600
|
+
// already fired (thanks to { once: true }) or was never added.
|
|
601
|
+
if (signal && abortListener) {
|
|
602
|
+
signal.removeEventListener('abort', abortListener);
|
|
606
603
|
}
|
|
604
|
+
}
|
|
607
605
|
}
|
|
608
606
|
// NitroHeaders is now imported from './Headers'
|
|
609
607
|
async function nitroStreamFetch(input, init) {
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
const status = info?.httpStatusCode ?? 0;
|
|
671
|
-
const hdrs = info?.allHeadersAsList ?? [];
|
|
672
|
-
NetworkInspector._recordEnd(inspectorId, status, info?.httpStatusText ?? '', hdrs, streamBytesReceived);
|
|
673
|
-
}
|
|
674
|
-
});
|
|
675
|
-
builder.onFailed((_info, error) => {
|
|
676
|
-
const err = new Error(error.message);
|
|
677
|
-
if (inspectorId) {
|
|
678
|
-
NetworkInspector._recordEnd(inspectorId, 0, '', [], 0, error.message);
|
|
679
|
-
}
|
|
680
|
-
if (!responseResolved) {
|
|
681
|
-
responseResolved = true;
|
|
682
|
-
rejectResponse(err);
|
|
683
|
-
}
|
|
684
|
-
else {
|
|
685
|
-
streamController.error(err);
|
|
686
|
-
}
|
|
687
|
-
});
|
|
688
|
-
builder.onCanceled(() => {
|
|
689
|
-
const err = createAbortError();
|
|
690
|
-
if (inspectorId) {
|
|
691
|
-
NetworkInspector._recordEnd(inspectorId, 0, '', [], 0, 'Request canceled');
|
|
692
|
-
}
|
|
693
|
-
if (!responseResolved) {
|
|
694
|
-
responseResolved = true;
|
|
695
|
-
rejectResponse(err);
|
|
696
|
-
}
|
|
697
|
-
else {
|
|
698
|
-
streamController.error(err);
|
|
699
|
-
}
|
|
700
|
-
});
|
|
701
|
-
const request = builder.build();
|
|
702
|
-
request.start();
|
|
608
|
+
const url = typeof input === 'string' ? input : String(input);
|
|
609
|
+
const method = init?.method?.toUpperCase() ?? 'GET';
|
|
610
|
+
const headers = headersToPairs(init?.headers);
|
|
611
|
+
// Inspector: record start
|
|
612
|
+
let inspectorId;
|
|
613
|
+
if (NetworkInspector.isEnabled()) {
|
|
614
|
+
inspectorId = String(Date.now()) + '-' + String(Math.random()).slice(2, 8);
|
|
615
|
+
NetworkInspector._recordStart(
|
|
616
|
+
inspectorId,
|
|
617
|
+
url,
|
|
618
|
+
method,
|
|
619
|
+
headers ?? [],
|
|
620
|
+
typeof init?.body === 'string' ? init.body : undefined
|
|
621
|
+
);
|
|
622
|
+
}
|
|
623
|
+
const builder = NitroCronetSingleton.newUrlRequestBuilder(url);
|
|
624
|
+
builder.setHttpMethod(method);
|
|
625
|
+
headers?.forEach((h) => builder.addHeader(h.key, h.value));
|
|
626
|
+
const body = init?.body;
|
|
627
|
+
if (body != null) {
|
|
628
|
+
if (typeof body === 'string') builder.setUploadBody(body);
|
|
629
|
+
else if (body instanceof ArrayBuffer) builder.setUploadBody(body);
|
|
630
|
+
}
|
|
631
|
+
return new Promise((resolveResponse, rejectResponse) => {
|
|
632
|
+
let streamController;
|
|
633
|
+
const stream = new ReadableStream({
|
|
634
|
+
start(controller) {
|
|
635
|
+
streamController = controller;
|
|
636
|
+
},
|
|
637
|
+
});
|
|
638
|
+
let responseResolved = false;
|
|
639
|
+
let streamBytesReceived = 0;
|
|
640
|
+
builder.onResponseStarted((info) => {
|
|
641
|
+
if (responseResolved) return;
|
|
642
|
+
responseResolved = true;
|
|
643
|
+
const status = info.httpStatusCode;
|
|
644
|
+
const responseHeaders = new NitroHeaders(
|
|
645
|
+
Object.entries(info.allHeaders).map(([key, value]) => ({ key, value }))
|
|
646
|
+
);
|
|
647
|
+
const response = new NitroResponse({
|
|
648
|
+
url: info.url,
|
|
649
|
+
ok: status >= 200 && status < 300,
|
|
650
|
+
status,
|
|
651
|
+
statusText: info.httpStatusText,
|
|
652
|
+
headers: responseHeaders,
|
|
653
|
+
redirected: false,
|
|
654
|
+
body: stream,
|
|
655
|
+
});
|
|
656
|
+
resolveResponse(response);
|
|
657
|
+
// Android/Cronet: kick off the first buffer read.
|
|
658
|
+
// iOS/URLSession handles reading automatically so this is a no-op there.
|
|
659
|
+
request.read();
|
|
660
|
+
});
|
|
661
|
+
builder.onReadCompleted((_info, byteBuffer, bytesRead) => {
|
|
662
|
+
const chunk = new Uint8Array(byteBuffer, 0, bytesRead).slice();
|
|
663
|
+
streamBytesReceived += bytesRead;
|
|
664
|
+
streamController.enqueue(chunk);
|
|
665
|
+
if (!request.isDone()) {
|
|
666
|
+
request.read();
|
|
667
|
+
}
|
|
703
668
|
});
|
|
669
|
+
builder.onSucceeded((_info) => {
|
|
670
|
+
streamController.close();
|
|
671
|
+
if (inspectorId) {
|
|
672
|
+
const info = _info;
|
|
673
|
+
const status = info?.httpStatusCode ?? 0;
|
|
674
|
+
const hdrs = info?.allHeadersAsList ?? [];
|
|
675
|
+
NetworkInspector._recordEnd(
|
|
676
|
+
inspectorId,
|
|
677
|
+
status,
|
|
678
|
+
info?.httpStatusText ?? '',
|
|
679
|
+
hdrs,
|
|
680
|
+
streamBytesReceived
|
|
681
|
+
);
|
|
682
|
+
}
|
|
683
|
+
});
|
|
684
|
+
builder.onFailed((_info, error) => {
|
|
685
|
+
const err = new Error(error.message);
|
|
686
|
+
if (inspectorId) {
|
|
687
|
+
NetworkInspector._recordEnd(inspectorId, 0, '', [], 0, error.message);
|
|
688
|
+
}
|
|
689
|
+
if (!responseResolved) {
|
|
690
|
+
responseResolved = true;
|
|
691
|
+
rejectResponse(err);
|
|
692
|
+
} else {
|
|
693
|
+
streamController.error(err);
|
|
694
|
+
}
|
|
695
|
+
});
|
|
696
|
+
builder.onCanceled(() => {
|
|
697
|
+
const err = createAbortError();
|
|
698
|
+
if (inspectorId) {
|
|
699
|
+
NetworkInspector._recordEnd(
|
|
700
|
+
inspectorId,
|
|
701
|
+
0,
|
|
702
|
+
'',
|
|
703
|
+
[],
|
|
704
|
+
0,
|
|
705
|
+
'Request canceled'
|
|
706
|
+
);
|
|
707
|
+
}
|
|
708
|
+
if (!responseResolved) {
|
|
709
|
+
responseResolved = true;
|
|
710
|
+
rejectResponse(err);
|
|
711
|
+
} else {
|
|
712
|
+
streamController.error(err);
|
|
713
|
+
}
|
|
714
|
+
});
|
|
715
|
+
const request = builder.build();
|
|
716
|
+
request.start();
|
|
717
|
+
});
|
|
704
718
|
}
|
|
705
719
|
export async function nitroFetch(input, init) {
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
720
|
+
// Merge defaults from NitroRequestClass if input is one
|
|
721
|
+
if (input instanceof NitroRequestClass) {
|
|
722
|
+
init = {
|
|
723
|
+
...init,
|
|
724
|
+
signal: init?.signal ?? input.signal,
|
|
725
|
+
redirect: init?.redirect ?? input.redirect,
|
|
726
|
+
cache: init?.cache ?? input.cache,
|
|
727
|
+
};
|
|
728
|
+
}
|
|
729
|
+
// Streaming is http(s)-only; local URLs fall through to nitroFetchRaw (check runs only when streaming).
|
|
730
|
+
if (init?.stream === true && isHttpUrl(getUrlString(input))) {
|
|
731
|
+
return nitroStreamFetch(input, init);
|
|
732
|
+
}
|
|
733
|
+
const redirectOption = init?.redirect ?? 'follow';
|
|
734
|
+
const res = await nitroFetchRaw(input, init);
|
|
735
|
+
// Handle redirect: "error" — if we got a 3xx back (followRedirects was false), throw
|
|
736
|
+
if (redirectOption === 'error' && res.status >= 300 && res.status < 400) {
|
|
737
|
+
throw new TypeError(
|
|
738
|
+
`redirect mode is "error": redirected request to "${res.url}"`
|
|
739
|
+
);
|
|
740
|
+
}
|
|
741
|
+
const response = new NitroResponse({
|
|
742
|
+
url: res.url,
|
|
743
|
+
status: res.status,
|
|
744
|
+
statusText: res.statusText,
|
|
745
|
+
ok: res.ok,
|
|
746
|
+
redirected: res.redirected,
|
|
747
|
+
headers: res.headers,
|
|
748
|
+
bodyBytes: res.bodyBytes,
|
|
749
|
+
bodyString: res.bodyString,
|
|
750
|
+
});
|
|
751
|
+
return response;
|
|
736
752
|
}
|
|
737
753
|
// Start a native prefetch. Requires a `prefetchKey` header on the request.
|
|
738
754
|
export async function prefetch(input, init) {
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
755
|
+
// If native implementation is not present yet, do nothing
|
|
756
|
+
const hasNative = typeof NitroFetchHybrid?.createClient === 'function';
|
|
757
|
+
if (!hasNative) return;
|
|
758
|
+
// Build NitroRequest and ensure prefetchKey header exists
|
|
759
|
+
const req = buildNitroRequest(input, init);
|
|
760
|
+
const hasKey =
|
|
761
|
+
req.headers?.some((h) => h.key.toLowerCase() === 'prefetchkey') ?? false;
|
|
762
|
+
// Also support passing prefetchKey via non-standard field on init
|
|
763
|
+
const fromInit = init?.prefetchKey;
|
|
764
|
+
if (!hasKey && fromInit) {
|
|
765
|
+
req.headers = (req.headers ?? []).concat([
|
|
766
|
+
{ key: 'prefetchKey', value: fromInit },
|
|
767
|
+
]);
|
|
768
|
+
}
|
|
769
|
+
const finalHasKey = req.headers?.some(
|
|
770
|
+
(h) => h.key.toLowerCase() === 'prefetchkey'
|
|
771
|
+
);
|
|
772
|
+
if (!finalHasKey) {
|
|
773
|
+
throw new Error('prefetch requires a "prefetchKey" header');
|
|
774
|
+
}
|
|
775
|
+
// Ensure client and call native prefetch
|
|
776
|
+
ensureClient();
|
|
777
|
+
if (!client || typeof client.prefetch !== 'function') return;
|
|
778
|
+
await client.prefetch(req);
|
|
762
779
|
}
|
|
763
780
|
const AUTOPREFETCH_QUEUE_KEY = 'nitrofetch_autoprefetch_queue';
|
|
764
781
|
// Persist a request to storage so native can prefetch it on app start.
|
|
765
782
|
export async function prefetchOnAppStart(input, init) {
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
783
|
+
// Resolve request and prefetchKey
|
|
784
|
+
const req = buildNitroRequest(input, init);
|
|
785
|
+
const fromHeader = req.headers?.find(
|
|
786
|
+
(h) => h.key.toLowerCase() === 'prefetchkey'
|
|
787
|
+
)?.value;
|
|
788
|
+
const fromInit = init?.prefetchKey;
|
|
789
|
+
const prefetchKey = fromHeader ?? fromInit;
|
|
790
|
+
if (!prefetchKey) {
|
|
791
|
+
throw new Error(
|
|
792
|
+
'prefetchOnAppStart requires a "prefetchKey" (header or init.prefetchKey)'
|
|
793
|
+
);
|
|
794
|
+
}
|
|
795
|
+
// Convert headers to a plain object for storage
|
|
796
|
+
const headersObj = (req.headers ?? []).reduce((acc, { key, value }) => {
|
|
797
|
+
acc[String(key)] = String(value);
|
|
798
|
+
return acc;
|
|
799
|
+
}, {});
|
|
800
|
+
const entry = {
|
|
801
|
+
url: req.url,
|
|
802
|
+
prefetchKey,
|
|
803
|
+
headers: headersObj,
|
|
804
|
+
};
|
|
805
|
+
if (req.method && req.method !== 'GET') entry.method = req.method;
|
|
806
|
+
if (req.bodyString !== undefined) entry.bodyString = req.bodyString;
|
|
807
|
+
if (typeof req.bodyBytes === 'string' && req.bodyBytes.length > 0)
|
|
808
|
+
entry.bodyBytes = req.bodyBytes;
|
|
809
|
+
if (req.bodyFormData && req.bodyFormData.length > 0)
|
|
810
|
+
entry.bodyFormData = req.bodyFormData;
|
|
811
|
+
if (typeof req.timeoutMs === 'number') entry.timeoutMs = req.timeoutMs;
|
|
812
|
+
if (req.followRedirects === false) entry.followRedirects = false;
|
|
813
|
+
if (typeof req.prefetchCacheTtlMs === 'number')
|
|
814
|
+
entry.prefetchCacheTtlMs = req.prefetchCacheTtlMs;
|
|
815
|
+
// Write or append to storage queue
|
|
816
|
+
try {
|
|
817
|
+
let arr = [];
|
|
799
818
|
try {
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
catch (e) {
|
|
818
|
-
console.warn('Failed to persist prefetch queue', e);
|
|
819
|
-
}
|
|
819
|
+
const raw = NativeStorageSingleton.getString(AUTOPREFETCH_QUEUE_KEY);
|
|
820
|
+
if (raw) arr = JSON.parse(raw);
|
|
821
|
+
if (!Array.isArray(arr)) arr = [];
|
|
822
|
+
} catch {
|
|
823
|
+
arr = [];
|
|
824
|
+
}
|
|
825
|
+
if (arr.some((e) => e && e.prefetchKey === prefetchKey)) {
|
|
826
|
+
arr = arr.filter((e) => e && e.prefetchKey !== prefetchKey);
|
|
827
|
+
}
|
|
828
|
+
arr.push(entry);
|
|
829
|
+
NativeStorageSingleton.setString(
|
|
830
|
+
AUTOPREFETCH_QUEUE_KEY,
|
|
831
|
+
JSON.stringify(arr)
|
|
832
|
+
);
|
|
833
|
+
} catch (e) {
|
|
834
|
+
console.warn('Failed to persist prefetch queue', e);
|
|
835
|
+
}
|
|
820
836
|
}
|
|
821
837
|
// Remove one entry (by prefetchKey) from the auto-prefetch queue.
|
|
822
838
|
export async function removeFromAutoPrefetch(prefetchKey) {
|
|
839
|
+
try {
|
|
840
|
+
let arr = [];
|
|
823
841
|
try {
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
}
|
|
843
|
-
catch (e) {
|
|
844
|
-
console.warn('Failed to remove from prefetch queue', e);
|
|
845
|
-
}
|
|
842
|
+
const raw = NativeStorageSingleton.getString(AUTOPREFETCH_QUEUE_KEY);
|
|
843
|
+
if (raw) arr = JSON.parse(raw);
|
|
844
|
+
if (!Array.isArray(arr)) arr = [];
|
|
845
|
+
} catch {
|
|
846
|
+
arr = [];
|
|
847
|
+
}
|
|
848
|
+
const next = arr.filter((e) => e && e.prefetchKey !== prefetchKey);
|
|
849
|
+
if (next.length === 0) {
|
|
850
|
+
NativeStorageSingleton.removeString(AUTOPREFETCH_QUEUE_KEY);
|
|
851
|
+
} else if (next.length !== arr.length) {
|
|
852
|
+
NativeStorageSingleton.setString(
|
|
853
|
+
AUTOPREFETCH_QUEUE_KEY,
|
|
854
|
+
JSON.stringify(next)
|
|
855
|
+
);
|
|
856
|
+
}
|
|
857
|
+
} catch (e) {
|
|
858
|
+
console.warn('Failed to remove from prefetch queue', e);
|
|
859
|
+
}
|
|
846
860
|
}
|
|
847
861
|
// Remove all entries from the auto-prefetch queue.
|
|
848
862
|
export async function removeAllFromAutoprefetch() {
|
|
849
|
-
|
|
863
|
+
NativeStorageSingleton.setString(AUTOPREFETCH_QUEUE_KEY, JSON.stringify([]));
|
|
850
864
|
}
|
|
851
865
|
export function __readAutoPrefetchQueue() {
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
return [];
|
|
861
|
-
}
|
|
866
|
+
try {
|
|
867
|
+
const raw = NativeStorageSingleton.getString(AUTOPREFETCH_QUEUE_KEY);
|
|
868
|
+
if (!raw) return [];
|
|
869
|
+
const parsed = JSON.parse(raw);
|
|
870
|
+
return Array.isArray(parsed) ? parsed : [];
|
|
871
|
+
} catch {
|
|
872
|
+
return [];
|
|
873
|
+
}
|
|
862
874
|
}
|
|
863
875
|
let nitroRuntime;
|
|
864
876
|
function ensureWorkletRuntime(name = 'nitro-fetch') {
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
}
|
|
877
|
+
try {
|
|
878
|
+
const { createWorkletRuntime } = require('react-native-worklets');
|
|
879
|
+
nitroRuntime = nitroRuntime ?? createWorkletRuntime(name);
|
|
880
|
+
return nitroRuntime;
|
|
881
|
+
} catch {
|
|
882
|
+
console.warn('react-native-worklets not available');
|
|
883
|
+
return undefined;
|
|
884
|
+
}
|
|
874
885
|
}
|
|
875
886
|
export async function nitroFetchOnWorklet(input, init, mapWorklet, options) {
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
});
|
|
887
|
+
const preferBytes = options?.preferBytes === true; // default true
|
|
888
|
+
let runOnRuntimeAsync;
|
|
889
|
+
let rt;
|
|
890
|
+
try {
|
|
891
|
+
rt = ensureWorkletRuntime(options?.runtimeName);
|
|
892
|
+
const worklets = require('react-native-worklets');
|
|
893
|
+
runOnRuntimeAsync = worklets.runOnRuntimeAsync;
|
|
894
|
+
} catch {
|
|
895
|
+
// Module not available
|
|
896
|
+
}
|
|
897
|
+
// Fallback: if runtime is not available, do the work on JS
|
|
898
|
+
if (!runOnRuntimeAsync || !rt) {
|
|
899
|
+
console.warn('nitroFetchOnWorklet: no runtime, mapping on JS thread');
|
|
900
|
+
const res = await nitroFetchRaw(input, init);
|
|
901
|
+
const payload = {
|
|
902
|
+
url: res.url,
|
|
903
|
+
status: res.status,
|
|
904
|
+
statusText: res.statusText,
|
|
905
|
+
ok: res.ok,
|
|
906
|
+
redirected: res.redirected,
|
|
907
|
+
headers: res.headers,
|
|
908
|
+
bodyBytes: preferBytes ? res.bodyBytes : undefined,
|
|
909
|
+
bodyString: preferBytes ? undefined : res.bodyString,
|
|
910
|
+
};
|
|
911
|
+
return mapWorklet(payload);
|
|
912
|
+
}
|
|
913
|
+
return await runOnRuntimeAsync(rt, () => {
|
|
914
|
+
'worklet';
|
|
915
|
+
const nitroFetchClient = NitroFetchHybrid.createClient();
|
|
916
|
+
const request = buildNitroRequestPure(input, init);
|
|
917
|
+
const res = nitroFetchClient.requestSync(request);
|
|
918
|
+
const payload = {
|
|
919
|
+
url: res.url,
|
|
920
|
+
status: res.status,
|
|
921
|
+
statusText: res.statusText,
|
|
922
|
+
ok: res.ok,
|
|
923
|
+
redirected: res.redirected,
|
|
924
|
+
headers: res.headers,
|
|
925
|
+
bodyBytes: preferBytes ? res.bodyBytes : undefined,
|
|
926
|
+
bodyString: preferBytes ? undefined : res.bodyString,
|
|
927
|
+
};
|
|
928
|
+
return mapWorklet(payload);
|
|
929
|
+
});
|
|
920
930
|
}
|
|
921
931
|
export { NitroHeaders } from './Headers';
|
|
922
932
|
export { NitroResponse } from './Response';
|