Skip to content

Commit 05a45df

Browse files
committed
implement fetch
1 parent 52c58b3 commit 05a45df

File tree

2 files changed

+111
-30
lines changed

2 files changed

+111
-30
lines changed

packages/rrweb/src/plugins/network/record/index.ts

Lines changed: 99 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1+
/* eslint-disable no-useless-catch */
12
import type { IWindow, listenerHandler, RecordPlugin } from '@rrweb/types';
3+
import { findLast } from '../../../utils';
24
import type { StringifyOptions } from '../../utils/stringify';
35

46
export type InitiatorType =
@@ -73,24 +75,13 @@ const defaultNetworkOptions: NetworkRecordOptions = {
7375
};
7476

7577
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;
8778

8879
type NetworkRequest = {
8980
performanceEntry: PerformanceEntry;
9081
requestHeaders?: Headers;
91-
requestBody?: Body;
82+
requestBody?: string | null;
9283
responseHeaders?: Headers;
93-
responseBody?: Body;
84+
responseBody?: string | null;
9485
};
9586

9687
export type NetworkData = {
@@ -100,13 +91,14 @@ export type NetworkData = {
10091

10192
type networkCallback = (data: NetworkData) => void;
10293

94+
type NetworkObserverOptions = NetworkRecordOptions & {
95+
initiatorType: InitiatorType[];
96+
};
97+
10398
function initPerformanceObserver(
10499
cb: networkCallback,
105100
win: IWindow,
106-
options: {
107-
initiatorType: InitiatorType[];
108-
recordInitialEvents: boolean;
109-
},
101+
options: NetworkObserverOptions,
110102
) {
111103
if (!('performance' in win)) {
112104
return () => {
@@ -157,8 +149,13 @@ function initPerformanceObserver(
157149
function initXhrObserver(
158150
cb: networkCallback,
159151
win: IWindow,
160-
options: NetworkRecordOptions,
152+
options: NetworkObserverOptions,
161153
): listenerHandler {
154+
if (!options.initiatorType.includes('xmlhttprequest')) {
155+
return () => {
156+
//
157+
};
158+
}
162159
return () => {
163160
// TODO:
164161
};
@@ -167,10 +164,88 @@ function initXhrObserver(
167164
function initFetchObserver(
168165
cb: networkCallback,
169166
win: IWindow,
170-
options: NetworkRecordOptions,
167+
options: NetworkObserverOptions,
171168
): 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;
172247
return () => {
173-
// TODO:
248+
win.fetch = originalFetch;
174249
};
175250
}
176251

@@ -187,19 +262,13 @@ function initNetworkObserver(
187262
};
188263

189264
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);
198267

199268
return () => {
200269
performanceObserver();
201-
xhrObserver?.();
202-
fetchObserver?.();
270+
xhrObserver();
271+
fetchObserver();
203272
};
204273
}
205274

packages/rrweb/src/utils.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -545,3 +545,15 @@ export function inDom(n: Node): boolean {
545545
if (!doc) return false;
546546
return doc.contains(n) || shadowHostInDom(n);
547547
}
548+
549+
export function findLast<T>(
550+
array: Array<T>,
551+
predicate: (value: T) => boolean,
552+
): T | undefined {
553+
const length = array.length;
554+
for (let i = length - 1; i >= 0; i -= 1) {
555+
if (predicate(array[i])) {
556+
return array[i];
557+
}
558+
}
559+
}

0 commit comments

Comments
 (0)