Skip to content

Commit e348233

Browse files
committed
feat(replay): Add ReplayCanvas integration
Adding this integration in addition to `Replay` will set up canvas recording.
1 parent 09e4ecc commit e348233

File tree

15 files changed

+271
-4
lines changed

15 files changed

+271
-4
lines changed

.size-limit.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,13 @@ module.exports = [
1414
gzip: true,
1515
limit: '75 KB',
1616
},
17+
{
18+
name: '@sentry/browser (incl. Tracing, Replay with Canvas) - Webpack (gzipped)',
19+
path: 'packages/browser/build/npm/esm/index.js',
20+
import: '{ init, Replay, BrowserTracing, ReplayCanvas }',
21+
gzip: true,
22+
limit: '90 KB',
23+
},
1724
{
1825
name: '@sentry/browser (incl. Tracing, Replay) - Webpack with treeshaking flags (gzipped)',
1926
path: 'packages/browser/build/npm/esm/index.js',

dev-packages/browser-integration-tests/utils/replayHelpers.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
1+
/* eslint-disable max-lines */
12
import type { fullSnapshotEvent, incrementalSnapshotEvent } from '@sentry-internal/rrweb';
23
import { EventType } from '@sentry-internal/rrweb';
34
import type { ReplayEventWithTime } from '@sentry/browser';
45
import type {
56
InternalEventContext,
67
RecordingEvent,
78
ReplayContainer,
9+
ReplayPluginOptions,
810
Session,
911
} from '@sentry/replay/build/npm/types/types';
1012
import type { Breadcrumb, Event, ReplayEvent, ReplayRecordingMode } from '@sentry/types';
@@ -171,6 +173,8 @@ export function getReplaySnapshot(page: Page): Promise<{
171173
_isPaused: boolean;
172174
_isEnabled: boolean;
173175
_context: InternalEventContext;
176+
_options: ReplayPluginOptions;
177+
_hasCanvas: boolean;
174178
session: Session | undefined;
175179
recordingMode: ReplayRecordingMode;
176180
}> {
@@ -182,6 +186,9 @@ export function getReplaySnapshot(page: Page): Promise<{
182186
_isPaused: replay.isPaused(),
183187
_isEnabled: replay.isEnabled(),
184188
_context: replay.getContext(),
189+
_options: replay.getOptions(),
190+
// We cannot pass the function through as this is serialized
191+
_hasCanvas: typeof replay.getOptions()._experiments.canvas?.manager === 'function',
185192
session: replay.session,
186193
recordingMode: replay.recordingMode,
187194
};
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<meta charset="utf-8" />
5+
</head>
6+
<body>
7+
<button onclick="console.log('Test log')">Click me</button>
8+
</body>
9+
</html>
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import * as Sentry from '@sentry/browser';
2+
3+
window.Sentry = Sentry;
4+
window.Replay = new Sentry.Replay({
5+
flushMinDelay: 200,
6+
flushMaxDelay: 200,
7+
minReplayDuration: 0,
8+
});
9+
10+
Sentry.init({
11+
dsn: 'https://[email protected]/1337',
12+
sampleRate: 0,
13+
replaysSessionSampleRate: 1.0,
14+
replaysOnErrorSampleRate: 0.0,
15+
debug: true,
16+
17+
integrations: [new Sentry.ReplayCanvas(), window.Replay],
18+
});
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { expect } from '@playwright/test';
2+
3+
import { sentryTest } from '../../../../utils/fixtures';
4+
import { getReplaySnapshot, shouldSkipReplayTest } from '../../../../utils/replayHelpers';
5+
6+
sentryTest('sets up canvas when adding ReplayCanvas integration first', async ({ getLocalTestUrl, page }) => {
7+
if (shouldSkipReplayTest()) {
8+
sentryTest.skip();
9+
}
10+
11+
await page.route('https://dsn.ingest.sentry.io/**/*', route => {
12+
return route.fulfill({
13+
status: 200,
14+
contentType: 'application/json',
15+
body: JSON.stringify({ id: 'test-id' }),
16+
});
17+
});
18+
19+
const url = await getLocalTestUrl({ testDir: __dirname });
20+
21+
await page.goto(url);
22+
23+
const replay = await getReplaySnapshot(page);
24+
const canvasOptions = replay._options._experiments?.canvas;
25+
expect(canvasOptions.fps).toBe(4);
26+
expect(canvasOptions.quality).toBe(0.6);
27+
expect(replay._hasCanvas).toBe(true);
28+
});
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import * as Sentry from '@sentry/browser';
2+
3+
window.Sentry = Sentry;
4+
window.Replay = new Sentry.Replay({
5+
flushMinDelay: 200,
6+
flushMaxDelay: 200,
7+
minReplayDuration: 0,
8+
});
9+
10+
Sentry.init({
11+
dsn: 'https://[email protected]/1337',
12+
sampleRate: 0,
13+
replaysSessionSampleRate: 1.0,
14+
replaysOnErrorSampleRate: 0.0,
15+
debug: true,
16+
17+
integrations: [window.Replay, new Sentry.ReplayCanvas()],
18+
});
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { expect } from '@playwright/test';
2+
3+
import { sentryTest } from '../../../../utils/fixtures';
4+
import { getReplaySnapshot, shouldSkipReplayTest } from '../../../../utils/replayHelpers';
5+
6+
sentryTest('sets up canvas when adding ReplayCanvas integration after Replay', async ({ getLocalTestUrl, page }) => {
7+
if (shouldSkipReplayTest()) {
8+
sentryTest.skip();
9+
}
10+
11+
await page.route('https://dsn.ingest.sentry.io/**/*', route => {
12+
return route.fulfill({
13+
status: 200,
14+
contentType: 'application/json',
15+
body: JSON.stringify({ id: 'test-id' }),
16+
});
17+
});
18+
19+
const url = await getLocalTestUrl({ testDir: __dirname });
20+
21+
await page.goto(url);
22+
23+
const replay = await getReplaySnapshot(page);
24+
const canvasOptions = replay._options._experiments?.canvas;
25+
expect(canvasOptions.fps).toBe(4);
26+
expect(canvasOptions.quality).toBe(0.6);
27+
expect(replay._hasCanvas).toBe(true);
28+
});
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import * as Sentry from '@sentry/browser';
2+
3+
window.Sentry = Sentry;
4+
window.Replay = new Sentry.Replay({
5+
flushMinDelay: 200,
6+
flushMaxDelay: 200,
7+
minReplayDuration: 0,
8+
});
9+
10+
Sentry.init({
11+
dsn: 'https://[email protected]/1337',
12+
sampleRate: 0,
13+
replaysSessionSampleRate: 1.0,
14+
replaysOnErrorSampleRate: 0.0,
15+
debug: true,
16+
17+
integrations: [window.Replay],
18+
});
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { expect } from '@playwright/test';
2+
3+
import { sentryTest } from '../../../../utils/fixtures';
4+
import { getReplaySnapshot, shouldSkipReplayTest } from '../../../../utils/replayHelpers';
5+
6+
sentryTest('does not setup up canvas without ReplayCanvas integration', async ({ getLocalTestUrl, page }) => {
7+
if (shouldSkipReplayTest()) {
8+
sentryTest.skip();
9+
}
10+
11+
await page.route('https://dsn.ingest.sentry.io/**/*', route => {
12+
return route.fulfill({
13+
status: 200,
14+
contentType: 'application/json',
15+
body: JSON.stringify({ id: 'test-id' }),
16+
});
17+
});
18+
19+
const url = await getLocalTestUrl({ testDir: __dirname });
20+
21+
await page.goto(url);
22+
23+
const replay = await getReplaySnapshot(page);
24+
const canvasOptions = replay._options._experiments?.canvas;
25+
expect(canvasOptions).toBe(undefined);
26+
expect(replay._hasCanvas).toBe(false);
27+
});

packages/browser/src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ const INTEGRATIONS = {
2020

2121
export { INTEGRATIONS as Integrations };
2222

23-
export { Replay } from '@sentry/replay';
23+
export { Replay, ReplayCanvas } from '@sentry/replay';
2424
export type {
2525
ReplayEventType,
2626
ReplayEventWithTime,

0 commit comments

Comments
 (0)