@spotify-confidence/csr-recorder 0.17.17 → 0.17.18

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,19 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.17.18](https://github.com/spotify/confidence-sdk-js/compare/csr-recorder-v0.17.17...csr-recorder-v0.17.18) (2026-09-09)
4
+
5
+
6
+ ### 🐛 Bug Fixes
7
+
8
+ * strip query and hash from recording URLs ([#452](https://github.com/spotify/confidence-sdk-js/issues/452)) ([1ce37f3](https://github.com/spotify/confidence-sdk-js/commit/1ce37f3f43c05bfea4a90839a6de42314096bb40))
9
+
10
+
11
+ ### Dependencies
12
+
13
+ * The following workspace dependencies were updated
14
+ * dependencies
15
+ * @spotify-confidence/csr-common bumped to 0.18.10
16
+
3
17
  ## [0.17.17](https://github.com/spotify/confidence-sdk-js/compare/csr-recorder-v0.17.16...csr-recorder-v0.17.17) (2026-09-07)
4
18
 
5
19
 
package/README.md CHANGED
@@ -53,7 +53,7 @@ const stop = record(event => {}, {
53
53
  });
54
54
  ```
55
55
 
56
- The same parameterization is also applied to the `href` in rrweb Meta events.
56
+ The same parameterization is also applied to the `href` in rrweb Meta events. Query strings and fragments are removed from Meta event URLs and the session's document referrer before recording.
57
57
 
58
58
  ## rrweb version
59
59
 
package/dist/index.cjs CHANGED
@@ -221,12 +221,13 @@ var Recorder = class Recorder {
221
221
  };
222
222
  }
223
223
  parameterizeHref(href) {
224
+ const strippedHref = (0, _spotify_confidence_csr_common.stripUrlQueryAndHash)(href);
224
225
  try {
225
- const url = new URL(href);
226
+ const url = new URL(strippedHref);
226
227
  url.pathname = this.parameterizeRoute(url.pathname);
227
228
  return url.toString();
228
229
  } catch (_e) {
229
- return this.parameterizeRoute(href);
230
+ return this.parameterizeRoute(strippedHref);
230
231
  }
231
232
  }
232
233
  emitRouteChange(from, to, trigger) {
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { RecordingEventType, RecordingPluginName } from "@spotify-confidence/csr-common";
1
+ import { RecordingEventType, RecordingPluginName, stripUrlQueryAndHash } from "@spotify-confidence/csr-common";
2
2
  //#region src/types.ts
3
3
  const DEFAULT_MASK_SELECTORS = ["[data-csr-mask]"];
4
4
  const DEFAULT_BLOCK_SELECTORS = ["[data-csr-block]", "video"];
@@ -220,12 +220,13 @@ var Recorder = class Recorder {
220
220
  };
221
221
  }
222
222
  parameterizeHref(href) {
223
+ const strippedHref = stripUrlQueryAndHash(href);
223
224
  try {
224
- const url = new URL(href);
225
+ const url = new URL(strippedHref);
225
226
  url.pathname = this.parameterizeRoute(url.pathname);
226
227
  return url.toString();
227
228
  } catch (_e) {
228
- return this.parameterizeRoute(href);
229
+ return this.parameterizeRoute(strippedHref);
229
230
  }
230
231
  }
231
232
  emitRouteChange(from, to, trigger) {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@spotify-confidence/csr-recorder",
3
3
  "license": "Apache-2.0",
4
- "version": "0.17.17",
4
+ "version": "0.17.18",
5
5
  "repository": {
6
6
  "type": "git",
7
7
  "url": "https://github.com/spotify/confidence-sdk-js.git",
@@ -35,7 +35,7 @@
35
35
  },
36
36
  "dependencies": {
37
37
  "@rrweb/rrweb-plugin-console-record": "^2.0.1",
38
- "@spotify-confidence/csr-common": "0.18.9",
38
+ "@spotify-confidence/csr-common": "0.18.10",
39
39
  "rrweb": "^2.0.1"
40
40
  },
41
41
  "module": "./dist/index.js",
@@ -234,21 +234,59 @@ describe('Recorder route change capture', () => {
234
234
  recorder.stop();
235
235
  });
236
236
 
237
+ it.each([
238
+ ['https://example.com/users/123?email=alice@example.com#token', 'https://example.com/users/:id'],
239
+ ['https://example.com/users/123#token?email=alice@example.com', 'https://example.com/users/:id'],
240
+ ['https://example.com/users/123?email=alice@example.com', 'https://example.com/users/:id'],
241
+ ['https://example.com/users/123#token', 'https://example.com/users/:id'],
242
+ ['/users/123?email=alice@example.com#token', '/users/:id'],
243
+ ['invalid-url?email=alice@example.com#token', 'invalid-url'],
244
+ ['', ''],
245
+ ])('strips query/hash from Meta href %s even with route capture disabled', (href, expected) => {
246
+ const engine = new MockEngine();
247
+ const event: RecordingEvent = {
248
+ type: RecordingEventType.Meta,
249
+ timestamp: 1,
250
+ data: { href, width: 1920, height: 1080 },
251
+ };
252
+ engine.eventsOnStart = [event];
253
+ const onEvent = vi.fn();
254
+ const recorder = new Recorder({ engine, onEvent });
255
+ recorder.start({ captureRouteChanges: false });
256
+ // Later snapshots must be sanitized too.
257
+ engine.emit(event);
258
+
259
+ expect(onEvent).toHaveBeenCalledTimes(2);
260
+ for (const [recorded] of onEvent.mock.calls) {
261
+ expect(recorded).toEqual({ ...event, data: { ...event.data, href: expected } });
262
+ }
263
+ expect(event.data).toEqual({ href, width: 1920, height: 1080 });
264
+
265
+ recorder.stop();
266
+ });
267
+
237
268
  it('parameterizes meta href using custom parameterizeRoute', () => {
238
269
  const engine = new MockEngine();
239
270
  engine.eventsOnStart = [
240
271
  {
241
272
  type: RecordingEventType.Meta,
242
273
  timestamp: 1,
243
- data: { href: 'https://example.com/teams/acme-corp/dashboard', width: 1920, height: 1080 },
274
+ data: {
275
+ href: 'https://example.com/teams/acme-corp/dashboard?email=alice@example.com#token',
276
+ width: 1920,
277
+ height: 1080,
278
+ },
244
279
  },
245
280
  ];
246
281
  const onEvent = vi.fn();
247
282
  const recorder = new Recorder({ engine, onEvent });
283
+ const parameterizeRoute = vi.fn((route: string) => route.replace(/\/teams\/[^/]+/, '/teams/:slug'));
248
284
  recorder.start({
249
- parameterizeRoute: route => route.replace(/\/teams\/[^/]+/, '/teams/:slug'),
285
+ parameterizeRoute,
250
286
  });
251
287
 
288
+ expect(parameterizeRoute).toHaveBeenCalledWith('/teams/acme-corp/dashboard');
289
+
252
290
  const metaEvents = (onEvent.mock.calls as [RecordingEvent][])
253
291
  .map(([e]) => e)
254
292
  .filter(e => e.type === RecordingEventType.Meta);
package/src/recorder.ts CHANGED
@@ -2,6 +2,7 @@ import {
2
2
  RecordingEvent,
3
3
  RecordingEventType,
4
4
  RecordingPluginName,
5
+ stripUrlQueryAndHash,
5
6
  type TabVisibilityPluginData,
6
7
  type NetworkRequestPluginData,
7
8
  type RouteChangePluginData,
@@ -202,12 +203,13 @@ export class Recorder {
202
203
  }
203
204
 
204
205
  private parameterizeHref(href: string): string {
206
+ const strippedHref = stripUrlQueryAndHash(href);
205
207
  try {
206
- const url = new URL(href);
208
+ const url = new URL(strippedHref);
207
209
  url.pathname = this.parameterizeRoute(url.pathname);
208
210
  return url.toString();
209
211
  } catch (_e) {
210
- return this.parameterizeRoute(href);
212
+ return this.parameterizeRoute(strippedHref);
211
213
  }
212
214
  }
213
215