PageSourceSearch

https://ebsa2019.com/wp-content/plugins/tbs-child-plugin/js/fathom-autofill.js?ver=1.0.0

js ebsa2019.com collected 2026-10-01 14:50:40 UTC 15,921 bytes, 512 lines download raw bytes

1/**
2 * Fathom Analytics Auto-Fill Handler
3 * Handles automated Fathom site ID creation and auto-filling
4 */
5
6(function() {
7    'use strict';
8    
9    // Configuration
10    const FATHOM_FIELD_SELECTORS = [
11        '#fathom_site_id',
12        'input[name="fathom_site_id"]',
13        'input[name="fathom_analytics_site_id"]',
14        'input[name="_fathom_analytics_site_id"]',
15        'input[name="analytics_fathom_site_id"]',
16        'input[name="fathom_tracking_code"]',
17        'input[name="fathom_code"]',
18        'input[name="fathom_tracking_id"]',
19        'input[name="fathom_site_code"]',
20        '[data-fathom-field]'
21    ];
22    
23    const API_ENDPOINT = '/wp-json/tbs-child/v1/fathom/site';
24    const DEBOUNCE_DELAY = 500;
25    const CACHE_DURATION = 300000; // 5 minutes
26    
27    // Global state
28    let isRequestInFlight = false;
29    let debounceTimer = null;
30    let fathomCache = {
31        id: null,
32        domain: null,
33        timestamp: null
34    };
35    
36    // Clear any invalid cached IDs on page load
37    function clearInvalidCache() {
38        if (fathomCache.id && !isValidFathomId(fathomCache.id)) {
39            console.log('Clearing invalid cached Fathom ID:', fathomCache.id);
40            fathomCache = { id: null, domain: null, timestamp: null };
41        }
42    }
43    
44    /**
45     * Initialize the Fathom auto-fill system
46     */
47    function initFathomAutoFill() {
48        // Wait for DOM to be ready
49        if (document.readyState === 'loading') {
50            document.addEventListener('DOMContentLoaded', setupFathomAutoFill);
51        } else {
52            setupFathomAutoFill();
53        }
54    }
55    
56    /**
57     * Setup Fathom auto-fill functionality
58     */
59    function setupFathomAutoFill() {
60        // Clear any invalid cached IDs first
61        clearInvalidCache();
62        
63        const fathomField = findFathomField();
64        
65        if (!fathomField) {
66            console.log('TBS Fathom Auto-fill: No Fathom field found');
67            return;
68        }
69        
70        console.log('TBS Fathom Auto-fill: Found Fathom field', fathomField);
71        
72        // Add status indicator
73        addStatusIndicator(fathomField);
74        
75        // Auto-fill on page load if field is empty
76        if (!fathomField.value.trim()) {
77            autoFillFathomId(fathomField);
78        } else {
79            // Field already has value, show connected status
80            showStatus('connected', 'Connected ✓ (' + fathomField.value.substr(-6) + ')');
81        }
82        
83        // Add refresh button
84        addRefreshButton(fathomField);
85        
86        
87        // Monitor field changes to prevent overwrites
88        monitorFieldChanges(fathomField);
89    }
90    
91    /**
92     * Find the Fathom field in the DOM
93     */
94    function findFathomField() {
95        for (const selector of FATHOM_FIELD_SELECTORS) {
96            const field = document.querySelector(selector);
97            if (field) {
98                return field;
99            }
100        }
101        return null;
102    }
103    
104    /**
105     * Auto-fill Fathom ID with proper guards
106     */
107    function autoFillFathomId(field, force = false) {
108        // Check if request is already in flight
109        if (isRequestInFlight && !force) {
110            console.log('TBS Fathom Auto-fill: Request already in flight, skipping');
111            return Promise.resolve();
112        }
113        
114        // Check if field already has a valid ID and we're not forcing
115        if (!force && field.value.trim() && isValidFathomId(field.value.trim())) {
116            console.log('TBS Fathom Auto-fill: Field already has valid ID');
117            showStatus('connected', 'Connected ✓ (' + field.value.substr(-6) + ')');
118            return Promise.resolve();
119        }
120        
121        // Check cache first
122        const cachedId = getCachedFathomId();
123        if (cachedId && !force) {
124            console.log('TBS Fathom Auto-fill: Using cached ID');
125            setFieldValue(field, cachedId);
126            showStatus('connected', 'Connected ✓ (' + cachedId.substr(-6) + ')');
127            return Promise.resolve();
128        }
129        
130        // Clear any existing debounce timer
131        if (debounceTimer) {
132            clearTimeout(debounceTimer);
133        }
134        
135        // Debounce the request
136        return new Promise((resolve, reject) => {
137            debounceTimer = setTimeout(() => {
138                fetchFathomId(field).then(resolve).catch(reject);
139            }, DEBOUNCE_DELAY);
140        });
141    }
142    
143    /**
144     * Fetch Fathom ID from the API
145     */
146    async function fetchFathomId(field) {
147        if (isRequestInFlight) {
148            console.log('TBS Fathom Auto-fill: Request already in flight');
149            return;
150        }
151        
152        isRequestInFlight = true;
153        showStatus('creating', 'Creating...');
154        
155        try {
156            const domain = getCurrentDomain();
157            
158            const response = await fetch(API_ENDPOINT, {
159                method: 'POST',
160                headers: {
161                    'Content-Type': 'application/json',
162                    'X-WP-Nonce': getFathomNonce()
163                },
164                body: JSON.stringify({ domain })
165            });
166            
167            if (!response.ok) {
168                throw new Error(`HTTP ${response.status}: ${response.statusText}`);
169            }
170            
171            const data = await response.json();
172            
173            if (data.success && data.id) {
174                // Cache the result
175                cacheFathomId(data.id, domain);
176                
177                // Set the field value
178                setFieldValue(field, data.id);
179                
180                // Show success status
181                const statusText = 'Connected ✓ (' + data.id.substr(-6) + ')';
182                showStatus('connected', statusText);
183                
184                console.log('TBS Fathom Auto-fill: Success', data);
185                
186            } else {
187                // Don't auto-fill if no ID found - let user create via CRM
188                showStatus('info', 'Create via CRM');
189                console.log('TBS Fathom Auto-fill: No Fathom ID found, create via CRM');
190            }
191            
192        } catch (error) {
193            console.error('TBS Fathom Auto-fill: Error', error);
194            showStatus('error', 'Retry');
195            
196            // Don't clear existing value on error
197            if (!field.value.trim()) {
198                showToast('Couldn\'t fetch Fathom ID. Try again.', 'error');
199            }
200            
201        } finally {
202            isRequestInFlight = false;
203        }
204    }
205    
206    /**
207     * Set field value and trigger events
208     */
209    function setFieldValue(field, value) {
210        if (field.value === value) {
211            return; // No change needed
212        }
213        
214        field.value = value;
215        
216        // Mark field as changed/dirty
217        field.setAttribute('data-fathom-autofilled', 'true');
218        
219        // Trigger change events for form frameworks
220        const events = ['input', 'change', 'blur'];
221        events.forEach(eventType => {
222            const event = new Event(eventType, { bubbles: true, cancelable: true });
223            field.dispatchEvent(event);
224        });
225        
226        // Trigger jQuery events if jQuery is available
227        if (window.jQuery && window.jQuery(field).length) {
228            window.jQuery(field).trigger('change').trigger('input');
229        }
230        
231        console.log('TBS Fathom Auto-fill: Field value set and events triggered', value);
232    }
233    
234    /**
235     * Add status indicator next to the field
236     */
237    function addStatusIndicator(field) {
238        // Check if status indicator already exists
239        if (field.parentNode.querySelector('.fathom-status')) {
240            return;
241        }
242        
243        const status = document.createElement('span');
244        status.className = 'fathom-status';
245        status.style.cssText = `
246            margin-left: 8px;
247            padding: 4px 8px;
248            border-radius: 4px;
249            font-size: 12px;
250            font-weight: 500;
251            display: inline-block;
252            min-width: 60px;
253            text-align: center;
254        `;
255        
256        // Insert after the field
257        field.parentNode.insertBefore(status, field.nextSibling);
258    }
259    
260    /**
261     * Add refresh button
262     */
263    function addRefreshButton(field) {
264        // Check if refresh button already exists
265        if (field.parentNode.querySelector('.fathom-refresh')) {
266            return;
267        }
268        
269        const button = document.createElement('button');
270        button.type = 'button';
271        button.className = 'fathom-refresh button button-secondary';
272        button.textContent = 'Get Fathom ID';
273        button.style.cssText = `
274            margin-left: 8px;
275            padding: 4px 12px;
276            font-size: 12px;
277            height: auto;
278        `;
279        
280        button.addEventListener('click', (e) => {
281            e.preventDefault();
282            autoFillFathomId(field, true); // Force refresh
283        });
284        
285        // Insert after status or field
286        const status = field.parentNode.querySelector('.fathom-status');
287        const insertAfter = status || field;
288        insertAfter.parentNode.insertBefore(button, insertAfter.nextSibling);
289    }
290    
291    
292    /**
293     * Show status with appropriate styling
294     */
295    function showStatus(type, text) {
296        const status = document.querySelector('.fathom-status');
297        if (!status) return;
298        
299        status.textContent = text;
300        status.className = 'fathom-status fathom-status-' + type;
301        
302        // Apply type-specific styles
303        const baseStyle = `
304            margin-left: 8px;
305            padding: 4px 8px;
306            border-radius: 4px;
307            font-size: 12px;
308            font-weight: 500;
309            display: inline-block;
310            min-width: 60px;
311            text-align: center;
312        `;
313        
314        const typeStyles = {
315            creating: 'background: #f0f0f1; color: #646970;',
316            connected: 'background: #d1e7dd; color: #0f5132;',
317            error: 'background: #f8d7da; color: #721c24; cursor: pointer;'
318        };
319        
320        status.style.cssText = baseStyle + (typeStyles[type] || '');
321        
322        // Make error status clickable to retry
323        if (type === 'error') {
324            status.style.cursor = 'pointer';
325            status.title = 'Click to retry';
326            status.onclick = () => {
327                const field = findFathomField();
328                if (field) {
329                    autoFillFathomId(field, true);
330                }
331            };
332        } else {
333            status.onclick = null;
334            status.title = '';
335        }
336    }
337    
338    /**
339     * Monitor field changes to prevent overwrites
340     */
341    function monitorFieldChanges(field) {
342        let lastValue = field.value;
343        
344        const checkChanges = () => {
345            if (field.value !== lastValue) {
346                lastValue = field.value;
347                
348                if (field.value.trim() && isValidFathomId(field.value.trim())) {
349                    showStatus('connected', 'Connected ✓ (' + field.value.substr(-6) + ')');
350                } else if (!field.value.trim()) {
351                    showStatus('', '');
352                }
353            }
354        };
355        
356        field.addEventListener('input', checkChanges);
357        field.addEventListener('change', checkChanges);
358        field.addEventListener('paste', () => setTimeout(checkChanges, 10));
359    }
360    
361    /**
362     * Validate Fathom ID format
363     */
364    function isValidFathomId(id) {
365        // Basic format validation
366        if (!/^[A-Z0-9]{8}$/.test(id)) {
367            return false;
368        }
369        
370        // Must contain both letters and numbers
371        if (!/[A-Z]/.test(id) || !/[0-9]/.test(id)) {
372            return false;
373        }
374        
375        // Check against configurable invalid patterns
376        const invalidPatterns = getInvalidFathomPatterns();
377        for (const pattern of invalidPatterns) {
378            if (new RegExp(pattern.replace(/\//g, '')).test(id)) {
379                return false;
380            }
381        }
382        
383        return true;
384    }
385    
386    /**
387     * Get invalid Fathom ID patterns (configurable)
388     */
389    function getInvalidFathomPatterns() {
390        // Default patterns that are unlikely to be valid Fathom IDs
391        const defaultPatterns = [
392            '^[0-9]{8}$',      // All digits
393            '^[A-Z]{8}$',      // All letters
394            '^(TEST|DEMO|FAKE|NULL|VOID)', // Test values
395            '^MGCGAZMU$',      // Block old hardcoded ID
396            '^3X8IDTFI$',      // Block current fake ID
397        ];
398        
399        // Allow customization via window object
400        if (window.tbsInvalidFathomPatterns && Array.isArray(window.tbsInvalidFathomPatterns)) {
401            return [...defaultPatterns, ...window.tbsInvalidFathomPatterns];
402        }
403        
404        return defaultPatterns;
405    }
406    
407    /**
408     * Get current domain for API request
409     */
410    function getCurrentDomain() {
411        return window.location.hostname;
412    }
413    
414    /**
415     * Get WordPress nonce for API requests
416     */
417    function getFathomNonce() {
418        // Try to get nonce from various sources
419        const sources = [
420            () => window.fathomNonce,
421            () => window.tbsAjax?.nonce,
422            () => window.pbnAjax?.nonce,
423            () => document.querySelector('meta[name="wp-nonce"]')?.content,
424            () => document.querySelector('#_wpnonce')?.value
425        ];
426        
427        for (const source of sources) {
428            try {
429                const nonce = source();
430                if (nonce) return nonce;
431            } catch (e) {
432                // Continue to next source
433            }
434        }
435        
436        return ''; // Endpoint doesn't require nonce
437    }
438    
439    /**
440     * Cache Fathom ID
441     */
442    function cacheFathomId(id, domain) {
443        fathomCache = {
444            id,
445            domain,
446            timestamp: Date.now()
447        };
448    }
449    
450    /**
451     * Get cached Fathom ID if valid
452     */
453    function getCachedFathomId() {
454        const now = Date.now();
455        const domain = getCurrentDomain();
456        
457        if (fathomCache.id && 
458            fathomCache.domain === domain && 
459            fathomCache.timestamp && 
460            (now - fathomCache.timestamp) < CACHE_DURATION) {
461            return fathomCache.id;
462        }
463        
464        return null;
465    }
466    
467    /**
468     * Show toast notification
469     */
470    function showToast(message, type = 'info') {
471        // Create toast element
472        const toast = document.createElement('div');
473        toast.className = 'fathom-toast fathom-toast-' + type;
474        toast.textContent = message;
475        toast.style.cssText = `
476            position: fixed;
477            top: 20px;
478            right: 20px;
479            padding: 12px 16px;
480            border-radius: 4px;
481            font-size: 14px;
482            font-weight: 500;
483            z-index: 999999;
484            box-shadow: 0 2px 8px rgba(0,0,0,0.15);
485            ${type === 'error' ? 'background: #dc3545; color: white;' : 'background: #17a2b8; color: white;'}
486        `;
487        
488        document.body.appendChild(toast);
489        
490        // Auto-remove after 3 seconds
491        setTimeout(() => {
492            if (toast.parentNode) {
493                toast.parentNode.removeChild(toast);
494            }
495        }, 3000);
496    }
497    
498    // Initialize when script loads
499    initFathomAutoFill();
500    
501    // Export for manual triggering if needed
502    window.TBSFathomAutoFill = {
503        init: initFathomAutoFill,
504        refresh: () => {
505            const field = findFathomField();
506            if (field) {
507                autoFillFathomId(field, true);
508            }
509        }
510    };
511    
512})();

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.