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 ReplayCanvas integration #10112

Merged
merged 50 commits into from
Jan 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
62bd2bb
feat(replay): Add `ReplayCanvas` integration
mydea Jan 4, 2024
5ee991b
new quality options
billyvg Jan 4, 2024
14d8836
fix
billyvg Jan 5, 2024
7ce25c5
create replay-canvas package
billyvg Jan 8, 2024
230b5bb
cleanup repo
billyvg Jan 9, 2024
f9bf43d
remove local path
billyvg Jan 9, 2024
748076f
add craft/build items
billyvg Jan 9, 2024
2a214f8
add to resolutions
billyvg Jan 9, 2024
af1819d
change to not use `_experiments` for canvas
billyvg Jan 9, 2024
39e8788
revert entrypoint change
billyvg Jan 9, 2024
5d9f5c3
move tests to dev-packages
billyvg Jan 9, 2024
cdf1ed9
update tests
billyvg Jan 9, 2024
458b0c8
remove old template
billyvg Jan 10, 2024
820879d
CR fixes
billyvg Jan 10, 2024
f3290c4
do not publish as separate package
billyvg Jan 10, 2024
80bd36f
convert to fn
billyvg Jan 10, 2024
b5e1e9c
remove yarn lockfile
billyvg Jan 10, 2024
46b5f50
refactor out integrations abstraction
billyvg Jan 10, 2024
74d7565
bump version
billyvg Jan 10, 2024
5e5991c
lint
billyvg Jan 10, 2024
39eb70b
update eslint
billyvg Jan 10, 2024
0dc1ea9
upgrade rrweb pkg
billyvg Jan 10, 2024
0e7963a
add snapshot() to interface
billyvg Jan 10, 2024
7cbfe1a
missing setupOnce
billyvg Jan 10, 2024
8aededc
litn
billyvg Jan 10, 2024
b5e69ee
fix test
billyvg Jan 10, 2024
ecc4a85
remove old test, we have other coverage
billyvg Jan 10, 2024
57046cd
add replaycanvas to replay bundles
billyvg Jan 10, 2024
498dfd1
forgot shims
billyvg Jan 10, 2024
138c581
debug playwright test
billyvg Jan 10, 2024
ea6c74e
Revert "debug playwright test"
billyvg Jan 11, 2024
62ddb76
dont mangle _canvas
billyvg Jan 11, 2024
46d3859
fix types
billyvg Jan 11, 2024
3160c19
lint
billyvg Jan 11, 2024
a316a10
wait for replay request
billyvg Jan 11, 2024
7c29642
Revert "add replaycanvas to replay bundles"
billyvg Jan 15, 2024
5fa012d
Revert "forgot shims"
billyvg Jan 15, 2024
b2686b3
skip CDN bundles for canvas tests
billyvg Jan 15, 2024
7b11d63
add `canvas` to options event
billyvg Jan 16, 2024
0b2e850
move replay-canvas to devDeps
billyvg Jan 16, 2024
097b5c7
canvas --> shouldRecordCanvas
billyvg Jan 16, 2024
223ae91
fix tests for optionsEvent
billyvg Jan 16, 2024
4c412da
move back to deps
billyvg Jan 16, 2024
6014437
lint
billyvg Jan 16, 2024
94b8ac1
remove private: true, fails the e2e tests
billyvg Jan 16, 2024
60dcc35
fix optionEvent tests w/ shouldRecordCanvas
billyvg Jan 16, 2024
b87f421
add core as dep to replay-canvas
billyvg Jan 16, 2024
1530509
fix types due to https://github.com/getsentry/sentry-javascript/pull/…
billyvg Jan 16, 2024
4d82a1f
lint
billyvg Jan 16, 2024
006133b
Revert "do not publish as separate package"
billyvg Jan 16, 2024
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
4 changes: 4 additions & 0 deletions .craft.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ targets:
- name: npm
id: '@sentry-internal/feedback'
includeNames: /^sentry-internal-feedback-\d.*\.tgz$/
## 1.8 ReplayCanvas package (browser only)
Copy link
Member

Choose a reason for hiding this comment

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

Do we need to publish this? Or could we just inline this into browser/replay, same as we do with e.g. replay-worker? IMHO if we can avoid to publish it, we should!

Copy link
Member Author

Choose a reason for hiding this comment

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

@mydea I added this back, it's been a pain trying to test this package without it being published. e.g. in my test app, I'll use yalc to add @sentry/browser @sentry/replay, but yarn will complain:

error Couldn't find package "@sentry-internal/replay-canvas@7.93.0" required by "@sentry/browser@file:.yalc/@sentry/browser" on the "npm" registry.

- name: npm
id: '@sentry-internal/replay-canvas'
includeNames: /^sentry-internal-replay-canvas-\d.*\.tgz$/

## 2. Browser & Node SDKs
- name: npm
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ jobs:
- *shared
- 'packages/browser/**'
- 'packages/replay/**'
- 'packages/replay-canvas/**'
- 'packages/feedback/**'
browser_integration:
- *shared
Expand Down Expand Up @@ -371,6 +372,7 @@ jobs:
${{ github.workspace }}/packages/browser/build/bundles/**
${{ github.workspace }}/packages/integrations/build/bundles/**
${{ github.workspace }}/packages/replay/build/bundles/**
${{ github.workspace }}/packages/replay-canvas/build/bundles/**
${{ github.workspace }}/packages/**/*.tgz
${{ github.workspace }}/packages/serverless/build/aws/dist-serverless/*.zip

Expand Down
7 changes: 7 additions & 0 deletions .size-limit.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,13 @@ module.exports = [
gzip: true,
limit: '75 KB',
},
{
name: '@sentry/browser (incl. Tracing, Replay with Canvas) - Webpack (gzipped)',
path: 'packages/browser/build/npm/esm/index.js',
import: '{ init, Replay, BrowserTracing, ReplayCanvas }',
gzip: true,
limit: '90 KB',
},
{
name: '@sentry/browser (incl. Tracing, Replay) - Webpack with treeshaking flags (gzipped)',
path: 'packages/browser/build/npm/esm/index.js',
Expand Down
2 changes: 1 addition & 1 deletion dev-packages/browser-integration-tests/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@
"dependencies": {
"@babel/preset-typescript": "^7.16.7",
"@playwright/test": "^1.31.1",
"@sentry-internal/rrweb": "2.7.3",
"@sentry-internal/rrweb": "2.8.0",
"@sentry/browser": "7.93.0",
"@sentry/tracing": "7.93.0",
"axios": "1.6.0",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,10 @@
import { getCanvasManager } from '@sentry-internal/rrweb';
import * as Sentry from '@sentry/browser';

window.Sentry = Sentry;
window.Replay = new Sentry.Replay({
flushMinDelay: 50,
flushMaxDelay: 50,
minReplayDuration: 0,
_experiments: {
canvas: {
manager: getCanvasManager,
},
},
});

Sentry.init({
Expand All @@ -20,5 +14,5 @@ Sentry.init({
replaysOnErrorSampleRate: 0.0,
debug: true,

integrations: [window.Replay],
integrations: [window.Replay, new Sentry.ReplayCanvas()],
});
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { sentryTest } from '../../../../utils/fixtures';
import { getReplayRecordingContent, shouldSkipReplayTest, waitForReplayRequest } from '../../../../utils/replayHelpers';

sentryTest('can record canvas', async ({ getLocalTestUrl, page, browserName }) => {
if (shouldSkipReplayTest() || browserName === 'webkit') {
if (shouldSkipReplayTest() || browserName === 'webkit' || (process.env.PW_BUNDLE || '').startsWith('bundle')) {
sentryTest.skip();
}

Expand All @@ -24,6 +24,16 @@ sentryTest('can record canvas', async ({ getLocalTestUrl, page, browserName }) =

await page.goto(url);
await reqPromise0;
const content0 = getReplayRecordingContent(await reqPromise0);
expect(content0.optionsEvents).toEqual([
{
tag: 'options',
payload: expect.objectContaining({
shouldRecordCanvas: true,
}),
},
]);

await Promise.all([page.click('#draw'), reqPromise1]);

const { incrementalSnapshots } = getReplayRecordingContent(await reqPromise2);
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,
});

Sentry.init({
dsn: 'https://public@dsn.ingest.sentry.io/1337',
sampleRate: 0,
replaysSessionSampleRate: 1.0,
replaysOnErrorSampleRate: 0.0,
debug: true,

integrations: [new Sentry.ReplayCanvas(), window.Replay],
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { expect } from '@playwright/test';

import { sentryTest } from '../../../../utils/fixtures';
import { getReplaySnapshot, shouldSkipReplayTest, waitForReplayRequest } from '../../../../utils/replayHelpers';

sentryTest('sets up canvas when adding ReplayCanvas integration first', async ({ getLocalTestUrl, page }) => {
if (shouldSkipReplayTest() || (process.env.PW_BUNDLE || '').startsWith('bundle')) {
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 });

await page.goto(url);
await reqPromise0;

const replay = await getReplaySnapshot(page);
const canvasOptions = replay._canvas;
expect(canvasOptions?.sampling.canvas).toBe(2);
expect(canvasOptions?.dataURLOptions.quality).toBe(0.4);
expect(replay._hasCanvas).toBe(true);
});
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,
});

Sentry.init({
dsn: 'https://public@dsn.ingest.sentry.io/1337',
sampleRate: 0,
replaysSessionSampleRate: 1.0,
replaysOnErrorSampleRate: 0.0,
debug: true,

integrations: [window.Replay, new Sentry.ReplayCanvas()],
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { expect } from '@playwright/test';

import { sentryTest } from '../../../../utils/fixtures';
import { getReplaySnapshot, shouldSkipReplayTest, waitForReplayRequest } from '../../../../utils/replayHelpers';

sentryTest('sets up canvas when adding ReplayCanvas integration after Replay', async ({ getLocalTestUrl, page }) => {
if (shouldSkipReplayTest() || (process.env.PW_BUNDLE || '').startsWith('bundle')) {
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 });

await page.goto(url);
await reqPromise0;

const replay = await getReplaySnapshot(page);
const canvasOptions = replay._canvas;
expect(canvasOptions?.sampling.canvas).toBe(2);
expect(canvasOptions?.dataURLOptions.quality).toBe(0.4);
expect(replay._hasCanvas).toBe(true);
});
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,
});

Sentry.init({
dsn: 'https://public@dsn.ingest.sentry.io/1337',
sampleRate: 0,
replaysSessionSampleRate: 1.0,
replaysOnErrorSampleRate: 0.0,
debug: true,

integrations: [window.Replay],
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { expect } from '@playwright/test';

import { sentryTest } from '../../../../utils/fixtures';
import { getReplaySnapshot, shouldSkipReplayTest } from '../../../../utils/replayHelpers';

sentryTest('does not setup up canvas without ReplayCanvas integration', async ({ getLocalTestUrl, page }) => {
if (shouldSkipReplayTest()) {
sentryTest.skip();
}

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 });

await page.goto(url);

const replay = await getReplaySnapshot(page);
const canvasOptions = replay._canvas;
expect(canvasOptions).toBe(undefined);
expect(replay._hasCanvas).toBe(false);
});
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,7 @@ sentryTest(
networkCaptureBodies: true,
networkRequestHasHeaders: true,
networkResponseHasHeaders: true,
shouldRecordCanvas: false,
},
},
]);
Expand Down
11 changes: 9 additions & 2 deletions dev-packages/browser-integration-tests/utils/replayHelpers.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
/* eslint-disable max-lines */
import type { ReplayCanvasIntegrationOptions } from '@sentry-internal/replay-canvas';
import type { fullSnapshotEvent, incrementalSnapshotEvent } from '@sentry-internal/rrweb';
import { EventType } from '@sentry-internal/rrweb';
import type { ReplayEventWithTime } from '@sentry/browser';
Expand Down Expand Up @@ -174,21 +175,27 @@ export function getReplaySnapshot(page: Page): Promise<{
_isEnabled: boolean;
_context: InternalEventContext;
_options: ReplayPluginOptions;
_canvas: ReplayCanvasIntegrationOptions | undefined;
_hasCanvas: boolean;
session: Session | undefined;
recordingMode: ReplayRecordingMode;
}> {
return page.evaluate(() => {
const replayIntegration = (window as unknown as Window & { Replay: { _replay: ReplayContainer } }).Replay;
const replayIntegration = (
window as unknown as Window & {
Replay: { _replay: ReplayContainer & { _canvas: ReplayCanvasIntegrationOptions | undefined } };
}
).Replay;
const replay = replayIntegration._replay;

const replaySnapshot = {
_isPaused: replay.isPaused(),
_isEnabled: replay.isEnabled(),
_context: replay.getContext(),
_options: replay.getOptions(),
_canvas: replay['_canvas'],
// We cannot pass the function through as this is serialized
_hasCanvas: typeof replay.getOptions()._experiments.canvas?.manager === 'function',
_hasCanvas: typeof replay['_canvas']?.getCanvasManager === 'function',
session: replay.session,
recordingMode: replay.recordingMode,
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export const ReplayRecordingData = [
networkRequestHasHeaders: true,
networkResponseHasHeaders: true,
sessionSampleRate: 1,
shouldRecordCanvas: false,
useCompression: false,
useCompressionOption: true,
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export const ReplayRecordingData = [
networkRequestHasHeaders: true,
networkResponseHasHeaders: true,
sessionSampleRate: 1,
shouldRecordCanvas: false,
useCompression: false,
useCompressionOption: true,
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export const ReplayRecordingData = [
networkRequestHasHeaders: true,
networkResponseHasHeaders: true,
sessionSampleRate: 1,
shouldRecordCanvas: false,
useCompression: false,
useCompressionOption: true,
},
Expand Down
1 change: 1 addition & 0 deletions dev-packages/rollup-utils/plugins/bundlePlugins.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ export function makeTerserPlugin() {
'_support',
// We want to keep some replay fields unmangled to enable integration tests to access them
'_replay',
'_canvas',
// We also can't mangle rrweb private fields when bundling rrweb in the replay CDN bundles
'_cssText',
// We want to keep the _integrations variable unmangled to send all installed integrations from replay
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@
"packages/react",
"packages/remix",
"packages/replay",
"packages/replay-canvas",
"packages/replay-worker",
"packages/serverless",
"packages/svelte",
Expand Down
1 change: 1 addition & 0 deletions packages/browser/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
},
"dependencies": {
"@sentry-internal/feedback": "7.93.0",
"@sentry-internal/replay-canvas": "7.93.0",
"@sentry-internal/tracing": "7.93.0",
"@sentry/core": "7.93.0",
"@sentry/replay": "7.93.0",
Expand Down
2 changes: 2 additions & 0 deletions packages/browser/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ export type {
ReplaySpanFrameEvent,
} from '@sentry/replay';

export { ReplayCanvas } from '@sentry-internal/replay-canvas';

export { Feedback } from '@sentry-internal/feedback';

export {
Expand Down
1 change: 1 addition & 0 deletions packages/nextjs/test/integration/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
"@sentry/node": "file:../../../node",
"@sentry/react": "file:../../../react",
"@sentry/replay": "file:../../../replay",
"@sentry-internal/replay-canvas": "file:../../../replay-canvas",
"@sentry/tracing": "file:../../../tracing",
"@sentry-internal/tracing": "file:../../../tracing-internal",
"@sentry-internal/feedback": "file:../../../feedback",
Expand Down
1 change: 1 addition & 0 deletions packages/remix/test/integration/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
"@sentry/node": "file:../../../node",
"@sentry/react": "file:../../../react",
"@sentry/replay": "file:../../../replay",
"@sentry-internal/replay-canvas": "file:../../../replay-canvas",
"@sentry/tracing": "file:../../../tracing",
"@sentry-internal/tracing": "file:../../../tracing-internal",
"@sentry-internal/feedback": "file:../../../feedback",
Expand Down
2 changes: 2 additions & 0 deletions packages/replay-canvas/.eslintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
node_modules/
build/
25 changes: 25 additions & 0 deletions packages/replay-canvas/.eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// Note: All paths are relative to the directory in which eslint is being run, rather than the directory where this file
// lives

// ESLint config docs: https://eslint.org/docs/user-guide/configuring/

module.exports = {
extends: ['../../.eslintrc.js'],
overrides: [
{
files: ['src/**/*.ts'],
rules: {
'@sentry-internal/sdk/no-unsupported-es6-methods': 'off',
},
},
{
files: ['jest.setup.ts', 'jest.config.ts'],
parserOptions: {
project: ['tsconfig.test.json'],
},
rules: {
'no-console': 'off',
},
},
],
};
4 changes: 4 additions & 0 deletions packages/replay-canvas/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
node_modules
/*.tgz
.eslintcache
build
Loading