1
+ /* eslint-disable no-useless-catch */
1
2
import type { IWindow , listenerHandler , RecordPlugin } from '@rrweb/types' ;
3
+ import { findLast } from '../../../utils' ;
2
4
import type { StringifyOptions } from '../../utils/stringify' ;
3
5
4
6
export type InitiatorType =
@@ -73,24 +75,13 @@ const defaultNetworkOptions: NetworkRecordOptions = {
73
75
} ;
74
76
75
77
type Headers = Record < string , string > ;
76
- type Body =
77
- | string
78
- | Document
79
- | Blob
80
- | ArrayBufferView
81
- | ArrayBuffer
82
- | FormData
83
- | URLSearchParams
84
- | ReadableStream < Uint8Array >
85
- | null
86
- | undefined ;
87
78
88
79
type NetworkRequest = {
89
80
performanceEntry : PerformanceEntry ;
90
81
requestHeaders ?: Headers ;
91
- requestBody ?: Body ;
82
+ requestBody ?: string | null ;
92
83
responseHeaders ?: Headers ;
93
- responseBody ?: Body ;
84
+ responseBody ?: string | null ;
94
85
} ;
95
86
96
87
export type NetworkData = {
@@ -100,13 +91,14 @@ export type NetworkData = {
100
91
101
92
type networkCallback = ( data : NetworkData ) => void ;
102
93
94
+ type NetworkObserverOptions = NetworkRecordOptions & {
95
+ initiatorType : InitiatorType [ ] ;
96
+ } ;
97
+
103
98
function initPerformanceObserver (
104
99
cb : networkCallback ,
105
100
win : IWindow ,
106
- options : {
107
- initiatorType : InitiatorType [ ] ;
108
- recordInitialEvents : boolean ;
109
- } ,
101
+ options : NetworkObserverOptions ,
110
102
) {
111
103
if ( ! ( 'performance' in win ) ) {
112
104
return ( ) => {
@@ -157,8 +149,13 @@ function initPerformanceObserver(
157
149
function initXhrObserver (
158
150
cb : networkCallback ,
159
151
win : IWindow ,
160
- options : NetworkRecordOptions ,
152
+ options : NetworkObserverOptions ,
161
153
) : listenerHandler {
154
+ if ( ! options . initiatorType . includes ( 'xmlhttprequest' ) ) {
155
+ return ( ) => {
156
+ //
157
+ } ;
158
+ }
162
159
return ( ) => {
163
160
// TODO:
164
161
} ;
@@ -167,10 +164,88 @@ function initXhrObserver(
167
164
function initFetchObserver (
168
165
cb : networkCallback ,
169
166
win : IWindow ,
170
- options : NetworkRecordOptions ,
167
+ options : NetworkObserverOptions ,
171
168
) : listenerHandler {
169
+ if ( ! options . initiatorType . includes ( 'fetch' ) ) {
170
+ return ( ) => {
171
+ //
172
+ } ;
173
+ }
174
+ const originalFetch = win . fetch ;
175
+ const wrappedFetch : typeof fetch = async function ( url , init ) {
176
+ const recordRequestHeaders =
177
+ ! ! options . recordHeaders &&
178
+ ( typeof options . recordHeaders === 'boolean' ||
179
+ ! ( 'request' in options . recordHeaders ) ||
180
+ options . recordHeaders . request ) ;
181
+ const recordRequestBody =
182
+ ! ! options . recordBody &&
183
+ ( typeof options . recordBody === 'boolean' ||
184
+ ! ( 'request' in options . recordBody ) ||
185
+ options . recordBody . request ) ;
186
+ const recordResponseHeaders =
187
+ ! ! options . recordHeaders &&
188
+ ( typeof options . recordHeaders === 'boolean' ||
189
+ ! ( 'response' in options . recordHeaders ) ||
190
+ options . recordHeaders . response ) ;
191
+ const recordResponseBody =
192
+ ! ! options . recordBody &&
193
+ ( typeof options . recordBody === 'boolean' ||
194
+ ! ( 'response' in options . recordBody ) ||
195
+ options . recordBody . response ) ;
196
+
197
+ const req = new Request ( url , init ) ;
198
+ let performanceEntry : PerformanceResourceTiming | undefined ;
199
+ const networkRequest : Partial < NetworkRequest > = { } ;
200
+ if ( recordRequestHeaders ) {
201
+ networkRequest . requestHeaders = { } ;
202
+ req . headers . forEach ( ( value , key ) => {
203
+ networkRequest . requestHeaders ! [ key ] = value ;
204
+ } ) ;
205
+ }
206
+ if ( recordRequestBody ) {
207
+ networkRequest . requestBody = init ?. body ?. toString ( ) ;
208
+ if ( networkRequest . requestBody === undefined ) {
209
+ networkRequest . requestBody = null ;
210
+ }
211
+ }
212
+ try {
213
+ const res = await originalFetch ( req ) ;
214
+ const performanceEntries = win . performance . getEntriesByType (
215
+ 'resource' ,
216
+ ) as PerformanceResourceTiming [ ] ;
217
+ performanceEntry = findLast (
218
+ performanceEntries ,
219
+ ( p ) => p . initiatorType === 'fetch' && p . name === req . url ,
220
+ ) ;
221
+ if ( recordResponseHeaders ) {
222
+ networkRequest . responseHeaders = { } ;
223
+ res . headers . forEach ( ( value , key ) => {
224
+ networkRequest . responseHeaders ! [ key ] = value ;
225
+ } ) ;
226
+ }
227
+ if ( recordResponseBody ) {
228
+ networkRequest . responseBody = await res . clone ( ) . text ( ) ;
229
+ }
230
+ return res ;
231
+ } catch ( cause ) {
232
+ throw cause ;
233
+ } finally {
234
+ if ( performanceEntry ) {
235
+ cb ( { requests : [ { performanceEntry, ...networkRequest } ] } ) ;
236
+ }
237
+ }
238
+ } ;
239
+ wrappedFetch . prototype = { } ;
240
+ Object . defineProperties ( wrappedFetch , {
241
+ __rrweb_original__ : {
242
+ enumerable : false ,
243
+ value : originalFetch ,
244
+ } ,
245
+ } ) ;
246
+ win . fetch = wrappedFetch ;
172
247
return ( ) => {
173
- // TODO:
248
+ win . fetch = originalFetch ;
174
249
} ;
175
250
}
176
251
@@ -187,19 +262,13 @@ function initNetworkObserver(
187
262
} ;
188
263
189
264
const performanceObserver = initPerformanceObserver ( cb , win , networkOptions ) ;
190
- let xhrObserver : listenerHandler | undefined ;
191
- if ( networkOptions . initiatorType . includes ( 'xmlhttprequest' ) ) {
192
- xhrObserver = initXhrObserver ( cb , win , networkOptions ) ;
193
- }
194
- let fetchObserver : listenerHandler | undefined ;
195
- if ( networkOptions . initiatorType . includes ( 'fetch' ) ) {
196
- fetchObserver = initFetchObserver ( cb , win , networkOptions ) ;
197
- }
265
+ const xhrObserver = initXhrObserver ( cb , win , networkOptions ) ;
266
+ const fetchObserver = initFetchObserver ( cb , win , networkOptions ) ;
198
267
199
268
return ( ) => {
200
269
performanceObserver ( ) ;
201
- xhrObserver ?. ( ) ;
202
- fetchObserver ?. ( ) ;
270
+ xhrObserver ( ) ;
271
+ fetchObserver ( ) ;
203
272
} ;
204
273
}
205
274
0 commit comments