react-native-nitro-fetch 1.3.0 → 1.3.2
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 +12 -0
- package/android/src/main/java/com/margelo/nitro/nitrofetch/AutoPrefetcher.kt +258 -82
- package/android/src/main/java/com/margelo/nitro/nitrofetch/NitroFetchClient.kt +4 -4
- package/ios/NitroAutoPrefetcher.h +21 -0
- package/ios/NitroAutoPrefetcher.swift +292 -74
- package/ios/NitroFetchClient.swift +4 -3
- package/lib/module/CurlGenerator.js.map +2 -1
- package/lib/module/NitroCronet.nitro.js.map +1 -1
- package/lib/module/NitroFetch.nitro.js.map +1 -1
- package/lib/module/Request.js.map +2 -1
- package/lib/module/Response.js.map +2 -1
- package/lib/module/fetch.js +30 -11
- package/lib/module/fetch.js.map +1 -1
- package/lib/module/index.js +1 -1
- package/lib/module/index.js.map +1 -1
- package/lib/module/index.web.js +0 -1
- package/lib/module/index.web.js.map +2 -1
- package/lib/module/tokenRefresh.js +1 -4
- package/lib/module/tokenRefresh.js.map +2 -1
- package/lib/module/utf8.js.map +2 -1
- package/lib/typescript/src/NitroFetch.nitro.d.ts +1 -0
- package/lib/typescript/src/NitroFetch.nitro.d.ts.map +1 -1
- package/lib/typescript/src/fetch.d.ts +4 -1
- package/lib/typescript/src/fetch.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +1 -1
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/tokenRefresh.d.ts +14 -0
- package/lib/typescript/src/tokenRefresh.d.ts.map +1 -1
- package/nitrogen/generated/android/c++/JNitroRequest.hpp +5 -1
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrofetch/NitroRequest.kt +5 -2
- package/nitrogen/generated/ios/swift/NitroRequest.swift +19 -1
- package/nitrogen/generated/shared/c++/NitroRequest.hpp +5 -1
- package/package.json +1 -1
- package/src/CurlGenerator.js +28 -0
- package/src/Headers.js +119 -0
- package/src/HermesProfiler.js +20 -0
- package/src/NetworkInspector.js +175 -0
- package/src/NitroCronet.nitro.js +1 -0
- package/src/NitroFetch.nitro.js +1 -0
- package/src/NitroFetch.nitro.ts +3 -0
- package/src/NitroInstances.js +7 -0
- package/src/Request.js +176 -0
- package/src/Response.js +260 -0
- package/src/fetch.js +787 -0
- package/src/fetch.ts +55 -17
- package/src/index.js +25 -0
- package/src/index.tsx +1 -0
- package/src/index.web.js +106 -0
- package/src/tokenRefresh.js +102 -0
- package/src/tokenRefresh.ts +16 -0
- package/src/utf8.js +41 -0
|
@@ -57,11 +57,12 @@ namespace margelo::nitro::nitrofetch {
|
|
|
57
57
|
std::optional<std::vector<NitroFormDataPart>> bodyFormData SWIFT_PRIVATE;
|
|
58
58
|
std::optional<double> timeoutMs SWIFT_PRIVATE;
|
|
59
59
|
std::optional<bool> followRedirects SWIFT_PRIVATE;
|
|
60
|
+
std::optional<double> prefetchCacheTtlMs SWIFT_PRIVATE;
|
|
60
61
|
std::optional<std::string> requestId SWIFT_PRIVATE;
|
|
61
62
|
|
|
62
63
|
public:
|
|
63
64
|
NitroRequest() = default;
|
|
64
|
-
explicit NitroRequest(std::string url, std::optional<NitroRequestMethod> method, std::optional<std::vector<NitroHeader>> headers, std::optional<std::string> bodyString, std::optional<std::string> bodyBytes, std::optional<std::vector<NitroFormDataPart>> bodyFormData, std::optional<double> timeoutMs, std::optional<bool> followRedirects, std::optional<std::string> requestId): url(url), method(method), headers(headers), bodyString(bodyString), bodyBytes(bodyBytes), bodyFormData(bodyFormData), timeoutMs(timeoutMs), followRedirects(followRedirects), requestId(requestId) {}
|
|
65
|
+
explicit NitroRequest(std::string url, std::optional<NitroRequestMethod> method, std::optional<std::vector<NitroHeader>> headers, std::optional<std::string> bodyString, std::optional<std::string> bodyBytes, std::optional<std::vector<NitroFormDataPart>> bodyFormData, std::optional<double> timeoutMs, std::optional<bool> followRedirects, std::optional<double> prefetchCacheTtlMs, std::optional<std::string> requestId): url(url), method(method), headers(headers), bodyString(bodyString), bodyBytes(bodyBytes), bodyFormData(bodyFormData), timeoutMs(timeoutMs), followRedirects(followRedirects), prefetchCacheTtlMs(prefetchCacheTtlMs), requestId(requestId) {}
|
|
65
66
|
|
|
66
67
|
public:
|
|
67
68
|
friend bool operator==(const NitroRequest& lhs, const NitroRequest& rhs) = default;
|
|
@@ -85,6 +86,7 @@ namespace margelo::nitro {
|
|
|
85
86
|
JSIConverter<std::optional<std::vector<margelo::nitro::nitrofetch::NitroFormDataPart>>>::fromJSI(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "bodyFormData"))),
|
|
86
87
|
JSIConverter<std::optional<double>>::fromJSI(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "timeoutMs"))),
|
|
87
88
|
JSIConverter<std::optional<bool>>::fromJSI(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "followRedirects"))),
|
|
89
|
+
JSIConverter<std::optional<double>>::fromJSI(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "prefetchCacheTtlMs"))),
|
|
88
90
|
JSIConverter<std::optional<std::string>>::fromJSI(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "requestId")))
|
|
89
91
|
);
|
|
90
92
|
}
|
|
@@ -98,6 +100,7 @@ namespace margelo::nitro {
|
|
|
98
100
|
obj.setProperty(runtime, PropNameIDCache::get(runtime, "bodyFormData"), JSIConverter<std::optional<std::vector<margelo::nitro::nitrofetch::NitroFormDataPart>>>::toJSI(runtime, arg.bodyFormData));
|
|
99
101
|
obj.setProperty(runtime, PropNameIDCache::get(runtime, "timeoutMs"), JSIConverter<std::optional<double>>::toJSI(runtime, arg.timeoutMs));
|
|
100
102
|
obj.setProperty(runtime, PropNameIDCache::get(runtime, "followRedirects"), JSIConverter<std::optional<bool>>::toJSI(runtime, arg.followRedirects));
|
|
103
|
+
obj.setProperty(runtime, PropNameIDCache::get(runtime, "prefetchCacheTtlMs"), JSIConverter<std::optional<double>>::toJSI(runtime, arg.prefetchCacheTtlMs));
|
|
101
104
|
obj.setProperty(runtime, PropNameIDCache::get(runtime, "requestId"), JSIConverter<std::optional<std::string>>::toJSI(runtime, arg.requestId));
|
|
102
105
|
return obj;
|
|
103
106
|
}
|
|
@@ -117,6 +120,7 @@ namespace margelo::nitro {
|
|
|
117
120
|
if (!JSIConverter<std::optional<std::vector<margelo::nitro::nitrofetch::NitroFormDataPart>>>::canConvert(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "bodyFormData")))) return false;
|
|
118
121
|
if (!JSIConverter<std::optional<double>>::canConvert(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "timeoutMs")))) return false;
|
|
119
122
|
if (!JSIConverter<std::optional<bool>>::canConvert(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "followRedirects")))) return false;
|
|
123
|
+
if (!JSIConverter<std::optional<double>>::canConvert(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "prefetchCacheTtlMs")))) return false;
|
|
120
124
|
if (!JSIConverter<std::optional<std::string>>::canConvert(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "requestId")))) return false;
|
|
121
125
|
return true;
|
|
122
126
|
}
|
package/package.json
CHANGED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
function shellEscape(str) {
|
|
2
|
+
if (/^[a-zA-Z0-9._\-/:=@,+]+$/.test(str)) return str;
|
|
3
|
+
return "'" + str.replace(/'/g, "'\\''") + "'";
|
|
4
|
+
}
|
|
5
|
+
export function generateCurl(options) {
|
|
6
|
+
const parts = ['curl'];
|
|
7
|
+
if (options.method && options.method !== 'GET') {
|
|
8
|
+
parts.push('-X', shellEscape(options.method));
|
|
9
|
+
}
|
|
10
|
+
if (options.headers) {
|
|
11
|
+
for (const h of options.headers) {
|
|
12
|
+
if (h.key.toLowerCase() === 'prefetchkey') continue;
|
|
13
|
+
parts.push('-H', shellEscape(`${h.key}: ${h.value}`));
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
if (options.body) {
|
|
17
|
+
const maxLen = 10_000;
|
|
18
|
+
const truncated =
|
|
19
|
+
options.body.length > maxLen
|
|
20
|
+
? options.body.slice(0, maxLen) + '...[truncated]'
|
|
21
|
+
: options.body;
|
|
22
|
+
parts.push('-d', shellEscape(truncated));
|
|
23
|
+
}
|
|
24
|
+
if (options.verbose) parts.push('-v');
|
|
25
|
+
if (options.compressed) parts.push('--compressed');
|
|
26
|
+
parts.push(shellEscape(options.url));
|
|
27
|
+
return parts.join(' ');
|
|
28
|
+
}
|
package/src/Headers.js
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
function normalizeName(name) {
|
|
2
|
+
return name.toLowerCase();
|
|
3
|
+
}
|
|
4
|
+
export class NitroHeaders {
|
|
5
|
+
_map;
|
|
6
|
+
constructor(init) {
|
|
7
|
+
this._map = new Map();
|
|
8
|
+
if (!init) return;
|
|
9
|
+
if (init instanceof NitroHeaders) {
|
|
10
|
+
init._map.forEach((values, key) => {
|
|
11
|
+
this._map.set(key, [...values]);
|
|
12
|
+
});
|
|
13
|
+
} else if (
|
|
14
|
+
typeof init === 'object' &&
|
|
15
|
+
!Array.isArray(init) &&
|
|
16
|
+
typeof init.forEach === 'function' &&
|
|
17
|
+
typeof init.get === 'function'
|
|
18
|
+
) {
|
|
19
|
+
// Headers-like object (standard Headers or duck-typed)
|
|
20
|
+
init.forEach((value, key) => {
|
|
21
|
+
this._map.set(normalizeName(key), [value]);
|
|
22
|
+
});
|
|
23
|
+
} else if (Array.isArray(init)) {
|
|
24
|
+
for (const entry of init) {
|
|
25
|
+
if (Array.isArray(entry) && entry.length >= 2) {
|
|
26
|
+
// [string, string] tuple
|
|
27
|
+
const key = normalizeName(String(entry[0]));
|
|
28
|
+
const value = String(entry[1]);
|
|
29
|
+
const existing = this._map.get(key);
|
|
30
|
+
if (existing) existing.push(value);
|
|
31
|
+
else this._map.set(key, [value]);
|
|
32
|
+
} else if (
|
|
33
|
+
entry &&
|
|
34
|
+
typeof entry === 'object' &&
|
|
35
|
+
'key' in entry &&
|
|
36
|
+
'value' in entry
|
|
37
|
+
) {
|
|
38
|
+
// NitroHeader object
|
|
39
|
+
const key = normalizeName(entry.key);
|
|
40
|
+
const value = entry.value;
|
|
41
|
+
const existing = this._map.get(key);
|
|
42
|
+
if (existing) existing.push(value);
|
|
43
|
+
else this._map.set(key, [value]);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
} else if (typeof init === 'object' && init !== null) {
|
|
47
|
+
const keys = Object.keys(init);
|
|
48
|
+
for (let i = 0; i < keys.length; i++) {
|
|
49
|
+
const k = keys[i];
|
|
50
|
+
const v = init[k];
|
|
51
|
+
if (v !== undefined) {
|
|
52
|
+
this._map.set(normalizeName(k), [String(v)]);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
append(name, value) {
|
|
58
|
+
const key = normalizeName(name);
|
|
59
|
+
const existing = this._map.get(key);
|
|
60
|
+
if (existing) existing.push(value);
|
|
61
|
+
else this._map.set(key, [value]);
|
|
62
|
+
}
|
|
63
|
+
delete(name) {
|
|
64
|
+
this._map.delete(normalizeName(name));
|
|
65
|
+
}
|
|
66
|
+
get(name) {
|
|
67
|
+
const values = this._map.get(normalizeName(name));
|
|
68
|
+
if (!values || values.length === 0) return null;
|
|
69
|
+
return values.join(', ');
|
|
70
|
+
}
|
|
71
|
+
getSetCookie() {
|
|
72
|
+
return this._map.get('set-cookie') ?? [];
|
|
73
|
+
}
|
|
74
|
+
has(name) {
|
|
75
|
+
return this._map.has(normalizeName(name));
|
|
76
|
+
}
|
|
77
|
+
set(name, value) {
|
|
78
|
+
this._map.set(normalizeName(name), [value]);
|
|
79
|
+
}
|
|
80
|
+
forEach(callback, thisArg) {
|
|
81
|
+
const sortedKeys = Array.from(this._map.keys()).sort();
|
|
82
|
+
for (const key of sortedKeys) {
|
|
83
|
+
callback.call(thisArg, this._map.get(key).join(', '), key, this);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
entries() {
|
|
87
|
+
const map = this._map;
|
|
88
|
+
const sortedKeys = Array.from(map.keys()).sort();
|
|
89
|
+
function* gen() {
|
|
90
|
+
for (const key of sortedKeys) {
|
|
91
|
+
yield [key, map.get(key).join(', ')];
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return gen();
|
|
95
|
+
}
|
|
96
|
+
keys() {
|
|
97
|
+
const map = this._map;
|
|
98
|
+
const sortedKeys = Array.from(map.keys()).sort();
|
|
99
|
+
function* gen() {
|
|
100
|
+
for (const key of sortedKeys) {
|
|
101
|
+
yield key;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return gen();
|
|
105
|
+
}
|
|
106
|
+
values() {
|
|
107
|
+
const map = this._map;
|
|
108
|
+
const sortedKeys = Array.from(map.keys()).sort();
|
|
109
|
+
function* gen() {
|
|
110
|
+
for (const key of sortedKeys) {
|
|
111
|
+
yield map.get(key).join(', ');
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return gen();
|
|
115
|
+
}
|
|
116
|
+
[Symbol.iterator]() {
|
|
117
|
+
return this.entries();
|
|
118
|
+
}
|
|
119
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export async function profileFetch(fn, outputPath) {
|
|
2
|
+
const hermes = global.HermesInternal;
|
|
3
|
+
if (!hermes) {
|
|
4
|
+
const result = await fn();
|
|
5
|
+
return { result };
|
|
6
|
+
}
|
|
7
|
+
const path = outputPath ?? `/tmp/nitrofetch-profile-${Date.now()}.cpuprofile`;
|
|
8
|
+
hermes.enableSamplingProfiler();
|
|
9
|
+
try {
|
|
10
|
+
const result = await fn();
|
|
11
|
+
return { result, profilePath: path };
|
|
12
|
+
} finally {
|
|
13
|
+
hermes.disableSamplingProfiler();
|
|
14
|
+
try {
|
|
15
|
+
hermes.dumpSamplingProfiler(path);
|
|
16
|
+
} catch {
|
|
17
|
+
// Profile dump may fail on some platforms
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import { generateCurl } from './CurlGenerator';
|
|
2
|
+
class NetworkInspectorImpl {
|
|
3
|
+
_enabled = false;
|
|
4
|
+
_entries = [];
|
|
5
|
+
_maxEntries = 500;
|
|
6
|
+
_maxBodyCapture = 4096;
|
|
7
|
+
_listeners = new Set();
|
|
8
|
+
enable(options) {
|
|
9
|
+
this._enabled = true;
|
|
10
|
+
if (options?.maxEntries != null) this._maxEntries = options.maxEntries;
|
|
11
|
+
if (options?.maxBodyCapture != null)
|
|
12
|
+
this._maxBodyCapture = options.maxBodyCapture;
|
|
13
|
+
}
|
|
14
|
+
disable() {
|
|
15
|
+
this._enabled = false;
|
|
16
|
+
}
|
|
17
|
+
isEnabled() {
|
|
18
|
+
return this._enabled;
|
|
19
|
+
}
|
|
20
|
+
getEntries() {
|
|
21
|
+
return this._entries;
|
|
22
|
+
}
|
|
23
|
+
getHttpEntries() {
|
|
24
|
+
return this._entries.filter((e) => e.type === 'http');
|
|
25
|
+
}
|
|
26
|
+
getWebSocketEntries() {
|
|
27
|
+
return this._entries.filter((e) => e.type === 'websocket');
|
|
28
|
+
}
|
|
29
|
+
getEntry(id) {
|
|
30
|
+
return this._entries.find((e) => e.id === id);
|
|
31
|
+
}
|
|
32
|
+
clear() {
|
|
33
|
+
this._entries = [];
|
|
34
|
+
}
|
|
35
|
+
onEntry(callback) {
|
|
36
|
+
this._listeners.add(callback);
|
|
37
|
+
return () => {
|
|
38
|
+
this._listeners.delete(callback);
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
_notify(entry) {
|
|
42
|
+
for (const cb of this._listeners) {
|
|
43
|
+
try {
|
|
44
|
+
cb(entry);
|
|
45
|
+
} catch {
|
|
46
|
+
// swallow listener errors
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
_trimEntries() {
|
|
51
|
+
if (this._entries.length > this._maxEntries) {
|
|
52
|
+
this._entries.shift();
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
// --- HTTP recording ---
|
|
56
|
+
_recordStart(id, url, method, headers, body) {
|
|
57
|
+
if (!this._enabled) return;
|
|
58
|
+
const bodySize = body ? body.length : 0;
|
|
59
|
+
const entry = {
|
|
60
|
+
id,
|
|
61
|
+
type: 'http',
|
|
62
|
+
url,
|
|
63
|
+
method,
|
|
64
|
+
requestHeaders: headers.map((h) => ({ key: h.key, value: h.value })),
|
|
65
|
+
requestBody: body ? body.slice(0, this._maxBodyCapture) : undefined,
|
|
66
|
+
requestBodySize: bodySize,
|
|
67
|
+
status: 0,
|
|
68
|
+
statusText: '',
|
|
69
|
+
responseHeaders: [],
|
|
70
|
+
responseBodySize: 0,
|
|
71
|
+
startTime: performance.now(),
|
|
72
|
+
endTime: 0,
|
|
73
|
+
duration: 0,
|
|
74
|
+
curl: generateCurl({ url, method, headers, body }),
|
|
75
|
+
};
|
|
76
|
+
this._entries.push(entry);
|
|
77
|
+
this._trimEntries();
|
|
78
|
+
}
|
|
79
|
+
_recordEnd(id, status, statusText, headers, bodySize, error, responseBody) {
|
|
80
|
+
if (!this._enabled) return;
|
|
81
|
+
const entry = this._entries.find((e) => e.id === id && e.type === 'http');
|
|
82
|
+
if (!entry) return;
|
|
83
|
+
entry.status = status;
|
|
84
|
+
entry.statusText = statusText;
|
|
85
|
+
entry.responseHeaders = headers.map((h) => ({
|
|
86
|
+
key: h.key,
|
|
87
|
+
value: h.value,
|
|
88
|
+
}));
|
|
89
|
+
entry.responseBodySize = bodySize;
|
|
90
|
+
entry.endTime = performance.now();
|
|
91
|
+
entry.duration = entry.endTime - entry.startTime;
|
|
92
|
+
if (error) entry.error = error;
|
|
93
|
+
if (responseBody != null) {
|
|
94
|
+
entry.responseBody = responseBody.slice(0, this._maxBodyCapture);
|
|
95
|
+
}
|
|
96
|
+
this._notify(entry);
|
|
97
|
+
}
|
|
98
|
+
// --- WebSocket recording ---
|
|
99
|
+
_recordWsOpen(id, url, protocols, headers) {
|
|
100
|
+
if (!this._enabled) return;
|
|
101
|
+
const entry = {
|
|
102
|
+
id,
|
|
103
|
+
type: 'websocket',
|
|
104
|
+
url,
|
|
105
|
+
protocols,
|
|
106
|
+
requestHeaders: headers.map((h) => ({ key: h.key, value: h.value })),
|
|
107
|
+
startTime: performance.now(),
|
|
108
|
+
endTime: 0,
|
|
109
|
+
duration: 0,
|
|
110
|
+
readyState: 'CONNECTING',
|
|
111
|
+
messages: [],
|
|
112
|
+
messagesSent: 0,
|
|
113
|
+
messagesReceived: 0,
|
|
114
|
+
bytesSent: 0,
|
|
115
|
+
bytesReceived: 0,
|
|
116
|
+
};
|
|
117
|
+
this._entries.push(entry);
|
|
118
|
+
this._trimEntries();
|
|
119
|
+
this._notify(entry);
|
|
120
|
+
}
|
|
121
|
+
_recordWsConnected(id) {
|
|
122
|
+
if (!this._enabled) return;
|
|
123
|
+
const entry = this._entries.find(
|
|
124
|
+
(e) => e.id === id && e.type === 'websocket'
|
|
125
|
+
);
|
|
126
|
+
if (!entry) return;
|
|
127
|
+
entry.readyState = 'OPEN';
|
|
128
|
+
this._notify(entry);
|
|
129
|
+
}
|
|
130
|
+
_recordWsMessage(id, direction, data, size, isBinary) {
|
|
131
|
+
if (!this._enabled) return;
|
|
132
|
+
const entry = this._entries.find(
|
|
133
|
+
(e) => e.id === id && e.type === 'websocket'
|
|
134
|
+
);
|
|
135
|
+
if (!entry) return;
|
|
136
|
+
entry.messages.push({
|
|
137
|
+
direction,
|
|
138
|
+
data: data.slice(0, this._maxBodyCapture),
|
|
139
|
+
size,
|
|
140
|
+
isBinary,
|
|
141
|
+
timestamp: performance.now(),
|
|
142
|
+
});
|
|
143
|
+
if (direction === 'sent') {
|
|
144
|
+
entry.messagesSent++;
|
|
145
|
+
entry.bytesSent += size;
|
|
146
|
+
} else {
|
|
147
|
+
entry.messagesReceived++;
|
|
148
|
+
entry.bytesReceived += size;
|
|
149
|
+
}
|
|
150
|
+
this._notify(entry);
|
|
151
|
+
}
|
|
152
|
+
_recordWsClose(id, code, reason) {
|
|
153
|
+
if (!this._enabled) return;
|
|
154
|
+
const entry = this._entries.find(
|
|
155
|
+
(e) => e.id === id && e.type === 'websocket'
|
|
156
|
+
);
|
|
157
|
+
if (!entry) return;
|
|
158
|
+
entry.readyState = 'CLOSED';
|
|
159
|
+
entry.closeCode = code;
|
|
160
|
+
entry.closeReason = reason;
|
|
161
|
+
entry.endTime = performance.now();
|
|
162
|
+
entry.duration = entry.endTime - entry.startTime;
|
|
163
|
+
this._notify(entry);
|
|
164
|
+
}
|
|
165
|
+
_recordWsError(id, error) {
|
|
166
|
+
if (!this._enabled) return;
|
|
167
|
+
const entry = this._entries.find(
|
|
168
|
+
(e) => e.id === id && e.type === 'websocket'
|
|
169
|
+
);
|
|
170
|
+
if (!entry) return;
|
|
171
|
+
entry.error = error;
|
|
172
|
+
this._notify(entry);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
export const NetworkInspector = new NetworkInspectorImpl();
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/src/NitroFetch.nitro.ts
CHANGED
|
@@ -36,6 +36,9 @@ export interface NitroRequest {
|
|
|
36
36
|
// Controls
|
|
37
37
|
timeoutMs?: number;
|
|
38
38
|
followRedirects?: boolean; // default true
|
|
39
|
+
// Max age (ms) a prefetch cache entry is considered fresh at read time.
|
|
40
|
+
// Default 5000 when omitted. <= 0 disables cache hits.
|
|
41
|
+
prefetchCacheTtlMs?: number;
|
|
39
42
|
// Optional ID used for cancellation via cancelRequest()
|
|
40
43
|
requestId?: string;
|
|
41
44
|
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { NitroModules } from 'react-native-nitro-modules';
|
|
2
|
+
// Create singletons once per JS runtime
|
|
3
|
+
export const NitroFetch = NitroModules.createHybridObject('NitroFetch');
|
|
4
|
+
export const NativeStorage = NitroModules.createHybridObject('NativeStorage');
|
|
5
|
+
export const boxedNitroFetch = NitroModules.box(NitroFetch);
|
|
6
|
+
export const NitroCronetSingleton =
|
|
7
|
+
NitroModules.createHybridObject('NitroCronet');
|
package/src/Request.js
ADDED
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import { NitroHeaders } from './Headers';
|
|
2
|
+
import { stringToUTF8, utf8ToString } from './utf8';
|
|
3
|
+
export class NitroRequest {
|
|
4
|
+
url;
|
|
5
|
+
method;
|
|
6
|
+
headers;
|
|
7
|
+
redirect;
|
|
8
|
+
signal;
|
|
9
|
+
cache;
|
|
10
|
+
credentials;
|
|
11
|
+
mode;
|
|
12
|
+
referrer;
|
|
13
|
+
referrerPolicy;
|
|
14
|
+
integrity;
|
|
15
|
+
keepalive;
|
|
16
|
+
destination;
|
|
17
|
+
_body;
|
|
18
|
+
_bodyUsed = false;
|
|
19
|
+
constructor(input, init) {
|
|
20
|
+
if (input instanceof NitroRequest) {
|
|
21
|
+
// Clone from another NitroRequest
|
|
22
|
+
this.url = input.url;
|
|
23
|
+
this.method = (init?.method ?? input.method).toUpperCase();
|
|
24
|
+
this.headers = new NitroHeaders(
|
|
25
|
+
init?.headers
|
|
26
|
+
? init.headers instanceof NitroHeaders
|
|
27
|
+
? init.headers
|
|
28
|
+
: init.headers
|
|
29
|
+
: input.headers
|
|
30
|
+
);
|
|
31
|
+
this.redirect = init?.redirect ?? input.redirect;
|
|
32
|
+
this.signal = init?.signal ?? input.signal;
|
|
33
|
+
this.cache = init?.cache ?? input.cache;
|
|
34
|
+
this.credentials = init?.credentials ?? input.credentials;
|
|
35
|
+
this.mode = init?.mode ?? input.mode;
|
|
36
|
+
this.referrer = init?.referrer ?? input.referrer;
|
|
37
|
+
this.referrerPolicy = init?.referrerPolicy ?? input.referrerPolicy;
|
|
38
|
+
this.integrity = init?.integrity ?? input.integrity;
|
|
39
|
+
this.keepalive = init?.keepalive ?? input.keepalive;
|
|
40
|
+
this._body = init?.body !== undefined ? (init.body ?? null) : input._body;
|
|
41
|
+
} else if (
|
|
42
|
+
typeof input === 'object' &&
|
|
43
|
+
input !== null &&
|
|
44
|
+
'url' in input &&
|
|
45
|
+
'method' in input &&
|
|
46
|
+
'headers' in input &&
|
|
47
|
+
!(input instanceof URL)
|
|
48
|
+
) {
|
|
49
|
+
// Construct from a Request-like object (standard Request or duck-typed)
|
|
50
|
+
this.url = input.url;
|
|
51
|
+
this.method = (init?.method ?? input.method).toUpperCase();
|
|
52
|
+
this.headers = new NitroHeaders(
|
|
53
|
+
init?.headers
|
|
54
|
+
? init.headers instanceof NitroHeaders
|
|
55
|
+
? init.headers
|
|
56
|
+
: init.headers
|
|
57
|
+
: input.headers
|
|
58
|
+
);
|
|
59
|
+
this.redirect = init?.redirect ?? input.redirect ?? 'follow';
|
|
60
|
+
this.signal = init?.signal ?? input.signal;
|
|
61
|
+
this.cache = init?.cache ?? input.cache ?? 'default';
|
|
62
|
+
this.credentials =
|
|
63
|
+
init?.credentials ?? input.credentials ?? 'same-origin';
|
|
64
|
+
this.mode = init?.mode ?? input.mode ?? 'cors';
|
|
65
|
+
this.referrer = init?.referrer ?? input.referrer ?? 'about:client';
|
|
66
|
+
this.referrerPolicy = init?.referrerPolicy ?? input.referrerPolicy ?? '';
|
|
67
|
+
this.integrity = init?.integrity ?? input.integrity ?? '';
|
|
68
|
+
this.keepalive = init?.keepalive ?? input.keepalive ?? false;
|
|
69
|
+
this._body = init?.body ?? null;
|
|
70
|
+
} else {
|
|
71
|
+
this.url = String(input);
|
|
72
|
+
this.method = (init?.method ?? 'GET').toUpperCase();
|
|
73
|
+
this.headers = new NitroHeaders(
|
|
74
|
+
init?.headers
|
|
75
|
+
? init.headers instanceof NitroHeaders
|
|
76
|
+
? init.headers
|
|
77
|
+
: init.headers
|
|
78
|
+
: undefined
|
|
79
|
+
);
|
|
80
|
+
this.redirect = init?.redirect ?? 'follow';
|
|
81
|
+
this.signal = init?.signal ?? new AbortController().signal;
|
|
82
|
+
this.cache = init?.cache ?? 'default';
|
|
83
|
+
this.credentials = init?.credentials ?? 'same-origin';
|
|
84
|
+
this.mode = init?.mode ?? 'cors';
|
|
85
|
+
this.referrer = init?.referrer ?? 'about:client';
|
|
86
|
+
this.referrerPolicy = init?.referrerPolicy ?? '';
|
|
87
|
+
this.integrity = init?.integrity ?? '';
|
|
88
|
+
this.keepalive = init?.keepalive ?? false;
|
|
89
|
+
this._body = init?.body ?? null;
|
|
90
|
+
}
|
|
91
|
+
this.destination = '';
|
|
92
|
+
}
|
|
93
|
+
get bodyUsed() {
|
|
94
|
+
return this._bodyUsed;
|
|
95
|
+
}
|
|
96
|
+
get body() {
|
|
97
|
+
if (this._body == null) return null;
|
|
98
|
+
const bodyBytes = this._getBodyBytes();
|
|
99
|
+
if (!bodyBytes) return null;
|
|
100
|
+
return new ReadableStream({
|
|
101
|
+
start(controller) {
|
|
102
|
+
controller.enqueue(new Uint8Array(bodyBytes));
|
|
103
|
+
controller.close();
|
|
104
|
+
},
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
_throwIfBodyUsed() {
|
|
108
|
+
if (this._bodyUsed) {
|
|
109
|
+
throw new TypeError('Body has already been consumed.');
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
_getBodyBytes() {
|
|
113
|
+
if (this._body == null) return undefined;
|
|
114
|
+
if (typeof this._body === 'string') {
|
|
115
|
+
const encoded = stringToUTF8(this._body);
|
|
116
|
+
return encoded.buffer.slice(
|
|
117
|
+
encoded.byteOffset,
|
|
118
|
+
encoded.byteOffset + encoded.byteLength
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
if (this._body instanceof ArrayBuffer) return this._body;
|
|
122
|
+
if (ArrayBuffer.isView(this._body)) {
|
|
123
|
+
const view = this._body;
|
|
124
|
+
return view.buffer.slice(
|
|
125
|
+
view.byteOffset,
|
|
126
|
+
view.byteOffset + view.byteLength
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
return undefined;
|
|
130
|
+
}
|
|
131
|
+
_getBodyString() {
|
|
132
|
+
if (this._body == null) return '';
|
|
133
|
+
if (typeof this._body === 'string') return this._body;
|
|
134
|
+
const bytes = this._getBodyBytes();
|
|
135
|
+
if (bytes) return utf8ToString(new Uint8Array(bytes));
|
|
136
|
+
return '';
|
|
137
|
+
}
|
|
138
|
+
async text() {
|
|
139
|
+
this._throwIfBodyUsed();
|
|
140
|
+
this._bodyUsed = true;
|
|
141
|
+
return this._getBodyString();
|
|
142
|
+
}
|
|
143
|
+
async json() {
|
|
144
|
+
this._throwIfBodyUsed();
|
|
145
|
+
this._bodyUsed = true;
|
|
146
|
+
const t = this._getBodyString();
|
|
147
|
+
return JSON.parse(t || '{}');
|
|
148
|
+
}
|
|
149
|
+
async arrayBuffer() {
|
|
150
|
+
this._throwIfBodyUsed();
|
|
151
|
+
this._bodyUsed = true;
|
|
152
|
+
return this._getBodyBytes() ?? new ArrayBuffer(0);
|
|
153
|
+
}
|
|
154
|
+
async blob() {
|
|
155
|
+
this._throwIfBodyUsed();
|
|
156
|
+
this._bodyUsed = true;
|
|
157
|
+
const buffer = this._getBodyBytes() ?? new ArrayBuffer(0);
|
|
158
|
+
const contentType = this.headers.get('content-type') ?? '';
|
|
159
|
+
return new Blob([buffer], { type: contentType });
|
|
160
|
+
}
|
|
161
|
+
async bytes() {
|
|
162
|
+
this._throwIfBodyUsed();
|
|
163
|
+
this._bodyUsed = true;
|
|
164
|
+
const buffer = this._getBodyBytes() ?? new ArrayBuffer(0);
|
|
165
|
+
return new Uint8Array(buffer);
|
|
166
|
+
}
|
|
167
|
+
clone() {
|
|
168
|
+
if (this._bodyUsed) {
|
|
169
|
+
throw new TypeError('Cannot clone a Request whose body has been used.');
|
|
170
|
+
}
|
|
171
|
+
return new NitroRequest(this);
|
|
172
|
+
}
|
|
173
|
+
async formData() {
|
|
174
|
+
throw new TypeError('formData() is not supported in NitroRequest');
|
|
175
|
+
}
|
|
176
|
+
}
|