From 59ade6ac441a345328f2286aa868dbc166f529c2 Mon Sep 17 00:00:00 2001 From: Sebastian Alex Date: Wed, 28 Feb 2024 10:22:10 +0000 Subject: [PATCH 01/12] sdk-core: add useModule to builder, export dataStructures --- .../sdk-core/src/builder/BacktraceCoreClientBuilder.ts | 10 ++++++++++ packages/sdk-core/src/dataStructures/index.ts | 2 ++ packages/sdk-core/src/index.ts | 1 + 3 files changed, 13 insertions(+) create mode 100644 packages/sdk-core/src/dataStructures/index.ts diff --git a/packages/sdk-core/src/builder/BacktraceCoreClientBuilder.ts b/packages/sdk-core/src/builder/BacktraceCoreClientBuilder.ts index c68dd942..0a156edc 100644 --- a/packages/sdk-core/src/builder/BacktraceCoreClientBuilder.ts +++ b/packages/sdk-core/src/builder/BacktraceCoreClientBuilder.ts @@ -1,5 +1,6 @@ import { BacktraceReportSubmission } from '../model/http/BacktraceReportSubmission'; import { BacktraceRequestHandler } from '../model/http/BacktraceRequestHandler'; +import { BacktraceModule } from '../modules/BacktraceModule'; import { BacktraceAttributeProvider } from '../modules/attribute/BacktraceAttributeProvider'; import { BreadcrumbsEventSubscriber, BreadcrumbsStorage } from '../modules/breadcrumbs'; import { BacktraceStackTraceConverter } from '../modules/converter'; @@ -81,4 +82,13 @@ export abstract class BacktraceCoreClientBuilder Date: Wed, 28 Feb 2024 10:24:01 +0000 Subject: [PATCH 02/12] session-replay: add initial workspace --- package.json | 3 +- packages/session-replay/LICENSE | 21 ++++++++++++++ packages/session-replay/package.json | 34 +++++++++++++++++++++++ packages/session-replay/tsconfig.json | 14 ++++++++++ packages/session-replay/webpack.config.js | 28 +++++++++++++++++++ 5 files changed, 99 insertions(+), 1 deletion(-) create mode 100644 packages/session-replay/LICENSE create mode 100644 packages/session-replay/package.json create mode 100644 packages/session-replay/tsconfig.json create mode 100644 packages/session-replay/webpack.config.js diff --git a/package.json b/package.json index 472bbb89..9cef27e0 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,8 @@ "tools/rollup-plugin", "tools/webpack-plugin", "tools/vite-plugin", - "packages/electron" + "packages/electron", + "packages/session-replay" ], "repository": { "type": "git", diff --git a/packages/session-replay/LICENSE b/packages/session-replay/LICENSE new file mode 100644 index 00000000..cf679f7e --- /dev/null +++ b/packages/session-replay/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 Backtrace Labs + +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/session-replay/package.json b/packages/session-replay/package.json new file mode 100644 index 00000000..329c4ae8 --- /dev/null +++ b/packages/session-replay/package.json @@ -0,0 +1,34 @@ +{ + "name": "@backtrace/session-replay", + "version": "0.0.1", + "description": "", + "main": "lib/index.js", + "types": "lib/index.d.ts", + "scripts": { + "build": "tsc", + "clean": "tsc -b --clean && rimraf \"lib\"", + "format": "prettier --write '**/*.ts'", + "lint": "eslint . --ext .ts", + "watch": "tsc -w", + "test": "NODE_ENV=test jest" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/backtrace-labs/backtrace-javascript.git" + }, + "author": "Backtrace ", + "license": "MIT", + "bugs": { + "url": "https://github.com/backtrace-labs/backtrace-javascript/issues" + }, + "files": [ + "/lib" + ], + "homepage": "https://github.com/backtrace-labs/backtrace-javascript#readme", + "peerDependencies": { + "@backtrace/sdk-core": "^0.2.0" + }, + "dependencies": { + "rrweb": "^2.0.0-alpha.4" + } +} diff --git a/packages/session-replay/tsconfig.json b/packages/session-replay/tsconfig.json new file mode 100644 index 00000000..93e86d7f --- /dev/null +++ b/packages/session-replay/tsconfig.json @@ -0,0 +1,14 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "rootDir": "./src", + "outDir": "./lib", + "composite": true + }, + "exclude": ["node_modules", "tests", "lib"], + "references": [ + { + "path": "../sdk-core/tsconfig.json" + } + ] +} diff --git a/packages/session-replay/webpack.config.js b/packages/session-replay/webpack.config.js new file mode 100644 index 00000000..a47cc23c --- /dev/null +++ b/packages/session-replay/webpack.config.js @@ -0,0 +1,28 @@ +const path = require('path'); +const { webpackTypescriptConfig, minifiedAndUnminified } = require('../../build/common'); +const agentDefinitionPlugin = require('../../build/agentDefinitionPlugin'); + +/** @type {import('webpack').Configuration} */ +const common = { + ...webpackTypescriptConfig, + target: 'web', + mode: process.env.NODE_ENV ?? 'production', + devtool: 'source-map', + entry: './src/index.ts', + plugins: [agentDefinitionPlugin(path.join(__dirname, 'package.json'))], +}; + +/** @type {Array} */ +module.exports = [ + ...minifiedAndUnminified({ + ...common, + output: { + filename: 'index.js', + path: path.join(__dirname, 'lib'), + library: { + name: 'Backtrace', + type: 'umd', + }, + }, + }), +]; From 0b37da44880f745bdb4b2ea2f42c2476fab8d883 Mon Sep 17 00:00:00 2001 From: Sebastian Alex Date: Wed, 28 Feb 2024 10:39:20 +0000 Subject: [PATCH 03/12] session-replay: add session recorder module --- .../src/BacktraceSessionRecorder.ts | 58 +++++++++++++++++++ .../src/BacktraceSessionReplayModule.ts | 22 +++++++ packages/session-replay/src/index.ts | 2 + 3 files changed, 82 insertions(+) create mode 100644 packages/session-replay/src/BacktraceSessionRecorder.ts create mode 100644 packages/session-replay/src/BacktraceSessionReplayModule.ts create mode 100644 packages/session-replay/src/index.ts diff --git a/packages/session-replay/src/BacktraceSessionRecorder.ts b/packages/session-replay/src/BacktraceSessionRecorder.ts new file mode 100644 index 00000000..c062927f --- /dev/null +++ b/packages/session-replay/src/BacktraceSessionRecorder.ts @@ -0,0 +1,58 @@ +import { BacktraceAttachment } from '@backtrace/sdk-core'; +import { eventWithTime } from '@rrweb/types'; +import { record } from 'rrweb'; +import { recordOptions } from 'rrweb/typings/types'; + +export interface BacktraceSessionRecorderOptions { + readonly maxEventCount?: number; + readonly maxTime?: number; + readonly rrOptions?: recordOptions; +} + +export class BacktraceSessionRecorder implements BacktraceAttachment { + public readonly type = 'dynamic'; + private _previousEvents?: eventWithTime[] = []; + private _events?: eventWithTime[] = []; + + private _stop?: () => void; + + constructor(private readonly _options: BacktraceSessionRecorderOptions) { + this._events = []; + } + + public name = 'bt-session-replay-0'; + + public start() { + const stop = record({ + ...this._options.rrOptions, + emit: (event, isCheckout) => { + if (isCheckout || !this._events) { + this._previousEvents = this._events; + this._events = []; + } + + this._events.push(event); + }, + checkoutEveryNth: this._options.maxEventCount, + checkoutEveryNms: this._options.maxTime, + }); + + this._stop = stop; + } + + public stop() { + if (this._stop) { + this._stop(); + } + } + + public get(): string { + let events = [...(this._events ?? []), ...(this._previousEvents ?? [])]; + if (this._options.maxTime) { + const cutout = Date.now() - this._options.maxTime; + events = events.filter((e) => e.timestamp >= cutout); + } + + return JSON.stringify(events); + } +} diff --git a/packages/session-replay/src/BacktraceSessionReplayModule.ts b/packages/session-replay/src/BacktraceSessionReplayModule.ts new file mode 100644 index 00000000..72bd8aeb --- /dev/null +++ b/packages/session-replay/src/BacktraceSessionReplayModule.ts @@ -0,0 +1,22 @@ +import { BacktraceModule, BacktraceModuleBindData } from '@backtrace/sdk-core'; +import { BacktraceSessionRecorder, BacktraceSessionRecorderOptions } from './BacktraceSessionRecorder'; + +export class BacktraceSessionReplayModule implements BacktraceModule { + private readonly _recorder: BacktraceSessionRecorder; + + constructor(options: BacktraceSessionRecorderOptions) { + this._recorder = new BacktraceSessionRecorder(options); + } + + public bind({ client }: BacktraceModuleBindData): void { + client.addAttachment(this._recorder); + } + + public initialize(): void { + this._recorder.start(); + } + + public dispose(): void { + this._recorder.stop(); + } +} diff --git a/packages/session-replay/src/index.ts b/packages/session-replay/src/index.ts new file mode 100644 index 00000000..70806baf --- /dev/null +++ b/packages/session-replay/src/index.ts @@ -0,0 +1,2 @@ +export * from './BacktraceSessionRecorder'; +export * from './BacktraceSessionReplayModule'; From 47e369bff76bfd7b13e1c50fb95a51ce79c1e646 Mon Sep 17 00:00:00 2001 From: Sebastian Alex Date: Thu, 4 Apr 2024 10:50:10 +0000 Subject: [PATCH 04/12] session-replay: re-emit events to advancedOptions emit --- packages/session-replay/src/BacktraceSessionRecorder.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/session-replay/src/BacktraceSessionRecorder.ts b/packages/session-replay/src/BacktraceSessionRecorder.ts index c062927f..c180b7e2 100644 --- a/packages/session-replay/src/BacktraceSessionRecorder.ts +++ b/packages/session-replay/src/BacktraceSessionRecorder.ts @@ -6,7 +6,7 @@ import { recordOptions } from 'rrweb/typings/types'; export interface BacktraceSessionRecorderOptions { readonly maxEventCount?: number; readonly maxTime?: number; - readonly rrOptions?: recordOptions; + readonly advancedOptions?: recordOptions; } export class BacktraceSessionRecorder implements BacktraceAttachment { @@ -24,7 +24,7 @@ export class BacktraceSessionRecorder implements BacktraceAttachment { public start() { const stop = record({ - ...this._options.rrOptions, + ...this._options.advancedOptions, emit: (event, isCheckout) => { if (isCheckout || !this._events) { this._previousEvents = this._events; @@ -32,6 +32,10 @@ export class BacktraceSessionRecorder implements BacktraceAttachment { } this._events.push(event); + + if (this._options.advancedOptions?.emit) { + this._options.advancedOptions.emit(event as never, isCheckout); + } }, checkoutEveryNth: this._options.maxEventCount, checkoutEveryNms: this._options.maxTime, From 6a5c0d333a63d7dc93cfbedb7b1f42853b8e380d Mon Sep 17 00:00:00 2001 From: Sebastian Alex Date: Thu, 4 Apr 2024 11:50:31 +0000 Subject: [PATCH 05/12] session-replay: add options and docs --- .../src/BacktraceSessionRecorder.ts | 224 +++++++++++++++++- .../src/BacktraceSessionReplayModule.ts | 15 +- 2 files changed, 232 insertions(+), 7 deletions(-) diff --git a/packages/session-replay/src/BacktraceSessionRecorder.ts b/packages/session-replay/src/BacktraceSessionRecorder.ts index c180b7e2..3c403ac9 100644 --- a/packages/session-replay/src/BacktraceSessionRecorder.ts +++ b/packages/session-replay/src/BacktraceSessionRecorder.ts @@ -1,16 +1,219 @@ import { BacktraceAttachment } from '@backtrace/sdk-core'; -import { eventWithTime } from '@rrweb/types'; +import { MouseInteractions, eventWithTime } from '@rrweb/types'; import { record } from 'rrweb'; +import { MaskInputFn, MaskInputOptions, MaskTextFn } from 'rrweb-snapshot'; import { recordOptions } from 'rrweb/typings/types'; +interface BacktraceSessionRecorderSamplingOptions { + /** + * Controls whether mouse movement is recorded. + * @default true + */ + readonly mousemove?: boolean | number; + + /** + * Controls specific mouse interactions. + * + * Can be one of: + * * `true` - all mouse interactions will be recorded + * * `false` - no mouse interactions will be recorded + * * `{[string]: boolean}` - control specific mouse interactions: + * * `MouseUp` + * * `MouseDown` + * * `Click` + * * `ContextMenu` + * * `DblClick` + * * `Focus` + * * `Blur` + * * `TouchStart` + * * `TouchMove_Departed` + * * `TouchEnd` + * * `TouchCancel` + * @default true + * @example + * // Record only MouseUp and ContextMenu interactions: + * mouseInteraction = { + * DblClick: false, + * Blur: false, + * Click: false, + * ContextMenu: true, + * Focus: false, + * MouseDown: false, + * MouseUp: true, + * TouchCancel: false, + * TouchEnd: false, + * TouchMove_Departed: false, + * TouchStart: false, + * }; + */ + readonly mouseInteraction?: + | boolean + | Partial> + | Partial>; + + /** + * Interval of scrolling events in milliseconds (i.e. will not capture more than one event every set time). + * @default undefined + */ + readonly scroll?: number; + + /** + * Interval of media events in milliseconds (i.e. will not capture more than one event every set time). + * @default undefined + */ + readonly media?: number; + + /** + * Capture either `all` or `last` input events. + * + * When set to `last`, only final input will be captured. + * @default "all" + */ + readonly input?: 'all' | 'last'; +} + +export interface BacktraceSessionReplayPrivacyOptions { + /** + * Use a `string` or `RegExp` to configure which elements should be blocked. + * @default "rr-block" + */ + readonly blockClass?: string | RegExp; + + /** + * Use a `string` to configure which selector should be blocked. + * @default undefined + */ + readonly blockSelector?: string; + + /** + * Use a `string` or `RegExp` to configure which elements should be ignored. + * @default "rr-ignore" + */ + readonly ignoreClass?: string; + + /** + * Use a `string` to configure which selector should be ignored. + * @default undefined + */ + readonly ignoreSelector?: string; + + /** + * Array of CSS attributes that should be ignored. + */ + readonly ignoreCSSAttributes?: string; + + /** + * Use a `string` or `RegExp` to configure which elements should be masked. + * @default "rr-mask" + */ + readonly maskTextClass?: string; + + /** + * Use a `string` to configure which selector should be masked. + * @default undefined + */ + readonly maskTextSelector?: string; + + /** + * If `true`, will mask all inputs. + * @default false + */ + readonly maskAllInputs?: boolean; + + /** + * Mask specific kinds of input. + * + * Can be an object with the following keys: + * * `color` + * * `date` + * * `'datetime-local'` + * * `email` + * * `month` + * * `number` + * * `range` + * * `search` + * * `tel` + * * `text` + * * `time` + * * `url` + * * `week` + * * `textarea` + * * `select` + * * `password` + * @default { password: true } + */ + readonly maskInputOptions?: MaskInputOptions; + + /** + * Callback to customize input masking. + * @param text input text to mask + * @param element input HTML element + * @returns masked text + * @default undefined + * @example + * // replace text with letter A repeated for the text length if element has class "mask-a" + * maskInputFn = (text, element) => + * element.classList.contains('mask-a') + * ? 'A'.repeat(text.length) + * : text; + */ + readonly maskInputFn?: MaskInputFn; + + /** + * Callback to customize text masking. + * @param text text to mask + * @returns masked text + * @default undefined + * @example + * // replace text with letter A repeated for the text length + * maskTextFn = (text) => 'A'.repeat(text.length); + */ + readonly maskTextFn?: MaskTextFn; +} + export interface BacktraceSessionRecorderOptions { - readonly maxEventCount?: number; - readonly maxTime?: number; + /** + * Maximum recorded event count to be sent with the report. Use `false` to disable the limit. + * @default 100 + */ + readonly maxEventCount?: number | false; + + /** + * Maximum timeframe for recorded events to be sent with the report. Use `false` to disable the limit. + * @default false + */ + readonly maxTime?: number | false; + + /** + * Sampling options. Use those to reduce event count or size. + */ + readonly sampling?: BacktraceSessionRecorderSamplingOptions; + + /** + * Privacy options. Use those to remove confidendial data. + */ + readonly privacy?: BacktraceSessionReplayPrivacyOptions; + + /** + * Options passed to `rrweb.record` function. Refer to `rrweb` documentation for more information. + */ readonly advancedOptions?: recordOptions; } +function defaultIfNotFalse(value: T | false, defaultValue?: T) { + if (value === false) { + return undefined; + } + + return value ?? defaultValue; +} + export class BacktraceSessionRecorder implements BacktraceAttachment { public readonly type = 'dynamic'; + + private readonly _maxEventCount?: number; + private readonly _maxTime?: number; + private _previousEvents?: eventWithTime[] = []; private _events?: eventWithTime[] = []; @@ -18,6 +221,8 @@ export class BacktraceSessionRecorder implements BacktraceAttachment { constructor(private readonly _options: BacktraceSessionRecorderOptions) { this._events = []; + this._maxEventCount = defaultIfNotFalse(_options.maxEventCount, 100); + this._maxTime = defaultIfNotFalse(_options.maxTime, undefined); } public name = 'bt-session-replay-0'; @@ -25,6 +230,14 @@ export class BacktraceSessionRecorder implements BacktraceAttachment { public start() { const stop = record({ ...this._options.advancedOptions, + sampling: { + mousemove: this._options.sampling?.mousemove, + mouseInteraction: this._options.sampling?.mouseInteraction, + input: this._options.sampling?.input, + media: this._options.sampling?.media, + scroll: this._options.sampling?.scroll, + ...this._options.advancedOptions, + }, emit: (event, isCheckout) => { if (isCheckout || !this._events) { this._previousEvents = this._events; @@ -32,13 +245,14 @@ export class BacktraceSessionRecorder implements BacktraceAttachment { } this._events.push(event); + console.log(event); if (this._options.advancedOptions?.emit) { this._options.advancedOptions.emit(event as never, isCheckout); } }, - checkoutEveryNth: this._options.maxEventCount, - checkoutEveryNms: this._options.maxTime, + checkoutEveryNth: this._maxEventCount && Math.ceil(this._maxEventCount / 2), + checkoutEveryNms: this._maxTime && Math.ceil(this._maxTime / 2), }); this._stop = stop; diff --git a/packages/session-replay/src/BacktraceSessionReplayModule.ts b/packages/session-replay/src/BacktraceSessionReplayModule.ts index 72bd8aeb..f304cafc 100644 --- a/packages/session-replay/src/BacktraceSessionReplayModule.ts +++ b/packages/session-replay/src/BacktraceSessionReplayModule.ts @@ -1,11 +1,22 @@ import { BacktraceModule, BacktraceModuleBindData } from '@backtrace/sdk-core'; import { BacktraceSessionRecorder, BacktraceSessionRecorderOptions } from './BacktraceSessionRecorder'; +/** + * Adds session recorder module to `BacktraceClient`. + * + * Add using `useModule` on `BacktraceClient` builder. + * @example + const client = BacktraceClient.builder({ + ... + }).useModule(new BacktraceSessionReplayModule({ + // options here + })).build(); + */ export class BacktraceSessionReplayModule implements BacktraceModule { private readonly _recorder: BacktraceSessionRecorder; - constructor(options: BacktraceSessionRecorderOptions) { - this._recorder = new BacktraceSessionRecorder(options); + constructor(options?: BacktraceSessionRecorderOptions) { + this._recorder = new BacktraceSessionRecorder(options ?? {}); } public bind({ client }: BacktraceModuleBindData): void { From dcbf497e7a3fb88d5751dbadae041742ba76d729 Mon Sep 17 00:00:00 2001 From: Sebastian Alex Date: Wed, 28 Feb 2024 10:40:57 +0000 Subject: [PATCH 06/12] browser example: add example usage of session-replay --- examples/sdk/browser/package.json | 3 ++- examples/sdk/browser/src/index.ts | 11 +++++++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/examples/sdk/browser/package.json b/examples/sdk/browser/package.json index 316efadd..f6e94e59 100644 --- a/examples/sdk/browser/package.json +++ b/examples/sdk/browser/package.json @@ -41,6 +41,7 @@ }, "dependencies": { "@reduxjs/toolkit": "^1.9.5", - "@backtrace/browser": "^0.1.0" + "@backtrace/browser": "^0.2.0", + "@backtrace/session-replay": "^0.0.1" } } diff --git a/examples/sdk/browser/src/index.ts b/examples/sdk/browser/src/index.ts index 781c6234..58f6767a 100644 --- a/examples/sdk/browser/src/index.ts +++ b/examples/sdk/browser/src/index.ts @@ -1,8 +1,9 @@ import { BacktraceClient, BacktraceStringAttachment, createBacktraceReduxMiddleware } from '@backtrace/browser'; +import { BacktraceSessionReplayModule } from '@backtrace/session-replay'; import { configureStore, createSlice } from '@reduxjs/toolkit'; import { SUBMISSION_URL } from './consts'; -const client = BacktraceClient.initialize({ +const client = BacktraceClient.builder({ url: SUBMISSION_URL, name: '@backtrace/browser-example', version: '0.0.1', @@ -13,7 +14,13 @@ const client = BacktraceClient.initialize({ prop2: 123, }, }, -}); +}) + .useModule( + new BacktraceSessionReplayModule({ + maxEventCount: 100, + }), + ) + .build(); interface DemoState { count: number; From f768fff33d1d50198ff7c4603fc088ad00bf18f8 Mon Sep 17 00:00:00 2001 From: Sebastian Alex Date: Thu, 4 Apr 2024 12:01:17 +0000 Subject: [PATCH 07/12] chore: update package-lock.json --- package-lock.json | 81 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 80 insertions(+), 1 deletion(-) diff --git a/package-lock.json b/package-lock.json index cb880bd7..be8a4346 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,7 +20,8 @@ "tools/rollup-plugin", "tools/webpack-plugin", "tools/vite-plugin", - "packages/electron" + "packages/electron", + "packages/session-replay" ], "devDependencies": { "@types/node": "^14.18.51", @@ -2544,6 +2545,10 @@ "resolved": "packages/sdk-core", "link": true }, + "node_modules/@backtrace/session-replay": { + "resolved": "packages/session-replay", + "link": true + }, "node_modules/@backtrace/sourcemap-tools": { "resolved": "tools/sourcemap-tools", "link": true @@ -4473,6 +4478,14 @@ } } }, + "node_modules/@rrweb/types": { + "version": "2.0.0-alpha.11", + "resolved": "https://registry.npmjs.org/@rrweb/types/-/types-2.0.0-alpha.11.tgz", + "integrity": "sha512-8ccocIkT5J/bfNRQY85qR/g6p5YQFpgFO2cMt4+Ex7w31Lq0yqZBRaoYEsawQKpLrn5KOHkdn2UTUrna7WMQuA==", + "dependencies": { + "rrweb-snapshot": "^2.0.0-alpha.11" + } + }, "node_modules/@sideway/address": { "version": "4.1.4", "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.4.tgz", @@ -4708,6 +4721,11 @@ "integrity": "sha512-he+DHOWReW0nghN24E1WUqM0efK4kI9oTqDm6XmK8ZPe2djZ90BSNdGnIyCLzCPw7/pogPlGbzI2wHGGmi4O/Q==", "dev": true }, + "node_modules/@types/css-font-loading-module": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/@types/css-font-loading-module/-/css-font-loading-module-0.0.7.tgz", + "integrity": "sha512-nl09VhutdjINdWyXxHWN/w9zlNCfr60JUqJbd24YXUuCwgeL0TpFSdElCwb6cxfB6ybE19Gjj4g0jsgkXxKv1Q==" + }, "node_modules/@types/decompress": { "version": "4.2.7", "resolved": "https://registry.npmjs.org/@types/decompress/-/decompress-4.2.7.tgz", @@ -5576,6 +5594,11 @@ } } }, + "node_modules/@xstate/fsm": { + "version": "1.6.5", + "resolved": "https://registry.npmjs.org/@xstate/fsm/-/fsm-1.6.5.tgz", + "integrity": "sha512-b5o1I6aLNeYlU/3CPlj/Z91ybk1gUsKT+5NAJI+2W4UjvS5KLG28K9v5UvNoFVjHV8PajVZ00RH3vnjyQO7ZAw==" + }, "node_modules/@xtuc/ieee754": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", @@ -6455,6 +6478,14 @@ "node": ">=0.10.0" } }, + "node_modules/base64-arraybuffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz", + "integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==", + "engines": { + "node": ">= 0.6.0" + } + }, "node_modules/base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", @@ -10061,6 +10092,11 @@ "pend": "~1.2.0" } }, + "node_modules/fflate": { + "version": "0.4.8", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.4.8.tgz", + "integrity": "sha512-FJqqoDBR00Mdj9ppamLa/Y7vxm+PRmNWA67N846RvsoYVMKB4q3y/de5PA7gUmRMYK/8CMz2GDZQmCRN1wBcWA==" + }, "node_modules/figgy-pudding": { "version": "3.5.2", "resolved": "https://registry.npmjs.org/figgy-pudding/-/figgy-pudding-3.5.2.tgz", @@ -14694,6 +14730,11 @@ "node": ">=4.0.0" } }, + "node_modules/mitt": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", + "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==" + }, "node_modules/mixin-deep": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", @@ -16986,6 +17027,34 @@ "fsevents": "~2.3.2" } }, + "node_modules/rrdom": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/rrdom/-/rrdom-0.1.7.tgz", + "integrity": "sha512-ZLd8f14z9pUy2Hk9y636cNv5Y2BMnNEY99wxzW9tD2BLDfe1xFxtLjB4q/xCBYo6HRe0wofzKzjm4JojmpBfFw==", + "dependencies": { + "rrweb-snapshot": "^2.0.0-alpha.4" + } + }, + "node_modules/rrweb": { + "version": "2.0.0-alpha.4", + "resolved": "https://registry.npmjs.org/rrweb/-/rrweb-2.0.0-alpha.4.tgz", + "integrity": "sha512-wEHUILbxDPcNwkM3m4qgPgXAiBJyqCbbOHyVoNEVBJzHszWEFYyTbrZqUdeb1EfmTRC2PsumCIkVcomJ/xcOzA==", + "dependencies": { + "@rrweb/types": "^2.0.0-alpha.4", + "@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", + "rrdom": "^0.1.7", + "rrweb-snapshot": "^2.0.0-alpha.4" + } + }, + "node_modules/rrweb-snapshot": { + "version": "2.0.0-alpha.11", + "resolved": "https://registry.npmjs.org/rrweb-snapshot/-/rrweb-snapshot-2.0.0-alpha.11.tgz", + "integrity": "sha512-N0dzeJA2VhrlSOadkKwCVmV/DuNOwBH+Lhx89hAf9PQK4lCS8AP4AaylhqUdZOYHqwVjqsYel/uZ4hN79vuLhw==" + }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -21121,6 +21190,16 @@ "typescript": "^5.0.4" } }, + "packages/session-replay": { + "version": "0.0.1", + "license": "MIT", + "dependencies": { + "rrweb": "^2.0.0-alpha.4" + }, + "peerDependencies": { + "@backtrace/sdk-core": "^0.2.0" + } + }, "tools/cli": { "name": "@backtrace/javascript-cli", "version": "0.3.2", From 9d4b34f974001d0d99ccbcce84475495f8698f4f Mon Sep 17 00:00:00 2001 From: Sebastian Alex Date: Thu, 4 Apr 2024 12:24:05 +0000 Subject: [PATCH 08/12] session-replay: extract options to separate file, add inspect callback --- .../src/BacktraceSessionRecorder.ts | 243 ++---------------- .../src/BacktraceSessionReplayModule.ts | 3 +- packages/session-replay/src/options.ts | 208 +++++++++++++++ 3 files changed, 236 insertions(+), 218 deletions(-) create mode 100644 packages/session-replay/src/options.ts diff --git a/packages/session-replay/src/BacktraceSessionRecorder.ts b/packages/session-replay/src/BacktraceSessionRecorder.ts index 3c403ac9..e67f5360 100644 --- a/packages/session-replay/src/BacktraceSessionRecorder.ts +++ b/packages/session-replay/src/BacktraceSessionRecorder.ts @@ -1,204 +1,7 @@ import { BacktraceAttachment } from '@backtrace/sdk-core'; -import { MouseInteractions, eventWithTime } from '@rrweb/types'; +import { eventWithTime } from '@rrweb/types'; import { record } from 'rrweb'; -import { MaskInputFn, MaskInputOptions, MaskTextFn } from 'rrweb-snapshot'; -import { recordOptions } from 'rrweb/typings/types'; - -interface BacktraceSessionRecorderSamplingOptions { - /** - * Controls whether mouse movement is recorded. - * @default true - */ - readonly mousemove?: boolean | number; - - /** - * Controls specific mouse interactions. - * - * Can be one of: - * * `true` - all mouse interactions will be recorded - * * `false` - no mouse interactions will be recorded - * * `{[string]: boolean}` - control specific mouse interactions: - * * `MouseUp` - * * `MouseDown` - * * `Click` - * * `ContextMenu` - * * `DblClick` - * * `Focus` - * * `Blur` - * * `TouchStart` - * * `TouchMove_Departed` - * * `TouchEnd` - * * `TouchCancel` - * @default true - * @example - * // Record only MouseUp and ContextMenu interactions: - * mouseInteraction = { - * DblClick: false, - * Blur: false, - * Click: false, - * ContextMenu: true, - * Focus: false, - * MouseDown: false, - * MouseUp: true, - * TouchCancel: false, - * TouchEnd: false, - * TouchMove_Departed: false, - * TouchStart: false, - * }; - */ - readonly mouseInteraction?: - | boolean - | Partial> - | Partial>; - - /** - * Interval of scrolling events in milliseconds (i.e. will not capture more than one event every set time). - * @default undefined - */ - readonly scroll?: number; - - /** - * Interval of media events in milliseconds (i.e. will not capture more than one event every set time). - * @default undefined - */ - readonly media?: number; - - /** - * Capture either `all` or `last` input events. - * - * When set to `last`, only final input will be captured. - * @default "all" - */ - readonly input?: 'all' | 'last'; -} - -export interface BacktraceSessionReplayPrivacyOptions { - /** - * Use a `string` or `RegExp` to configure which elements should be blocked. - * @default "rr-block" - */ - readonly blockClass?: string | RegExp; - - /** - * Use a `string` to configure which selector should be blocked. - * @default undefined - */ - readonly blockSelector?: string; - - /** - * Use a `string` or `RegExp` to configure which elements should be ignored. - * @default "rr-ignore" - */ - readonly ignoreClass?: string; - - /** - * Use a `string` to configure which selector should be ignored. - * @default undefined - */ - readonly ignoreSelector?: string; - - /** - * Array of CSS attributes that should be ignored. - */ - readonly ignoreCSSAttributes?: string; - - /** - * Use a `string` or `RegExp` to configure which elements should be masked. - * @default "rr-mask" - */ - readonly maskTextClass?: string; - - /** - * Use a `string` to configure which selector should be masked. - * @default undefined - */ - readonly maskTextSelector?: string; - - /** - * If `true`, will mask all inputs. - * @default false - */ - readonly maskAllInputs?: boolean; - - /** - * Mask specific kinds of input. - * - * Can be an object with the following keys: - * * `color` - * * `date` - * * `'datetime-local'` - * * `email` - * * `month` - * * `number` - * * `range` - * * `search` - * * `tel` - * * `text` - * * `time` - * * `url` - * * `week` - * * `textarea` - * * `select` - * * `password` - * @default { password: true } - */ - readonly maskInputOptions?: MaskInputOptions; - - /** - * Callback to customize input masking. - * @param text input text to mask - * @param element input HTML element - * @returns masked text - * @default undefined - * @example - * // replace text with letter A repeated for the text length if element has class "mask-a" - * maskInputFn = (text, element) => - * element.classList.contains('mask-a') - * ? 'A'.repeat(text.length) - * : text; - */ - readonly maskInputFn?: MaskInputFn; - - /** - * Callback to customize text masking. - * @param text text to mask - * @returns masked text - * @default undefined - * @example - * // replace text with letter A repeated for the text length - * maskTextFn = (text) => 'A'.repeat(text.length); - */ - readonly maskTextFn?: MaskTextFn; -} - -export interface BacktraceSessionRecorderOptions { - /** - * Maximum recorded event count to be sent with the report. Use `false` to disable the limit. - * @default 100 - */ - readonly maxEventCount?: number | false; - - /** - * Maximum timeframe for recorded events to be sent with the report. Use `false` to disable the limit. - * @default false - */ - readonly maxTime?: number | false; - - /** - * Sampling options. Use those to reduce event count or size. - */ - readonly sampling?: BacktraceSessionRecorderSamplingOptions; - - /** - * Privacy options. Use those to remove confidendial data. - */ - readonly privacy?: BacktraceSessionReplayPrivacyOptions; - - /** - * Options passed to `rrweb.record` function. Refer to `rrweb` documentation for more information. - */ - readonly advancedOptions?: recordOptions; -} +import { BacktraceSessionRecorderOptions } from './options'; function defaultIfNotFalse(value: T | false, defaultValue?: T) { if (value === false) { @@ -209,6 +12,7 @@ function defaultIfNotFalse(value: T | false, defaultValue?: T) { } export class BacktraceSessionRecorder implements BacktraceAttachment { + public readonly name = 'bt-session-replay-0'; public readonly type = 'dynamic'; private readonly _maxEventCount?: number; @@ -225,10 +29,8 @@ export class BacktraceSessionRecorder implements BacktraceAttachment { this._maxTime = defaultIfNotFalse(_options.maxTime, undefined); } - public name = 'bt-session-replay-0'; - public start() { - const stop = record({ + this._stop = record({ ...this._options.advancedOptions, sampling: { mousemove: this._options.sampling?.mousemove, @@ -238,24 +40,10 @@ export class BacktraceSessionRecorder implements BacktraceAttachment { scroll: this._options.sampling?.scroll, ...this._options.advancedOptions, }, - emit: (event, isCheckout) => { - if (isCheckout || !this._events) { - this._previousEvents = this._events; - this._events = []; - } - - this._events.push(event); - console.log(event); - - if (this._options.advancedOptions?.emit) { - this._options.advancedOptions.emit(event as never, isCheckout); - } - }, + emit: (event, isCheckout) => this.onEmit(event, isCheckout), checkoutEveryNth: this._maxEventCount && Math.ceil(this._maxEventCount / 2), checkoutEveryNms: this._maxTime && Math.ceil(this._maxTime / 2), }); - - this._stop = stop; } public stop() { @@ -273,4 +61,25 @@ export class BacktraceSessionRecorder implements BacktraceAttachment { return JSON.stringify(events); } + + private onEmit(event: eventWithTime, isCheckout?: boolean) { + if (isCheckout || !this._events) { + this._previousEvents = this._events; + this._events = []; + } + + if (this._options.privacy?.inspect) { + const inspected = this._options.privacy.inspect(event); + if (!inspected) { + return; + } + event = inspected; + } + + this._events.push(event); + + if (this._options.advancedOptions?.emit) { + this._options.advancedOptions.emit(event as never, isCheckout); + } + } } diff --git a/packages/session-replay/src/BacktraceSessionReplayModule.ts b/packages/session-replay/src/BacktraceSessionReplayModule.ts index f304cafc..dfd988eb 100644 --- a/packages/session-replay/src/BacktraceSessionReplayModule.ts +++ b/packages/session-replay/src/BacktraceSessionReplayModule.ts @@ -1,5 +1,6 @@ import { BacktraceModule, BacktraceModuleBindData } from '@backtrace/sdk-core'; -import { BacktraceSessionRecorder, BacktraceSessionRecorderOptions } from './BacktraceSessionRecorder'; +import { BacktraceSessionRecorder } from './BacktraceSessionRecorder'; +import { BacktraceSessionRecorderOptions } from './options'; /** * Adds session recorder module to `BacktraceClient`. diff --git a/packages/session-replay/src/options.ts b/packages/session-replay/src/options.ts new file mode 100644 index 00000000..3d8bb4e5 --- /dev/null +++ b/packages/session-replay/src/options.ts @@ -0,0 +1,208 @@ +import { MouseInteractions, eventWithTime } from '@rrweb/types'; +import { MaskInputFn, MaskInputOptions, MaskTextFn } from 'rrweb-snapshot'; +import { recordOptions } from 'rrweb/typings/types'; + +export interface BacktraceSessionRecorderSamplingOptions { + /** + * Controls whether mouse movement is recorded. + * @default true + */ + readonly mousemove?: boolean | number; + + /** + * Controls specific mouse interactions. + * + * Can be one of: + * * `true` - all mouse interactions will be recorded + * * `false` - no mouse interactions will be recorded + * * `{[string]: boolean}` - control specific mouse interactions: + * * `MouseUp` + * * `MouseDown` + * * `Click` + * * `ContextMenu` + * * `DblClick` + * * `Focus` + * * `Blur` + * * `TouchStart` + * * `TouchMove_Departed` + * * `TouchEnd` + * * `TouchCancel` + * @default true + * @example + * // Record only MouseUp and ContextMenu interactions: + * mouseInteraction = { + * DblClick: false, + * Blur: false, + * Click: false, + * ContextMenu: true, + * Focus: false, + * MouseDown: false, + * MouseUp: true, + * TouchCancel: false, + * TouchEnd: false, + * TouchMove_Departed: false, + * TouchStart: false, + * }; + */ + readonly mouseInteraction?: + | boolean + | Partial> + | Partial>; + + /** + * Interval of scrolling events in milliseconds (i.e. will not capture more than one event every set time). + * @default undefined + */ + readonly scroll?: number; + + /** + * Interval of media events in milliseconds (i.e. will not capture more than one event every set time). + * @default undefined + */ + readonly media?: number; + + /** + * Capture either `all` or `last` input events. + * + * When set to `last`, only final input will be captured. + * @default "all" + */ + readonly input?: 'all' | 'last'; +} + +export interface BacktraceSessionReplayPrivacyOptions { + /** + * Use a `string` or `RegExp` to configure which elements should be blocked. + * @default "rr-block" + */ + readonly blockClass?: string | RegExp; + + /** + * Use a `string` to configure which selector should be blocked. + * @default undefined + */ + readonly blockSelector?: string; + + /** + * Use a `string` or `RegExp` to configure which elements should be ignored. + * @default "rr-ignore" + */ + readonly ignoreClass?: string; + + /** + * Use a `string` to configure which selector should be ignored. + * @default undefined + */ + readonly ignoreSelector?: string; + + /** + * Array of CSS attributes that should be ignored. + */ + readonly ignoreCSSAttributes?: string; + + /** + * Use a `string` or `RegExp` to configure which elements should be masked. + * @default "rr-mask" + */ + readonly maskTextClass?: string; + + /** + * Use a `string` to configure which selector should be masked. + * @default undefined + */ + readonly maskTextSelector?: string; + + /** + * If `true`, will mask all inputs. + * @default false + */ + readonly maskAllInputs?: boolean; + + /** + * Mask specific kinds of input. + * + * Can be an object with the following keys: + * * `color` + * * `date` + * * `'datetime-local'` + * * `email` + * * `month` + * * `number` + * * `range` + * * `search` + * * `tel` + * * `text` + * * `time` + * * `url` + * * `week` + * * `textarea` + * * `select` + * * `password` + * @default { password: true } + */ + readonly maskInputOptions?: MaskInputOptions; + + /** + * Callback to customize input masking. + * @param text input text to mask + * @param element input HTML element + * @returns masked text + * @default undefined + * @example + * // replace text with letter A repeated for the text length if element has class "mask-a" + * maskInputFn = (text, element) => + * element.classList.contains('mask-a') + * ? 'A'.repeat(text.length) + * : text; + */ + readonly maskInputFn?: MaskInputFn; + + /** + * Callback to customize text masking. + * @param text text to mask + * @returns masked text + * @default undefined + * @example + * // replace text with letter A repeated for the text length + * maskTextFn = (text) => 'A'.repeat(text.length); + */ + readonly maskTextFn?: MaskTextFn; + + /** + * Callback to inspect the added event. You must return an event for it to be included. + * + * Return `undefined` to skip this event. + * @param event Event to be added to the report. + * @returns modified event or `undefined` + */ + readonly inspect?: (event: eventWithTime) => eventWithTime | undefined; +} + +export interface BacktraceSessionRecorderOptions { + /** + * Maximum recorded event count to be sent with the report. Use `false` to disable the limit. + * @default 100 + */ + readonly maxEventCount?: number | false; + + /** + * Maximum timeframe for recorded events to be sent with the report. Use `false` to disable the limit. + * @default false + */ + readonly maxTime?: number | false; + + /** + * Sampling options. Use those to reduce event count or size. + */ + readonly sampling?: BacktraceSessionRecorderSamplingOptions; + + /** + * Privacy options. Use those to remove confidendial data. + */ + readonly privacy?: BacktraceSessionReplayPrivacyOptions; + + /** + * Options passed to `rrweb.record` function. Refer to `rrweb` documentation for more information. + */ + readonly advancedOptions?: recordOptions; +} From e4123b14dc3e180a47596ba4f2b9990753be03d3 Mon Sep 17 00:00:00 2001 From: Sebastian Alex Date: Thu, 4 Apr 2024 12:25:17 +0000 Subject: [PATCH 09/12] session-replay: update description --- packages/session-replay/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/session-replay/package.json b/packages/session-replay/package.json index 329c4ae8..6b314a2a 100644 --- a/packages/session-replay/package.json +++ b/packages/session-replay/package.json @@ -1,7 +1,7 @@ { "name": "@backtrace/session-replay", "version": "0.0.1", - "description": "", + "description": "Backtrace-JavaScript Session Replay module", "main": "lib/index.js", "types": "lib/index.d.ts", "scripts": { From 93d0a8c2d8395ef37eca8fab90639c327a0d06e2 Mon Sep 17 00:00:00 2001 From: Sebastian Alex Date: Thu, 4 Apr 2024 12:25:55 +0000 Subject: [PATCH 10/12] session-replay: remove test script --- packages/session-replay/package.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/session-replay/package.json b/packages/session-replay/package.json index 6b314a2a..7a7e7e86 100644 --- a/packages/session-replay/package.json +++ b/packages/session-replay/package.json @@ -9,8 +9,7 @@ "clean": "tsc -b --clean && rimraf \"lib\"", "format": "prettier --write '**/*.ts'", "lint": "eslint . --ext .ts", - "watch": "tsc -w", - "test": "NODE_ENV=test jest" + "watch": "tsc -w" }, "repository": { "type": "git", From b7964615e79d76b1c0fa48305042a5244b7fd884 Mon Sep 17 00:00:00 2001 From: Sebastian Alex Date: Fri, 5 Apr 2024 11:43:26 +0000 Subject: [PATCH 11/12] session-replay: remove unnecessary check from BacktraceSessionRecorder.get --- packages/session-replay/src/BacktraceSessionRecorder.ts | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/packages/session-replay/src/BacktraceSessionRecorder.ts b/packages/session-replay/src/BacktraceSessionRecorder.ts index e67f5360..4637b0ef 100644 --- a/packages/session-replay/src/BacktraceSessionRecorder.ts +++ b/packages/session-replay/src/BacktraceSessionRecorder.ts @@ -53,12 +53,7 @@ export class BacktraceSessionRecorder implements BacktraceAttachment { } public get(): string { - let events = [...(this._events ?? []), ...(this._previousEvents ?? [])]; - if (this._options.maxTime) { - const cutout = Date.now() - this._options.maxTime; - events = events.filter((e) => e.timestamp >= cutout); - } - + const events = [...(this._events ?? []), ...(this._previousEvents ?? [])]; return JSON.stringify(events); } From 984a380687d57c06b401374c65184ac16c06d452 Mon Sep 17 00:00:00 2001 From: Sebastian Alex Date: Tue, 9 Apr 2024 11:52:48 +0000 Subject: [PATCH 12/12] session-replay: add separate options for disabling limits --- .../src/BacktraceSessionRecorder.ts | 12 ++------- packages/session-replay/src/options.ts | 26 ++++++++++++++++--- 2 files changed, 24 insertions(+), 14 deletions(-) diff --git a/packages/session-replay/src/BacktraceSessionRecorder.ts b/packages/session-replay/src/BacktraceSessionRecorder.ts index 4637b0ef..e9aa0a08 100644 --- a/packages/session-replay/src/BacktraceSessionRecorder.ts +++ b/packages/session-replay/src/BacktraceSessionRecorder.ts @@ -3,14 +3,6 @@ import { eventWithTime } from '@rrweb/types'; import { record } from 'rrweb'; import { BacktraceSessionRecorderOptions } from './options'; -function defaultIfNotFalse(value: T | false, defaultValue?: T) { - if (value === false) { - return undefined; - } - - return value ?? defaultValue; -} - export class BacktraceSessionRecorder implements BacktraceAttachment { public readonly name = 'bt-session-replay-0'; public readonly type = 'dynamic'; @@ -25,8 +17,8 @@ export class BacktraceSessionRecorder implements BacktraceAttachment { constructor(private readonly _options: BacktraceSessionRecorderOptions) { this._events = []; - this._maxEventCount = defaultIfNotFalse(_options.maxEventCount, 100); - this._maxTime = defaultIfNotFalse(_options.maxTime, undefined); + this._maxEventCount = !_options.disableMaxEventCount ? _options.maxEventCount ?? 100 : undefined; + this._maxTime = !_options.disableMaxTime ? _options.maxTime : undefined; } public start() { diff --git a/packages/session-replay/src/options.ts b/packages/session-replay/src/options.ts index 3d8bb4e5..586a1dc9 100644 --- a/packages/session-replay/src/options.ts +++ b/packages/session-replay/src/options.ts @@ -180,16 +180,34 @@ export interface BacktraceSessionReplayPrivacyOptions { export interface BacktraceSessionRecorderOptions { /** - * Maximum recorded event count to be sent with the report. Use `false` to disable the limit. + * Maximum recorded event count to be sent with the report. + * + * Set `disableMaxEventCount` to `true` to disable the limit. * @default 100 */ - readonly maxEventCount?: number | false; + readonly maxEventCount?: number; + + /** + * Disables `maxEventCount` limit. + * + * @default false + */ + readonly disableMaxEventCount?: boolean; /** - * Maximum timeframe for recorded events to be sent with the report. Use `false` to disable the limit. + * Maximum timeframe for recorded events to be sent with the report. + * + * Set `disableMaxTime` to `true` to disable the limit. + * @default undefined + */ + readonly maxTime?: number; + + /** + * Disables `maxEventCount` limit. + * * @default false */ - readonly maxTime?: number | false; + readonly disableMaxTime?: boolean; /** * Sampling options. Use those to reduce event count or size.