@groundcover/browser 2.0.0 → 2.1.0-rc.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,31 @@ All notable changes to `@groundcover/browser` are documented here. This project
4
4
  [Semantic Versioning](https://semver.org/spec/v2.0.0.html) and the
5
5
  [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) format.
6
6
 
7
+ ## [2.1.0-rc.0] — 2026-10-06
8
+
9
+ First release candidate of `2.1.0`, on top of `2.0.0`. Adds opt-in canvas capture to session replay;
10
+ nothing changes unless `options.replay.canvas.enabled` is set.
11
+
12
+ ### Added
13
+
14
+ - **Session replay can capture `<canvas>`-rendered content** (e.g. chart data series that replayed
15
+ as empty frames) via `options.replay.canvas`. Off by default. `enabled` maps to rrweb
16
+ `recordCanvas`, `fps` (default 4, clamped to 1–15 with a warning) to `sampling.canvas`, and
17
+ `imageType` / `imageQuality` (default `image/webp` at 0.6) to `dataURLOptions`. Canvas frames are
18
+ pixel snapshots: they increase replay bandwidth and page CPU, and are **not** covered by `privacy`
19
+ text/input masking. WebGL capture is best-effort only (the page's context must use
20
+ `preserveDrawingBuffer: true`).
21
+ - **Changing `replay.canvas` through `updateConfig` restarts the active recording**, like
22
+ `replay.blockedSelectors` and `privacy`. A partial update merges into the current canvas options,
23
+ so `{ canvas: { fps: 8 } }` keeps capture enabled.
24
+
25
+ ### Fixed
26
+
27
+ - **rrweb recorder errors stay inside the SDK.** Observer failures that fire after `record()`
28
+ returns are routed through the SDK's error handler instead of reaching the host page.
29
+ - **`updateConfig` restarts the recording only for keys the update itself sets.** An inherited
30
+ (non-own) `privacy` or `replay` key no longer splits the recording without changing the config.
31
+
7
32
  ## [2.0.0] — 2026-10-06
8
33
 
9
34
  Stable release of the `2.0.0-rc.0` and `2.0.0-rc.1` line. **Contains breaking changes from `1.x`** —
package/README.md CHANGED
@@ -179,17 +179,68 @@ content use [`privacy`](#privacy--data-masking).
179
179
  | Option | Type | Default | Description |
180
180
  |---|---|---|---|
181
181
  | `blockedSelectors` | `string[]` | — | CSS selectors whose elements (and subtrees) are excluded from recording. Useful for noisy extension-injected DOM (e.g. Grammarly). |
182
+ | `canvas` | `ReplayCanvasOptions` | off | **Opt-in** capture of `<canvas>`-rendered content (charts, WebGL widgets). Off by default — see [Canvas capture](#canvas-capture-opt-in) for the fields and tradeoffs. |
182
183
 
183
184
  > ⚠️ Don’t confuse `options.replay.blockedSelectors` (recording noise reduction) with
184
185
  > `options.privacy.replay.*` (rrweb masking callbacks). They live at different levels and serve
185
186
  > different purposes.
186
187
 
188
+ #### Canvas capture (opt-in)
189
+
190
+ By default rrweb only serializes the DOM/SVG around a `<canvas>`, so anything **drawn to a canvas**
191
+ — chart data series, WebGL scenes — is **not** recorded and replays as an empty frame. Set
192
+ `options.replay.canvas.enabled` to capture canvas pixels as periodic image snapshots.
193
+
194
+ ```ts
195
+ groundcover.init({
196
+ // …
197
+ options: {
198
+ enabledEvents: ["replay" /* … */],
199
+ replay: {
200
+ canvas: {
201
+ enabled: true, // default false
202
+ fps: 4, // default 4, clamped to [1, 15]
203
+ imageType: "image/webp", // default 'image/webp' ('image/jpeg' | 'image/png')
204
+ imageQuality: 0.6, // default 0.6 (lossy formats only)
205
+ },
206
+ },
207
+ },
208
+ });
209
+ ```
210
+
211
+ | Field | Type | Default | Maps to rrweb |
212
+ |---|---|---|---|
213
+ | `enabled` | `boolean` | `false` | `recordCanvas` |
214
+ | `fps` | `number` | `4` (clamped `[1, 15]`) | `sampling.canvas` |
215
+ | `imageType` | `'image/webp' \| 'image/jpeg' \| 'image/png'` | `'image/webp'` | `dataURLOptions.type` |
216
+ | `imageQuality` | `number` (0–1) | `0.6` | `dataURLOptions.quality` |
217
+
218
+ > ⚠️ **Canvas capture is expensive and off by default for good reason.** Enable it only when you
219
+ > knowingly accept the tradeoffs:
220
+ > - **Bandwidth** — every snapshot is a serialized image, so replay payloads grow substantially on
221
+ > canvas-heavy pages. Keep `fps` low and prefer lossy `image/webp`/`image/jpeg`.
222
+ > - **CPU** — snapshotting runs on the recorded (end-user) page and adds work to a busy dashboard.
223
+ > - **Privacy** — canvas pixels **bypass `privacy` text/input masking entirely**. Whatever is drawn
224
+ > is captured verbatim; do not enable canvas capture on views that render sensitive data to a
225
+ > canvas.
226
+
227
+ > **WebGL:** capture is **best-effort and not officially supported** by this option. Snapshotting a
228
+ > WebGL/WebGL2 canvas only yields pixels if the page created its context with
229
+ > `preserveDrawingBuffer: true`; otherwise frames may come back blank. 2D canvases (the common chart
230
+ > case) are captured reliably.
231
+
232
+ Changing `replay.canvas` (or `replay.blockedSelectors`) via `updateConfig` restarts the active
233
+ recording so the new options take effect at rrweb `record()` time. A partial `canvas` update merges
234
+ into the current one, so `{ canvas: { fps: 8 } }` keeps capture enabled; pass `enabled: false` to
235
+ turn it off.
236
+
187
237
  ### Updating config at runtime
188
238
 
189
239
  `updateConfig` accepts these optional fields: `cluster`, `environment`, `namespace`, `releaseId`,
190
240
  `dsn`, `apiKey`, `options`, and `user`. `appId` cannot be updated. `sessionId` is not accepted;
191
- use the separate `setSessionId()` API. Nested groups merge one level deep (and `tracing.origin` /
192
- `privacy.replay` one level deeper), so a partial update preserves the other keys in the group:
241
+ use the separate `setSessionId()` API. Nested groups merge one level deep (and `tracing.origin`,
242
+ `privacy.replay` and `replay.canvas` one level deeper), so a partial update preserves the other keys in
243
+ the group:
193
244
 
194
245
  ```ts
195
246
  groundcover.updateConfig({
@@ -320,6 +371,7 @@ record-time masking from your [`privacy`](#privacy--data-masking) config:
320
371
  - `privacy.level` / `privacy.maskSelectors` → rrweb `maskAllInputs` + `maskTextSelector`
321
372
  - `privacy.replay.maskTextFn` / `maskInputFn` → rrweb `maskTextFn` / `maskInputFn`
322
373
  - `options.replay.blockedSelectors` → rrweb `blockSelector` (noise reduction)
374
+ - `options.replay.canvas` → rrweb `recordCanvas` + `sampling.canvas` + `dataURLOptions` (opt-in, off by default — see [Canvas capture](#canvas-capture-opt-in))
323
375
 
324
376
  Replay recording does **not** start automatically — even when `replay` is in `enabledEvents`. You
325
377
  must start it explicitly (e.g. after obtaining user consent):
package/dist/index.d.mts CHANGED
@@ -324,6 +324,44 @@ type EventTypes = {
324
324
  };
325
325
  /** The browser SDK's listener names for `enabledEvents`. */
326
326
  type BrowserEventName = "dom" | "network" | "exceptions" | "logs" | "pageload" | "navigation" | "performance" | "replay";
327
+ /**
328
+ * Opt-in canvas capture for session replay. Charts and other `<canvas>`-drawn
329
+ * content (2D or WebGL) are NOT recorded by default; rrweb only serializes the
330
+ * DOM/SVG around them, so canvas-rendered data series replay as empty frames.
331
+ * Enabling this makes rrweb snapshot canvas pixels.
332
+ *
333
+ * ⚠️ HIGH COST — off by default for good reason:
334
+ * - Bandwidth: canvas frames serialize as images, inflating replay payloads.
335
+ * - CPU: snapshotting canvases is costly on the recorded (customer) page.
336
+ * - Privacy: canvas pixels bypass text/input masking entirely — whatever is
337
+ * drawn is captured verbatim, regardless of the `privacy` config.
338
+ */
339
+ interface ReplayCanvasOptions {
340
+ /**
341
+ * Enable canvas capture. Defaults to `false` (canvas is not recorded). Maps
342
+ * to rrweb's `recordCanvas`.
343
+ */
344
+ enabled?: boolean;
345
+ /**
346
+ * Snapshot frame rate, in frames per second. Defaults to 4 and is clamped to
347
+ * [1, 15]; out-of-range values are clamped with a `console.warn`. Maps to
348
+ * rrweb's `sampling.canvas`. Higher values mean smoother playback but larger
349
+ * payloads and more CPU.
350
+ */
351
+ fps?: number;
352
+ /**
353
+ * Image format for canvas snapshots. Defaults to `'image/webp'` (much smaller
354
+ * than rrweb's default lossless `image/png`). Maps to rrweb
355
+ * `dataURLOptions.type`.
356
+ */
357
+ imageType?: "image/webp" | "image/jpeg" | "image/png";
358
+ /**
359
+ * Lossy compression quality in [0, 1] for `image/webp` / `image/jpeg`.
360
+ * Defaults to 0.6 (ignored for `image/png`). Maps to rrweb
361
+ * `dataURLOptions.quality`.
362
+ */
363
+ imageQuality?: number;
364
+ }
327
365
  /**
328
366
  * Session-replay (rrweb) recording controls. This is NOT a privacy surface —
329
367
  * to mask sensitive content use `privacy`.
@@ -339,6 +377,12 @@ interface ReplayOptions {
339
377
  * sensitive content use `privacy` instead.
340
378
  */
341
379
  blockedSelectors?: string[];
380
+ /**
381
+ * Opt-in canvas capture (chart data series, WebGL-backed widgets). Off by
382
+ * default. Capturing canvas is bandwidth-, CPU-, and privacy-sensitive — see
383
+ * {@link ReplayCanvasOptions}.
384
+ */
385
+ canvas?: ReplayCanvasOptions;
342
386
  }
343
387
  /** Session-recording (rrweb replay) masking — the only place replay can be masked. */
344
388
  interface ReplayMaskingOptions {
@@ -369,8 +413,8 @@ interface SDKOptions extends Omit<SDKOptions$1, "privacy" | "enabledEvents" | "b
369
413
  */
370
414
  privacy?: PrivacyOptions;
371
415
  /**
372
- * Session-replay recording controls (noise reduction). For replay MASKING use
373
- * `privacy` instead. See {@link ReplayOptions}.
416
+ * Session-replay recording controls (noise reduction, opt-in canvas capture).
417
+ * For replay MASKING use `privacy` instead. See {@link ReplayOptions}.
374
418
  */
375
419
  replay?: ReplayOptions;
376
420
  }
package/dist/index.d.ts CHANGED
@@ -324,6 +324,44 @@ type EventTypes = {
324
324
  };
325
325
  /** The browser SDK's listener names for `enabledEvents`. */
326
326
  type BrowserEventName = "dom" | "network" | "exceptions" | "logs" | "pageload" | "navigation" | "performance" | "replay";
327
+ /**
328
+ * Opt-in canvas capture for session replay. Charts and other `<canvas>`-drawn
329
+ * content (2D or WebGL) are NOT recorded by default; rrweb only serializes the
330
+ * DOM/SVG around them, so canvas-rendered data series replay as empty frames.
331
+ * Enabling this makes rrweb snapshot canvas pixels.
332
+ *
333
+ * ⚠️ HIGH COST — off by default for good reason:
334
+ * - Bandwidth: canvas frames serialize as images, inflating replay payloads.
335
+ * - CPU: snapshotting canvases is costly on the recorded (customer) page.
336
+ * - Privacy: canvas pixels bypass text/input masking entirely — whatever is
337
+ * drawn is captured verbatim, regardless of the `privacy` config.
338
+ */
339
+ interface ReplayCanvasOptions {
340
+ /**
341
+ * Enable canvas capture. Defaults to `false` (canvas is not recorded). Maps
342
+ * to rrweb's `recordCanvas`.
343
+ */
344
+ enabled?: boolean;
345
+ /**
346
+ * Snapshot frame rate, in frames per second. Defaults to 4 and is clamped to
347
+ * [1, 15]; out-of-range values are clamped with a `console.warn`. Maps to
348
+ * rrweb's `sampling.canvas`. Higher values mean smoother playback but larger
349
+ * payloads and more CPU.
350
+ */
351
+ fps?: number;
352
+ /**
353
+ * Image format for canvas snapshots. Defaults to `'image/webp'` (much smaller
354
+ * than rrweb's default lossless `image/png`). Maps to rrweb
355
+ * `dataURLOptions.type`.
356
+ */
357
+ imageType?: "image/webp" | "image/jpeg" | "image/png";
358
+ /**
359
+ * Lossy compression quality in [0, 1] for `image/webp` / `image/jpeg`.
360
+ * Defaults to 0.6 (ignored for `image/png`). Maps to rrweb
361
+ * `dataURLOptions.quality`.
362
+ */
363
+ imageQuality?: number;
364
+ }
327
365
  /**
328
366
  * Session-replay (rrweb) recording controls. This is NOT a privacy surface —
329
367
  * to mask sensitive content use `privacy`.
@@ -339,6 +377,12 @@ interface ReplayOptions {
339
377
  * sensitive content use `privacy` instead.
340
378
  */
341
379
  blockedSelectors?: string[];
380
+ /**
381
+ * Opt-in canvas capture (chart data series, WebGL-backed widgets). Off by
382
+ * default. Capturing canvas is bandwidth-, CPU-, and privacy-sensitive — see
383
+ * {@link ReplayCanvasOptions}.
384
+ */
385
+ canvas?: ReplayCanvasOptions;
342
386
  }
343
387
  /** Session-recording (rrweb replay) masking — the only place replay can be masked. */
344
388
  interface ReplayMaskingOptions {
@@ -369,8 +413,8 @@ interface SDKOptions extends Omit<SDKOptions$1, "privacy" | "enabledEvents" | "b
369
413
  */
370
414
  privacy?: PrivacyOptions;
371
415
  /**
372
- * Session-replay recording controls (noise reduction). For replay MASKING use
373
- * `privacy` instead. See {@link ReplayOptions}.
416
+ * Session-replay recording controls (noise reduction, opt-in canvas capture).
417
+ * For replay MASKING use `privacy` instead. See {@link ReplayOptions}.
374
418
  */
375
419
  replay?: ReplayOptions;
376
420
  }