PageSourceSearch

https://www.mapcon.com/js/error-handler.sr45f33654.js

js mapcon.com collected 2026-09-24 11:32:59 UTC 18,497 bytes, 485 lines download raw bytes

1// Global JavaScript Error Handler for MAPCON Website
2// Traps JavaScript errors (especially from migrated content) and displays user-friendly messages
3
4(function() {
5    'use strict';
6
7    let errorCount = 0;
8    const MAX_ERRORS_TO_SHOW = 3;
9    const ERROR_DISPLAY_DURATION = 8000; // 8 seconds
10    let errorContainer = null;
11    let shownErrors = new Set();
12    let blazorReady = false;
13    let pendingErrors = [];
14    let pendingFlushHandle = null;
15    let hasLoggedInteropWait = false;
16
17    function canUseBlazorInterop() {
18        return !!(window.DotNet && typeof window.DotNet.invokeMethodAsync === 'function');
19    }
20
21    function isTransientInteropError(error) {
22        const message = String(error?.message || error || '');
23
24        return message.includes('No call dispatcher') ||
25               message.includes('There is no active circuit') ||
26               message.includes('Cannot send data if the connection is not in the') ||
27               message.includes('The circuit has disconnected') ||
28               message.includes('JSDisconnectedException');
29    }
30
31    function queueErrorForLater(errorEntry) {
32        const errorKey = `${errorEntry.message}:${errorEntry.source}:${errorEntry.lineno}:${errorEntry.colno}`;
33        const alreadyQueued = pendingErrors.some(err =>
34            `${err.message}:${err.source}:${err.lineno}:${err.colno}` === errorKey
35        );
36
37        if (!alreadyQueued) {
38            pendingErrors.push(errorEntry);
39        }
40
41        if (!hasLoggedInteropWait) {
42            hasLoggedInteropWait = true;
43            console.log('[MAPCON] Blazor runtime not ready - queueing JavaScript errors for later logging');
44        }
45
46        schedulePendingFlush();
47    }
48
49    function schedulePendingFlush(delay) {
50        if (pendingFlushHandle || pendingErrors.length === 0) {
51            return;
52        }
53
54        pendingFlushHandle = setTimeout(() => {
55            pendingFlushHandle = null;
56            processPendingErrors();
57        }, delay || 1000);
58    }
59
60    async function sendErrorToServer(errorEntry) {
61        if (!canUseBlazorInterop()) {
62            return false;
63        }
64
65        try {
66            await window.DotNet.invokeMethodAsync(
67                'MAPCONWebsiteBlazor',
68                'LogJavaScriptError',
69                errorEntry.message,
70                errorEntry.source,
71                errorEntry.lineno || 0,
72                errorEntry.colno || 0,
73                errorEntry.stack || ''
74            );
75
76            if (!blazorReady) {
77                blazorReady = true;
78                console.log('[MAPCON] Blazor runtime ready - processing pending errors');
79            }
80
81            hasLoggedInteropWait = false;
82            return true;
83        } catch (error) {
84            blazorReady = false;
85
86            if (isTransientInteropError(error)) {
87                return false;
88            }
89
90            console.warn('[MAPCON] Could not log error to server:', error?.message || error);
91            return false;
92        }
93    }
94
95    // Process all pending errors
96    async function processPendingErrors() {
97        if (pendingErrors.length === 0) {
98            return;
99        }
100
101        const queuedErrors = pendingErrors.slice();
102        pendingErrors = [];
103
104        console.log(`[MAPCON] Processing ${queuedErrors.length} pending error(s)`);
105
106        for (let index = 0; index < queuedErrors.length; index++) {
107            const sent = await sendErrorToServer(queuedErrors[index]);
108            if (!sent) {
109                pendingErrors = queuedErrors.slice(index).concat(pendingErrors);
110                schedulePendingFlush();
111                return;
112            }
113        }
114    }
115
116    // Log error to server
117    function logErrorToServer(message, source, lineno, colno, stack) {
118        const errorEntry = { message, source, lineno, colno, stack };
119
120        sendErrorToServer(errorEntry)
121            .then(sent => {
122                if (!sent) {
123                    queueErrorForLater(errorEntry);
124                    return;
125                }
126
127                const { errorType, errorDetail } = extractErrorDetails(message);
128                console.log(`[MAPCON] Error logged to server: ${errorType} - ${errorDetail}`);
129
130                if (pendingErrors.length > 0) {
131                    schedulePendingFlush(0);
132                }
133            })
134            .catch(error => {
135                console.warn('[MAPCON] Failed to invoke LogJavaScriptError:', error?.message || error);
136                queueErrorForLater(errorEntry);
137            });
138    }
139
140    // Create stub objects/functions for common legacy references to prevent errors
141    function createLegacyStubs() {
142        // Common legacy patterns that might be referenced
143        const legacyNames = [
144            'P7_OMMop', 'P7_OMMup', 'P7_MMldr', 'P7_MMop',
145            'P7_OMldr', 'P7_OMup', 'P7_MMup', 'P7_OCup',
146            'P7_OCop', 'P7_OCldr', 'pagePopup', 'pageTooltip',
147            'initPage', 'loadContent', 'setupHandlers'
148        ];
149
150        legacyNames.forEach(name => {
151            if (typeof window[name] === 'undefined') {
152                window[name] = function() {
153                    // Silent stub - does nothing but prevents ReferenceError
154                    if (window._legacyStubWarnings !== false) {
155                        console.log(`[MAPCON] Legacy function called: ${name} (stubbed for compatibility)`);
156                    }
157                };
158            }
159        });
160
161        // Flag to track that stubs were created
162        window._legacyStubsCreated = true;
163    }
164
165    // Initialize stubs early
166    createLegacyStubs();
167
168    // Initialize error container
169    function initErrorContainer() {
170        if (errorContainer) return true;
171
172        const parent = document.body || document.documentElement;
173        if (!parent) {
174            return false;
175        }
176
177        errorContainer = document.createElement('div');
178        errorContainer.id = 'js-error-container';
179        errorContainer.style.cssText = `
180            position: fixed;
181            top: 80px;
182            right: 20px;
183            z-index: 9999;
184            max-width: 400px;
185        `;
186        parent.appendChild(errorContainer);
187        return true;
188    }
189
190    // Extract meaningful error details from message
191    function extractErrorDetails(message) {
192        const msg = String(message || '');
193        let errorType = 'Unknown Error';
194        let errorDetail = '';
195        
196        // Check for "is not defined" errors
197        const notDefinedMatch = msg.match(/(.+?)\s+is not defined/i);
198        if (notDefinedMatch) {
199            errorType = 'Reference Error';
200            errorDetail = `Function or variable "${notDefinedMatch[1]}" does not exist`;
201        }
202        // Check for property read errors
203        else if (msg.match(/Cannot read propert(y|ies)/i)) {
204            const propMatch = msg.match(/property ['"]?(.+?)['"]? of/i);
205            if (propMatch) {
206                errorType = 'Property Error';
207                errorDetail = `Cannot access property "${propMatch[1]}"`;
208            } else {
209                errorType = 'Property Error';
210                errorDetail = 'Cannot access property of undefined or null';
211            }
212        }
213        // Check for jQuery errors
214        else if (msg.includes('$') || msg.toLowerCase().includes('jquery')) {
215            errorType = 'jQuery Error';
216            errorDetail = 'jQuery library not available';
217        }
218        // Generic error
219        else {
220            errorType = 'Script Error';
221            errorDetail = msg.substring(0, 80) + (msg.length > 80 ? '...' : '');
222        }
223        
224        return { errorType, errorDetail };
225    }
226
227    // Create error notification element
228    function createErrorNotification(message, errorType, errorDetail, source) {
229        const notification = document.createElement('div');
230        notification.style.cssText = `
231            background: linear-gradient(135deg, #f8d7da 0%, #f5c2c7 100%);
232            border: 1px solid #f1aeb5;
233            border-left: 4px solid #dc3545;
234            border-radius: 8px;
235            padding: 16px;
236            margin-bottom: 12px;
237            box-shadow: 0 4px 12px rgba(0,0,0,0.15);
238            animation: slideInRight 0.3s ease-out;
239            font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
240        `;
241
242        notification.innerHTML = `
243            <div style="display: flex; align-items: start; gap: 12px;">
244                <div style="color: #dc3545; flex-shrink: 0; font-size: 20px;">&#9888;</div>
245                <div style="flex: 1; min-width: 0;">
246                    <div style="color: #842029; font-weight: 600; font-size: 14px; margin-bottom: 6px;">
247                        Legacy Content Issue
248                    </div>
249                    <div style="color: #58151c; font-size: 13px; line-height: 1.5; margin-bottom: 8px;">
250                        ${message}
251                    </div>
252                    <div style="color: #721c24; font-size: 12px; line-height: 1.4; margin-bottom: 4px; font-weight: 500;">
253                        ${errorType}: ${errorDetail}
254                    </div>
255                    <div style="color: #6c757d; font-size: 11px; font-family: monospace; word-break: break-all;">
256                        ${source}
257                    </div>
258                </div>
259                <button onclick="this.parentElement.parentElement.remove()" 
260                        style="background: none; border: none; color: #842029; cursor: pointer; font-size: 20px; line-height: 1; padding: 0; margin-left: 8px; flex-shrink: 0;"
261                        aria-label="Close">&times;</button>
262            </div>
263        `;
264
265        return notification;
266    }
267
268    // Handle JavaScript errors
269    function handleError(message, source, lineno, colno, error) {
270        // Ensure we have valid strings to work with
271        const safeMessage = String(message || '');
272        const safeSource = String(source || 'unknown');
273        
274        // FILTER OUT: ResizeObserver loop errors (benign, not actionable)
275        if (safeMessage.includes('ResizeObserver loop') || 
276            safeMessage.includes('ResizeObserver loop completed with undelivered notifications') ||
277            safeMessage.includes('ResizeObserver loop limit exceeded')) {
278            // This is a known browser behavior when ResizeObserver triggers rapid changes
279            // It's not a real error and doesn't affect functionality
280            // Common with: Calendly iframes, Google reCAPTCHA, dynamic form elements
281            console.log('[MAPCON] Suppressing benign ResizeObserver loop notification (not a real error)');
282            return true; // Suppress
283        }
284        
285        // FILTER OUT: Completely useless errors with no information
286        if (!safeMessage || safeMessage === '' || safeMessage === 'Script error.' || safeMessage === 'Script error') {
287            // These are cross-origin errors blocked by CORS - we can't get details
288            // Don't log or show these - they're not useful
289            console.log('[MAPCON] Ignoring cross-origin script error (no details available due to CORS)');
290            return true;
291        }
292        
293        // FILTER OUT: Errors with literally no information
294        if (safeMessage.trim() === '' && safeSource === 'unknown' && (!lineno || lineno === 0)) {
295            console.log('[MAPCON] Ignoring empty error event with no details');
296            return true;
297        }
298        
299        // Avoid duplicate errors
300        const errorKey = `${safeMessage}:${safeSource}`;
301        if (shownErrors.has(errorKey)) {
302            return true; // Already shown this error
303        }
304
305        errorCount++;
306        shownErrors.add(errorKey);
307
308        // Extract error details for better reporting
309        const { errorType, errorDetail } = extractErrorDetails(safeMessage);
310
311        // Add more context for logging
312        const errorContext = {
313            message: safeMessage,
314            source: safeSource,
315            lineno: lineno || 0,
316            colno: colno || 0,
317            stack: error?.stack || '',
318            // Additional context
319            url: window.location.href,
320            userAgent: navigator.userAgent.substring(0, 100),
321            timestamp: new Date().toISOString()
322        };
323
324        // Log to Blazor - will queue if not ready yet
325        logErrorToServer(
326            safeMessage, 
327            safeSource, 
328            lineno || 0, 
329            colno || 0, 
330            error?.stack || `URL: ${window.location.href} | UA: ${navigator.userAgent.substring(0, 100)}`
331        );
332
333        // Show notification to user (limit to MAX_ERRORS_TO_SHOW)
334        if (errorCount <= MAX_ERRORS_TO_SHOW) {
335            if (!initErrorContainer()) {
336                return true;
337            }
338            
339            const friendlyMessage = getFriendlyMessage(safeMessage);
340            const shortSource = formatSource(safeSource, lineno, colno);
341            
342            const notification = createErrorNotification(friendlyMessage, errorType, errorDetail, shortSource);
343            errorContainer.appendChild(notification);
344
345            // Auto-remove after duration
346            setTimeout(() => {
347                notification.style.animation = 'slideOutRight 0.3s ease-in';
348                setTimeout(() => notification.remove(), 300);
349            }, ERROR_DISPLAY_DURATION);
350        } else if (errorCount === MAX_ERRORS_TO_SHOW + 1) {
351            // Show "too many errors" message
352            if (!initErrorContainer()) {
353                return true;
354            }
355            const notification = createErrorNotification(
356                'Multiple legacy content issues detected. The page may not display as originally designed. Please contact support if you need assistance.',
357                'Multiple Errors',
358                'Error limit reached',
359                'Multiple sources'
360            );
361            errorContainer.appendChild(notification);
362        }
363
364        // Suppress the error from appearing in console as unhandled
365        return true;
366    }
367
368    // Format source information for display
369    function formatSource(source, lineno, colno) {
370        const fileName = source.split('/').pop().substring(0, 50);
371        let location = fileName;
372        
373        if (lineno && lineno > 0) {
374            location += `:${lineno}`;
375            if (colno && colno > 0) {
376                location += `:${colno}`;
377            }
378        }
379        
380        return location || 'unknown';
381    }
382
383    // Generate friendly error messages
384    function getFriendlyMessage(message) {
385        // Ensure message is a string
386        const msg = String(message || '');
387        
388        if (msg.includes('is not defined')) {
389            return 'This page contains references to outdated CSS classes or scripts from the old website. The page is being migrated and may not display exactly as before.';
390        } else if (msg.includes('Cannot read property') || msg.includes('Cannot read properties')) {
391            return 'Some legacy page elements are not available in the new design system. Functionality may differ from the original page.';
392        } else if (msg.includes('$ is not defined') || msg.includes('jQuery')) {
393            return 'This page uses jQuery which is being phased out. Some interactive features may need updating.';
394        } else {
395            return 'A legacy content issue was detected. The page may not display exactly as originally designed.';
396        }
397    }
398
399    // Add CSS animations
400    const style = document.createElement('style');
401    style.textContent = `
402        @keyframes slideInRight {
403            from {
404                transform: translateX(400px);
405                opacity: 0;
406            }
407            to {
408                transform: translateX(0);
409                opacity: 1;
410            }
411        }
412        
413        @keyframes slideOutRight {
414            from {
415                transform: translateX(0);
416                opacity: 1;
417            }
418            to {
419                transform: translateX(400px);
420                opacity: 0;
421            }
422        }
423    `;
424    document.head.appendChild(style);
425
426    // Set up global error handler with capture phase to catch errors early
427    window.addEventListener('error', function(event) {
428        const handled = handleError(
429            event.message,
430            event.filename,
431            event.lineno,
432            event.colno,
433            event.error
434        );
435        
436        if (handled) {
437            event.preventDefault(); // Prevent the error from propagating
438            event.stopImmediatePropagation(); // Stop other handlers
439        }
440        
441        return handled;
442    }, true); // Use capture phase
443
444    // Handle unhandled promise rejections
445    window.addEventListener('unhandledrejection', function(event) {
446        const handled = handleError(
447            `Unhandled Promise Rejection: ${event.reason}`,
448            'Promise',
449            0,
450            0,
451            null
452        );
453        
454        if (handled) {
455            event.preventDefault();
456        }
457    });
458
459    // Expose error clearing function for page transitions
460    window.clearJavaScriptErrors = function() {
461        errorCount = 0;
462        shownErrors.clear();
463        if (errorContainer) {
464            errorContainer.innerHTML = '';
465        }
466        // Recreate stubs for new page
467        createLegacyStubs();
468    };
469
470    // Expose function to disable stub warnings (for production)
471    window.disableLegacyStubWarnings = function() {
472        window._legacyStubWarnings = false;
473    };
474
475    // Expose function to add custom stubs dynamically
476    window.addLegacyStub = function(name) {
477        if (typeof window[name] === 'undefined') {
478            window[name] = function() {
479                console.log(`[MAPCON] Legacy function called: ${name} (stubbed for compatibility)`);
480            };
481        }
482    };
483
484    console.log('[MAPCON] JavaScript error handler initialized with legacy stubs');
485})();

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.