Skip to content

Commit 1d8df82

Browse files
committed
feat(replay): Add "maxCanvasSize" option for replay canvases
We do not want to allow users to capture super large canvases, enforce a max size limit Requires getsentry/rrweb#174 Closes #10498 Backport of #11617
1 parent d7cf686 commit 1d8df82

File tree

7 files changed

+96
-29
lines changed

7 files changed

+96
-29
lines changed

dev-packages/browser-integration-tests/suites/replay/canvas/manualSnapshot/test.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,8 @@ sentryTest('can manually snapshot canvas', async ({ getLocalTestUrl, page, brows
6363
},
6464
0,
6565
0,
66+
150,
67+
150,
6668
],
6769
property: 'drawImage',
6870
},

dev-packages/browser-integration-tests/suites/replay/canvas/records/test.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,8 @@ sentryTest('can record canvas', async ({ getLocalTestUrl, page, browserName }) =
6565
},
6666
0,
6767
0,
68+
150,
69+
150,
6870
],
6971
property: 'drawImage',
7072
},

packages/replay-canvas/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@
5656
"homepage": "https://docs.sentry.io/platforms/javascript/session-replay/",
5757
"devDependencies": {
5858
"@babel/core": "^7.17.5",
59-
"@sentry-internal/rrweb": "2.12.0"
59+
"@sentry-internal/rrweb": "2.13.0"
6060
},
6161
"dependencies": {
6262
"@sentry/core": "7.111.0",

packages/replay-canvas/src/canvas.ts

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,14 @@ import type { Integration, IntegrationClass, IntegrationFn } from '@sentry/types
55

66
interface ReplayCanvasOptions {
77
enableManualSnapshot?: boolean;
8+
maxCanvasSize?: [width: number, height: number];
89
quality: 'low' | 'medium' | 'high';
910
}
1011

1112
type GetCanvasManager = (options: InternalCanvasManagerOptions) => InternalCanvasManagerInterface;
1213
export interface ReplayCanvasIntegrationOptions {
1314
enableManualSnapshot?: boolean;
15+
maxCanvasSize?: number;
1416
recordCanvas: true;
1517
getCanvasManager: GetCanvasManager;
1618
sampling: {
@@ -53,12 +55,18 @@ const CANVAS_QUALITY = {
5355
};
5456

5557
const INTEGRATION_NAME = 'ReplayCanvas';
58+
const DEFAULT_MAX_CANVAS_SIZE = 1280;
5659

5760
/** Exported only for type safe tests. */
5861
export const _replayCanvasIntegration = ((options: Partial<ReplayCanvasOptions> = {}) => {
62+
const [maxCanvasWidth, maxCanvasHeight] = options.maxCanvasSize || [];
5963
const _canvasOptions = {
6064
quality: options.quality || 'medium',
6165
enableManualSnapshot: options.enableManualSnapshot,
66+
maxCanvasSize: [
67+
maxCanvasWidth ? Math.min(maxCanvasWidth, DEFAULT_MAX_CANVAS_SIZE) : DEFAULT_MAX_CANVAS_SIZE,
68+
maxCanvasHeight ? Math.min(maxCanvasHeight, DEFAULT_MAX_CANVAS_SIZE) : DEFAULT_MAX_CANVAS_SIZE,
69+
] as [number, number],
6270
};
6371

6472
let canvasManagerResolve: (value: CanvasManager) => void;
@@ -69,15 +77,16 @@ export const _replayCanvasIntegration = ((options: Partial<ReplayCanvasOptions>
6977
// eslint-disable-next-line @typescript-eslint/no-empty-function
7078
setupOnce() {},
7179
getOptions(): ReplayCanvasIntegrationOptions {
72-
const { quality, enableManualSnapshot } = _canvasOptions;
80+
const { quality, enableManualSnapshot, maxCanvasSize } = _canvasOptions;
7381

7482
return {
7583
enableManualSnapshot,
7684
recordCanvas: true,
77-
getCanvasManager: (options: InternalCanvasManagerOptions) => {
85+
getCanvasManager: (getCanvasManagerOptions: InternalCanvasManagerOptions) => {
7886
const manager = new CanvasManager({
79-
...options,
87+
...getCanvasManagerOptions,
8088
enableManualSnapshot,
89+
maxCanvasSize,
8190
errorHandler: (err: unknown) => {
8291
try {
8392
if (typeof err === 'object') {

packages/replay-canvas/test/canvas.test.ts

Lines changed: 56 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,17 @@
1+
import { CanvasManager } from '@sentry-internal/rrweb';
12
import { _replayCanvasIntegration } from '../src/canvas';
23

4+
jest.mock('@sentry-internal/rrweb');
5+
6+
beforeEach(() => {
7+
jest.clearAllMocks();
8+
});
9+
310
it('initializes with default options', () => {
411
const rc = _replayCanvasIntegration();
12+
const options = rc.getOptions();
513

6-
expect(rc.getOptions()).toEqual({
14+
expect(options).toEqual({
715
recordCanvas: true,
816
getCanvasManager: expect.any(Function),
917
sampling: {
@@ -14,12 +22,22 @@ it('initializes with default options', () => {
1422
quality: 0.4,
1523
},
1624
});
25+
26+
// @ts-expect-error don't care about the normal options we need to call this with, just want to test maxCanvasSize
27+
options.getCanvasManager({});
28+
29+
expect(CanvasManager).toHaveBeenCalledWith(
30+
expect.objectContaining({
31+
maxCanvasSize: [1280, 1280],
32+
}),
33+
);
1734
});
1835

1936
it('initializes with quality option and manual snapshot', () => {
2037
const rc = _replayCanvasIntegration({ enableManualSnapshot: true, quality: 'low' });
38+
const options = rc.getOptions();
2139

22-
expect(rc.getOptions()).toEqual({
40+
expect(options).toEqual({
2341
enableManualSnapshot: true,
2442
recordCanvas: true,
2543
getCanvasManager: expect.any(Function),
@@ -31,4 +49,40 @@ it('initializes with quality option and manual snapshot', () => {
3149
quality: 0.25,
3250
},
3351
});
52+
53+
// @ts-expect-error don't care about the normal options we need to call this with, just want to test maxCanvasSize
54+
options.getCanvasManager({});
55+
56+
expect(CanvasManager).toHaveBeenCalledWith(
57+
expect.objectContaining({
58+
maxCanvasSize: [1280, 1280],
59+
}),
60+
);
61+
});
62+
63+
it('enforces a max canvas size', () => {
64+
const rc = _replayCanvasIntegration({ enableManualSnapshot: true, quality: 'low', maxCanvasSize: [2000, 2000] });
65+
const options = rc.getOptions();
66+
67+
expect(options).toEqual({
68+
enableManualSnapshot: true,
69+
recordCanvas: true,
70+
getCanvasManager: expect.any(Function),
71+
sampling: {
72+
canvas: 1,
73+
},
74+
dataURLOptions: {
75+
type: 'image/webp',
76+
quality: 0.25,
77+
},
78+
});
79+
80+
// @ts-expect-error don't care about the normal options we need to call this with, just want to test maxCanvasSize
81+
options.getCanvasManager({});
82+
83+
expect(CanvasManager).toHaveBeenCalledWith(
84+
expect.objectContaining({
85+
maxCanvasSize: [1280, 1280],
86+
}),
87+
);
3488
});

packages/replay/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,8 @@
5454
"devDependencies": {
5555
"@babel/core": "^7.17.5",
5656
"@sentry-internal/replay-worker": "7.111.0",
57-
"@sentry-internal/rrweb": "2.12.0",
58-
"@sentry-internal/rrweb-snapshot": "2.12.0",
57+
"@sentry-internal/rrweb": "2.13.0",
58+
"@sentry-internal/rrweb-snapshot": "2.13.0",
5959
"fflate": "^0.8.1",
6060
"jsdom-worker": "^0.2.1"
6161
},

yarn.lock

Lines changed: 21 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -5554,22 +5554,22 @@
55545554
dependencies:
55555555
"@sentry-internal/rrweb-snapshot" "2.11.0"
55565556

5557-
"@sentry-internal/rrdom@2.12.0":
5558-
version "2.12.0"
5559-
resolved "https://registry.yarnpkg.com/@sentry-internal/rrdom/-/rrdom-2.12.0.tgz#d3ca32b1e4b8c5d8cc9bdb44f933fe4b059573a0"
5560-
integrity sha512-EQ9vmhkTREdtzKp6SmD4GEkwr+RJcaEnbVcDZjbnQnxagskOpqvXjoPMONPf9hZhkULwnrnyFGGp0VpQOGBS0w==
5557+
"@sentry-internal/rrdom@2.13.0":
5558+
version "2.13.0"
5559+
resolved "https://registry.yarnpkg.com/@sentry-internal/rrdom/-/rrdom-2.13.0.tgz#3bb77fd67e72f743d33699431d8f66efb193e951"
5560+
integrity sha512-Idm+phUohY74mu9KxFX+lhBqHAN7qTMB6TZGf1hKBn8CusGm91jdEoBe4xqwesnthe8a37svkf8D3CIsiblZPA==
55615561
dependencies:
5562-
"@sentry-internal/rrweb-snapshot" "2.12.0"
5562+
"@sentry-internal/rrweb-snapshot" "2.13.0"
55635563

55645564
"@sentry-internal/[email protected]":
55655565
version "2.11.0"
55665566
resolved "https://registry.yarnpkg.com/@sentry-internal/rrweb-snapshot/-/rrweb-snapshot-2.11.0.tgz#1af79130604afea989d325465b209ac015b27c9a"
55675567
integrity sha512-1nP22QlplMNooSNvTh+L30NSZ+E3UcfaJyxXSMLxUjQHTGPyM1VkndxZMmxlKhyR5X+rLbxi/+RvuAcpM43VoA==
55685568

5569-
"@sentry-internal/rrweb-snapshot@2.12.0":
5570-
version "2.12.0"
5571-
resolved "https://registry.yarnpkg.com/@sentry-internal/rrweb-snapshot/-/rrweb-snapshot-2.12.0.tgz#2f1f6d4867a07ab757475fb4fa337d7f1aaa6b2d"
5572-
integrity sha512-AYo8CeDA7qDOKFG75E+bnxrS/qm7l5Ad0ftClA3VzoGV58bNNgv/aKiECtUPk0UPs4EqTQ8z8W/MZ9EYDF6vvA==
5569+
"@sentry-internal/rrweb-snapshot@2.13.0":
5570+
version "2.13.0"
5571+
resolved "https://registry.yarnpkg.com/@sentry-internal/rrweb-snapshot/-/rrweb-snapshot-2.13.0.tgz#47fb017b2031075cdd4f1b54c098c0bd8eb75e16"
5572+
integrity sha512-ftSybKlmddX9QsLXq02gMiWfuXEfyjysSJe0tvKxGMP2r1y4rS5h2qjJeKx+GYPhcGi1s48KkjphLNwHehqf4g==
55735573

55745574
"@sentry-internal/[email protected]":
55755575
version "2.11.0"
@@ -5578,12 +5578,12 @@
55785578
dependencies:
55795579
"@sentry-internal/rrweb-snapshot" "2.11.0"
55805580

5581-
"@sentry-internal/rrweb-types@2.12.0":
5582-
version "2.12.0"
5583-
resolved "https://registry.yarnpkg.com/@sentry-internal/rrweb-types/-/rrweb-types-2.12.0.tgz#f7c57eda7610882c71860437657ffbbcb788184d"
5584-
integrity sha512-W0iLlTx3HeapBTGjg/uLoKQr1/DGPbkANqwjf4mW0IS4jHAVcxFX/e769aHHKEmd68Lm3+A8b08xdA9UDBXW5w==
5581+
"@sentry-internal/rrweb-types@2.13.0":
5582+
version "2.13.0"
5583+
resolved "https://registry.yarnpkg.com/@sentry-internal/rrweb-types/-/rrweb-types-2.13.0.tgz#b68b9cf03c51626051bed9f8c41a40f1cf362991"
5584+
integrity sha512-noG66TvuN5xJuQAPfxHECW84XUsjYR95fytH6tSvPQQh7a0JUC+i5H76YSWdGhbu5eCMcrypzgATfw/GEN7bPA==
55855585
dependencies:
5586-
"@sentry-internal/rrweb-snapshot" "2.12.0"
5586+
"@sentry-internal/rrweb-snapshot" "2.13.0"
55875587

55885588
"@sentry-internal/[email protected]":
55895589
version "2.11.0"
@@ -5599,14 +5599,14 @@
55995599
fflate "^0.4.4"
56005600
mitt "^3.0.0"
56015601

5602-
"@sentry-internal/rrweb@2.12.0":
5603-
version "2.12.0"
5604-
resolved "https://registry.yarnpkg.com/@sentry-internal/rrweb/-/rrweb-2.12.0.tgz#4becbedf7315f4b4e0ebc35319a848ec6f082dce"
5605-
integrity sha512-NosAF5f8dXdj6linXpI+e38/eKVtwy3R2rzmMohBCwdhPXgTkTV/Laj/9OsRxARNRyz81mIEGcn/Ivp/De7RaA==
5602+
"@sentry-internal/rrweb@2.13.0":
5603+
version "2.13.0"
5604+
resolved "https://registry.yarnpkg.com/@sentry-internal/rrweb/-/rrweb-2.13.0.tgz#27f997a5922fa4af5990198b62a149bbf4c18833"
5605+
integrity sha512-Xi+Sg7T8+1UbIaW5l5zKNb+X7FlD7O0l8TZwZjMXVGQtfQcJTkPkrALspqZfUeaAtX+rQHQArcfpNhQWiDYezQ==
56065606
dependencies:
5607-
"@sentry-internal/rrdom" "2.12.0"
5608-
"@sentry-internal/rrweb-snapshot" "2.12.0"
5609-
"@sentry-internal/rrweb-types" "2.12.0"
5607+
"@sentry-internal/rrdom" "2.13.0"
5608+
"@sentry-internal/rrweb-snapshot" "2.13.0"
5609+
"@sentry-internal/rrweb-types" "2.13.0"
56105610
"@types/css-font-loading-module" "0.0.7"
56115611
"@xstate/fsm" "^1.4.0"
56125612
base64-arraybuffer "^1.0.1"

0 commit comments

Comments
 (0)