@amplitude/session-replay-browser 1.29.4 → 1.29.5-zen-plus-zoning.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,uBAAuB,CACrC,OAAO,GAAE,wBAA6B,GACrC,YAAY,CAAC,wBAAwB,CAAC,CA4KxC;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;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,uBAAuB,CACrC,OAAO,GAAE,wBAA6B,GACrC,YAAY,CAAC,wBAAwB,CAAC,CAyMxC;AAED;;;GAGG;AACH,eAAO,MAAM,iBAAiB,wCAA4B,CAAC"}
@@ -43,6 +43,12 @@ function createUrlTrackingPlugin(options) {
43
43
  // Track the last URL to prevent duplicate events
44
44
  // Initialize to undefined to ensure first call always emits an event
45
45
  var lastTrackedUrl = undefined;
46
+ // Patch detection marker to prevent double-patching
47
+ var PATCH_MARKER = '__amplitude_url_tracking_patched__';
48
+ // Global flag key on globalScope to track if the plugin has been reset/cleaned up
49
+ // When true, prevents emitUrlChange from being called from patched history methods
50
+ // even if other plugins have also patched them and we can't restore the originals
51
+ var RESET_FLAG_KEY = '__amplitude_url_tracking_reset__';
46
52
  // Helper functions
47
53
  /**
48
54
  * Gets the current URL with proper normalization
@@ -97,21 +103,27 @@ function createUrlTrackingPlugin(options) {
97
103
  * Creates a patched version of history methods (pushState/replaceState)
98
104
  * that calls the original method and then emits a URL change event
99
105
  * Ensures URL changes are detected even when history methods are called programmatically
106
+ * Checks global reset flag to prevent emitting after plugin cleanup
100
107
  * @param originalMethod The original history method to patch
101
108
  * @returns Patched function that calls original method then emits URL change event
102
109
  */
103
110
  var createHistoryMethodPatch = function (originalMethod) {
104
- return function () {
111
+ var patchedMethod = function () {
105
112
  var args = [];
106
113
  for (var _i = 0; _i < arguments.length; _i++) {
107
114
  args[_i] = arguments[_i];
108
115
  }
109
116
  // Call the original history method first
110
117
  var result = originalMethod.apply(this, args);
111
- // Then emit URL change event
112
- emitUrlChange();
118
+ // Then emit URL change event only if plugin hasn't been reset
119
+ if (!globalScope[RESET_FLAG_KEY]) {
120
+ emitUrlChange();
121
+ }
113
122
  return result;
114
123
  };
124
+ // Mark the patched method to prevent double-patching
125
+ patchedMethod[PATCH_MARKER] = true;
126
+ return patchedMethod;
115
127
  };
116
128
  // Hashchange event handler - delegates to emitUrlChange for consistency
117
129
  var hashChangeHandler = function () {
@@ -141,8 +153,14 @@ function createUrlTrackingPlugin(options) {
141
153
  /**
142
154
  * Sets up history method patching to intercept pushState and replaceState calls
143
155
  * This ensures URL changes are detected even when history methods are called programmatically
156
+ * Includes patch detection to prevent double-patching by the same plugin
144
157
  */
145
158
  var setupHistoryPatching = function () {
159
+ // Check if we already patched these methods
160
+ if (globalScope.history.pushState[PATCH_MARKER]) {
161
+ // Already patched by this plugin, skip patching
162
+ return;
163
+ }
146
164
  // Patch pushState to emit URL change events
147
165
  globalScope.history.pushState = createHistoryMethodPatch(originalPushState_1);
148
166
  // Patch replaceState to emit URL change events
@@ -158,9 +176,11 @@ function createUrlTrackingPlugin(options) {
158
176
  emitUrlChange();
159
177
  // Return cleanup function to restore original state
160
178
  return function () {
161
- // Restore original history methods
162
- globalScope.history.pushState = originalPushState_1;
163
- globalScope.history.replaceState = originalReplaceState_1;
179
+ // Restore original history methods - cannot be done here
180
+ // because the plugin is not aware of the history methods modified by other plugins
181
+ // so we need to set a flag on the globalScope to prevent further emitUrlChange calls from patched methods
182
+ // Set reset flag on globalScope to prevent further emitUrlChange calls from patched methods
183
+ globalScope[RESET_FLAG_KEY] = true;
164
184
  // Remove popstate event listener
165
185
  globalScope.removeEventListener('popstate', emitUrlChange);
166
186
  // Remove hashchange event listener
@@ -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/**\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 * Prevents duplicate events for the same URL on subsequent calls\n * Handles edge cases like undefined/null/empty URLs gracefully\n */\n const emitUrlChange = (): void => {\n const currentUrl = getCurrentUrl();\n\n // Always emit on first call, or if URL actually changed\n if (lastTrackedUrl === undefined || currentUrl !== lastTrackedUrl) {\n lastTrackedUrl = currentUrl;\n const event = createUrlChangeEvent();\n cb(event);\n }\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 * Ensures URL changes are detected even when history methods are called programmatically\n * @param originalMethod The original history method to patch\n * @returns Patched function that calls original method then emits URL change event\n */\n const createHistoryMethodPatch = <T extends typeof history.pushState | typeof history.replaceState>(\n originalMethod: T,\n ) => {\n return function (this: History, ...args: Parameters<T>) {\n // Call the original history method first\n const result = originalMethod.apply(this, args);\n // Then emit URL change event\n emitUrlChange();\n return result;\n };\n };\n\n // Hashchange event handler - delegates to emitUrlChange for consistency\n const hashChangeHandler = () => {\n emitUrlChange();\n };\n\n // 1. if explicitly enable polling → use polling\n if (enablePolling) {\n // Use polling (covers everything)\n const urlChangeInterval = globalScope.setInterval(() => {\n emitUrlChange();\n }, pollingInterval);\n\n // Emit initial URL immediately\n emitUrlChange();\n\n // Return cleanup function to stop polling\n return () => {\n if (urlChangeInterval) {\n globalScope.clearInterval(urlChangeInterval);\n }\n };\n }\n\n // 2. if polling not enabled → check history, if exist, use history\n if (globalScope.history) {\n // Use History API + hashchange (covers History API + hash routing)\n // Store original history methods for restoration during cleanup\n const originalPushState = globalScope.history.pushState.bind(globalScope.history);\n const originalReplaceState = globalScope.history.replaceState.bind(globalScope.history);\n\n /**\n * Sets up history method patching to intercept pushState and replaceState calls\n * This ensures URL changes are detected even when history methods are called programmatically\n */\n const setupHistoryPatching = (): void => {\n // Patch pushState to emit URL change events\n globalScope.history.pushState = createHistoryMethodPatch(originalPushState);\n\n // Patch replaceState to emit URL change events\n globalScope.history.replaceState = createHistoryMethodPatch(originalReplaceState);\n };\n\n // Apply history method patches\n setupHistoryPatching();\n\n // Listen to popstate events for browser back/forward navigation\n globalScope.addEventListener('popstate', emitUrlChange);\n // Listen to hashchange events for hash routing\n globalScope.addEventListener('hashchange', hashChangeHandler);\n\n // Emit initial URL immediately\n emitUrlChange();\n\n // Return cleanup function to restore original state\n return () => {\n // Restore original history methods\n globalScope.history.pushState = originalPushState;\n globalScope.history.replaceState = originalReplaceState;\n\n // Remove popstate event listener\n globalScope.removeEventListener('popstate', emitUrlChange);\n // Remove hashchange event listener\n globalScope.removeEventListener('hashchange', hashChangeHandler);\n };\n }\n\n // 3. if not, then the framework is probably using hash router → do hash\n // Fallback: just hashchange (for pure hash routing)\n globalScope.addEventListener('hashchange', hashChangeHandler);\n emitUrlChange();\n return () => {\n globalScope.removeEventListener('hashchange', hashChangeHandler);\n };\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/**\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 // Patch detection marker to prevent double-patching\n const PATCH_MARKER = '__amplitude_url_tracking_patched__';\n\n // Global flag key on globalScope to track if the plugin has been reset/cleaned up\n // When true, prevents emitUrlChange from being called from patched history methods\n // even if other plugins have also patched them and we can't restore the originals\n const RESET_FLAG_KEY = '__amplitude_url_tracking_reset__';\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 * Prevents duplicate events for the same URL on subsequent calls\n * Handles edge cases like undefined/null/empty URLs gracefully\n */\n const emitUrlChange = (): void => {\n const currentUrl = getCurrentUrl();\n\n // Always emit on first call, or if URL actually changed\n if (lastTrackedUrl === undefined || currentUrl !== lastTrackedUrl) {\n lastTrackedUrl = currentUrl;\n const event = createUrlChangeEvent();\n cb(event);\n }\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 * Ensures URL changes are detected even when history methods are called programmatically\n * Checks global reset flag to prevent emitting after plugin cleanup\n * @param originalMethod The original history method to patch\n * @returns Patched function that calls original method then emits 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 // Call the original history method first\n const result = originalMethod.apply(this, args);\n // Then emit URL change event only if plugin hasn't been reset\n if (!(globalScope as Record<string, any>)[RESET_FLAG_KEY]) {\n emitUrlChange();\n }\n return result;\n } as T & { [PATCH_MARKER]: boolean };\n\n // Mark the patched method to prevent double-patching\n patchedMethod[PATCH_MARKER] = true;\n\n return patchedMethod;\n };\n\n // Hashchange event handler - delegates to emitUrlChange for consistency\n const hashChangeHandler = () => {\n emitUrlChange();\n };\n\n // 1. if explicitly enable polling → use polling\n if (enablePolling) {\n // Use polling (covers everything)\n const urlChangeInterval = globalScope.setInterval(() => {\n emitUrlChange();\n }, pollingInterval);\n\n // Emit initial URL immediately\n emitUrlChange();\n\n // Return cleanup function to stop polling\n return () => {\n if (urlChangeInterval) {\n globalScope.clearInterval(urlChangeInterval);\n }\n };\n }\n\n // 2. if polling not enabled → check history, if exist, use history\n if (globalScope.history) {\n // Use History API + hashchange (covers History API + hash routing)\n // Store original history methods for restoration during cleanup\n const originalPushState = globalScope.history.pushState.bind(globalScope.history);\n const originalReplaceState = globalScope.history.replaceState.bind(globalScope.history);\n\n /**\n * Sets up history method patching to intercept pushState and replaceState calls\n * This ensures URL changes are detected even when history methods are called programmatically\n * Includes patch detection to prevent double-patching by the same plugin\n */\n const setupHistoryPatching = (): void => {\n // Check if we already patched these methods\n if (\n (globalScope.history.pushState as typeof globalScope.history.pushState & { [PATCH_MARKER]?: boolean })[\n PATCH_MARKER\n ]\n ) {\n // Already patched by this plugin, skip patching\n return;\n }\n\n // Patch pushState to emit URL change events\n globalScope.history.pushState = createHistoryMethodPatch(originalPushState);\n\n // Patch replaceState to emit URL change events\n globalScope.history.replaceState = createHistoryMethodPatch(originalReplaceState);\n };\n\n // Apply history method patches\n setupHistoryPatching();\n\n // Listen to popstate events for browser back/forward navigation\n globalScope.addEventListener('popstate', emitUrlChange);\n // Listen to hashchange events for hash routing\n globalScope.addEventListener('hashchange', hashChangeHandler);\n\n // Emit initial URL immediately\n emitUrlChange();\n\n // Return cleanup function to restore original state\n return () => {\n // Restore original history methods - cannot be done here\n // because the plugin is not aware of the history methods modified by other plugins\n // so we need to set a flag on the globalScope to prevent further emitUrlChange calls from patched methods\n // Set reset flag on globalScope to prevent further emitUrlChange calls from patched methods\n (globalScope as Record<string, any>)[RESET_FLAG_KEY] = true;\n\n // Remove popstate event listener\n globalScope.removeEventListener('popstate', emitUrlChange);\n // Remove hashchange event listener\n globalScope.removeEventListener('hashchange', hashChangeHandler);\n };\n }\n\n // 3. if not, then the framework is probably using hash router → do hash\n // Fallback: just hashchange (for pure hash routing)\n globalScope.addEventListener('hashchange', hashChangeHandler);\n emitUrlChange();\n return () => {\n globalScope.removeEventListener('hashchange', hashChangeHandler);\n };\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,2 +1,2 @@
1
- export declare const VERSION = "1.29.4";
1
+ export declare const VERSION = "1.29.5-zen-plus-zoning.0";
2
2
  //# sourceMappingURL=version.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"version.d.ts","sourceRoot":"","sources":["../../src/version.ts"],"names":[],"mappings":"AACA,eAAO,MAAM,OAAO,WAAW,CAAC"}
1
+ {"version":3,"file":"version.d.ts","sourceRoot":"","sources":["../../src/version.ts"],"names":[],"mappings":"AACA,eAAO,MAAM,OAAO,6BAA6B,CAAC"}
@@ -2,5 +2,5 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.VERSION = void 0;
4
4
  // Autogenerated by `yarn version-file`. DO NOT EDIT
5
- exports.VERSION = '1.29.4';
5
+ exports.VERSION = '1.29.5-zen-plus-zoning.0';
6
6
  //# sourceMappingURL=version.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"version.js","sourceRoot":"","sources":["../../src/version.ts"],"names":[],"mappings":";;;AAAA,oDAAoD;AACvC,QAAA,OAAO,GAAG,QAAQ,CAAC","sourcesContent":["// Autogenerated by `yarn version-file`. DO NOT EDIT\nexport const VERSION = '1.29.4';\n"]}
1
+ {"version":3,"file":"version.js","sourceRoot":"","sources":["../../src/version.ts"],"names":[],"mappings":";;;AAAA,oDAAoD;AACvC,QAAA,OAAO,GAAG,0BAA0B,CAAC","sourcesContent":["// Autogenerated by `yarn version-file`. DO NOT EDIT\nexport const VERSION = '1.29.5-zen-plus-zoning.0';\n"]}
@@ -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;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,uBAAuB,CACrC,OAAO,GAAE,wBAA6B,GACrC,YAAY,CAAC,wBAAwB,CAAC,CA4KxC;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;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,uBAAuB,CACrC,OAAO,GAAE,wBAA6B,GACrC,YAAY,CAAC,wBAAwB,CAAC,CAyMxC;AAED;;;GAGG;AACH,eAAO,MAAM,iBAAiB,wCAA4B,CAAC"}
@@ -40,6 +40,12 @@ export function createUrlTrackingPlugin(options) {
40
40
  // Track the last URL to prevent duplicate events
41
41
  // Initialize to undefined to ensure first call always emits an event
42
42
  var lastTrackedUrl = undefined;
43
+ // Patch detection marker to prevent double-patching
44
+ var PATCH_MARKER = '__amplitude_url_tracking_patched__';
45
+ // Global flag key on globalScope to track if the plugin has been reset/cleaned up
46
+ // When true, prevents emitUrlChange from being called from patched history methods
47
+ // even if other plugins have also patched them and we can't restore the originals
48
+ var RESET_FLAG_KEY = '__amplitude_url_tracking_reset__';
43
49
  // Helper functions
44
50
  /**
45
51
  * Gets the current URL with proper normalization
@@ -94,21 +100,27 @@ export function createUrlTrackingPlugin(options) {
94
100
  * Creates a patched version of history methods (pushState/replaceState)
95
101
  * that calls the original method and then emits a URL change event
96
102
  * Ensures URL changes are detected even when history methods are called programmatically
103
+ * Checks global reset flag to prevent emitting after plugin cleanup
97
104
  * @param originalMethod The original history method to patch
98
105
  * @returns Patched function that calls original method then emits URL change event
99
106
  */
100
107
  var createHistoryMethodPatch = function (originalMethod) {
101
- return function () {
108
+ var patchedMethod = function () {
102
109
  var args = [];
103
110
  for (var _i = 0; _i < arguments.length; _i++) {
104
111
  args[_i] = arguments[_i];
105
112
  }
106
113
  // Call the original history method first
107
114
  var result = originalMethod.apply(this, args);
108
- // Then emit URL change event
109
- emitUrlChange();
115
+ // Then emit URL change event only if plugin hasn't been reset
116
+ if (!globalScope[RESET_FLAG_KEY]) {
117
+ emitUrlChange();
118
+ }
110
119
  return result;
111
120
  };
121
+ // Mark the patched method to prevent double-patching
122
+ patchedMethod[PATCH_MARKER] = true;
123
+ return patchedMethod;
112
124
  };
113
125
  // Hashchange event handler - delegates to emitUrlChange for consistency
114
126
  var hashChangeHandler = function () {
@@ -138,8 +150,14 @@ export function createUrlTrackingPlugin(options) {
138
150
  /**
139
151
  * Sets up history method patching to intercept pushState and replaceState calls
140
152
  * This ensures URL changes are detected even when history methods are called programmatically
153
+ * Includes patch detection to prevent double-patching by the same plugin
141
154
  */
142
155
  var setupHistoryPatching = function () {
156
+ // Check if we already patched these methods
157
+ if (globalScope.history.pushState[PATCH_MARKER]) {
158
+ // Already patched by this plugin, skip patching
159
+ return;
160
+ }
143
161
  // Patch pushState to emit URL change events
144
162
  globalScope.history.pushState = createHistoryMethodPatch(originalPushState_1);
145
163
  // Patch replaceState to emit URL change events
@@ -155,9 +173,11 @@ export function createUrlTrackingPlugin(options) {
155
173
  emitUrlChange();
156
174
  // Return cleanup function to restore original state
157
175
  return function () {
158
- // Restore original history methods
159
- globalScope.history.pushState = originalPushState_1;
160
- globalScope.history.replaceState = originalReplaceState_1;
176
+ // Restore original history methods - cannot be done here
177
+ // because the plugin is not aware of the history methods modified by other plugins
178
+ // so we need to set a flag on the globalScope to prevent further emitUrlChange calls from patched methods
179
+ // Set reset flag on globalScope to prevent further emitUrlChange calls from patched methods
180
+ globalScope[RESET_FLAG_KEY] = true;
161
181
  // Remove popstate event listener
162
182
  globalScope.removeEventListener('popstate', emitUrlChange);
163
183
  // Remove hashchange event listener
@@ -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/**\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 * Prevents duplicate events for the same URL on subsequent calls\n * Handles edge cases like undefined/null/empty URLs gracefully\n */\n const emitUrlChange = (): void => {\n const currentUrl = getCurrentUrl();\n\n // Always emit on first call, or if URL actually changed\n if (lastTrackedUrl === undefined || currentUrl !== lastTrackedUrl) {\n lastTrackedUrl = currentUrl;\n const event = createUrlChangeEvent();\n cb(event);\n }\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 * Ensures URL changes are detected even when history methods are called programmatically\n * @param originalMethod The original history method to patch\n * @returns Patched function that calls original method then emits URL change event\n */\n const createHistoryMethodPatch = <T extends typeof history.pushState | typeof history.replaceState>(\n originalMethod: T,\n ) => {\n return function (this: History, ...args: Parameters<T>) {\n // Call the original history method first\n const result = originalMethod.apply(this, args);\n // Then emit URL change event\n emitUrlChange();\n return result;\n };\n };\n\n // Hashchange event handler - delegates to emitUrlChange for consistency\n const hashChangeHandler = () => {\n emitUrlChange();\n };\n\n // 1. if explicitly enable polling → use polling\n if (enablePolling) {\n // Use polling (covers everything)\n const urlChangeInterval = globalScope.setInterval(() => {\n emitUrlChange();\n }, pollingInterval);\n\n // Emit initial URL immediately\n emitUrlChange();\n\n // Return cleanup function to stop polling\n return () => {\n if (urlChangeInterval) {\n globalScope.clearInterval(urlChangeInterval);\n }\n };\n }\n\n // 2. if polling not enabled → check history, if exist, use history\n if (globalScope.history) {\n // Use History API + hashchange (covers History API + hash routing)\n // Store original history methods for restoration during cleanup\n const originalPushState = globalScope.history.pushState.bind(globalScope.history);\n const originalReplaceState = globalScope.history.replaceState.bind(globalScope.history);\n\n /**\n * Sets up history method patching to intercept pushState and replaceState calls\n * This ensures URL changes are detected even when history methods are called programmatically\n */\n const setupHistoryPatching = (): void => {\n // Patch pushState to emit URL change events\n globalScope.history.pushState = createHistoryMethodPatch(originalPushState);\n\n // Patch replaceState to emit URL change events\n globalScope.history.replaceState = createHistoryMethodPatch(originalReplaceState);\n };\n\n // Apply history method patches\n setupHistoryPatching();\n\n // Listen to popstate events for browser back/forward navigation\n globalScope.addEventListener('popstate', emitUrlChange);\n // Listen to hashchange events for hash routing\n globalScope.addEventListener('hashchange', hashChangeHandler);\n\n // Emit initial URL immediately\n emitUrlChange();\n\n // Return cleanup function to restore original state\n return () => {\n // Restore original history methods\n globalScope.history.pushState = originalPushState;\n globalScope.history.replaceState = originalReplaceState;\n\n // Remove popstate event listener\n globalScope.removeEventListener('popstate', emitUrlChange);\n // Remove hashchange event listener\n globalScope.removeEventListener('hashchange', hashChangeHandler);\n };\n }\n\n // 3. if not, then the framework is probably using hash router → do hash\n // Fallback: just hashchange (for pure hash routing)\n globalScope.addEventListener('hashchange', hashChangeHandler);\n emitUrlChange();\n return () => {\n globalScope.removeEventListener('hashchange', hashChangeHandler);\n };\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/**\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 // Patch detection marker to prevent double-patching\n const PATCH_MARKER = '__amplitude_url_tracking_patched__';\n\n // Global flag key on globalScope to track if the plugin has been reset/cleaned up\n // When true, prevents emitUrlChange from being called from patched history methods\n // even if other plugins have also patched them and we can't restore the originals\n const RESET_FLAG_KEY = '__amplitude_url_tracking_reset__';\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 * Prevents duplicate events for the same URL on subsequent calls\n * Handles edge cases like undefined/null/empty URLs gracefully\n */\n const emitUrlChange = (): void => {\n const currentUrl = getCurrentUrl();\n\n // Always emit on first call, or if URL actually changed\n if (lastTrackedUrl === undefined || currentUrl !== lastTrackedUrl) {\n lastTrackedUrl = currentUrl;\n const event = createUrlChangeEvent();\n cb(event);\n }\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 * Ensures URL changes are detected even when history methods are called programmatically\n * Checks global reset flag to prevent emitting after plugin cleanup\n * @param originalMethod The original history method to patch\n * @returns Patched function that calls original method then emits 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 // Call the original history method first\n const result = originalMethod.apply(this, args);\n // Then emit URL change event only if plugin hasn't been reset\n if (!(globalScope as Record<string, any>)[RESET_FLAG_KEY]) {\n emitUrlChange();\n }\n return result;\n } as T & { [PATCH_MARKER]: boolean };\n\n // Mark the patched method to prevent double-patching\n patchedMethod[PATCH_MARKER] = true;\n\n return patchedMethod;\n };\n\n // Hashchange event handler - delegates to emitUrlChange for consistency\n const hashChangeHandler = () => {\n emitUrlChange();\n };\n\n // 1. if explicitly enable polling → use polling\n if (enablePolling) {\n // Use polling (covers everything)\n const urlChangeInterval = globalScope.setInterval(() => {\n emitUrlChange();\n }, pollingInterval);\n\n // Emit initial URL immediately\n emitUrlChange();\n\n // Return cleanup function to stop polling\n return () => {\n if (urlChangeInterval) {\n globalScope.clearInterval(urlChangeInterval);\n }\n };\n }\n\n // 2. if polling not enabled → check history, if exist, use history\n if (globalScope.history) {\n // Use History API + hashchange (covers History API + hash routing)\n // Store original history methods for restoration during cleanup\n const originalPushState = globalScope.history.pushState.bind(globalScope.history);\n const originalReplaceState = globalScope.history.replaceState.bind(globalScope.history);\n\n /**\n * Sets up history method patching to intercept pushState and replaceState calls\n * This ensures URL changes are detected even when history methods are called programmatically\n * Includes patch detection to prevent double-patching by the same plugin\n */\n const setupHistoryPatching = (): void => {\n // Check if we already patched these methods\n if (\n (globalScope.history.pushState as typeof globalScope.history.pushState & { [PATCH_MARKER]?: boolean })[\n PATCH_MARKER\n ]\n ) {\n // Already patched by this plugin, skip patching\n return;\n }\n\n // Patch pushState to emit URL change events\n globalScope.history.pushState = createHistoryMethodPatch(originalPushState);\n\n // Patch replaceState to emit URL change events\n globalScope.history.replaceState = createHistoryMethodPatch(originalReplaceState);\n };\n\n // Apply history method patches\n setupHistoryPatching();\n\n // Listen to popstate events for browser back/forward navigation\n globalScope.addEventListener('popstate', emitUrlChange);\n // Listen to hashchange events for hash routing\n globalScope.addEventListener('hashchange', hashChangeHandler);\n\n // Emit initial URL immediately\n emitUrlChange();\n\n // Return cleanup function to restore original state\n return () => {\n // Restore original history methods - cannot be done here\n // because the plugin is not aware of the history methods modified by other plugins\n // so we need to set a flag on the globalScope to prevent further emitUrlChange calls from patched methods\n // Set reset flag on globalScope to prevent further emitUrlChange calls from patched methods\n (globalScope as Record<string, any>)[RESET_FLAG_KEY] = true;\n\n // Remove popstate event listener\n globalScope.removeEventListener('popstate', emitUrlChange);\n // Remove hashchange event listener\n globalScope.removeEventListener('hashchange', hashChangeHandler);\n };\n }\n\n // 3. if not, then the framework is probably using hash router → do hash\n // Fallback: just hashchange (for pure hash routing)\n globalScope.addEventListener('hashchange', hashChangeHandler);\n emitUrlChange();\n return () => {\n globalScope.removeEventListener('hashchange', hashChangeHandler);\n };\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,2 +1,2 @@
1
- export declare const VERSION = "1.29.4";
1
+ export declare const VERSION = "1.29.5-zen-plus-zoning.0";
2
2
  //# sourceMappingURL=version.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"version.d.ts","sourceRoot":"","sources":["../../src/version.ts"],"names":[],"mappings":"AACA,eAAO,MAAM,OAAO,WAAW,CAAC"}
1
+ {"version":3,"file":"version.d.ts","sourceRoot":"","sources":["../../src/version.ts"],"names":[],"mappings":"AACA,eAAO,MAAM,OAAO,6BAA6B,CAAC"}
@@ -1,3 +1,3 @@
1
1
  // Autogenerated by `yarn version-file`. DO NOT EDIT
2
- export var VERSION = '1.29.4';
2
+ export var VERSION = '1.29.5-zen-plus-zoning.0';
3
3
  //# sourceMappingURL=version.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"version.js","sourceRoot":"","sources":["../../src/version.ts"],"names":[],"mappings":"AAAA,oDAAoD;AACpD,MAAM,CAAC,IAAM,OAAO,GAAG,QAAQ,CAAC","sourcesContent":["// Autogenerated by `yarn version-file`. DO NOT EDIT\nexport const VERSION = '1.29.4';\n"]}
1
+ {"version":3,"file":"version.js","sourceRoot":"","sources":["../../src/version.ts"],"names":[],"mappings":"AAAA,oDAAoD;AACpD,MAAM,CAAC,IAAM,OAAO,GAAG,0BAA0B,CAAC","sourcesContent":["// Autogenerated by `yarn version-file`. DO NOT EDIT\nexport const VERSION = '1.29.5-zen-plus-zoning.0';\n"]}