@multiplayer-app/session-recorder-react-native 1.2.27 → 1.2.29

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.
@@ -24,6 +24,33 @@ function _tryReadFetchBody({
24
24
  }
25
25
  return `[Fetch] Cannot read body of type ${Object.prototype.toString.call(body)}`;
26
26
  }
27
+ async function _tryReadResponseBody(response) {
28
+ try {
29
+ // Clone the response to avoid consuming the original stream.
30
+ const clonedResponse = response.clone();
31
+ const contentType = response.headers.get('content-type') || '';
32
+ if (contentType.includes('application/json')) {
33
+ const json = await clonedResponse.json();
34
+ return JSON.stringify(json);
35
+ } else if (contentType.includes('text/')) {
36
+ return await clonedResponse.text();
37
+ } else {
38
+ // For other content types, try text first, fallback to arrayBuffer
39
+ try {
40
+ return await clonedResponse.text();
41
+ } catch {
42
+ try {
43
+ const arrayBuffer = await clonedResponse.arrayBuffer();
44
+ return `[Fetch] Binary data (${arrayBuffer.byteLength} bytes)`;
45
+ } catch {
46
+ return '[Fetch] Unable to read response body';
47
+ }
48
+ }
49
+ }
50
+ } catch (error) {
51
+ return `[Fetch] Error reading response body: ${error instanceof Error ? error.message : 'Unknown error'}`;
52
+ }
53
+ }
27
54
  function _headersToObject(headers) {
28
55
  const result = {};
29
56
  headers.forEach((value, key) => {
@@ -32,6 +59,31 @@ function _headersToObject(headers) {
32
59
  return result;
33
60
  }
34
61
 
62
+ // Convert HeadersInit to a plain object without needing to construct a Request
63
+ function _headersInitToObject(headersInit) {
64
+ if (!headersInit) return {};
65
+
66
+ // Headers instance
67
+ if (typeof Headers !== 'undefined' && headersInit instanceof Headers) {
68
+ return _headersToObject(headersInit);
69
+ }
70
+ const result = {};
71
+
72
+ // Array of tuples
73
+ if (Array.isArray(headersInit)) {
74
+ for (const [key, value] of headersInit) {
75
+ result[String(key).toLowerCase()] = String(value);
76
+ }
77
+ return result;
78
+ }
79
+
80
+ // Record<string, string>
81
+ for (const [key, value] of Object.entries(headersInit)) {
82
+ result[String(key).toLowerCase()] = String(value);
83
+ }
84
+ return result;
85
+ }
86
+
35
87
  // Only patch fetch if available and safe to do so
36
88
  if (typeof fetch !== 'undefined' && typeof global !== 'undefined') {
37
89
  // Store original fetch
@@ -40,72 +92,52 @@ if (typeof fetch !== 'undefined' && typeof global !== 'undefined') {
40
92
  // Override fetch with safer implementation
41
93
  global.fetch = async function (input, init) {
42
94
  const networkRequest = {};
43
- try {
44
- // Capture request data safely
45
- const request = new Request(input, init);
46
- if (configs.recordRequestHeaders) {
47
- try {
48
- networkRequest.requestHeaders = _headersToObject(request.headers);
49
- } catch (error) {
50
- console.warn('[Fetch Patch] Failed to capture request headers:', error);
51
- }
95
+
96
+ // Capture request data
97
+ const inputIsRequest = typeof Request !== 'undefined' && input instanceof Request;
98
+ const safeToConstructRequest = !inputIsRequest || !input.bodyUsed;
99
+
100
+ // Only construct a new Request when it's safe (i.e., body not already used)
101
+ let requestForMetadata = null;
102
+ if (safeToConstructRequest) {
103
+ try {
104
+ requestForMetadata = new Request(input, init);
105
+ } catch {
106
+ requestForMetadata = null;
52
107
  }
53
- if (configs.shouldRecordBody && request.body) {
54
- try {
55
- const requestBody = _tryReadFetchBody({
56
- body: request.body
57
- });
58
- if (requestBody?.length && requestBody.length <= configs.maxCapturingHttpPayloadSize) {
59
- networkRequest.requestBody = requestBody;
60
- }
61
- } catch (error) {
62
- console.warn('[Fetch Patch] Failed to capture request body:', error);
108
+ }
109
+ if (configs.recordRequestHeaders) {
110
+ if (requestForMetadata) {
111
+ networkRequest.requestHeaders = _headersToObject(requestForMetadata.headers);
112
+ } else if (inputIsRequest) {
113
+ networkRequest.requestHeaders = _headersToObject(input.headers);
114
+ } else {
115
+ networkRequest.requestHeaders = _headersInitToObject(init?.headers);
116
+ }
117
+ }
118
+ if (configs.shouldRecordBody) {
119
+ const candidateBody = requestForMetadata ? requestForMetadata.body : inputIsRequest ? init?.body : init?.body;
120
+ if (!isNullish(candidateBody)) {
121
+ const requestBody = _tryReadFetchBody({
122
+ body: candidateBody
123
+ });
124
+ if (requestBody?.length && (typeof Blob !== 'undefined' ? new Blob([requestBody]).size <= configs.maxCapturingHttpPayloadSize : requestBody.length <= configs.maxCapturingHttpPayloadSize)) {
125
+ networkRequest.requestBody = requestBody;
63
126
  }
64
127
  }
65
-
128
+ }
129
+ try {
66
130
  // Make the actual fetch request
67
131
  const response = await originalFetch(input, init);
68
132
 
69
- // Capture response data safely
133
+ // Capture response data
70
134
  if (configs.recordResponseHeaders) {
71
- try {
72
- networkRequest.responseHeaders = _headersToObject(response.headers);
73
- } catch (error) {
74
- console.warn('[Fetch Patch] Failed to capture response headers:', error);
75
- }
135
+ networkRequest.responseHeaders = _headersToObject(response.headers);
76
136
  }
77
137
  if (configs.shouldRecordBody) {
78
- try {
79
- // Try to capture response body without cloning first
80
- let responseBody = null;
81
-
82
- // Check if response body is available and not consumed
83
- if (response.body && !response.bodyUsed) {
84
- try {
85
- // Try cloning first (might fail in React Native)
86
- const clonedResponse = response.clone();
87
- responseBody = await clonedResponse.text();
88
- } catch (cloneError) {
89
- // If cloning fails, try to read from original response
90
- // This is risky but we'll catch the error
91
- try {
92
- responseBody = await response.text();
93
- // If we get here, we consumed the body, so we need to recreate the response
94
- // This is a limitation - we can't both capture and preserve the body
95
- console.warn('[Fetch Patch] Response body consumed for capture - user code may not be able to read it');
96
- } catch (readError) {
97
- console.warn('[Fetch Patch] Failed to read response body:', readError);
98
- responseBody = '[Fetch] Unable to read response body';
99
- }
100
- }
101
- } else if (response.bodyUsed) {
102
- responseBody = '[Fetch] Response body already consumed';
103
- }
104
- if (responseBody?.length && responseBody.length <= configs.maxCapturingHttpPayloadSize) {
105
- networkRequest.responseBody = responseBody;
106
- }
107
- } catch (error) {
108
- console.warn('[Fetch Patch] Failed to capture response body:', error);
138
+ const responseBody = await _tryReadResponseBody(response);
139
+ if (responseBody?.length && (typeof Blob !== 'undefined' ? new Blob([responseBody]).size <= configs.maxCapturingHttpPayloadSize : responseBody.length <= configs.maxCapturingHttpPayloadSize)) {
140
+ networkRequest.responseBody = responseBody;
109
141
  }
110
142
  }
111
143
 
@@ -114,8 +146,13 @@ if (typeof fetch !== 'undefined' && typeof global !== 'undefined') {
114
146
  response.networkRequest = networkRequest;
115
147
  return response;
116
148
  } catch (error) {
117
- // Don't interfere with the original error - just log and rethrow
118
- console.warn('[Fetch Patch] Fetch failed:', error);
149
+ // Even if the fetch fails, we can still capture the request data
150
+ // Attach captured request data to the thrown error for downstream handling
151
+ // @ts-ignore
152
+ if (error && typeof error === 'object') {
153
+ // @ts-ignore
154
+ error.networkRequest = networkRequest;
155
+ }
119
156
  throw error;
120
157
  }
121
158
  };
@@ -1 +1 @@
1
- {"version":3,"names":["isFormData","isNullish","isObject","isString","formDataToQuery","configs","_tryReadFetchBody","body","JSON","stringify","Object","prototype","toString","call","_headersToObject","headers","result","forEach","value","key","fetch","global","originalFetch","input","init","networkRequest","request","Request","recordRequestHeaders","requestHeaders","error","console","warn","shouldRecordBody","requestBody","length","maxCapturingHttpPayloadSize","response","recordResponseHeaders","responseHeaders","responseBody","bodyUsed","clonedResponse","clone","text","cloneError","readError","setPrototypeOf","defineProperty","info"],"sourceRoot":"../../../src","sources":["patch/fetch.ts"],"mappings":"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","ignoreList":[]}
1
+ {"version":3,"names":["isFormData","isNullish","isObject","isString","formDataToQuery","configs","_tryReadFetchBody","body","JSON","stringify","Object","prototype","toString","call","_tryReadResponseBody","response","clonedResponse","clone","contentType","headers","get","includes","json","text","arrayBuffer","byteLength","error","Error","message","_headersToObject","result","forEach","value","key","_headersInitToObject","headersInit","Headers","Array","isArray","String","toLowerCase","entries","fetch","global","originalFetch","input","init","networkRequest","inputIsRequest","Request","safeToConstructRequest","bodyUsed","requestForMetadata","recordRequestHeaders","requestHeaders","shouldRecordBody","candidateBody","requestBody","length","Blob","size","maxCapturingHttpPayloadSize","recordResponseHeaders","responseHeaders","responseBody","setPrototypeOf","defineProperty","console","warn","info"],"sourceRoot":"../../../src","sources":["patch/fetch.ts"],"mappings":";;AAAA,SACEA,UAAU,EACVC,SAAS,EACTC,QAAQ,EACRC,QAAQ,QACH,wBAAqB;AAC5B,SAASC,eAAe,QAAQ,2BAAwB;AACxD,SAASC,OAAO,QAAQ,cAAW;AAEnC,SAASC,iBAAiBA,CAAC;EACzBC;AAGF,CAAC,EAAiB;EAChB,IAAIN,SAAS,CAACM,IAAI,CAAC,EAAE;IACnB,OAAO,IAAI;EACb;EAEA,IAAIJ,QAAQ,CAACI,IAAI,CAAC,EAAE;IAClB,OAAOA,IAAI;EACb;EAEA,IAAIP,UAAU,CAACO,IAAI,CAAC,EAAE;IACpB,OAAOH,eAAe,CAACG,IAAI,CAAC;EAC9B;EAEA,IAAIL,QAAQ,CAACK,IAAI,CAAC,EAAE;IAClB,IAAI;MACF,OAAOC,IAAI,CAACC,SAAS,CAACF,IAAI,CAAC;IAC7B,CAAC,CAAC,MAAM;MACN,OAAO,4CAA4C;IACrD;EACF;EAEA,OAAO,oCAAoCG,MAAM,CAACC,SAAS,CAACC,QAAQ,CAACC,IAAI,CAACN,IAAI,CAAC,EAAE;AACnF;AAEA,eAAeO,oBAAoBA,CAACC,QAAkB,EAA0B;EAC9E,IAAI;IACF;IACA,MAAMC,cAAc,GAAGD,QAAQ,CAACE,KAAK,CAAC,CAAC;IAEvC,MAAMC,WAAW,GAAGH,QAAQ,CAACI,OAAO,CAACC,GAAG,CAAC,cAAc,CAAC,IAAI,EAAE;IAC9D,IAAIF,WAAW,CAACG,QAAQ,CAAC,kBAAkB,CAAC,EAAE;MAC5C,MAAMC,IAAI,GAAG,MAAMN,cAAc,CAACM,IAAI,CAAC,CAAC;MACxC,OAAOd,IAAI,CAACC,SAAS,CAACa,IAAI,CAAC;IAC7B,CAAC,MAAM,IAAIJ,WAAW,CAACG,QAAQ,CAAC,OAAO,CAAC,EAAE;MACxC,OAAO,MAAML,cAAc,CAACO,IAAI,CAAC,CAAC;IACpC,CAAC,MAAM;MACL;MACA,IAAI;QACF,OAAO,MAAMP,cAAc,CAACO,IAAI,CAAC,CAAC;MACpC,CAAC,CAAC,MAAM;QACN,IAAI;UACF,MAAMC,WAAW,GAAG,MAAMR,cAAc,CAACQ,WAAW,CAAC,CAAC;UACtD,OAAO,wBAAwBA,WAAW,CAACC,UAAU,SAAS;QAChE,CAAC,CAAC,MAAM;UACN,OAAO,sCAAsC;QAC/C;MACF;IACF;EACF,CAAC,CAAC,OAAOC,KAAK,EAAE;IACd,OAAO,wCAAwCA,KAAK,YAAYC,KAAK,GAAGD,KAAK,CAACE,OAAO,GAAG,eAAe,EAAE;EAC3G;AACF;AAEA,SAASC,gBAAgBA,CAACV,OAAgB,EAA0B;EAClE,MAAMW,MAA8B,GAAG,CAAC,CAAC;EACzCX,OAAO,CAACY,OAAO,CAAC,CAACC,KAAK,EAAEC,GAAG,KAAK;IAC9BH,MAAM,CAACG,GAAG,CAAC,GAAGD,KAAK;EACrB,CAAC,CAAC;EACF,OAAOF,MAAM;AACf;;AAEA;AACA,SAASI,oBAAoBA,CAACC,WAAiB,EAA0B;EACvE,IAAI,CAACA,WAAW,EAAE,OAAO,CAAC,CAAC;;EAE3B;EACA,IAAI,OAAOC,OAAO,KAAK,WAAW,IAAID,WAAW,YAAYC,OAAO,EAAE;IACpE,OAAOP,gBAAgB,CAACM,WAAW,CAAC;EACtC;EAEA,MAAML,MAA8B,GAAG,CAAC,CAAC;;EAEzC;EACA,IAAIO,KAAK,CAACC,OAAO,CAACH,WAAW,CAAC,EAAE;IAC9B,KAAK,MAAM,CAACF,GAAG,EAAED,KAAK,CAAC,IAAIG,WAAW,EAAE;MACtCL,MAAM,CAACS,MAAM,CAACN,GAAG,CAAC,CAACO,WAAW,CAAC,CAAC,CAAC,GAAGD,MAAM,CAACP,KAAK,CAAC;IACnD;IACA,OAAOF,MAAM;EACf;;EAEA;EACA,KAAK,MAAM,CAACG,GAAG,EAAED,KAAK,CAAC,IAAItB,MAAM,CAAC+B,OAAO,CAACN,WAAqC,CAAC,EAAE;IAChFL,MAAM,CAACS,MAAM,CAACN,GAAG,CAAC,CAACO,WAAW,CAAC,CAAC,CAAC,GAAGD,MAAM,CAACP,KAAK,CAAC;EACnD;EAEA,OAAOF,MAAM;AACf;;AAEA;AACA,IAAI,OAAOY,KAAK,KAAK,WAAW,IAAI,OAAOC,MAAM,KAAK,WAAW,EAAE;EACjE;EACA,MAAMC,aAAa,GAAGD,MAAM,CAACD,KAAK;;EAElC;EACAC,MAAM,CAACD,KAAK,GAAG,gBACbG,KAAU,EACVC,IAAU,EACS;IACnB,MAAMC,cAKL,GAAG,CAAC,CAAC;;IAEN;IACA,MAAMC,cAAc,GAAG,OAAOC,OAAO,KAAK,WAAW,IAAIJ,KAAK,YAAYI,OAAO;IACjF,MAAMC,sBAAsB,GAAG,CAACF,cAAc,IAAI,CAAEH,KAAK,CAAaM,QAAQ;;IAE9E;IACA,IAAIC,kBAAkC,GAAG,IAAI;IAC7C,IAAIF,sBAAsB,EAAE;MAC1B,IAAI;QACFE,kBAAkB,GAAG,IAAIH,OAAO,CAACJ,KAAK,EAAiBC,IAAI,CAAC;MAC9D,CAAC,CAAC,MAAM;QACNM,kBAAkB,GAAG,IAAI;MAC3B;IACF;IAEA,IAAI/C,OAAO,CAACgD,oBAAoB,EAAE;MAChC,IAAID,kBAAkB,EAAE;QACtBL,cAAc,CAACO,cAAc,GAAGzB,gBAAgB,CAACuB,kBAAkB,CAACjC,OAAO,CAAC;MAC9E,CAAC,MAAM,IAAI6B,cAAc,EAAE;QACzBD,cAAc,CAACO,cAAc,GAAGzB,gBAAgB,CAAEgB,KAAK,CAAa1B,OAAO,CAAC;MAC9E,CAAC,MAAM;QACL4B,cAAc,CAACO,cAAc,GAAGpB,oBAAoB,CAACY,IAAI,EAAE3B,OAAO,CAAC;MACrE;IACF;IAEA,IAAId,OAAO,CAACkD,gBAAgB,EAAE;MAC5B,MAAMC,aAAqC,GAAGJ,kBAAkB,GAC3DA,kBAAkB,CAAS7C,IAAI,GAC/ByC,cAAc,GAAIF,IAAI,EAAUvC,IAAI,GAAIuC,IAAI,EAAUvC,IAAK;MAEhE,IAAI,CAACN,SAAS,CAACuD,aAAa,CAAC,EAAE;QAC7B,MAAMC,WAAW,GAAGnD,iBAAiB,CAAC;UACpCC,IAAI,EAAEiD;QACR,CAAC,CAAC;QAEF,IACEC,WAAW,EAAEC,MAAM,KAClB,OAAOC,IAAI,KAAK,WAAW,GACxB,IAAIA,IAAI,CAAC,CAACF,WAAW,CAAC,CAAC,CAACG,IAAI,IAAIvD,OAAO,CAACwD,2BAA2B,GACnEJ,WAAW,CAACC,MAAM,IAAIrD,OAAO,CAACwD,2BAA2B,CAAC,EAC9D;UACAd,cAAc,CAACU,WAAW,GAAGA,WAAW;QAC1C;MACF;IACF;IAEA,IAAI;MACF;MACA,MAAM1C,QAAQ,GAAG,MAAM6B,aAAa,CAACC,KAAK,EAAEC,IAAI,CAAC;;MAEjD;MACA,IAAIzC,OAAO,CAACyD,qBAAqB,EAAE;QACjCf,cAAc,CAACgB,eAAe,GAAGlC,gBAAgB,CAACd,QAAQ,CAACI,OAAO,CAAC;MACrE;MAEA,IAAId,OAAO,CAACkD,gBAAgB,EAAE;QAC5B,MAAMS,YAAY,GAAG,MAAMlD,oBAAoB,CAACC,QAAQ,CAAC;QAEzD,IACEiD,YAAY,EAAEN,MAAM,KACnB,OAAOC,IAAI,KAAK,WAAW,GACxB,IAAIA,IAAI,CAAC,CAACK,YAAY,CAAC,CAAC,CAACJ,IAAI,IAAIvD,OAAO,CAACwD,2BAA2B,GACpEG,YAAY,CAACN,MAAM,IAAIrD,OAAO,CAACwD,2BAA2B,CAAC,EAC/D;UACAd,cAAc,CAACiB,YAAY,GAAGA,YAAY;QAC5C;MACF;;MAEA;MACA;MACAjD,QAAQ,CAACgC,cAAc,GAAGA,cAAc;MAExC,OAAOhC,QAAQ;IACjB,CAAC,CAAC,OAAOW,KAAK,EAAE;MACd;MACA;MACA;MACA,IAAIA,KAAK,IAAI,OAAOA,KAAK,KAAK,QAAQ,EAAE;QACtC;QACAA,KAAK,CAACqB,cAAc,GAAGA,cAAc;MACvC;MACA,MAAMrB,KAAK;IACb;EACF,CAAC;;EAED;EACA,IAAI;IACFhB,MAAM,CAACuD,cAAc,CAACtB,MAAM,CAACD,KAAK,EAAEE,aAAa,CAAC;IAClDlC,MAAM,CAACwD,cAAc,CAACvB,MAAM,CAACD,KAAK,EAAE,MAAM,EAAE;MAAEV,KAAK,EAAE;IAAQ,CAAC,CAAC;IAC/DtB,MAAM,CAACwD,cAAc,CAACvB,MAAM,CAACD,KAAK,EAAE,QAAQ,EAAE;MAAEV,KAAK,EAAEY,aAAa,CAACc;IAAO,CAAC,CAAC;EAChF,CAAC,CAAC,OAAOhC,KAAK,EAAE;IACdyC,OAAO,CAACC,IAAI,CAAC,oDAAoD,EAAE1C,KAAK,CAAC;EAC3E;AACF,CAAC,MAAM;EACLyC,OAAO,CAACE,IAAI,CAAC,kFAAkF,CAAC;AAClG","ignoreList":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@multiplayer-app/session-recorder-react-native",
3
- "version": "1.2.27",
3
+ "version": "1.2.29",
4
4
  "description": "Multiplayer Fullstack Session Recorder for React Native",
5
5
  "author": {
6
6
  "name": "Multiplayer Software, Inc.",
@@ -107,7 +107,7 @@
107
107
  "typescript": "^5.9.2"
108
108
  },
109
109
  "dependencies": {
110
- "@multiplayer-app/session-recorder-common": "1.2.27",
110
+ "@multiplayer-app/session-recorder-common": "1.2.29",
111
111
  "@opentelemetry/core": "2.0.1",
112
112
  "@opentelemetry/exporter-trace-otlp-http": "0.203.0",
113
113
  "@opentelemetry/instrumentation": "0.203.0",
@@ -35,6 +35,35 @@ function _tryReadFetchBody({
35
35
  return `[Fetch] Cannot read body of type ${Object.prototype.toString.call(body)}`
36
36
  }
37
37
 
38
+ async function _tryReadResponseBody(response: Response): Promise<string | null> {
39
+ try {
40
+ // Clone the response to avoid consuming the original stream.
41
+ const clonedResponse = response.clone()
42
+
43
+ const contentType = response.headers.get('content-type') || ''
44
+ if (contentType.includes('application/json')) {
45
+ const json = await clonedResponse.json()
46
+ return JSON.stringify(json)
47
+ } else if (contentType.includes('text/')) {
48
+ return await clonedResponse.text()
49
+ } else {
50
+ // For other content types, try text first, fallback to arrayBuffer
51
+ try {
52
+ return await clonedResponse.text()
53
+ } catch {
54
+ try {
55
+ const arrayBuffer = await clonedResponse.arrayBuffer()
56
+ return `[Fetch] Binary data (${arrayBuffer.byteLength} bytes)`
57
+ } catch {
58
+ return '[Fetch] Unable to read response body'
59
+ }
60
+ }
61
+ }
62
+ } catch (error) {
63
+ return `[Fetch] Error reading response body: ${error instanceof Error ? error.message : 'Unknown error'}`
64
+ }
65
+ }
66
+
38
67
  function _headersToObject(headers: Headers): Record<string, string> {
39
68
  const result: Record<string, string> = {}
40
69
  headers.forEach((value, key) => {
@@ -43,6 +72,33 @@ function _headersToObject(headers: Headers): Record<string, string> {
43
72
  return result
44
73
  }
45
74
 
75
+ // Convert HeadersInit to a plain object without needing to construct a Request
76
+ function _headersInitToObject(headersInit?: any): Record<string, string> {
77
+ if (!headersInit) return {}
78
+
79
+ // Headers instance
80
+ if (typeof Headers !== 'undefined' && headersInit instanceof Headers) {
81
+ return _headersToObject(headersInit)
82
+ }
83
+
84
+ const result: Record<string, string> = {}
85
+
86
+ // Array of tuples
87
+ if (Array.isArray(headersInit)) {
88
+ for (const [key, value] of headersInit) {
89
+ result[String(key).toLowerCase()] = String(value)
90
+ }
91
+ return result
92
+ }
93
+
94
+ // Record<string, string>
95
+ for (const [key, value] of Object.entries(headersInit as Record<string, string>)) {
96
+ result[String(key).toLowerCase()] = String(value)
97
+ }
98
+
99
+ return result
100
+ }
101
+
46
102
  // Only patch fetch if available and safe to do so
47
103
  if (typeof fetch !== 'undefined' && typeof global !== 'undefined') {
48
104
  // Store original fetch
@@ -60,83 +116,70 @@ if (typeof fetch !== 'undefined' && typeof global !== 'undefined') {
60
116
  responseBody?: string
61
117
  } = {}
62
118
 
63
- try {
64
- // Capture request data safely
65
- const request = new Request(input, init)
119
+ // Capture request data
120
+ const inputIsRequest = typeof Request !== 'undefined' && input instanceof Request
121
+ const safeToConstructRequest = !inputIsRequest || !(input as Request).bodyUsed
122
+
123
+ // Only construct a new Request when it's safe (i.e., body not already used)
124
+ let requestForMetadata: Request | null = null
125
+ if (safeToConstructRequest) {
126
+ try {
127
+ requestForMetadata = new Request(input as RequestInfo, init)
128
+ } catch {
129
+ requestForMetadata = null
130
+ }
131
+ }
66
132
 
67
- if (configs.recordRequestHeaders) {
68
- try {
69
- networkRequest.requestHeaders = _headersToObject(request.headers)
70
- } catch (error) {
71
- console.warn('[Fetch Patch] Failed to capture request headers:', error)
72
- }
133
+ if (configs.recordRequestHeaders) {
134
+ if (requestForMetadata) {
135
+ networkRequest.requestHeaders = _headersToObject(requestForMetadata.headers)
136
+ } else if (inputIsRequest) {
137
+ networkRequest.requestHeaders = _headersToObject((input as Request).headers)
138
+ } else {
139
+ networkRequest.requestHeaders = _headersInitToObject(init?.headers)
73
140
  }
141
+ }
74
142
 
75
- if (configs.shouldRecordBody && request.body) {
76
- try {
77
- const requestBody = _tryReadFetchBody({
78
- body: request.body,
79
- })
80
-
81
- if (
82
- requestBody?.length &&
83
- requestBody.length <= configs.maxCapturingHttpPayloadSize
84
- ) {
85
- networkRequest.requestBody = requestBody
86
- }
87
- } catch (error) {
88
- console.warn('[Fetch Patch] Failed to capture request body:', error)
143
+ if (configs.shouldRecordBody) {
144
+ const candidateBody: any | null | undefined = requestForMetadata
145
+ ? (requestForMetadata as any).body as any
146
+ : (inputIsRequest ? (init as any)?.body : (init as any)?.body)
147
+
148
+ if (!isNullish(candidateBody)) {
149
+ const requestBody = _tryReadFetchBody({
150
+ body: candidateBody,
151
+ })
152
+
153
+ if (
154
+ requestBody?.length &&
155
+ (typeof Blob !== 'undefined'
156
+ ? new Blob([requestBody]).size <= configs.maxCapturingHttpPayloadSize
157
+ : requestBody.length <= configs.maxCapturingHttpPayloadSize)
158
+ ) {
159
+ networkRequest.requestBody = requestBody
89
160
  }
90
161
  }
162
+ }
91
163
 
164
+ try {
92
165
  // Make the actual fetch request
93
166
  const response = await originalFetch(input, init)
94
167
 
95
- // Capture response data safely
168
+ // Capture response data
96
169
  if (configs.recordResponseHeaders) {
97
- try {
98
- networkRequest.responseHeaders = _headersToObject(response.headers)
99
- } catch (error) {
100
- console.warn('[Fetch Patch] Failed to capture response headers:', error)
101
- }
170
+ networkRequest.responseHeaders = _headersToObject(response.headers)
102
171
  }
103
172
 
104
173
  if (configs.shouldRecordBody) {
105
- try {
106
- // Try to capture response body without cloning first
107
- let responseBody: string | null = null
108
-
109
- // Check if response body is available and not consumed
110
- if (response.body && !response.bodyUsed) {
111
- try {
112
- // Try cloning first (might fail in React Native)
113
- const clonedResponse = response.clone()
114
- responseBody = await clonedResponse.text()
115
- } catch (cloneError) {
116
- // If cloning fails, try to read from original response
117
- // This is risky but we'll catch the error
118
- try {
119
- responseBody = await response.text()
120
- // If we get here, we consumed the body, so we need to recreate the response
121
- // This is a limitation - we can't both capture and preserve the body
122
- console.warn('[Fetch Patch] Response body consumed for capture - user code may not be able to read it')
123
- } catch (readError) {
124
- console.warn('[Fetch Patch] Failed to read response body:', readError)
125
- responseBody = '[Fetch] Unable to read response body'
126
- }
127
- }
128
- } else if (response.bodyUsed) {
129
- responseBody = '[Fetch] Response body already consumed'
130
- }
131
-
132
- if (
133
- responseBody?.length &&
134
- responseBody.length <= configs.maxCapturingHttpPayloadSize
135
- ) {
136
- networkRequest.responseBody = responseBody
137
- }
138
- } catch (error) {
139
- console.warn('[Fetch Patch] Failed to capture response body:', error)
174
+ const responseBody = await _tryReadResponseBody(response)
175
+
176
+ if (
177
+ responseBody?.length &&
178
+ (typeof Blob !== 'undefined'
179
+ ? new Blob([responseBody]).size <= configs.maxCapturingHttpPayloadSize
180
+ : responseBody.length <= configs.maxCapturingHttpPayloadSize)
181
+ ) {
182
+ networkRequest.responseBody = responseBody
140
183
  }
141
184
  }
142
185
 
@@ -146,8 +189,13 @@ if (typeof fetch !== 'undefined' && typeof global !== 'undefined') {
146
189
 
147
190
  return response
148
191
  } catch (error) {
149
- // Don't interfere with the original error - just log and rethrow
150
- console.warn('[Fetch Patch] Fetch failed:', error)
192
+ // Even if the fetch fails, we can still capture the request data
193
+ // Attach captured request data to the thrown error for downstream handling
194
+ // @ts-ignore
195
+ if (error && typeof error === 'object') {
196
+ // @ts-ignore
197
+ error.networkRequest = networkRequest
198
+ }
151
199
  throw error
152
200
  }
153
201
  }