Skip to content

Commit

Permalink
feat(replay): Add "maxCanvasSize" option for replay canvases (#11732)
Browse files Browse the repository at this point in the history
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
  • Loading branch information
billyvg committed Apr 23, 2024
1 parent 8da1c8d commit 6a0a4c6
Show file tree
Hide file tree
Showing 7 changed files with 96 additions and 29 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ sentryTest('can manually snapshot canvas', async ({ getLocalTestUrl, page, brows
},
0,
0,
150,
150,
],
property: 'drawImage',
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@ sentryTest('can record canvas', async ({ getLocalTestUrl, page, browserName }) =
},
0,
0,
150,
150,
],
property: 'drawImage',
},
Expand Down
2 changes: 1 addition & 1 deletion packages/replay-canvas/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@
"homepage": "https://docs.sentry.io/platforms/javascript/session-replay/",
"devDependencies": {
"@babel/core": "^7.17.5",
"@sentry-internal/rrweb": "2.12.0"
"@sentry-internal/rrweb": "2.13.0"
},
"dependencies": {
"@sentry/core": "7.111.0",
Expand Down
15 changes: 12 additions & 3 deletions packages/replay-canvas/src/canvas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@ import type { Integration, IntegrationClass, IntegrationFn } from '@sentry/types

interface ReplayCanvasOptions {
enableManualSnapshot?: boolean;
maxCanvasSize?: [width: number, height: number];
quality: 'low' | 'medium' | 'high';
}

type GetCanvasManager = (options: InternalCanvasManagerOptions) => InternalCanvasManagerInterface;
export interface ReplayCanvasIntegrationOptions {
enableManualSnapshot?: boolean;
maxCanvasSize?: number;
recordCanvas: true;
getCanvasManager: GetCanvasManager;
sampling: {
Expand Down Expand Up @@ -53,12 +55,18 @@ const CANVAS_QUALITY = {
};

const INTEGRATION_NAME = 'ReplayCanvas';
const DEFAULT_MAX_CANVAS_SIZE = 1280;

/** Exported only for type safe tests. */
export const _replayCanvasIntegration = ((options: Partial<ReplayCanvasOptions> = {}) => {
const [maxCanvasWidth, maxCanvasHeight] = options.maxCanvasSize || [];
const _canvasOptions = {
quality: options.quality || 'medium',
enableManualSnapshot: options.enableManualSnapshot,
maxCanvasSize: [
maxCanvasWidth ? Math.min(maxCanvasWidth, DEFAULT_MAX_CANVAS_SIZE) : DEFAULT_MAX_CANVAS_SIZE,
maxCanvasHeight ? Math.min(maxCanvasHeight, DEFAULT_MAX_CANVAS_SIZE) : DEFAULT_MAX_CANVAS_SIZE,
] as [number, number],
};

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

return {
enableManualSnapshot,
recordCanvas: true,
getCanvasManager: (options: InternalCanvasManagerOptions) => {
getCanvasManager: (getCanvasManagerOptions: InternalCanvasManagerOptions) => {
const manager = new CanvasManager({
...options,
...getCanvasManagerOptions,
enableManualSnapshot,
maxCanvasSize,
errorHandler: (err: unknown) => {
try {
if (typeof err === 'object') {
Expand Down
58 changes: 56 additions & 2 deletions packages/replay-canvas/test/canvas.test.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,17 @@
import { CanvasManager } from '@sentry-internal/rrweb';
import { _replayCanvasIntegration } from '../src/canvas';

jest.mock('@sentry-internal/rrweb');

beforeEach(() => {
jest.clearAllMocks();
});

it('initializes with default options', () => {
const rc = _replayCanvasIntegration();
const options = rc.getOptions();

expect(rc.getOptions()).toEqual({
expect(options).toEqual({
recordCanvas: true,
getCanvasManager: expect.any(Function),
sampling: {
Expand All @@ -14,12 +22,22 @@ it('initializes with default options', () => {
quality: 0.4,
},
});

// @ts-expect-error don't care about the normal options we need to call this with, just want to test maxCanvasSize
options.getCanvasManager({});

expect(CanvasManager).toHaveBeenCalledWith(
expect.objectContaining({
maxCanvasSize: [1280, 1280],
}),
);
});

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

expect(rc.getOptions()).toEqual({
expect(options).toEqual({
enableManualSnapshot: true,
recordCanvas: true,
getCanvasManager: expect.any(Function),
Expand All @@ -31,4 +49,40 @@ it('initializes with quality option and manual snapshot', () => {
quality: 0.25,
},
});

// @ts-expect-error don't care about the normal options we need to call this with, just want to test maxCanvasSize
options.getCanvasManager({});

expect(CanvasManager).toHaveBeenCalledWith(
expect.objectContaining({
maxCanvasSize: [1280, 1280],
}),
);
});

it('enforces a max canvas size', () => {
const rc = _replayCanvasIntegration({ enableManualSnapshot: true, quality: 'low', maxCanvasSize: [2000, 2000] });
const options = rc.getOptions();

expect(options).toEqual({
enableManualSnapshot: true,
recordCanvas: true,
getCanvasManager: expect.any(Function),
sampling: {
canvas: 1,
},
dataURLOptions: {
type: 'image/webp',
quality: 0.25,
},
});

// @ts-expect-error don't care about the normal options we need to call this with, just want to test maxCanvasSize
options.getCanvasManager({});

expect(CanvasManager).toHaveBeenCalledWith(
expect.objectContaining({
maxCanvasSize: [1280, 1280],
}),
);
});
4 changes: 2 additions & 2 deletions packages/replay/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,8 @@
"devDependencies": {
"@babel/core": "^7.17.5",
"@sentry-internal/replay-worker": "7.111.0",
"@sentry-internal/rrweb": "2.12.0",
"@sentry-internal/rrweb-snapshot": "2.12.0",
"@sentry-internal/rrweb": "2.13.0",
"@sentry-internal/rrweb-snapshot": "2.13.0",
"fflate": "^0.8.1",
"jsdom-worker": "^0.2.1"
},
Expand Down
42 changes: 21 additions & 21 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -5554,22 +5554,22 @@
dependencies:
"@sentry-internal/rrweb-snapshot" "2.11.0"

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

"@sentry-internal/rrweb-snapshot@2.11.0":
version "2.11.0"
resolved "https://registry.yarnpkg.com/@sentry-internal/rrweb-snapshot/-/rrweb-snapshot-2.11.0.tgz#1af79130604afea989d325465b209ac015b27c9a"
integrity sha512-1nP22QlplMNooSNvTh+L30NSZ+E3UcfaJyxXSMLxUjQHTGPyM1VkndxZMmxlKhyR5X+rLbxi/+RvuAcpM43VoA==

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

"@sentry-internal/rrweb-types@2.11.0":
version "2.11.0"
Expand All @@ -5578,12 +5578,12 @@
dependencies:
"@sentry-internal/rrweb-snapshot" "2.11.0"

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

"@sentry-internal/rrweb@2.11.0":
version "2.11.0"
Expand All @@ -5599,14 +5599,14 @@
fflate "^0.4.4"
mitt "^3.0.0"

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

0 comments on commit 6a0a4c6

Please sign in to comment.