Zendesk — Page Event Detector v1.0.1
Tests techniques for detecting Zendesk page events using the Navigation API and MutationObserver. Logs to the console when navigation occurs, identifies the page type (ticket, filter, or other), and registers a tightly-scoped MutationObserver to detect when the new page has fully loaded. Also processes the page type on initial load without waiting for a navigation event.
Script Content
// ==UserScript==
// @name Zendesk: Page Event Detector
// @namespace https://www.timhilton.xyz/user-scripts
// @version 1.0.1
// @description Tests techniques for detecting Zendesk page events using the Navigation API and MutationObserver.
// @author Tim Hilton using GitHub Copilot
// @match https://*.zendesk.com/agent/*
// @grant none
// ==/UserScript==
(function() {
'use strict';
const LOG_PREFIX = '[Zendesk: Page Event Detector]';
console.debug(`${LOG_PREFIX} Script initialised`);
function getPageType(url) {
if (/\/agent\/tickets\/\d+/.test(url)) {
return 'ticket';
}
if (/\/agent\/(filters|views)\/\d+/.test(url)) {
return 'filter';
}
return 'other';
}
let ticketObserverCallCount = 0;
let filterObserverCallCount = 0;
function waitForTicketLoad(url) {
const mainEl = document.querySelector('main') || document.body;
let callCount = 0;
const observer = new MutationObserver((_mutations, obs) => {
callCount++;
ticketObserverCallCount++;
console.debug(`${LOG_PREFIX} Ticket MutationObserver fired (call #${callCount}, total #${ticketObserverCallCount})`);
if (document.querySelector('div[data-ticket-id]')) {
const ticketId = url.match(/\/tickets\/(\d+)/)?.[1] ?? 'unknown';
console.log(`${LOG_PREFIX} ✅ Ticket page fully loaded — ticket ID: ${ticketId}, observer fired ${callCount} time(s): ${url}`);
obs.disconnect();
console.debug(`${LOG_PREFIX} Ticket MutationObserver disconnected`);
}
});
observer.observe(mainEl, { childList: true, subtree: true });
console.debug(`${LOG_PREFIX} Ticket MutationObserver registered on <${mainEl.tagName.toLowerCase()}>`);
}
function waitForFilterLoad(url) {
const mainEl = document.querySelector('main') || document.body;
let callCount = 0;
const observer = new MutationObserver((_mutations, obs) => {
callCount++;
filterObserverCallCount++;
console.debug(`${LOG_PREFIX} Filter MutationObserver fired (call #${callCount}, total #${filterObserverCallCount})`);
if (document.querySelector('[data-test-id="views-list"], [data-garden-id="tables.table"]')) {
console.log(`${LOG_PREFIX} ✅ Filter page fully loaded — observer fired ${callCount} time(s): ${url}`);
obs.disconnect();
console.debug(`${LOG_PREFIX} Filter MutationObserver disconnected`);
}
});
observer.observe(mainEl, { childList: true, subtree: true });
console.debug(`${LOG_PREFIX} Filter MutationObserver registered on <${mainEl.tagName.toLowerCase()}>`);
}
function handleNavigation(url) {
const pageType = getPageType(url);
console.debug(`${LOG_PREFIX} Handling navigation to ${pageType} page: ${url}`);
console.log(`${LOG_PREFIX} 📍 Page type: ${pageType} — ${url}`);
if (pageType === 'ticket') {
waitForTicketLoad(url);
} else if (pageType === 'filter') {
waitForFilterLoad(url);
}
}
console.debug(`${LOG_PREFIX} Processing initial page URL: ${window.location.href}`);
handleNavigation(window.location.href);
if (window.navigation) {
console.debug(`${LOG_PREFIX} Navigation API available, registering listener`);
window.navigation.addEventListener('navigate', (event) => {
const url = event.destination.url;
console.debug(`${LOG_PREFIX} Navigation API event fired for: ${url}`);
handleNavigation(url);
});
console.log(`${LOG_PREFIX} ✅ Navigation API listener registered`);
} else {
console.log(`${LOG_PREFIX} ❌ Navigation API not available in this browser`);
}
})();