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.
Files changed (51) hide show
  1. package/android/build.gradle +12 -0
  2. package/android/src/main/java/com/margelo/nitro/nitrofetch/AutoPrefetcher.kt +258 -82
  3. package/android/src/main/java/com/margelo/nitro/nitrofetch/NitroFetchClient.kt +4 -4
  4. package/ios/NitroAutoPrefetcher.h +21 -0
  5. package/ios/NitroAutoPrefetcher.swift +292 -74
  6. package/ios/NitroFetchClient.swift +4 -3
  7. package/lib/module/CurlGenerator.js.map +2 -1
  8. package/lib/module/NitroCronet.nitro.js.map +1 -1
  9. package/lib/module/NitroFetch.nitro.js.map +1 -1
  10. package/lib/module/Request.js.map +2 -1
  11. package/lib/module/Response.js.map +2 -1
  12. package/lib/module/fetch.js +30 -11
  13. package/lib/module/fetch.js.map +1 -1
  14. package/lib/module/index.js +1 -1
  15. package/lib/module/index.js.map +1 -1
  16. package/lib/module/index.web.js +0 -1
  17. package/lib/module/index.web.js.map +2 -1
  18. package/lib/module/tokenRefresh.js +1 -4
  19. package/lib/module/tokenRefresh.js.map +2 -1
  20. package/lib/module/utf8.js.map +2 -1
  21. package/lib/typescript/src/NitroFetch.nitro.d.ts +1 -0
  22. package/lib/typescript/src/NitroFetch.nitro.d.ts.map +1 -1
  23. package/lib/typescript/src/fetch.d.ts +4 -1
  24. package/lib/typescript/src/fetch.d.ts.map +1 -1
  25. package/lib/typescript/src/index.d.ts +1 -1
  26. package/lib/typescript/src/index.d.ts.map +1 -1
  27. package/lib/typescript/src/tokenRefresh.d.ts +14 -0
  28. package/lib/typescript/src/tokenRefresh.d.ts.map +1 -1
  29. package/nitrogen/generated/android/c++/JNitroRequest.hpp +5 -1
  30. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrofetch/NitroRequest.kt +5 -2
  31. package/nitrogen/generated/ios/swift/NitroRequest.swift +19 -1
  32. package/nitrogen/generated/shared/c++/NitroRequest.hpp +5 -1
  33. package/package.json +1 -1
  34. package/src/CurlGenerator.js +28 -0
  35. package/src/Headers.js +119 -0
  36. package/src/HermesProfiler.js +20 -0
  37. package/src/NetworkInspector.js +175 -0
  38. package/src/NitroCronet.nitro.js +1 -0
  39. package/src/NitroFetch.nitro.js +1 -0
  40. package/src/NitroFetch.nitro.ts +3 -0
  41. package/src/NitroInstances.js +7 -0
  42. package/src/Request.js +176 -0
  43. package/src/Response.js +260 -0
  44. package/src/fetch.js +787 -0
  45. package/src/fetch.ts +55 -17
  46. package/src/index.js +25 -0
  47. package/src/index.tsx +1 -0
  48. package/src/index.web.js +106 -0
  49. package/src/tokenRefresh.js +102 -0
  50. package/src/tokenRefresh.ts +16 -0
  51. 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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-nitro-fetch",
3
- "version": "1.3.0",
3
+ "version": "1.3.2",
4
4
  "description": "Awesome Fetch :)",
5
5
  "main": "./lib/module/index.js",
6
6
  "module": "./lib/module/index.js",
@@ -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 {};
@@ -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
+ }