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;">⚠</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">×</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.