@@ -3,6 +3,7 @@ import { logger } from '@sentry/utils';
3
3
4
4
import type {
5
5
FetchHint ,
6
+ NetworkMetaWarning ,
6
7
ReplayContainer ,
7
8
ReplayNetworkOptions ,
8
9
ReplayNetworkRequestData ,
@@ -16,6 +17,7 @@ import {
16
17
getBodySize ,
17
18
getBodyString ,
18
19
makeNetworkReplayBreadcrumb ,
20
+ mergeWarning ,
19
21
parseContentLengthHeader ,
20
22
urlMatches ,
21
23
} from './networkUtils' ;
@@ -118,17 +120,24 @@ function _getRequestInfo(
118
120
119
121
// We only want to transmit string or string-like bodies
120
122
const requestBody = _getFetchRequestArgBody ( input ) ;
121
- const bodyStr = getBodyString ( requestBody ) ;
122
- return buildNetworkRequestOrResponse ( headers , requestBodySize , bodyStr ) ;
123
+ const [ bodyStr , warning ] = getBodyString ( requestBody ) ;
124
+ const data = buildNetworkRequestOrResponse ( headers , requestBodySize , bodyStr ) ;
125
+
126
+ if ( warning ) {
127
+ return mergeWarning ( data , warning ) ;
128
+ }
129
+
130
+ return data ;
123
131
}
124
132
125
- async function _getResponseInfo (
133
+ /** Exported only for tests. */
134
+ export async function _getResponseInfo (
126
135
captureDetails : boolean ,
127
136
{
128
137
networkCaptureBodies,
129
138
textEncoder,
130
139
networkResponseHeaders,
131
- } : ReplayNetworkOptions & {
140
+ } : Pick < ReplayNetworkOptions , 'networkCaptureBodies' | 'networkResponseHeaders' > & {
132
141
textEncoder : TextEncoderInternal ;
133
142
} ,
134
143
response : Response | undefined ,
@@ -144,12 +153,39 @@ async function _getResponseInfo(
144
153
return buildNetworkRequestOrResponse ( headers , responseBodySize , undefined ) ;
145
154
}
146
155
147
- // Only clone the response if we need to
148
- try {
149
- // We have to clone this, as the body can only be read once
150
- const res = response . clone ( ) ;
151
- const bodyText = await _parseFetchBody ( res ) ;
156
+ const [ bodyText , warning ] = await _parseFetchResponseBody ( response ) ;
157
+ const result = getResponseData ( bodyText , {
158
+ networkCaptureBodies,
159
+ textEncoder,
160
+ responseBodySize,
161
+ captureDetails,
162
+ headers,
163
+ } ) ;
164
+
165
+ if ( warning ) {
166
+ return mergeWarning ( result , warning ) ;
167
+ }
152
168
169
+ return result ;
170
+ }
171
+
172
+ function getResponseData (
173
+ bodyText : string | undefined ,
174
+ {
175
+ networkCaptureBodies,
176
+ textEncoder,
177
+ responseBodySize,
178
+ captureDetails,
179
+ headers,
180
+ } : {
181
+ captureDetails : boolean ;
182
+ networkCaptureBodies : boolean ;
183
+ responseBodySize : number | undefined ;
184
+ headers : Record < string , string > ;
185
+ textEncoder : TextEncoderInternal ;
186
+ } ,
187
+ ) : ReplayNetworkRequestOrResponse | undefined {
188
+ try {
153
189
const size =
154
190
bodyText && bodyText . length && responseBodySize === undefined
155
191
? getBodySize ( bodyText , textEncoder )
@@ -171,11 +207,19 @@ async function _getResponseInfo(
171
207
}
172
208
}
173
209
174
- async function _parseFetchBody ( response : Response ) : Promise < string | undefined > {
210
+ async function _parseFetchResponseBody ( response : Response ) : Promise < [ string | undefined , NetworkMetaWarning ?] > {
211
+ const res = _tryCloneResponse ( response ) ;
212
+
213
+ if ( ! res ) {
214
+ return [ undefined , 'BODY_PARSE_ERROR' ] ;
215
+ }
216
+
175
217
try {
176
- return await response . text ( ) ;
177
- } catch {
178
- return undefined ;
218
+ const text = await _tryGetResponseText ( res ) ;
219
+ return [ text ] ;
220
+ } catch ( error ) {
221
+ __DEBUG_BUILD__ && logger . warn ( '[Replay] Failed to get text body from response' , error ) ;
222
+ return [ undefined , 'BODY_PARSE_ERROR' ] ;
179
223
}
180
224
}
181
225
@@ -237,3 +281,39 @@ function getHeadersFromOptions(
237
281
238
282
return getAllowedHeaders ( headers , allowedHeaders ) ;
239
283
}
284
+
285
+ function _tryCloneResponse ( response : Response ) : Response | void {
286
+ try {
287
+ // We have to clone this, as the body can only be read once
288
+ return response . clone ( ) ;
289
+ } catch ( error ) {
290
+ // this can throw if the response was already consumed before
291
+ __DEBUG_BUILD__ && logger . warn ( '[Replay] Failed to clone response body' , error ) ;
292
+ }
293
+ }
294
+
295
+ /**
296
+ * Get the response body of a fetch request, or timeout after 500ms.
297
+ * Fetch can return a streaming body, that may not resolve (or not for a long time).
298
+ * If that happens, we rather abort after a short time than keep waiting for this.
299
+ */
300
+ function _tryGetResponseText ( response : Response ) : Promise < string | undefined > {
301
+ return new Promise ( ( resolve , reject ) => {
302
+ const timeout = setTimeout ( ( ) => reject ( new Error ( 'Timeout while trying to read response body' ) ) , 500 ) ;
303
+
304
+ _getResponseText ( response )
305
+ . then (
306
+ txt => resolve ( txt ) ,
307
+ reason => reject ( reason ) ,
308
+ )
309
+ . finally ( ( ) => clearTimeout ( timeout ) ) ;
310
+ } ) ;
311
+
312
+ return _getResponseText ( response ) ;
313
+ }
314
+
315
+ async function _getResponseText ( response : Response ) : Promise < string > {
316
+ // Force this to be a promise, just to be safe
317
+ // eslint-disable-next-line no-return-await
318
+ return await response . text ( ) ;
319
+ }
0 commit comments