@spotify-confidence/csr-recorder 0.17.16 → 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,33 @@
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
+
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)
18
+
19
+
20
+ ### 🐛 Bug Fixes
21
+
22
+ * stop passive recording frames extending sessions ([#449](https://github.com/spotify/confidence-sdk-js/issues/449)) ([df763a1](https://github.com/spotify/confidence-sdk-js/commit/df763a10d6f16ce5b871530ca3fe8b5c153c16d7))
23
+
24
+
25
+ ### Dependencies
26
+
27
+ * The following workspace dependencies were updated
28
+ * dependencies
29
+ * @spotify-confidence/csr-common bumped to 0.18.9
30
+
3
31
  ## [0.17.16](https://github.com/spotify/confidence-sdk-js/compare/csr-recorder-v0.17.15...csr-recorder-v0.17.16) (2026-08-28)
4
32
 
5
33
 
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
@@ -105,7 +105,7 @@ var Recorder = class Recorder {
105
105
  if (typeof document !== "undefined") {
106
106
  this.visibilityHandler = () => {
107
107
  const data = {
108
- plugin: "csr:tabVisibility",
108
+ plugin: _spotify_confidence_csr_common.RecordingPluginName.TabVisibility,
109
109
  payload: { hidden: document.hidden }
110
110
  };
111
111
  this.onEvent({
@@ -127,7 +127,7 @@ var Recorder = class Recorder {
127
127
  }
128
128
  emitNetworkRequest(payload) {
129
129
  const data = {
130
- plugin: "csr:networkRequest",
130
+ plugin: _spotify_confidence_csr_common.RecordingPluginName.NetworkRequest,
131
131
  payload
132
132
  };
133
133
  this.onEvent({
@@ -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) {
@@ -235,7 +236,7 @@ var Recorder = class Recorder {
235
236
  const paramTo = this.parameterizeRoute(to);
236
237
  if (paramFrom === paramTo) return;
237
238
  const data = {
238
- plugin: "csr:routeChange",
239
+ plugin: _spotify_confidence_csr_common.RecordingPluginName.RouteChange,
239
240
  payload: {
240
241
  from: paramFrom,
241
242
  to: paramTo,
@@ -11140,7 +11141,7 @@ function labelBlockedElement(node) {
11140
11141
  */
11141
11142
  function blockedElementLabelsPlugin() {
11142
11143
  return {
11143
- name: "csr/blocked-element-labels@1",
11144
+ name: _spotify_confidence_csr_common.RecordingPluginName.BlockedElementLabels,
11144
11145
  options: {},
11145
11146
  observer: () => () => {},
11146
11147
  eventProcessor: (event) => {
@@ -11158,7 +11159,7 @@ function blockedElementLabelsPlugin() {
11158
11159
  function clipboardActionsPlugin() {
11159
11160
  let getId;
11160
11161
  return {
11161
- name: "csr/clipboard@1",
11162
+ name: _spotify_confidence_csr_common.RecordingPluginName.Clipboard,
11162
11163
  options: {},
11163
11164
  getMirror: ({ nodeMirror }) => {
11164
11165
  getId = (node) => nodeMirror.getId(node);
@@ -11190,7 +11191,7 @@ function clipboardActionsPlugin() {
11190
11191
  function clickModifiersPlugin() {
11191
11192
  let pendingClick = null;
11192
11193
  return {
11193
- name: "csr/click-modifiers@1",
11194
+ name: _spotify_confidence_csr_common.RecordingPluginName.ClickModifiers,
11194
11195
  options: {},
11195
11196
  observer: (_callback, win) => {
11196
11197
  const onClick = (event) => {
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { RecordingEventType } 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"];
@@ -104,7 +104,7 @@ var Recorder = class Recorder {
104
104
  if (typeof document !== "undefined") {
105
105
  this.visibilityHandler = () => {
106
106
  const data = {
107
- plugin: "csr:tabVisibility",
107
+ plugin: RecordingPluginName.TabVisibility,
108
108
  payload: { hidden: document.hidden }
109
109
  };
110
110
  this.onEvent({
@@ -126,7 +126,7 @@ var Recorder = class Recorder {
126
126
  }
127
127
  emitNetworkRequest(payload) {
128
128
  const data = {
129
- plugin: "csr:networkRequest",
129
+ plugin: RecordingPluginName.NetworkRequest,
130
130
  payload
131
131
  };
132
132
  this.onEvent({
@@ -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) {
@@ -234,7 +235,7 @@ var Recorder = class Recorder {
234
235
  const paramTo = this.parameterizeRoute(to);
235
236
  if (paramFrom === paramTo) return;
236
237
  const data = {
237
- plugin: "csr:routeChange",
238
+ plugin: RecordingPluginName.RouteChange,
238
239
  payload: {
239
240
  from: paramFrom,
240
241
  to: paramTo,
@@ -11139,7 +11140,7 @@ function labelBlockedElement(node) {
11139
11140
  */
11140
11141
  function blockedElementLabelsPlugin() {
11141
11142
  return {
11142
- name: "csr/blocked-element-labels@1",
11143
+ name: RecordingPluginName.BlockedElementLabels,
11143
11144
  options: {},
11144
11145
  observer: () => () => {},
11145
11146
  eventProcessor: (event) => {
@@ -11157,7 +11158,7 @@ function blockedElementLabelsPlugin() {
11157
11158
  function clipboardActionsPlugin() {
11158
11159
  let getId;
11159
11160
  return {
11160
- name: "csr/clipboard@1",
11161
+ name: RecordingPluginName.Clipboard,
11161
11162
  options: {},
11162
11163
  getMirror: ({ nodeMirror }) => {
11163
11164
  getId = (node) => nodeMirror.getId(node);
@@ -11189,7 +11190,7 @@ function clipboardActionsPlugin() {
11189
11190
  function clickModifiersPlugin() {
11190
11191
  let pendingClick = null;
11191
11192
  return {
11192
- name: "csr/click-modifiers@1",
11193
+ name: RecordingPluginName.ClickModifiers,
11193
11194
  options: {},
11194
11195
  observer: (_callback, win) => {
11195
11196
  const onClick = (event) => {
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.16",
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.8",
38
+ "@spotify-confidence/csr-common": "0.18.10",
39
39
  "rrweb": "^2.0.1"
40
40
  },
41
41
  "module": "./dist/index.js",
@@ -1,6 +1,7 @@
1
1
  // @vitest-environment happy-dom
2
2
 
3
3
  import { describe, it, expect, vi, beforeEach } from 'vitest';
4
+ import { RecordingPluginName } from '@spotify-confidence/csr-common';
4
5
  import { RrwebEngine } from './rrweb-engine';
5
6
 
6
7
  const recordSpy = vi.fn().mockReturnValue(() => {});
@@ -61,7 +62,7 @@ describe('RrwebEngine', () => {
61
62
  it('rebuilds blocked elements as labelled inert placeholders', () => {
62
63
  new RrwebEngine().start({}, () => {});
63
64
  const plugin = recordSpy.mock.calls[0][0].plugins.find(
64
- ({ name }: { name: string }) => name === 'csr/blocked-element-labels@1',
65
+ ({ name }: { name: string }) => name === RecordingPluginName.BlockedElementLabels,
65
66
  );
66
67
  const event = {
67
68
  type: 2,
@@ -120,7 +121,9 @@ describe('RrwebEngine', () => {
120
121
 
121
122
  it('records copy, cut, and paste actions without reading clipboard contents', () => {
122
123
  new RrwebEngine().start({}, () => {});
123
- const plugin = recordSpy.mock.calls[0][0].plugins.find(({ name }: { name: string }) => name === 'csr/clipboard@1');
124
+ const plugin = recordSpy.mock.calls[0][0].plugins.find(
125
+ ({ name }: { name: string }) => name === RecordingPluginName.Clipboard,
126
+ );
124
127
  const getId = vi.fn().mockReturnValue(42);
125
128
  plugin.getMirror({ nodeMirror: { getId } });
126
129
  const callback = vi.fn();
@@ -154,7 +157,7 @@ describe('RrwebEngine', () => {
154
157
  it('keeps native click modifiers through a browser microtask checkpoint', async () => {
155
158
  new RrwebEngine().start({}, () => {});
156
159
  const plugin = recordSpy.mock.calls[0][0].plugins.find(
157
- ({ name }: { name: string }) => name === 'csr/click-modifiers@1',
160
+ ({ name }: { name: string }) => name === RecordingPluginName.ClickModifiers,
158
161
  );
159
162
  const removeObserver = plugin.observer(() => {}, window);
160
163
 
@@ -200,7 +203,7 @@ describe('RrwebEngine', () => {
200
203
  it('does not add stale modifiers to a later click', async () => {
201
204
  new RrwebEngine().start({}, () => {});
202
205
  const plugin = recordSpy.mock.calls[0][0].plugins.find(
203
- ({ name }: { name: string }) => name === 'csr/click-modifiers@1',
206
+ ({ name }: { name: string }) => name === RecordingPluginName.ClickModifiers,
204
207
  );
205
208
  const removeObserver = plugin.observer(() => {}, window);
206
209
  document.dispatchEvent(new MouseEvent('click', { metaKey: true }));
@@ -1,4 +1,4 @@
1
- import { type ConsoleLogLevel, type RecordingEvent } from '@spotify-confidence/csr-common';
1
+ import { RecordingPluginName, type ConsoleLogLevel, type RecordingEvent } from '@spotify-confidence/csr-common';
2
2
  import { RecordingConfig, DEFAULT_MASK_SELECTORS, DEFAULT_BLOCK_SELECTORS } from '../types';
3
3
  import { RecordingEngine } from './index';
4
4
  import { EventType, IncrementalSource, MouseInteractions, record, takeFullSnapshot, type recordOptions } from 'rrweb';
@@ -42,7 +42,7 @@ function labelBlockedElement(node: SerializedNode): void {
42
42
  */
43
43
  function blockedElementLabelsPlugin(): RrwebPlugin {
44
44
  return {
45
- name: 'csr/blocked-element-labels@1',
45
+ name: RecordingPluginName.BlockedElementLabels,
46
46
  options: {},
47
47
  observer: () => () => {},
48
48
  eventProcessor: event => {
@@ -68,7 +68,7 @@ function clipboardActionsPlugin(): RrwebPlugin {
68
68
  let getId: ((node: Node) => number) | undefined;
69
69
 
70
70
  return {
71
- name: 'csr/clipboard@1',
71
+ name: RecordingPluginName.Clipboard,
72
72
  options: {},
73
73
  getMirror: ({ nodeMirror }) => {
74
74
  getId = node => nodeMirror.getId(node);
@@ -99,7 +99,7 @@ function clickModifiersPlugin(): RrwebPlugin {
99
99
  let pendingClick: ClickModifiers | null = null;
100
100
 
101
101
  return {
102
- name: 'csr/click-modifiers@1',
102
+ name: RecordingPluginName.ClickModifiers,
103
103
  options: {},
104
104
  observer: (_callback, win) => {
105
105
  const onClick = (event: Event) => {
@@ -1,6 +1,6 @@
1
1
  // @vitest-environment happy-dom
2
2
  import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
3
- import { RecordingEvent, RecordingEventType, type RouteChangePluginData } from '@spotify-confidence/csr-common';
3
+ import { RecordingEvent, RecordingEventType, RecordingPluginName } from '@spotify-confidence/csr-common';
4
4
  import { Recorder } from './recorder';
5
5
  import { RecordingEngine } from './engine';
6
6
 
@@ -32,10 +32,11 @@ class MockEngine implements RecordingEngine {
32
32
  }
33
33
 
34
34
  function routeChangeEvents(onEvent: ReturnType<typeof vi.fn>) {
35
- return (onEvent.mock.calls as [RecordingEvent][])
36
- .map(([e]) => e)
37
- .filter(e => e.type === RecordingEventType.Plugin && (e.data as RouteChangePluginData).plugin === 'csr:routeChange')
38
- .map(e => (e.data as RouteChangePluginData).payload);
35
+ return onEvent.mock.calls.flatMap(([event]: [RecordingEvent]) =>
36
+ event.type === RecordingEventType.Plugin && event.data.plugin === RecordingPluginName.RouteChange
37
+ ? [event.data.payload]
38
+ : [],
39
+ );
39
40
  }
40
41
 
41
42
  describe('Recorder route change capture', () => {
@@ -233,21 +234,59 @@ describe('Recorder route change capture', () => {
233
234
  recorder.stop();
234
235
  });
235
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
+
236
268
  it('parameterizes meta href using custom parameterizeRoute', () => {
237
269
  const engine = new MockEngine();
238
270
  engine.eventsOnStart = [
239
271
  {
240
272
  type: RecordingEventType.Meta,
241
273
  timestamp: 1,
242
- 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
+ },
243
279
  },
244
280
  ];
245
281
  const onEvent = vi.fn();
246
282
  const recorder = new Recorder({ engine, onEvent });
283
+ const parameterizeRoute = vi.fn((route: string) => route.replace(/\/teams\/[^/]+/, '/teams/:slug'));
247
284
  recorder.start({
248
- parameterizeRoute: route => route.replace(/\/teams\/[^/]+/, '/teams/:slug'),
285
+ parameterizeRoute,
249
286
  });
250
287
 
288
+ expect(parameterizeRoute).toHaveBeenCalledWith('/teams/acme-corp/dashboard');
289
+
251
290
  const metaEvents = (onEvent.mock.calls as [RecordingEvent][])
252
291
  .map(([e]) => e)
253
292
  .filter(e => e.type === RecordingEventType.Meta);
@@ -1,9 +1,16 @@
1
1
  import { describe, it, expect, vi, afterEach } from 'vitest';
2
- import { RecordingEvent, RecordingEventType, type NetworkRequestPluginData } from '@spotify-confidence/csr-common';
2
+ import { RecordingEvent, RecordingEventType, RecordingPluginName } from '@spotify-confidence/csr-common';
3
3
  import { Recorder } from './recorder';
4
4
  import { RecordingEngine } from './engine';
5
5
  import { RecorderState } from './types';
6
6
 
7
+ const networkRequestEvents = (onEvent: ReturnType<typeof vi.fn>) =>
8
+ onEvent.mock.calls.flatMap(([event]: [RecordingEvent]) =>
9
+ event.type === RecordingEventType.Plugin && event.data.plugin === RecordingPluginName.NetworkRequest
10
+ ? [event.data]
11
+ : [],
12
+ );
13
+
7
14
  function makeEvent(timestamp: number): RecordingEvent {
8
15
  return { type: RecordingEventType.Meta, timestamp, data: {} };
9
16
  }
@@ -130,12 +137,10 @@ describe('Recorder network request capture', () => {
130
137
 
131
138
  await globalThis.fetch('https://api.example.com/data');
132
139
 
133
- const pluginEvents = onEvent.mock.calls
134
- .map(([e]: [RecordingEvent]) => e)
135
- .filter(e => e.type === RecordingEventType.Plugin);
136
- expect(pluginEvents).toHaveLength(1);
137
- const data = pluginEvents[0].data as NetworkRequestPluginData;
138
- expect(data.plugin).toBe('csr:networkRequest');
140
+ const events = networkRequestEvents(onEvent);
141
+ expect(events).toHaveLength(1);
142
+ const data = events[0];
143
+ expect(data.plugin).toBe(RecordingPluginName.NetworkRequest);
139
144
  expect(data.payload.initiator).toBe('fetch');
140
145
  expect(data.payload.method).toBe('GET');
141
146
  expect(data.payload.url).toBe('https://api.example.com/data');
@@ -156,11 +161,9 @@ describe('Recorder network request capture', () => {
156
161
 
157
162
  await globalThis.fetch('https://api.example.com/data').catch(() => {});
158
163
 
159
- const pluginEvents = onEvent.mock.calls
160
- .map(([e]: [RecordingEvent]) => e)
161
- .filter(e => e.type === RecordingEventType.Plugin);
162
- expect(pluginEvents).toHaveLength(1);
163
- const data = pluginEvents[0].data as NetworkRequestPluginData;
164
+ const events = networkRequestEvents(onEvent);
165
+ expect(events).toHaveLength(1);
166
+ const data = events[0];
164
167
  expect(data.payload.status).toBe(0);
165
168
 
166
169
  recorder.stop();
@@ -177,10 +180,7 @@ describe('Recorder network request capture', () => {
177
180
 
178
181
  await globalThis.fetch('https://api.example.com/data', { method: 'post' });
179
182
 
180
- const pluginEvents = onEvent.mock.calls
181
- .map(([e]: [RecordingEvent]) => e)
182
- .filter(e => e.type === RecordingEventType.Plugin);
183
- const data = pluginEvents[0].data as NetworkRequestPluginData;
183
+ const data = networkRequestEvents(onEvent)[0];
184
184
  expect(data.payload.method).toBe('POST');
185
185
 
186
186
  recorder.stop();
@@ -197,10 +197,7 @@ describe('Recorder network request capture', () => {
197
197
 
198
198
  await globalThis.fetch(new Request('https://api.example.com/data', { method: 'DELETE' }));
199
199
 
200
- const pluginEvents = onEvent.mock.calls
201
- .map(([e]: [RecordingEvent]) => e)
202
- .filter(e => e.type === RecordingEventType.Plugin);
203
- const data = pluginEvents[0].data as NetworkRequestPluginData;
200
+ const data = networkRequestEvents(onEvent)[0];
204
201
  expect(data.payload.method).toBe('DELETE');
205
202
  expect(data.payload.url).toBe('https://api.example.com/data');
206
203
 
@@ -228,7 +225,7 @@ describe('Recorder network request capture', () => {
228
225
  }),
229
226
  });
230
227
 
231
- const data = onEvent.mock.calls[0][0].data as NetworkRequestPluginData;
228
+ const data = networkRequestEvents(onEvent)[0];
232
229
  expect(data.payload.graphql).toEqual({ operationName: 'GetUser' });
233
230
  expect(JSON.stringify(data.payload)).not.toContain('private-user-id');
234
231
 
package/src/recorder.ts CHANGED
@@ -1,6 +1,8 @@
1
1
  import {
2
2
  RecordingEvent,
3
3
  RecordingEventType,
4
+ RecordingPluginName,
5
+ stripUrlQueryAndHash,
4
6
  type TabVisibilityPluginData,
5
7
  type NetworkRequestPluginData,
6
8
  type RouteChangePluginData,
@@ -54,7 +56,7 @@ export class Recorder {
54
56
  if (typeof document !== 'undefined') {
55
57
  this.visibilityHandler = () => {
56
58
  const data: TabVisibilityPluginData = {
57
- plugin: 'csr:tabVisibility',
59
+ plugin: RecordingPluginName.TabVisibility,
58
60
  payload: { hidden: document.hidden },
59
61
  };
60
62
  this.onEvent({
@@ -86,7 +88,7 @@ export class Recorder {
86
88
 
87
89
  private emitNetworkRequest(payload: NetworkRequestPluginData['payload']): void {
88
90
  const data: NetworkRequestPluginData = {
89
- plugin: 'csr:networkRequest',
91
+ plugin: RecordingPluginName.NetworkRequest,
90
92
  payload,
91
93
  };
92
94
  this.onEvent({
@@ -201,12 +203,13 @@ export class Recorder {
201
203
  }
202
204
 
203
205
  private parameterizeHref(href: string): string {
206
+ const strippedHref = stripUrlQueryAndHash(href);
204
207
  try {
205
- const url = new URL(href);
208
+ const url = new URL(strippedHref);
206
209
  url.pathname = this.parameterizeRoute(url.pathname);
207
210
  return url.toString();
208
211
  } catch (_e) {
209
- return this.parameterizeRoute(href);
212
+ return this.parameterizeRoute(strippedHref);
210
213
  }
211
214
  }
212
215
 
@@ -216,7 +219,7 @@ export class Recorder {
216
219
  const paramTo = this.parameterizeRoute(to);
217
220
  if (paramFrom === paramTo) return;
218
221
  const data: RouteChangePluginData = {
219
- plugin: 'csr:routeChange',
222
+ plugin: RecordingPluginName.RouteChange,
220
223
  payload: { from: paramFrom, to: paramTo, trigger },
221
224
  };
222
225
  this.onEvent({