From 62bd2bb662a59a71735862fb4707fe7000068e27 Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Thu, 4 Jan 2024 16:48:07 +0100 Subject: [PATCH 01/50] feat(replay): Add `ReplayCanvas` integration Adding this integration in addition to `Replay` will set up canvas recording. --- .size-limit.js | 7 +++ .../suites/replay/canvas/template.html | 9 ++++ .../canvas/withCanvasIntegrationFirst/init.js | 18 +++++++ .../canvas/withCanvasIntegrationFirst/test.ts | 28 ++++++++++ .../withCanvasIntegrationSecond/init.js | 18 +++++++ .../withCanvasIntegrationSecond/test.ts | 28 ++++++++++ .../canvas/withoutCanvasIntegration/init.js | 18 +++++++ .../canvas/withoutCanvasIntegration/test.ts | 27 ++++++++++ packages/browser/src/index.ts | 2 +- packages/replay/rollup.bundle.config.mjs | 12 ++++- packages/replay/src/canvas.ts | 54 +++++++++++++++++++ packages/replay/src/index.ts | 1 + packages/replay/src/integration.ts | 41 ++++++++++++++ 13 files changed, 260 insertions(+), 3 deletions(-) create mode 100644 packages/browser-integration-tests/suites/replay/canvas/template.html create mode 100644 packages/browser-integration-tests/suites/replay/canvas/withCanvasIntegrationFirst/init.js create mode 100644 packages/browser-integration-tests/suites/replay/canvas/withCanvasIntegrationFirst/test.ts create mode 100644 packages/browser-integration-tests/suites/replay/canvas/withCanvasIntegrationSecond/init.js create mode 100644 packages/browser-integration-tests/suites/replay/canvas/withCanvasIntegrationSecond/test.ts create mode 100644 packages/browser-integration-tests/suites/replay/canvas/withoutCanvasIntegration/init.js create mode 100644 packages/browser-integration-tests/suites/replay/canvas/withoutCanvasIntegration/test.ts create mode 100644 packages/replay/src/canvas.ts diff --git a/.size-limit.js b/.size-limit.js index 2ddf44088be9..606014bfec0e 100644 --- a/.size-limit.js +++ b/.size-limit.js @@ -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', diff --git a/packages/browser-integration-tests/suites/replay/canvas/template.html b/packages/browser-integration-tests/suites/replay/canvas/template.html new file mode 100644 index 000000000000..2b3e2f0b27b4 --- /dev/null +++ b/packages/browser-integration-tests/suites/replay/canvas/template.html @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/packages/browser-integration-tests/suites/replay/canvas/withCanvasIntegrationFirst/init.js b/packages/browser-integration-tests/suites/replay/canvas/withCanvasIntegrationFirst/init.js new file mode 100644 index 000000000000..fa3248066150 --- /dev/null +++ b/packages/browser-integration-tests/suites/replay/canvas/withCanvasIntegrationFirst/init.js @@ -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], +}); diff --git a/packages/browser-integration-tests/suites/replay/canvas/withCanvasIntegrationFirst/test.ts b/packages/browser-integration-tests/suites/replay/canvas/withCanvasIntegrationFirst/test.ts new file mode 100644 index 000000000000..7326bed95ae6 --- /dev/null +++ b/packages/browser-integration-tests/suites/replay/canvas/withCanvasIntegrationFirst/test.ts @@ -0,0 +1,28 @@ +import { expect } from '@playwright/test'; + +import { sentryTest } from '../../../../utils/fixtures'; +import { getReplaySnapshot, shouldSkipReplayTest } from '../../../../utils/replayHelpers'; + +sentryTest('sets up canvas when adding ReplayCanvas integration first', 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._options._experiments?.canvas; + expect(canvasOptions.fps).toBe(4); + expect(canvasOptions.quality).toBe(0.6); + expect(replay._hasCanvas).toBe(true); +}); diff --git a/packages/browser-integration-tests/suites/replay/canvas/withCanvasIntegrationSecond/init.js b/packages/browser-integration-tests/suites/replay/canvas/withCanvasIntegrationSecond/init.js new file mode 100644 index 000000000000..1a9d5d179c4a --- /dev/null +++ b/packages/browser-integration-tests/suites/replay/canvas/withCanvasIntegrationSecond/init.js @@ -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()], +}); diff --git a/packages/browser-integration-tests/suites/replay/canvas/withCanvasIntegrationSecond/test.ts b/packages/browser-integration-tests/suites/replay/canvas/withCanvasIntegrationSecond/test.ts new file mode 100644 index 000000000000..9168864db7e0 --- /dev/null +++ b/packages/browser-integration-tests/suites/replay/canvas/withCanvasIntegrationSecond/test.ts @@ -0,0 +1,28 @@ +import { expect } from '@playwright/test'; + +import { sentryTest } from '../../../../utils/fixtures'; +import { getReplaySnapshot, shouldSkipReplayTest } from '../../../../utils/replayHelpers'; + +sentryTest('sets up canvas when adding ReplayCanvas integration after Replay', 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._options._experiments?.canvas; + expect(canvasOptions.fps).toBe(4); + expect(canvasOptions.quality).toBe(0.6); + expect(replay._hasCanvas).toBe(true); +}); diff --git a/packages/browser-integration-tests/suites/replay/canvas/withoutCanvasIntegration/init.js b/packages/browser-integration-tests/suites/replay/canvas/withoutCanvasIntegration/init.js new file mode 100644 index 000000000000..92a463a4bc84 --- /dev/null +++ b/packages/browser-integration-tests/suites/replay/canvas/withoutCanvasIntegration/init.js @@ -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], +}); diff --git a/packages/browser-integration-tests/suites/replay/canvas/withoutCanvasIntegration/test.ts b/packages/browser-integration-tests/suites/replay/canvas/withoutCanvasIntegration/test.ts new file mode 100644 index 000000000000..b2ce69211be0 --- /dev/null +++ b/packages/browser-integration-tests/suites/replay/canvas/withoutCanvasIntegration/test.ts @@ -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._options._experiments?.canvas; + expect(canvasOptions).toBe(undefined); + expect(replay._hasCanvas).toBe(false); +}); diff --git a/packages/browser/src/index.ts b/packages/browser/src/index.ts index 97abefea8242..857538a81139 100644 --- a/packages/browser/src/index.ts +++ b/packages/browser/src/index.ts @@ -20,7 +20,7 @@ const INTEGRATIONS = { export { INTEGRATIONS as Integrations }; -export { Replay } from '@sentry/replay'; +export { Replay, ReplayCanvas } from '@sentry/replay'; export type { ReplayEventType, ReplayEventWithTime, diff --git a/packages/replay/rollup.bundle.config.mjs b/packages/replay/rollup.bundle.config.mjs index a209b8d41af4..b254dae8d8d2 100644 --- a/packages/replay/rollup.bundle.config.mjs +++ b/packages/replay/rollup.bundle.config.mjs @@ -2,12 +2,20 @@ import { makeBaseBundleConfig, makeBundleConfigVariants } from '@sentry-internal const baseBundleConfig = makeBaseBundleConfig({ bundleType: 'addon', - entrypoints: ['src/index.ts'], + entrypoints: ['src/integration.ts'], jsVersion: 'es6', licenseTitle: '@sentry/replay', outputFileBase: () => 'bundles/replay', }); -const builds = makeBundleConfigVariants(baseBundleConfig); +const baseCanvasBundleConfig = makeBaseBundleConfig({ + bundleType: 'addon', + entrypoints: ['src/canvas.ts'], + jsVersion: 'es6', + licenseTitle: '@sentry/replaycanvas', + outputFileBase: () => 'bundles/replaycanvas', +}); + +const builds = [...makeBundleConfigVariants(baseBundleConfig), ...makeBundleConfigVariants(baseCanvasBundleConfig)]; export default builds; diff --git a/packages/replay/src/canvas.ts b/packages/replay/src/canvas.ts new file mode 100644 index 000000000000..b3fe2afa60f0 --- /dev/null +++ b/packages/replay/src/canvas.ts @@ -0,0 +1,54 @@ +import { getCanvasManager } from '@sentry-internal/rrweb'; +import type { Integration } from '@sentry/types'; +import type { ReplayConfiguration } from './types'; + +interface ReplayCanvasOptions { + fps: number; + quality: number; +} + +/** An integration to add canvas recording to replay. */ +export class ReplayCanvas implements Integration { + /** + * @inheritDoc + */ + public static id: string = 'ReplayCanvas'; + + /** + * @inheritDoc + */ + public name: string; + + private _canvasOptions: ReplayCanvasOptions; + + public constructor() { + this.name = ReplayCanvas.id; + // TODO FN: Allow to configure this + // But since we haven't finalized how to configure this, this is predefined for now + // to avoid breaking changes + this._canvasOptions = { + fps: 4, + quality: 0.6, + }; + } + + /** @inheritdoc */ + public setupOnce(): void { + // noop + } + + /** + * Get the options that should be merged into replay options. + * This is what is actually called by the Replay integration to setup canvas. + */ + public getOptions(): Partial { + return { + _experiments: { + canvas: { + ...this._canvasOptions, + manager: getCanvasManager, + }, + }, + }; + } +} diff --git a/packages/replay/src/index.ts b/packages/replay/src/index.ts index 412354f1dc54..d6ea1de76732 100644 --- a/packages/replay/src/index.ts +++ b/packages/replay/src/index.ts @@ -1,4 +1,5 @@ export { Replay } from './integration'; +export { ReplayCanvas } from './canvas'; export type { ReplayEventType, diff --git a/packages/replay/src/integration.ts b/packages/replay/src/integration.ts index 80bbeca5fdf3..6c62523098b9 100644 --- a/packages/replay/src/integration.ts +++ b/packages/replay/src/integration.ts @@ -318,6 +318,7 @@ Sentry.init({ replaysOnErrorSampleRate: ${errorSampleRate} })`, return this._replay.getSessionId(); } + /** * Initializes replay. */ @@ -326,6 +327,12 @@ Sentry.init({ replaysOnErrorSampleRate: ${errorSampleRate} })`, return; } + // We have to run this in _initialize, because this runs in setTimeout + // So when this runs all integrations have been added + // Before this, we cannot access integrations on the client, + // so we need to mutate the options here + this._maybeLoadFromReplayCanvasIntegration(); + this._replay.initializeSampling(); } @@ -339,6 +346,40 @@ Sentry.init({ replaysOnErrorSampleRate: ${errorSampleRate} })`, recordingOptions: this._recordingOptions, }); } + + /** Get canvas options from ReplayCanvas integration, if it is also added. */ + private _maybeLoadFromReplayCanvasIntegration(): void { + // If already defined, skip this... + if (this._initialOptions._experiments.canvas) { + return; + } + + // To save bundle size, we skip checking for stuff here + // and instead just try-catch everything - as generally this should all be defined + /* eslint-disable @typescript-eslint/no-non-null-assertion */ + try { + const client = getClient()!; + const canvasIntegration = client.getIntegrationByName!('ReplayCanvas') as Integration & { + getOptions(): Partial; + }; + if (!canvasIntegration) { + return; + } + const additionalOptions = canvasIntegration.getOptions(); + + const mergedExperimentsOptions = { + ...this._initialOptions._experiments, + ...additionalOptions._experiments, + }; + + this._initialOptions._experiments = mergedExperimentsOptions; + + this._replay!.getOptions()._experiments = mergedExperimentsOptions; + } catch { + // ignore errors here + } + /* eslint-enable @typescript-eslint/no-non-null-assertion */ + } } /** Parse Replay-related options from SDK options */ From 5ee991bba765ec096cb5f5ed10759542410e61bb Mon Sep 17 00:00:00 2001 From: Billy Vong Date: Thu, 4 Jan 2024 14:34:51 -0500 Subject: [PATCH 02/50] new quality options --- packages/replay/src/canvas.ts | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/packages/replay/src/canvas.ts b/packages/replay/src/canvas.ts index b3fe2afa60f0..a35bf323b961 100644 --- a/packages/replay/src/canvas.ts +++ b/packages/replay/src/canvas.ts @@ -3,8 +3,7 @@ import type { Integration } from '@sentry/types'; import type { ReplayConfiguration } from './types'; interface ReplayCanvasOptions { - fps: number; - quality: number; + quality: 'low' | 'medium' | 'high'; } /** An integration to add canvas recording to replay. */ @@ -21,14 +20,10 @@ export class ReplayCanvas implements Integration { private _canvasOptions: ReplayCanvasOptions; - public constructor() { + public constructor(options?: Partial) { this.name = ReplayCanvas.id; - // TODO FN: Allow to configure this - // But since we haven't finalized how to configure this, this is predefined for now - // to avoid breaking changes this._canvasOptions = { - fps: 4, - quality: 0.6, + quality: options && options.quality || 'medium', }; } @@ -47,6 +42,7 @@ export class ReplayCanvas implements Integration { canvas: { ...this._canvasOptions, manager: getCanvasManager, + quality: this._canvasOptions.quality, }, }, }; From 14d88368929dc4220665cc33013c782a103c6361 Mon Sep 17 00:00:00 2001 From: Billy Vong Date: Fri, 5 Jan 2024 11:19:54 -0500 Subject: [PATCH 03/50] fix --- packages/replay/src/canvas.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/replay/src/canvas.ts b/packages/replay/src/canvas.ts index a35bf323b961..d4b869c2f024 100644 --- a/packages/replay/src/canvas.ts +++ b/packages/replay/src/canvas.ts @@ -23,7 +23,7 @@ export class ReplayCanvas implements Integration { public constructor(options?: Partial) { this.name = ReplayCanvas.id; this._canvasOptions = { - quality: options && options.quality || 'medium', + quality: (options && options.quality) || 'medium', }; } From 7ce25c5259a4d7a3fec55e335b0e0d0cd68eeb79 Mon Sep 17 00:00:00 2001 From: Billy Vong Date: Mon, 8 Jan 2024 10:19:23 -0500 Subject: [PATCH 04/50] create replay-canvas package --- package.json | 1 + packages/browser/package.json | 1 + packages/browser/src/index.ts | 4 +- packages/replay-canvas/.eslintignore | 6 + packages/replay-canvas/.eslintrc.js | 42 +++ packages/replay-canvas/.gitignore | 4 + packages/replay-canvas/CONTRIBUTING.md | 4 + packages/replay-canvas/LICENSE | 14 + packages/replay-canvas/MIGRATION.md | 149 ++++++++++ packages/replay-canvas/README.md | 251 ++++++++++++++++ packages/replay-canvas/jest.config.ts | 17 ++ packages/replay-canvas/jest.setup.ts | 269 ++++++++++++++++++ packages/replay-canvas/package.json | 68 +++++ .../replay-canvas/rollup.bundle.config.mjs | 13 + packages/replay-canvas/rollup.npm.config.mjs | 16 ++ .../scripts/craft-pre-release.sh | 8 + .../{replay => replay-canvas}/src/canvas.ts | 2 +- packages/replay-canvas/src/index.ts | 1 + packages/replay-canvas/test/index.ts | 4 + packages/replay-canvas/tsconfig.json | 7 + packages/replay-canvas/tsconfig.test.json | 15 + packages/replay-canvas/tsconfig.types.json | 10 + packages/replay/rollup.bundle.config.mjs | 10 +- packages/replay/src/index.ts | 2 +- yarn.lock | 6 + 25 files changed, 912 insertions(+), 12 deletions(-) create mode 100644 packages/replay-canvas/.eslintignore create mode 100644 packages/replay-canvas/.eslintrc.js create mode 100644 packages/replay-canvas/.gitignore create mode 100644 packages/replay-canvas/CONTRIBUTING.md create mode 100644 packages/replay-canvas/LICENSE create mode 100644 packages/replay-canvas/MIGRATION.md create mode 100644 packages/replay-canvas/README.md create mode 100644 packages/replay-canvas/jest.config.ts create mode 100644 packages/replay-canvas/jest.setup.ts create mode 100644 packages/replay-canvas/package.json create mode 100644 packages/replay-canvas/rollup.bundle.config.mjs create mode 100644 packages/replay-canvas/rollup.npm.config.mjs create mode 100644 packages/replay-canvas/scripts/craft-pre-release.sh rename packages/{replay => replay-canvas}/src/canvas.ts (95%) create mode 100644 packages/replay-canvas/src/index.ts create mode 100644 packages/replay-canvas/test/index.ts create mode 100644 packages/replay-canvas/tsconfig.json create mode 100644 packages/replay-canvas/tsconfig.test.json create mode 100644 packages/replay-canvas/tsconfig.types.json diff --git a/package.json b/package.json index aa16add34b06..930794c00e91 100644 --- a/package.json +++ b/package.json @@ -66,6 +66,7 @@ "packages/react", "packages/remix", "packages/replay", + "packages/replay-canvas", "packages/replay-worker", "packages/serverless", "packages/svelte", diff --git a/packages/browser/package.json b/packages/browser/package.json index 196bc7c8f65c..c35135301e44 100644 --- a/packages/browser/package.json +++ b/packages/browser/package.json @@ -30,6 +30,7 @@ }, "dependencies": { "@sentry-internal/feedback": "7.93.0", + "@sentry-internal/replay-canvas": "file:../replay-canvas", "@sentry-internal/tracing": "7.93.0", "@sentry/core": "7.93.0", "@sentry/replay": "7.93.0", diff --git a/packages/browser/src/index.ts b/packages/browser/src/index.ts index 857538a81139..aae8a2f70d89 100644 --- a/packages/browser/src/index.ts +++ b/packages/browser/src/index.ts @@ -20,7 +20,7 @@ const INTEGRATIONS = { export { INTEGRATIONS as Integrations }; -export { Replay, ReplayCanvas } from '@sentry/replay'; +export { Replay } from '@sentry/replay'; export type { ReplayEventType, ReplayEventWithTime, @@ -33,6 +33,8 @@ export type { ReplaySpanFrameEvent, } from '@sentry/replay'; +export { ReplayCanvas } from '@sentry-internal/replay-canvas'; + export { Feedback } from '@sentry-internal/feedback'; export { diff --git a/packages/replay-canvas/.eslintignore b/packages/replay-canvas/.eslintignore new file mode 100644 index 000000000000..c76c6c2d64d1 --- /dev/null +++ b/packages/replay-canvas/.eslintignore @@ -0,0 +1,6 @@ +node_modules/ +build/ +demo/build/ +# TODO: Check if we can re-introduce linting in demo +demo +metrics diff --git a/packages/replay-canvas/.eslintrc.js b/packages/replay-canvas/.eslintrc.js new file mode 100644 index 000000000000..4f69827ac50b --- /dev/null +++ b/packages/replay-canvas/.eslintrc.js @@ -0,0 +1,42 @@ +// 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', + }, + }, + { + files: ['test/**/*.ts'], + + rules: { + // most of these errors come from `new Promise(process.nextTick)` + '@typescript-eslint/unbound-method': 'off', + // TODO: decide if we want to enable this again after the migration + // We can take the freedom to be a bit more lenient with tests + '@typescript-eslint/no-floating-promises': 'off', + }, + }, + { + files: ['src/types/deprecated.ts'], + rules: { + '@typescript-eslint/naming-convention': 'off', + }, + }, + ], +}; diff --git a/packages/replay-canvas/.gitignore b/packages/replay-canvas/.gitignore new file mode 100644 index 000000000000..363d3467c6fa --- /dev/null +++ b/packages/replay-canvas/.gitignore @@ -0,0 +1,4 @@ +node_modules +/*.tgz +.eslintcache +build diff --git a/packages/replay-canvas/CONTRIBUTING.md b/packages/replay-canvas/CONTRIBUTING.md new file mode 100644 index 000000000000..829930e2b05e --- /dev/null +++ b/packages/replay-canvas/CONTRIBUTING.md @@ -0,0 +1,4 @@ +## Updating the rrweb dependency + +When [updating the `rrweb` dependency](https://github.com/getsentry/sentry-javascript/blob/a493aa6a46555b944c8d896a2164bcd8b11caaf5/packages/replay/package.json?plain=1#LL55), +please be aware that [`@sentry/replay`'s README.md](https://github.com/getsentry/sentry-javascript/blob/a493aa6a46555b944c8d896a2164bcd8b11caaf5/packages/replay/README.md?plain=1#LL204) also needs to be updated. diff --git a/packages/replay-canvas/LICENSE b/packages/replay-canvas/LICENSE new file mode 100644 index 000000000000..4ac873d49f33 --- /dev/null +++ b/packages/replay-canvas/LICENSE @@ -0,0 +1,14 @@ +Copyright (c) 2022 Sentry (https://sentry.io) and individual contributors. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the Software without restriction, including without limitation the +rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit +persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the +Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/packages/replay-canvas/MIGRATION.md b/packages/replay-canvas/MIGRATION.md new file mode 100644 index 000000000000..ba6326939970 --- /dev/null +++ b/packages/replay-canvas/MIGRATION.md @@ -0,0 +1,149 @@ +# End of Replay Beta + +Sentry Replay is now out of Beta. This means that the usual stability guarantees apply. + +Because of experimentation and rapid iteration, during the Beta period some bugs and problems came up which have since been fixed/improved. +We **strongly** recommend anyone using Replay in a version before 7.39.0 to update to 7.39.0 or newer, in order to prevent running Replay with known problems that have since been fixed. + +Below you can find a list of relevant replay issues that have been resolved until 7.39.0: + +## New features / improvements + +- Remove `autoplay` attribute from audio/video tags ([#59](https://github.com/getsentry/rrweb/pull/59)) +- Exclude fetching scripts that use `` ([#52](https://github.com/getsentry/rrweb/pull/52)) +- With maskAllText, mask the attributes: placeholder, title, `aria-label` +- Lower the flush max delay from 15 seconds to 5 seconds (#6761) +- Stop recording when retry fails (#6765) +- Stop without retry when receiving bad API response (#6773) +- Send client_report when replay sending fails (#7093) +- Stop recording when hitting a rate limit (#7018) +- Allow Replay to be used in Electron renderers with nodeIntegration enabled (#6644) +- Do not renew session in error mode (#6948) +- Remove default sample rates for replay (#6878) +- Add `flush` method to integration (#6776) +- Improve compression worker & fallback behavior (#6988, #6936, #6827) +- Improve error handling (#7087, #7094, #7010, getsentry/rrweb#16, #6856) +- Add more default block filters (#7233) + +## Fixes + +- Fix masking inputs on change when `maskAllInputs:false` ([#61](https://github.com/getsentry/rrweb/pull/61)) +- More robust `rootShadowHost` check ([#50](https://github.com/getsentry/rrweb/pull/50)) +- Fix duplicated textarea value ([#62](https://github.com/getsentry/rrweb/pull/62)) +- Handle removed attributes ([#65](https://github.com/getsentry/rrweb/pull/65)) +- Change LCP calculation (#7187, #7225) +- Fix debounced flushes not respecting `maxWait` (#7207, #7208) +- Fix svgs not getting unblocked (#7132) +- Fix missing fetch/xhr requests (#7134) +- Fix feature detection of PerformanceObserver (#7029) +- Fix `checkoutEveryNms` (#6722) +- Fix incorrect uncompressed recording size due to encoding (#6740) +- Ensure dropping replays works (#6522) +- Envelope send should be awaited in try/catch (#6625) +- Improve handling of `maskAllText` selector (#6637) + +# Upgrading Replay from 7.34.0 to 7.35.0 - #6645 + +This release will remove the ability to change the default rrweb recording options (outside of privacy options). The following are the new configuration values all replays will use: +`slimDOMOptions: 'all'` - Removes `script`, comments, `favicon`, whitespace in `head`, and a few `meta` tags in `head` +`recordCanvas: false` - This option did not do anything as playback of recorded canvas means we would have to remove the playback sandbox (which is a security concern). +`inlineStylesheet: true` - Inlines styles into the recording itself instead of attempting to fetch it remotely. This means that styles in the replay will reflect the styles at the time of recording and not the current styles of the remote stylesheet. +`collectFonts: true` - Attempts to load custom fonts. +`inlineImages: false` - Does not inline images to recording and instead loads the asset remotely. During playback, images may not load due to CORS (add sentry.io as an origin). + +Additionally, we have streamlined the privacy options. The following table lists the deprecated value, and what it is replaced by: + +| deprecated key | replaced by | description | +| ---------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------ | +| maskInputOptions | mask | Use CSS selectors in `mask` in order to mask all inputs of a certain type. For example, `input[type="address"]` | +| blockSelector | block | The selector(s) can be moved directly in the `block` array. | +| blockClass | block | Convert the class name to a CSS selector and add to `block` array. For example, `first-name` becomes `.first-name`. Regexes can be moved as-is. | +| maskClass | mask | Convert the class name to a CSS selector and add to `mask` array. For example, `first-name` becomes `.first-name`. Regexes can be moved as-is. | +| maskSelector | mask | The selector(s) can be moved directly in the `mask` array. | +| ignoreClass | ignore | Convert the class name to a CSS selector and add to `ignore` array. For example, `first-name` becomes `.first-name`. Regexes can be moved as-is. | + +# Upgrading Replay from 7.31.0 to 7.32.0 + +In 7.32.0, we have removed the default values for the replay sample rates. +Previously, they were: + +* `replaysSessionSampleRate: 0.1` +* `replaysOnErrorSampleRate: 1.0` + +Now, you have to explicitly set the sample rates, otherwise they default to 0. + +# Upgrading Replay from 0.6.x to 7.24.0 + +The Sentry Replay integration was moved to the Sentry JavaScript SDK monorepo. Hence we're jumping from version 0.x to the monorepo's 7.x version which is shared across all JS SDK packages. + +## Replay sample rates are defined on top level (https://github.com/getsentry/sentry-javascript/issues/6351) + +Instead of defining the sample rates on the integration like this: + +```js +Sentry.init({ + dsn: '__DSN__', + integrations: [ + new Replay({ + sessionSampleRate: 0.1, + errorSampleRate: 1.0, + }) + ], + // ... +}); +``` + +They are now defined on the top level of the SDK: + +```js +Sentry.init({ + dsn: '__DSN__', + replaysSessionSampleRate: 0.1, + replaysOnErrorSampleRate: 1.0, + integrations: [ + new Replay({ + // other replay config still goes in here + }) + ], +}); +``` + +Note that the sample rate options inside of `new Replay({})` have been deprecated and will be removed in a future update. + +## Removed deprecated options (https://github.com/getsentry/sentry-javascript/pull/6370) + +Two options, which have been deprecated for some time, have been removed: + +* `replaysSamplingRate` - instead use `sessionSampleRate` +* `captureOnlyOnError` - instead use `errorSampleRate` + +## New NPM package structure (https://github.com/getsentry/sentry-javascript/issues/6280) + +The internal structure of the npm package has changed. This is unlikely to affect you, unless you have imported something from e.g.: + +```js +import something from '@sentry/replay/submodule'; +``` + +If you only imported from `@sentry/replay`, this will not affect you. + +## Changed type name from `IEventBuffer` to `EventBuffer` (https://github.com/getsentry/sentry-javascript/pull/6416) + +It is highly unlikely to affect anybody, but the type `IEventBuffer` was renamed to `EventBuffer` for consistency. +Unless you manually imported this and used it somewhere in your codebase, this will not affect you. + +## Session object is now a plain object (https://github.com/getsentry/sentry-javascript/pull/6417) + +The `Session` object exported from Replay is now a plain object, instead of a class. +This should not affect you unless you specifically accessed this class & did custom things with it. + +## Reduce public API of Replay integration (https://github.com/getsentry/sentry-javascript/pull/6407) + +The result of `new Replay()` now has a much more limited public API. Only the following methods are exposed: + +```js +const replay = new Replay(); + +replay.start(); +replay.stop(); +``` diff --git a/packages/replay-canvas/README.md b/packages/replay-canvas/README.md new file mode 100644 index 000000000000..091f51d785bf --- /dev/null +++ b/packages/replay-canvas/README.md @@ -0,0 +1,251 @@ +

+ + Sentry + +

+ +# Sentry Session Replay + +[![npm version](https://img.shields.io/npm/v/@sentry/replay.svg)](https://www.npmjs.com/package/@sentry/replay) +[![npm dm](https://img.shields.io/npm/dm/@sentry/replay.svg)](https://www.npmjs.com/package/@sentry/replay) +[![npm dt](https://img.shields.io/npm/dt/@sentry/replay.svg)](https://www.npmjs.com/package/@sentry/replay) + +## Pre-requisites + +`@sentry/replay` requires Node 12+, and browsers newer than IE11. + +## Installation + +Replay can be imported from `@sentry/browser`, or a respective SDK package like `@sentry/react` or `@sentry/vue`. +You don't need to install anything in order to use Session Replay. The minimum version that includes Replay is 7.27.0. + +For details on using Replay when using Sentry via the CDN bundles, see [CDN bundle](#loading-replay-as-a-cdn-bundle). + +## Setup + +To set up the integration, add the following to your Sentry initialization. Several options are supported and passable via the integration constructor. +See the [configuration section](#configuration) below for more details. + +```javascript +import * as Sentry from '@sentry/browser'; +// or e.g. import * as Sentry from '@sentry/react'; + +Sentry.init({ + dsn: '__DSN__', + + // This sets the sample rate to be 10%. You may want this to be 100% while + // in development and sample at a lower rate in production + replaysSessionSampleRate: 0.1, + + // If the entire session is not sampled, use the below sample rate to sample + // sessions when an error occurs. + replaysOnErrorSampleRate: 1.0, + + integrations: [ + new Sentry.Replay({ + // Additional SDK configuration goes in here, for example: + maskAllText: true, + blockAllMedia: true + // See below for all available options + }) + ], + // ... +}); +``` + +### Lazy loading Replay + +Replay will start automatically when you add the integration. +If you do not want to start Replay immediately (e.g. if you want to lazy-load it), +you can also use `addIntegration` to load it later: + +```js +import * as Sentry from "@sentry/react"; +import { BrowserClient } from "@sentry/browser"; + +Sentry.init({ + // Do not load it initially + integrations: [] +}); + +// Sometime later +const { Replay } = await import('@sentry/browser'); +const client = Sentry.getCurrentHub().getClient(); + +// Client can be undefined +client?.addIntegration(new Replay()); +``` + +### Identifying Users + +If you have only followed the above instructions to setup session replays, you will only see IP addresses in Sentry's UI. In order to associate a user identity to a session replay, use [`setUser`](https://docs.sentry.io/platforms/javascript/enriching-events/identify-user/). + +```javascript +import * as Sentry from "@sentry/browser"; + +Sentry.setUser({ email: "jane.doe@example.com" }); +``` + +### Stopping & starting Replays manually + +Replay recording only starts when it is included in the `integrations` array when calling `Sentry.init` or calling `addIntegration` from the a Sentry client instance. To stop recording you can call `stop()`. + +```js +import * as Sentry from "@sentry/react"; +import { BrowserClient } from "@sentry/browser"; + +const replay = new Replay(); + +Sentry.init({ + integrations: [replay] +}); + +const client = Sentry.getCurrentHub().getClient(); + +// Add replay integration, will start recoring +client?.addIntegration(replay); + +// Stop recording +replay.stop(); +``` + +When both `replaysSessionSampleRate` and `replaysOnErrorSampleRate` are `0`, recording will _not_ start. +In this case, you can manually start recording: + +```js +replay.start(); // Will start a session in "session" mode, regardless of sample rates +replay.startBuffering(); // Will start a session in "buffer" mode, regardless of sample rates +``` + + + +## Loading Replay as a CDN Bundle + +As an alternative to the NPM package, you can use Replay as a CDN bundle. +Please refer to the [Session Replay installation guide](https://docs.sentry.io/platforms/javascript/session-replay/#install) for CDN bundle instructions. + +
+Deprecated Replay integration bundle +Installing the replay integration as a secondary integration bundle to the SDK bundle was deprecated in favour of +complete CDN bundles that already contain the replay integration. No need to keep two bundles in sync anymore. +The `replay.(min.)js` bundle will be removed in v8 of the JS SDKs. + +```html + + +``` +
+ +## Sessions + +A session starts when the Session Replay SDK is first loaded and initialized. The session will continue until 5 minutes passes without any user interactions[^1] with the application *OR* until a maximum of 30 minutes have elapsed. Closing the browser tab will end the session immediately according to the rules for [SessionStorage](https://developer.mozilla.org/en-US/docs/Web/API/Window/sessionStorage). + +[^1]: An 'interaction' refers to either a mouse click or a browser navigation event. + +### Accessing the Replay Session ID + +You can get the ID of the currently running session via `replay.getReplayId()`. +This will return `undefined` if no session is ongoing. + +### Replay Captures Only on Errors + +Alternatively, rather than recording an entire session, you can capture a replay only when an error occurs. In this case, the integration will buffer up to one minute worth of events prior to the error being thrown. It will continue to record the session following the rules above regarding session life and activity. Read the [sampling](#Sampling) section for configuration options. + +## Sampling + +Sampling allows you to control how much of your website's traffic will result in a Session Replay. There are two sample rates you can adjust to get the replays more relevant to your interests: + +- `replaysSessionSampleRate` - The sample rate for replays that begin recording immediately and last the entirety of the user's session. +- `replaysOnErrorSampleRate` - The sample rate for replays that are recorded when an error happens. This type of replay will record up to a minute of events prior to the error and continue recording until the session ends. + +When Replay is initialized, we check the `replaysSessionSampleRate`. +If it is sampled, then we start recording & sending Replay data immediately. +Else, if `replaysOnErrorSampleRate > 0`, we'll start recording in buffering mode. +In this mode, whenever an error occurs we'll check `replaysOnErrorSampleRate`. +If it is sampled, when we'll upload the Replay to Sentry and continue recording normally. + +## Configuration + +### SDK Configuration + +The following options can be configured on the root level of your browser-based Sentry SDK, in `init({})`: + + +| key | type | default | description | +| ------------------- | ------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| replaysSessionSampleRate | number | `0` | The sample rate for replays that begin recording immediately and last the entirety of the user's session. 1.0 will collect all replays, 0 will collect no replays. | +| replaysOnErrorSampleRate | number | `0` |The sample rate for replays that are recorded when an error happens. This type of replay will record up to a minute of events prior to the error and continue recording until the session ends. 1.0 capturing all sessions with an error, and 0 capturing none. + +### General Integration Configuration + +The following options can be configured as options to the integration, in `new Replay({})`: + +| key | type | default | description | +| ------------------- | ------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| stickySession | boolean | `true` | Keep track of the user across page loads. Note a single user using multiple tabs will result in multiple sessions. Closing a tab will result in the session being closed as well. | + + +### Privacy Configuration + +The following options can be configured as options to the integration, in `new Replay({})`: + +| key | type | default | description | +| ---------------- | ------------------------ | --------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- | +| maskAllText | boolean | `true` | Mask _all_ text content. Will pass text content through `maskFn` before sending to server. | +| maskAllInputs | boolean | `true` | Mask values of `` elements. Passes input values through `maskInputFn` before sending to server. | +| blockAllMedia | boolean | `true` | Block _all_ media elements (`img, svg, video, object, picture, embed, map, audio`) | +| maskFn | (text: string) => string | `(text) => '*'.repeat(text.length)` | Function to customize how text content is masked before sending to server. By default, masks text with `*`. | +| block | Array | `.sentry-block, [data-sentry-block]` | Redact any elements that match the DOM selectors. See [privacy](#blocking) section for an example. | +| unblock | Array | `.sentry-unblock, [data-sentry-unblock]`| Do not redact any elements that match the DOM selectors. Useful when using `blockAllMedia`. See [privacy](#blocking) section for an example. | +| mask | Array | `.sentry-mask, [data-sentry-mask]` | Mask all elements that match the given DOM selectors. See [privacy](#masking) section for an example. | +| unmask | Array | `.sentry-unmask, [data-sentry-unmask]` | Unmask all elements that match the given DOM selectors. Useful when using `maskAllText`. See [privacy](#masking) section for an example. | +| ignore | Array | `.sentry-ignore, [data-sentry-ignore]` | Ignores all events on the matching input fields. See [privacy](#ignoring) section for an example. | + +#### Deprecated options +In order to streamline our privacy options, the following have been deprecated in favor for the respective options above. + +| deprecated key | replaced by | description | +| ---------------- | ----------- | ----------- | +| maskInputOptions | mask | Use CSS selectors in `mask` in order to mask all inputs of a certain type. For example, `input[type="address"]` | +| blockSelector | block | The selector(s) can be moved directly in the `block` array. | +| blockClass | block | Convert the class name to a CSS selector and add to `block` array. For example, `first-name` becomes `.first-name`. Regexes can be moved as-is. | +| maskClass | mask | Convert the class name to a CSS selector and add to `mask` array. For example, `first-name` becomes `.first-name`. Regexes can be moved as-is. | +| maskSelector | mask | The selector(s) can be moved directly in the `mask` array. | +| ignoreClass | ignore | Convert the class name to a CSS selector and add to `ignore` array. For example, `first-name` becomes `.first-name`. Regexes can be moved as-is. | + +## Privacy +There are several ways to deal with PII. By default, the integration will mask all text content with `*` and block all media elements (`img, svg, video, object, picture, embed, map, audio`). This can be disabled by setting `maskAllText` to `false`. It is also possible to add the following CSS classes to specific DOM elements to prevent recording its contents: `sentry-block`, `sentry-ignore`, and `sentry-mask`. The following sections will show examples of how content is handled by the differing methods. + +### Masking +Masking replaces the text content with something else. The default masking behavior is to replace each character with a `*`. In this example the relevant html code is: `...
`. +![Masking example](https://user-images.githubusercontent.com/79684/193118192-dee1d3d8-5813-47e8-b532-f9ee1c8714b3.png) + +### Blocking +Blocking replaces the element with a placeholder that has the same dimensions. The recording will show an empty space where the content was. In this example the relevant html code is: `...
`. +![Blocking example](https://user-images.githubusercontent.com/79684/193118084-51a589fc-2160-476a-a8dc-b681eddb136c.png) + +### Ignoring +Ignoring only applies to form inputs. Events will be ignored on the input element so that the replay does not show what occurs inside of the input. In the below example, notice how the results in the table below the input changes, but no text is visible in the input. + +https://user-images.githubusercontent.com/79684/192815134-a6451c3f-d3cb-455f-a699-7c3fe04d0a2e.mov + +## Error Linking + +Currently, errors that happen on the page while a replay is running are linked to the Replay, +making it as easy as possible to jump between related issues/replays. +However, please note that it is _possible_ that the error count reported on the Replay Detail page +does not match the actual errors that have been captured. +The reason for that is that errors _can_ be lost, e.g. a network request fails, or similar. +This should not happen to often, but be aware that it is theoretically possible. + +## Manually sending replay data + +You can use `replay.flush()` to immediately send all currently captured replay data. +When Replay is currently in buffering mode, this will send up to the last 60 seconds of replay data, +and also continue sending afterwards, similar to when an error happens & is recorded. diff --git a/packages/replay-canvas/jest.config.ts b/packages/replay-canvas/jest.config.ts new file mode 100644 index 000000000000..90a3cf471f8d --- /dev/null +++ b/packages/replay-canvas/jest.config.ts @@ -0,0 +1,17 @@ +import type { Config } from '@jest/types'; +import { jsWithTs as jsWithTsPreset } from 'ts-jest/presets'; + +export default async (): Promise => { + return { + ...jsWithTsPreset, + globals: { + 'ts-jest': { + tsconfig: '/tsconfig.test.json', + }, + __DEBUG_BUILD__: true, + }, + setupFilesAfterEnv: ['./jest.setup.ts'], + testEnvironment: 'jsdom', + testMatch: ['/test/**/*(*.)@(spec|test).ts'], + }; +}; diff --git a/packages/replay-canvas/jest.setup.ts b/packages/replay-canvas/jest.setup.ts new file mode 100644 index 000000000000..09e6c68a4483 --- /dev/null +++ b/packages/replay-canvas/jest.setup.ts @@ -0,0 +1,269 @@ +import { TextEncoder } from 'util'; +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +import { getCurrentHub } from '@sentry/core'; +import type { ReplayRecordingData, Transport } from '@sentry/types'; +import * as SentryUtils from '@sentry/utils'; + +import type { ReplayContainer, Session } from './src/types'; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +(global as any).TextEncoder = TextEncoder; + +type MockTransport = jest.MockedFunction; + +jest.spyOn(SentryUtils, 'isBrowser').mockImplementation(() => true); + +type EnvelopeHeader = { + event_id: string; + sent_at: string; + sdk: { + name: string; + version?: string; + }; +}; + +type ReplayEventHeader = { type: 'replay_event' }; +type ReplayEventPayload = Record; +type RecordingHeader = { type: 'replay_recording'; length: number }; +type RecordingPayloadHeader = Record; +type SentReplayExpected = { + envelopeHeader?: EnvelopeHeader; + replayEventHeader?: ReplayEventHeader; + replayEventPayload?: ReplayEventPayload; + recordingHeader?: RecordingHeader; + recordingPayloadHeader?: RecordingPayloadHeader; + recordingData?: ReplayRecordingData; +}; + +// eslint-disable-next-line @typescript-eslint/explicit-function-return-type +const toHaveSameSession = function (received: jest.Mocked, expected: undefined | Session) { + const pass = this.equals(received.session?.id, expected?.id) as boolean; + + const options = { + isNot: this.isNot, + promise: this.promise, + }; + + return { + pass, + message: () => + `${this.utils.matcherHint( + 'toHaveSameSession', + undefined, + undefined, + options, + )}\n\n${this.utils.printDiffOrStringify(expected, received.session, 'Expected', 'Received')}`, + }; +}; + +type Result = { + passed: boolean; + key: string; + expectedVal: SentReplayExpected[keyof SentReplayExpected]; + actualVal: SentReplayExpected[keyof SentReplayExpected]; +}; +type Call = [ + EnvelopeHeader, + [ + [ReplayEventHeader | undefined, ReplayEventPayload | undefined], + [RecordingHeader | undefined, RecordingPayloadHeader | undefined], + ], +]; +type CheckCallForSentReplayResult = { pass: boolean; call: Call | undefined; results: Result[] }; + +function checkCallForSentReplay( + call: Call | undefined, + expected?: SentReplayExpected | { sample: SentReplayExpected; inverse: boolean }, +): CheckCallForSentReplayResult { + const envelopeHeader = call?.[0]; + const envelopeItems = call?.[1] || [[], []]; + const [[replayEventHeader, replayEventPayload], [recordingHeader, recordingPayload] = []] = envelopeItems; + + // @ts-expect-error recordingPayload is always a string in our tests + const [recordingPayloadHeader, recordingData] = recordingPayload?.split('\n') || []; + + const actualObj: Required = { + // @ts-expect-error Custom envelope + envelopeHeader: envelopeHeader, + // @ts-expect-error Custom envelope + replayEventHeader: replayEventHeader, + // @ts-expect-error Custom envelope + replayEventPayload: replayEventPayload, + // @ts-expect-error Custom envelope + recordingHeader: recordingHeader, + recordingPayloadHeader: recordingPayloadHeader && JSON.parse(recordingPayloadHeader), + recordingData, + }; + + const isObjectContaining = expected && 'sample' in expected && 'inverse' in expected; + const expectedObj = isObjectContaining + ? (expected as { sample: SentReplayExpected }).sample + : (expected as SentReplayExpected); + + if (isObjectContaining) { + console.warn('`expect.objectContaining` is unnecessary when using the `toHaveSentReplay` matcher'); + } + + const results = expected + ? Object.keys(expectedObj) + .map(key => { + const actualVal = actualObj[key as keyof SentReplayExpected]; + const expectedVal = expectedObj[key as keyof SentReplayExpected]; + const passed = !expectedVal || this.equals(actualVal, expectedVal); + + return { passed, key, expectedVal, actualVal }; + }) + .filter(({ passed }) => !passed) + : []; + + const pass = Boolean(call && (!expected || results.length === 0)); + + return { + pass, + call, + results, + }; +} + +/** + * Only want calls that send replay events, i.e. ignore error events + */ +// eslint-disable-next-line @typescript-eslint/no-explicit-any +function getReplayCalls(calls: any[][][]): any[][][] { + return calls + .map(call => { + const arg = call[0]; + if (arg.length !== 2) { + return []; + } + + if (!arg[1][0].find(({ type }: { type: string }) => ['replay_event', 'replay_recording'].includes(type))) { + return []; + } + + return [arg]; + }) + .filter(Boolean); +} + +/** + * Checks all calls to `fetch` and ensures a replay was uploaded by + * checking the `fetch()` request's body. + */ +// eslint-disable-next-line @typescript-eslint/explicit-function-return-type +const toHaveSentReplay = function ( + _received: jest.Mocked, + expected?: SentReplayExpected | { sample: SentReplayExpected; inverse: boolean }, +) { + const { calls } = (getCurrentHub().getClient()?.getTransport()?.send as MockTransport).mock; + + let result: CheckCallForSentReplayResult; + + const expectedKeysLength = expected + ? ('sample' in expected ? Object.keys(expected.sample) : Object.keys(expected)).length + : 0; + + const replayCalls = getReplayCalls(calls); + + for (const currentCall of replayCalls) { + result = checkCallForSentReplay.call(this, currentCall[0], expected); + if (result.pass) { + break; + } + + // stop on the first call where any of the expected obj passes + if (result.results.length < expectedKeysLength) { + break; + } + } + + // @ts-expect-error use before assigned + const { results, call, pass } = result; + + const options = { + isNot: this.isNot, + promise: this.promise, + }; + + return { + pass, + message: () => + !call + ? pass + ? 'Expected Replay to not have been sent, but a request was attempted' + : 'Expected Replay to have been sent, but a request was not attempted' + : `${this.utils.matcherHint('toHaveSentReplay', undefined, undefined, options)}\n\n${results + .map(({ key, expectedVal, actualVal }: Result) => + this.utils.printDiffOrStringify( + expectedVal, + actualVal, + `Expected (key: ${key})`, + `Received (key: ${key})`, + ), + ) + .join('\n')}`, + }; +}; + +/** + * Checks the last call to `fetch` and ensures a replay was uploaded by + * checking the `fetch()` request's body. + */ +// eslint-disable-next-line @typescript-eslint/explicit-function-return-type +const toHaveLastSentReplay = function ( + _received: jest.Mocked, + expected?: SentReplayExpected | { sample: SentReplayExpected; inverse: boolean }, +) { + const { calls } = (getCurrentHub().getClient()?.getTransport()?.send as MockTransport).mock; + const replayCalls = getReplayCalls(calls); + + const lastCall = replayCalls[calls.length - 1]?.[0]; + + const { results, call, pass } = checkCallForSentReplay.call(this, lastCall, expected); + + const options = { + isNot: this.isNot, + promise: this.promise, + }; + + return { + pass, + message: () => + !call + ? pass + ? 'Expected Replay to not have been sent, but a request was attempted' + : 'Expected Replay to have last been sent, but a request was not attempted' + : `${this.utils.matcherHint('toHaveSentReplay', undefined, undefined, options)}\n\n${results + .map(({ key, expectedVal, actualVal }: Result) => + this.utils.printDiffOrStringify( + expectedVal, + actualVal, + `Expected (key: ${key})`, + `Received (key: ${key})`, + ), + ) + .join('\n')}`, + }; +}; + +expect.extend({ + toHaveSameSession, + toHaveSentReplay, + toHaveLastSentReplay, +}); + +declare global { + // eslint-disable-next-line @typescript-eslint/no-namespace + namespace jest { + interface AsymmetricMatchers { + toHaveSentReplay(expected?: SentReplayExpected): void; + toHaveLastSentReplay(expected?: SentReplayExpected): void; + toHaveSameSession(expected: undefined | Session): void; + } + interface Matchers { + toHaveSentReplay(expected?: SentReplayExpected): R; + toHaveLastSentReplay(expected?: SentReplayExpected): R; + toHaveSameSession(expected: undefined | Session): R; + } + } +} diff --git a/packages/replay-canvas/package.json b/packages/replay-canvas/package.json new file mode 100644 index 000000000000..288ff6cecc99 --- /dev/null +++ b/packages/replay-canvas/package.json @@ -0,0 +1,68 @@ +{ + "name": "@sentry-internal/replay-canvas", + "version": "7.92.0", + "description": "Replay canvas integration", + "main": "build/npm/cjs/index.js", + "module": "build/npm/esm/index.js", + "types": "build/npm/types/index.d.ts", + "typesVersions": { + "<4.9": { + "build/npm/types/index.d.ts": [ + "build/npm/types-ts3.8/index.d.ts" + ] + } + }, + "files": [ + "cjs", + "esm", + "types", + "types-ts3.8" + ], + "sideEffects": false, + "scripts": { + "build": "run-p build:transpile build:types build:bundle", + "build:transpile": "rollup -c rollup.npm.config.mjs", + "build:bundle": "rollup -c rollup.bundle.config.mjs", + "build:dev": "run-p build:transpile build:types", + "build:types": "run-s build:types:core build:types:downlevel", + "build:types:core": "tsc -p tsconfig.types.json", + "build:types:downlevel": "yarn downlevel-dts build/npm/types build/npm/types-ts3.8 --to ts3.8", + "build:watch": "run-p build:transpile:watch build:bundle:watch build:types:watch", + "build:dev:watch": "run-p build:transpile:watch build:types:watch", + "build:transpile:watch": "yarn build:transpile --watch", + "build:bundle:watch": "yarn build:bundle --watch", + "build:types:watch": "tsc -p tsconfig.types.json --watch", + "build:tarball": "ts-node ../../scripts/prepack.ts --bundles && npm pack ./build/npm", + "circularDepCheck": "madge --circular src/index.ts", + "clean": "rimraf build sentry-replay-*.tgz", + "fix": "eslint . --format stylish --fix", + "lint": "eslint . --format stylish", + "test": "jest", + "test:watch": "jest --watch", + "yalc:publish": "ts-node ../../scripts/prepack.ts --bundles && yalc publish ./build/npm --push --sig" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/getsentry/sentry-javascript.git" + }, + "author": "Sentry", + "license": "MIT", + "bugs": { + "url": "https://github.com/getsentry/sentry-javascript/issues" + }, + "homepage": "https://docs.sentry.io/platforms/javascript/session-replay/", + "devDependencies": { + "@babel/core": "^7.17.5", + "@sentry-internal/rrweb": "2.7.3" + }, + "dependencies": { + "@sentry/replay": "7.92.0", + "@sentry/types": "7.92.0" + }, + "engines": { + "node": ">=12" + }, + "volta": { + "extends": "../../package.json" + } +} diff --git a/packages/replay-canvas/rollup.bundle.config.mjs b/packages/replay-canvas/rollup.bundle.config.mjs new file mode 100644 index 000000000000..1575b8114594 --- /dev/null +++ b/packages/replay-canvas/rollup.bundle.config.mjs @@ -0,0 +1,13 @@ +import { makeBaseBundleConfig, makeBundleConfigVariants } from '@sentry-internal/rollup-utils'; + +const baseBundleConfig = makeBaseBundleConfig({ + bundleType: 'addon', + entrypoints: ['src/index.ts'], + jsVersion: 'es6', + licenseTitle: '@sentry-internal/replay-canvas', + outputFileBase: () => 'bundles/replay-canvas', +}); + +const builds = makeBundleConfigVariants(baseBundleConfig); + +export default builds; diff --git a/packages/replay-canvas/rollup.npm.config.mjs b/packages/replay-canvas/rollup.npm.config.mjs new file mode 100644 index 000000000000..8c50a33f0afb --- /dev/null +++ b/packages/replay-canvas/rollup.npm.config.mjs @@ -0,0 +1,16 @@ +import { makeBaseNPMConfig, makeNPMConfigVariants } from '@sentry-internal/rollup-utils'; + +export default makeNPMConfigVariants( + makeBaseNPMConfig({ + hasBundles: true, + packageSpecificConfig: { + output: { + // set exports to 'named' or 'auto' so that rollup doesn't warn + exports: 'named', + // set preserveModules to false because for Replay we actually want + // to bundle everything into one file. + preserveModules: false, + }, + }, + }), +); diff --git a/packages/replay-canvas/scripts/craft-pre-release.sh b/packages/replay-canvas/scripts/craft-pre-release.sh new file mode 100644 index 000000000000..bae7c3246cdb --- /dev/null +++ b/packages/replay-canvas/scripts/craft-pre-release.sh @@ -0,0 +1,8 @@ +#!/bin/bash +set -eux +OLD_VERSION="${1}" +NEW_VERSION="${2}" + +# Do not tag and commit changes made by "npm version" +export npm_config_git_tag_version=false +npm version "${NEW_VERSION}" diff --git a/packages/replay/src/canvas.ts b/packages/replay-canvas/src/canvas.ts similarity index 95% rename from packages/replay/src/canvas.ts rename to packages/replay-canvas/src/canvas.ts index d4b869c2f024..c15a0e2c9a4d 100644 --- a/packages/replay/src/canvas.ts +++ b/packages/replay-canvas/src/canvas.ts @@ -1,6 +1,6 @@ import { getCanvasManager } from '@sentry-internal/rrweb'; import type { Integration } from '@sentry/types'; -import type { ReplayConfiguration } from './types'; +import type { ReplayConfiguration } from '@sentry/replay'; interface ReplayCanvasOptions { quality: 'low' | 'medium' | 'high'; diff --git a/packages/replay-canvas/src/index.ts b/packages/replay-canvas/src/index.ts new file mode 100644 index 000000000000..90d0f74188af --- /dev/null +++ b/packages/replay-canvas/src/index.ts @@ -0,0 +1 @@ +export { ReplayCanvas } from './canvas'; diff --git a/packages/replay-canvas/test/index.ts b/packages/replay-canvas/test/index.ts new file mode 100644 index 000000000000..ed4b82a6c780 --- /dev/null +++ b/packages/replay-canvas/test/index.ts @@ -0,0 +1,4 @@ +export * from './mocks/mockRrweb'; // XXX: Needs to happen before `mockSdk` or importing Replay! +export * from './mocks/mockSdk'; + +export const BASE_TIMESTAMP = new Date('2020-02-02 00:00:00').getTime(); // 1580619600000 diff --git a/packages/replay-canvas/tsconfig.json b/packages/replay-canvas/tsconfig.json new file mode 100644 index 000000000000..f8f54556da93 --- /dev/null +++ b/packages/replay-canvas/tsconfig.json @@ -0,0 +1,7 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "module": "esnext" + }, + "include": ["src/**/*.ts"] +} diff --git a/packages/replay-canvas/tsconfig.test.json b/packages/replay-canvas/tsconfig.test.json new file mode 100644 index 000000000000..ad87caa06c48 --- /dev/null +++ b/packages/replay-canvas/tsconfig.test.json @@ -0,0 +1,15 @@ +{ + "extends": "./tsconfig.json", + + "include": ["test/**/*.ts", "jest.config.ts", "jest.setup.ts"], + + "compilerOptions": { + "types": ["node", "jest"], + "esModuleInterop": true, + "allowJs": true, + "noImplicitAny": true, + "noImplicitThis": false, + "strictNullChecks": true, + "strictPropertyInitialization": false + } +} diff --git a/packages/replay-canvas/tsconfig.types.json b/packages/replay-canvas/tsconfig.types.json new file mode 100644 index 000000000000..374fd9bc9364 --- /dev/null +++ b/packages/replay-canvas/tsconfig.types.json @@ -0,0 +1,10 @@ +{ + "extends": "./tsconfig.json", + + "compilerOptions": { + "declaration": true, + "declarationMap": true, + "emitDeclarationOnly": true, + "outDir": "build/npm/types" + } +} diff --git a/packages/replay/rollup.bundle.config.mjs b/packages/replay/rollup.bundle.config.mjs index b254dae8d8d2..bc81b0cb46a4 100644 --- a/packages/replay/rollup.bundle.config.mjs +++ b/packages/replay/rollup.bundle.config.mjs @@ -8,14 +8,6 @@ const baseBundleConfig = makeBaseBundleConfig({ outputFileBase: () => 'bundles/replay', }); -const baseCanvasBundleConfig = makeBaseBundleConfig({ - bundleType: 'addon', - entrypoints: ['src/canvas.ts'], - jsVersion: 'es6', - licenseTitle: '@sentry/replaycanvas', - outputFileBase: () => 'bundles/replaycanvas', -}); - -const builds = [...makeBundleConfigVariants(baseBundleConfig), ...makeBundleConfigVariants(baseCanvasBundleConfig)]; +const builds = makeBundleConfigVariants(baseBundleConfig); export default builds; diff --git a/packages/replay/src/index.ts b/packages/replay/src/index.ts index d6ea1de76732..fa7363fae13f 100644 --- a/packages/replay/src/index.ts +++ b/packages/replay/src/index.ts @@ -1,7 +1,7 @@ export { Replay } from './integration'; -export { ReplayCanvas } from './canvas'; export type { + ReplayConfiguration, ReplayEventType, ReplayEventWithTime, ReplayBreadcrumbFrame, diff --git a/yarn.lock b/yarn.lock index d888e192dd15..8ce0595de8f3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5231,6 +5231,12 @@ semver "7.3.2" semver-intersect "1.4.0" +"@sentry-internal/replay-canvas@file:packages/replay-canvas": + version "7.92.0" + dependencies: + "@sentry/replay" "7.92.0" + "@sentry/types" "7.92.0" + "@sentry-internal/rrdom@2.7.3": version "2.7.3" resolved "https://registry.yarnpkg.com/@sentry-internal/rrdom/-/rrdom-2.7.3.tgz#2efe68a9cf23de9a8970acf4303748cdd7866b20" From 230b5bb5dca33cd2b9dc1a8b193b7a6fd45b0a7b Mon Sep 17 00:00:00 2001 From: Billy Vong Date: Tue, 9 Jan 2024 12:34:49 -0500 Subject: [PATCH 05/50] cleanup repo --- packages/replay-canvas/MIGRATION.md | 149 ------- packages/replay-canvas/README.md | 218 +--------- packages/replay-canvas/jest.config.js | 6 + packages/replay-canvas/jest.config.ts | 17 - packages/replay-canvas/jest.setup.ts | 269 ------------ packages/replay-canvas/src/canvas.ts | 2 +- packages/replay-canvas/test/canvas.test.ts | 27 ++ packages/replay-canvas/test/index.ts | 4 - packages/replay-canvas/yarn.lock | 469 +++++++++++++++++++++ scripts/node-unit-tests.ts | 1 + 10 files changed, 515 insertions(+), 647 deletions(-) delete mode 100644 packages/replay-canvas/MIGRATION.md create mode 100644 packages/replay-canvas/jest.config.js delete mode 100644 packages/replay-canvas/jest.config.ts delete mode 100644 packages/replay-canvas/jest.setup.ts create mode 100644 packages/replay-canvas/test/canvas.test.ts delete mode 100644 packages/replay-canvas/test/index.ts create mode 100644 packages/replay-canvas/yarn.lock diff --git a/packages/replay-canvas/MIGRATION.md b/packages/replay-canvas/MIGRATION.md deleted file mode 100644 index ba6326939970..000000000000 --- a/packages/replay-canvas/MIGRATION.md +++ /dev/null @@ -1,149 +0,0 @@ -# End of Replay Beta - -Sentry Replay is now out of Beta. This means that the usual stability guarantees apply. - -Because of experimentation and rapid iteration, during the Beta period some bugs and problems came up which have since been fixed/improved. -We **strongly** recommend anyone using Replay in a version before 7.39.0 to update to 7.39.0 or newer, in order to prevent running Replay with known problems that have since been fixed. - -Below you can find a list of relevant replay issues that have been resolved until 7.39.0: - -## New features / improvements - -- Remove `autoplay` attribute from audio/video tags ([#59](https://github.com/getsentry/rrweb/pull/59)) -- Exclude fetching scripts that use `` ([#52](https://github.com/getsentry/rrweb/pull/52)) -- With maskAllText, mask the attributes: placeholder, title, `aria-label` -- Lower the flush max delay from 15 seconds to 5 seconds (#6761) -- Stop recording when retry fails (#6765) -- Stop without retry when receiving bad API response (#6773) -- Send client_report when replay sending fails (#7093) -- Stop recording when hitting a rate limit (#7018) -- Allow Replay to be used in Electron renderers with nodeIntegration enabled (#6644) -- Do not renew session in error mode (#6948) -- Remove default sample rates for replay (#6878) -- Add `flush` method to integration (#6776) -- Improve compression worker & fallback behavior (#6988, #6936, #6827) -- Improve error handling (#7087, #7094, #7010, getsentry/rrweb#16, #6856) -- Add more default block filters (#7233) - -## Fixes - -- Fix masking inputs on change when `maskAllInputs:false` ([#61](https://github.com/getsentry/rrweb/pull/61)) -- More robust `rootShadowHost` check ([#50](https://github.com/getsentry/rrweb/pull/50)) -- Fix duplicated textarea value ([#62](https://github.com/getsentry/rrweb/pull/62)) -- Handle removed attributes ([#65](https://github.com/getsentry/rrweb/pull/65)) -- Change LCP calculation (#7187, #7225) -- Fix debounced flushes not respecting `maxWait` (#7207, #7208) -- Fix svgs not getting unblocked (#7132) -- Fix missing fetch/xhr requests (#7134) -- Fix feature detection of PerformanceObserver (#7029) -- Fix `checkoutEveryNms` (#6722) -- Fix incorrect uncompressed recording size due to encoding (#6740) -- Ensure dropping replays works (#6522) -- Envelope send should be awaited in try/catch (#6625) -- Improve handling of `maskAllText` selector (#6637) - -# Upgrading Replay from 7.34.0 to 7.35.0 - #6645 - -This release will remove the ability to change the default rrweb recording options (outside of privacy options). The following are the new configuration values all replays will use: -`slimDOMOptions: 'all'` - Removes `script`, comments, `favicon`, whitespace in `head`, and a few `meta` tags in `head` -`recordCanvas: false` - This option did not do anything as playback of recorded canvas means we would have to remove the playback sandbox (which is a security concern). -`inlineStylesheet: true` - Inlines styles into the recording itself instead of attempting to fetch it remotely. This means that styles in the replay will reflect the styles at the time of recording and not the current styles of the remote stylesheet. -`collectFonts: true` - Attempts to load custom fonts. -`inlineImages: false` - Does not inline images to recording and instead loads the asset remotely. During playback, images may not load due to CORS (add sentry.io as an origin). - -Additionally, we have streamlined the privacy options. The following table lists the deprecated value, and what it is replaced by: - -| deprecated key | replaced by | description | -| ---------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------ | -| maskInputOptions | mask | Use CSS selectors in `mask` in order to mask all inputs of a certain type. For example, `input[type="address"]` | -| blockSelector | block | The selector(s) can be moved directly in the `block` array. | -| blockClass | block | Convert the class name to a CSS selector and add to `block` array. For example, `first-name` becomes `.first-name`. Regexes can be moved as-is. | -| maskClass | mask | Convert the class name to a CSS selector and add to `mask` array. For example, `first-name` becomes `.first-name`. Regexes can be moved as-is. | -| maskSelector | mask | The selector(s) can be moved directly in the `mask` array. | -| ignoreClass | ignore | Convert the class name to a CSS selector and add to `ignore` array. For example, `first-name` becomes `.first-name`. Regexes can be moved as-is. | - -# Upgrading Replay from 7.31.0 to 7.32.0 - -In 7.32.0, we have removed the default values for the replay sample rates. -Previously, they were: - -* `replaysSessionSampleRate: 0.1` -* `replaysOnErrorSampleRate: 1.0` - -Now, you have to explicitly set the sample rates, otherwise they default to 0. - -# Upgrading Replay from 0.6.x to 7.24.0 - -The Sentry Replay integration was moved to the Sentry JavaScript SDK monorepo. Hence we're jumping from version 0.x to the monorepo's 7.x version which is shared across all JS SDK packages. - -## Replay sample rates are defined on top level (https://github.com/getsentry/sentry-javascript/issues/6351) - -Instead of defining the sample rates on the integration like this: - -```js -Sentry.init({ - dsn: '__DSN__', - integrations: [ - new Replay({ - sessionSampleRate: 0.1, - errorSampleRate: 1.0, - }) - ], - // ... -}); -``` - -They are now defined on the top level of the SDK: - -```js -Sentry.init({ - dsn: '__DSN__', - replaysSessionSampleRate: 0.1, - replaysOnErrorSampleRate: 1.0, - integrations: [ - new Replay({ - // other replay config still goes in here - }) - ], -}); -``` - -Note that the sample rate options inside of `new Replay({})` have been deprecated and will be removed in a future update. - -## Removed deprecated options (https://github.com/getsentry/sentry-javascript/pull/6370) - -Two options, which have been deprecated for some time, have been removed: - -* `replaysSamplingRate` - instead use `sessionSampleRate` -* `captureOnlyOnError` - instead use `errorSampleRate` - -## New NPM package structure (https://github.com/getsentry/sentry-javascript/issues/6280) - -The internal structure of the npm package has changed. This is unlikely to affect you, unless you have imported something from e.g.: - -```js -import something from '@sentry/replay/submodule'; -``` - -If you only imported from `@sentry/replay`, this will not affect you. - -## Changed type name from `IEventBuffer` to `EventBuffer` (https://github.com/getsentry/sentry-javascript/pull/6416) - -It is highly unlikely to affect anybody, but the type `IEventBuffer` was renamed to `EventBuffer` for consistency. -Unless you manually imported this and used it somewhere in your codebase, this will not affect you. - -## Session object is now a plain object (https://github.com/getsentry/sentry-javascript/pull/6417) - -The `Session` object exported from Replay is now a plain object, instead of a class. -This should not affect you unless you specifically accessed this class & did custom things with it. - -## Reduce public API of Replay integration (https://github.com/getsentry/sentry-javascript/pull/6407) - -The result of `new Replay()` now has a much more limited public API. Only the following methods are exposed: - -```js -const replay = new Replay(); - -replay.start(); -replay.stop(); -``` diff --git a/packages/replay-canvas/README.md b/packages/replay-canvas/README.md index 091f51d785bf..c074bedb01e0 100644 --- a/packages/replay-canvas/README.md +++ b/packages/replay-canvas/README.md @@ -4,7 +4,7 @@

-# Sentry Session Replay +# Sentry Session Replay with Canvas [![npm version](https://img.shields.io/npm/v/@sentry/replay.svg)](https://www.npmjs.com/package/@sentry/replay) [![npm dm](https://img.shields.io/npm/dm/@sentry/replay.svg)](https://www.npmjs.com/package/@sentry/replay) @@ -16,15 +16,20 @@ ## Installation -Replay can be imported from `@sentry/browser`, or a respective SDK package like `@sentry/react` or `@sentry/vue`. +Replay and ReplayCanvas can be imported from `@sentry/browser`, or a respective SDK package like `@sentry/react` or `@sentry/vue`. You don't need to install anything in order to use Session Replay. The minimum version that includes Replay is 7.27.0. For details on using Replay when using Sentry via the CDN bundles, see [CDN bundle](#loading-replay-as-a-cdn-bundle). ## Setup -To set up the integration, add the following to your Sentry initialization. Several options are supported and passable via the integration constructor. -See the [configuration section](#configuration) below for more details. +To set up the canvas integration, add the following to your Sentry integrations: + +```javascript +new Sentry.ReplayCanvas(), +``` + +### Full Example ```javascript import * as Sentry from '@sentry/browser'; @@ -42,210 +47,9 @@ Sentry.init({ replaysOnErrorSampleRate: 1.0, integrations: [ - new Sentry.Replay({ - // Additional SDK configuration goes in here, for example: - maskAllText: true, - blockAllMedia: true - // See below for all available options - }) + new Sentry.Replay(), + new Sentry.ReplayCanvas(), ], // ... }); ``` - -### Lazy loading Replay - -Replay will start automatically when you add the integration. -If you do not want to start Replay immediately (e.g. if you want to lazy-load it), -you can also use `addIntegration` to load it later: - -```js -import * as Sentry from "@sentry/react"; -import { BrowserClient } from "@sentry/browser"; - -Sentry.init({ - // Do not load it initially - integrations: [] -}); - -// Sometime later -const { Replay } = await import('@sentry/browser'); -const client = Sentry.getCurrentHub().getClient(); - -// Client can be undefined -client?.addIntegration(new Replay()); -``` - -### Identifying Users - -If you have only followed the above instructions to setup session replays, you will only see IP addresses in Sentry's UI. In order to associate a user identity to a session replay, use [`setUser`](https://docs.sentry.io/platforms/javascript/enriching-events/identify-user/). - -```javascript -import * as Sentry from "@sentry/browser"; - -Sentry.setUser({ email: "jane.doe@example.com" }); -``` - -### Stopping & starting Replays manually - -Replay recording only starts when it is included in the `integrations` array when calling `Sentry.init` or calling `addIntegration` from the a Sentry client instance. To stop recording you can call `stop()`. - -```js -import * as Sentry from "@sentry/react"; -import { BrowserClient } from "@sentry/browser"; - -const replay = new Replay(); - -Sentry.init({ - integrations: [replay] -}); - -const client = Sentry.getCurrentHub().getClient(); - -// Add replay integration, will start recoring -client?.addIntegration(replay); - -// Stop recording -replay.stop(); -``` - -When both `replaysSessionSampleRate` and `replaysOnErrorSampleRate` are `0`, recording will _not_ start. -In this case, you can manually start recording: - -```js -replay.start(); // Will start a session in "session" mode, regardless of sample rates -replay.startBuffering(); // Will start a session in "buffer" mode, regardless of sample rates -``` - - - -## Loading Replay as a CDN Bundle - -As an alternative to the NPM package, you can use Replay as a CDN bundle. -Please refer to the [Session Replay installation guide](https://docs.sentry.io/platforms/javascript/session-replay/#install) for CDN bundle instructions. - -
-Deprecated Replay integration bundle -Installing the replay integration as a secondary integration bundle to the SDK bundle was deprecated in favour of -complete CDN bundles that already contain the replay integration. No need to keep two bundles in sync anymore. -The `replay.(min.)js` bundle will be removed in v8 of the JS SDKs. - -```html - - -``` -
- -## Sessions - -A session starts when the Session Replay SDK is first loaded and initialized. The session will continue until 5 minutes passes without any user interactions[^1] with the application *OR* until a maximum of 30 minutes have elapsed. Closing the browser tab will end the session immediately according to the rules for [SessionStorage](https://developer.mozilla.org/en-US/docs/Web/API/Window/sessionStorage). - -[^1]: An 'interaction' refers to either a mouse click or a browser navigation event. - -### Accessing the Replay Session ID - -You can get the ID of the currently running session via `replay.getReplayId()`. -This will return `undefined` if no session is ongoing. - -### Replay Captures Only on Errors - -Alternatively, rather than recording an entire session, you can capture a replay only when an error occurs. In this case, the integration will buffer up to one minute worth of events prior to the error being thrown. It will continue to record the session following the rules above regarding session life and activity. Read the [sampling](#Sampling) section for configuration options. - -## Sampling - -Sampling allows you to control how much of your website's traffic will result in a Session Replay. There are two sample rates you can adjust to get the replays more relevant to your interests: - -- `replaysSessionSampleRate` - The sample rate for replays that begin recording immediately and last the entirety of the user's session. -- `replaysOnErrorSampleRate` - The sample rate for replays that are recorded when an error happens. This type of replay will record up to a minute of events prior to the error and continue recording until the session ends. - -When Replay is initialized, we check the `replaysSessionSampleRate`. -If it is sampled, then we start recording & sending Replay data immediately. -Else, if `replaysOnErrorSampleRate > 0`, we'll start recording in buffering mode. -In this mode, whenever an error occurs we'll check `replaysOnErrorSampleRate`. -If it is sampled, when we'll upload the Replay to Sentry and continue recording normally. - -## Configuration - -### SDK Configuration - -The following options can be configured on the root level of your browser-based Sentry SDK, in `init({})`: - - -| key | type | default | description | -| ------------------- | ------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| replaysSessionSampleRate | number | `0` | The sample rate for replays that begin recording immediately and last the entirety of the user's session. 1.0 will collect all replays, 0 will collect no replays. | -| replaysOnErrorSampleRate | number | `0` |The sample rate for replays that are recorded when an error happens. This type of replay will record up to a minute of events prior to the error and continue recording until the session ends. 1.0 capturing all sessions with an error, and 0 capturing none. - -### General Integration Configuration - -The following options can be configured as options to the integration, in `new Replay({})`: - -| key | type | default | description | -| ------------------- | ------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| stickySession | boolean | `true` | Keep track of the user across page loads. Note a single user using multiple tabs will result in multiple sessions. Closing a tab will result in the session being closed as well. | - - -### Privacy Configuration - -The following options can be configured as options to the integration, in `new Replay({})`: - -| key | type | default | description | -| ---------------- | ------------------------ | --------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- | -| maskAllText | boolean | `true` | Mask _all_ text content. Will pass text content through `maskFn` before sending to server. | -| maskAllInputs | boolean | `true` | Mask values of `` elements. Passes input values through `maskInputFn` before sending to server. | -| blockAllMedia | boolean | `true` | Block _all_ media elements (`img, svg, video, object, picture, embed, map, audio`) | -| maskFn | (text: string) => string | `(text) => '*'.repeat(text.length)` | Function to customize how text content is masked before sending to server. By default, masks text with `*`. | -| block | Array | `.sentry-block, [data-sentry-block]` | Redact any elements that match the DOM selectors. See [privacy](#blocking) section for an example. | -| unblock | Array | `.sentry-unblock, [data-sentry-unblock]`| Do not redact any elements that match the DOM selectors. Useful when using `blockAllMedia`. See [privacy](#blocking) section for an example. | -| mask | Array | `.sentry-mask, [data-sentry-mask]` | Mask all elements that match the given DOM selectors. See [privacy](#masking) section for an example. | -| unmask | Array | `.sentry-unmask, [data-sentry-unmask]` | Unmask all elements that match the given DOM selectors. Useful when using `maskAllText`. See [privacy](#masking) section for an example. | -| ignore | Array | `.sentry-ignore, [data-sentry-ignore]` | Ignores all events on the matching input fields. See [privacy](#ignoring) section for an example. | - -#### Deprecated options -In order to streamline our privacy options, the following have been deprecated in favor for the respective options above. - -| deprecated key | replaced by | description | -| ---------------- | ----------- | ----------- | -| maskInputOptions | mask | Use CSS selectors in `mask` in order to mask all inputs of a certain type. For example, `input[type="address"]` | -| blockSelector | block | The selector(s) can be moved directly in the `block` array. | -| blockClass | block | Convert the class name to a CSS selector and add to `block` array. For example, `first-name` becomes `.first-name`. Regexes can be moved as-is. | -| maskClass | mask | Convert the class name to a CSS selector and add to `mask` array. For example, `first-name` becomes `.first-name`. Regexes can be moved as-is. | -| maskSelector | mask | The selector(s) can be moved directly in the `mask` array. | -| ignoreClass | ignore | Convert the class name to a CSS selector and add to `ignore` array. For example, `first-name` becomes `.first-name`. Regexes can be moved as-is. | - -## Privacy -There are several ways to deal with PII. By default, the integration will mask all text content with `*` and block all media elements (`img, svg, video, object, picture, embed, map, audio`). This can be disabled by setting `maskAllText` to `false`. It is also possible to add the following CSS classes to specific DOM elements to prevent recording its contents: `sentry-block`, `sentry-ignore`, and `sentry-mask`. The following sections will show examples of how content is handled by the differing methods. - -### Masking -Masking replaces the text content with something else. The default masking behavior is to replace each character with a `*`. In this example the relevant html code is: `...
`. -![Masking example](https://user-images.githubusercontent.com/79684/193118192-dee1d3d8-5813-47e8-b532-f9ee1c8714b3.png) - -### Blocking -Blocking replaces the element with a placeholder that has the same dimensions. The recording will show an empty space where the content was. In this example the relevant html code is: `...
`. -![Blocking example](https://user-images.githubusercontent.com/79684/193118084-51a589fc-2160-476a-a8dc-b681eddb136c.png) - -### Ignoring -Ignoring only applies to form inputs. Events will be ignored on the input element so that the replay does not show what occurs inside of the input. In the below example, notice how the results in the table below the input changes, but no text is visible in the input. - -https://user-images.githubusercontent.com/79684/192815134-a6451c3f-d3cb-455f-a699-7c3fe04d0a2e.mov - -## Error Linking - -Currently, errors that happen on the page while a replay is running are linked to the Replay, -making it as easy as possible to jump between related issues/replays. -However, please note that it is _possible_ that the error count reported on the Replay Detail page -does not match the actual errors that have been captured. -The reason for that is that errors _can_ be lost, e.g. a network request fails, or similar. -This should not happen to often, but be aware that it is theoretically possible. - -## Manually sending replay data - -You can use `replay.flush()` to immediately send all currently captured replay data. -When Replay is currently in buffering mode, this will send up to the last 60 seconds of replay data, -and also continue sending afterwards, similar to when an error happens & is recorded. diff --git a/packages/replay-canvas/jest.config.js b/packages/replay-canvas/jest.config.js new file mode 100644 index 000000000000..cd02790794a7 --- /dev/null +++ b/packages/replay-canvas/jest.config.js @@ -0,0 +1,6 @@ +const baseConfig = require('../../jest/jest.config.js'); + +module.exports = { + ...baseConfig, + testEnvironment: 'jsdom', +}; diff --git a/packages/replay-canvas/jest.config.ts b/packages/replay-canvas/jest.config.ts deleted file mode 100644 index 90a3cf471f8d..000000000000 --- a/packages/replay-canvas/jest.config.ts +++ /dev/null @@ -1,17 +0,0 @@ -import type { Config } from '@jest/types'; -import { jsWithTs as jsWithTsPreset } from 'ts-jest/presets'; - -export default async (): Promise => { - return { - ...jsWithTsPreset, - globals: { - 'ts-jest': { - tsconfig: '/tsconfig.test.json', - }, - __DEBUG_BUILD__: true, - }, - setupFilesAfterEnv: ['./jest.setup.ts'], - testEnvironment: 'jsdom', - testMatch: ['/test/**/*(*.)@(spec|test).ts'], - }; -}; diff --git a/packages/replay-canvas/jest.setup.ts b/packages/replay-canvas/jest.setup.ts deleted file mode 100644 index 09e6c68a4483..000000000000 --- a/packages/replay-canvas/jest.setup.ts +++ /dev/null @@ -1,269 +0,0 @@ -import { TextEncoder } from 'util'; -/* eslint-disable @typescript-eslint/no-unsafe-member-access */ -import { getCurrentHub } from '@sentry/core'; -import type { ReplayRecordingData, Transport } from '@sentry/types'; -import * as SentryUtils from '@sentry/utils'; - -import type { ReplayContainer, Session } from './src/types'; - -// eslint-disable-next-line @typescript-eslint/no-explicit-any -(global as any).TextEncoder = TextEncoder; - -type MockTransport = jest.MockedFunction; - -jest.spyOn(SentryUtils, 'isBrowser').mockImplementation(() => true); - -type EnvelopeHeader = { - event_id: string; - sent_at: string; - sdk: { - name: string; - version?: string; - }; -}; - -type ReplayEventHeader = { type: 'replay_event' }; -type ReplayEventPayload = Record; -type RecordingHeader = { type: 'replay_recording'; length: number }; -type RecordingPayloadHeader = Record; -type SentReplayExpected = { - envelopeHeader?: EnvelopeHeader; - replayEventHeader?: ReplayEventHeader; - replayEventPayload?: ReplayEventPayload; - recordingHeader?: RecordingHeader; - recordingPayloadHeader?: RecordingPayloadHeader; - recordingData?: ReplayRecordingData; -}; - -// eslint-disable-next-line @typescript-eslint/explicit-function-return-type -const toHaveSameSession = function (received: jest.Mocked, expected: undefined | Session) { - const pass = this.equals(received.session?.id, expected?.id) as boolean; - - const options = { - isNot: this.isNot, - promise: this.promise, - }; - - return { - pass, - message: () => - `${this.utils.matcherHint( - 'toHaveSameSession', - undefined, - undefined, - options, - )}\n\n${this.utils.printDiffOrStringify(expected, received.session, 'Expected', 'Received')}`, - }; -}; - -type Result = { - passed: boolean; - key: string; - expectedVal: SentReplayExpected[keyof SentReplayExpected]; - actualVal: SentReplayExpected[keyof SentReplayExpected]; -}; -type Call = [ - EnvelopeHeader, - [ - [ReplayEventHeader | undefined, ReplayEventPayload | undefined], - [RecordingHeader | undefined, RecordingPayloadHeader | undefined], - ], -]; -type CheckCallForSentReplayResult = { pass: boolean; call: Call | undefined; results: Result[] }; - -function checkCallForSentReplay( - call: Call | undefined, - expected?: SentReplayExpected | { sample: SentReplayExpected; inverse: boolean }, -): CheckCallForSentReplayResult { - const envelopeHeader = call?.[0]; - const envelopeItems = call?.[1] || [[], []]; - const [[replayEventHeader, replayEventPayload], [recordingHeader, recordingPayload] = []] = envelopeItems; - - // @ts-expect-error recordingPayload is always a string in our tests - const [recordingPayloadHeader, recordingData] = recordingPayload?.split('\n') || []; - - const actualObj: Required = { - // @ts-expect-error Custom envelope - envelopeHeader: envelopeHeader, - // @ts-expect-error Custom envelope - replayEventHeader: replayEventHeader, - // @ts-expect-error Custom envelope - replayEventPayload: replayEventPayload, - // @ts-expect-error Custom envelope - recordingHeader: recordingHeader, - recordingPayloadHeader: recordingPayloadHeader && JSON.parse(recordingPayloadHeader), - recordingData, - }; - - const isObjectContaining = expected && 'sample' in expected && 'inverse' in expected; - const expectedObj = isObjectContaining - ? (expected as { sample: SentReplayExpected }).sample - : (expected as SentReplayExpected); - - if (isObjectContaining) { - console.warn('`expect.objectContaining` is unnecessary when using the `toHaveSentReplay` matcher'); - } - - const results = expected - ? Object.keys(expectedObj) - .map(key => { - const actualVal = actualObj[key as keyof SentReplayExpected]; - const expectedVal = expectedObj[key as keyof SentReplayExpected]; - const passed = !expectedVal || this.equals(actualVal, expectedVal); - - return { passed, key, expectedVal, actualVal }; - }) - .filter(({ passed }) => !passed) - : []; - - const pass = Boolean(call && (!expected || results.length === 0)); - - return { - pass, - call, - results, - }; -} - -/** - * Only want calls that send replay events, i.e. ignore error events - */ -// eslint-disable-next-line @typescript-eslint/no-explicit-any -function getReplayCalls(calls: any[][][]): any[][][] { - return calls - .map(call => { - const arg = call[0]; - if (arg.length !== 2) { - return []; - } - - if (!arg[1][0].find(({ type }: { type: string }) => ['replay_event', 'replay_recording'].includes(type))) { - return []; - } - - return [arg]; - }) - .filter(Boolean); -} - -/** - * Checks all calls to `fetch` and ensures a replay was uploaded by - * checking the `fetch()` request's body. - */ -// eslint-disable-next-line @typescript-eslint/explicit-function-return-type -const toHaveSentReplay = function ( - _received: jest.Mocked, - expected?: SentReplayExpected | { sample: SentReplayExpected; inverse: boolean }, -) { - const { calls } = (getCurrentHub().getClient()?.getTransport()?.send as MockTransport).mock; - - let result: CheckCallForSentReplayResult; - - const expectedKeysLength = expected - ? ('sample' in expected ? Object.keys(expected.sample) : Object.keys(expected)).length - : 0; - - const replayCalls = getReplayCalls(calls); - - for (const currentCall of replayCalls) { - result = checkCallForSentReplay.call(this, currentCall[0], expected); - if (result.pass) { - break; - } - - // stop on the first call where any of the expected obj passes - if (result.results.length < expectedKeysLength) { - break; - } - } - - // @ts-expect-error use before assigned - const { results, call, pass } = result; - - const options = { - isNot: this.isNot, - promise: this.promise, - }; - - return { - pass, - message: () => - !call - ? pass - ? 'Expected Replay to not have been sent, but a request was attempted' - : 'Expected Replay to have been sent, but a request was not attempted' - : `${this.utils.matcherHint('toHaveSentReplay', undefined, undefined, options)}\n\n${results - .map(({ key, expectedVal, actualVal }: Result) => - this.utils.printDiffOrStringify( - expectedVal, - actualVal, - `Expected (key: ${key})`, - `Received (key: ${key})`, - ), - ) - .join('\n')}`, - }; -}; - -/** - * Checks the last call to `fetch` and ensures a replay was uploaded by - * checking the `fetch()` request's body. - */ -// eslint-disable-next-line @typescript-eslint/explicit-function-return-type -const toHaveLastSentReplay = function ( - _received: jest.Mocked, - expected?: SentReplayExpected | { sample: SentReplayExpected; inverse: boolean }, -) { - const { calls } = (getCurrentHub().getClient()?.getTransport()?.send as MockTransport).mock; - const replayCalls = getReplayCalls(calls); - - const lastCall = replayCalls[calls.length - 1]?.[0]; - - const { results, call, pass } = checkCallForSentReplay.call(this, lastCall, expected); - - const options = { - isNot: this.isNot, - promise: this.promise, - }; - - return { - pass, - message: () => - !call - ? pass - ? 'Expected Replay to not have been sent, but a request was attempted' - : 'Expected Replay to have last been sent, but a request was not attempted' - : `${this.utils.matcherHint('toHaveSentReplay', undefined, undefined, options)}\n\n${results - .map(({ key, expectedVal, actualVal }: Result) => - this.utils.printDiffOrStringify( - expectedVal, - actualVal, - `Expected (key: ${key})`, - `Received (key: ${key})`, - ), - ) - .join('\n')}`, - }; -}; - -expect.extend({ - toHaveSameSession, - toHaveSentReplay, - toHaveLastSentReplay, -}); - -declare global { - // eslint-disable-next-line @typescript-eslint/no-namespace - namespace jest { - interface AsymmetricMatchers { - toHaveSentReplay(expected?: SentReplayExpected): void; - toHaveLastSentReplay(expected?: SentReplayExpected): void; - toHaveSameSession(expected: undefined | Session): void; - } - interface Matchers { - toHaveSentReplay(expected?: SentReplayExpected): R; - toHaveLastSentReplay(expected?: SentReplayExpected): R; - toHaveSameSession(expected: undefined | Session): R; - } - } -} diff --git a/packages/replay-canvas/src/canvas.ts b/packages/replay-canvas/src/canvas.ts index c15a0e2c9a4d..1bc8cf8dffae 100644 --- a/packages/replay-canvas/src/canvas.ts +++ b/packages/replay-canvas/src/canvas.ts @@ -1,6 +1,6 @@ import { getCanvasManager } from '@sentry-internal/rrweb'; -import type { Integration } from '@sentry/types'; import type { ReplayConfiguration } from '@sentry/replay'; +import type { Integration } from '@sentry/types'; interface ReplayCanvasOptions { quality: 'low' | 'medium' | 'high'; diff --git a/packages/replay-canvas/test/canvas.test.ts b/packages/replay-canvas/test/canvas.test.ts new file mode 100644 index 000000000000..d7d7b1c8ef22 --- /dev/null +++ b/packages/replay-canvas/test/canvas.test.ts @@ -0,0 +1,27 @@ +import { ReplayCanvas } from '../src/canvas'; + +it('initializes with default options', () => { + const rc = new ReplayCanvas(); + + expect(rc.getOptions()).toEqual({ + _experiments: { + canvas: { + manager: expect.any(Function), + quality: 'medium', + }, + }, + }); +}); + +it('initializes with quality option', () => { + const rc = new ReplayCanvas({ quality: 'low' }); + + expect(rc.getOptions()).toEqual({ + _experiments: { + canvas: { + manager: expect.any(Function), + quality: 'low', + }, + }, + }); +}); diff --git a/packages/replay-canvas/test/index.ts b/packages/replay-canvas/test/index.ts deleted file mode 100644 index ed4b82a6c780..000000000000 --- a/packages/replay-canvas/test/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -export * from './mocks/mockRrweb'; // XXX: Needs to happen before `mockSdk` or importing Replay! -export * from './mocks/mockSdk'; - -export const BASE_TIMESTAMP = new Date('2020-02-02 00:00:00').getTime(); // 1580619600000 diff --git a/packages/replay-canvas/yarn.lock b/packages/replay-canvas/yarn.lock new file mode 100644 index 000000000000..08c8645d6c10 --- /dev/null +++ b/packages/replay-canvas/yarn.lock @@ -0,0 +1,469 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@ampproject/remapping@^2.2.0": + version "2.2.1" + resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.2.1.tgz#99e8e11851128b8702cd57c33684f1d0f260b630" + integrity sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg== + dependencies: + "@jridgewell/gen-mapping" "^0.3.0" + "@jridgewell/trace-mapping" "^0.3.9" + +"@babel/code-frame@^7.22.13", "@babel/code-frame@^7.23.5": + version "7.23.5" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.23.5.tgz#9009b69a8c602293476ad598ff53e4562e15c244" + integrity sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA== + dependencies: + "@babel/highlight" "^7.23.4" + chalk "^2.4.2" + +"@babel/compat-data@^7.23.5": + version "7.23.5" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.23.5.tgz#ffb878728bb6bdcb6f4510aa51b1be9afb8cfd98" + integrity sha512-uU27kfDRlhfKl+w1U6vp16IuvSLtjAxdArVXPa9BvLkrr7CYIsxH5adpHObeAGY/41+syctUWOZ140a2Rvkgjw== + +"@babel/core@^7.17.5": + version "7.23.7" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.23.7.tgz#4d8016e06a14b5f92530a13ed0561730b5c6483f" + integrity sha512-+UpDgowcmqe36d4NwqvKsyPMlOLNGMsfMmQ5WGCu+siCe3t3dfe9njrzGfdN4qq+bcNUt0+Vw6haRxBOycs4dw== + dependencies: + "@ampproject/remapping" "^2.2.0" + "@babel/code-frame" "^7.23.5" + "@babel/generator" "^7.23.6" + "@babel/helper-compilation-targets" "^7.23.6" + "@babel/helper-module-transforms" "^7.23.3" + "@babel/helpers" "^7.23.7" + "@babel/parser" "^7.23.6" + "@babel/template" "^7.22.15" + "@babel/traverse" "^7.23.7" + "@babel/types" "^7.23.6" + convert-source-map "^2.0.0" + debug "^4.1.0" + gensync "^1.0.0-beta.2" + json5 "^2.2.3" + semver "^6.3.1" + +"@babel/generator@^7.23.6": + version "7.23.6" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.23.6.tgz#9e1fca4811c77a10580d17d26b57b036133f3c2e" + integrity sha512-qrSfCYxYQB5owCmGLbl8XRpX1ytXlpueOb0N0UmQwA073KZxejgQTzAmJezxvpwQD9uGtK2shHdi55QT+MbjIw== + dependencies: + "@babel/types" "^7.23.6" + "@jridgewell/gen-mapping" "^0.3.2" + "@jridgewell/trace-mapping" "^0.3.17" + jsesc "^2.5.1" + +"@babel/helper-compilation-targets@^7.23.6": + version "7.23.6" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.23.6.tgz#4d79069b16cbcf1461289eccfbbd81501ae39991" + integrity sha512-9JB548GZoQVmzrFgp8o7KxdgkTGm6xs9DW0o/Pim72UDjzr5ObUQ6ZzYPqA+g9OTS2bBQoctLJrky0RDCAWRgQ== + dependencies: + "@babel/compat-data" "^7.23.5" + "@babel/helper-validator-option" "^7.23.5" + browserslist "^4.22.2" + lru-cache "^5.1.1" + semver "^6.3.1" + +"@babel/helper-environment-visitor@^7.22.20": + version "7.22.20" + resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz#96159db61d34a29dba454c959f5ae4a649ba9167" + integrity sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA== + +"@babel/helper-function-name@^7.23.0": + version "7.23.0" + resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz#1f9a3cdbd5b2698a670c30d2735f9af95ed52759" + integrity sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw== + dependencies: + "@babel/template" "^7.22.15" + "@babel/types" "^7.23.0" + +"@babel/helper-hoist-variables@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz#c01a007dac05c085914e8fb652b339db50d823bb" + integrity sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw== + dependencies: + "@babel/types" "^7.22.5" + +"@babel/helper-module-imports@^7.22.15": + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz#16146307acdc40cc00c3b2c647713076464bdbf0" + integrity sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w== + dependencies: + "@babel/types" "^7.22.15" + +"@babel/helper-module-transforms@^7.23.3": + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.23.3.tgz#d7d12c3c5d30af5b3c0fcab2a6d5217773e2d0f1" + integrity sha512-7bBs4ED9OmswdfDzpz4MpWgSrV7FXlc3zIagvLFjS5H+Mk7Snr21vQ6QwrsoCGMfNC4e4LQPdoULEt4ykz0SRQ== + dependencies: + "@babel/helper-environment-visitor" "^7.22.20" + "@babel/helper-module-imports" "^7.22.15" + "@babel/helper-simple-access" "^7.22.5" + "@babel/helper-split-export-declaration" "^7.22.6" + "@babel/helper-validator-identifier" "^7.22.20" + +"@babel/helper-simple-access@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz#4938357dc7d782b80ed6dbb03a0fba3d22b1d5de" + integrity sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w== + dependencies: + "@babel/types" "^7.22.5" + +"@babel/helper-split-export-declaration@^7.22.6": + version "7.22.6" + resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz#322c61b7310c0997fe4c323955667f18fcefb91c" + integrity sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g== + dependencies: + "@babel/types" "^7.22.5" + +"@babel/helper-string-parser@^7.23.4": + version "7.23.4" + resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.23.4.tgz#9478c707febcbbe1ddb38a3d91a2e054ae622d83" + integrity sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ== + +"@babel/helper-validator-identifier@^7.22.20": + version "7.22.20" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz#c4ae002c61d2879e724581d96665583dbc1dc0e0" + integrity sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A== + +"@babel/helper-validator-option@^7.23.5": + version "7.23.5" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.23.5.tgz#907a3fbd4523426285365d1206c423c4c5520307" + integrity sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw== + +"@babel/helpers@^7.23.7": + version "7.23.8" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.23.8.tgz#fc6b2d65b16847fd50adddbd4232c76378959e34" + integrity sha512-KDqYz4PiOWvDFrdHLPhKtCThtIcKVy6avWD2oG4GEvyQ+XDZwHD4YQd+H2vNMnq2rkdxsDkU82T+Vk8U/WXHRQ== + dependencies: + "@babel/template" "^7.22.15" + "@babel/traverse" "^7.23.7" + "@babel/types" "^7.23.6" + +"@babel/highlight@^7.23.4": + version "7.23.4" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.23.4.tgz#edaadf4d8232e1a961432db785091207ead0621b" + integrity sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A== + dependencies: + "@babel/helper-validator-identifier" "^7.22.20" + chalk "^2.4.2" + js-tokens "^4.0.0" + +"@babel/parser@^7.22.15", "@babel/parser@^7.23.6": + version "7.23.6" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.23.6.tgz#ba1c9e512bda72a47e285ae42aff9d2a635a9e3b" + integrity sha512-Z2uID7YJ7oNvAI20O9X0bblw7Qqs8Q2hFy0R9tAfnfLkp5MW0UH9eUvnDSnFwKZ0AvgS1ucqR4KzvVHgnke1VQ== + +"@babel/template@^7.22.15": + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.22.15.tgz#09576efc3830f0430f4548ef971dde1350ef2f38" + integrity sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w== + dependencies: + "@babel/code-frame" "^7.22.13" + "@babel/parser" "^7.22.15" + "@babel/types" "^7.22.15" + +"@babel/traverse@^7.23.7": + version "7.23.7" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.23.7.tgz#9a7bf285c928cb99b5ead19c3b1ce5b310c9c305" + integrity sha512-tY3mM8rH9jM0YHFGyfC0/xf+SB5eKUu7HPj7/k3fpi9dAlsMc5YbQvDi0Sh2QTPXqMhyaAtzAr807TIyfQrmyg== + dependencies: + "@babel/code-frame" "^7.23.5" + "@babel/generator" "^7.23.6" + "@babel/helper-environment-visitor" "^7.22.20" + "@babel/helper-function-name" "^7.23.0" + "@babel/helper-hoist-variables" "^7.22.5" + "@babel/helper-split-export-declaration" "^7.22.6" + "@babel/parser" "^7.23.6" + "@babel/types" "^7.23.6" + debug "^4.3.1" + globals "^11.1.0" + +"@babel/types@^7.22.15", "@babel/types@^7.22.5", "@babel/types@^7.23.0", "@babel/types@^7.23.6": + version "7.23.6" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.23.6.tgz#be33fdb151e1f5a56877d704492c240fc71c7ccd" + integrity sha512-+uarb83brBzPKN38NX1MkB6vb6+mwvR6amUulqAE7ccQw1pEl+bCia9TbdG1lsnFP7lZySvUn37CHyXQdfTwzg== + dependencies: + "@babel/helper-string-parser" "^7.23.4" + "@babel/helper-validator-identifier" "^7.22.20" + to-fast-properties "^2.0.0" + +"@jridgewell/gen-mapping@^0.3.0", "@jridgewell/gen-mapping@^0.3.2": + version "0.3.3" + resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz#7e02e6eb5df901aaedb08514203b096614024098" + integrity sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ== + dependencies: + "@jridgewell/set-array" "^1.0.1" + "@jridgewell/sourcemap-codec" "^1.4.10" + "@jridgewell/trace-mapping" "^0.3.9" + +"@jridgewell/resolve-uri@^3.1.0": + version "3.1.1" + resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz#c08679063f279615a3326583ba3a90d1d82cc721" + integrity sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA== + +"@jridgewell/set-array@^1.0.1": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.1.2.tgz#7c6cf998d6d20b914c0a55a91ae928ff25965e72" + integrity sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw== + +"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.14": + version "1.4.15" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32" + integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg== + +"@jridgewell/trace-mapping@^0.3.17", "@jridgewell/trace-mapping@^0.3.9": + version "0.3.20" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.20.tgz#72e45707cf240fa6b081d0366f8265b0cd10197f" + integrity sha512-R8LcPeWZol2zR8mmH3JeKQ6QRCFb7XgUhV9ZlGhHLGyg4wpPiPZNQOOWhFZhxKw8u//yTbNGI42Bx/3paXEQ+Q== + dependencies: + "@jridgewell/resolve-uri" "^3.1.0" + "@jridgewell/sourcemap-codec" "^1.4.14" + +"@sentry-internal/rrdom@2.7.3": + version "2.7.3" + resolved "https://registry.yarnpkg.com/@sentry-internal/rrdom/-/rrdom-2.7.3.tgz#2efe68a9cf23de9a8970acf4303748cdd7866b20" + integrity sha512-XD14G4Lv3ppvJlR7VkkCgHTKu1ylh7yvXdSsN5/FyGTH+IAXQIKL5nINIgWZTN3noNBWV9R0vcHDufXG/WktWA== + dependencies: + "@sentry-internal/rrweb-snapshot" "2.7.3" + +"@sentry-internal/rrweb-snapshot@2.7.3": + version "2.7.3" + resolved "https://registry.yarnpkg.com/@sentry-internal/rrweb-snapshot/-/rrweb-snapshot-2.7.3.tgz#9a7173825a31c07ccf27a5956f154400e11fdd97" + integrity sha512-mSZuBPmWia3x9wCuaJiZMD9ZVDnFv7TSG1Nz9X4ZqWb3DdaxB2MogGUU/2aTVqmRj6F91nl+GHb5NpmNYojUsw== + +"@sentry-internal/rrweb-types@2.7.3": + version "2.7.3" + resolved "https://registry.yarnpkg.com/@sentry-internal/rrweb-types/-/rrweb-types-2.7.3.tgz#e38737bc5c31aa9dfb8ce8faf46af374c2aa7cbb" + integrity sha512-EqALxhZtvH0rimYfj7J48DRC+fj+AGsZ/VDdOPKh3MQXptTyHncoWBj4ZtB1AaH7foYUr+2wkyxl3HqMVwe+6g== + dependencies: + "@sentry-internal/rrweb-snapshot" "2.7.3" + +"@sentry-internal/rrweb@file:.yalc/@sentry-internal/rrweb": + version "2.7.3" + dependencies: + "@sentry-internal/rrdom" "2.7.3" + "@sentry-internal/rrweb-snapshot" "2.7.3" + "@sentry-internal/rrweb-types" "2.7.3" + "@types/css-font-loading-module" "0.0.7" + "@xstate/fsm" "^1.4.0" + base64-arraybuffer "^1.0.1" + fflate "^0.4.4" + mitt "^3.0.0" + +"@sentry-internal/tracing@7.92.0": + version "7.92.0" + resolved "https://registry.yarnpkg.com/@sentry-internal/tracing/-/tracing-7.92.0.tgz#505d94a93b5df965ec6bfb35da43389988259d4d" + integrity sha512-ur55vPcUUUWFUX4eVLNP71ohswK7ZZpleNZw9Y1GfLqyI+0ILQUwjtzqItJrdClvVsdRZJMRmDV40Hp9Lbb9mA== + dependencies: + "@sentry/core" "7.92.0" + "@sentry/types" "7.92.0" + "@sentry/utils" "7.92.0" + +"@sentry/core@7.92.0": + version "7.92.0" + resolved "https://registry.yarnpkg.com/@sentry/core/-/core-7.92.0.tgz#4e74c1959348b698226c49ead7a24e165502b55c" + integrity sha512-1Tly7YB2I1byI5xb0Cwrxs56Rhww+6mQ7m9P7rTmdC3/ijOzbEoohtYIUPwcooCEarpbEJe/tAayRx6BrH2UbQ== + dependencies: + "@sentry/types" "7.92.0" + "@sentry/utils" "7.92.0" + +"@sentry/replay@7.92.0": + version "7.92.0" + resolved "https://registry.yarnpkg.com/@sentry/replay/-/replay-7.92.0.tgz#d94e9f6b72e540e73378a74ca1190068edd447f2" + integrity sha512-G1t9Uvc9cR8VpNkElwvHIMGzykjIKikb10n0tfVd3e+rBPMCCjCPWOduwG6jZYxcvCjTpqmJh6NSLXxL/Mt4JA== + dependencies: + "@sentry-internal/tracing" "7.92.0" + "@sentry/core" "7.92.0" + "@sentry/types" "7.92.0" + "@sentry/utils" "7.92.0" + +"@sentry/types@7.92.0": + version "7.92.0" + resolved "https://registry.yarnpkg.com/@sentry/types/-/types-7.92.0.tgz#4c308fdb316c0272f55f0816230fe87e7b9b551a" + integrity sha512-APmSOuZuoRGpbPpPeYIbMSplPjiWNLZRQa73QiXuTflW4Tu/ItDlU8hOa2+A6JKVkJCuD2EN6yUrxDGSMyNXeg== + +"@sentry/utils@7.92.0": + version "7.92.0" + resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-7.92.0.tgz#20ed29742594eab007f9ff72e008b5262456a319" + integrity sha512-3nEfrQ1z28b/2zgFGANPh5yMVtgwXmrasZxTvKbrAj+KWJpjrJHrIR84r9W277J44NMeZ5RhRW2uoDmuBslPnA== + dependencies: + "@sentry/types" "7.92.0" + +"@types/css-font-loading-module@0.0.7": + version "0.0.7" + resolved "https://registry.yarnpkg.com/@types/css-font-loading-module/-/css-font-loading-module-0.0.7.tgz#2f98ede46acc0975de85c0b7b0ebe06041d24601" + integrity sha512-nl09VhutdjINdWyXxHWN/w9zlNCfr60JUqJbd24YXUuCwgeL0TpFSdElCwb6cxfB6ybE19Gjj4g0jsgkXxKv1Q== + +"@xstate/fsm@^1.4.0": + version "1.6.5" + resolved "https://registry.yarnpkg.com/@xstate/fsm/-/fsm-1.6.5.tgz#f599e301997ad7e3c572a0b1ff0696898081bea5" + integrity sha512-b5o1I6aLNeYlU/3CPlj/Z91ybk1gUsKT+5NAJI+2W4UjvS5KLG28K9v5UvNoFVjHV8PajVZ00RH3vnjyQO7ZAw== + +ansi-styles@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" + integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== + dependencies: + color-convert "^1.9.0" + +base64-arraybuffer@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz#1c37589a7c4b0746e34bd1feb951da2df01c1bdc" + integrity sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ== + +browserslist@^4.22.2: + version "4.22.2" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.22.2.tgz#704c4943072bd81ea18997f3bd2180e89c77874b" + integrity sha512-0UgcrvQmBDvZHFGdYUehrCNIazki7/lUP3kkoi/r3YB2amZbFM9J43ZRkJTXBUZK4gmx56+Sqk9+Vs9mwZx9+A== + dependencies: + caniuse-lite "^1.0.30001565" + electron-to-chromium "^1.4.601" + node-releases "^2.0.14" + update-browserslist-db "^1.0.13" + +caniuse-lite@^1.0.30001565: + version "1.0.30001576" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001576.tgz#893be772cf8ee6056d6c1e2d07df365b9ec0a5c4" + integrity sha512-ff5BdakGe2P3SQsMsiqmt1Lc8221NR1VzHj5jXN5vBny9A6fpze94HiVV/n7XRosOlsShJcvMv5mdnpjOGCEgg== + +chalk@^2.4.2: + version "2.4.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" + integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== + dependencies: + ansi-styles "^3.2.1" + escape-string-regexp "^1.0.5" + supports-color "^5.3.0" + +color-convert@^1.9.0: + version "1.9.3" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" + integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== + dependencies: + color-name "1.1.3" + +color-name@1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" + integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw== + +convert-source-map@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a" + integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg== + +debug@^4.1.0, debug@^4.3.1: + version "4.3.4" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" + integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== + dependencies: + ms "2.1.2" + +electron-to-chromium@^1.4.601: + version "1.4.624" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.624.tgz#ff40ac0afeaeda0c2c7b8cfc19d93c49adb1245b" + integrity sha512-w9niWuheXjz23vezH3w90n9KKcHe0UkhTfJ+rXJkuGGogHyQbQ7KS1x0a8ER4LbI3ljFS/gqxKh1TidNXDMHOg== + +escalade@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" + integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== + +escape-string-regexp@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" + integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg== + +fflate@^0.4.4: + version "0.4.8" + resolved "https://registry.yarnpkg.com/fflate/-/fflate-0.4.8.tgz#f90b82aefbd8ac174213abb338bd7ef848f0f5ae" + integrity sha512-FJqqoDBR00Mdj9ppamLa/Y7vxm+PRmNWA67N846RvsoYVMKB4q3y/de5PA7gUmRMYK/8CMz2GDZQmCRN1wBcWA== + +gensync@^1.0.0-beta.2: + version "1.0.0-beta.2" + resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" + integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== + +globals@^11.1.0: + version "11.12.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" + integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== + +has-flag@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" + integrity sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw== + +js-tokens@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" + integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== + +jsesc@^2.5.1: + version "2.5.2" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" + integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== + +json5@^2.2.3: + version "2.2.3" + resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" + integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== + +lru-cache@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" + integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w== + dependencies: + yallist "^3.0.2" + +mitt@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/mitt/-/mitt-3.0.1.tgz#ea36cf0cc30403601ae074c8f77b7092cdab36d1" + integrity sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw== + +ms@2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" + integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== + +node-releases@^2.0.14: + version "2.0.14" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.14.tgz#2ffb053bceb8b2be8495ece1ab6ce600c4461b0b" + integrity sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw== + +picocolors@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" + integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== + +semver@^6.3.1: + version "6.3.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" + integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== + +supports-color@^5.3.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" + integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== + dependencies: + has-flag "^3.0.0" + +to-fast-properties@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" + integrity sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog== + +update-browserslist-db@^1.0.13: + version "1.0.13" + resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz#3c5e4f5c083661bd38ef64b6328c26ed6c8248c4" + integrity sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg== + dependencies: + escalade "^3.1.1" + picocolors "^1.0.0" + +yallist@^3.0.2: + version "3.1.1" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" + integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== diff --git a/scripts/node-unit-tests.ts b/scripts/node-unit-tests.ts index fb1d90fbb900..567224854baa 100644 --- a/scripts/node-unit-tests.ts +++ b/scripts/node-unit-tests.ts @@ -20,6 +20,7 @@ const DEFAULT_SKIP_TESTS_PACKAGES = [ '@sentry/angular', '@sentry/svelte', '@sentry/replay', + '@sentry-internal/replay-canvas', '@sentry-internal/feedback', '@sentry/wasm', '@sentry/bun', From f9bf43d28d69f00f2a307534fc93498b38880656 Mon Sep 17 00:00:00 2001 From: Billy Vong Date: Tue, 9 Jan 2024 12:51:01 -0500 Subject: [PATCH 06/50] remove local path --- packages/browser/package.json | 2 +- yarn.lock | 6 ------ 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/packages/browser/package.json b/packages/browser/package.json index c35135301e44..d367f603d701 100644 --- a/packages/browser/package.json +++ b/packages/browser/package.json @@ -30,7 +30,7 @@ }, "dependencies": { "@sentry-internal/feedback": "7.93.0", - "@sentry-internal/replay-canvas": "file:../replay-canvas", + "@sentry-internal/replay-canvas": "7.93.0", "@sentry-internal/tracing": "7.93.0", "@sentry/core": "7.93.0", "@sentry/replay": "7.93.0", diff --git a/yarn.lock b/yarn.lock index 8ce0595de8f3..d888e192dd15 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5231,12 +5231,6 @@ semver "7.3.2" semver-intersect "1.4.0" -"@sentry-internal/replay-canvas@file:packages/replay-canvas": - version "7.92.0" - dependencies: - "@sentry/replay" "7.92.0" - "@sentry/types" "7.92.0" - "@sentry-internal/rrdom@2.7.3": version "2.7.3" resolved "https://registry.yarnpkg.com/@sentry-internal/rrdom/-/rrdom-2.7.3.tgz#2efe68a9cf23de9a8970acf4303748cdd7866b20" From 748076fa3c4c1be0b309e6cfe29c6be28bee5d81 Mon Sep 17 00:00:00 2001 From: Billy Vong Date: Tue, 9 Jan 2024 13:21:37 -0500 Subject: [PATCH 07/50] add craft/build items --- .craft.yml | 4 ++++ .github/workflows/build.yml | 2 ++ packages/replay-canvas/README.md | 8 ++++---- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/.craft.yml b/.craft.yml index 1a4b07c18874..7c09cb4ddd4c 100644 --- a/.craft.yml +++ b/.craft.yml @@ -32,6 +32,10 @@ targets: - name: npm id: '@sentry-internal/feedback' includeNames: /^sentry-internal-feedback-\d.*\.tgz$/ + ## 1.8 ReplayCanvas package (browser only) + - name: npm + id: '@sentry-internal/replay-canvas' + includeNames: /^sentry-internal-replay-canvas-\d.*\.tgz$/ ## 2. Browser & Node SDKs - name: npm diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 83fb3f0be7b1..e590f8e8b066 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -100,6 +100,7 @@ jobs: - *shared - 'packages/browser/**' - 'packages/replay/**' + - 'packages/replay-canvas/**' - 'packages/feedback/**' browser_integration: - *shared @@ -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 diff --git a/packages/replay-canvas/README.md b/packages/replay-canvas/README.md index c074bedb01e0..56b4ae0ca101 100644 --- a/packages/replay-canvas/README.md +++ b/packages/replay-canvas/README.md @@ -6,13 +6,13 @@ # Sentry Session Replay with Canvas -[![npm version](https://img.shields.io/npm/v/@sentry/replay.svg)](https://www.npmjs.com/package/@sentry/replay) -[![npm dm](https://img.shields.io/npm/dm/@sentry/replay.svg)](https://www.npmjs.com/package/@sentry/replay) -[![npm dt](https://img.shields.io/npm/dt/@sentry/replay.svg)](https://www.npmjs.com/package/@sentry/replay) +[![npm version](https://img.shields.io/npm/v/@sentry-internal/replay-canvas.svg)](https://www.npmjs.com/package/@sentry-internal/replay-canvas) +[![npm dm](https://img.shields.io/npm/dm/@sentry-internal/replay-canvas.svg)](https://www.npmjs.com/package/@sentry-internal/replay-canvas) +[![npm dt](https://img.shields.io/npm/dt/@sentry-internal/replay-canvas.svg)](https://www.npmjs.com/package/@sentry-internal/replay-canvas) ## Pre-requisites -`@sentry/replay` requires Node 12+, and browsers newer than IE11. +`@sentry-internal/replay-canvas` requires Node 12+, and browsers newer than IE11. ## Installation From 2a214f825f372aa1d16a4032be29c3e3a2821f62 Mon Sep 17 00:00:00 2001 From: Billy Vong Date: Tue, 9 Jan 2024 13:23:13 -0500 Subject: [PATCH 08/50] add to resolutions --- packages/nextjs/test/integration/package.json | 1 + packages/remix/test/integration/package.json | 1 + 2 files changed, 2 insertions(+) diff --git a/packages/nextjs/test/integration/package.json b/packages/nextjs/test/integration/package.json index 0bbe126a10b6..14ac5a38aa6b 100644 --- a/packages/nextjs/test/integration/package.json +++ b/packages/nextjs/test/integration/package.json @@ -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", diff --git a/packages/remix/test/integration/package.json b/packages/remix/test/integration/package.json index 04030a492670..232c4fc9ea59 100644 --- a/packages/remix/test/integration/package.json +++ b/packages/remix/test/integration/package.json @@ -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", From af1819de33c6241f691c83202917ef9950164ce2 Mon Sep 17 00:00:00 2001 From: Billy Vong Date: Tue, 9 Jan 2024 16:21:04 -0500 Subject: [PATCH 09/50] change to not use `_experiments` for canvas --- packages/replay-canvas/src/canvas.ts | 64 ++++++++++++++++++++++------ packages/replay/src/constants.ts | 30 ------------- packages/replay/src/index.ts | 1 + packages/replay/src/integration.ts | 15 +------ packages/replay/src/replay.ts | 24 +++++++---- 5 files changed, 70 insertions(+), 64 deletions(-) diff --git a/packages/replay-canvas/src/canvas.ts b/packages/replay-canvas/src/canvas.ts index 1bc8cf8dffae..00c884a6cfd4 100644 --- a/packages/replay-canvas/src/canvas.ts +++ b/packages/replay-canvas/src/canvas.ts @@ -1,11 +1,53 @@ -import { getCanvasManager } from '@sentry-internal/rrweb'; -import type { ReplayConfiguration } from '@sentry/replay'; +import { CanvasManager } from '@sentry-internal/rrweb'; +import type { CanvasManagerInterface } from '@sentry/replay'; import type { Integration } from '@sentry/types'; interface ReplayCanvasOptions { quality: 'low' | 'medium' | 'high'; } +interface ReplayCanvasIntegrationOptions { + recordCanvas: true; + getCanvasManager: (options: ConstructorParameters[0]) => CanvasManagerInterface; + sampling: { + canvas: number; + }; + dataURLOptions: { + type: string; + quality: number; + }; +} + +const CANVAS_QUALITY = { + low: { + sampling: { + canvas: 1, + }, + dataURLOptions: { + type: 'image/webp', + quality: 0.25, + }, + }, + medium: { + sampling: { + canvas: 2, + }, + dataURLOptions: { + type: 'image/webp', + quality: 0.4, + }, + }, + high: { + sampling: { + canvas: 4, + }, + dataURLOptions: { + type: 'image/webp', + quality: 0.5, + }, + }, +}; + /** An integration to add canvas recording to replay. */ export class ReplayCanvas implements Integration { /** @@ -20,10 +62,10 @@ export class ReplayCanvas implements Integration { private _canvasOptions: ReplayCanvasOptions; - public constructor(options?: Partial) { + public constructor(options: Partial = {}) { this.name = ReplayCanvas.id; this._canvasOptions = { - quality: (options && options.quality) || 'medium', + quality: options.quality || 'medium', }; } @@ -36,15 +78,13 @@ export class ReplayCanvas implements Integration { * Get the options that should be merged into replay options. * This is what is actually called by the Replay integration to setup canvas. */ - public getOptions(): Partial { + public getOptions(): ReplayCanvasIntegrationOptions { + const { quality } = this._canvasOptions; + return { - _experiments: { - canvas: { - ...this._canvasOptions, - manager: getCanvasManager, - quality: this._canvasOptions.quality, - }, - }, + recordCanvas: true, + getCanvasManager: (options: ConstructorParameters[0]) => new CanvasManager(options), + ...(CANVAS_QUALITY[quality || 'medium'] || CANVAS_QUALITY.medium), }; } } diff --git a/packages/replay/src/constants.ts b/packages/replay/src/constants.ts index ec3325a4253a..13ccea43df22 100644 --- a/packages/replay/src/constants.ts +++ b/packages/replay/src/constants.ts @@ -53,33 +53,3 @@ export const MAX_REPLAY_DURATION = 3_600_000; // 60 minutes in ms; /** Default attributes to be ignored when `maskAllText` is enabled */ export const DEFAULT_IGNORED_ATTRIBUTES = ['title', 'placeholder']; - -export const CANVAS_QUALITY = { - low: { - sampling: { - canvas: 1, - }, - dataURLOptions: { - type: 'image/webp', - quality: 0.25, - }, - }, - medium: { - sampling: { - canvas: 2, - }, - dataURLOptions: { - type: 'image/webp', - quality: 0.4, - }, - }, - high: { - sampling: { - canvas: 4, - }, - dataURLOptions: { - type: 'image/webp', - quality: 0.5, - }, - }, -}; diff --git a/packages/replay/src/index.ts b/packages/replay/src/index.ts index fa7363fae13f..b2d4d575d019 100644 --- a/packages/replay/src/index.ts +++ b/packages/replay/src/index.ts @@ -11,6 +11,7 @@ export type { ReplayFrameEvent, ReplaySpanFrame, ReplaySpanFrameEvent, + CanvasManagerInterface, } from './types'; // TODO (v8): Remove deprecated types diff --git a/packages/replay/src/integration.ts b/packages/replay/src/integration.ts index 6c62523098b9..fa32f2fecb5b 100644 --- a/packages/replay/src/integration.ts +++ b/packages/replay/src/integration.ts @@ -349,11 +349,6 @@ Sentry.init({ replaysOnErrorSampleRate: ${errorSampleRate} })`, /** Get canvas options from ReplayCanvas integration, if it is also added. */ private _maybeLoadFromReplayCanvasIntegration(): void { - // If already defined, skip this... - if (this._initialOptions._experiments.canvas) { - return; - } - // To save bundle size, we skip checking for stuff here // and instead just try-catch everything - as generally this should all be defined /* eslint-disable @typescript-eslint/no-non-null-assertion */ @@ -365,16 +360,8 @@ Sentry.init({ replaysOnErrorSampleRate: ${errorSampleRate} })`, if (!canvasIntegration) { return; } - const additionalOptions = canvasIntegration.getOptions(); - - const mergedExperimentsOptions = { - ...this._initialOptions._experiments, - ...additionalOptions._experiments, - }; - - this._initialOptions._experiments = mergedExperimentsOptions; - this._replay!.getOptions()._experiments = mergedExperimentsOptions; + this._replay!.addIntegration('canvas', canvasIntegration.getOptions()); } catch { // ignore errors here } diff --git a/packages/replay/src/replay.ts b/packages/replay/src/replay.ts index 27513fe5643d..d34944bc2a83 100644 --- a/packages/replay/src/replay.ts +++ b/packages/replay/src/replay.ts @@ -12,7 +12,6 @@ import { logger } from '@sentry/utils'; import { BUFFER_CHECKOUT_TIME, - CANVAS_QUALITY, SESSION_IDLE_EXPIRE_DURATION, SESSION_IDLE_PAUSE_DURATION, SLOW_CLICK_SCROLL_TIMEOUT, @@ -145,6 +144,11 @@ export class ReplayContainer implements ReplayContainerInterface { private _context: InternalEventContext; + /** + * Internal integrations (e.g. canvas) + */ + private _integrations: Record>; + public constructor({ options, recordingOptions, @@ -163,6 +167,7 @@ export class ReplayContainer implements ReplayContainerInterface { this._lastActivity = Date.now(); this._isEnabled = false; this._isPaused = false; + this._integrations = {}; this._hasInitializedCoreListeners = false; this._context = { errorIds: new Set(), @@ -338,7 +343,8 @@ export class ReplayContainer implements ReplayContainerInterface { */ public startRecording(): void { try { - const canvas = this._options._experiments.canvas; + const { canvas } = this._integrations; + this._stopRecording = record({ ...this._recordingOptions, // When running in error sampling mode, we need to overwrite `checkoutEveryNms` @@ -347,12 +353,7 @@ export class ReplayContainer implements ReplayContainerInterface { ...(this.recordingMode === 'buffer' && { checkoutEveryNms: BUFFER_CHECKOUT_TIME }), emit: getHandleRecordingEmit(this), onMutation: this._onMutationHandler, - ...(canvas && - canvas.manager && { - recordCanvas: true, - getCanvasManager: canvas.manager, - ...(CANVAS_QUALITY[canvas.quality || 'medium'] || CANVAS_QUALITY.medium), - }), + ...canvas, }); } catch (err) { this._handleException(err); @@ -717,6 +718,13 @@ export class ReplayContainer implements ReplayContainerInterface { return spanToJSON(lastTransaction).description; } + /** + * Internal integration use only, should not be public + */ + public addIntegration(name: string, options: Record): void { + this._integrations[name] = options; + } + /** * Initialize and start all listeners to varying events (DOM, * Performance Observer, Recording, Sentry SDK, etc) From 39e8788173585e4eec0bc65d47cb64f034ac435e Mon Sep 17 00:00:00 2001 From: Billy Vong Date: Tue, 9 Jan 2024 16:26:48 -0500 Subject: [PATCH 10/50] revert entrypoint change --- packages/replay/rollup.bundle.config.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/replay/rollup.bundle.config.mjs b/packages/replay/rollup.bundle.config.mjs index bc81b0cb46a4..a209b8d41af4 100644 --- a/packages/replay/rollup.bundle.config.mjs +++ b/packages/replay/rollup.bundle.config.mjs @@ -2,7 +2,7 @@ import { makeBaseBundleConfig, makeBundleConfigVariants } from '@sentry-internal const baseBundleConfig = makeBaseBundleConfig({ bundleType: 'addon', - entrypoints: ['src/integration.ts'], + entrypoints: ['src/index.ts'], jsVersion: 'es6', licenseTitle: '@sentry/replay', outputFileBase: () => 'bundles/replay', From 5d9f5c3058c569495ba063ee71f40a2dc24c0c8b Mon Sep 17 00:00:00 2001 From: Billy Vong Date: Tue, 9 Jan 2024 17:57:03 -0500 Subject: [PATCH 11/50] move tests to dev-packages --- .../suites/replay/canvas/withCanvasIntegrationFirst/init.js | 0 .../suites/replay/canvas/withCanvasIntegrationFirst/test.ts | 0 .../suites/replay/canvas/withCanvasIntegrationSecond/init.js | 0 .../suites/replay/canvas/withCanvasIntegrationSecond/test.ts | 0 .../suites/replay/canvas/withoutCanvasIntegration/init.js | 0 .../suites/replay/canvas/withoutCanvasIntegration/test.ts | 0 6 files changed, 0 insertions(+), 0 deletions(-) rename {packages => dev-packages}/browser-integration-tests/suites/replay/canvas/withCanvasIntegrationFirst/init.js (100%) rename {packages => dev-packages}/browser-integration-tests/suites/replay/canvas/withCanvasIntegrationFirst/test.ts (100%) rename {packages => dev-packages}/browser-integration-tests/suites/replay/canvas/withCanvasIntegrationSecond/init.js (100%) rename {packages => dev-packages}/browser-integration-tests/suites/replay/canvas/withCanvasIntegrationSecond/test.ts (100%) rename {packages => dev-packages}/browser-integration-tests/suites/replay/canvas/withoutCanvasIntegration/init.js (100%) rename {packages => dev-packages}/browser-integration-tests/suites/replay/canvas/withoutCanvasIntegration/test.ts (100%) diff --git a/packages/browser-integration-tests/suites/replay/canvas/withCanvasIntegrationFirst/init.js b/dev-packages/browser-integration-tests/suites/replay/canvas/withCanvasIntegrationFirst/init.js similarity index 100% rename from packages/browser-integration-tests/suites/replay/canvas/withCanvasIntegrationFirst/init.js rename to dev-packages/browser-integration-tests/suites/replay/canvas/withCanvasIntegrationFirst/init.js diff --git a/packages/browser-integration-tests/suites/replay/canvas/withCanvasIntegrationFirst/test.ts b/dev-packages/browser-integration-tests/suites/replay/canvas/withCanvasIntegrationFirst/test.ts similarity index 100% rename from packages/browser-integration-tests/suites/replay/canvas/withCanvasIntegrationFirst/test.ts rename to dev-packages/browser-integration-tests/suites/replay/canvas/withCanvasIntegrationFirst/test.ts diff --git a/packages/browser-integration-tests/suites/replay/canvas/withCanvasIntegrationSecond/init.js b/dev-packages/browser-integration-tests/suites/replay/canvas/withCanvasIntegrationSecond/init.js similarity index 100% rename from packages/browser-integration-tests/suites/replay/canvas/withCanvasIntegrationSecond/init.js rename to dev-packages/browser-integration-tests/suites/replay/canvas/withCanvasIntegrationSecond/init.js diff --git a/packages/browser-integration-tests/suites/replay/canvas/withCanvasIntegrationSecond/test.ts b/dev-packages/browser-integration-tests/suites/replay/canvas/withCanvasIntegrationSecond/test.ts similarity index 100% rename from packages/browser-integration-tests/suites/replay/canvas/withCanvasIntegrationSecond/test.ts rename to dev-packages/browser-integration-tests/suites/replay/canvas/withCanvasIntegrationSecond/test.ts diff --git a/packages/browser-integration-tests/suites/replay/canvas/withoutCanvasIntegration/init.js b/dev-packages/browser-integration-tests/suites/replay/canvas/withoutCanvasIntegration/init.js similarity index 100% rename from packages/browser-integration-tests/suites/replay/canvas/withoutCanvasIntegration/init.js rename to dev-packages/browser-integration-tests/suites/replay/canvas/withoutCanvasIntegration/init.js diff --git a/packages/browser-integration-tests/suites/replay/canvas/withoutCanvasIntegration/test.ts b/dev-packages/browser-integration-tests/suites/replay/canvas/withoutCanvasIntegration/test.ts similarity index 100% rename from packages/browser-integration-tests/suites/replay/canvas/withoutCanvasIntegration/test.ts rename to dev-packages/browser-integration-tests/suites/replay/canvas/withoutCanvasIntegration/test.ts From cdf1ed99b98d584184220678c223fd08061a415b Mon Sep 17 00:00:00 2001 From: Billy Vong Date: Tue, 9 Jan 2024 18:05:02 -0500 Subject: [PATCH 12/50] update tests --- .../suites/replay/canvas/records/init.js | 8 +------- .../replay/canvas/withCanvasIntegrationFirst/test.ts | 7 ++++--- .../replay/canvas/withCanvasIntegrationSecond/test.ts | 7 ++++--- .../suites/replay/canvas/withoutCanvasIntegration/test.ts | 2 +- .../browser-integration-tests/utils/replayHelpers.ts | 4 +++- packages/replay-canvas/src/canvas.ts | 2 +- packages/replay-canvas/src/index.ts | 1 + packages/replay/src/replay.ts | 5 +++++ packages/replay/src/types/replay.ts | 7 ++----- 9 files changed, 22 insertions(+), 21 deletions(-) diff --git a/dev-packages/browser-integration-tests/suites/replay/canvas/records/init.js b/dev-packages/browser-integration-tests/suites/replay/canvas/records/init.js index e530d741a8bc..296245b01c26 100644 --- a/dev-packages/browser-integration-tests/suites/replay/canvas/records/init.js +++ b/dev-packages/browser-integration-tests/suites/replay/canvas/records/init.js @@ -1,4 +1,3 @@ -import { getCanvasManager } from '@sentry-internal/rrweb'; import * as Sentry from '@sentry/browser'; window.Sentry = Sentry; @@ -6,11 +5,6 @@ window.Replay = new Sentry.Replay({ flushMinDelay: 50, flushMaxDelay: 50, minReplayDuration: 0, - _experiments: { - canvas: { - manager: getCanvasManager, - }, - }, }); Sentry.init({ @@ -20,5 +14,5 @@ Sentry.init({ replaysOnErrorSampleRate: 0.0, debug: true, - integrations: [window.Replay], + integrations: [window.Replay, new Sentry.ReplayCanvas()], }); diff --git a/dev-packages/browser-integration-tests/suites/replay/canvas/withCanvasIntegrationFirst/test.ts b/dev-packages/browser-integration-tests/suites/replay/canvas/withCanvasIntegrationFirst/test.ts index 7326bed95ae6..84a4d663cec6 100644 --- a/dev-packages/browser-integration-tests/suites/replay/canvas/withCanvasIntegrationFirst/test.ts +++ b/dev-packages/browser-integration-tests/suites/replay/canvas/withCanvasIntegrationFirst/test.ts @@ -1,4 +1,5 @@ import { expect } from '@playwright/test'; +import type { ReplayCanvasIntegrationOptions } from '@sentry-internal/replay-canvas'; import { sentryTest } from '../../../../utils/fixtures'; import { getReplaySnapshot, shouldSkipReplayTest } from '../../../../utils/replayHelpers'; @@ -21,8 +22,8 @@ sentryTest('sets up canvas when adding ReplayCanvas integration first', async ({ await page.goto(url); const replay = await getReplaySnapshot(page); - const canvasOptions = replay._options._experiments?.canvas; - expect(canvasOptions.fps).toBe(4); - expect(canvasOptions.quality).toBe(0.6); + const canvasOptions = replay._integrations.canvas as ReplayCanvasIntegrationOptions; + expect(canvasOptions.sampling.canvas).toBe(2); + expect(canvasOptions.dataURLOptions.quality).toBe(0.4); expect(replay._hasCanvas).toBe(true); }); diff --git a/dev-packages/browser-integration-tests/suites/replay/canvas/withCanvasIntegrationSecond/test.ts b/dev-packages/browser-integration-tests/suites/replay/canvas/withCanvasIntegrationSecond/test.ts index 9168864db7e0..95dc3e93999c 100644 --- a/dev-packages/browser-integration-tests/suites/replay/canvas/withCanvasIntegrationSecond/test.ts +++ b/dev-packages/browser-integration-tests/suites/replay/canvas/withCanvasIntegrationSecond/test.ts @@ -1,4 +1,5 @@ import { expect } from '@playwright/test'; +import type { ReplayCanvasIntegrationOptions } from '@sentry-internal/replay-canvas'; import { sentryTest } from '../../../../utils/fixtures'; import { getReplaySnapshot, shouldSkipReplayTest } from '../../../../utils/replayHelpers'; @@ -21,8 +22,8 @@ sentryTest('sets up canvas when adding ReplayCanvas integration after Replay', a await page.goto(url); const replay = await getReplaySnapshot(page); - const canvasOptions = replay._options._experiments?.canvas; - expect(canvasOptions.fps).toBe(4); - expect(canvasOptions.quality).toBe(0.6); + const canvasOptions = replay._integrations.canvas as ReplayCanvasIntegrationOptions; + expect(canvasOptions.sampling.canvas).toBe(2); + expect(canvasOptions.dataURLOptions.quality).toBe(0.4); expect(replay._hasCanvas).toBe(true); }); diff --git a/dev-packages/browser-integration-tests/suites/replay/canvas/withoutCanvasIntegration/test.ts b/dev-packages/browser-integration-tests/suites/replay/canvas/withoutCanvasIntegration/test.ts index b2ce69211be0..072a1dc66260 100644 --- a/dev-packages/browser-integration-tests/suites/replay/canvas/withoutCanvasIntegration/test.ts +++ b/dev-packages/browser-integration-tests/suites/replay/canvas/withoutCanvasIntegration/test.ts @@ -21,7 +21,7 @@ sentryTest('does not setup up canvas without ReplayCanvas integration', async ({ await page.goto(url); const replay = await getReplaySnapshot(page); - const canvasOptions = replay._options._experiments?.canvas; + const canvasOptions = replay._integrations.canvas; expect(canvasOptions).toBe(undefined); expect(replay._hasCanvas).toBe(false); }); diff --git a/dev-packages/browser-integration-tests/utils/replayHelpers.ts b/dev-packages/browser-integration-tests/utils/replayHelpers.ts index 2a951e4a215e..cee47b61d635 100644 --- a/dev-packages/browser-integration-tests/utils/replayHelpers.ts +++ b/dev-packages/browser-integration-tests/utils/replayHelpers.ts @@ -174,6 +174,7 @@ export function getReplaySnapshot(page: Page): Promise<{ _isEnabled: boolean; _context: InternalEventContext; _options: ReplayPluginOptions; + _integrations: Record; _hasCanvas: boolean; session: Session | undefined; recordingMode: ReplayRecordingMode; @@ -187,8 +188,9 @@ export function getReplaySnapshot(page: Page): Promise<{ _isEnabled: replay.isEnabled(), _context: replay.getContext(), _options: replay.getOptions(), + _integrations: replay.getIntegrations(), // We cannot pass the function through as this is serialized - _hasCanvas: typeof replay.getOptions()._experiments.canvas?.manager === 'function', + _hasCanvas: typeof replay.getIntegrations().canvas?.getCanvasManager === 'function', session: replay.session, recordingMode: replay.recordingMode, }; diff --git a/packages/replay-canvas/src/canvas.ts b/packages/replay-canvas/src/canvas.ts index 00c884a6cfd4..cbe7d1eceeeb 100644 --- a/packages/replay-canvas/src/canvas.ts +++ b/packages/replay-canvas/src/canvas.ts @@ -6,7 +6,7 @@ interface ReplayCanvasOptions { quality: 'low' | 'medium' | 'high'; } -interface ReplayCanvasIntegrationOptions { +export interface ReplayCanvasIntegrationOptions { recordCanvas: true; getCanvasManager: (options: ConstructorParameters[0]) => CanvasManagerInterface; sampling: { diff --git a/packages/replay-canvas/src/index.ts b/packages/replay-canvas/src/index.ts index 90d0f74188af..97e5bab007a2 100644 --- a/packages/replay-canvas/src/index.ts +++ b/packages/replay-canvas/src/index.ts @@ -1 +1,2 @@ export { ReplayCanvas } from './canvas'; +export type { ReplayCanvasIntegrationOptions } from './canvas'; diff --git a/packages/replay/src/replay.ts b/packages/replay/src/replay.ts index d34944bc2a83..07cee3433136 100644 --- a/packages/replay/src/replay.ts +++ b/packages/replay/src/replay.ts @@ -228,6 +228,11 @@ export class ReplayContainer implements ReplayContainerInterface { return this._options; } + /** Get the replay integrations. [test only] */ + public getIntegrations(): Record> { + return { ...this._integrations }; + } + /** * Initializes the plugin based on sampling configuration. Should not be * called outside of constructor. diff --git a/packages/replay/src/types/replay.ts b/packages/replay/src/types/replay.ts index 1b2b19d4d4cd..d22409154477 100644 --- a/packages/replay/src/types/replay.ts +++ b/packages/replay/src/types/replay.ts @@ -14,7 +14,7 @@ import type { SKIPPED, THROTTLED } from '../util/throttle'; import type { AllPerformanceEntry, AllPerformanceEntryData, ReplayPerformanceEntry } from './performance'; import type { ReplayFrameEvent } from './replayFrame'; import type { ReplayNetworkRequestOrResponse } from './request'; -import type { CanvasManagerInterface, GetCanvasManagerOptions, ReplayEventWithTime, RrwebRecordOptions } from './rrweb'; +import type { ReplayEventWithTime, RrwebRecordOptions } from './rrweb'; export type RecordingEvent = ReplayFrameEvent | ReplayEventWithTime; export type RecordingOptions = RrwebRecordOptions; @@ -232,10 +232,6 @@ export interface ReplayPluginOptions extends ReplayNetworkOptions { _experiments: Partial<{ captureExceptions: boolean; traceInternals: boolean; - canvas: { - quality?: 'low' | 'medium' | 'high'; - manager: (options: GetCanvasManagerOptions) => CanvasManagerInterface; - }; }>; } @@ -506,6 +502,7 @@ export interface ReplayContainer { updateUserActivity(): void; addUpdate(cb: AddUpdateCallback): void; getOptions(): ReplayPluginOptions; + getIntegrations(): Record>; getSessionId(): string | undefined; checkAndHandleExpiredSession(): boolean | void; setInitialState(): void; From 458b0c8aac09ba67349fbe0de6091791fc2f6bb5 Mon Sep 17 00:00:00 2001 From: Billy Vong Date: Wed, 10 Jan 2024 08:44:54 -0500 Subject: [PATCH 13/50] remove old template --- .../suites/replay/canvas/template.html | 9 --------- 1 file changed, 9 deletions(-) delete mode 100644 packages/browser-integration-tests/suites/replay/canvas/template.html diff --git a/packages/browser-integration-tests/suites/replay/canvas/template.html b/packages/browser-integration-tests/suites/replay/canvas/template.html deleted file mode 100644 index 2b3e2f0b27b4..000000000000 --- a/packages/browser-integration-tests/suites/replay/canvas/template.html +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - From 820879d02692cf18757a923c90a500a3822fc057 Mon Sep 17 00:00:00 2001 From: Billy Vong Date: Wed, 10 Jan 2024 08:47:50 -0500 Subject: [PATCH 14/50] CR fixes --- packages/replay-canvas/.eslintrc.js | 17 ----------------- packages/replay-canvas/LICENSE | 2 +- packages/replay-canvas/README.md | 6 +----- 3 files changed, 2 insertions(+), 23 deletions(-) diff --git a/packages/replay-canvas/.eslintrc.js b/packages/replay-canvas/.eslintrc.js index 4f69827ac50b..eaa6b88347ca 100644 --- a/packages/replay-canvas/.eslintrc.js +++ b/packages/replay-canvas/.eslintrc.js @@ -21,22 +21,5 @@ module.exports = { 'no-console': 'off', }, }, - { - files: ['test/**/*.ts'], - - rules: { - // most of these errors come from `new Promise(process.nextTick)` - '@typescript-eslint/unbound-method': 'off', - // TODO: decide if we want to enable this again after the migration - // We can take the freedom to be a bit more lenient with tests - '@typescript-eslint/no-floating-promises': 'off', - }, - }, - { - files: ['src/types/deprecated.ts'], - rules: { - '@typescript-eslint/naming-convention': 'off', - }, - }, ], }; diff --git a/packages/replay-canvas/LICENSE b/packages/replay-canvas/LICENSE index 4ac873d49f33..ea5e82344f87 100644 --- a/packages/replay-canvas/LICENSE +++ b/packages/replay-canvas/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2022 Sentry (https://sentry.io) and individual contributors. All rights reserved. +Copyright (c) 2024 Sentry (https://sentry.io) and individual contributors. All rights reserved. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the diff --git a/packages/replay-canvas/README.md b/packages/replay-canvas/README.md index 56b4ae0ca101..eac14facc5cc 100644 --- a/packages/replay-canvas/README.md +++ b/packages/replay-canvas/README.md @@ -6,13 +6,9 @@ # Sentry Session Replay with Canvas -[![npm version](https://img.shields.io/npm/v/@sentry-internal/replay-canvas.svg)](https://www.npmjs.com/package/@sentry-internal/replay-canvas) -[![npm dm](https://img.shields.io/npm/dm/@sentry-internal/replay-canvas.svg)](https://www.npmjs.com/package/@sentry-internal/replay-canvas) -[![npm dt](https://img.shields.io/npm/dt/@sentry-internal/replay-canvas.svg)](https://www.npmjs.com/package/@sentry-internal/replay-canvas) - ## Pre-requisites -`@sentry-internal/replay-canvas` requires Node 12+, and browsers newer than IE11. +Replay with canvas requires Node 12+, and browsers newer than IE11. ## Installation From f3290c4630df755cd80f4747c74d55211a864145 Mon Sep 17 00:00:00 2001 From: Billy Vong Date: Wed, 10 Jan 2024 08:48:01 -0500 Subject: [PATCH 15/50] do not publish as separate package --- .craft.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.craft.yml b/.craft.yml index 7c09cb4ddd4c..1a4b07c18874 100644 --- a/.craft.yml +++ b/.craft.yml @@ -32,10 +32,6 @@ targets: - name: npm id: '@sentry-internal/feedback' includeNames: /^sentry-internal-feedback-\d.*\.tgz$/ - ## 1.8 ReplayCanvas package (browser only) - - name: npm - id: '@sentry-internal/replay-canvas' - includeNames: /^sentry-internal-replay-canvas-\d.*\.tgz$/ ## 2. Browser & Node SDKs - name: npm From 80bd36f313e205b36dea2a61510405344bf8bb88 Mon Sep 17 00:00:00 2001 From: Billy Vong Date: Wed, 10 Jan 2024 08:53:18 -0500 Subject: [PATCH 16/50] convert to fn --- packages/replay-canvas/src/canvas.ts | 60 +++++++++++----------------- 1 file changed, 23 insertions(+), 37 deletions(-) diff --git a/packages/replay-canvas/src/canvas.ts b/packages/replay-canvas/src/canvas.ts index cbe7d1eceeeb..2cb3ee793dcf 100644 --- a/packages/replay-canvas/src/canvas.ts +++ b/packages/replay-canvas/src/canvas.ts @@ -1,6 +1,7 @@ import { CanvasManager } from '@sentry-internal/rrweb'; +import { convertIntegrationFnToClass } from '@sentry/core'; import type { CanvasManagerInterface } from '@sentry/replay'; -import type { Integration } from '@sentry/types'; +import { IntegrationFn } from '@sentry/types'; interface ReplayCanvasOptions { quality: 'low' | 'medium' | 'high'; @@ -48,43 +49,28 @@ const CANVAS_QUALITY = { }, }; -/** An integration to add canvas recording to replay. */ -export class ReplayCanvas implements Integration { - /** - * @inheritDoc - */ - public static id: string = 'ReplayCanvas'; +const INTEGRATION_NAME = 'ReplayCanvas'; - /** - * @inheritDoc - */ - public name: string; - - private _canvasOptions: ReplayCanvasOptions; - - public constructor(options: Partial = {}) { - this.name = ReplayCanvas.id; - this._canvasOptions = { - quality: options.quality || 'medium', - }; - } +/** + * An integration to add canvas recording to replay. + */ +const replayCanvasIntegration = ((options: Partial = {}) => { + const _canvasOptions = { + quality: options.quality || 'medium', + }; - /** @inheritdoc */ - public setupOnce(): void { - // noop - } + return { + name: INTEGRATION_NAME, + getOptions(): ReplayCanvasIntegrationOptions { + const { quality } = _canvasOptions; - /** - * Get the options that should be merged into replay options. - * This is what is actually called by the Replay integration to setup canvas. - */ - public getOptions(): ReplayCanvasIntegrationOptions { - const { quality } = this._canvasOptions; + return { + recordCanvas: true, + getCanvasManager: (options: ConstructorParameters[0]) => new CanvasManager(options), + ...(CANVAS_QUALITY[quality || 'medium'] || CANVAS_QUALITY.medium), + }; + } + }; +}) satisfies IntegrationFn; - return { - recordCanvas: true, - getCanvasManager: (options: ConstructorParameters[0]) => new CanvasManager(options), - ...(CANVAS_QUALITY[quality || 'medium'] || CANVAS_QUALITY.medium), - }; - } -} +export const ReplayCanvasIntegration = convertIntegrationFnToClass(replayCanvasIntegration, INTEGRATION_NAME); From b5e1e9ce95500a7e2631b4313922340e0d7ca6ac Mon Sep 17 00:00:00 2001 From: Billy Vong Date: Wed, 10 Jan 2024 09:18:21 -0500 Subject: [PATCH 17/50] remove yarn lockfile --- packages/replay-canvas/yarn.lock | 469 ------------------------------- 1 file changed, 469 deletions(-) delete mode 100644 packages/replay-canvas/yarn.lock diff --git a/packages/replay-canvas/yarn.lock b/packages/replay-canvas/yarn.lock deleted file mode 100644 index 08c8645d6c10..000000000000 --- a/packages/replay-canvas/yarn.lock +++ /dev/null @@ -1,469 +0,0 @@ -# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. -# yarn lockfile v1 - - -"@ampproject/remapping@^2.2.0": - version "2.2.1" - resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.2.1.tgz#99e8e11851128b8702cd57c33684f1d0f260b630" - integrity sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg== - dependencies: - "@jridgewell/gen-mapping" "^0.3.0" - "@jridgewell/trace-mapping" "^0.3.9" - -"@babel/code-frame@^7.22.13", "@babel/code-frame@^7.23.5": - version "7.23.5" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.23.5.tgz#9009b69a8c602293476ad598ff53e4562e15c244" - integrity sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA== - dependencies: - "@babel/highlight" "^7.23.4" - chalk "^2.4.2" - -"@babel/compat-data@^7.23.5": - version "7.23.5" - resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.23.5.tgz#ffb878728bb6bdcb6f4510aa51b1be9afb8cfd98" - integrity sha512-uU27kfDRlhfKl+w1U6vp16IuvSLtjAxdArVXPa9BvLkrr7CYIsxH5adpHObeAGY/41+syctUWOZ140a2Rvkgjw== - -"@babel/core@^7.17.5": - version "7.23.7" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.23.7.tgz#4d8016e06a14b5f92530a13ed0561730b5c6483f" - integrity sha512-+UpDgowcmqe36d4NwqvKsyPMlOLNGMsfMmQ5WGCu+siCe3t3dfe9njrzGfdN4qq+bcNUt0+Vw6haRxBOycs4dw== - dependencies: - "@ampproject/remapping" "^2.2.0" - "@babel/code-frame" "^7.23.5" - "@babel/generator" "^7.23.6" - "@babel/helper-compilation-targets" "^7.23.6" - "@babel/helper-module-transforms" "^7.23.3" - "@babel/helpers" "^7.23.7" - "@babel/parser" "^7.23.6" - "@babel/template" "^7.22.15" - "@babel/traverse" "^7.23.7" - "@babel/types" "^7.23.6" - convert-source-map "^2.0.0" - debug "^4.1.0" - gensync "^1.0.0-beta.2" - json5 "^2.2.3" - semver "^6.3.1" - -"@babel/generator@^7.23.6": - version "7.23.6" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.23.6.tgz#9e1fca4811c77a10580d17d26b57b036133f3c2e" - integrity sha512-qrSfCYxYQB5owCmGLbl8XRpX1ytXlpueOb0N0UmQwA073KZxejgQTzAmJezxvpwQD9uGtK2shHdi55QT+MbjIw== - dependencies: - "@babel/types" "^7.23.6" - "@jridgewell/gen-mapping" "^0.3.2" - "@jridgewell/trace-mapping" "^0.3.17" - jsesc "^2.5.1" - -"@babel/helper-compilation-targets@^7.23.6": - version "7.23.6" - resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.23.6.tgz#4d79069b16cbcf1461289eccfbbd81501ae39991" - integrity sha512-9JB548GZoQVmzrFgp8o7KxdgkTGm6xs9DW0o/Pim72UDjzr5ObUQ6ZzYPqA+g9OTS2bBQoctLJrky0RDCAWRgQ== - dependencies: - "@babel/compat-data" "^7.23.5" - "@babel/helper-validator-option" "^7.23.5" - browserslist "^4.22.2" - lru-cache "^5.1.1" - semver "^6.3.1" - -"@babel/helper-environment-visitor@^7.22.20": - version "7.22.20" - resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz#96159db61d34a29dba454c959f5ae4a649ba9167" - integrity sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA== - -"@babel/helper-function-name@^7.23.0": - version "7.23.0" - resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz#1f9a3cdbd5b2698a670c30d2735f9af95ed52759" - integrity sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw== - dependencies: - "@babel/template" "^7.22.15" - "@babel/types" "^7.23.0" - -"@babel/helper-hoist-variables@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz#c01a007dac05c085914e8fb652b339db50d823bb" - integrity sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw== - dependencies: - "@babel/types" "^7.22.5" - -"@babel/helper-module-imports@^7.22.15": - version "7.22.15" - resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz#16146307acdc40cc00c3b2c647713076464bdbf0" - integrity sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w== - dependencies: - "@babel/types" "^7.22.15" - -"@babel/helper-module-transforms@^7.23.3": - version "7.23.3" - resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.23.3.tgz#d7d12c3c5d30af5b3c0fcab2a6d5217773e2d0f1" - integrity sha512-7bBs4ED9OmswdfDzpz4MpWgSrV7FXlc3zIagvLFjS5H+Mk7Snr21vQ6QwrsoCGMfNC4e4LQPdoULEt4ykz0SRQ== - dependencies: - "@babel/helper-environment-visitor" "^7.22.20" - "@babel/helper-module-imports" "^7.22.15" - "@babel/helper-simple-access" "^7.22.5" - "@babel/helper-split-export-declaration" "^7.22.6" - "@babel/helper-validator-identifier" "^7.22.20" - -"@babel/helper-simple-access@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz#4938357dc7d782b80ed6dbb03a0fba3d22b1d5de" - integrity sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w== - dependencies: - "@babel/types" "^7.22.5" - -"@babel/helper-split-export-declaration@^7.22.6": - version "7.22.6" - resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz#322c61b7310c0997fe4c323955667f18fcefb91c" - integrity sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g== - dependencies: - "@babel/types" "^7.22.5" - -"@babel/helper-string-parser@^7.23.4": - version "7.23.4" - resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.23.4.tgz#9478c707febcbbe1ddb38a3d91a2e054ae622d83" - integrity sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ== - -"@babel/helper-validator-identifier@^7.22.20": - version "7.22.20" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz#c4ae002c61d2879e724581d96665583dbc1dc0e0" - integrity sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A== - -"@babel/helper-validator-option@^7.23.5": - version "7.23.5" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.23.5.tgz#907a3fbd4523426285365d1206c423c4c5520307" - integrity sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw== - -"@babel/helpers@^7.23.7": - version "7.23.8" - resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.23.8.tgz#fc6b2d65b16847fd50adddbd4232c76378959e34" - integrity sha512-KDqYz4PiOWvDFrdHLPhKtCThtIcKVy6avWD2oG4GEvyQ+XDZwHD4YQd+H2vNMnq2rkdxsDkU82T+Vk8U/WXHRQ== - dependencies: - "@babel/template" "^7.22.15" - "@babel/traverse" "^7.23.7" - "@babel/types" "^7.23.6" - -"@babel/highlight@^7.23.4": - version "7.23.4" - resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.23.4.tgz#edaadf4d8232e1a961432db785091207ead0621b" - integrity sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A== - dependencies: - "@babel/helper-validator-identifier" "^7.22.20" - chalk "^2.4.2" - js-tokens "^4.0.0" - -"@babel/parser@^7.22.15", "@babel/parser@^7.23.6": - version "7.23.6" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.23.6.tgz#ba1c9e512bda72a47e285ae42aff9d2a635a9e3b" - integrity sha512-Z2uID7YJ7oNvAI20O9X0bblw7Qqs8Q2hFy0R9tAfnfLkp5MW0UH9eUvnDSnFwKZ0AvgS1ucqR4KzvVHgnke1VQ== - -"@babel/template@^7.22.15": - version "7.22.15" - resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.22.15.tgz#09576efc3830f0430f4548ef971dde1350ef2f38" - integrity sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w== - dependencies: - "@babel/code-frame" "^7.22.13" - "@babel/parser" "^7.22.15" - "@babel/types" "^7.22.15" - -"@babel/traverse@^7.23.7": - version "7.23.7" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.23.7.tgz#9a7bf285c928cb99b5ead19c3b1ce5b310c9c305" - integrity sha512-tY3mM8rH9jM0YHFGyfC0/xf+SB5eKUu7HPj7/k3fpi9dAlsMc5YbQvDi0Sh2QTPXqMhyaAtzAr807TIyfQrmyg== - dependencies: - "@babel/code-frame" "^7.23.5" - "@babel/generator" "^7.23.6" - "@babel/helper-environment-visitor" "^7.22.20" - "@babel/helper-function-name" "^7.23.0" - "@babel/helper-hoist-variables" "^7.22.5" - "@babel/helper-split-export-declaration" "^7.22.6" - "@babel/parser" "^7.23.6" - "@babel/types" "^7.23.6" - debug "^4.3.1" - globals "^11.1.0" - -"@babel/types@^7.22.15", "@babel/types@^7.22.5", "@babel/types@^7.23.0", "@babel/types@^7.23.6": - version "7.23.6" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.23.6.tgz#be33fdb151e1f5a56877d704492c240fc71c7ccd" - integrity sha512-+uarb83brBzPKN38NX1MkB6vb6+mwvR6amUulqAE7ccQw1pEl+bCia9TbdG1lsnFP7lZySvUn37CHyXQdfTwzg== - dependencies: - "@babel/helper-string-parser" "^7.23.4" - "@babel/helper-validator-identifier" "^7.22.20" - to-fast-properties "^2.0.0" - -"@jridgewell/gen-mapping@^0.3.0", "@jridgewell/gen-mapping@^0.3.2": - version "0.3.3" - resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz#7e02e6eb5df901aaedb08514203b096614024098" - integrity sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ== - dependencies: - "@jridgewell/set-array" "^1.0.1" - "@jridgewell/sourcemap-codec" "^1.4.10" - "@jridgewell/trace-mapping" "^0.3.9" - -"@jridgewell/resolve-uri@^3.1.0": - version "3.1.1" - resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz#c08679063f279615a3326583ba3a90d1d82cc721" - integrity sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA== - -"@jridgewell/set-array@^1.0.1": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.1.2.tgz#7c6cf998d6d20b914c0a55a91ae928ff25965e72" - integrity sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw== - -"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.14": - version "1.4.15" - resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32" - integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg== - -"@jridgewell/trace-mapping@^0.3.17", "@jridgewell/trace-mapping@^0.3.9": - version "0.3.20" - resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.20.tgz#72e45707cf240fa6b081d0366f8265b0cd10197f" - integrity sha512-R8LcPeWZol2zR8mmH3JeKQ6QRCFb7XgUhV9ZlGhHLGyg4wpPiPZNQOOWhFZhxKw8u//yTbNGI42Bx/3paXEQ+Q== - dependencies: - "@jridgewell/resolve-uri" "^3.1.0" - "@jridgewell/sourcemap-codec" "^1.4.14" - -"@sentry-internal/rrdom@2.7.3": - version "2.7.3" - resolved "https://registry.yarnpkg.com/@sentry-internal/rrdom/-/rrdom-2.7.3.tgz#2efe68a9cf23de9a8970acf4303748cdd7866b20" - integrity sha512-XD14G4Lv3ppvJlR7VkkCgHTKu1ylh7yvXdSsN5/FyGTH+IAXQIKL5nINIgWZTN3noNBWV9R0vcHDufXG/WktWA== - dependencies: - "@sentry-internal/rrweb-snapshot" "2.7.3" - -"@sentry-internal/rrweb-snapshot@2.7.3": - version "2.7.3" - resolved "https://registry.yarnpkg.com/@sentry-internal/rrweb-snapshot/-/rrweb-snapshot-2.7.3.tgz#9a7173825a31c07ccf27a5956f154400e11fdd97" - integrity sha512-mSZuBPmWia3x9wCuaJiZMD9ZVDnFv7TSG1Nz9X4ZqWb3DdaxB2MogGUU/2aTVqmRj6F91nl+GHb5NpmNYojUsw== - -"@sentry-internal/rrweb-types@2.7.3": - version "2.7.3" - resolved "https://registry.yarnpkg.com/@sentry-internal/rrweb-types/-/rrweb-types-2.7.3.tgz#e38737bc5c31aa9dfb8ce8faf46af374c2aa7cbb" - integrity sha512-EqALxhZtvH0rimYfj7J48DRC+fj+AGsZ/VDdOPKh3MQXptTyHncoWBj4ZtB1AaH7foYUr+2wkyxl3HqMVwe+6g== - dependencies: - "@sentry-internal/rrweb-snapshot" "2.7.3" - -"@sentry-internal/rrweb@file:.yalc/@sentry-internal/rrweb": - version "2.7.3" - dependencies: - "@sentry-internal/rrdom" "2.7.3" - "@sentry-internal/rrweb-snapshot" "2.7.3" - "@sentry-internal/rrweb-types" "2.7.3" - "@types/css-font-loading-module" "0.0.7" - "@xstate/fsm" "^1.4.0" - base64-arraybuffer "^1.0.1" - fflate "^0.4.4" - mitt "^3.0.0" - -"@sentry-internal/tracing@7.92.0": - version "7.92.0" - resolved "https://registry.yarnpkg.com/@sentry-internal/tracing/-/tracing-7.92.0.tgz#505d94a93b5df965ec6bfb35da43389988259d4d" - integrity sha512-ur55vPcUUUWFUX4eVLNP71ohswK7ZZpleNZw9Y1GfLqyI+0ILQUwjtzqItJrdClvVsdRZJMRmDV40Hp9Lbb9mA== - dependencies: - "@sentry/core" "7.92.0" - "@sentry/types" "7.92.0" - "@sentry/utils" "7.92.0" - -"@sentry/core@7.92.0": - version "7.92.0" - resolved "https://registry.yarnpkg.com/@sentry/core/-/core-7.92.0.tgz#4e74c1959348b698226c49ead7a24e165502b55c" - integrity sha512-1Tly7YB2I1byI5xb0Cwrxs56Rhww+6mQ7m9P7rTmdC3/ijOzbEoohtYIUPwcooCEarpbEJe/tAayRx6BrH2UbQ== - dependencies: - "@sentry/types" "7.92.0" - "@sentry/utils" "7.92.0" - -"@sentry/replay@7.92.0": - version "7.92.0" - resolved "https://registry.yarnpkg.com/@sentry/replay/-/replay-7.92.0.tgz#d94e9f6b72e540e73378a74ca1190068edd447f2" - integrity sha512-G1t9Uvc9cR8VpNkElwvHIMGzykjIKikb10n0tfVd3e+rBPMCCjCPWOduwG6jZYxcvCjTpqmJh6NSLXxL/Mt4JA== - dependencies: - "@sentry-internal/tracing" "7.92.0" - "@sentry/core" "7.92.0" - "@sentry/types" "7.92.0" - "@sentry/utils" "7.92.0" - -"@sentry/types@7.92.0": - version "7.92.0" - resolved "https://registry.yarnpkg.com/@sentry/types/-/types-7.92.0.tgz#4c308fdb316c0272f55f0816230fe87e7b9b551a" - integrity sha512-APmSOuZuoRGpbPpPeYIbMSplPjiWNLZRQa73QiXuTflW4Tu/ItDlU8hOa2+A6JKVkJCuD2EN6yUrxDGSMyNXeg== - -"@sentry/utils@7.92.0": - version "7.92.0" - resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-7.92.0.tgz#20ed29742594eab007f9ff72e008b5262456a319" - integrity sha512-3nEfrQ1z28b/2zgFGANPh5yMVtgwXmrasZxTvKbrAj+KWJpjrJHrIR84r9W277J44NMeZ5RhRW2uoDmuBslPnA== - dependencies: - "@sentry/types" "7.92.0" - -"@types/css-font-loading-module@0.0.7": - version "0.0.7" - resolved "https://registry.yarnpkg.com/@types/css-font-loading-module/-/css-font-loading-module-0.0.7.tgz#2f98ede46acc0975de85c0b7b0ebe06041d24601" - integrity sha512-nl09VhutdjINdWyXxHWN/w9zlNCfr60JUqJbd24YXUuCwgeL0TpFSdElCwb6cxfB6ybE19Gjj4g0jsgkXxKv1Q== - -"@xstate/fsm@^1.4.0": - version "1.6.5" - resolved "https://registry.yarnpkg.com/@xstate/fsm/-/fsm-1.6.5.tgz#f599e301997ad7e3c572a0b1ff0696898081bea5" - integrity sha512-b5o1I6aLNeYlU/3CPlj/Z91ybk1gUsKT+5NAJI+2W4UjvS5KLG28K9v5UvNoFVjHV8PajVZ00RH3vnjyQO7ZAw== - -ansi-styles@^3.2.1: - version "3.2.1" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" - integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== - dependencies: - color-convert "^1.9.0" - -base64-arraybuffer@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz#1c37589a7c4b0746e34bd1feb951da2df01c1bdc" - integrity sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ== - -browserslist@^4.22.2: - version "4.22.2" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.22.2.tgz#704c4943072bd81ea18997f3bd2180e89c77874b" - integrity sha512-0UgcrvQmBDvZHFGdYUehrCNIazki7/lUP3kkoi/r3YB2amZbFM9J43ZRkJTXBUZK4gmx56+Sqk9+Vs9mwZx9+A== - dependencies: - caniuse-lite "^1.0.30001565" - electron-to-chromium "^1.4.601" - node-releases "^2.0.14" - update-browserslist-db "^1.0.13" - -caniuse-lite@^1.0.30001565: - version "1.0.30001576" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001576.tgz#893be772cf8ee6056d6c1e2d07df365b9ec0a5c4" - integrity sha512-ff5BdakGe2P3SQsMsiqmt1Lc8221NR1VzHj5jXN5vBny9A6fpze94HiVV/n7XRosOlsShJcvMv5mdnpjOGCEgg== - -chalk@^2.4.2: - version "2.4.2" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" - integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== - dependencies: - ansi-styles "^3.2.1" - escape-string-regexp "^1.0.5" - supports-color "^5.3.0" - -color-convert@^1.9.0: - version "1.9.3" - resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" - integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== - dependencies: - color-name "1.1.3" - -color-name@1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" - integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw== - -convert-source-map@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a" - integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg== - -debug@^4.1.0, debug@^4.3.1: - version "4.3.4" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" - integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== - dependencies: - ms "2.1.2" - -electron-to-chromium@^1.4.601: - version "1.4.624" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.624.tgz#ff40ac0afeaeda0c2c7b8cfc19d93c49adb1245b" - integrity sha512-w9niWuheXjz23vezH3w90n9KKcHe0UkhTfJ+rXJkuGGogHyQbQ7KS1x0a8ER4LbI3ljFS/gqxKh1TidNXDMHOg== - -escalade@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" - integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== - -escape-string-regexp@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" - integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg== - -fflate@^0.4.4: - version "0.4.8" - resolved "https://registry.yarnpkg.com/fflate/-/fflate-0.4.8.tgz#f90b82aefbd8ac174213abb338bd7ef848f0f5ae" - integrity sha512-FJqqoDBR00Mdj9ppamLa/Y7vxm+PRmNWA67N846RvsoYVMKB4q3y/de5PA7gUmRMYK/8CMz2GDZQmCRN1wBcWA== - -gensync@^1.0.0-beta.2: - version "1.0.0-beta.2" - resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" - integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== - -globals@^11.1.0: - version "11.12.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" - integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== - -has-flag@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" - integrity sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw== - -js-tokens@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" - integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== - -jsesc@^2.5.1: - version "2.5.2" - resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" - integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== - -json5@^2.2.3: - version "2.2.3" - resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" - integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== - -lru-cache@^5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" - integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w== - dependencies: - yallist "^3.0.2" - -mitt@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/mitt/-/mitt-3.0.1.tgz#ea36cf0cc30403601ae074c8f77b7092cdab36d1" - integrity sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw== - -ms@2.1.2: - version "2.1.2" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" - integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== - -node-releases@^2.0.14: - version "2.0.14" - resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.14.tgz#2ffb053bceb8b2be8495ece1ab6ce600c4461b0b" - integrity sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw== - -picocolors@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" - integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== - -semver@^6.3.1: - version "6.3.1" - resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" - integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== - -supports-color@^5.3.0: - version "5.5.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" - integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== - dependencies: - has-flag "^3.0.0" - -to-fast-properties@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" - integrity sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog== - -update-browserslist-db@^1.0.13: - version "1.0.13" - resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz#3c5e4f5c083661bd38ef64b6328c26ed6c8248c4" - integrity sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg== - dependencies: - escalade "^3.1.1" - picocolors "^1.0.0" - -yallist@^3.0.2: - version "3.1.1" - resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" - integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== From 46b5f50cff2ed34de5ff7b3bc915267057974683 Mon Sep 17 00:00:00 2001 From: Billy Vong Date: Wed, 10 Jan 2024 10:01:36 -0500 Subject: [PATCH 18/50] refactor out integrations abstraction --- .../canvas/withCanvasIntegrationFirst/test.ts | 7 ++--- .../withCanvasIntegrationSecond/test.ts | 7 ++--- .../canvas/withoutCanvasIntegration/test.ts | 2 +- .../utils/replayHelpers.ts | 13 ++++++--- packages/replay-canvas/src/canvas.ts | 6 ++-- packages/replay/src/integration.ts | 12 ++++++-- packages/replay/src/replay.ts | 29 ++++++++----------- packages/replay/src/types/replay.ts | 15 ++++++++-- packages/replay/src/types/rrweb.ts | 2 +- 9 files changed, 54 insertions(+), 39 deletions(-) diff --git a/dev-packages/browser-integration-tests/suites/replay/canvas/withCanvasIntegrationFirst/test.ts b/dev-packages/browser-integration-tests/suites/replay/canvas/withCanvasIntegrationFirst/test.ts index 84a4d663cec6..4caeea4ba0e5 100644 --- a/dev-packages/browser-integration-tests/suites/replay/canvas/withCanvasIntegrationFirst/test.ts +++ b/dev-packages/browser-integration-tests/suites/replay/canvas/withCanvasIntegrationFirst/test.ts @@ -1,5 +1,4 @@ import { expect } from '@playwright/test'; -import type { ReplayCanvasIntegrationOptions } from '@sentry-internal/replay-canvas'; import { sentryTest } from '../../../../utils/fixtures'; import { getReplaySnapshot, shouldSkipReplayTest } from '../../../../utils/replayHelpers'; @@ -22,8 +21,8 @@ sentryTest('sets up canvas when adding ReplayCanvas integration first', async ({ await page.goto(url); const replay = await getReplaySnapshot(page); - const canvasOptions = replay._integrations.canvas as ReplayCanvasIntegrationOptions; - expect(canvasOptions.sampling.canvas).toBe(2); - expect(canvasOptions.dataURLOptions.quality).toBe(0.4); + const canvasOptions = replay._canvas; + expect(canvasOptions?.sampling.canvas).toBe(2); + expect(canvasOptions?.dataURLOptions.quality).toBe(0.4); expect(replay._hasCanvas).toBe(true); }); diff --git a/dev-packages/browser-integration-tests/suites/replay/canvas/withCanvasIntegrationSecond/test.ts b/dev-packages/browser-integration-tests/suites/replay/canvas/withCanvasIntegrationSecond/test.ts index 95dc3e93999c..61367935ba22 100644 --- a/dev-packages/browser-integration-tests/suites/replay/canvas/withCanvasIntegrationSecond/test.ts +++ b/dev-packages/browser-integration-tests/suites/replay/canvas/withCanvasIntegrationSecond/test.ts @@ -1,5 +1,4 @@ import { expect } from '@playwright/test'; -import type { ReplayCanvasIntegrationOptions } from '@sentry-internal/replay-canvas'; import { sentryTest } from '../../../../utils/fixtures'; import { getReplaySnapshot, shouldSkipReplayTest } from '../../../../utils/replayHelpers'; @@ -22,8 +21,8 @@ sentryTest('sets up canvas when adding ReplayCanvas integration after Replay', a await page.goto(url); const replay = await getReplaySnapshot(page); - const canvasOptions = replay._integrations.canvas as ReplayCanvasIntegrationOptions; - expect(canvasOptions.sampling.canvas).toBe(2); - expect(canvasOptions.dataURLOptions.quality).toBe(0.4); + const canvasOptions = replay._canvas; + expect(canvasOptions?.sampling.canvas).toBe(2); + expect(canvasOptions?.dataURLOptions.quality).toBe(0.4); expect(replay._hasCanvas).toBe(true); }); diff --git a/dev-packages/browser-integration-tests/suites/replay/canvas/withoutCanvasIntegration/test.ts b/dev-packages/browser-integration-tests/suites/replay/canvas/withoutCanvasIntegration/test.ts index 072a1dc66260..af3ec4a4bc97 100644 --- a/dev-packages/browser-integration-tests/suites/replay/canvas/withoutCanvasIntegration/test.ts +++ b/dev-packages/browser-integration-tests/suites/replay/canvas/withoutCanvasIntegration/test.ts @@ -21,7 +21,7 @@ sentryTest('does not setup up canvas without ReplayCanvas integration', async ({ await page.goto(url); const replay = await getReplaySnapshot(page); - const canvasOptions = replay._integrations.canvas; + const canvasOptions = replay._canvas; expect(canvasOptions).toBe(undefined); expect(replay._hasCanvas).toBe(false); }); diff --git a/dev-packages/browser-integration-tests/utils/replayHelpers.ts b/dev-packages/browser-integration-tests/utils/replayHelpers.ts index cee47b61d635..4b1623ed7318 100644 --- a/dev-packages/browser-integration-tests/utils/replayHelpers.ts +++ b/dev-packages/browser-integration-tests/utils/replayHelpers.ts @@ -1,4 +1,5 @@ /* eslint-disable max-lines */ +import { 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'; @@ -174,13 +175,17 @@ export function getReplaySnapshot(page: Page): Promise<{ _isEnabled: boolean; _context: InternalEventContext; _options: ReplayPluginOptions; - _integrations: Record; + _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 = { @@ -188,9 +193,9 @@ export function getReplaySnapshot(page: Page): Promise<{ _isEnabled: replay.isEnabled(), _context: replay.getContext(), _options: replay.getOptions(), - _integrations: replay.getIntegrations(), + _canvas: replay['_canvas'], // We cannot pass the function through as this is serialized - _hasCanvas: typeof replay.getIntegrations().canvas?.getCanvasManager === 'function', + _hasCanvas: typeof replay['_canvas']?.getCanvasManager === 'function', session: replay.session, recordingMode: replay.recordingMode, }; diff --git a/packages/replay-canvas/src/canvas.ts b/packages/replay-canvas/src/canvas.ts index 2cb3ee793dcf..6f54a109861d 100644 --- a/packages/replay-canvas/src/canvas.ts +++ b/packages/replay-canvas/src/canvas.ts @@ -1,7 +1,7 @@ import { CanvasManager } from '@sentry-internal/rrweb'; import { convertIntegrationFnToClass } from '@sentry/core'; import type { CanvasManagerInterface } from '@sentry/replay'; -import { IntegrationFn } from '@sentry/types'; +import type { IntegrationFn } from '@sentry/types'; interface ReplayCanvasOptions { quality: 'low' | 'medium' | 'high'; @@ -69,8 +69,8 @@ const replayCanvasIntegration = ((options: Partial = {}) => getCanvasManager: (options: ConstructorParameters[0]) => new CanvasManager(options), ...(CANVAS_QUALITY[quality || 'medium'] || CANVAS_QUALITY.medium), }; - } + }, }; }) satisfies IntegrationFn; -export const ReplayCanvasIntegration = convertIntegrationFnToClass(replayCanvasIntegration, INTEGRATION_NAME); +export const ReplayCanvas = convertIntegrationFnToClass(INTEGRATION_NAME, replayCanvasIntegration); diff --git a/packages/replay/src/integration.ts b/packages/replay/src/integration.ts index fa32f2fecb5b..c73da03c1b85 100644 --- a/packages/replay/src/integration.ts +++ b/packages/replay/src/integration.ts @@ -10,7 +10,13 @@ import { MIN_REPLAY_DURATION_LIMIT, } from './constants'; import { ReplayContainer } from './replay'; -import type { RecordingOptions, ReplayConfiguration, ReplayPluginOptions, SendBufferedReplayOptions } from './types'; +import type { + RecordingOptions, + ReplayCanvasIntegrationOptions, + ReplayConfiguration, + ReplayPluginOptions, + SendBufferedReplayOptions, +} from './types'; import { getPrivacyOptions } from './util/getPrivacyOptions'; import { maskAttribute } from './util/maskAttribute'; @@ -355,13 +361,13 @@ Sentry.init({ replaysOnErrorSampleRate: ${errorSampleRate} })`, try { const client = getClient()!; const canvasIntegration = client.getIntegrationByName!('ReplayCanvas') as Integration & { - getOptions(): Partial; + getOptions(): ReplayCanvasIntegrationOptions; }; if (!canvasIntegration) { return; } - this._replay!.addIntegration('canvas', canvasIntegration.getOptions()); + this._replay!['_canvas'] = canvasIntegration.getOptions(); } catch { // ignore errors here } diff --git a/packages/replay/src/replay.ts b/packages/replay/src/replay.ts index 07cee3433136..8302bbb0c8b6 100644 --- a/packages/replay/src/replay.ts +++ b/packages/replay/src/replay.ts @@ -38,6 +38,7 @@ import type { RecordingEvent, RecordingOptions, ReplayBreadcrumbFrame, + ReplayCanvasIntegrationOptions, ReplayContainer as ReplayContainerInterface, ReplayPerformanceEntry, ReplayPluginOptions, @@ -145,9 +146,9 @@ export class ReplayContainer implements ReplayContainerInterface { private _context: InternalEventContext; /** - * Internal integrations (e.g. canvas) + * Internal use for canvas recording options */ - private _integrations: Record>; + private _canvas: ReplayCanvasIntegrationOptions | undefined; public constructor({ options, @@ -167,7 +168,6 @@ export class ReplayContainer implements ReplayContainerInterface { this._lastActivity = Date.now(); this._isEnabled = false; this._isPaused = false; - this._integrations = {}; this._hasInitializedCoreListeners = false; this._context = { errorIds: new Set(), @@ -228,11 +228,6 @@ export class ReplayContainer implements ReplayContainerInterface { return this._options; } - /** Get the replay integrations. [test only] */ - public getIntegrations(): Record> { - return { ...this._integrations }; - } - /** * Initializes the plugin based on sampling configuration. Should not be * called outside of constructor. @@ -348,7 +343,7 @@ export class ReplayContainer implements ReplayContainerInterface { */ public startRecording(): void { try { - const { canvas } = this._integrations; + const canvasOptions = this._canvas; this._stopRecording = record({ ...this._recordingOptions, @@ -358,7 +353,14 @@ export class ReplayContainer implements ReplayContainerInterface { ...(this.recordingMode === 'buffer' && { checkoutEveryNms: BUFFER_CHECKOUT_TIME }), emit: getHandleRecordingEmit(this), onMutation: this._onMutationHandler, - ...canvas, + ...(canvasOptions + ? { + recordCanvas: canvasOptions.recordCanvas, + getCanvasManager: canvasOptions.getCanvasManager, + sampling: canvasOptions.sampling, + dataURLOptions: canvasOptions.dataURLOptions, + } + : {}), }); } catch (err) { this._handleException(err); @@ -723,13 +725,6 @@ export class ReplayContainer implements ReplayContainerInterface { return spanToJSON(lastTransaction).description; } - /** - * Internal integration use only, should not be public - */ - public addIntegration(name: string, options: Record): void { - this._integrations[name] = options; - } - /** * Initialize and start all listeners to varying events (DOM, * Performance Observer, Recording, Sentry SDK, etc) diff --git a/packages/replay/src/types/replay.ts b/packages/replay/src/types/replay.ts index d22409154477..23f4d163b587 100644 --- a/packages/replay/src/types/replay.ts +++ b/packages/replay/src/types/replay.ts @@ -14,7 +14,7 @@ import type { SKIPPED, THROTTLED } from '../util/throttle'; import type { AllPerformanceEntry, AllPerformanceEntryData, ReplayPerformanceEntry } from './performance'; import type { ReplayFrameEvent } from './replayFrame'; import type { ReplayNetworkRequestOrResponse } from './request'; -import type { ReplayEventWithTime, RrwebRecordOptions } from './rrweb'; +import type { CanvasManagerInterface, CanvasManagerOptions, ReplayEventWithTime, RrwebRecordOptions } from './rrweb'; export type RecordingEvent = ReplayFrameEvent | ReplayEventWithTime; export type RecordingOptions = RrwebRecordOptions; @@ -502,7 +502,6 @@ export interface ReplayContainer { updateUserActivity(): void; addUpdate(cb: AddUpdateCallback): void; getOptions(): ReplayPluginOptions; - getIntegrations(): Record>; getSessionId(): string | undefined; checkAndHandleExpiredSession(): boolean | void; setInitialState(): void; @@ -536,3 +535,15 @@ export interface SlowClickConfig { scrollTimeout: number; ignoreSelector: string; } + +export interface ReplayCanvasIntegrationOptions { + recordCanvas: true; + getCanvasManager: (options: CanvasManagerOptions) => CanvasManagerInterface; + sampling: { + canvas: number; + }; + dataURLOptions: { + type: string; + quality: number; + }; +} diff --git a/packages/replay/src/types/rrweb.ts b/packages/replay/src/types/rrweb.ts index bbef4f94903f..ae5a8ca3bac6 100644 --- a/packages/replay/src/types/rrweb.ts +++ b/packages/replay/src/types/rrweb.ts @@ -53,7 +53,7 @@ export interface CanvasManagerInterface { unlock(): void; } -export interface GetCanvasManagerOptions { +export interface CanvasManagerOptions { recordCanvas: boolean; blockClass: string | RegExp; blockSelector: string | null; From 74d75652dd403c3a8236ddee62f0b2d9780f3734 Mon Sep 17 00:00:00 2001 From: Billy Vong Date: Wed, 10 Jan 2024 10:08:03 -0500 Subject: [PATCH 19/50] bump version --- packages/replay-canvas/package.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/replay-canvas/package.json b/packages/replay-canvas/package.json index 288ff6cecc99..7aeb5078f0d1 100644 --- a/packages/replay-canvas/package.json +++ b/packages/replay-canvas/package.json @@ -1,6 +1,6 @@ { "name": "@sentry-internal/replay-canvas", - "version": "7.92.0", + "version": "7.93.0", "description": "Replay canvas integration", "main": "build/npm/cjs/index.js", "module": "build/npm/esm/index.js", @@ -56,8 +56,8 @@ "@sentry-internal/rrweb": "2.7.3" }, "dependencies": { - "@sentry/replay": "7.92.0", - "@sentry/types": "7.92.0" + "@sentry/replay": "7.93.0", + "@sentry/types": "7.93.0" }, "engines": { "node": ">=12" From 5e5991cb349907062405fdefcabd4f6f6667ceef Mon Sep 17 00:00:00 2001 From: Billy Vong Date: Wed, 10 Jan 2024 10:12:52 -0500 Subject: [PATCH 20/50] lint --- packages/replay-canvas/src/canvas.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/replay-canvas/src/canvas.ts b/packages/replay-canvas/src/canvas.ts index 6f54a109861d..593187043682 100644 --- a/packages/replay-canvas/src/canvas.ts +++ b/packages/replay-canvas/src/canvas.ts @@ -73,4 +73,6 @@ const replayCanvasIntegration = ((options: Partial = {}) => }; }) satisfies IntegrationFn; +// TODO(v8) +// eslint-disable-next-line deprecation/deprecation export const ReplayCanvas = convertIntegrationFnToClass(INTEGRATION_NAME, replayCanvasIntegration); From 39eb70b7f549f5aa98482b7f433794e5cfb97fd3 Mon Sep 17 00:00:00 2001 From: Billy Vong Date: Wed, 10 Jan 2024 10:28:12 -0500 Subject: [PATCH 21/50] update eslint --- packages/replay-canvas/.eslintignore | 4 ---- 1 file changed, 4 deletions(-) diff --git a/packages/replay-canvas/.eslintignore b/packages/replay-canvas/.eslintignore index c76c6c2d64d1..b38db2f296ff 100644 --- a/packages/replay-canvas/.eslintignore +++ b/packages/replay-canvas/.eslintignore @@ -1,6 +1,2 @@ node_modules/ build/ -demo/build/ -# TODO: Check if we can re-introduce linting in demo -demo -metrics From 0dc1ea92d09107057c5e89ffe3308bc0f0c044d4 Mon Sep 17 00:00:00 2001 From: Billy Vong Date: Wed, 10 Jan 2024 14:43:03 -0500 Subject: [PATCH 22/50] upgrade rrweb pkg --- .../browser-integration-tests/package.json | 2 +- packages/replay-canvas/package.json | 2 +- packages/replay/package.json | 4 +- yarn.lock | 42 +++++++++---------- 4 files changed, 25 insertions(+), 25 deletions(-) diff --git a/dev-packages/browser-integration-tests/package.json b/dev-packages/browser-integration-tests/package.json index 2d7a2629af15..8fb2ce61b0ea 100644 --- a/dev-packages/browser-integration-tests/package.json +++ b/dev-packages/browser-integration-tests/package.json @@ -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", diff --git a/packages/replay-canvas/package.json b/packages/replay-canvas/package.json index 7aeb5078f0d1..5691ee06677d 100644 --- a/packages/replay-canvas/package.json +++ b/packages/replay-canvas/package.json @@ -53,7 +53,7 @@ "homepage": "https://docs.sentry.io/platforms/javascript/session-replay/", "devDependencies": { "@babel/core": "^7.17.5", - "@sentry-internal/rrweb": "2.7.3" + "@sentry-internal/rrweb": "2.8.0" }, "dependencies": { "@sentry/replay": "7.93.0", diff --git a/packages/replay/package.json b/packages/replay/package.json index 02a23ffc1df1..936b861a7f17 100644 --- a/packages/replay/package.json +++ b/packages/replay/package.json @@ -54,8 +54,8 @@ "devDependencies": { "@babel/core": "^7.17.5", "@sentry-internal/replay-worker": "7.93.0", - "@sentry-internal/rrweb": "2.7.3", - "@sentry-internal/rrweb-snapshot": "2.7.3", + "@sentry-internal/rrweb": "2.8.0", + "@sentry-internal/rrweb-snapshot": "2.8.0", "fflate": "^0.8.1", "jsdom-worker": "^0.2.1" }, diff --git a/yarn.lock b/yarn.lock index d888e192dd15..1c41f5a976da 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5231,33 +5231,33 @@ semver "7.3.2" semver-intersect "1.4.0" -"@sentry-internal/rrdom@2.7.3": - version "2.7.3" - resolved "https://registry.yarnpkg.com/@sentry-internal/rrdom/-/rrdom-2.7.3.tgz#2efe68a9cf23de9a8970acf4303748cdd7866b20" - integrity sha512-XD14G4Lv3ppvJlR7VkkCgHTKu1ylh7yvXdSsN5/FyGTH+IAXQIKL5nINIgWZTN3noNBWV9R0vcHDufXG/WktWA== +"@sentry-internal/rrdom@2.8.0": + version "2.8.0" + resolved "https://registry.yarnpkg.com/@sentry-internal/rrdom/-/rrdom-2.8.0.tgz#092c6201b71474f650fdda0320a9a82319c0f460" + integrity sha512-2jPoYMwRDwMjpYPnQ0/BTjpVvemABrQpH2zWuBYsACCLcAeCenNNQ7Qrqciv+hBnpUwBpTD1sYDqvSx2bP0C+w== dependencies: - "@sentry-internal/rrweb-snapshot" "2.7.3" + "@sentry-internal/rrweb-snapshot" "2.8.0" -"@sentry-internal/rrweb-snapshot@2.7.3": - version "2.7.3" - resolved "https://registry.yarnpkg.com/@sentry-internal/rrweb-snapshot/-/rrweb-snapshot-2.7.3.tgz#9a7173825a31c07ccf27a5956f154400e11fdd97" - integrity sha512-mSZuBPmWia3x9wCuaJiZMD9ZVDnFv7TSG1Nz9X4ZqWb3DdaxB2MogGUU/2aTVqmRj6F91nl+GHb5NpmNYojUsw== +"@sentry-internal/rrweb-snapshot@2.8.0": + version "2.8.0" + resolved "https://registry.yarnpkg.com/@sentry-internal/rrweb-snapshot/-/rrweb-snapshot-2.8.0.tgz#4db7c10125d53fb20b4ff2a2a61a63b3f3ccaac1" + integrity sha512-JTpwdT6sVS4X3zr+qBs6/SsCR2JMLGHMDz4pN48HHDuBmW0QqiXDrM4SD9Tl7vUahHBCO0hG+E/UGFUbCV2b3w== -"@sentry-internal/rrweb-types@2.7.3": - version "2.7.3" - resolved "https://registry.yarnpkg.com/@sentry-internal/rrweb-types/-/rrweb-types-2.7.3.tgz#e38737bc5c31aa9dfb8ce8faf46af374c2aa7cbb" - integrity sha512-EqALxhZtvH0rimYfj7J48DRC+fj+AGsZ/VDdOPKh3MQXptTyHncoWBj4ZtB1AaH7foYUr+2wkyxl3HqMVwe+6g== +"@sentry-internal/rrweb-types@2.8.0": + version "2.8.0" + resolved "https://registry.yarnpkg.com/@sentry-internal/rrweb-types/-/rrweb-types-2.8.0.tgz#ca1da1847630c4457a7b838d79f58b2b89b2d740" + integrity sha512-K66baWV0srzWl+ioUPf1FRGEp7gNOl8PqVaabmQfXc7RmA5t2215+63f1f3dMnEwMSJF0WmojA7orFcSy8sx0g== dependencies: - "@sentry-internal/rrweb-snapshot" "2.7.3" + "@sentry-internal/rrweb-snapshot" "2.8.0" -"@sentry-internal/rrweb@2.7.3": - version "2.7.3" - resolved "https://registry.yarnpkg.com/@sentry-internal/rrweb/-/rrweb-2.7.3.tgz#900ce7b1bd8ec43b5557d73698cc1b4dc9f47f72" - integrity sha512-K2pksQ1FgDumv54r1o0+RAKB3Qx3Zgx6OrQAkU/SI6/7HcBIxe7b4zepg80NyvnfohUs/relw2EoD3K3kqd8tg== +"@sentry-internal/rrweb@2.8.0": + version "2.8.0" + resolved "https://registry.yarnpkg.com/@sentry-internal/rrweb/-/rrweb-2.8.0.tgz#d1492bdabff2a87001dd7d4514038d40c142267f" + integrity sha512-vKqGk7epbi8a4wn5yWMJCe+m2tbYDHT7v258o/moj0YdlBXMxHjIBJF/m1AlqreQlCP7AESyFnYFX2OTdKyygQ== dependencies: - "@sentry-internal/rrdom" "2.7.3" - "@sentry-internal/rrweb-snapshot" "2.7.3" - "@sentry-internal/rrweb-types" "2.7.3" + "@sentry-internal/rrdom" "2.8.0" + "@sentry-internal/rrweb-snapshot" "2.8.0" + "@sentry-internal/rrweb-types" "2.8.0" "@types/css-font-loading-module" "0.0.7" "@xstate/fsm" "^1.4.0" base64-arraybuffer "^1.0.1" From 0e7963a74387c3400d06010efc0792c3e59ab9e0 Mon Sep 17 00:00:00 2001 From: Billy Vong Date: Wed, 10 Jan 2024 15:07:07 -0500 Subject: [PATCH 23/50] add snapshot() to interface --- packages/replay/src/types/rrweb.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/replay/src/types/rrweb.ts b/packages/replay/src/types/rrweb.ts index ae5a8ca3bac6..185c8be12611 100644 --- a/packages/replay/src/types/rrweb.ts +++ b/packages/replay/src/types/rrweb.ts @@ -51,6 +51,7 @@ export interface CanvasManagerInterface { unfreeze(): void; lock(): void; unlock(): void; + snapshot(): void; } export interface CanvasManagerOptions { From 7cbfe1a4f2fd6d03d967c712a3b6432ecb157dcd Mon Sep 17 00:00:00 2001 From: Billy Vong Date: Wed, 10 Jan 2024 15:18:36 -0500 Subject: [PATCH 24/50] missing setupOnce --- packages/replay-canvas/src/canvas.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/replay-canvas/src/canvas.ts b/packages/replay-canvas/src/canvas.ts index 593187043682..d8f6cb8db12d 100644 --- a/packages/replay-canvas/src/canvas.ts +++ b/packages/replay-canvas/src/canvas.ts @@ -61,6 +61,7 @@ const replayCanvasIntegration = ((options: Partial = {}) => return { name: INTEGRATION_NAME, + setupOnce() {}, getOptions(): ReplayCanvasIntegrationOptions { const { quality } = _canvasOptions; From 8aededc76a122cffb211a2e51abc5b966fcbb94b Mon Sep 17 00:00:00 2001 From: Billy Vong Date: Wed, 10 Jan 2024 15:41:14 -0500 Subject: [PATCH 25/50] litn --- dev-packages/browser-integration-tests/utils/replayHelpers.ts | 2 +- packages/replay-canvas/src/canvas.ts | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/dev-packages/browser-integration-tests/utils/replayHelpers.ts b/dev-packages/browser-integration-tests/utils/replayHelpers.ts index 4b1623ed7318..295ae82e0fa3 100644 --- a/dev-packages/browser-integration-tests/utils/replayHelpers.ts +++ b/dev-packages/browser-integration-tests/utils/replayHelpers.ts @@ -1,5 +1,5 @@ /* eslint-disable max-lines */ -import { ReplayCanvasIntegrationOptions } from '@sentry-internal/replay-canvas'; +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'; diff --git a/packages/replay-canvas/src/canvas.ts b/packages/replay-canvas/src/canvas.ts index d8f6cb8db12d..ee8b6f3a7c3c 100644 --- a/packages/replay-canvas/src/canvas.ts +++ b/packages/replay-canvas/src/canvas.ts @@ -61,6 +61,7 @@ const replayCanvasIntegration = ((options: Partial = {}) => return { name: INTEGRATION_NAME, + // eslint-disable-next-line @typescript-eslint/no-empty-function setupOnce() {}, getOptions(): ReplayCanvasIntegrationOptions { const { quality } = _canvasOptions; From b5e69ee81817515375dd996041424db6dcba1b7e Mon Sep 17 00:00:00 2001 From: Billy Vong Date: Wed, 10 Jan 2024 15:41:19 -0500 Subject: [PATCH 26/50] fix test --- packages/replay-canvas/test/canvas.test.ts | 26 +++++++++++++--------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/packages/replay-canvas/test/canvas.test.ts b/packages/replay-canvas/test/canvas.test.ts index d7d7b1c8ef22..e5464ba544ec 100644 --- a/packages/replay-canvas/test/canvas.test.ts +++ b/packages/replay-canvas/test/canvas.test.ts @@ -4,11 +4,14 @@ it('initializes with default options', () => { const rc = new ReplayCanvas(); expect(rc.getOptions()).toEqual({ - _experiments: { - canvas: { - manager: expect.any(Function), - quality: 'medium', - }, + recordCanvas: true, + getCanvasManager: expect.any(Function), + sampling: { + canvas: 2, + }, + dataURLOptions: { + type: 'image/webp', + quality: 0.4, }, }); }); @@ -17,11 +20,14 @@ it('initializes with quality option', () => { const rc = new ReplayCanvas({ quality: 'low' }); expect(rc.getOptions()).toEqual({ - _experiments: { - canvas: { - manager: expect.any(Function), - quality: 'low', - }, + recordCanvas: true, + getCanvasManager: expect.any(Function), + sampling: { + canvas: 1, + }, + dataURLOptions: { + type: 'image/webp', + quality: 0.25, }, }); }); From ecc4a85d02b0b82595f3810fa5e6072b7b23bf8e Mon Sep 17 00:00:00 2001 From: Billy Vong Date: Wed, 10 Jan 2024 15:54:30 -0500 Subject: [PATCH 27/50] remove old test, we have other coverage --- .../replay/test/integration/rrweb.test.ts | 29 ------------------- 1 file changed, 29 deletions(-) diff --git a/packages/replay/test/integration/rrweb.test.ts b/packages/replay/test/integration/rrweb.test.ts index 2e648358ffe3..82dd18f2d6ec 100644 --- a/packages/replay/test/integration/rrweb.test.ts +++ b/packages/replay/test/integration/rrweb.test.ts @@ -40,33 +40,4 @@ describe('Integration | rrweb', () => { } `); }); - - it('calls rrweb.record with default canvas options', async () => { - const { mockRecord } = await resetSdkMock({ - replayOptions: { - _experiments: { - canvas: { - // @ts-expect-error This should return - // CanvasManagerInterface, but we don't care about it - // for this test - manager: () => null, - }, - }, - }, - }); - - expect(mockRecord).toHaveBeenLastCalledWith( - expect.objectContaining({ - recordCanvas: true, - getCanvasManager: expect.any(Function), - dataURLOptions: { - quality: 0.4, - type: 'image/webp', - }, - sampling: { - canvas: 2, - }, - }), - ); - }); }); From 57046cdf27eeb060be58f0afd13c65dae705a099 Mon Sep 17 00:00:00 2001 From: Billy Vong Date: Wed, 10 Jan 2024 16:54:43 -0500 Subject: [PATCH 28/50] add replaycanvas to replay bundles --- packages/browser/src/index.bundle.feedback.ts | 4 ++-- packages/browser/src/index.bundle.replay.ts | 3 ++- .../src/index.bundle.tracing.replay.feedback.ts | 3 ++- packages/browser/src/index.bundle.tracing.replay.ts | 3 ++- packages/browser/src/index.bundle.tracing.ts | 4 ++-- packages/browser/src/index.bundle.ts | 10 ++++++++-- .../browser/test/unit/index.bundle.feedback.test.ts | 7 ++++++- packages/browser/test/unit/index.bundle.replay.test.ts | 2 ++ packages/browser/test/unit/index.bundle.test.ts | 2 ++ .../unit/index.bundle.tracing.replay.feedback.test.ts | 3 ++- .../test/unit/index.bundle.tracing.replay.test.ts | 2 ++ .../browser/test/unit/index.bundle.tracing.test.ts | 7 ++++++- packages/integration-shims/src/index.ts | 1 + 13 files changed, 39 insertions(+), 12 deletions(-) diff --git a/packages/browser/src/index.bundle.feedback.ts b/packages/browser/src/index.bundle.feedback.ts index 7aab6485d254..48d7b4aecf13 100644 --- a/packages/browser/src/index.bundle.feedback.ts +++ b/packages/browser/src/index.bundle.feedback.ts @@ -1,6 +1,6 @@ // This is exported so the loader does not fail when switching off Replay/Tracing import { Feedback } from '@sentry-internal/feedback'; -import { BrowserTracing, Replay, addTracingExtensions } from '@sentry-internal/integration-shims'; +import { BrowserTracing, Replay, ReplayCanvas, addTracingExtensions } from '@sentry-internal/integration-shims'; import * as Sentry from './index.bundle.base'; @@ -10,5 +10,5 @@ Sentry.Integrations.Replay = Replay; Sentry.Integrations.BrowserTracing = BrowserTracing; export * from './index.bundle.base'; -export { BrowserTracing, addTracingExtensions, Replay, Feedback }; +export { BrowserTracing, addTracingExtensions, Replay, ReplayCanvas, Feedback }; // Note: We do not export a shim for `Span` here, as that is quite complex and would blow up the bundle diff --git a/packages/browser/src/index.bundle.replay.ts b/packages/browser/src/index.bundle.replay.ts index 0b556bc71b8e..e1fe19cda06c 100644 --- a/packages/browser/src/index.bundle.replay.ts +++ b/packages/browser/src/index.bundle.replay.ts @@ -1,5 +1,6 @@ // This is exported so the loader does not fail when switching off Replay/Tracing import { BrowserTracing, Feedback, addTracingExtensions } from '@sentry-internal/integration-shims'; +import { ReplayCanvas } from '@sentry-internal/replay-canvas'; import { Replay } from '@sentry/replay'; import * as Sentry from './index.bundle.base'; @@ -10,5 +11,5 @@ Sentry.Integrations.Replay = Replay; Sentry.Integrations.BrowserTracing = BrowserTracing; export * from './index.bundle.base'; -export { BrowserTracing, addTracingExtensions, Replay, Feedback }; +export { BrowserTracing, addTracingExtensions, Replay, ReplayCanvas, Feedback }; // Note: We do not export a shim for `Span` here, as that is quite complex and would blow up the bundle diff --git a/packages/browser/src/index.bundle.tracing.replay.feedback.ts b/packages/browser/src/index.bundle.tracing.replay.feedback.ts index 5558703f7703..04dc02a8e946 100644 --- a/packages/browser/src/index.bundle.tracing.replay.feedback.ts +++ b/packages/browser/src/index.bundle.tracing.replay.feedback.ts @@ -1,4 +1,5 @@ import { Feedback } from '@sentry-internal/feedback'; +import { ReplayCanvas } from '@sentry-internal/replay-canvas'; import { BrowserTracing, Span, addExtensionMethods } from '@sentry-internal/tracing'; import { Replay } from '@sentry/replay'; @@ -14,5 +15,5 @@ Sentry.Integrations.BrowserTracing = BrowserTracing; // We are patching the global object with our hub extension methods addExtensionMethods(); -export { Feedback, Replay, BrowserTracing, Span, addExtensionMethods }; +export { Feedback, Replay, ReplayCanvas, BrowserTracing, Span, addExtensionMethods }; export * from './index.bundle.base'; diff --git a/packages/browser/src/index.bundle.tracing.replay.ts b/packages/browser/src/index.bundle.tracing.replay.ts index 0e7aa1ec19bc..683d1509f9d8 100644 --- a/packages/browser/src/index.bundle.tracing.replay.ts +++ b/packages/browser/src/index.bundle.tracing.replay.ts @@ -1,4 +1,5 @@ import { Feedback } from '@sentry-internal/integration-shims'; +import { ReplayCanvas } from '@sentry-internal/replay-canvas'; import { BrowserTracing, Span, addExtensionMethods } from '@sentry-internal/tracing'; import { Replay } from '@sentry/replay'; @@ -14,5 +15,5 @@ Sentry.Integrations.BrowserTracing = BrowserTracing; // We are patching the global object with our hub extension methods addExtensionMethods(); -export { Feedback, Replay, BrowserTracing, Span, addExtensionMethods }; +export { Feedback, Replay, ReplayCanvas, BrowserTracing, Span, addExtensionMethods }; export * from './index.bundle.base'; diff --git a/packages/browser/src/index.bundle.tracing.ts b/packages/browser/src/index.bundle.tracing.ts index 22b33962e860..0a958c634974 100644 --- a/packages/browser/src/index.bundle.tracing.ts +++ b/packages/browser/src/index.bundle.tracing.ts @@ -1,5 +1,5 @@ // This is exported so the loader does not fail when switching off Replay -import { Feedback, Replay } from '@sentry-internal/integration-shims'; +import { Feedback, Replay, ReplayCanvas } from '@sentry-internal/integration-shims'; import { BrowserTracing, Span, addExtensionMethods } from '@sentry-internal/tracing'; import * as Sentry from './index.bundle.base'; @@ -14,5 +14,5 @@ Sentry.Integrations.BrowserTracing = BrowserTracing; // We are patching the global object with our hub extension methods addExtensionMethods(); -export { Feedback, Replay, BrowserTracing, Span, addExtensionMethods }; +export { Feedback, Replay, ReplayCanvas, BrowserTracing, Span, addExtensionMethods }; export * from './index.bundle.base'; diff --git a/packages/browser/src/index.bundle.ts b/packages/browser/src/index.bundle.ts index a2541ceadda1..f479757db105 100644 --- a/packages/browser/src/index.bundle.ts +++ b/packages/browser/src/index.bundle.ts @@ -1,5 +1,11 @@ // This is exported so the loader does not fail when switching off Replay/Tracing -import { BrowserTracing, Feedback, Replay, addTracingExtensions } from '@sentry-internal/integration-shims'; +import { + BrowserTracing, + Feedback, + Replay, + ReplayCanvas, + addTracingExtensions, +} from '@sentry-internal/integration-shims'; import * as Sentry from './index.bundle.base'; @@ -9,5 +15,5 @@ Sentry.Integrations.Replay = Replay; Sentry.Integrations.BrowserTracing = BrowserTracing; export * from './index.bundle.base'; -export { BrowserTracing, addTracingExtensions, Replay, Feedback }; +export { BrowserTracing, addTracingExtensions, Replay, ReplayCanvas, Feedback }; // Note: We do not export a shim for `Span` here, as that is quite complex and would blow up the bundle diff --git a/packages/browser/test/unit/index.bundle.feedback.test.ts b/packages/browser/test/unit/index.bundle.feedback.test.ts index 4c5f26e5f313..03bb93047a5c 100644 --- a/packages/browser/test/unit/index.bundle.feedback.test.ts +++ b/packages/browser/test/unit/index.bundle.feedback.test.ts @@ -1,4 +1,8 @@ -import { BrowserTracing as BrowserTracingShim, Replay as ReplayShim } from '@sentry-internal/integration-shims'; +import { + BrowserTracing as BrowserTracingShim, + Replay as ReplayShim, + ReplayCanvas as ReplayCanvasShim, +} from '@sentry-internal/integration-shims'; import { Feedback } from '@sentry/browser'; import * as TracingReplayBundle from '../../src/index.bundle.feedback'; @@ -16,6 +20,7 @@ describe('index.bundle.feedback', () => { expect(TracingReplayBundle.Integrations.Replay).toBe(ReplayShim); expect(TracingReplayBundle.Replay).toBe(ReplayShim); + expect(TracingReplayBundle.ReplayCanvas).toBe(ReplayCanvasShim); expect(TracingReplayBundle.Integrations.BrowserTracing).toBe(BrowserTracingShim); expect(TracingReplayBundle.BrowserTracing).toBe(BrowserTracingShim); diff --git a/packages/browser/test/unit/index.bundle.replay.test.ts b/packages/browser/test/unit/index.bundle.replay.test.ts index b14eb70f6330..30eb69ee27cd 100644 --- a/packages/browser/test/unit/index.bundle.replay.test.ts +++ b/packages/browser/test/unit/index.bundle.replay.test.ts @@ -1,5 +1,6 @@ import { BrowserTracing as BrowserTracingShim, Feedback as FeedbackShim } from '@sentry-internal/integration-shims'; import { Replay } from '@sentry/browser'; +import { ReplayCanvas } from '@sentry/browser'; import * as TracingReplayBundle from '../../src/index.bundle.replay'; @@ -16,6 +17,7 @@ describe('index.bundle.replay', () => { expect(TracingReplayBundle.Integrations.Replay).toBe(Replay); expect(TracingReplayBundle.Replay).toBe(Replay); + expect(TracingReplayBundle.ReplayCanvas).toBe(ReplayCanvas); expect(TracingReplayBundle.Integrations.BrowserTracing).toBe(BrowserTracingShim); expect(TracingReplayBundle.BrowserTracing).toBe(BrowserTracingShim); diff --git a/packages/browser/test/unit/index.bundle.test.ts b/packages/browser/test/unit/index.bundle.test.ts index f61987732858..08cbd86145a4 100644 --- a/packages/browser/test/unit/index.bundle.test.ts +++ b/packages/browser/test/unit/index.bundle.test.ts @@ -2,6 +2,7 @@ import { BrowserTracing as BrowserTracingShim, Feedback as FeedbackShim, Replay as ReplayShim, + ReplayCanvas as ReplayCanvasShim, } from '@sentry-internal/integration-shims'; import * as TracingBundle from '../../src/index.bundle'; @@ -19,6 +20,7 @@ describe('index.bundle', () => { expect(TracingBundle.Integrations.Replay).toBe(ReplayShim); expect(TracingBundle.Replay).toBe(ReplayShim); + expect(TracingBundle.ReplayCanvas).toBe(ReplayCanvasShim); expect(TracingBundle.Integrations.BrowserTracing).toBe(BrowserTracingShim); expect(TracingBundle.BrowserTracing).toBe(BrowserTracingShim); diff --git a/packages/browser/test/unit/index.bundle.tracing.replay.feedback.test.ts b/packages/browser/test/unit/index.bundle.tracing.replay.feedback.test.ts index 0bd50453da43..9f9076374b1e 100644 --- a/packages/browser/test/unit/index.bundle.tracing.replay.feedback.test.ts +++ b/packages/browser/test/unit/index.bundle.tracing.replay.feedback.test.ts @@ -1,5 +1,5 @@ import { BrowserTracing } from '@sentry-internal/tracing'; -import { Feedback, Replay } from '@sentry/browser'; +import { Feedback, Replay, ReplayCanvas } from '@sentry/browser'; import * as TracingReplayFeedbackBundle from '../../src/index.bundle.tracing.replay.feedback'; @@ -16,6 +16,7 @@ describe('index.bundle.tracing.replay.feedback', () => { expect(TracingReplayFeedbackBundle.Integrations.Replay).toBe(Replay); expect(TracingReplayFeedbackBundle.Replay).toBe(Replay); + expect(TracingReplayFeedbackBundle.ReplayCanvas).toBe(ReplayCanvas); expect(TracingReplayFeedbackBundle.Integrations.BrowserTracing).toBe(BrowserTracing); expect(TracingReplayFeedbackBundle.BrowserTracing).toBe(BrowserTracing); diff --git a/packages/browser/test/unit/index.bundle.tracing.replay.test.ts b/packages/browser/test/unit/index.bundle.tracing.replay.test.ts index 4c8af2130a3b..c016367f2bf6 100644 --- a/packages/browser/test/unit/index.bundle.tracing.replay.test.ts +++ b/packages/browser/test/unit/index.bundle.tracing.replay.test.ts @@ -1,6 +1,7 @@ import { Feedback as FeedbackShim } from '@sentry-internal/integration-shims'; import { BrowserTracing } from '@sentry-internal/tracing'; import { Replay } from '@sentry/browser'; +import { ReplayCanvas } from '@sentry/browser'; import * as TracingReplayBundle from '../../src/index.bundle.tracing.replay'; @@ -17,6 +18,7 @@ describe('index.bundle.tracing.replay', () => { expect(TracingReplayBundle.Integrations.Replay).toBe(Replay); expect(TracingReplayBundle.Replay).toBe(Replay); + expect(TracingReplayBundle.ReplayCanvas).toBe(ReplayCanvas); expect(TracingReplayBundle.Integrations.BrowserTracing).toBe(BrowserTracing); expect(TracingReplayBundle.BrowserTracing).toBe(BrowserTracing); diff --git a/packages/browser/test/unit/index.bundle.tracing.test.ts b/packages/browser/test/unit/index.bundle.tracing.test.ts index b439dece8c6f..bce177ce0d72 100644 --- a/packages/browser/test/unit/index.bundle.tracing.test.ts +++ b/packages/browser/test/unit/index.bundle.tracing.test.ts @@ -1,4 +1,8 @@ -import { Feedback as FeedbackShim, Replay as ReplayShim } from '@sentry-internal/integration-shims'; +import { + Feedback as FeedbackShim, + Replay as ReplayShim, + ReplayCanvas as ReplayCanvasShim, +} from '@sentry-internal/integration-shims'; import { BrowserTracing } from '@sentry-internal/tracing'; import * as TracingBundle from '../../src/index.bundle.tracing'; @@ -16,6 +20,7 @@ describe('index.bundle.tracing', () => { expect(TracingBundle.Integrations.Replay).toBe(ReplayShim); expect(TracingBundle.Replay).toBe(ReplayShim); + expect(TracingBundle.ReplayCanvas).toBe(ReplayCanvasShim); expect(TracingBundle.Integrations.BrowserTracing).toBe(BrowserTracing); expect(TracingBundle.BrowserTracing).toBe(BrowserTracing); diff --git a/packages/integration-shims/src/index.ts b/packages/integration-shims/src/index.ts index 410a4a31d9c8..31958781ac44 100644 --- a/packages/integration-shims/src/index.ts +++ b/packages/integration-shims/src/index.ts @@ -1,3 +1,4 @@ export { Feedback } from './Feedback'; export { Replay } from './Replay'; +export { ReplayCanvas } from './ReplayCanvas'; export { BrowserTracing, addTracingExtensions } from './BrowserTracing'; From 498dfd1fbbc94128271e87d8d3bf73544ad0a286 Mon Sep 17 00:00:00 2001 From: Billy Vong Date: Wed, 10 Jan 2024 18:03:25 -0500 Subject: [PATCH 29/50] forgot shims --- .../integration-shims/src/ReplayCanvas.ts | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 packages/integration-shims/src/ReplayCanvas.ts diff --git a/packages/integration-shims/src/ReplayCanvas.ts b/packages/integration-shims/src/ReplayCanvas.ts new file mode 100644 index 000000000000..1bff93359342 --- /dev/null +++ b/packages/integration-shims/src/ReplayCanvas.ts @@ -0,0 +1,41 @@ +import type { Integration } from '@sentry/types'; +import { consoleSandbox } from '@sentry/utils'; + +/** + * This is a shim for the Replay integration. + * It is needed in order for the CDN bundles to continue working when users add/remove replay + * from it, without changing their config. This is necessary for the loader mechanism. + */ +class ReplayCanvasShim implements Integration { + /** + * @inheritDoc + */ + public static id: string = 'ReplayCanvas'; + + /** + * @inheritDoc + */ + public name: string; + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + public constructor(_options: any) { + this.name = ReplayCanvasShim.id; + + consoleSandbox(() => { + // eslint-disable-next-line no-console + console.error('You are using new ReplayCanvas() even though this bundle does not include replay canvas.'); + }); + } + + /** jsdoc */ + public setupOnce(): void { + // noop + } + + /** jsdoc */ + public getOptions(): void { + // noop + } +} + +export { ReplayCanvasShim as ReplayCanvas }; From 138c5819dc7108cbe5619c38b94539865bccf255 Mon Sep 17 00:00:00 2001 From: Billy Vong Date: Wed, 10 Jan 2024 18:53:42 -0500 Subject: [PATCH 30/50] debug playwright test --- .../suites/replay/canvas/withCanvasIntegrationFirst/test.ts | 3 +++ .../suites/replay/canvas/withCanvasIntegrationSecond/test.ts | 3 +++ packages/replay-canvas/src/canvas.ts | 1 + packages/replay/src/integration.ts | 3 +++ 4 files changed, 10 insertions(+) diff --git a/dev-packages/browser-integration-tests/suites/replay/canvas/withCanvasIntegrationFirst/test.ts b/dev-packages/browser-integration-tests/suites/replay/canvas/withCanvasIntegrationFirst/test.ts index 4caeea4ba0e5..4f8a07d7f6aa 100644 --- a/dev-packages/browser-integration-tests/suites/replay/canvas/withCanvasIntegrationFirst/test.ts +++ b/dev-packages/browser-integration-tests/suites/replay/canvas/withCanvasIntegrationFirst/test.ts @@ -20,6 +20,9 @@ sentryTest('sets up canvas when adding ReplayCanvas integration first', async ({ await page.goto(url); + page.on('console', (m) => { + console.log(m); + }); const replay = await getReplaySnapshot(page); const canvasOptions = replay._canvas; expect(canvasOptions?.sampling.canvas).toBe(2); diff --git a/dev-packages/browser-integration-tests/suites/replay/canvas/withCanvasIntegrationSecond/test.ts b/dev-packages/browser-integration-tests/suites/replay/canvas/withCanvasIntegrationSecond/test.ts index 61367935ba22..0d025eb59cd9 100644 --- a/dev-packages/browser-integration-tests/suites/replay/canvas/withCanvasIntegrationSecond/test.ts +++ b/dev-packages/browser-integration-tests/suites/replay/canvas/withCanvasIntegrationSecond/test.ts @@ -20,6 +20,9 @@ sentryTest('sets up canvas when adding ReplayCanvas integration after Replay', a await page.goto(url); + page.on('console', (m) => { + console.log(m); + }); const replay = await getReplaySnapshot(page); const canvasOptions = replay._canvas; expect(canvasOptions?.sampling.canvas).toBe(2); diff --git a/packages/replay-canvas/src/canvas.ts b/packages/replay-canvas/src/canvas.ts index ee8b6f3a7c3c..ec71b1b01a05 100644 --- a/packages/replay-canvas/src/canvas.ts +++ b/packages/replay-canvas/src/canvas.ts @@ -66,6 +66,7 @@ const replayCanvasIntegration = ((options: Partial = {}) => getOptions(): ReplayCanvasIntegrationOptions { const { quality } = _canvasOptions; + console.log('getoptions'); return { recordCanvas: true, getCanvasManager: (options: ConstructorParameters[0]) => new CanvasManager(options), diff --git a/packages/replay/src/integration.ts b/packages/replay/src/integration.ts index c73da03c1b85..56f928cfb011 100644 --- a/packages/replay/src/integration.ts +++ b/packages/replay/src/integration.ts @@ -357,6 +357,7 @@ Sentry.init({ replaysOnErrorSampleRate: ${errorSampleRate} })`, private _maybeLoadFromReplayCanvasIntegration(): void { // To save bundle size, we skip checking for stuff here // and instead just try-catch everything - as generally this should all be defined + console.log("maybeload") /* eslint-disable @typescript-eslint/no-non-null-assertion */ try { const client = getClient()!; @@ -364,10 +365,12 @@ Sentry.init({ replaysOnErrorSampleRate: ${errorSampleRate} })`, getOptions(): ReplayCanvasIntegrationOptions; }; if (!canvasIntegration) { + console.log('no canvas integration'); return; } this._replay!['_canvas'] = canvasIntegration.getOptions(); + console.log(this._replay!['_canvas']) } catch { // ignore errors here } From ea6c74e154bed4d3c2fe3662d418b417b1812b15 Mon Sep 17 00:00:00 2001 From: Billy Vong Date: Wed, 10 Jan 2024 22:52:54 -0500 Subject: [PATCH 31/50] Revert "debug playwright test" This reverts commit 7cf7b73a66bcd0a6ff485243e8662fe5344637e9. --- .../suites/replay/canvas/withCanvasIntegrationFirst/test.ts | 3 --- .../suites/replay/canvas/withCanvasIntegrationSecond/test.ts | 3 --- packages/replay-canvas/src/canvas.ts | 1 - packages/replay/src/integration.ts | 3 --- 4 files changed, 10 deletions(-) diff --git a/dev-packages/browser-integration-tests/suites/replay/canvas/withCanvasIntegrationFirst/test.ts b/dev-packages/browser-integration-tests/suites/replay/canvas/withCanvasIntegrationFirst/test.ts index 4f8a07d7f6aa..4caeea4ba0e5 100644 --- a/dev-packages/browser-integration-tests/suites/replay/canvas/withCanvasIntegrationFirst/test.ts +++ b/dev-packages/browser-integration-tests/suites/replay/canvas/withCanvasIntegrationFirst/test.ts @@ -20,9 +20,6 @@ sentryTest('sets up canvas when adding ReplayCanvas integration first', async ({ await page.goto(url); - page.on('console', (m) => { - console.log(m); - }); const replay = await getReplaySnapshot(page); const canvasOptions = replay._canvas; expect(canvasOptions?.sampling.canvas).toBe(2); diff --git a/dev-packages/browser-integration-tests/suites/replay/canvas/withCanvasIntegrationSecond/test.ts b/dev-packages/browser-integration-tests/suites/replay/canvas/withCanvasIntegrationSecond/test.ts index 0d025eb59cd9..61367935ba22 100644 --- a/dev-packages/browser-integration-tests/suites/replay/canvas/withCanvasIntegrationSecond/test.ts +++ b/dev-packages/browser-integration-tests/suites/replay/canvas/withCanvasIntegrationSecond/test.ts @@ -20,9 +20,6 @@ sentryTest('sets up canvas when adding ReplayCanvas integration after Replay', a await page.goto(url); - page.on('console', (m) => { - console.log(m); - }); const replay = await getReplaySnapshot(page); const canvasOptions = replay._canvas; expect(canvasOptions?.sampling.canvas).toBe(2); diff --git a/packages/replay-canvas/src/canvas.ts b/packages/replay-canvas/src/canvas.ts index ec71b1b01a05..ee8b6f3a7c3c 100644 --- a/packages/replay-canvas/src/canvas.ts +++ b/packages/replay-canvas/src/canvas.ts @@ -66,7 +66,6 @@ const replayCanvasIntegration = ((options: Partial = {}) => getOptions(): ReplayCanvasIntegrationOptions { const { quality } = _canvasOptions; - console.log('getoptions'); return { recordCanvas: true, getCanvasManager: (options: ConstructorParameters[0]) => new CanvasManager(options), diff --git a/packages/replay/src/integration.ts b/packages/replay/src/integration.ts index 56f928cfb011..c73da03c1b85 100644 --- a/packages/replay/src/integration.ts +++ b/packages/replay/src/integration.ts @@ -357,7 +357,6 @@ Sentry.init({ replaysOnErrorSampleRate: ${errorSampleRate} })`, private _maybeLoadFromReplayCanvasIntegration(): void { // To save bundle size, we skip checking for stuff here // and instead just try-catch everything - as generally this should all be defined - console.log("maybeload") /* eslint-disable @typescript-eslint/no-non-null-assertion */ try { const client = getClient()!; @@ -365,12 +364,10 @@ Sentry.init({ replaysOnErrorSampleRate: ${errorSampleRate} })`, getOptions(): ReplayCanvasIntegrationOptions; }; if (!canvasIntegration) { - console.log('no canvas integration'); return; } this._replay!['_canvas'] = canvasIntegration.getOptions(); - console.log(this._replay!['_canvas']) } catch { // ignore errors here } From 62ddb76383b89cc85df73b275468c35a7e320a0e Mon Sep 17 00:00:00 2001 From: Billy Vong Date: Thu, 11 Jan 2024 10:31:59 -0500 Subject: [PATCH 32/50] dont mangle _canvas --- dev-packages/rollup-utils/plugins/bundlePlugins.mjs | 1 + 1 file changed, 1 insertion(+) diff --git a/dev-packages/rollup-utils/plugins/bundlePlugins.mjs b/dev-packages/rollup-utils/plugins/bundlePlugins.mjs index 9aef97dc828f..b0a1c806ef98 100644 --- a/dev-packages/rollup-utils/plugins/bundlePlugins.mjs +++ b/dev-packages/rollup-utils/plugins/bundlePlugins.mjs @@ -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 From 46d3859332dc1ec78105f70c422bf01e3fc6cf31 Mon Sep 17 00:00:00 2001 From: Billy Vong Date: Thu, 11 Jan 2024 11:59:59 -0500 Subject: [PATCH 33/50] fix types --- packages/replay-canvas/src/canvas.ts | 9 +++++---- packages/replay/src/index.ts | 1 + packages/replay/src/types/rrweb.ts | 3 +++ 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/packages/replay-canvas/src/canvas.ts b/packages/replay-canvas/src/canvas.ts index ee8b6f3a7c3c..5dcf4a36f4af 100644 --- a/packages/replay-canvas/src/canvas.ts +++ b/packages/replay-canvas/src/canvas.ts @@ -1,15 +1,16 @@ -import { CanvasManager } from '@sentry-internal/rrweb'; import { convertIntegrationFnToClass } from '@sentry/core'; -import type { CanvasManagerInterface } from '@sentry/replay'; +import { CanvasManager } from '@sentry-internal/rrweb'; +import type { CanvasManagerInterface, CanvasManagerOptions } from '@sentry/replay'; import type { IntegrationFn } from '@sentry/types'; interface ReplayCanvasOptions { quality: 'low' | 'medium' | 'high'; } +type GetCanvasManager = (options: CanvasManagerOptions) => CanvasManagerInterface; export interface ReplayCanvasIntegrationOptions { recordCanvas: true; - getCanvasManager: (options: ConstructorParameters[0]) => CanvasManagerInterface; + getCanvasManager: GetCanvasManager; sampling: { canvas: number; }; @@ -68,7 +69,7 @@ const replayCanvasIntegration = ((options: Partial = {}) => return { recordCanvas: true, - getCanvasManager: (options: ConstructorParameters[0]) => new CanvasManager(options), + getCanvasManager: (options: CanvasManagerOptions) => new CanvasManager(options), ...(CANVAS_QUALITY[quality || 'medium'] || CANVAS_QUALITY.medium), }; }, diff --git a/packages/replay/src/index.ts b/packages/replay/src/index.ts index b2d4d575d019..8e8a5d55579a 100644 --- a/packages/replay/src/index.ts +++ b/packages/replay/src/index.ts @@ -12,6 +12,7 @@ export type { ReplaySpanFrame, ReplaySpanFrameEvent, CanvasManagerInterface, + CanvasManagerOptions, } from './types'; // TODO (v8): Remove deprecated types diff --git a/packages/replay/src/types/rrweb.ts b/packages/replay/src/types/rrweb.ts index 185c8be12611..7cbea7af07eb 100644 --- a/packages/replay/src/types/rrweb.ts +++ b/packages/replay/src/types/rrweb.ts @@ -64,4 +64,7 @@ export interface CanvasManagerOptions { type: string; quality: number; }>; + mutationCb: (p: any) => void; + win: typeof globalThis & Window; + mirror: any; } From 3160c19f6b5111a816c1f4271797764e2daf3e08 Mon Sep 17 00:00:00 2001 From: Billy Vong Date: Thu, 11 Jan 2024 12:24:32 -0500 Subject: [PATCH 34/50] lint --- packages/replay-canvas/src/canvas.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/replay-canvas/src/canvas.ts b/packages/replay-canvas/src/canvas.ts index 5dcf4a36f4af..e08131f96a78 100644 --- a/packages/replay-canvas/src/canvas.ts +++ b/packages/replay-canvas/src/canvas.ts @@ -1,5 +1,5 @@ -import { convertIntegrationFnToClass } from '@sentry/core'; import { CanvasManager } from '@sentry-internal/rrweb'; +import { convertIntegrationFnToClass } from '@sentry/core'; import type { CanvasManagerInterface, CanvasManagerOptions } from '@sentry/replay'; import type { IntegrationFn } from '@sentry/types'; From a316a103b99650451bdad2f3f19f1ec71ee7478c Mon Sep 17 00:00:00 2001 From: Billy Vong Date: Thu, 11 Jan 2024 15:34:21 -0500 Subject: [PATCH 35/50] wait for replay request --- .../suites/replay/canvas/withCanvasIntegrationFirst/test.ts | 5 ++++- .../suites/replay/canvas/withCanvasIntegrationSecond/test.ts | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/dev-packages/browser-integration-tests/suites/replay/canvas/withCanvasIntegrationFirst/test.ts b/dev-packages/browser-integration-tests/suites/replay/canvas/withCanvasIntegrationFirst/test.ts index 4caeea4ba0e5..ae45b5824cb5 100644 --- a/dev-packages/browser-integration-tests/suites/replay/canvas/withCanvasIntegrationFirst/test.ts +++ b/dev-packages/browser-integration-tests/suites/replay/canvas/withCanvasIntegrationFirst/test.ts @@ -1,13 +1,15 @@ import { expect } from '@playwright/test'; import { sentryTest } from '../../../../utils/fixtures'; -import { getReplaySnapshot, shouldSkipReplayTest } from '../../../../utils/replayHelpers'; +import { getReplaySnapshot, shouldSkipReplayTest, waitForReplayRequest } from '../../../../utils/replayHelpers'; sentryTest('sets up canvas when adding ReplayCanvas integration first', async ({ getLocalTestUrl, page }) => { if (shouldSkipReplayTest()) { sentryTest.skip(); } + const reqPromise0 = waitForReplayRequest(page, 0); + await page.route('https://dsn.ingest.sentry.io/**/*', route => { return route.fulfill({ status: 200, @@ -19,6 +21,7 @@ sentryTest('sets up canvas when adding ReplayCanvas integration first', async ({ const url = await getLocalTestUrl({ testDir: __dirname }); await page.goto(url); + await reqPromise0; const replay = await getReplaySnapshot(page); const canvasOptions = replay._canvas; diff --git a/dev-packages/browser-integration-tests/suites/replay/canvas/withCanvasIntegrationSecond/test.ts b/dev-packages/browser-integration-tests/suites/replay/canvas/withCanvasIntegrationSecond/test.ts index 61367935ba22..674f94f86cab 100644 --- a/dev-packages/browser-integration-tests/suites/replay/canvas/withCanvasIntegrationSecond/test.ts +++ b/dev-packages/browser-integration-tests/suites/replay/canvas/withCanvasIntegrationSecond/test.ts @@ -1,13 +1,15 @@ import { expect } from '@playwright/test'; import { sentryTest } from '../../../../utils/fixtures'; -import { getReplaySnapshot, shouldSkipReplayTest } from '../../../../utils/replayHelpers'; +import { getReplaySnapshot, shouldSkipReplayTest, waitForReplayRequest } from '../../../../utils/replayHelpers'; sentryTest('sets up canvas when adding ReplayCanvas integration after Replay', async ({ getLocalTestUrl, page }) => { if (shouldSkipReplayTest()) { sentryTest.skip(); } + const reqPromise0 = waitForReplayRequest(page, 0); + await page.route('https://dsn.ingest.sentry.io/**/*', route => { return route.fulfill({ status: 200, @@ -19,6 +21,7 @@ sentryTest('sets up canvas when adding ReplayCanvas integration after Replay', a const url = await getLocalTestUrl({ testDir: __dirname }); await page.goto(url); + await reqPromise0; const replay = await getReplaySnapshot(page); const canvasOptions = replay._canvas; From 7c29642dc63ab70a1e61175a5c0169dd6924f0f7 Mon Sep 17 00:00:00 2001 From: Billy Vong Date: Mon, 15 Jan 2024 14:54:39 -0500 Subject: [PATCH 36/50] Revert "add replaycanvas to replay bundles" This reverts commit ab2d746f1365532695c53fda4263d48351752a8f. --- packages/browser/src/index.bundle.feedback.ts | 4 ++-- packages/browser/src/index.bundle.replay.ts | 3 +-- .../src/index.bundle.tracing.replay.feedback.ts | 3 +-- packages/browser/src/index.bundle.tracing.replay.ts | 3 +-- packages/browser/src/index.bundle.tracing.ts | 4 ++-- packages/browser/src/index.bundle.ts | 10 ++-------- .../browser/test/unit/index.bundle.feedback.test.ts | 7 +------ packages/browser/test/unit/index.bundle.replay.test.ts | 2 -- packages/browser/test/unit/index.bundle.test.ts | 2 -- .../unit/index.bundle.tracing.replay.feedback.test.ts | 3 +-- .../test/unit/index.bundle.tracing.replay.test.ts | 2 -- .../browser/test/unit/index.bundle.tracing.test.ts | 7 +------ packages/integration-shims/src/index.ts | 1 - 13 files changed, 12 insertions(+), 39 deletions(-) diff --git a/packages/browser/src/index.bundle.feedback.ts b/packages/browser/src/index.bundle.feedback.ts index 48d7b4aecf13..7aab6485d254 100644 --- a/packages/browser/src/index.bundle.feedback.ts +++ b/packages/browser/src/index.bundle.feedback.ts @@ -1,6 +1,6 @@ // This is exported so the loader does not fail when switching off Replay/Tracing import { Feedback } from '@sentry-internal/feedback'; -import { BrowserTracing, Replay, ReplayCanvas, addTracingExtensions } from '@sentry-internal/integration-shims'; +import { BrowserTracing, Replay, addTracingExtensions } from '@sentry-internal/integration-shims'; import * as Sentry from './index.bundle.base'; @@ -10,5 +10,5 @@ Sentry.Integrations.Replay = Replay; Sentry.Integrations.BrowserTracing = BrowserTracing; export * from './index.bundle.base'; -export { BrowserTracing, addTracingExtensions, Replay, ReplayCanvas, Feedback }; +export { BrowserTracing, addTracingExtensions, Replay, Feedback }; // Note: We do not export a shim for `Span` here, as that is quite complex and would blow up the bundle diff --git a/packages/browser/src/index.bundle.replay.ts b/packages/browser/src/index.bundle.replay.ts index e1fe19cda06c..0b556bc71b8e 100644 --- a/packages/browser/src/index.bundle.replay.ts +++ b/packages/browser/src/index.bundle.replay.ts @@ -1,6 +1,5 @@ // This is exported so the loader does not fail when switching off Replay/Tracing import { BrowserTracing, Feedback, addTracingExtensions } from '@sentry-internal/integration-shims'; -import { ReplayCanvas } from '@sentry-internal/replay-canvas'; import { Replay } from '@sentry/replay'; import * as Sentry from './index.bundle.base'; @@ -11,5 +10,5 @@ Sentry.Integrations.Replay = Replay; Sentry.Integrations.BrowserTracing = BrowserTracing; export * from './index.bundle.base'; -export { BrowserTracing, addTracingExtensions, Replay, ReplayCanvas, Feedback }; +export { BrowserTracing, addTracingExtensions, Replay, Feedback }; // Note: We do not export a shim for `Span` here, as that is quite complex and would blow up the bundle diff --git a/packages/browser/src/index.bundle.tracing.replay.feedback.ts b/packages/browser/src/index.bundle.tracing.replay.feedback.ts index 04dc02a8e946..5558703f7703 100644 --- a/packages/browser/src/index.bundle.tracing.replay.feedback.ts +++ b/packages/browser/src/index.bundle.tracing.replay.feedback.ts @@ -1,5 +1,4 @@ import { Feedback } from '@sentry-internal/feedback'; -import { ReplayCanvas } from '@sentry-internal/replay-canvas'; import { BrowserTracing, Span, addExtensionMethods } from '@sentry-internal/tracing'; import { Replay } from '@sentry/replay'; @@ -15,5 +14,5 @@ Sentry.Integrations.BrowserTracing = BrowserTracing; // We are patching the global object with our hub extension methods addExtensionMethods(); -export { Feedback, Replay, ReplayCanvas, BrowserTracing, Span, addExtensionMethods }; +export { Feedback, Replay, BrowserTracing, Span, addExtensionMethods }; export * from './index.bundle.base'; diff --git a/packages/browser/src/index.bundle.tracing.replay.ts b/packages/browser/src/index.bundle.tracing.replay.ts index 683d1509f9d8..0e7aa1ec19bc 100644 --- a/packages/browser/src/index.bundle.tracing.replay.ts +++ b/packages/browser/src/index.bundle.tracing.replay.ts @@ -1,5 +1,4 @@ import { Feedback } from '@sentry-internal/integration-shims'; -import { ReplayCanvas } from '@sentry-internal/replay-canvas'; import { BrowserTracing, Span, addExtensionMethods } from '@sentry-internal/tracing'; import { Replay } from '@sentry/replay'; @@ -15,5 +14,5 @@ Sentry.Integrations.BrowserTracing = BrowserTracing; // We are patching the global object with our hub extension methods addExtensionMethods(); -export { Feedback, Replay, ReplayCanvas, BrowserTracing, Span, addExtensionMethods }; +export { Feedback, Replay, BrowserTracing, Span, addExtensionMethods }; export * from './index.bundle.base'; diff --git a/packages/browser/src/index.bundle.tracing.ts b/packages/browser/src/index.bundle.tracing.ts index 0a958c634974..22b33962e860 100644 --- a/packages/browser/src/index.bundle.tracing.ts +++ b/packages/browser/src/index.bundle.tracing.ts @@ -1,5 +1,5 @@ // This is exported so the loader does not fail when switching off Replay -import { Feedback, Replay, ReplayCanvas } from '@sentry-internal/integration-shims'; +import { Feedback, Replay } from '@sentry-internal/integration-shims'; import { BrowserTracing, Span, addExtensionMethods } from '@sentry-internal/tracing'; import * as Sentry from './index.bundle.base'; @@ -14,5 +14,5 @@ Sentry.Integrations.BrowserTracing = BrowserTracing; // We are patching the global object with our hub extension methods addExtensionMethods(); -export { Feedback, Replay, ReplayCanvas, BrowserTracing, Span, addExtensionMethods }; +export { Feedback, Replay, BrowserTracing, Span, addExtensionMethods }; export * from './index.bundle.base'; diff --git a/packages/browser/src/index.bundle.ts b/packages/browser/src/index.bundle.ts index f479757db105..a2541ceadda1 100644 --- a/packages/browser/src/index.bundle.ts +++ b/packages/browser/src/index.bundle.ts @@ -1,11 +1,5 @@ // This is exported so the loader does not fail when switching off Replay/Tracing -import { - BrowserTracing, - Feedback, - Replay, - ReplayCanvas, - addTracingExtensions, -} from '@sentry-internal/integration-shims'; +import { BrowserTracing, Feedback, Replay, addTracingExtensions } from '@sentry-internal/integration-shims'; import * as Sentry from './index.bundle.base'; @@ -15,5 +9,5 @@ Sentry.Integrations.Replay = Replay; Sentry.Integrations.BrowserTracing = BrowserTracing; export * from './index.bundle.base'; -export { BrowserTracing, addTracingExtensions, Replay, ReplayCanvas, Feedback }; +export { BrowserTracing, addTracingExtensions, Replay, Feedback }; // Note: We do not export a shim for `Span` here, as that is quite complex and would blow up the bundle diff --git a/packages/browser/test/unit/index.bundle.feedback.test.ts b/packages/browser/test/unit/index.bundle.feedback.test.ts index 03bb93047a5c..4c5f26e5f313 100644 --- a/packages/browser/test/unit/index.bundle.feedback.test.ts +++ b/packages/browser/test/unit/index.bundle.feedback.test.ts @@ -1,8 +1,4 @@ -import { - BrowserTracing as BrowserTracingShim, - Replay as ReplayShim, - ReplayCanvas as ReplayCanvasShim, -} from '@sentry-internal/integration-shims'; +import { BrowserTracing as BrowserTracingShim, Replay as ReplayShim } from '@sentry-internal/integration-shims'; import { Feedback } from '@sentry/browser'; import * as TracingReplayBundle from '../../src/index.bundle.feedback'; @@ -20,7 +16,6 @@ describe('index.bundle.feedback', () => { expect(TracingReplayBundle.Integrations.Replay).toBe(ReplayShim); expect(TracingReplayBundle.Replay).toBe(ReplayShim); - expect(TracingReplayBundle.ReplayCanvas).toBe(ReplayCanvasShim); expect(TracingReplayBundle.Integrations.BrowserTracing).toBe(BrowserTracingShim); expect(TracingReplayBundle.BrowserTracing).toBe(BrowserTracingShim); diff --git a/packages/browser/test/unit/index.bundle.replay.test.ts b/packages/browser/test/unit/index.bundle.replay.test.ts index 30eb69ee27cd..b14eb70f6330 100644 --- a/packages/browser/test/unit/index.bundle.replay.test.ts +++ b/packages/browser/test/unit/index.bundle.replay.test.ts @@ -1,6 +1,5 @@ import { BrowserTracing as BrowserTracingShim, Feedback as FeedbackShim } from '@sentry-internal/integration-shims'; import { Replay } from '@sentry/browser'; -import { ReplayCanvas } from '@sentry/browser'; import * as TracingReplayBundle from '../../src/index.bundle.replay'; @@ -17,7 +16,6 @@ describe('index.bundle.replay', () => { expect(TracingReplayBundle.Integrations.Replay).toBe(Replay); expect(TracingReplayBundle.Replay).toBe(Replay); - expect(TracingReplayBundle.ReplayCanvas).toBe(ReplayCanvas); expect(TracingReplayBundle.Integrations.BrowserTracing).toBe(BrowserTracingShim); expect(TracingReplayBundle.BrowserTracing).toBe(BrowserTracingShim); diff --git a/packages/browser/test/unit/index.bundle.test.ts b/packages/browser/test/unit/index.bundle.test.ts index 08cbd86145a4..f61987732858 100644 --- a/packages/browser/test/unit/index.bundle.test.ts +++ b/packages/browser/test/unit/index.bundle.test.ts @@ -2,7 +2,6 @@ import { BrowserTracing as BrowserTracingShim, Feedback as FeedbackShim, Replay as ReplayShim, - ReplayCanvas as ReplayCanvasShim, } from '@sentry-internal/integration-shims'; import * as TracingBundle from '../../src/index.bundle'; @@ -20,7 +19,6 @@ describe('index.bundle', () => { expect(TracingBundle.Integrations.Replay).toBe(ReplayShim); expect(TracingBundle.Replay).toBe(ReplayShim); - expect(TracingBundle.ReplayCanvas).toBe(ReplayCanvasShim); expect(TracingBundle.Integrations.BrowserTracing).toBe(BrowserTracingShim); expect(TracingBundle.BrowserTracing).toBe(BrowserTracingShim); diff --git a/packages/browser/test/unit/index.bundle.tracing.replay.feedback.test.ts b/packages/browser/test/unit/index.bundle.tracing.replay.feedback.test.ts index 9f9076374b1e..0bd50453da43 100644 --- a/packages/browser/test/unit/index.bundle.tracing.replay.feedback.test.ts +++ b/packages/browser/test/unit/index.bundle.tracing.replay.feedback.test.ts @@ -1,5 +1,5 @@ import { BrowserTracing } from '@sentry-internal/tracing'; -import { Feedback, Replay, ReplayCanvas } from '@sentry/browser'; +import { Feedback, Replay } from '@sentry/browser'; import * as TracingReplayFeedbackBundle from '../../src/index.bundle.tracing.replay.feedback'; @@ -16,7 +16,6 @@ describe('index.bundle.tracing.replay.feedback', () => { expect(TracingReplayFeedbackBundle.Integrations.Replay).toBe(Replay); expect(TracingReplayFeedbackBundle.Replay).toBe(Replay); - expect(TracingReplayFeedbackBundle.ReplayCanvas).toBe(ReplayCanvas); expect(TracingReplayFeedbackBundle.Integrations.BrowserTracing).toBe(BrowserTracing); expect(TracingReplayFeedbackBundle.BrowserTracing).toBe(BrowserTracing); diff --git a/packages/browser/test/unit/index.bundle.tracing.replay.test.ts b/packages/browser/test/unit/index.bundle.tracing.replay.test.ts index c016367f2bf6..4c8af2130a3b 100644 --- a/packages/browser/test/unit/index.bundle.tracing.replay.test.ts +++ b/packages/browser/test/unit/index.bundle.tracing.replay.test.ts @@ -1,7 +1,6 @@ import { Feedback as FeedbackShim } from '@sentry-internal/integration-shims'; import { BrowserTracing } from '@sentry-internal/tracing'; import { Replay } from '@sentry/browser'; -import { ReplayCanvas } from '@sentry/browser'; import * as TracingReplayBundle from '../../src/index.bundle.tracing.replay'; @@ -18,7 +17,6 @@ describe('index.bundle.tracing.replay', () => { expect(TracingReplayBundle.Integrations.Replay).toBe(Replay); expect(TracingReplayBundle.Replay).toBe(Replay); - expect(TracingReplayBundle.ReplayCanvas).toBe(ReplayCanvas); expect(TracingReplayBundle.Integrations.BrowserTracing).toBe(BrowserTracing); expect(TracingReplayBundle.BrowserTracing).toBe(BrowserTracing); diff --git a/packages/browser/test/unit/index.bundle.tracing.test.ts b/packages/browser/test/unit/index.bundle.tracing.test.ts index bce177ce0d72..b439dece8c6f 100644 --- a/packages/browser/test/unit/index.bundle.tracing.test.ts +++ b/packages/browser/test/unit/index.bundle.tracing.test.ts @@ -1,8 +1,4 @@ -import { - Feedback as FeedbackShim, - Replay as ReplayShim, - ReplayCanvas as ReplayCanvasShim, -} from '@sentry-internal/integration-shims'; +import { Feedback as FeedbackShim, Replay as ReplayShim } from '@sentry-internal/integration-shims'; import { BrowserTracing } from '@sentry-internal/tracing'; import * as TracingBundle from '../../src/index.bundle.tracing'; @@ -20,7 +16,6 @@ describe('index.bundle.tracing', () => { expect(TracingBundle.Integrations.Replay).toBe(ReplayShim); expect(TracingBundle.Replay).toBe(ReplayShim); - expect(TracingBundle.ReplayCanvas).toBe(ReplayCanvasShim); expect(TracingBundle.Integrations.BrowserTracing).toBe(BrowserTracing); expect(TracingBundle.BrowserTracing).toBe(BrowserTracing); diff --git a/packages/integration-shims/src/index.ts b/packages/integration-shims/src/index.ts index 31958781ac44..410a4a31d9c8 100644 --- a/packages/integration-shims/src/index.ts +++ b/packages/integration-shims/src/index.ts @@ -1,4 +1,3 @@ export { Feedback } from './Feedback'; export { Replay } from './Replay'; -export { ReplayCanvas } from './ReplayCanvas'; export { BrowserTracing, addTracingExtensions } from './BrowserTracing'; From 5fa012d8499c43fa5072262e921d8d61f41af95f Mon Sep 17 00:00:00 2001 From: Billy Vong Date: Mon, 15 Jan 2024 14:55:00 -0500 Subject: [PATCH 37/50] Revert "forgot shims" This reverts commit 9dd87585ac01e33cd6134030d6c40cc91db14291. --- .../integration-shims/src/ReplayCanvas.ts | 41 ------------------- 1 file changed, 41 deletions(-) delete mode 100644 packages/integration-shims/src/ReplayCanvas.ts diff --git a/packages/integration-shims/src/ReplayCanvas.ts b/packages/integration-shims/src/ReplayCanvas.ts deleted file mode 100644 index 1bff93359342..000000000000 --- a/packages/integration-shims/src/ReplayCanvas.ts +++ /dev/null @@ -1,41 +0,0 @@ -import type { Integration } from '@sentry/types'; -import { consoleSandbox } from '@sentry/utils'; - -/** - * This is a shim for the Replay integration. - * It is needed in order for the CDN bundles to continue working when users add/remove replay - * from it, without changing their config. This is necessary for the loader mechanism. - */ -class ReplayCanvasShim implements Integration { - /** - * @inheritDoc - */ - public static id: string = 'ReplayCanvas'; - - /** - * @inheritDoc - */ - public name: string; - - // eslint-disable-next-line @typescript-eslint/no-explicit-any - public constructor(_options: any) { - this.name = ReplayCanvasShim.id; - - consoleSandbox(() => { - // eslint-disable-next-line no-console - console.error('You are using new ReplayCanvas() even though this bundle does not include replay canvas.'); - }); - } - - /** jsdoc */ - public setupOnce(): void { - // noop - } - - /** jsdoc */ - public getOptions(): void { - // noop - } -} - -export { ReplayCanvasShim as ReplayCanvas }; From b2686b36bf5426dc482ce209e1cd365af67c78e0 Mon Sep 17 00:00:00 2001 From: Billy Vong Date: Mon, 15 Jan 2024 17:25:12 -0500 Subject: [PATCH 38/50] skip CDN bundles for canvas tests --- .../suites/replay/canvas/records/test.ts | 2 +- .../suites/replay/canvas/withCanvasIntegrationFirst/test.ts | 2 +- .../suites/replay/canvas/withCanvasIntegrationSecond/test.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/dev-packages/browser-integration-tests/suites/replay/canvas/records/test.ts b/dev-packages/browser-integration-tests/suites/replay/canvas/records/test.ts index 372ca8978356..a73976f89ae0 100644 --- a/dev-packages/browser-integration-tests/suites/replay/canvas/records/test.ts +++ b/dev-packages/browser-integration-tests/suites/replay/canvas/records/test.ts @@ -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(); } diff --git a/dev-packages/browser-integration-tests/suites/replay/canvas/withCanvasIntegrationFirst/test.ts b/dev-packages/browser-integration-tests/suites/replay/canvas/withCanvasIntegrationFirst/test.ts index ae45b5824cb5..104098eed2cf 100644 --- a/dev-packages/browser-integration-tests/suites/replay/canvas/withCanvasIntegrationFirst/test.ts +++ b/dev-packages/browser-integration-tests/suites/replay/canvas/withCanvasIntegrationFirst/test.ts @@ -4,7 +4,7 @@ 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()) { + if (shouldSkipReplayTest() || (process.env.PW_BUNDLE || '').startsWith('bundle')) { sentryTest.skip(); } diff --git a/dev-packages/browser-integration-tests/suites/replay/canvas/withCanvasIntegrationSecond/test.ts b/dev-packages/browser-integration-tests/suites/replay/canvas/withCanvasIntegrationSecond/test.ts index 674f94f86cab..d25066dc065b 100644 --- a/dev-packages/browser-integration-tests/suites/replay/canvas/withCanvasIntegrationSecond/test.ts +++ b/dev-packages/browser-integration-tests/suites/replay/canvas/withCanvasIntegrationSecond/test.ts @@ -4,7 +4,7 @@ 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()) { + if (shouldSkipReplayTest() || (process.env.PW_BUNDLE || '').startsWith('bundle')) { sentryTest.skip(); } From 7b11d639cf7c4b4addd648f37ea860f81972192b Mon Sep 17 00:00:00 2001 From: Billy Vong Date: Mon, 15 Jan 2024 19:35:08 -0500 Subject: [PATCH 39/50] add `canvas` to options event --- packages/replay/src/replay.ts | 7 +++++++ packages/replay/src/types/replay.ts | 1 + packages/replay/src/types/replayFrame.ts | 1 + packages/replay/src/util/handleRecordingEmit.ts | 1 + 4 files changed, 10 insertions(+) diff --git a/packages/replay/src/replay.ts b/packages/replay/src/replay.ts index 8302bbb0c8b6..30ccda422544 100644 --- a/packages/replay/src/replay.ts +++ b/packages/replay/src/replay.ts @@ -223,6 +223,13 @@ export class ReplayContainer implements ReplayContainerInterface { return this._isPaused; } + /** + * Determine if canvas recording is enabled + */ + public isRecordingCanvas(): boolean { + return Boolean(this._canvas); + } + /** Get the replay integration options. */ public getOptions(): ReplayPluginOptions { return this._options; diff --git a/packages/replay/src/types/replay.ts b/packages/replay/src/types/replay.ts index 23f4d163b587..93fd60a868f3 100644 --- a/packages/replay/src/types/replay.ts +++ b/packages/replay/src/types/replay.ts @@ -485,6 +485,7 @@ export interface ReplayContainer { ) => typeof THROTTLED | typeof SKIPPED | Promise; isEnabled(): boolean; isPaused(): boolean; + isRecordingCanvas(): boolean; getContext(): InternalEventContext; initializeSampling(): void; start(): void; diff --git a/packages/replay/src/types/replayFrame.ts b/packages/replay/src/types/replayFrame.ts index 8c41173e1a8e..fa6a4be8bfd9 100644 --- a/packages/replay/src/types/replayFrame.ts +++ b/packages/replay/src/types/replayFrame.ts @@ -114,6 +114,7 @@ export interface ReplayMultiClickFrame extends ReplayBaseBreadcrumbFrame { interface ReplayOptionFrame { blockAllMedia: boolean; + canvas: boolean; errorSampleRate: number; maskAllInputs: boolean; maskAllText: boolean; diff --git a/packages/replay/src/util/handleRecordingEmit.ts b/packages/replay/src/util/handleRecordingEmit.ts index 1d0fd10d0aa3..465b8229c1e0 100644 --- a/packages/replay/src/util/handleRecordingEmit.ts +++ b/packages/replay/src/util/handleRecordingEmit.ts @@ -122,6 +122,7 @@ export function createOptionsEvent(replay: ReplayContainer): ReplayOptionFrameEv data: { tag: 'options', payload: { + canvas: replay.isRecordingCanvas(), sessionSampleRate: options.sessionSampleRate, errorSampleRate: options.errorSampleRate, useCompressionOption: options.useCompression, From 0b2e850e935083c23345f7fa12d71de2a77cb49d Mon Sep 17 00:00:00 2001 From: Billy Vong Date: Tue, 16 Jan 2024 12:01:11 -0500 Subject: [PATCH 40/50] move replay-canvas to devDeps --- packages/browser/package.json | 2 +- packages/replay-canvas/package.json | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/browser/package.json b/packages/browser/package.json index d367f603d701..1311053ffdee 100644 --- a/packages/browser/package.json +++ b/packages/browser/package.json @@ -30,7 +30,6 @@ }, "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", @@ -38,6 +37,7 @@ "@sentry/utils": "7.93.0" }, "devDependencies": { + "@sentry-internal/replay-canvas": "7.93.0", "@sentry-internal/integration-shims": "7.93.0", "@types/md5": "2.1.33", "btoa": "^1.2.1", diff --git a/packages/replay-canvas/package.json b/packages/replay-canvas/package.json index 5691ee06677d..05135e8809a8 100644 --- a/packages/replay-canvas/package.json +++ b/packages/replay-canvas/package.json @@ -19,6 +19,7 @@ "types-ts3.8" ], "sideEffects": false, + "private": true, "scripts": { "build": "run-p build:transpile build:types build:bundle", "build:transpile": "rollup -c rollup.npm.config.mjs", From 097b5c71f704b6300fb4a7c5d23b54af3dfa1200 Mon Sep 17 00:00:00 2001 From: Billy Vong Date: Tue, 16 Jan 2024 12:53:15 -0500 Subject: [PATCH 41/50] canvas --> shouldRecordCanvas --- packages/replay/src/types/replayFrame.ts | 2 +- packages/replay/src/util/handleRecordingEmit.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/replay/src/types/replayFrame.ts b/packages/replay/src/types/replayFrame.ts index fa6a4be8bfd9..3a595e47a4cf 100644 --- a/packages/replay/src/types/replayFrame.ts +++ b/packages/replay/src/types/replayFrame.ts @@ -114,7 +114,6 @@ export interface ReplayMultiClickFrame extends ReplayBaseBreadcrumbFrame { interface ReplayOptionFrame { blockAllMedia: boolean; - canvas: boolean; errorSampleRate: number; maskAllInputs: boolean; maskAllText: boolean; @@ -123,6 +122,7 @@ interface ReplayOptionFrame { networkRequestHasHeaders: boolean; networkResponseHasHeaders: boolean; sessionSampleRate: number; + shouldRecordCanvas: boolean; useCompression: boolean; useCompressionOption: boolean; } diff --git a/packages/replay/src/util/handleRecordingEmit.ts b/packages/replay/src/util/handleRecordingEmit.ts index 465b8229c1e0..eaec29be261a 100644 --- a/packages/replay/src/util/handleRecordingEmit.ts +++ b/packages/replay/src/util/handleRecordingEmit.ts @@ -122,7 +122,7 @@ export function createOptionsEvent(replay: ReplayContainer): ReplayOptionFrameEv data: { tag: 'options', payload: { - canvas: replay.isRecordingCanvas(), + shouldRecordCanvas: replay.isRecordingCanvas(), sessionSampleRate: options.sessionSampleRate, errorSampleRate: options.errorSampleRate, useCompressionOption: options.useCompression, From 223ae913580a7c68932cff414b99a5fbb7ff3293 Mon Sep 17 00:00:00 2001 From: Billy Vong Date: Tue, 16 Jan 2024 13:07:45 -0500 Subject: [PATCH 42/50] fix tests for optionsEvent --- .../suites/replay/canvas/records/test.ts | 11 +++++++++++ .../suites/replay/customEvents/test.ts | 1 + 2 files changed, 12 insertions(+) diff --git a/dev-packages/browser-integration-tests/suites/replay/canvas/records/test.ts b/dev-packages/browser-integration-tests/suites/replay/canvas/records/test.ts index a73976f89ae0..681905eda932 100644 --- a/dev-packages/browser-integration-tests/suites/replay/canvas/records/test.ts +++ b/dev-packages/browser-integration-tests/suites/replay/canvas/records/test.ts @@ -24,6 +24,17 @@ 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); diff --git a/dev-packages/browser-integration-tests/suites/replay/customEvents/test.ts b/dev-packages/browser-integration-tests/suites/replay/customEvents/test.ts index c4bcbcc2c792..78fbca08d4cd 100644 --- a/dev-packages/browser-integration-tests/suites/replay/customEvents/test.ts +++ b/dev-packages/browser-integration-tests/suites/replay/customEvents/test.ts @@ -227,6 +227,7 @@ sentryTest( networkCaptureBodies: true, networkRequestHasHeaders: true, networkResponseHasHeaders: true, + shouldRecordCanvas: false, }, }, ]); From 4c412dafc072b540ff9d92718846eb5b329803ba Mon Sep 17 00:00:00 2001 From: Billy Vong Date: Tue, 16 Jan 2024 13:09:02 -0500 Subject: [PATCH 43/50] move back to deps --- packages/browser/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/browser/package.json b/packages/browser/package.json index 1311053ffdee..d367f603d701 100644 --- a/packages/browser/package.json +++ b/packages/browser/package.json @@ -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", @@ -37,7 +38,6 @@ "@sentry/utils": "7.93.0" }, "devDependencies": { - "@sentry-internal/replay-canvas": "7.93.0", "@sentry-internal/integration-shims": "7.93.0", "@types/md5": "2.1.33", "btoa": "^1.2.1", From 6014437db150f2ab169a2a2a735896ac6a17533a Mon Sep 17 00:00:00 2001 From: Billy Vong Date: Tue, 16 Jan 2024 13:28:50 -0500 Subject: [PATCH 44/50] lint --- .../suites/replay/canvas/records/test.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/dev-packages/browser-integration-tests/suites/replay/canvas/records/test.ts b/dev-packages/browser-integration-tests/suites/replay/canvas/records/test.ts index 681905eda932..0f9580fccc52 100644 --- a/dev-packages/browser-integration-tests/suites/replay/canvas/records/test.ts +++ b/dev-packages/browser-integration-tests/suites/replay/canvas/records/test.ts @@ -34,7 +34,6 @@ sentryTest('can record canvas', async ({ getLocalTestUrl, page, browserName }) = }, ]); - await Promise.all([page.click('#draw'), reqPromise1]); const { incrementalSnapshots } = getReplayRecordingContent(await reqPromise2); From 94b8ac14b92630fa3db9cf9d622f4b41502e3d35 Mon Sep 17 00:00:00 2001 From: Billy Vong Date: Tue, 16 Jan 2024 13:29:07 -0500 Subject: [PATCH 45/50] remove private: true, fails the e2e tests --- packages/replay-canvas/package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/replay-canvas/package.json b/packages/replay-canvas/package.json index 05135e8809a8..5691ee06677d 100644 --- a/packages/replay-canvas/package.json +++ b/packages/replay-canvas/package.json @@ -19,7 +19,6 @@ "types-ts3.8" ], "sideEffects": false, - "private": true, "scripts": { "build": "run-p build:transpile build:types build:bundle", "build:transpile": "rollup -c rollup.npm.config.mjs", From 60dcc3598a4225b59944029c68bd361229106cd5 Mon Sep 17 00:00:00 2001 From: Billy Vong Date: Tue, 16 Jan 2024 14:12:43 -0500 Subject: [PATCH 46/50] fix optionEvent tests w/ shouldRecordCanvas --- .../tests/fixtures/ReplayRecordingData.ts | 1 + .../tests/fixtures/ReplayRecordingData.ts | 1 + .../tests/fixtures/ReplayRecordingData.ts | 1 + 3 files changed, 3 insertions(+) diff --git a/dev-packages/e2e-tests/test-applications/react-create-hash-router/tests/fixtures/ReplayRecordingData.ts b/dev-packages/e2e-tests/test-applications/react-create-hash-router/tests/fixtures/ReplayRecordingData.ts index 3cf31a74ca49..698cb83b5fb4 100644 --- a/dev-packages/e2e-tests/test-applications/react-create-hash-router/tests/fixtures/ReplayRecordingData.ts +++ b/dev-packages/e2e-tests/test-applications/react-create-hash-router/tests/fixtures/ReplayRecordingData.ts @@ -18,6 +18,7 @@ export const ReplayRecordingData = [ networkRequestHasHeaders: true, networkResponseHasHeaders: true, sessionSampleRate: 1, + shouldRecordCanvas: false, useCompression: false, useCompressionOption: true, }, diff --git a/dev-packages/e2e-tests/test-applications/react-router-6-use-routes/tests/fixtures/ReplayRecordingData.ts b/dev-packages/e2e-tests/test-applications/react-router-6-use-routes/tests/fixtures/ReplayRecordingData.ts index 3cf31a74ca49..698cb83b5fb4 100644 --- a/dev-packages/e2e-tests/test-applications/react-router-6-use-routes/tests/fixtures/ReplayRecordingData.ts +++ b/dev-packages/e2e-tests/test-applications/react-router-6-use-routes/tests/fixtures/ReplayRecordingData.ts @@ -18,6 +18,7 @@ export const ReplayRecordingData = [ networkRequestHasHeaders: true, networkResponseHasHeaders: true, sessionSampleRate: 1, + shouldRecordCanvas: false, useCompression: false, useCompressionOption: true, }, diff --git a/dev-packages/e2e-tests/test-applications/standard-frontend-react/tests/fixtures/ReplayRecordingData.ts b/dev-packages/e2e-tests/test-applications/standard-frontend-react/tests/fixtures/ReplayRecordingData.ts index 3cf31a74ca49..698cb83b5fb4 100644 --- a/dev-packages/e2e-tests/test-applications/standard-frontend-react/tests/fixtures/ReplayRecordingData.ts +++ b/dev-packages/e2e-tests/test-applications/standard-frontend-react/tests/fixtures/ReplayRecordingData.ts @@ -18,6 +18,7 @@ export const ReplayRecordingData = [ networkRequestHasHeaders: true, networkResponseHasHeaders: true, sessionSampleRate: 1, + shouldRecordCanvas: false, useCompression: false, useCompressionOption: true, }, From b87f4213006722afc2fd078832a55f517432d136 Mon Sep 17 00:00:00 2001 From: Billy Vong Date: Tue, 16 Jan 2024 14:12:54 -0500 Subject: [PATCH 47/50] add core as dep to replay-canvas --- packages/replay-canvas/package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/replay-canvas/package.json b/packages/replay-canvas/package.json index 5691ee06677d..81a70f18c20d 100644 --- a/packages/replay-canvas/package.json +++ b/packages/replay-canvas/package.json @@ -56,6 +56,7 @@ "@sentry-internal/rrweb": "2.8.0" }, "dependencies": { + "@sentry/core": "7.93.0", "@sentry/replay": "7.93.0", "@sentry/types": "7.93.0" }, From 15305090906f0fce275c1c638c463cd2aebd7f89 Mon Sep 17 00:00:00 2001 From: Billy Vong Date: Tue, 16 Jan 2024 14:39:30 -0500 Subject: [PATCH 48/50] fix types due to https://github.com/getsentry/sentry-javascript/pull/10183 --- packages/replay-canvas/src/canvas.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/replay-canvas/src/canvas.ts b/packages/replay-canvas/src/canvas.ts index e08131f96a78..bcc586633dd6 100644 --- a/packages/replay-canvas/src/canvas.ts +++ b/packages/replay-canvas/src/canvas.ts @@ -1,7 +1,7 @@ import { CanvasManager } from '@sentry-internal/rrweb'; import { convertIntegrationFnToClass } from '@sentry/core'; import type { CanvasManagerInterface, CanvasManagerOptions } from '@sentry/replay'; -import type { IntegrationFn } from '@sentry/types'; +import type { Integration, IntegrationClass, IntegrationFn } from '@sentry/types'; interface ReplayCanvasOptions { quality: 'low' | 'medium' | 'high'; @@ -78,4 +78,8 @@ const replayCanvasIntegration = ((options: Partial = {}) => // TODO(v8) // eslint-disable-next-line deprecation/deprecation -export const ReplayCanvas = convertIntegrationFnToClass(INTEGRATION_NAME, replayCanvasIntegration); +export const ReplayCanvas = convertIntegrationFnToClass(INTEGRATION_NAME, replayCanvasIntegration) as IntegrationClass< + Integration & { + getOptions: () => ReplayCanvasIntegrationOptions + } +>; From 4d82a1f5c2737610d6a131a2c07da9441d4884e5 Mon Sep 17 00:00:00 2001 From: Billy Vong Date: Tue, 16 Jan 2024 15:17:02 -0500 Subject: [PATCH 49/50] lint --- packages/replay-canvas/src/canvas.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/replay-canvas/src/canvas.ts b/packages/replay-canvas/src/canvas.ts index bcc586633dd6..90b65e5ccd35 100644 --- a/packages/replay-canvas/src/canvas.ts +++ b/packages/replay-canvas/src/canvas.ts @@ -80,6 +80,6 @@ const replayCanvasIntegration = ((options: Partial = {}) => // eslint-disable-next-line deprecation/deprecation export const ReplayCanvas = convertIntegrationFnToClass(INTEGRATION_NAME, replayCanvasIntegration) as IntegrationClass< Integration & { - getOptions: () => ReplayCanvasIntegrationOptions + getOptions: () => ReplayCanvasIntegrationOptions; } >; From 006133b48450ba4d50e64c3650fb7f3f4a600025 Mon Sep 17 00:00:00 2001 From: Billy Vong Date: Tue, 16 Jan 2024 15:21:20 -0500 Subject: [PATCH 50/50] Revert "do not publish as separate package" This reverts commit f3290c4630df755cd80f4747c74d55211a864145. --- .craft.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.craft.yml b/.craft.yml index 1a4b07c18874..7c09cb4ddd4c 100644 --- a/.craft.yml +++ b/.craft.yml @@ -32,6 +32,10 @@ targets: - name: npm id: '@sentry-internal/feedback' includeNames: /^sentry-internal-feedback-\d.*\.tgz$/ + ## 1.8 ReplayCanvas package (browser only) + - name: npm + id: '@sentry-internal/replay-canvas' + includeNames: /^sentry-internal-replay-canvas-\d.*\.tgz$/ ## 2. Browser & Node SDKs - name: npm