Skip to content

Commit 12ae3a0

Browse files
authored
Fix #607: use deferred promise to prevent fetch response body race condition
- Replace `responseBodyPromise = Promise.resolve(undefined)` with a deferred promise whose resolver is captured before `Promise.all` runs. - Inside the fetch `.then` handler, call `responseBodyResolve()` in both the recording and non-recording branches so `Promise.all` always settles. - Export `initFetchObserver` and add a regression test that asserts the `cb` receives a populated `responseBody` (would have failed before the fix). Fixes #607
1 parent 95b68a2 commit 12ae3a0

3 files changed

Lines changed: 56 additions & 32 deletions

File tree

package-lock.json

Lines changed: 0 additions & 25 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/recorder/rrweb-network-plugin.js

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -487,7 +487,7 @@ function initXhrObserver(cb, win, options) {
487487
* @param {Required<NetworkRecordOptions>} options
488488
* @returns {listenerHandler}
489489
*/
490-
function initFetchObserver(cb, win, options) {
490+
export function initFetchObserver(cb, win, options) {
491491
if (!options.initiatorTypes.includes('fetch')) {
492492
return function() {
493493
//
@@ -508,7 +508,10 @@ function initFetchObserver(cb, win, options) {
508508

509509
var originalFetchPromise;
510510
var requestBodyPromise = Promise.resolve(undefined);
511-
var responseBodyPromise = Promise.resolve(undefined);
511+
var responseBodyResolve;
512+
var responseBodyPromise = new Promise(function(resolve) {
513+
responseBodyResolve = resolve;
514+
});
512515
try {
513516
/** @type {Headers} */
514517
var requestHeaders = {};
@@ -541,10 +544,12 @@ function initFetchObserver(cb, win, options) {
541544
networkRequest.responseHeaders = responseHeaders;
542545

543546
if (shouldRecordBody('response', options.recordBodyUrls, req.url)) {
544-
responseBodyPromise = tryReadFetchBody(res)
545-
.then(function(body) {
546-
networkRequest.responseBody = body;
547-
});
547+
tryReadFetchBody(res).then(function(body) {
548+
networkRequest.responseBody = body;
549+
responseBodyResolve();
550+
});
551+
} else {
552+
responseBodyResolve();
548553
}
549554

550555
return res;

tests/unit/rrweb-network-plugin.js

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { expect } from 'chai';
2-
import { findLast, truncateBody, shouldRecordHeader, shouldRecordBody, patch } from '../../src/recorder/rrweb-network-plugin';
2+
import { findLast, truncateBody, shouldRecordHeader, shouldRecordBody, patch, initFetchObserver } from '../../src/recorder/rrweb-network-plugin';
33

44
describe(`rrweb-network-plugin utils`, function() {
55
describe(`findLast`, function() {
@@ -167,4 +167,48 @@ describe(`rrweb-network-plugin utils`, function() {
167167
expect(obj.val).to.equal(123);
168168
});
169169
});
170+
171+
describe(`initFetchObserver`, function() {
172+
it(`populates responseBody before emitting the network event (regression for #607)`, function(done) {
173+
const url = `https://example.com/api/data`;
174+
const responseText = `{"hello":"world"}`;
175+
176+
const fakeEntry = {
177+
name: url,
178+
initiatorType: `fetch`,
179+
entryType: `resource`,
180+
startTime: 0,
181+
responseEnd: 50,
182+
};
183+
184+
const fakeResponse = new Response(responseText, { status: 200 });
185+
186+
const win = {
187+
fetch: function() { return Promise.resolve(fakeResponse); },
188+
performance: {
189+
now: function() { return 0; },
190+
getEntriesByName: function() { return [fakeEntry]; },
191+
},
192+
};
193+
194+
const options = {
195+
initiatorTypes: [`fetch`],
196+
recordHeaders: { request: [], response: [] },
197+
recordBodyUrls: { request: [], response: [/example\.com/] },
198+
ignoreRequestUrls: [],
199+
ignoreRequestFn: function() { return false; },
200+
};
201+
202+
initFetchObserver(function(data) {
203+
try {
204+
expect(data.requests[0].responseBody).to.equal(responseText);
205+
done();
206+
} catch (e) {
207+
done(e);
208+
}
209+
}, win, options);
210+
211+
win.fetch(url);
212+
});
213+
});
170214
});

0 commit comments

Comments
 (0)