Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(replay): Add a new option networkDetailDenyUrls to Sentry Replay #8439

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
10 changes: 8 additions & 2 deletions packages/replay/src/coreHandlers/handleNetworkBreadcrumbs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,19 @@ export function handleNetworkBreadcrumbs(replay: ReplayContainer): void {
try {
const textEncoder = new TextEncoder();

const { networkDetailAllowUrls, networkCaptureBodies, networkRequestHeaders, networkResponseHeaders } =
replay.getOptions();
const {
networkDetailAllowUrls,
networkDetailDenyUrls,
networkCaptureBodies,
networkRequestHeaders,
networkResponseHeaders,
} = replay.getOptions();

const options: ExtendedNetworkBreadcrumbsOptions = {
replay,
textEncoder,
networkDetailAllowUrls,
networkDetailDenyUrls,
networkCaptureBodies,
networkRequestHeaders,
networkResponseHeaders,
Expand Down
3 changes: 2 additions & 1 deletion packages/replay/src/coreHandlers/util/fetchUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,8 @@ async function _prepareFetchData(
response_body_size: responseBodySize,
} = breadcrumb.data;

const captureDetails = urlMatches(url, options.networkDetailAllowUrls);
const captureDetails =
urlMatches(url, options.networkDetailAllowUrls) && !urlMatches(url, options.networkDetailDenyUrls);

const request = captureDetails
? _getRequestInfo(options, hint.input, requestBodySize)
Expand Down
2 changes: 1 addition & 1 deletion packages/replay/src/coreHandlers/util/xhrUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ function _prepareXhrData(
return null;
}

if (!urlMatches(url, options.networkDetailAllowUrls)) {
if (!urlMatches(url, options.networkDetailAllowUrls) || urlMatches(url, options.networkDetailDenyUrls)) {
const request = buildSkippedNetworkRequestOrResponse(requestBodySize);
const response = buildSkippedNetworkRequestOrResponse(responseBodySize);
return {
Expand Down
2 changes: 2 additions & 0 deletions packages/replay/src/integration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ export class Replay implements Integration {
slowClickIgnoreSelectors = [],

networkDetailAllowUrls = [],
networkDetailDenyUrls = [],
networkCaptureBodies = true,
networkRequestHeaders = [],
networkResponseHeaders = [],
Expand Down Expand Up @@ -138,6 +139,7 @@ export class Replay implements Integration {
slowClickTimeout,
slowClickIgnoreSelectors,
networkDetailAllowUrls,
networkDetailDenyUrls,
networkCaptureBodies,
networkRequestHeaders: _getMergedNetworkHeaders(networkRequestHeaders),
networkResponseHeaders: _getMergedNetworkHeaders(networkResponseHeaders),
Expand Down
15 changes: 12 additions & 3 deletions packages/replay/src/types/replay.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,23 +70,32 @@ export interface ReplayNetworkOptions {
*/
networkDetailAllowUrls: (string | RegExp)[];

/**
* Deny request/response details for XHR/Fetch requests that match the given URLs.
* The URLs can be strings or regular expressions.
* When provided a string, we will deny any URL that contains the given string.
* You can use a Regex to handle exact matches or more complex matching.
* URLs matching these patterns will not have bodies & additional headers captured.
*/
networkDetailDenyUrls: (string | RegExp)[];

/**
* If request & response bodies should be captured.
* Only applies to URLs matched by `networkDetailAllowUrls`.
* Only applies to URLs matched by `networkDetailAllowUrls` and not matched by `networkDetailDenyUrls`.
* Defaults to true.
*/
networkCaptureBodies: boolean;

/**
* Capture the following request headers, in addition to the default ones.
* Only applies to URLs matched by `networkDetailAllowUrls`.
* Only applies to URLs matched by `networkDetailAllowUrls` and not matched by `networkDetailDenyUrls`.
* Any headers defined here will be captured in addition to the default headers.
*/
networkRequestHeaders: string[];

/**
* Capture the following response headers, in addition to the default ones.
* Only applies to URLs matched by `networkDetailAllowUrls`.
* Only applies to URLs matched by `networkDetailAllowUrls` and not matched by `networkDetailDenyUrls`.
* Any headers defined here will be captured in addition to the default headers.
*/
networkResponseHeaders: string[];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ describe('Unit | coreHandlers | handleNetworkBreadcrumbs', () => {
textEncoder: new TextEncoder(),
replay: setupReplayContainer(),
networkDetailAllowUrls: ['https://example.com'],
networkDetailDenyUrls: ['http://localhost:8080'],
networkCaptureBodies: false,
networkRequestHeaders: ['content-type', 'accept', 'x-custom-header'],
networkResponseHeaders: ['content-type', 'accept', 'x-custom-header'],
Expand Down Expand Up @@ -1382,5 +1383,166 @@ other-header: test`;
]);
});
});

describe.each([
['exact string match', 'https://example.com/foo'],
['partial string match', 'https://example.com/bar/what'],
['exact regex match', 'http://example.com/exact'],
['partial regex match', 'http://example.com/partial/string'],
])('matching URL %s', (_label, url) => {
it('correctly deny URL for fetch request', async () => {
options.networkDetailDenyUrls = [
'https://example.com/foo',
'com/bar',
/^http:\/\/example.com\/exact$/,
/^http:\/\/example.com\/partial/,
];

const breadcrumb: Breadcrumb = {
category: 'fetch',
data: {
method: 'GET',
url,
status_code: 200,
},
};

const mockResponse = getMockResponse('13', 'test response');

const hint: FetchBreadcrumbHint = {
input: ['GET', { body: 'test input' }],
response: mockResponse,
startTimestamp: BASE_TIMESTAMP + 1000,
endTimestamp: BASE_TIMESTAMP + 2000,
};
beforeAddNetworkBreadcrumb(options, breadcrumb, hint);

expect(breadcrumb).toEqual({
category: 'fetch',
data: {
method: 'GET',
request_body_size: 10,
response_body_size: 13,
status_code: 200,
url,
},
});

await waitForReplayEventBuffer();

expect((options.replay.eventBuffer as EventBufferArray).events).toEqual([
{
data: {
payload: {
data: {
method: 'GET',
request: {
_meta: {
warnings: ['URL_SKIPPED'],
},
headers: {},
size: 10,
},
response: {
_meta: {
warnings: ['URL_SKIPPED'],
},
headers: {},
size: 13,
},
statusCode: 200,
},
description: url,
endTimestamp: (BASE_TIMESTAMP + 2000) / 1000,
op: 'resource.fetch',
startTimestamp: (BASE_TIMESTAMP + 1000) / 1000,
},
tag: 'performanceSpan',
},
timestamp: (BASE_TIMESTAMP + 1000) / 1000,
type: 5,
},
]);
});

it('correctly deny URL for xhr request', async () => {
options.networkDetailDenyUrls = [
'https://example.com/foo',
'com/bar',
/^http:\/\/example.com\/exact$/,
/^http:\/\/example.com\/partial/,
];

const breadcrumb: Breadcrumb = {
category: 'xhr',
data: {
method: 'GET',
url,
status_code: 200,
},
};
const xhr = new XMLHttpRequest();
Object.defineProperty(xhr, 'response', {
value: 'test response',
});
Object.defineProperty(xhr, 'responseText', {
value: 'test response',
});
const hint: XhrBreadcrumbHint = {
xhr,
input: 'test input',
startTimestamp: BASE_TIMESTAMP + 1000,
endTimestamp: BASE_TIMESTAMP + 2000,
};
beforeAddNetworkBreadcrumb(options, breadcrumb, hint);

expect(breadcrumb).toEqual({
category: 'xhr',
data: {
method: 'GET',
request_body_size: 10,
response_body_size: 13,
status_code: 200,
url,
},
});

await waitForReplayEventBuffer();

expect((options.replay.eventBuffer as EventBufferArray).events).toEqual([
{
data: {
payload: {
data: {
method: 'GET',
request: {
_meta: {
warnings: ['URL_SKIPPED'],
},
headers: {},
size: 10,
},
response: {
_meta: {
warnings: ['URL_SKIPPED'],
},
headers: {},
size: 13,
},
statusCode: 200,
},
description: url,
endTimestamp: (BASE_TIMESTAMP + 2000) / 1000,
op: 'resource.xhr',
startTimestamp: (BASE_TIMESTAMP + 1000) / 1000,
},
tag: 'performanceSpan',
},
timestamp: (BASE_TIMESTAMP + 1000) / 1000,
type: 5,
},
]);
});
});
});
});
1 change: 1 addition & 0 deletions packages/replay/test/utils/setupReplayContainer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ const DEFAULT_OPTIONS = {
useCompression: false,
blockAllMedia: true,
networkDetailAllowUrls: [],
networkDetailDenyUrls: [],
networkCaptureBodies: true,
networkRequestHeaders: [],
networkResponseHeaders: [],
Expand Down