Skip to content

Commit 9ede8a3

Browse files
feat(replay): Add a new option networkDetailDenyUrls (#8439)
1 parent 1db809b commit 9ede8a3

File tree

7 files changed

+188
-7
lines changed

7 files changed

+188
-7
lines changed

packages/replay/src/coreHandlers/handleNetworkBreadcrumbs.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,19 @@ export function handleNetworkBreadcrumbs(replay: ReplayContainer): void {
3131
try {
3232
const textEncoder = new TextEncoder();
3333

34-
const { networkDetailAllowUrls, networkCaptureBodies, networkRequestHeaders, networkResponseHeaders } =
35-
replay.getOptions();
34+
const {
35+
networkDetailAllowUrls,
36+
networkDetailDenyUrls,
37+
networkCaptureBodies,
38+
networkRequestHeaders,
39+
networkResponseHeaders,
40+
} = replay.getOptions();
3641

3742
const options: ExtendedNetworkBreadcrumbsOptions = {
3843
replay,
3944
textEncoder,
4045
networkDetailAllowUrls,
46+
networkDetailDenyUrls,
4147
networkCaptureBodies,
4248
networkRequestHeaders,
4349
networkResponseHeaders,

packages/replay/src/coreHandlers/util/fetchUtils.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,8 @@ async function _prepareFetchData(
8585
response_body_size: responseBodySize,
8686
} = breadcrumb.data;
8787

88-
const captureDetails = urlMatches(url, options.networkDetailAllowUrls);
88+
const captureDetails =
89+
urlMatches(url, options.networkDetailAllowUrls) && !urlMatches(url, options.networkDetailDenyUrls);
8990

9091
const request = captureDetails
9192
? _getRequestInfo(options, hint.input, requestBodySize)

packages/replay/src/coreHandlers/util/xhrUtils.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ function _prepareXhrData(
7878
return null;
7979
}
8080

81-
if (!urlMatches(url, options.networkDetailAllowUrls)) {
81+
if (!urlMatches(url, options.networkDetailAllowUrls) || urlMatches(url, options.networkDetailDenyUrls)) {
8282
const request = buildSkippedNetworkRequestOrResponse(requestBodySize);
8383
const response = buildSkippedNetworkRequestOrResponse(responseBodySize);
8484
return {

packages/replay/src/integration.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ export class Replay implements Integration {
6767
slowClickIgnoreSelectors = [],
6868

6969
networkDetailAllowUrls = [],
70+
networkDetailDenyUrls = [],
7071
networkCaptureBodies = true,
7172
networkRequestHeaders = [],
7273
networkResponseHeaders = [],
@@ -138,6 +139,7 @@ export class Replay implements Integration {
138139
slowClickTimeout,
139140
slowClickIgnoreSelectors,
140141
networkDetailAllowUrls,
142+
networkDetailDenyUrls,
141143
networkCaptureBodies,
142144
networkRequestHeaders: _getMergedNetworkHeaders(networkRequestHeaders),
143145
networkResponseHeaders: _getMergedNetworkHeaders(networkResponseHeaders),

packages/replay/src/types/replay.ts

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -70,23 +70,32 @@ export interface ReplayNetworkOptions {
7070
*/
7171
networkDetailAllowUrls: (string | RegExp)[];
7272

73+
/**
74+
* Deny request/response details for XHR/Fetch requests that match the given URLs.
75+
* The URLs can be strings or regular expressions.
76+
* When provided a string, we will deny any URL that contains the given string.
77+
* You can use a Regex to handle exact matches or more complex matching.
78+
* URLs matching these patterns will not have bodies & additional headers captured.
79+
*/
80+
networkDetailDenyUrls: (string | RegExp)[];
81+
7382
/**
7483
* If request & response bodies should be captured.
75-
* Only applies to URLs matched by `networkDetailAllowUrls`.
84+
* Only applies to URLs matched by `networkDetailAllowUrls` and not matched by `networkDetailDenyUrls`.
7685
* Defaults to true.
7786
*/
7887
networkCaptureBodies: boolean;
7988

8089
/**
8190
* Capture the following request headers, in addition to the default ones.
82-
* Only applies to URLs matched by `networkDetailAllowUrls`.
91+
* Only applies to URLs matched by `networkDetailAllowUrls` and not matched by `networkDetailDenyUrls`.
8392
* Any headers defined here will be captured in addition to the default headers.
8493
*/
8594
networkRequestHeaders: string[];
8695

8796
/**
8897
* Capture the following response headers, in addition to the default ones.
89-
* Only applies to URLs matched by `networkDetailAllowUrls`.
98+
* Only applies to URLs matched by `networkDetailAllowUrls` and not matched by `networkDetailDenyUrls`.
9099
* Any headers defined here will be captured in addition to the default headers.
91100
*/
92101
networkResponseHeaders: string[];

packages/replay/test/unit/coreHandlers/handleNetworkBreadcrumbs.test.ts

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ describe('Unit | coreHandlers | handleNetworkBreadcrumbs', () => {
6363
textEncoder: new TextEncoder(),
6464
replay: setupReplayContainer(),
6565
networkDetailAllowUrls: ['https://example.com'],
66+
networkDetailDenyUrls: ['http://localhost:8080'],
6667
networkCaptureBodies: false,
6768
networkRequestHeaders: ['content-type', 'accept', 'x-custom-header'],
6869
networkResponseHeaders: ['content-type', 'accept', 'x-custom-header'],
@@ -1382,5 +1383,166 @@ other-header: test`;
13821383
]);
13831384
});
13841385
});
1386+
1387+
describe.each([
1388+
['exact string match', 'https://example.com/foo'],
1389+
['partial string match', 'https://example.com/bar/what'],
1390+
['exact regex match', 'http://example.com/exact'],
1391+
['partial regex match', 'http://example.com/partial/string'],
1392+
])('matching URL %s', (_label, url) => {
1393+
it('correctly deny URL for fetch request', async () => {
1394+
options.networkDetailDenyUrls = [
1395+
'https://example.com/foo',
1396+
'com/bar',
1397+
/^http:\/\/example.com\/exact$/,
1398+
/^http:\/\/example.com\/partial/,
1399+
];
1400+
1401+
const breadcrumb: Breadcrumb = {
1402+
category: 'fetch',
1403+
data: {
1404+
method: 'GET',
1405+
url,
1406+
status_code: 200,
1407+
},
1408+
};
1409+
1410+
const mockResponse = getMockResponse('13', 'test response');
1411+
1412+
const hint: FetchBreadcrumbHint = {
1413+
input: ['GET', { body: 'test input' }],
1414+
response: mockResponse,
1415+
startTimestamp: BASE_TIMESTAMP + 1000,
1416+
endTimestamp: BASE_TIMESTAMP + 2000,
1417+
};
1418+
beforeAddNetworkBreadcrumb(options, breadcrumb, hint);
1419+
1420+
expect(breadcrumb).toEqual({
1421+
category: 'fetch',
1422+
data: {
1423+
method: 'GET',
1424+
request_body_size: 10,
1425+
response_body_size: 13,
1426+
status_code: 200,
1427+
url,
1428+
},
1429+
});
1430+
1431+
await waitForReplayEventBuffer();
1432+
1433+
expect((options.replay.eventBuffer as EventBufferArray).events).toEqual([
1434+
{
1435+
data: {
1436+
payload: {
1437+
data: {
1438+
method: 'GET',
1439+
request: {
1440+
_meta: {
1441+
warnings: ['URL_SKIPPED'],
1442+
},
1443+
headers: {},
1444+
size: 10,
1445+
},
1446+
response: {
1447+
_meta: {
1448+
warnings: ['URL_SKIPPED'],
1449+
},
1450+
headers: {},
1451+
size: 13,
1452+
},
1453+
statusCode: 200,
1454+
},
1455+
description: url,
1456+
endTimestamp: (BASE_TIMESTAMP + 2000) / 1000,
1457+
op: 'resource.fetch',
1458+
startTimestamp: (BASE_TIMESTAMP + 1000) / 1000,
1459+
},
1460+
tag: 'performanceSpan',
1461+
},
1462+
timestamp: (BASE_TIMESTAMP + 1000) / 1000,
1463+
type: 5,
1464+
},
1465+
]);
1466+
});
1467+
1468+
it('correctly deny URL for xhr request', async () => {
1469+
options.networkDetailDenyUrls = [
1470+
'https://example.com/foo',
1471+
'com/bar',
1472+
/^http:\/\/example.com\/exact$/,
1473+
/^http:\/\/example.com\/partial/,
1474+
];
1475+
1476+
const breadcrumb: Breadcrumb = {
1477+
category: 'xhr',
1478+
data: {
1479+
method: 'GET',
1480+
url,
1481+
status_code: 200,
1482+
},
1483+
};
1484+
const xhr = new XMLHttpRequest();
1485+
Object.defineProperty(xhr, 'response', {
1486+
value: 'test response',
1487+
});
1488+
Object.defineProperty(xhr, 'responseText', {
1489+
value: 'test response',
1490+
});
1491+
const hint: XhrBreadcrumbHint = {
1492+
xhr,
1493+
input: 'test input',
1494+
startTimestamp: BASE_TIMESTAMP + 1000,
1495+
endTimestamp: BASE_TIMESTAMP + 2000,
1496+
};
1497+
beforeAddNetworkBreadcrumb(options, breadcrumb, hint);
1498+
1499+
expect(breadcrumb).toEqual({
1500+
category: 'xhr',
1501+
data: {
1502+
method: 'GET',
1503+
request_body_size: 10,
1504+
response_body_size: 13,
1505+
status_code: 200,
1506+
url,
1507+
},
1508+
});
1509+
1510+
await waitForReplayEventBuffer();
1511+
1512+
expect((options.replay.eventBuffer as EventBufferArray).events).toEqual([
1513+
{
1514+
data: {
1515+
payload: {
1516+
data: {
1517+
method: 'GET',
1518+
request: {
1519+
_meta: {
1520+
warnings: ['URL_SKIPPED'],
1521+
},
1522+
headers: {},
1523+
size: 10,
1524+
},
1525+
response: {
1526+
_meta: {
1527+
warnings: ['URL_SKIPPED'],
1528+
},
1529+
headers: {},
1530+
size: 13,
1531+
},
1532+
statusCode: 200,
1533+
},
1534+
description: url,
1535+
endTimestamp: (BASE_TIMESTAMP + 2000) / 1000,
1536+
op: 'resource.xhr',
1537+
startTimestamp: (BASE_TIMESTAMP + 1000) / 1000,
1538+
},
1539+
tag: 'performanceSpan',
1540+
},
1541+
timestamp: (BASE_TIMESTAMP + 1000) / 1000,
1542+
type: 5,
1543+
},
1544+
]);
1545+
});
1546+
});
13851547
});
13861548
});

packages/replay/test/utils/setupReplayContainer.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ const DEFAULT_OPTIONS = {
1212
useCompression: false,
1313
blockAllMedia: true,
1414
networkDetailAllowUrls: [],
15+
networkDetailDenyUrls: [],
1516
networkCaptureBodies: true,
1617
networkRequestHeaders: [],
1718
networkResponseHeaders: [],

0 commit comments

Comments
 (0)