Forum Replies Created

Viewing 5 replies - 1 through 5 (of 5 total)
  • Thread Starter vbk2025

    (@vbk2025)

    Hi Mostafa

    I does fix my issues 🙂
    Hope it’s compatible with the rest of the WP Statistics functionality 🙂

    Thread Starter vbk2025

    (@vbk2025)

    I haven’t tested it, but maybe something like this?

    history.replaceState = function (…args) {
    const result = t.originalReplaceState.apply(this, args);
    t.handleUrlChange();
    // Dispatch a native-like event so other scripts can hook into this
    const event = new Event('replaceState');
    event.arguments = args;
    window.dispatchEvent(event);
    return result;
    };

    history.pushState = function (…args) {
    const result = t.originalPushState.apply(this, args);
    t.handleUrlChange();
    const event = new Event('pushState');
    event.arguments = args;
    window.dispatchEvent(event);
    return result;
    }

    Thread Starter vbk2025

    (@vbk2025)

    Hi Mostafa,

    Thanks again for getting back.

    I’ve updated the test code and confirmed that it works correctly with WP Statistics enabled on a clean WP install (tested here via a simple HTML block):
    https://wpstatistics.verdensbedstekunder.dk/

    However, in my main/production projects, I’m still running into issues – specifically, my listener on replaceState doesn’t fire consistently.

    What I’ve observed:
    My custom script loads after tracker.js, using type="module". Even when I load it before tracker.js (and not as a module), I still see the same problem.

    My script wraps history.replaceState to dispatch a custom or native-like event – this works at first, but stops working after WP Statistics runs.

    It looks like tracker.js calls WpStatisticsUserTracker.init(), which in turn calls trackUrlChange() — and each time this happens, it does:

    history.replaceState = function() {
    originalReplaceState.apply(history, arguments);
    handleUrlChange();
    }

    That seems to overwrite my wrapped replaceState, which explains why my event handler no longer fires.

    A fix that works in my production sites

    If I use this script at the very top of my scripts, then my original history.replaceState callbacks works (again)

    (function trapHistoryMethods() {
    const wrap = (type) => {
    let current = history[type];

    Object.defineProperty(history, type, {
    configurable: true,
    get() {
    return current;
    },
    set(fn) {
    if(typeof fn !== 'function') {
    current = fn;
    return;
    }

    // Wrap new setter
    current = function (...args) {
    const result = fn.apply(this, args);

    const event = new Event(type);
    event.arguments = args;
    window.dispatchEvent(event);

    return result;
    };
    console.log(
    ✅ Intercepted history.${type} setter);
    }
    });

    // Immediately wrap the current version
    history[type] = history[type];
    };

    wrap('replaceState');
    wrap('pushState');
    })();
    Thread Starter vbk2025

    (@vbk2025)

    /**
    * Test Script - URL Search Params Buttons
    *
    * This script creates 3 buttons that update URL search parameters
    * using history.replaceState to help test WP Statistics interference.
    */
    (function() {
    // Function to safely update URL search params
    function updateSearchParams(paramName, paramValue) {
    // Get current URL and create URL object for easy param manipulation
    const currentUrl = new URL(window.location.href);

    // Update the search parameter
    currentUrl.searchParams.set(paramName, paramValue);

    // Use replaceState to update the URL without reloading the page
    try {
    window.history.replaceState({}, '', currentUrl.toString());
    console.log(
    URL updated with ${paramName}=${paramValue});
    } catch (e) {
    console.error('Error updating URL:', e);
    }
    }

    // Function to create a styled button
    function createButton(text, paramName, paramValue) {
    const button = document.createElement('button');
    button.textContent = text;
    button.style.margin = '5px';
    button.style.padding = '8px 16px';
    button.style.backgroundColor = '#4CAF50';
    button.style.color = 'white';
    button.style.border = 'none';
    button.style.borderRadius = '4px';
    button.style.cursor = 'pointer';

    // Add click handler to update URL params
    button.addEventListener('click', () => {
    updateSearchParams(paramName, paramValue);
    });

    return button;
    }

    // Create container for buttons
    function createButtonContainer() {
    const container = document.createElement('div');
    container.style.position = 'fixed';
    container.style.top = '20px';
    container.style.right = '20px';
    container.style.backgroundColor = '#f8f9fa';
    container.style.padding = '10px';
    container.style.borderRadius = '8px';
    container.style.boxShadow = '0 2px 10px rgba(0,0,0,0.2)';
    container.style.zIndex = '9999';

    // Add title to container
    const title = document.createElement('div');
    title.textContent = 'URL Param Test Buttons';
    title.style.fontWeight = 'bold';
    title.style.marginBottom = '10px';
    title.style.borderBottom = '1px solid #ddd';
    title.style.paddingBottom = '5px';
    container.appendChild(title);

    return container;
    }

    // Create and add event monitor to show when replaceState events are triggered
    function createEventMonitor(container) {
    const monitor = document.createElement('div');
    monitor.style.fontSize = '12px';
    monitor.style.marginTop = '10px';
    monitor.style.padding = '5px';
    monitor.style.backgroundColor = '#f0f0f0';
    monitor.style.borderRadius = '4px';
    monitor.style.maxHeight = '100px';
    monitor.style.overflow = 'auto';
    monitor.textContent = 'Event Monitor: Waiting for events...';

    // Add event listeners to detect history method calls
    window.addEventListener('replaceState', () => {
    const time = new Date().toLocaleTimeString();
    monitor.textContent = ${time}: replaceState event detected!;
    monitor.style.color = 'green';

    // Reset after 3 seconds
    setTimeout(() => {
    monitor.style.color = 'black';
    }, 3000);
    });

    container.appendChild(monitor);
    return monitor;
    }

    // Function to initialize everything when DOM is ready
    function init() {
    // Create button container
    const container = createButtonContainer();

    // Create buttons
    const button1 = createButton('Set Filter: A', 'filter', 'option_a');
    const button2 = createButton('Set Filter: B', 'filter', 'option_b');
    const button3 = createButton('Set Page: 2', 'page', '2');

    // Add buttons to container
    container.appendChild(button1);
    container.appendChild(button2);
    container.appendChild(button3);

    // Create and add event monitor
    createEventMonitor(container);

    // Add container to body
    document.body.appendChild(container);

    console.log('Test buttons added to the page');
    }

    // Wait for DOM to be ready
    if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', init);
    } else {
    init();
    }

    })();

    Hi Mostafa. I can’t share my actual code, but I asked AI to generate some custom code that basically resemble the issues I’m having. 🙂

    If I have WP Statistics enabled, then window.addEventListener('replaceState', () => {}) is never called. 🙂

    Thread Starter vbk2025

    (@vbk2025)

    I have made a workaround where I dispatch a custom event that I use instead of the “replaceState”, but I wonder if there are any plans to change the behaviour of tracker.js ?

    const originalHandleUrlChange = window.WpStatisticsUserTracker.handleUrlChange;

    window.WpStatisticsUserTracker.handleUrlChange = function () {
    originalHandleUrlChange.apply(this, arguments);
    const searchParams = new URLSearchParams(window.location.search);
    const customEvent = new CustomEvent(“WP_STATISTICS_REPLACE_STATE_FIX”, {
    detail: {
    searchParams: searchParams.toString(),
    url: window.location.href
    }
    });
    window.dispatchEvent(customEvent);
    };

    window.addEventListener(‘WP_STATISTICS_REPLACE_STATE_FIX’, () => {})

Viewing 5 replies - 1 through 5 (of 5 total)