Skip to content

feat(replay): Allow to treeshake & configure compression worker URL #9409

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

Merged
merged 3 commits into from
Oct 31, 2023
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .size-limit.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ module.exports = [
__RRWEB_EXCLUDE_CANVAS__: true,
__RRWEB_EXCLUDE_SHADOW_DOM__: true,
__RRWEB_EXCLUDE_IFRAME__: true,
__SENTRY_EXCLUDE_REPLAY_WORKER__: true,
}),
);
return config;
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@ import { getExpectedReplayEvent } from '../../../utils/replayEventTemplates';
import {
getFullRecordingSnapshots,
getReplayEvent,
replayEnvelopeIsCompressed,
shouldSkipReplayTest,
waitForReplayRequest,
} from '../../../utils/replayHelpers';

sentryTest('replay recording should be compressed by default', async ({ getLocalTestPath, page }) => {
sentryTest('replay recording should be compressed by default', async ({ getLocalTestPath, page, forceFlushReplay }) => {
if (shouldSkipReplayTest()) {
sentryTest.skip();
}
Expand All @@ -27,10 +28,16 @@ sentryTest('replay recording should be compressed by default', async ({ getLocal
const url = await getLocalTestPath({ testDir: __dirname });

await page.goto(url);
const replayEvent0 = getReplayEvent(await reqPromise0);
await forceFlushReplay();

const req0 = await reqPromise0;

const replayEvent0 = getReplayEvent(req0);
expect(replayEvent0).toEqual(getExpectedReplayEvent());

const snapshots = getFullRecordingSnapshots(await reqPromise0);
expect(replayEnvelopeIsCompressed(req0)).toEqual(true);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also added a check to actually test that we are sending a compressed payload 😅


const snapshots = getFullRecordingSnapshots(req0);
expect(snapshots.length).toEqual(1);

const stringifiedSnapshot = JSON.stringify(snapshots[0]);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import * as Sentry from '@sentry/browser';

window.Sentry = Sentry;
window.Replay = new Sentry.Replay({
flushMinDelay: 200,
flushMaxDelay: 200,
minReplayDuration: 0,
useCompression: false,
});

Sentry.init({
dsn: 'https://[email protected]/1337',
sampleRate: 0,
replaysSessionSampleRate: 1.0,
replaysOnErrorSampleRate: 0.0,

integrations: [window.Replay],
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
</head>
<body>
<button id="go-background">New Tab</button>
</body>
</html>
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { expect } from '@playwright/test';

import { sentryTest } from '../../../utils/fixtures';
import { getExpectedReplayEvent } from '../../../utils/replayEventTemplates';
import {
getFullRecordingSnapshots,
getReplayEvent,
replayEnvelopeIsCompressed,
shouldSkipReplayTest,
waitForReplayRequest,
} from '../../../utils/replayHelpers';

sentryTest(
'replay recording should allow to disable compression',
async ({ getLocalTestPath, page, forceFlushReplay }) => {
if (shouldSkipReplayTest()) {
sentryTest.skip();
}

const reqPromise0 = waitForReplayRequest(page, 0);

await page.route('https://dsn.ingest.sentry.io/**/*', route => {
return route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({ id: 'test-id' }),
});
});

const url = await getLocalTestPath({ testDir: __dirname });

await page.goto(url);
await forceFlushReplay();

const req0 = await reqPromise0;

const replayEvent0 = getReplayEvent(req0);
expect(replayEvent0).toEqual(getExpectedReplayEvent());

expect(replayEnvelopeIsCompressed(req0)).toEqual(false);

const snapshots = getFullRecordingSnapshots(req0);
expect(snapshots.length).toEqual(1);

const stringifiedSnapshot = JSON.stringify(snapshots[0]);
expect(stringifiedSnapshot).toContain('"tagName":"body"');
expect(stringifiedSnapshot).toContain('"tagName":"html"');
expect(stringifiedSnapshot).toContain('"tagName":"button"');
expect(stringifiedSnapshot).toContain('"textContent":"*** ***"');
expect(stringifiedSnapshot).toContain('"id":"go-background"');
},
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import * as Sentry from '@sentry/browser';

window.Sentry = Sentry;
window.Replay = new Sentry.Replay({
flushMinDelay: 200,
flushMaxDelay: 200,
minReplayDuration: 0,
useCompression: true,
workerUrl: `${window.location.origin}/my-test-worker.js`,
});

Sentry.init({
dsn: 'https://[email protected]/1337',
sampleRate: 0,
replaysSessionSampleRate: 1.0,
replaysOnErrorSampleRate: 0.0,

integrations: [window.Replay],
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
</head>
<body>
<button id="go-background">New Tab</button>
</body>
</html>
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { expect } from '@playwright/test';
import fs from 'fs';
import path from 'path';

import { sentryTest, TEST_HOST } from '../../../utils/fixtures';
import { getExpectedReplayEvent } from '../../../utils/replayEventTemplates';
import {
getFullRecordingSnapshots,
getReplayEvent,
replayEnvelopeIsCompressed,
shouldSkipReplayTest,
waitForReplayRequest,
} from '../../../utils/replayHelpers';

sentryTest(
'replay recording should be compressed if using custom workerUrl',
async ({ getLocalTestUrl, page, forceFlushReplay }) => {
if (shouldSkipReplayTest()) {
sentryTest.skip();
}

const reqPromise0 = waitForReplayRequest(page, 0);

await page.route('https://dsn.ingest.sentry.io/**/*', route => {
return route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({ id: 'test-id' }),
});
});

const url = await getLocalTestUrl({ testDir: __dirname });

let customCompressCalled = 0;

// Ensure to register this _after_ getLocalTestUrl is called, as that also registers a default route for TEST_HOST
await page.route(`${TEST_HOST}/my-test-worker.js`, route => {
const filePath = path.resolve(__dirname, '../../../../replay-worker/examples/worker.min.js');

customCompressCalled++;

return fs.existsSync(filePath) ? route.fulfill({ path: filePath }) : route.continue();
});

await page.goto(url);
await forceFlushReplay();

const req0 = await reqPromise0;

const replayEvent0 = getReplayEvent(req0);
expect(replayEvent0).toEqual(getExpectedReplayEvent());

expect(replayEnvelopeIsCompressed(req0)).toEqual(true);
expect(customCompressCalled).toBe(1);

const snapshots = getFullRecordingSnapshots(req0);
expect(snapshots.length).toEqual(1);

const stringifiedSnapshot = JSON.stringify(snapshots[0]);
expect(stringifiedSnapshot).toContain('"tagName":"body"');
expect(stringifiedSnapshot).toContain('"tagName":"html"');
expect(stringifiedSnapshot).toContain('"tagName":"button"');
expect(stringifiedSnapshot).toContain('"textContent":"*** ***"');
expect(stringifiedSnapshot).toContain('"id":"go-background"');
},
);
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ const DEFAULT_REPLAY_EVENT = {
timestamp: expect.any(Number),
error_ids: [],
trace_ids: [],
urls: [expect.stringContaining('/dist/index.html')],
urls: [expect.stringContaining('/index.html')],
replay_id: expect.stringMatching(/\w{32}/),
replay_start_timestamp: expect.any(Number),
segment_id: 0,
Expand All @@ -31,7 +31,7 @@ const DEFAULT_REPLAY_EVENT = {
name: 'sentry.javascript.browser',
},
request: {
url: expect.stringContaining('/dist/index.html'),
url: expect.stringContaining('/index.html'),
headers: {
'User-Agent': expect.stringContaining(''),
},
Expand Down
32 changes: 32 additions & 0 deletions packages/browser-integration-tests/utils/replayHelpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,38 @@ const replayEnvelopeRequestParser = (request: Request | null, envelopeIndex = 2)
return envelope[envelopeIndex] as Event;
};

export function replayEnvelopeIsCompressed(resOrReq: Request | Response): boolean {
const request = getRequest(resOrReq);

// https://develop.sentry.dev/sdk/envelopes/
const envelopeBytes = request.postDataBuffer() || '';

// first, we convert the bugger to string to split and go through the uncompressed lines
const envelopeString = envelopeBytes.toString();

const lines: boolean[] = envelopeString.split('\n').map(line => {
try {
JSON.parse(line);
} catch (error) {
// If we fail to parse a line, we _might_ have found a compressed payload,
// so let's check if this is actually the case.
// This is quite hacky but we can't go through `line` because the prior operations
// seem to have altered its binary content. Hence, we take the raw envelope and
// look up the place where the zlib compression header(0x78 0x9c) starts
for (let i = 0; i < envelopeBytes.length; i++) {
if (envelopeBytes[i] === 0x78 && envelopeBytes[i + 1] === 0x9c) {
// We found a zlib-compressed payload
return true;
}
}
}

return false;
});

return lines.some(line => line);
}

export const replayEnvelopeParser = (request: Request | null): unknown[] => {
// https://develop.sentry.dev/sdk/envelopes/
const envelopeBytes = request?.postDataBuffer() || '';
Expand Down
Loading