@amplitude/session-replay-browser 1.47.0-sr-trc-debug-log.1 → 1.47.0-sr-trc-debug-log.2

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.
Files changed (43) hide show
  1. package/lib/cjs/diagnostics.d.ts +5 -0
  2. package/lib/cjs/diagnostics.d.ts.map +1 -1
  3. package/lib/cjs/diagnostics.js +11 -1
  4. package/lib/cjs/diagnostics.js.map +1 -1
  5. package/lib/cjs/plugins/url-tracking-plugin.d.ts +6 -0
  6. package/lib/cjs/plugins/url-tracking-plugin.d.ts.map +1 -1
  7. package/lib/cjs/plugins/url-tracking-plugin.js +3 -1
  8. package/lib/cjs/plugins/url-tracking-plugin.js.map +1 -1
  9. package/lib/cjs/session-replay.d.ts +7 -0
  10. package/lib/cjs/session-replay.d.ts.map +1 -1
  11. package/lib/cjs/session-replay.js +117 -24
  12. package/lib/cjs/session-replay.js.map +1 -1
  13. package/lib/cjs/targeting/targeting-manager.d.ts.map +1 -1
  14. package/lib/cjs/targeting/targeting-manager.js +26 -17
  15. package/lib/cjs/targeting/targeting-manager.js.map +1 -1
  16. package/lib/cjs/version.d.ts +1 -1
  17. package/lib/cjs/version.js +1 -1
  18. package/lib/cjs/version.js.map +1 -1
  19. package/lib/esm/diagnostics.d.ts +5 -0
  20. package/lib/esm/diagnostics.d.ts.map +1 -1
  21. package/lib/esm/diagnostics.js +11 -1
  22. package/lib/esm/diagnostics.js.map +1 -1
  23. package/lib/esm/plugins/url-tracking-plugin.d.ts +6 -0
  24. package/lib/esm/plugins/url-tracking-plugin.d.ts.map +1 -1
  25. package/lib/esm/plugins/url-tracking-plugin.js +3 -1
  26. package/lib/esm/plugins/url-tracking-plugin.js.map +1 -1
  27. package/lib/esm/session-replay.d.ts +7 -0
  28. package/lib/esm/session-replay.d.ts.map +1 -1
  29. package/lib/esm/session-replay.js +117 -24
  30. package/lib/esm/session-replay.js.map +1 -1
  31. package/lib/esm/targeting/targeting-manager.d.ts.map +1 -1
  32. package/lib/esm/targeting/targeting-manager.js +26 -17
  33. package/lib/esm/targeting/targeting-manager.js.map +1 -1
  34. package/lib/esm/version.d.ts +1 -1
  35. package/lib/esm/version.js +1 -1
  36. package/lib/esm/version.js.map +1 -1
  37. package/lib/scripts/index-min.js +1 -1
  38. package/lib/scripts/index-min.js.gz +0 -0
  39. package/lib/scripts/index-min.js.map +1 -1
  40. package/lib/scripts/session-replay-browser-min.js +1 -1
  41. package/lib/scripts/session-replay-browser-min.js.gz +0 -0
  42. package/lib/scripts/session-replay-browser-min.js.map +1 -1
  43. package/package.json +3 -3
@@ -10,11 +10,13 @@
10
10
  export declare const SR_DIAGNOSTIC_PREFIX = "sr.trc";
11
11
  export declare const SrDiagnostic: {
12
12
  readonly init: "sr.trc.init";
13
+ readonly sessionChanged: "sr.trc.session.changed";
13
14
  readonly configSource: (source: string) => string;
14
15
  readonly configHasTargeting: "sr.trc.config.has_targeting";
15
16
  readonly configNoTargeting: "sr.trc.config.no_targeting";
16
17
  readonly configFetchFailed: "sr.trc.config.fetch_failed";
17
18
  readonly configReceived: "sr.trc.config.received";
19
+ readonly targetingTrigger: "sr.trc.targeting.trigger";
18
20
  readonly evalTrigger: (trigger: string) => string;
19
21
  readonly evalNoConfig: "sr.trc.eval.no_config";
20
22
  readonly evalMissingPrereq: "sr.trc.eval.missing_prereq";
@@ -39,5 +41,8 @@ export declare const SrDiagnostic: {
39
41
  readonly decision: "sr.trc.decision";
40
42
  readonly urlChange: "sr.trc.url_change";
41
43
  readonly urlChangeEvent: "sr.trc.url_change";
44
+ readonly urlListenerSetup: "sr.trc.url_listener.setup";
45
+ readonly urlListenerAttached: "sr.trc.url_listener.attached";
46
+ readonly urlListenerSkipped: "sr.trc.url_listener.skipped";
42
47
  };
43
48
  //# sourceMappingURL=diagnostics.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"diagnostics.d.ts","sourceRoot":"","sources":["../../src/diagnostics.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,eAAO,MAAM,oBAAoB,WAAW,CAAC;AAI7C,eAAO,MAAM,YAAY;;oCAKA,MAAM;;;;;oCAQN,MAAM;;;;;;;;;;;;;;;;;;;;;;;;CA8BrB,CAAC"}
1
+ {"version":3,"file":"diagnostics.d.ts","sourceRoot":"","sources":["../../src/diagnostics.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,eAAO,MAAM,oBAAoB,WAAW,CAAC;AAI7C,eAAO,MAAM,YAAY;;;oCAUA,MAAM;;;;;;oCAUN,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;CAmCrB,CAAC"}
@@ -15,12 +15,17 @@ var p = exports.SR_DIAGNOSTIC_PREFIX;
15
15
  exports.SrDiagnostic = {
16
16
  // ── Init (Q1: did init even happen?) ─────────────────────────────────────
17
17
  init: "".concat(p, ".init"),
18
+ // ── Session lifecycle ────────────────────────────────────────────────────
19
+ // SR session id changed (timeout / explicit setSessionId / custom session id). New tabs and
20
+ // page refreshes do NOT change the session id, so this is distinct from a fresh init.
21
+ sessionChanged: "".concat(p, ".session.changed"),
18
22
  // ── Remote config fetch (joined-config) ──────────────────────────────────
19
23
  configSource: function (source) { return "".concat(p, ".config.source.").concat(source); },
20
24
  configHasTargeting: "".concat(p, ".config.has_targeting"),
21
25
  configNoTargeting: "".concat(p, ".config.no_targeting"),
22
26
  configFetchFailed: "".concat(p, ".config.fetch_failed"),
23
27
  configReceived: "".concat(p, ".config.received"),
28
+ targetingTrigger: "".concat(p, ".targeting.trigger"),
24
29
  // ── Targeting evaluation (evaluateTargetingAndCapture) ────────────────────
25
30
  // Q2 (did eval run?), Q3 (working/failed?), Q4 (missing value?), Q5 (all params).
26
31
  evalTrigger: function (trigger) { return "".concat(p, ".eval.").concat(trigger); },
@@ -49,6 +54,11 @@ exports.SrDiagnostic = {
49
54
  decision: "".concat(p, ".decision"),
50
55
  // ── SPA URL change (setupUrlChangeListener) ──────────────────────────────
51
56
  urlChange: "".concat(p, ".url_change"),
52
- urlChangeEvent: "".concat(p, ".url_change"), // event (with props); same name, logs vs metric
57
+ urlChangeEvent: "".concat(p, ".url_change"),
58
+ // Was the URL-change listener even wired up? (covers "the SDK never saw any navigation"
59
+ // because the listener was never attached — e.g. no targeting config, or no global scope.)
60
+ urlListenerSetup: "".concat(p, ".url_listener.setup"),
61
+ urlListenerAttached: "".concat(p, ".url_listener.attached"),
62
+ urlListenerSkipped: "".concat(p, ".url_listener.skipped"), // counter + event: listener NOT attached (with reason)
53
63
  };
54
64
  //# sourceMappingURL=diagnostics.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"diagnostics.js","sourceRoot":"","sources":["../../src/diagnostics.ts"],"names":[],"mappings":";;;AAAA;;;;;;;;GAQG;AACU,QAAA,oBAAoB,GAAG,QAAQ,CAAC;AAE7C,IAAM,CAAC,GAAG,4BAAoB,CAAC;AAElB,QAAA,YAAY,GAAG;IAC1B,4EAA4E;IAC5E,IAAI,EAAE,UAAG,CAAC,UAAO;IAEjB,4EAA4E;IAC5E,YAAY,EAAE,UAAC,MAAc,IAAK,OAAA,UAAG,CAAC,4BAAkB,MAAM,CAAE,EAA9B,CAA8B;IAChE,kBAAkB,EAAE,UAAG,CAAC,0BAAuB;IAC/C,iBAAiB,EAAE,UAAG,CAAC,yBAAsB;IAC7C,iBAAiB,EAAE,UAAG,CAAC,yBAAsB;IAC7C,cAAc,EAAE,UAAG,CAAC,qBAAkB;IAEtC,6EAA6E;IAC7E,kFAAkF;IAClF,WAAW,EAAE,UAAC,OAAe,IAAK,OAAA,UAAG,CAAC,mBAAS,OAAO,CAAE,EAAtB,CAAsB;IACxD,YAAY,EAAE,UAAG,CAAC,oBAAiB;IACnC,iBAAiB,EAAE,UAAG,CAAC,yBAAsB;IAC7C,SAAS,EAAE,UAAG,CAAC,gBAAa;IAC5B,WAAW,EAAE,UAAG,CAAC,mBAAgB;IACjC,SAAS,EAAE,UAAG,CAAC,gBAAa;IAC5B,kBAAkB,EAAE,UAAG,CAAC,0BAAuB;IAC/C,yBAAyB,EAAE,UAAG,CAAC,kCAA+B;IAC9D,cAAc,EAAE,UAAG,CAAC,sBAAmB;IACvC,SAAS,EAAE,UAAG,CAAC,UAAO;IACtB,UAAU,EAAE,UAAG,CAAC,iBAAc;IAE9B,mFAAmF;IACnF,aAAa,EAAE,UAAG,CAAC,oBAAiB;IACpC,gBAAgB,EAAE,UAAG,CAAC,yBAAsB;IAC5C,yBAAyB,EAAE,UAAG,CAAC,kCAA+B;IAE9D,4EAA4E;IAC5E,iBAAiB,EAAE,UAAG,CAAC,yBAAsB;IAC7C,mBAAmB,EAAE,UAAG,CAAC,2BAAwB;IACjD,UAAU,EAAE,UAAG,CAAC,iBAAc;IAC9B,YAAY,EAAE,UAAG,CAAC,oBAAiB;IACnC,cAAc,EAAE,UAAG,CAAC,uBAAoB;IACxC,YAAY,EAAE,UAAG,CAAC,oBAAiB;IACnC,aAAa,EAAE,UAAG,CAAC,qBAAkB;IACrC,QAAQ,EAAE,UAAG,CAAC,cAAW;IAEzB,4EAA4E;IAC5E,SAAS,EAAE,UAAG,CAAC,gBAAa;IAC5B,cAAc,EAAE,UAAG,CAAC,gBAAa,EAAE,gDAAgD;CAC3E,CAAC","sourcesContent":["/**\n * Centralized names for Session Replay diagnostics shipped via the analytics DiagnosticsClient.\n *\n * Every name shares SR_DIAGNOSTIC_PREFIX so they group together in DataDog as\n * `sdk.diagnostics.sr.trc.*`. The diagnostics pipeline prepends `sdk.diagnostics.` and appends\n * `.count` to counters / `.{min,max,avg,count}` to histograms; events are forwarded to DataDog\n * Logs keyed by `event_name`. Keep ALL SR diagnostic names here so the prefix stays unified —\n * changing the namespace is then a one-line edit.\n */\nexport const SR_DIAGNOSTIC_PREFIX = 'sr.trc';\n\nconst p = SR_DIAGNOSTIC_PREFIX;\n\nexport const SrDiagnostic = {\n // ── Init (Q1: did init even happen?) ─────────────────────────────────────\n init: `${p}.init`, // counter + event(with props): fires once per init()\n\n // ── Remote config fetch (joined-config) ──────────────────────────────────\n configSource: (source: string) => `${p}.config.source.${source}`,\n configHasTargeting: `${p}.config.has_targeting`,\n configNoTargeting: `${p}.config.no_targeting`,\n configFetchFailed: `${p}.config.fetch_failed`,\n configReceived: `${p}.config.received`, // event (with props)\n\n // ── Targeting evaluation (evaluateTargetingAndCapture) ────────────────────\n // Q2 (did eval run?), Q3 (working/failed?), Q4 (missing value?), Q5 (all params).\n evalTrigger: (trigger: string) => `${p}.eval.${trigger}`, // init | urlchange | event\n evalNoConfig: `${p}.eval.no_config`,\n evalMissingPrereq: `${p}.eval.missing_prereq`, // Q4: sessionId/config/deviceId missing\n evalMatch: `${p}.eval.match`,\n evalNoMatch: `${p}.eval.no_match`,\n evalError: `${p}.eval.error`, // Q3: targeting engine threw\n evalStaleDiscarded: `${p}.eval.stale_discarded`,\n evalSkippedAlreadyMatched: `${p}.eval.skipped_already_matched`,\n evalDurationMs: `${p}.eval.duration_ms`, // histogram\n evalEvent: `${p}.eval`, // event (with ALL eval params — Q5)\n evalResult: `${p}.eval.result`, // event: raw engine verdict (variantKey) — why match/no-match\n\n // ── Recording execution (getShouldRecord said yes — did rrweb actually start?) ──\n recordStarted: `${p}.record.started`, // event: capture began (carries the srId the replay uploads under)\n recordNoRecordFn: `${p}.record.no_record_fn`, // counter + event: rrweb import returned nothing\n sendSuppressedMinDuration: `${p}.send.suppressed_min_duration`, // counter: events held back by min_session_duration\n\n // ── Record / no-record gate (getShouldRecord) ────────────────────────────\n gateNoIdentifiers: `${p}.gate.no_identifiers`, // Q4: no config/sessionId at gate time\n gateCaptureDisabled: `${p}.gate.capture_disabled`,\n gateOptOut: `${p}.gate.optout`,\n gateTrcMatch: `${p}.gate.trc_match`,\n gateTrcNoMatch: `${p}.gate.trc_no_match`,\n gateSampleIn: `${p}.gate.sample_in`,\n gateSampleOut: `${p}.gate.sample_out`,\n decision: `${p}.decision`, // event (with props)\n\n // ── SPA URL change (setupUrlChangeListener) ──────────────────────────────\n urlChange: `${p}.url_change`,\n urlChangeEvent: `${p}.url_change`, // event (with props); same name, logs vs metric\n} as const;\n"]}
1
+ {"version":3,"file":"diagnostics.js","sourceRoot":"","sources":["../../src/diagnostics.ts"],"names":[],"mappings":";;;AAAA;;;;;;;;GAQG;AACU,QAAA,oBAAoB,GAAG,QAAQ,CAAC;AAE7C,IAAM,CAAC,GAAG,4BAAoB,CAAC;AAElB,QAAA,YAAY,GAAG;IAC1B,4EAA4E;IAC5E,IAAI,EAAE,UAAG,CAAC,UAAO;IAEjB,4EAA4E;IAC5E,4FAA4F;IAC5F,sFAAsF;IACtF,cAAc,EAAE,UAAG,CAAC,qBAAkB;IAEtC,4EAA4E;IAC5E,YAAY,EAAE,UAAC,MAAc,IAAK,OAAA,UAAG,CAAC,4BAAkB,MAAM,CAAE,EAA9B,CAA8B;IAChE,kBAAkB,EAAE,UAAG,CAAC,0BAAuB;IAC/C,iBAAiB,EAAE,UAAG,CAAC,yBAAsB;IAC7C,iBAAiB,EAAE,UAAG,CAAC,yBAAsB;IAC7C,cAAc,EAAE,UAAG,CAAC,qBAAkB;IAEtC,gBAAgB,EAAE,UAAG,CAAC,uBAAoB;IAE1C,6EAA6E;IAC7E,kFAAkF;IAClF,WAAW,EAAE,UAAC,OAAe,IAAK,OAAA,UAAG,CAAC,mBAAS,OAAO,CAAE,EAAtB,CAAsB;IACxD,YAAY,EAAE,UAAG,CAAC,oBAAiB;IACnC,iBAAiB,EAAE,UAAG,CAAC,yBAAsB;IAC7C,SAAS,EAAE,UAAG,CAAC,gBAAa;IAC5B,WAAW,EAAE,UAAG,CAAC,mBAAgB;IACjC,SAAS,EAAE,UAAG,CAAC,gBAAa;IAC5B,kBAAkB,EAAE,UAAG,CAAC,0BAAuB;IAC/C,yBAAyB,EAAE,UAAG,CAAC,kCAA+B;IAC9D,cAAc,EAAE,UAAG,CAAC,sBAAmB;IACvC,SAAS,EAAE,UAAG,CAAC,UAAO;IACtB,UAAU,EAAE,UAAG,CAAC,iBAAc;IAE9B,mFAAmF;IACnF,aAAa,EAAE,UAAG,CAAC,oBAAiB;IACpC,gBAAgB,EAAE,UAAG,CAAC,yBAAsB;IAC5C,yBAAyB,EAAE,UAAG,CAAC,kCAA+B;IAE9D,4EAA4E;IAC5E,iBAAiB,EAAE,UAAG,CAAC,yBAAsB;IAC7C,mBAAmB,EAAE,UAAG,CAAC,2BAAwB;IACjD,UAAU,EAAE,UAAG,CAAC,iBAAc;IAC9B,YAAY,EAAE,UAAG,CAAC,oBAAiB;IACnC,cAAc,EAAE,UAAG,CAAC,uBAAoB;IACxC,YAAY,EAAE,UAAG,CAAC,oBAAiB;IACnC,aAAa,EAAE,UAAG,CAAC,qBAAkB;IACrC,QAAQ,EAAE,UAAG,CAAC,cAAW;IAEzB,4EAA4E;IAC5E,SAAS,EAAE,UAAG,CAAC,gBAAa;IAC5B,cAAc,EAAE,UAAG,CAAC,gBAAa;IACjC,wFAAwF;IACxF,2FAA2F;IAC3F,gBAAgB,EAAE,UAAG,CAAC,wBAAqB;IAC3C,mBAAmB,EAAE,UAAG,CAAC,2BAAwB;IACjD,kBAAkB,EAAE,UAAG,CAAC,0BAAuB,EAAE,uDAAuD;CAChG,CAAC","sourcesContent":["/**\n * Centralized names for Session Replay diagnostics shipped via the analytics DiagnosticsClient.\n *\n * Every name shares SR_DIAGNOSTIC_PREFIX so they group together in DataDog as\n * `sdk.diagnostics.sr.trc.*`. The diagnostics pipeline prepends `sdk.diagnostics.` and appends\n * `.count` to counters / `.{min,max,avg,count}` to histograms; events are forwarded to DataDog\n * Logs keyed by `event_name`. Keep ALL SR diagnostic names here so the prefix stays unified —\n * changing the namespace is then a one-line edit.\n */\nexport const SR_DIAGNOSTIC_PREFIX = 'sr.trc';\n\nconst p = SR_DIAGNOSTIC_PREFIX;\n\nexport const SrDiagnostic = {\n // ── Init (Q1: did init even happen?) ─────────────────────────────────────\n init: `${p}.init`, // counter + event(with props): fires once per init()\n\n // ── Session lifecycle ────────────────────────────────────────────────────\n // SR session id changed (timeout / explicit setSessionId / custom session id). New tabs and\n // page refreshes do NOT change the session id, so this is distinct from a fresh init.\n sessionChanged: `${p}.session.changed`, // event (with from/to)\n\n // ── Remote config fetch (joined-config) ──────────────────────────────────\n configSource: (source: string) => `${p}.config.source.${source}`,\n configHasTargeting: `${p}.config.has_targeting`,\n configNoTargeting: `${p}.config.no_targeting`,\n configFetchFailed: `${p}.config.fetch_failed`,\n configReceived: `${p}.config.received`, // event (with props)\n\n targetingTrigger: `${p}.targeting.trigger`, // event: targeting evaluation triggered\n\n // ── Targeting evaluation (evaluateTargetingAndCapture) ────────────────────\n // Q2 (did eval run?), Q3 (working/failed?), Q4 (missing value?), Q5 (all params).\n evalTrigger: (trigger: string) => `${p}.eval.${trigger}`, // init | urlchange | event\n evalNoConfig: `${p}.eval.no_config`,\n evalMissingPrereq: `${p}.eval.missing_prereq`, // Q4: sessionId/config/deviceId missing\n evalMatch: `${p}.eval.match`,\n evalNoMatch: `${p}.eval.no_match`,\n evalError: `${p}.eval.error`, // Q3: targeting engine threw\n evalStaleDiscarded: `${p}.eval.stale_discarded`,\n evalSkippedAlreadyMatched: `${p}.eval.skipped_already_matched`,\n evalDurationMs: `${p}.eval.duration_ms`, // histogram\n evalEvent: `${p}.eval`, // event (with ALL eval params — Q5)\n evalResult: `${p}.eval.result`, // event: raw engine verdict (variantKey) — why match/no-match\n\n // ── Recording execution (getShouldRecord said yes — did rrweb actually start?) ──\n recordStarted: `${p}.record.started`, // event: capture began (carries the srId the replay uploads under)\n recordNoRecordFn: `${p}.record.no_record_fn`, // counter + event: rrweb import returned nothing\n sendSuppressedMinDuration: `${p}.send.suppressed_min_duration`, // counter: events held back by min_session_duration\n\n // ── Record / no-record gate (getShouldRecord) ────────────────────────────\n gateNoIdentifiers: `${p}.gate.no_identifiers`, // Q4: no config/sessionId at gate time\n gateCaptureDisabled: `${p}.gate.capture_disabled`,\n gateOptOut: `${p}.gate.optout`,\n gateTrcMatch: `${p}.gate.trc_match`,\n gateTrcNoMatch: `${p}.gate.trc_no_match`,\n gateSampleIn: `${p}.gate.sample_in`,\n gateSampleOut: `${p}.gate.sample_out`,\n decision: `${p}.decision`, // event (with props)\n\n // ── SPA URL change (setupUrlChangeListener) ──────────────────────────────\n urlChange: `${p}.url_change`,\n urlChangeEvent: `${p}.url_change`, // event (with props); same name, logs vs metric\n // Was the URL-change listener even wired up? (covers \"the SDK never saw any navigation\"\n // because the listener was never attached — e.g. no targeting config, or no global scope.)\n urlListenerSetup: `${p}.url_listener.setup`, // event: the needsUrlTracking decision + its inputs\n urlListenerAttached: `${p}.url_listener.attached`, // event: subscribeToUrlChanges succeeded (with polling opts)\n urlListenerSkipped: `${p}.url_listener.skipped`, // counter + event: listener NOT attached (with reason)\n} as const;\n"]}
@@ -35,6 +35,12 @@ export interface SubscribeToUrlChangesOptions {
35
35
  enablePolling?: boolean;
36
36
  /** Polling interval in ms when enablePolling is true (default: 1000) */
37
37
  pollingInterval?: number;
38
+ /**
39
+ * Optional debug logger, called once per poll tick (only on the polling path). Lets the SDK
40
+ * confirm in the browser console that the interval is actually firing — useful when verifying
41
+ * that enableUrlChangePolling took effect for an SPA that bypasses the History API.
42
+ */
43
+ log?: (message: string) => void;
38
44
  }
39
45
  /**
40
46
  * Single helper for URL change detection. Supports:
@@ -1 +1 @@
1
- {"version":3,"file":"url-tracking-plugin.d.ts","sourceRoot":"","sources":["../../../src/plugins/url-tracking-plugin.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAEhD,OAAO,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAEtD;;;GAGG;AACH,MAAM,WAAW,cAAc;IAC7B,sEAAsE;IACtE,IAAI,EAAE,MAAM,CAAC;IACb,6BAA6B;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,gCAAgC;IAChC,cAAc,EAAE,MAAM,CAAC;IACvB,+BAA+B;IAC/B,aAAa,EAAE,MAAM,CAAC;IACtB,mCAAmC;IACnC,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;GAEG;AACH,MAAM,WAAW,wBAAwB;IACvC,kEAAkE;IAClE,cAAc,CAAC,EAAE,aAAa,EAAE,CAAC;IACjC,6EAA6E;IAC7E,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,yEAAyE;IACzE,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,8EAA8E;IAC9E,oBAAoB,CAAC,EAAE,OAAO,CAAC;CAChC;AAED,oEAAoE;AACpE,MAAM,WAAW,4BAA4B;IAC3C,2FAA2F;IAC3F,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,wEAAwE;IACxE,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAiBD;;;;;;;;;;;;GAYG;AACH,wBAAgB,qBAAqB,CACnC,WAAW,EAAE,MAAM,GAAG,SAAS,EAC/B,WAAW,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,EACnC,OAAO,GAAE,4BAAiC,GACzC,MAAM,IAAI,CAqGZ;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,uBAAuB,CACrC,OAAO,GAAE,wBAA6B,GACrC,YAAY,CAAC,wBAAwB,CAAC,CAqFxC;AAED;;;GAGG;AACH,eAAO,MAAM,iBAAiB,wCAA4B,CAAC"}
1
+ {"version":3,"file":"url-tracking-plugin.d.ts","sourceRoot":"","sources":["../../../src/plugins/url-tracking-plugin.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAEhD,OAAO,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAEtD;;;GAGG;AACH,MAAM,WAAW,cAAc;IAC7B,sEAAsE;IACtE,IAAI,EAAE,MAAM,CAAC;IACb,6BAA6B;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,gCAAgC;IAChC,cAAc,EAAE,MAAM,CAAC;IACvB,+BAA+B;IAC/B,aAAa,EAAE,MAAM,CAAC;IACtB,mCAAmC;IACnC,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;GAEG;AACH,MAAM,WAAW,wBAAwB;IACvC,kEAAkE;IAClE,cAAc,CAAC,EAAE,aAAa,EAAE,CAAC;IACjC,6EAA6E;IAC7E,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,yEAAyE;IACzE,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,8EAA8E;IAC9E,oBAAoB,CAAC,EAAE,OAAO,CAAC;CAChC;AAED,oEAAoE;AACpE,MAAM,WAAW,4BAA4B;IAC3C,2FAA2F;IAC3F,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,wEAAwE;IACxE,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB;;;;OAIG;IACH,GAAG,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CACjC;AAiBD;;;;;;;;;;;;GAYG;AACH,wBAAgB,qBAAqB,CACnC,WAAW,EAAE,MAAM,GAAG,SAAS,EAC/B,WAAW,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,EACnC,OAAO,GAAE,4BAAiC,GACzC,MAAM,IAAI,CAuGZ;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,uBAAuB,CACrC,OAAO,GAAE,wBAA6B,GACrC,YAAY,CAAC,wBAAwB,CAAC,CAqFxC;AAED;;;GAGG;AACH,eAAO,MAAM,iBAAiB,wCAA4B,CAAC"}
@@ -28,12 +28,14 @@ function subscribeToUrlChanges(globalScope, onUrlChange, options) {
28
28
  return;
29
29
  };
30
30
  }
31
- var _a = options.enablePolling, enablePolling = _a === void 0 ? false : _a, _b = options.pollingInterval, pollingInterval = _b === void 0 ? constants_1.DEFAULT_URL_CHANGE_POLLING_INTERVAL : _b;
31
+ var _a = options.enablePolling, enablePolling = _a === void 0 ? false : _a, _b = options.pollingInterval, pollingInterval = _b === void 0 ? constants_1.DEFAULT_URL_CHANGE_POLLING_INTERVAL : _b, log = options.log;
32
32
  if (enablePolling) {
33
33
  var getHref_1 = function () { var _a; return (_a = globalScope.location.href) !== null && _a !== void 0 ? _a : ''; };
34
34
  var lastHref_1 = getHref_1();
35
35
  var id_1 = globalScope.setInterval(function () {
36
36
  var href = getHref_1();
37
+ // Logged every tick (not just on change) so we can confirm the polling loop is alive.
38
+ log === null || log === void 0 ? void 0 : log("URL polling tick (href=".concat(href, ", changed=").concat(String(href !== lastHref_1), ")."));
37
39
  if (href === lastHref_1) {
38
40
  return;
39
41
  }
@@ -1 +1 @@
1
- {"version":3,"file":"url-tracking-plugin.js","sourceRoot":"","sources":["../../../src/plugins/url-tracking-plugin.ts"],"names":[],"mappings":"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","sourcesContent":["import { getPageUrl } from '../helpers';\nimport { UGCFilterRule } from '../config/types';\nimport { DEFAULT_URL_CHANGE_POLLING_INTERVAL } from '../constants';\nimport { RecordPlugin } from '@amplitude/rrweb-types';\n\n/**\n * Event emitted when URL changes are detected by the plugin\n * Contains the current page URL, title, and viewport dimensions\n */\nexport interface URLChangeEvent {\n /** The current page URL (may be filtered if UGC rules are applied) */\n href: string;\n /** The current page title */\n title: string;\n /** Viewport height in pixels */\n viewportHeight: number;\n /** Viewport width in pixels */\n viewportWidth: number;\n /** The type of URL change event */\n type: string;\n}\n\n/**\n * Configuration options for the URL tracking plugin\n */\nexport interface URLTrackingPluginOptions {\n /** Rules for filtering sensitive URLs (User Generated Content) */\n ugcFilterRules?: UGCFilterRule[];\n /** Whether to use polling instead of history API events for URL detection */\n enablePolling?: boolean;\n /** Interval in milliseconds for polling URL changes (default: 1000ms) */\n pollingInterval?: number;\n /** Whether to capture document title in URL change events (default: false) */\n captureDocumentTitle?: boolean;\n}\n\n/** Options for subscribeToUrlChanges (polling vs history + hash) */\nexport interface SubscribeToUrlChangesOptions {\n /** Use polling instead of history/popstate/hashchange (e.g. when history is unreliable) */\n enablePolling?: boolean;\n /** Polling interval in ms when enablePolling is true (default: 1000) */\n pollingInterval?: number;\n}\n\n/** Patch detection marker to prevent double-patching */\nconst PATCH_MARKER = '__amplitude_url_tracking_patched__';\n\ntype PatchableHistoryMethod<T extends (...args: any[]) => any> = T & { [PATCH_MARKER]?: boolean };\n\ninterface UrlChangeSubscriptionState {\n callbacks: Set<(href: string) => void>;\n onPopStateOrHashChange: () => void;\n notify: () => void;\n listenersAttached: boolean;\n}\n\n/** Per-globalScope subscription state; persists to avoid wrapper stacking across re-subscribe cycles */\nconst urlChangeSubscriptionsByScope = new WeakMap<Window, UrlChangeSubscriptionState>();\n\n/**\n * Single helper for URL change detection. Supports:\n * - History API (pushState/replaceState) + popstate + hashchange (shared patch per scope)\n * - Optional polling (setInterval on location.href)\n *\n * Used by session-replay targeting (re-evaluate on URL change) and the URL tracking plugin\n * (emit rrweb events). Call the returned function to unsubscribe.\n *\n * @param globalScope - window (or equivalent); no-op if undefined\n * @param onUrlChange - called when the URL changes, with the new href\n * @param options - optional polling (default: event-based only)\n * @returns cleanup function to remove this subscription\n */\nexport function subscribeToUrlChanges(\n globalScope: Window | undefined,\n onUrlChange: (href: string) => void,\n options: SubscribeToUrlChangesOptions = {},\n): () => void {\n if (!globalScope?.location) {\n return (): void => {\n return;\n };\n }\n\n const { enablePolling = false, pollingInterval = DEFAULT_URL_CHANGE_POLLING_INTERVAL } = options;\n\n if (enablePolling) {\n const getHref = (): string => globalScope.location.href ?? '';\n let lastHref = getHref();\n const id = globalScope.setInterval(() => {\n const href = getHref();\n if (href === lastHref) {\n return;\n }\n lastHref = href;\n onUrlChange(href);\n }, pollingInterval);\n return (): void => {\n if (id != null) {\n globalScope.clearInterval(id);\n }\n };\n }\n\n let subscriptionState = urlChangeSubscriptionsByScope.get(globalScope);\n if (!subscriptionState) {\n let lastHref: string | undefined = undefined;\n const callbacks = new Set<(href: string) => void>();\n\n const getHref = (): string => globalScope.location.href ?? '';\n\n const notify = (): void => {\n const href = getHref();\n if (lastHref !== undefined && href === lastHref) return;\n lastHref = href;\n callbacks.forEach((c) => c(href));\n };\n\n /**\n * Creates a patched version of history methods (pushState/replaceState)\n * that calls the original method and then emits a URL change event.\n */\n const createHistoryMethodPatch = <T extends typeof history.pushState | typeof history.replaceState>(\n originalMethod: T,\n ) => {\n const patchedMethod = function (this: History, ...args: Parameters<T>) {\n const result = originalMethod.apply(this, args);\n // Read from location.href after history call so we always notify with resolved absolute URL.\n notify();\n return result;\n } as PatchableHistoryMethod<T>;\n\n patchedMethod[PATCH_MARKER] = true;\n return patchedMethod;\n };\n\n const history = globalScope.history;\n if (history?.pushState) {\n const pushState = Reflect.get(history, 'pushState') as PatchableHistoryMethod<History['pushState']>;\n if (!pushState[PATCH_MARKER]) {\n history.pushState = createHistoryMethodPatch(pushState);\n }\n }\n if (history?.replaceState) {\n const replaceState = Reflect.get(history, 'replaceState') as PatchableHistoryMethod<History['replaceState']>;\n if (!replaceState[PATCH_MARKER]) {\n history.replaceState = createHistoryMethodPatch(replaceState);\n }\n }\n\n subscriptionState = {\n callbacks,\n notify,\n onPopStateOrHashChange: () => notify(),\n listenersAttached: false,\n };\n urlChangeSubscriptionsByScope.set(globalScope, subscriptionState);\n }\n\n const state = subscriptionState;\n if (!state.listenersAttached) {\n globalScope.addEventListener('popstate', state.onPopStateOrHashChange);\n globalScope.addEventListener('hashchange', state.onPopStateOrHashChange);\n state.listenersAttached = true;\n }\n\n state.callbacks.add(onUrlChange);\n return (): void => {\n state.callbacks.delete(onUrlChange);\n if (state.callbacks.size === 0) {\n // Do not restore history methods: we are not aware of patches applied by other scripts.\n if (state.listenersAttached) {\n globalScope.removeEventListener('popstate', state.onPopStateOrHashChange);\n globalScope.removeEventListener('hashchange', state.onPopStateOrHashChange);\n state.listenersAttached = false;\n }\n }\n };\n}\n\n/**\n * Creates a URL tracking plugin for rrweb record function\n *\n * This plugin monitors URL changes in the browser and emits events when the URL changes.\n * It supports three tracking modes:\n * 1. Polling (if explicitly enabled) - periodically checks for URL changes\n * 2. History API + Hash routing (default) - patches pushState/replaceState, listens to popstate and hashchange\n * 3. Hash routing only (fallback) - listens to hashchange events when History API is unavailable\n *\n * The plugin handles edge cases gracefully:\n * - Missing or null location objects\n * - Undefined, null, or empty location.href values\n * - Temporal dead zone issues with variable declarations\n * - Consistent URL normalization across all code paths\n *\n * @param options Configuration options for URL tracking\n * @returns RecordPlugin instance that can be used with rrweb\n */\nexport function createUrlTrackingPlugin(\n options: URLTrackingPluginOptions = {},\n): RecordPlugin<URLTrackingPluginOptions> {\n return {\n name: 'amplitude/url-tracking@1',\n observer(cb, globalScope, pluginOptions?: URLTrackingPluginOptions) {\n // Merge options with plugin-level options taking precedence over constructor options\n const config = { ...options, ...pluginOptions };\n const ugcFilterRules = config.ugcFilterRules || [];\n const enablePolling = config.enablePolling ?? false;\n const pollingInterval = config.pollingInterval ?? DEFAULT_URL_CHANGE_POLLING_INTERVAL;\n const captureDocumentTitle = config.captureDocumentTitle ?? false;\n\n // Early return if no global scope is available\n if (!globalScope) {\n return () => {\n // No cleanup needed if no global scope available\n };\n }\n\n // Track the last URL to prevent duplicate events\n // Initialize to undefined to ensure first call always emits an event\n let lastTrackedUrl: string | undefined = undefined;\n\n // Helper functions\n /**\n * Gets the current URL with proper normalization\n * Handles edge cases where location.href might be undefined, null, or empty\n * Ensures consistent behavior across all code paths\n * @returns Normalized URL string (empty string if location unavailable)\n */\n const getCurrentUrl = (): string => {\n if (!globalScope.location) return '';\n return globalScope.location.href || '';\n };\n\n /**\n * Creates a URL change event with current page information\n * Applies UGC filtering if rules are configured\n * Uses getCurrentUrl() for consistent URL normalization\n * @returns URLChangeEvent with current page state\n */\n const createUrlChangeEvent = (): URLChangeEvent => {\n const { innerHeight, innerWidth, document } = globalScope;\n const currentUrl = getCurrentUrl();\n let currentTitle = '';\n if (captureDocumentTitle) {\n currentTitle = document?.title || '';\n }\n\n // Apply UGC filtering if rules are provided, otherwise use original URL\n const filteredUrl = ugcFilterRules.length > 0 ? getPageUrl(currentUrl, ugcFilterRules) : currentUrl;\n\n return {\n href: filteredUrl,\n title: currentTitle,\n viewportHeight: innerHeight,\n viewportWidth: innerWidth,\n type: 'url-change-event',\n };\n };\n\n /**\n * Emits a URL change event if the URL has actually changed\n * Always emits on first call (when lastTrackedUrl is undefined)\n */\n const emitUrlChange = (): void => {\n const currentUrl = getCurrentUrl();\n if (lastTrackedUrl === undefined || currentUrl !== lastTrackedUrl) {\n lastTrackedUrl = currentUrl;\n const event = createUrlChangeEvent();\n cb(event);\n }\n };\n\n // Single helper: history + popstate + hashchange, or polling when enabled\n const unsubscribe = subscribeToUrlChanges(\n globalScope as Window,\n () => emitUrlChange(),\n enablePolling ? { enablePolling: true, pollingInterval } : {},\n );\n emitUrlChange();\n\n return (): void => unsubscribe();\n },\n options,\n };\n}\n\n/**\n * Default URL tracking plugin instance with default options\n * Can be used directly without custom configuration\n */\nexport const urlTrackingPlugin = createUrlTrackingPlugin();\n"]}
1
+ {"version":3,"file":"url-tracking-plugin.js","sourceRoot":"","sources":["../../../src/plugins/url-tracking-plugin.ts"],"names":[],"mappings":"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","sourcesContent":["import { getPageUrl } from '../helpers';\nimport { UGCFilterRule } from '../config/types';\nimport { DEFAULT_URL_CHANGE_POLLING_INTERVAL } from '../constants';\nimport { RecordPlugin } from '@amplitude/rrweb-types';\n\n/**\n * Event emitted when URL changes are detected by the plugin\n * Contains the current page URL, title, and viewport dimensions\n */\nexport interface URLChangeEvent {\n /** The current page URL (may be filtered if UGC rules are applied) */\n href: string;\n /** The current page title */\n title: string;\n /** Viewport height in pixels */\n viewportHeight: number;\n /** Viewport width in pixels */\n viewportWidth: number;\n /** The type of URL change event */\n type: string;\n}\n\n/**\n * Configuration options for the URL tracking plugin\n */\nexport interface URLTrackingPluginOptions {\n /** Rules for filtering sensitive URLs (User Generated Content) */\n ugcFilterRules?: UGCFilterRule[];\n /** Whether to use polling instead of history API events for URL detection */\n enablePolling?: boolean;\n /** Interval in milliseconds for polling URL changes (default: 1000ms) */\n pollingInterval?: number;\n /** Whether to capture document title in URL change events (default: false) */\n captureDocumentTitle?: boolean;\n}\n\n/** Options for subscribeToUrlChanges (polling vs history + hash) */\nexport interface SubscribeToUrlChangesOptions {\n /** Use polling instead of history/popstate/hashchange (e.g. when history is unreliable) */\n enablePolling?: boolean;\n /** Polling interval in ms when enablePolling is true (default: 1000) */\n pollingInterval?: number;\n /**\n * Optional debug logger, called once per poll tick (only on the polling path). Lets the SDK\n * confirm in the browser console that the interval is actually firing — useful when verifying\n * that enableUrlChangePolling took effect for an SPA that bypasses the History API.\n */\n log?: (message: string) => void;\n}\n\n/** Patch detection marker to prevent double-patching */\nconst PATCH_MARKER = '__amplitude_url_tracking_patched__';\n\ntype PatchableHistoryMethod<T extends (...args: any[]) => any> = T & { [PATCH_MARKER]?: boolean };\n\ninterface UrlChangeSubscriptionState {\n callbacks: Set<(href: string) => void>;\n onPopStateOrHashChange: () => void;\n notify: () => void;\n listenersAttached: boolean;\n}\n\n/** Per-globalScope subscription state; persists to avoid wrapper stacking across re-subscribe cycles */\nconst urlChangeSubscriptionsByScope = new WeakMap<Window, UrlChangeSubscriptionState>();\n\n/**\n * Single helper for URL change detection. Supports:\n * - History API (pushState/replaceState) + popstate + hashchange (shared patch per scope)\n * - Optional polling (setInterval on location.href)\n *\n * Used by session-replay targeting (re-evaluate on URL change) and the URL tracking plugin\n * (emit rrweb events). Call the returned function to unsubscribe.\n *\n * @param globalScope - window (or equivalent); no-op if undefined\n * @param onUrlChange - called when the URL changes, with the new href\n * @param options - optional polling (default: event-based only)\n * @returns cleanup function to remove this subscription\n */\nexport function subscribeToUrlChanges(\n globalScope: Window | undefined,\n onUrlChange: (href: string) => void,\n options: SubscribeToUrlChangesOptions = {},\n): () => void {\n if (!globalScope?.location) {\n return (): void => {\n return;\n };\n }\n\n const { enablePolling = false, pollingInterval = DEFAULT_URL_CHANGE_POLLING_INTERVAL, log } = options;\n\n if (enablePolling) {\n const getHref = (): string => globalScope.location.href ?? '';\n let lastHref = getHref();\n const id = globalScope.setInterval(() => {\n const href = getHref();\n // Logged every tick (not just on change) so we can confirm the polling loop is alive.\n log?.(`URL polling tick (href=${href}, changed=${String(href !== lastHref)}).`);\n if (href === lastHref) {\n return;\n }\n lastHref = href;\n onUrlChange(href);\n }, pollingInterval);\n return (): void => {\n if (id != null) {\n globalScope.clearInterval(id);\n }\n };\n }\n\n let subscriptionState = urlChangeSubscriptionsByScope.get(globalScope);\n if (!subscriptionState) {\n let lastHref: string | undefined = undefined;\n const callbacks = new Set<(href: string) => void>();\n\n const getHref = (): string => globalScope.location.href ?? '';\n\n const notify = (): void => {\n const href = getHref();\n if (lastHref !== undefined && href === lastHref) return;\n lastHref = href;\n callbacks.forEach((c) => c(href));\n };\n\n /**\n * Creates a patched version of history methods (pushState/replaceState)\n * that calls the original method and then emits a URL change event.\n */\n const createHistoryMethodPatch = <T extends typeof history.pushState | typeof history.replaceState>(\n originalMethod: T,\n ) => {\n const patchedMethod = function (this: History, ...args: Parameters<T>) {\n const result = originalMethod.apply(this, args);\n // Read from location.href after history call so we always notify with resolved absolute URL.\n notify();\n return result;\n } as PatchableHistoryMethod<T>;\n\n patchedMethod[PATCH_MARKER] = true;\n return patchedMethod;\n };\n\n const history = globalScope.history;\n if (history?.pushState) {\n const pushState = Reflect.get(history, 'pushState') as PatchableHistoryMethod<History['pushState']>;\n if (!pushState[PATCH_MARKER]) {\n history.pushState = createHistoryMethodPatch(pushState);\n }\n }\n if (history?.replaceState) {\n const replaceState = Reflect.get(history, 'replaceState') as PatchableHistoryMethod<History['replaceState']>;\n if (!replaceState[PATCH_MARKER]) {\n history.replaceState = createHistoryMethodPatch(replaceState);\n }\n }\n\n subscriptionState = {\n callbacks,\n notify,\n onPopStateOrHashChange: () => notify(),\n listenersAttached: false,\n };\n urlChangeSubscriptionsByScope.set(globalScope, subscriptionState);\n }\n\n const state = subscriptionState;\n if (!state.listenersAttached) {\n globalScope.addEventListener('popstate', state.onPopStateOrHashChange);\n globalScope.addEventListener('hashchange', state.onPopStateOrHashChange);\n state.listenersAttached = true;\n }\n\n state.callbacks.add(onUrlChange);\n return (): void => {\n state.callbacks.delete(onUrlChange);\n if (state.callbacks.size === 0) {\n // Do not restore history methods: we are not aware of patches applied by other scripts.\n if (state.listenersAttached) {\n globalScope.removeEventListener('popstate', state.onPopStateOrHashChange);\n globalScope.removeEventListener('hashchange', state.onPopStateOrHashChange);\n state.listenersAttached = false;\n }\n }\n };\n}\n\n/**\n * Creates a URL tracking plugin for rrweb record function\n *\n * This plugin monitors URL changes in the browser and emits events when the URL changes.\n * It supports three tracking modes:\n * 1. Polling (if explicitly enabled) - periodically checks for URL changes\n * 2. History API + Hash routing (default) - patches pushState/replaceState, listens to popstate and hashchange\n * 3. Hash routing only (fallback) - listens to hashchange events when History API is unavailable\n *\n * The plugin handles edge cases gracefully:\n * - Missing or null location objects\n * - Undefined, null, or empty location.href values\n * - Temporal dead zone issues with variable declarations\n * - Consistent URL normalization across all code paths\n *\n * @param options Configuration options for URL tracking\n * @returns RecordPlugin instance that can be used with rrweb\n */\nexport function createUrlTrackingPlugin(\n options: URLTrackingPluginOptions = {},\n): RecordPlugin<URLTrackingPluginOptions> {\n return {\n name: 'amplitude/url-tracking@1',\n observer(cb, globalScope, pluginOptions?: URLTrackingPluginOptions) {\n // Merge options with plugin-level options taking precedence over constructor options\n const config = { ...options, ...pluginOptions };\n const ugcFilterRules = config.ugcFilterRules || [];\n const enablePolling = config.enablePolling ?? false;\n const pollingInterval = config.pollingInterval ?? DEFAULT_URL_CHANGE_POLLING_INTERVAL;\n const captureDocumentTitle = config.captureDocumentTitle ?? false;\n\n // Early return if no global scope is available\n if (!globalScope) {\n return () => {\n // No cleanup needed if no global scope available\n };\n }\n\n // Track the last URL to prevent duplicate events\n // Initialize to undefined to ensure first call always emits an event\n let lastTrackedUrl: string | undefined = undefined;\n\n // Helper functions\n /**\n * Gets the current URL with proper normalization\n * Handles edge cases where location.href might be undefined, null, or empty\n * Ensures consistent behavior across all code paths\n * @returns Normalized URL string (empty string if location unavailable)\n */\n const getCurrentUrl = (): string => {\n if (!globalScope.location) return '';\n return globalScope.location.href || '';\n };\n\n /**\n * Creates a URL change event with current page information\n * Applies UGC filtering if rules are configured\n * Uses getCurrentUrl() for consistent URL normalization\n * @returns URLChangeEvent with current page state\n */\n const createUrlChangeEvent = (): URLChangeEvent => {\n const { innerHeight, innerWidth, document } = globalScope;\n const currentUrl = getCurrentUrl();\n let currentTitle = '';\n if (captureDocumentTitle) {\n currentTitle = document?.title || '';\n }\n\n // Apply UGC filtering if rules are provided, otherwise use original URL\n const filteredUrl = ugcFilterRules.length > 0 ? getPageUrl(currentUrl, ugcFilterRules) : currentUrl;\n\n return {\n href: filteredUrl,\n title: currentTitle,\n viewportHeight: innerHeight,\n viewportWidth: innerWidth,\n type: 'url-change-event',\n };\n };\n\n /**\n * Emits a URL change event if the URL has actually changed\n * Always emits on first call (when lastTrackedUrl is undefined)\n */\n const emitUrlChange = (): void => {\n const currentUrl = getCurrentUrl();\n if (lastTrackedUrl === undefined || currentUrl !== lastTrackedUrl) {\n lastTrackedUrl = currentUrl;\n const event = createUrlChangeEvent();\n cb(event);\n }\n };\n\n // Single helper: history + popstate + hashchange, or polling when enabled\n const unsubscribe = subscribeToUrlChanges(\n globalScope as Window,\n () => emitUrlChange(),\n enablePolling ? { enablePolling: true, pollingInterval } : {},\n );\n emitUrlChange();\n\n return (): void => unsubscribe();\n },\n options,\n };\n}\n\n/**\n * Default URL tracking plugin instance with default options\n * Can be used directly without custom configuration\n */\nexport const urlTrackingPlugin = createUrlTrackingPlugin();\n"]}
@@ -67,6 +67,13 @@ export declare class SessionReplay implements AmplitudeSessionReplay {
67
67
  */
68
68
  private isBelowMinSessionDuration;
69
69
  private getCurrentPageForTargeting;
70
+ /**
71
+ * Best-effort navigation type from the Navigation Timing API: 'reload' | 'navigate' |
72
+ * 'back_forward' | 'prerender'. Surfaced in the init diagnostic so a page refresh ('reload')
73
+ * is distinguishable from a fresh load ('navigate') — neither changes the session id, so this
74
+ * is the only way to tell them apart. Returns undefined when the API is unavailable.
75
+ */
76
+ private getNavigationType;
70
77
  protected _init(apiKey: string, options: SessionReplayOptions): Promise<void>;
71
78
  setSessionId(sessionId: string | number, deviceId?: string): import("@amplitude/analytics-core").AmplitudeReturn<void>;
72
79
  asyncSetSessionId(sessionId: string | number, deviceId?: string, options?: {
@@ -1 +1 @@
1
- {"version":3,"file":"session-replay.d.ts","sourceRoot":"","sources":["../../src/session-replay.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"session-replay.d.ts","sourceRoot":"","sources":["../../src/session-replay.ts"],"names":[],"mappings":"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"}
@@ -172,6 +172,17 @@ var SessionReplay = /** @class */ (function () {
172
172
  }
173
173
  pageUrl = (_f = (_c = (_b = targetingParams.page) === null || _b === void 0 ? void 0 : _b.url) !== null && _c !== void 0 ? _c : (_e = (_d = (0, analytics_core_1.getGlobalScope)()) === null || _d === void 0 ? void 0 : _d.location) === null || _e === void 0 ? void 0 : _e.href) !== null && _f !== void 0 ? _f : '';
174
174
  pageForTargeting = (_g = targetingParams.page) !== null && _g !== void 0 ? _g : (pageUrl !== '' ? { url: pageUrl } : undefined);
175
+ // Record the targeting trigger event
176
+ this.recordDiagnosticEvent(diagnostics_1.SrDiagnostic.targetingTrigger, {
177
+ sessionId: this.identifiers.sessionId,
178
+ deviceId: this.getDeviceId(),
179
+ targetingConfig: this.config.targetingConfig,
180
+ targetingParams: {
181
+ userProperties: targetingParams.userProperties,
182
+ event: eventForTargeting,
183
+ page: pageForTargeting,
184
+ },
185
+ });
175
186
  evalStart = Date.now();
176
187
  return [4 /*yield*/, (0, targeting_manager_1.evaluateTargetingAndStore)({
177
188
  sessionId: this.identifiers.sessionId,
@@ -329,12 +340,17 @@ var SessionReplay = /** @class */ (function () {
329
340
  */
330
341
  SessionReplay.prototype.setupUrlChangeListener = function () {
331
342
  var _this = this;
332
- var _a, _b;
343
+ var _a, _b, _c, _d, _e, _f;
333
344
  // If init() runs multiple times, remove the previous URL-change subscription first
334
345
  // so we don't leak callbacks and trigger duplicate targeting evaluations.
335
346
  (_a = this.urlChangeCleanup) === null || _a === void 0 ? void 0 : _a.call(this);
336
347
  var globalScope = (0, analytics_core_1.getGlobalScope)();
337
348
  if (!(globalScope === null || globalScope === void 0 ? void 0 : globalScope.location)) {
349
+ // No window/location (SSR, worker, or pre-render) — the listener can't attach, so TRC will
350
+ // never re-evaluate on navigation. Surface it rather than failing silently.
351
+ this.incrementDiagnostic(diagnostics_1.SrDiagnostic.urlListenerSkipped);
352
+ this.recordDiagnosticEvent(diagnostics_1.SrDiagnostic.urlListenerSkipped, { reason: 'no_global_scope' });
353
+ this.loggerProvider.debug('URL-change listener not attached: no global scope/location.');
338
354
  return;
339
355
  }
340
356
  var hasTargeting = !!((_b = this.config) === null || _b === void 0 ? void 0 : _b.targetingConfig);
@@ -359,7 +375,27 @@ var SessionReplay = /** @class */ (function () {
359
375
  _this.loggerProvider.debug("Queued URL-change targeting re-evaluation #".concat(evaluationId, " for ").concat(href, "."));
360
376
  }
361
377
  };
362
- var unsubscribe = url_tracking_plugin_1.subscribeToUrlChanges(globalScope, onUrlChange);
378
+ // Pass the polling options so targeting re-evaluation also respects `enableUrlChangePolling`.
379
+ // Without this, the targeting listener only sees history.pushState/replaceState + popstate +
380
+ // hashchange — so SPA navigations that bypass the history API never re-evaluate TRC and
381
+ // recording never starts on the new URL (enableUrlChangePolling previously only affected the
382
+ // rrweb URL-tracking plugin, which runs only once recording is already active).
383
+ var enablePolling = (_d = (_c = this.config) === null || _c === void 0 ? void 0 : _c.enableUrlChangePolling) !== null && _d !== void 0 ? _d : false;
384
+ var unsubscribe = (0, url_tracking_plugin_1.subscribeToUrlChanges)(globalScope, onUrlChange, {
385
+ enablePolling: enablePolling,
386
+ pollingInterval: (_e = this.config) === null || _e === void 0 ? void 0 : _e.urlChangePollingInterval,
387
+ // Mirror each poll tick to the console (Debug level) so we can confirm polling is firing.
388
+ log: this.loggerProvider.debug.bind(this.loggerProvider),
389
+ });
390
+ // Confirm the listener is actually live, and under what settings — if recording never starts on
391
+ // navigation, this tells us whether the listener existed and whether polling (the fallback for
392
+ // SPAs that bypass the History API) was on.
393
+ this.recordDiagnosticEvent(diagnostics_1.SrDiagnostic.urlListenerAttached, {
394
+ hasTargeting: hasTargeting,
395
+ enablePolling: enablePolling,
396
+ pollingInterval: (_f = this.config) === null || _f === void 0 ? void 0 : _f.urlChangePollingInterval,
397
+ });
398
+ this.loggerProvider.debug("URL-change listener attached (polling: ".concat(String(enablePolling), ")."));
363
399
  this.urlChangeCleanup = function () {
364
400
  unsubscribe();
365
401
  _this.urlChangeCleanup = null;
@@ -388,14 +424,34 @@ var SessionReplay = /** @class */ (function () {
388
424
  var currentUrl = (_b = (_a = (0, analytics_core_1.getGlobalScope)()) === null || _a === void 0 ? void 0 : _a.location) === null || _b === void 0 ? void 0 : _b.href;
389
425
  return currentUrl != null ? { url: currentUrl } : undefined;
390
426
  };
427
+ /**
428
+ * Best-effort navigation type from the Navigation Timing API: 'reload' | 'navigate' |
429
+ * 'back_forward' | 'prerender'. Surfaced in the init diagnostic so a page refresh ('reload')
430
+ * is distinguishable from a fresh load ('navigate') — neither changes the session id, so this
431
+ * is the only way to tell them apart. Returns undefined when the API is unavailable.
432
+ */
433
+ SessionReplay.prototype.getNavigationType = function () {
434
+ try {
435
+ var globalScope = (0, analytics_core_1.getGlobalScope)();
436
+ var performance_1 = globalScope && globalScope.performance;
437
+ if (!performance_1 || typeof performance_1.getEntriesByType !== 'function') {
438
+ return undefined;
439
+ }
440
+ var navEntries = performance_1.getEntriesByType('navigation');
441
+ return navEntries.length > 0 ? navEntries[0].type : undefined;
442
+ }
443
+ catch (_a) {
444
+ return undefined;
445
+ }
446
+ };
391
447
  SessionReplay.prototype._init = function (apiKey, options) {
392
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q;
448
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t;
393
449
  return tslib_1.__awaiter(this, void 0, void 0, function () {
394
- var now, _r, _s, joinedConfig, localConfig, remoteConfig, scrollWatcher, managers, storeType, compressionWorkerScript, trackDestinationWorkerScript, globalScope, _t, compressionScript, trackDestinationScript, rrwebEventManager, error_1, typedError, payloadBatcher, interactionEventManager, error_2, typedError, onFullSnapshotProcessed, pending_2, pending_1, pending_1_1, _u, event_1, sessionId, messenger, needsUrlTracking;
395
- var e_2, _v;
450
+ var now, _u, _v, joinedConfig, localConfig, remoteConfig, scrollWatcher, managers, storeType, compressionWorkerScript, trackDestinationWorkerScript, globalScope, _w, compressionScript, trackDestinationScript, rrwebEventManager, error_1, typedError, payloadBatcher, interactionEventManager, error_2, typedError, onFullSnapshotProcessed, pending_2, pending_1, pending_1_1, _x, event_1, sessionId, messenger, needsUrlTracking;
451
+ var e_2, _y;
396
452
  var _this = this;
397
- return tslib_1.__generator(this, function (_w) {
398
- switch (_w.label) {
453
+ return tslib_1.__generator(this, function (_z) {
454
+ switch (_z.label) {
399
455
  case 0:
400
456
  // Re-init should always tear down any previous URL-change subscription, even when the
401
457
  // next config has no targeting config and we don't subscribe again.
@@ -411,13 +467,13 @@ var SessionReplay = /** @class */ (function () {
411
467
  options.sessionId !== undefined
412
468
  ? (_b = (0, replay_start_time_store_1.getOrInitReplayStartTime)(apiKey, options.sessionId, now, this.loggerProvider)) !== null && _b !== void 0 ? _b : now
413
469
  : now;
414
- _r = this;
470
+ _u = this;
415
471
  return [4 /*yield*/, (0, joined_config_1.createSessionReplayJoinedConfigGenerator)(apiKey, options)];
416
472
  case 1:
417
- _r.joinedConfigGenerator = _w.sent();
473
+ _u.joinedConfigGenerator = _z.sent();
418
474
  return [4 /*yield*/, this.joinedConfigGenerator.generateJoinedConfig()];
419
475
  case 2:
420
- _s = _w.sent(), joinedConfig = _s.joinedConfig, localConfig = _s.localConfig, remoteConfig = _s.remoteConfig;
476
+ _v = _z.sent(), joinedConfig = _v.joinedConfig, localConfig = _v.localConfig, remoteConfig = _v.remoteConfig;
421
477
  this.config = joinedConfig;
422
478
  this.setMetadata(options.sessionId, joinedConfig, localConfig, remoteConfig, (_c = options.version) === null || _c === void 0 ? void 0 : _c.version, version_1.VERSION, (_d = options.version) === null || _d === void 0 ? void 0 : _d.type);
423
479
  this.pageLeaveFns = [];
@@ -441,12 +497,12 @@ var SessionReplay = /** @class */ (function () {
441
497
  if (!(this.config.useWebWorker && globalScope && globalScope.Worker)) return [3 /*break*/, 4];
442
498
  return [4 /*yield*/, Promise.resolve().then(function () { return tslib_1.__importStar(require('./worker')); })];
443
499
  case 3:
444
- _t = _w.sent(), compressionScript = _t.compressionScript, trackDestinationScript = _t.trackDestinationScript;
500
+ _w = _z.sent(), compressionScript = _w.compressionScript, trackDestinationScript = _w.trackDestinationScript;
445
501
  compressionWorkerScript = compressionScript;
446
502
  trackDestinationWorkerScript = trackDestinationScript;
447
- _w.label = 4;
503
+ _z.label = 4;
448
504
  case 4:
449
- _w.trys.push([4, 6, , 7]);
505
+ _z.trys.push([4, 6, , 7]);
450
506
  return [4 /*yield*/, (0, events_manager_1.createEventsManager)({
451
507
  config: this.config,
452
508
  type: 'replay',
@@ -458,21 +514,21 @@ var SessionReplay = /** @class */ (function () {
458
514
  shouldSend: function () { return !_this.isBelowMinSessionDuration(); },
459
515
  })];
460
516
  case 5:
461
- rrwebEventManager = _w.sent();
517
+ rrwebEventManager = _z.sent();
462
518
  this.rrwebEventManager = rrwebEventManager;
463
519
  managers.push({ name: 'replay', manager: rrwebEventManager });
464
520
  return [3 /*break*/, 7];
465
521
  case 6:
466
- error_1 = _w.sent();
522
+ error_1 = _z.sent();
467
523
  typedError = error_1;
468
524
  this.loggerProvider.warn("Error occurred while creating replay events manager: ".concat(typedError.toString()));
469
525
  return [3 /*break*/, 7];
470
526
  case 7:
471
527
  if (!((_j = this.config.interactionConfig) === null || _j === void 0 ? void 0 : _j.enabled)) return [3 /*break*/, 11];
472
528
  payloadBatcher = this.config.interactionConfig.batch ? click_1.clickBatcher : click_1.clickNonBatcher;
473
- _w.label = 8;
529
+ _z.label = 8;
474
530
  case 8:
475
- _w.trys.push([8, 10, , 11]);
531
+ _z.trys.push([8, 10, , 11]);
476
532
  return [4 /*yield*/, (0, events_manager_1.createEventsManager)({
477
533
  config: this.config,
478
534
  type: 'interaction',
@@ -484,11 +540,11 @@ var SessionReplay = /** @class */ (function () {
484
540
  trackDestinationWorkerScript: trackDestinationWorkerScript,
485
541
  })];
486
542
  case 9:
487
- interactionEventManager = _w.sent();
543
+ interactionEventManager = _z.sent();
488
544
  managers.push({ name: 'interaction', manager: interactionEventManager });
489
545
  return [3 /*break*/, 11];
490
546
  case 10:
491
- error_2 = _w.sent();
547
+ error_2 = _z.sent();
492
548
  typedError = error_2;
493
549
  this.loggerProvider.warn("Error occurred while creating interaction events manager: ".concat(typedError.toString()));
494
550
  return [3 /*break*/, 11];
@@ -506,14 +562,14 @@ var SessionReplay = /** @class */ (function () {
506
562
  pending_2 = this.pendingEmitEvents.splice(0);
507
563
  try {
508
564
  for (pending_1 = tslib_1.__values(pending_2), pending_1_1 = pending_1.next(); !pending_1_1.done; pending_1_1 = pending_1.next()) {
509
- _u = pending_1_1.value, event_1 = _u.event, sessionId = _u.sessionId;
565
+ _x = pending_1_1.value, event_1 = _x.event, sessionId = _x.sessionId;
510
566
  this.eventCompressor.enqueueEvent(event_1, sessionId);
511
567
  }
512
568
  }
513
569
  catch (e_2_1) { e_2 = { error: e_2_1 }; }
514
570
  finally {
515
571
  try {
516
- if (pending_1_1 && !pending_1_1.done && (_v = pending_1.return)) _v.call(pending_1);
572
+ if (pending_1_1 && !pending_1_1.done && (_y = pending_1.return)) _y.call(pending_1);
517
573
  }
518
574
  finally { if (e_2) throw e_2.error; }
519
575
  }
@@ -550,7 +606,7 @@ var SessionReplay = /** @class */ (function () {
550
606
  ], false);
551
607
  return [4 /*yield*/, this.initializeNetworkObservers()];
552
608
  case 12:
553
- _w.sent();
609
+ _z.sent();
554
610
  // Enable background capture when this page is opened by the Amplitude app
555
611
  // (window.opener exists). Uses the shared messenger singleton so that if
556
612
  // autocapture is also loaded, both share a single messenger and script load.
@@ -572,11 +628,27 @@ var SessionReplay = /** @class */ (function () {
572
628
  sampleRate: this.config.sampleRate,
573
629
  optOut: this.shouldOptOut(),
574
630
  currentUrl: (_m = this.getCurrentPageForTargeting()) === null || _m === void 0 ? void 0 : _m.url,
631
+ // 'reload' tells a page refresh apart from a fresh load ('navigate') or back/forward
632
+ // ('back_forward'). New tabs and refreshes keep the same session id, so this is the only
633
+ // way to distinguish a refresh from a brand-new init within a session.
634
+ navigationType: this.getNavigationType(),
575
635
  });
576
636
  return [4 /*yield*/, this.evaluateTargetingAndCapture({ userProperties: options.userProperties, page: this.getCurrentPageForTargeting() }, true)];
577
637
  case 13:
578
- _w.sent();
638
+ _z.sent();
579
639
  needsUrlTracking = this.config.targetingConfig || ((_q = (_p = (_o = this.config.privacyConfig) === null || _o === void 0 ? void 0 : _o.urlMaskLevels) === null || _p === void 0 ? void 0 : _p.length) !== null && _q !== void 0 ? _q : 0) > 0;
640
+ // Record whether we even attempt to wire up the URL-change listener, and the inputs behind that
641
+ // decision. If `needsUrlTracking` is false the listener is never attached, so SPA navigations are
642
+ // invisible to TRC — this is the first thing to check when "TRC is on but never re-evaluates".
643
+ this.recordDiagnosticEvent(diagnostics_1.SrDiagnostic.urlListenerSetup, {
644
+ needsUrlTracking: !!needsUrlTracking,
645
+ hasTargetingConfig: !!this.config.targetingConfig,
646
+ urlMaskLevels: (_t = (_s = (_r = this.config.privacyConfig) === null || _r === void 0 ? void 0 : _r.urlMaskLevels) === null || _s === void 0 ? void 0 : _s.length) !== null && _t !== void 0 ? _t : 0,
647
+ // config always defaults this to a boolean (see local-config), so no `?? false` needed here.
648
+ enableUrlChangePolling: this.config.enableUrlChangePolling,
649
+ urlChangePollingInterval: this.config.urlChangePollingInterval,
650
+ });
651
+ this.loggerProvider.debug("URL-change listener needed: ".concat(String(!!needsUrlTracking), "."));
580
652
  if (needsUrlTracking) {
581
653
  this.setupUrlChangeListener();
582
654
  }
@@ -616,6 +688,18 @@ var SessionReplay = /** @class */ (function () {
616
688
  this.sendEvents(previousSessionId);
617
689
  }
618
690
  isSessionChange = previousSessionId !== sessionId;
691
+ // A real session transition (not the first set): recording stops/restarts and targeting is
692
+ // re-evaluated here, so session churn (timeout, multi-tab custom ids, explicit setSessionId)
693
+ // is a prime suspect for "sometimes records, sometimes not". recordDiagnosticEvent ships to
694
+ // DataDog AND mirrors to loggerProvider.debug. New tabs / refreshes keep the same id and won't
695
+ // hit this — use the init diagnostic's navigationType for those.
696
+ if (isSessionChange && previousSessionId !== undefined) {
697
+ this.recordDiagnosticEvent(diagnostics_1.SrDiagnostic.sessionChanged, {
698
+ from: previousSessionId,
699
+ to: sessionId,
700
+ deviceIdChanged: deviceId !== undefined && deviceId !== currentDeviceId,
701
+ });
702
+ }
619
703
  // Drop any beacon-buffered events from the previous session BEFORE installing the
620
704
  // new identifiers / start time. Otherwise the page-leave beacon path could attribute
621
705
  // old-session events to the new session id, and the gate (using the new start time)
@@ -780,6 +864,9 @@ var SessionReplay = /** @class */ (function () {
780
864
  SessionReplay.prototype.incrementDiagnostic = function (counter) {
781
865
  var _a, _b;
782
866
  try {
867
+ // Mirror to the console (Debug level) so the full set of diagnostics is visible in the
868
+ // browser, not only in DataDog — helps debugging when a customer can't share a repro env.
869
+ this.loggerProvider.debug("[SR diagnostics] ".concat(counter));
783
870
  (_b = (_a = this.config) === null || _a === void 0 ? void 0 : _a.diagnosticsClient) === null || _b === void 0 ? void 0 : _b.increment(counter);
784
871
  }
785
872
  catch (_c) {
@@ -793,6 +880,8 @@ var SessionReplay = /** @class */ (function () {
793
880
  SessionReplay.prototype.recordDiagnosticHistogram = function (name, value) {
794
881
  var _a, _b;
795
882
  try {
883
+ // Mirror to the console (Debug level) — see incrementDiagnostic.
884
+ this.loggerProvider.debug("[SR diagnostics] ".concat(name, "=").concat(value));
796
885
  // The only caller runs inside evaluateTargetingAndCapture's `this.config` guard, so the
797
886
  // `config == null` arm of this optional chain is unreachable here (kept as defensive parity
798
887
  // with recordDiagnosticEvent). The diagnosticsClient-absent arm IS exercised by no-client tests.
@@ -817,7 +906,11 @@ var SessionReplay = /** @class */ (function () {
817
906
  // Always stamp sessionId, deviceId and srId so every diagnostics event (→ DataDog Logs) can
818
907
  // be correlated to a single session/device — and to the actual replay, since the Session
819
908
  // Replay ID is `${deviceId}/${sessionId}`. Caller props override if they pass their own.
820
- (_c = (_b = this.config) === null || _b === void 0 ? void 0 : _b.diagnosticsClient) === null || _c === void 0 ? void 0 : _c.recordEvent(name, tslib_1.__assign({ sessionId: sessionId, deviceId: deviceId, srId: deviceId != null && sessionId != null ? "".concat(deviceId, "/").concat(sessionId) : undefined }, properties));
909
+ var enriched = tslib_1.__assign({ sessionId: sessionId, deviceId: deviceId, srId: deviceId != null && sessionId != null ? "".concat(deviceId, "/").concat(sessionId) : undefined }, properties);
910
+ // Mirror to the console (Debug level) so the full event + props are visible in the browser,
911
+ // not only in DataDog — see incrementDiagnostic.
912
+ this.loggerProvider.debug("[SR diagnostics] ".concat(name, " ").concat(JSON.stringify(enriched)));
913
+ (_c = (_b = this.config) === null || _b === void 0 ? void 0 : _b.diagnosticsClient) === null || _c === void 0 ? void 0 : _c.recordEvent(name, enriched);
821
914
  // Flush immediately so the event ships now rather than on the client's ~5-min timer (and so
822
915
  // short sessions that never reach the timer aren't lost). NOTE: this sends one capture POST
823
916
  // per event — higher request volume; revisit/gate before production.