react-native-nitro-fetch 1.3.2 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/NitroFetch.podspec +1 -3
  2. package/README.md +39 -11
  3. package/android/src/main/java/com/margelo/nitro/nitrofetch/AutoPrefetcher.kt +6 -2
  4. package/android/src/main/java/com/margelo/nitro/nitrofetch/DevToolsReporterImpl.kt +27 -36
  5. package/android/src/main/java/com/margelo/nitro/nitrofetch/NitroFetchClient.kt +45 -0
  6. package/ios/NitroAutoPrefetcher.swift +4 -0
  7. package/ios/NitroDevToolsReporter.mm +37 -31
  8. package/ios/NitroFetchClient.swift +56 -0
  9. package/lib/module/CurlGenerator.js.map +1 -2
  10. package/lib/module/Headers.js.map +2 -1
  11. package/lib/module/HermesProfiler.js.map +2 -1
  12. package/lib/module/NetworkInspector.js +1 -5
  13. package/lib/module/NetworkInspector.js.map +2 -1
  14. package/lib/module/NitroCronet.nitro.js.map +2 -1
  15. package/lib/module/NitroFetch.nitro.js.map +2 -1
  16. package/lib/module/NitroInstances.js.map +2 -1
  17. package/lib/module/Request.js.map +1 -2
  18. package/lib/module/Response.js.map +2 -2
  19. package/lib/module/fetch.js +147 -1
  20. package/lib/module/fetch.js.map +1 -1
  21. package/lib/module/index.web.js +1 -0
  22. package/lib/module/index.web.js.map +1 -2
  23. package/lib/module/tokenRefresh.js.map +1 -2
  24. package/lib/module/utf8.js.map +1 -2
  25. package/lib/typescript/src/fetch.d.ts.map +1 -1
  26. package/package.json +1 -1
  27. package/src/CurlGenerator.js +26 -23
  28. package/src/Headers.js +116 -108
  29. package/src/HermesProfiler.js +18 -16
  30. package/src/NetworkInspector.js +179 -171
  31. package/src/NitroInstances.js +1 -2
  32. package/src/Request.js +164 -167
  33. package/src/Response.js +242 -244
  34. package/src/fetch.js +842 -706
  35. package/src/fetch.ts +170 -1
  36. package/src/index.js +2 -17
  37. package/src/index.web.js +67 -69
  38. package/src/tokenRefresh.js +77 -75
  39. package/src/utf8.js +27 -28
package/src/Headers.js CHANGED
@@ -1,119 +1,127 @@
1
1
  function normalizeName(name) {
2
- return name.toLowerCase();
2
+ return name.toLowerCase();
3
3
  }
4
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]);
5
+ _map;
6
+ constructor(init) {
7
+ this._map = new Map();
8
+ if (!init)
9
+ return;
10
+ if (init instanceof NitroHeaders) {
11
+ init._map.forEach((values, key) => {
12
+ this._map.set(key, [...values]);
13
+ });
44
14
  }
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)]);
15
+ else if (typeof init === 'object' &&
16
+ !Array.isArray(init) &&
17
+ typeof init.forEach === 'function' &&
18
+ typeof init.get === 'function') {
19
+ // Headers-like object (standard Headers or duck-typed)
20
+ init.forEach((value, key) => {
21
+ this._map.set(normalizeName(key), [value]);
22
+ });
53
23
  }
54
- }
24
+ else if (Array.isArray(init)) {
25
+ for (const entry of init) {
26
+ if (Array.isArray(entry) && entry.length >= 2) {
27
+ // [string, string] tuple
28
+ const key = normalizeName(String(entry[0]));
29
+ const value = String(entry[1]);
30
+ const existing = this._map.get(key);
31
+ if (existing)
32
+ existing.push(value);
33
+ else
34
+ this._map.set(key, [value]);
35
+ }
36
+ else if (entry &&
37
+ typeof entry === 'object' &&
38
+ 'key' in entry &&
39
+ 'value' in entry) {
40
+ // NitroHeader object
41
+ const key = normalizeName(entry.key);
42
+ const value = entry.value;
43
+ const existing = this._map.get(key);
44
+ if (existing)
45
+ existing.push(value);
46
+ else
47
+ this._map.set(key, [value]);
48
+ }
49
+ }
50
+ }
51
+ else if (typeof init === 'object' && init !== null) {
52
+ const keys = Object.keys(init);
53
+ for (let i = 0; i < keys.length; i++) {
54
+ const k = keys[i];
55
+ const v = init[k];
56
+ if (v !== undefined) {
57
+ this._map.set(normalizeName(k), [String(v)]);
58
+ }
59
+ }
60
+ }
61
+ }
62
+ append(name, value) {
63
+ const key = normalizeName(name);
64
+ const existing = this._map.get(key);
65
+ if (existing)
66
+ existing.push(value);
67
+ else
68
+ this._map.set(key, [value]);
69
+ }
70
+ delete(name) {
71
+ this._map.delete(normalizeName(name));
55
72
  }
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);
73
+ get(name) {
74
+ const values = this._map.get(normalizeName(name));
75
+ if (!values || values.length === 0)
76
+ return null;
77
+ return values.join(', ');
84
78
  }
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
- }
79
+ getSetCookie() {
80
+ return this._map.get('set-cookie') ?? [];
93
81
  }
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
- }
82
+ has(name) {
83
+ return this._map.has(normalizeName(name));
84
+ }
85
+ set(name, value) {
86
+ this._map.set(normalizeName(name), [value]);
87
+ }
88
+ forEach(callback, thisArg) {
89
+ const sortedKeys = Array.from(this._map.keys()).sort();
90
+ for (const key of sortedKeys) {
91
+ callback.call(thisArg, this._map.get(key).join(', '), key, this);
92
+ }
93
+ }
94
+ entries() {
95
+ const map = this._map;
96
+ const sortedKeys = Array.from(map.keys()).sort();
97
+ function* gen() {
98
+ for (const key of sortedKeys) {
99
+ yield [key, map.get(key).join(', ')];
100
+ }
101
+ }
102
+ return gen();
103
+ }
104
+ keys() {
105
+ const map = this._map;
106
+ const sortedKeys = Array.from(map.keys()).sort();
107
+ function* gen() {
108
+ for (const key of sortedKeys) {
109
+ yield key;
110
+ }
111
+ }
112
+ return gen();
113
+ }
114
+ values() {
115
+ const map = this._map;
116
+ const sortedKeys = Array.from(map.keys()).sort();
117
+ function* gen() {
118
+ for (const key of sortedKeys) {
119
+ yield map.get(key).join(', ');
120
+ }
121
+ }
122
+ return gen();
103
123
  }
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
- }
124
+ [Symbol.iterator]() {
125
+ return this.entries();
113
126
  }
114
- return gen();
115
- }
116
- [Symbol.iterator]() {
117
- return this.entries();
118
- }
119
127
  }
@@ -1,20 +1,22 @@
1
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();
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();
14
9
  try {
15
- hermes.dumpSamplingProfiler(path);
16
- } catch {
17
- // Profile dump may fail on some platforms
10
+ const result = await fn();
11
+ return { result, profilePath: path };
12
+ }
13
+ finally {
14
+ hermes.disableSamplingProfiler();
15
+ try {
16
+ hermes.dumpSamplingProfiler(path);
17
+ }
18
+ catch {
19
+ // Profile dump may fail on some platforms
20
+ }
18
21
  }
19
- }
20
22
  }
@@ -1,175 +1,183 @@
1
1
  import { generateCurl } from './CurlGenerator';
2
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
- }
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)
11
+ this._maxEntries = options.maxEntries;
12
+ if (options?.maxBodyCapture != null)
13
+ this._maxBodyCapture = options.maxBodyCapture;
14
+ }
15
+ disable() {
16
+ this._enabled = false;
17
+ }
18
+ isEnabled() {
19
+ return this._enabled;
20
+ }
21
+ getEntries() {
22
+ return this._entries;
23
+ }
24
+ getHttpEntries() {
25
+ return this._entries.filter((e) => e.type === 'http');
26
+ }
27
+ getWebSocketEntries() {
28
+ return this._entries.filter((e) => e.type === 'websocket');
29
+ }
30
+ getEntry(id) {
31
+ return this._entries.find((e) => e.id === id);
32
+ }
33
+ clear() {
34
+ this._entries = [];
35
+ }
36
+ onEntry(callback) {
37
+ this._listeners.add(callback);
38
+ return () => {
39
+ this._listeners.delete(callback);
40
+ };
41
+ }
42
+ _notify(entry) {
43
+ for (const cb of this._listeners) {
44
+ try {
45
+ cb(entry);
46
+ }
47
+ catch {
48
+ // swallow listener errors
49
+ }
50
+ }
51
+ }
52
+ _trimEntries() {
53
+ if (this._entries.length > this._maxEntries) {
54
+ this._entries.shift();
55
+ }
56
+ }
57
+ // --- HTTP recording ---
58
+ _recordStart(id, url, method, headers, body) {
59
+ if (!this._enabled)
60
+ return;
61
+ const bodySize = body ? body.length : 0;
62
+ const entry = {
63
+ id,
64
+ type: 'http',
65
+ url,
66
+ method,
67
+ requestHeaders: headers.map((h) => ({ key: h.key, value: h.value })),
68
+ requestBody: body ? body.slice(0, this._maxBodyCapture) : undefined,
69
+ requestBodySize: bodySize,
70
+ status: 0,
71
+ statusText: '',
72
+ responseHeaders: [],
73
+ responseBodySize: 0,
74
+ startTime: performance.now(),
75
+ endTime: 0,
76
+ duration: 0,
77
+ curl: generateCurl({ url, method, headers, body }),
78
+ };
79
+ this._entries.push(entry);
80
+ this._trimEntries();
81
+ }
82
+ _recordEnd(id, status, statusText, headers, bodySize, error, responseBody) {
83
+ if (!this._enabled)
84
+ return;
85
+ const entry = this._entries.find((e) => e.id === id && e.type === 'http');
86
+ if (!entry)
87
+ return;
88
+ entry.status = status;
89
+ entry.statusText = statusText;
90
+ entry.responseHeaders = headers.map((h) => ({
91
+ key: h.key,
92
+ value: h.value,
93
+ }));
94
+ entry.responseBodySize = bodySize;
95
+ entry.endTime = performance.now();
96
+ entry.duration = entry.endTime - entry.startTime;
97
+ if (error)
98
+ entry.error = error;
99
+ if (responseBody != null) {
100
+ entry.responseBody = responseBody.slice(0, this._maxBodyCapture);
101
+ }
102
+ this._notify(entry);
103
+ }
104
+ // --- WebSocket recording ---
105
+ _recordWsOpen(id, url, protocols, headers) {
106
+ if (!this._enabled)
107
+ return;
108
+ const entry = {
109
+ id,
110
+ type: 'websocket',
111
+ url,
112
+ protocols,
113
+ requestHeaders: headers.map((h) => ({ key: h.key, value: h.value })),
114
+ startTime: performance.now(),
115
+ endTime: 0,
116
+ duration: 0,
117
+ readyState: 'CONNECTING',
118
+ messages: [],
119
+ messagesSent: 0,
120
+ messagesReceived: 0,
121
+ bytesSent: 0,
122
+ bytesReceived: 0,
123
+ };
124
+ this._entries.push(entry);
125
+ this._trimEntries();
126
+ this._notify(entry);
127
+ }
128
+ _recordWsConnected(id) {
129
+ if (!this._enabled)
130
+ return;
131
+ const entry = this._entries.find((e) => e.id === id && e.type === 'websocket');
132
+ if (!entry)
133
+ return;
134
+ entry.readyState = 'OPEN';
135
+ this._notify(entry);
136
+ }
137
+ _recordWsMessage(id, direction, data, size, isBinary) {
138
+ if (!this._enabled)
139
+ return;
140
+ const entry = this._entries.find((e) => e.id === id && e.type === 'websocket');
141
+ if (!entry)
142
+ return;
143
+ entry.messages.push({
144
+ direction,
145
+ data: data.slice(0, this._maxBodyCapture),
146
+ size,
147
+ isBinary,
148
+ timestamp: performance.now(),
149
+ });
150
+ if (direction === 'sent') {
151
+ entry.messagesSent++;
152
+ entry.bytesSent += size;
153
+ }
154
+ else {
155
+ entry.messagesReceived++;
156
+ entry.bytesReceived += size;
157
+ }
158
+ this._notify(entry);
159
+ }
160
+ _recordWsClose(id, code, reason) {
161
+ if (!this._enabled)
162
+ return;
163
+ const entry = this._entries.find((e) => e.id === id && e.type === 'websocket');
164
+ if (!entry)
165
+ return;
166
+ entry.readyState = 'CLOSED';
167
+ entry.closeCode = code;
168
+ entry.closeReason = reason;
169
+ entry.endTime = performance.now();
170
+ entry.duration = entry.endTime - entry.startTime;
171
+ this._notify(entry);
172
+ }
173
+ _recordWsError(id, error) {
174
+ if (!this._enabled)
175
+ return;
176
+ const entry = this._entries.find((e) => e.id === id && e.type === 'websocket');
177
+ if (!entry)
178
+ return;
179
+ entry.error = error;
180
+ this._notify(entry);
181
+ }
174
182
  }
175
183
  export const NetworkInspector = new NetworkInspectorImpl();
@@ -3,5 +3,4 @@ import { NitroModules } from 'react-native-nitro-modules';
3
3
  export const NitroFetch = NitroModules.createHybridObject('NitroFetch');
4
4
  export const NativeStorage = NitroModules.createHybridObject('NativeStorage');
5
5
  export const boxedNitroFetch = NitroModules.box(NitroFetch);
6
- export const NitroCronetSingleton =
7
- NitroModules.createHybridObject('NitroCronet');
6
+ export const NitroCronetSingleton = NitroModules.createHybridObject('NitroCronet');