@spotify-confidence/session-recording 0.18.9 → 0.18.11

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
@@ -1,5 +1,24 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.18.11](https://github.com/spotify/confidence-sdk-js/compare/session-recording-v0.18.10...session-recording-v0.18.11) (2026-08-28)
4
+
5
+
6
+ ### Dependencies
7
+
8
+ * The following workspace dependencies were updated
9
+ * dependencies
10
+ * @spotify-confidence/csr-common bumped to 0.18.8
11
+ * @spotify-confidence/csr-recorder bumped to 0.17.16
12
+
13
+ ## [0.18.10](https://github.com/spotify/confidence-sdk-js/compare/session-recording-v0.18.9...session-recording-v0.18.10) (2026-08-20)
14
+
15
+
16
+ ### Dependencies
17
+
18
+ * The following workspace dependencies were updated
19
+ * dependencies
20
+ * @spotify-confidence/csr-recorder bumped to 0.17.15
21
+
3
22
  ## [0.18.9](https://github.com/spotify/confidence-sdk-js/compare/session-recording-v0.18.8...session-recording-v0.18.9) (2026-08-20)
4
23
 
5
24
 
package/dist/index.cjs CHANGED
@@ -21,7 +21,7 @@ function validateMeasureValue(value) {
21
21
  //#endregion
22
22
  //#region ../csr-recorder/src/types.ts
23
23
  const DEFAULT_MASK_SELECTORS = ["[data-csr-mask]"];
24
- const DEFAULT_BLOCK_SELECTORS = ["[data-csr-block]"];
24
+ const DEFAULT_BLOCK_SELECTORS = ["[data-csr-block]", "video"];
25
25
  //#endregion
26
26
  //#region ../csr-recorder/src/route-parameterizer.ts
27
27
  const SEGMENT_PATTERNS = [
@@ -83,6 +83,7 @@ var Recorder = class Recorder {
83
83
  onEvent;
84
84
  state = "idle";
85
85
  visibilityHandler = null;
86
+ pageShowHandler = null;
86
87
  originalFetch = null;
87
88
  originalXhrOpen = null;
88
89
  originalXhrSend = null;
@@ -128,6 +129,12 @@ var Recorder = class Recorder {
128
129
  };
129
130
  document.addEventListener("visibilitychange", this.visibilityHandler);
130
131
  }
132
+ if (typeof window !== "undefined") {
133
+ this.pageShowHandler = (event) => {
134
+ if (event.persisted) this.engine.takeFullSnapshot();
135
+ };
136
+ window.addEventListener("pageshow", this.pageShowHandler);
137
+ }
131
138
  if (config?.captureNetworkRequests) this.patchNetwork();
132
139
  if (config?.captureRouteChanges !== false) this.patchRouting();
133
140
  }
@@ -314,6 +321,10 @@ var Recorder = class Recorder {
314
321
  document.removeEventListener("visibilitychange", this.visibilityHandler);
315
322
  this.visibilityHandler = null;
316
323
  }
324
+ if (this.pageShowHandler) {
325
+ window.removeEventListener("pageshow", this.pageShowHandler);
326
+ this.pageShowHandler = null;
327
+ }
317
328
  this.restoreNetwork();
318
329
  this.restoreRouting();
319
330
  this.engine.stop();
@@ -11127,6 +11138,63 @@ const ALL_CONSOLE_LEVELS = [
11127
11138
  "debug",
11128
11139
  "info"
11129
11140
  ];
11141
+ const BLOCKED_ELEMENT_ATTRIBUTE = "data-csr-blocked-element";
11142
+ function labelBlockedElement(node) {
11143
+ if (node.type === 2 && node.tagName !== void 0 && node.attributes !== void 0 && typeof node.attributes.rr_width === "string" && typeof node.attributes.rr_height === "string") {
11144
+ node.attributes[BLOCKED_ELEMENT_ATTRIBUTE] = node.tagName;
11145
+ node.tagName = "div";
11146
+ }
11147
+ node.childNodes?.forEach(labelBlockedElement);
11148
+ }
11149
+ /**
11150
+ * rrweb strips blocked elements down to their dimensions, but retains their
11151
+ * original tag name. Rebuild them as inert divs and keep the tag name as safe
11152
+ * metadata so players can render a useful placeholder label.
11153
+ */
11154
+ function blockedElementLabelsPlugin() {
11155
+ return {
11156
+ name: "csr/blocked-element-labels@1",
11157
+ options: {},
11158
+ observer: () => () => {},
11159
+ eventProcessor: (event) => {
11160
+ if (event.type === EventType.FullSnapshot) labelBlockedElement(event.data.node);
11161
+ else if (event.type === EventType.IncrementalSnapshot && event.data.source === IncrementalSource.Mutation) event.data.adds.forEach((add) => labelBlockedElement(add.node));
11162
+ return event;
11163
+ }
11164
+ };
11165
+ }
11166
+ /**
11167
+ * Record clipboard actions and their DOM target without reading clipboard
11168
+ * contents. The resulting rrweb Plugin events can explain otherwise
11169
+ * surprising input changes during analysis.
11170
+ */
11171
+ function clipboardActionsPlugin() {
11172
+ let getId;
11173
+ return {
11174
+ name: "csr/clipboard@1",
11175
+ options: {},
11176
+ getMirror: ({ nodeMirror }) => {
11177
+ getId = (node) => nodeMirror.getId(node);
11178
+ },
11179
+ observer: (callback, win) => {
11180
+ const handlers = [
11181
+ "copy",
11182
+ "cut",
11183
+ "paste"
11184
+ ].map((action) => {
11185
+ const handler = (event) => {
11186
+ callback({
11187
+ action,
11188
+ targetId: event.target instanceof win.Node ? getId?.(event.target) ?? -1 : -1
11189
+ });
11190
+ };
11191
+ win.document.addEventListener(action, handler, true);
11192
+ return () => win.document.removeEventListener(action, handler, true);
11193
+ });
11194
+ return () => handlers.forEach((remove) => remove());
11195
+ }
11196
+ };
11197
+ }
11130
11198
  /**
11131
11199
  * rrweb does not include modifier keys in mouse-interaction events. Capture
11132
11200
  * the native click first, then add its safe, non-text metadata to the rrweb
@@ -11179,7 +11247,11 @@ var RrwebEngine = class {
11179
11247
  start(config, onEvent) {
11180
11248
  const maskSelectors = config.maskSelectors ?? DEFAULT_MASK_SELECTORS;
11181
11249
  const blockSelectors = config.blockSelectors ?? DEFAULT_BLOCK_SELECTORS;
11182
- const plugins = [clickModifiersPlugin()];
11250
+ const plugins = [
11251
+ clickModifiersPlugin(),
11252
+ clipboardActionsPlugin(),
11253
+ blockedElementLabelsPlugin()
11254
+ ];
11183
11255
  const { captureConsoleLogs } = config;
11184
11256
  if (captureConsoleLogs) {
11185
11257
  const levels = captureConsoleLogs === true ? ALL_CONSOLE_LEVELS : captureConsoleLogs.levels;
@@ -11201,6 +11273,9 @@ var RrwebEngine = class {
11201
11273
  slimDOMOptions: "all"
11202
11274
  }) ?? null;
11203
11275
  }
11276
+ takeFullSnapshot() {
11277
+ takeFullSnapshot(true);
11278
+ }
11204
11279
  stop() {
11205
11280
  this.stopFn?.();
11206
11281
  this.stopFn = null;
@@ -11251,7 +11326,7 @@ function observeFlags(onFlagWrite) {
11251
11326
  }
11252
11327
  //#endregion
11253
11328
  //#region src/version.ts
11254
- const SDK_VERSION = "0.18.9";
11329
+ const SDK_VERSION = "0.18.11";
11255
11330
  //#endregion
11256
11331
  //#region src/index.ts
11257
11332
  const DEFAULT_API_URL = "https://recording.confidence.dev";
package/dist/index.js CHANGED
@@ -20,7 +20,7 @@ function validateMeasureValue(value) {
20
20
  //#endregion
21
21
  //#region ../csr-recorder/src/types.ts
22
22
  const DEFAULT_MASK_SELECTORS = ["[data-csr-mask]"];
23
- const DEFAULT_BLOCK_SELECTORS = ["[data-csr-block]"];
23
+ const DEFAULT_BLOCK_SELECTORS = ["[data-csr-block]", "video"];
24
24
  //#endregion
25
25
  //#region ../csr-recorder/src/route-parameterizer.ts
26
26
  const SEGMENT_PATTERNS = [
@@ -82,6 +82,7 @@ var Recorder = class Recorder {
82
82
  onEvent;
83
83
  state = "idle";
84
84
  visibilityHandler = null;
85
+ pageShowHandler = null;
85
86
  originalFetch = null;
86
87
  originalXhrOpen = null;
87
88
  originalXhrSend = null;
@@ -127,6 +128,12 @@ var Recorder = class Recorder {
127
128
  };
128
129
  document.addEventListener("visibilitychange", this.visibilityHandler);
129
130
  }
131
+ if (typeof window !== "undefined") {
132
+ this.pageShowHandler = (event) => {
133
+ if (event.persisted) this.engine.takeFullSnapshot();
134
+ };
135
+ window.addEventListener("pageshow", this.pageShowHandler);
136
+ }
130
137
  if (config?.captureNetworkRequests) this.patchNetwork();
131
138
  if (config?.captureRouteChanges !== false) this.patchRouting();
132
139
  }
@@ -313,6 +320,10 @@ var Recorder = class Recorder {
313
320
  document.removeEventListener("visibilitychange", this.visibilityHandler);
314
321
  this.visibilityHandler = null;
315
322
  }
323
+ if (this.pageShowHandler) {
324
+ window.removeEventListener("pageshow", this.pageShowHandler);
325
+ this.pageShowHandler = null;
326
+ }
316
327
  this.restoreNetwork();
317
328
  this.restoreRouting();
318
329
  this.engine.stop();
@@ -11108,6 +11119,63 @@ const ALL_CONSOLE_LEVELS = [
11108
11119
  "debug",
11109
11120
  "info"
11110
11121
  ];
11122
+ const BLOCKED_ELEMENT_ATTRIBUTE = "data-csr-blocked-element";
11123
+ function labelBlockedElement(node) {
11124
+ if (node.type === 2 && node.tagName !== void 0 && node.attributes !== void 0 && typeof node.attributes.rr_width === "string" && typeof node.attributes.rr_height === "string") {
11125
+ node.attributes[BLOCKED_ELEMENT_ATTRIBUTE] = node.tagName;
11126
+ node.tagName = "div";
11127
+ }
11128
+ node.childNodes?.forEach(labelBlockedElement);
11129
+ }
11130
+ /**
11131
+ * rrweb strips blocked elements down to their dimensions, but retains their
11132
+ * original tag name. Rebuild them as inert divs and keep the tag name as safe
11133
+ * metadata so players can render a useful placeholder label.
11134
+ */
11135
+ function blockedElementLabelsPlugin() {
11136
+ return {
11137
+ name: "csr/blocked-element-labels@1",
11138
+ options: {},
11139
+ observer: () => () => {},
11140
+ eventProcessor: (event) => {
11141
+ if (event.type === EventType.FullSnapshot) labelBlockedElement(event.data.node);
11142
+ else if (event.type === EventType.IncrementalSnapshot && event.data.source === IncrementalSource.Mutation) event.data.adds.forEach((add) => labelBlockedElement(add.node));
11143
+ return event;
11144
+ }
11145
+ };
11146
+ }
11147
+ /**
11148
+ * Record clipboard actions and their DOM target without reading clipboard
11149
+ * contents. The resulting rrweb Plugin events can explain otherwise
11150
+ * surprising input changes during analysis.
11151
+ */
11152
+ function clipboardActionsPlugin() {
11153
+ let getId;
11154
+ return {
11155
+ name: "csr/clipboard@1",
11156
+ options: {},
11157
+ getMirror: ({ nodeMirror }) => {
11158
+ getId = (node) => nodeMirror.getId(node);
11159
+ },
11160
+ observer: (callback, win) => {
11161
+ const handlers = [
11162
+ "copy",
11163
+ "cut",
11164
+ "paste"
11165
+ ].map((action) => {
11166
+ const handler = (event) => {
11167
+ callback({
11168
+ action,
11169
+ targetId: event.target instanceof win.Node ? getId?.(event.target) ?? -1 : -1
11170
+ });
11171
+ };
11172
+ win.document.addEventListener(action, handler, true);
11173
+ return () => win.document.removeEventListener(action, handler, true);
11174
+ });
11175
+ return () => handlers.forEach((remove) => remove());
11176
+ }
11177
+ };
11178
+ }
11111
11179
  /**
11112
11180
  * rrweb does not include modifier keys in mouse-interaction events. Capture
11113
11181
  * the native click first, then add its safe, non-text metadata to the rrweb
@@ -11160,7 +11228,11 @@ var RrwebEngine = class {
11160
11228
  start(config, onEvent) {
11161
11229
  const maskSelectors = config.maskSelectors ?? DEFAULT_MASK_SELECTORS;
11162
11230
  const blockSelectors = config.blockSelectors ?? DEFAULT_BLOCK_SELECTORS;
11163
- const plugins = [clickModifiersPlugin()];
11231
+ const plugins = [
11232
+ clickModifiersPlugin(),
11233
+ clipboardActionsPlugin(),
11234
+ blockedElementLabelsPlugin()
11235
+ ];
11164
11236
  const { captureConsoleLogs } = config;
11165
11237
  if (captureConsoleLogs) {
11166
11238
  const levels = captureConsoleLogs === true ? ALL_CONSOLE_LEVELS : captureConsoleLogs.levels;
@@ -11182,6 +11254,9 @@ var RrwebEngine = class {
11182
11254
  slimDOMOptions: "all"
11183
11255
  }) ?? null;
11184
11256
  }
11257
+ takeFullSnapshot() {
11258
+ takeFullSnapshot(true);
11259
+ }
11185
11260
  stop() {
11186
11261
  this.stopFn?.();
11187
11262
  this.stopFn = null;
@@ -11232,7 +11307,7 @@ function observeFlags(onFlagWrite) {
11232
11307
  }
11233
11308
  //#endregion
11234
11309
  //#region src/version.ts
11235
- const SDK_VERSION = "0.18.9";
11310
+ const SDK_VERSION = "0.18.11";
11236
11311
  //#endregion
11237
11312
  //#region src/index.ts
11238
11313
  const DEFAULT_API_URL = "https://recording.confidence.dev";
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@spotify-confidence/session-recording",
3
3
  "license": "Apache-2.0",
4
- "version": "0.18.9",
4
+ "version": "0.18.11",
5
5
  "repository": {
6
6
  "type": "git",
7
7
  "url": "https://github.com/spotify/confidence-sdk-js.git",
@@ -41,8 +41,8 @@
41
41
  }
42
42
  },
43
43
  "dependencies": {
44
- "@spotify-confidence/csr-common": "0.18.7",
45
- "@spotify-confidence/csr-recorder": "0.17.14"
44
+ "@spotify-confidence/csr-common": "0.18.8",
45
+ "@spotify-confidence/csr-recorder": "0.17.16"
46
46
  },
47
47
  "module": "./dist/index.js",
48
48
  "exports": {
package/src/version.ts CHANGED
@@ -1 +1 @@
1
- export const SDK_VERSION = '0.18.9';
1
+ export const SDK_VERSION = '0.18.11';