1import { Event } from '@sentry/types'; 2/* 3 * Error logging filtering - prevent users from submitting errors we do not care about, 4 * eg: specific error messages that are caused by extensions or other scripts 5 * out of our control, or the same error being reported many times. 6 */ 7 8// These exact error messages may be different depending on the browser! 9const ERRORS_TO_DISCARD = [ 10 // Filter out errors from extensions 11 'chrome-extension://', 12 'moz-extension://', 13 'safari-extension://', 14 // This error only appears in Safari 15 "undefined is not an object (evaluating 'window.__pad.performLoop')", 16 // This error appears in Firefox related to local storage and flooded our Sentry bandwidth 17 'SecurityError: The operation is insecure.', 18]; 19 20const REPORTED_ERRORS_KEY = 'sentry:reportedErrors'; 21const TIMESTAMP_KEY = 'sentry:errorReportingInit'; 22const ONE_DAY_MS = 24 * 60 * 60 * 1000; 23const HALF_HOUR_MS = 0.5 * 60 * 60 * 1000; 24 25export function preprocessSentryError(event: Event) { 26 const message = getMessage(event); 27 28 // Check if it's rate limited to avoid sending the same error over and over 29 if (isRateLimited(message || 'empty')) { 30 return null; 31 } 32 33 // If we don't know about this particular type of event then just pass it along 34 if (!message) { 35 return event; 36 } 37 38 // Discard any errors that we know we do not care about 39 if (ERRORS_TO_DISCARD.includes(message)) { 40 return null; 41 } 42 43 // Only attempt to check against cached reported messages if we have localStorage 44 try { 45 if (isLocalStorageAvailable()) { 46 // Clear the saved error messages every day 47 maybeResetReportedErrorsCache(); 48 49 // Bail out if we have reported the error already 50 if (userHasReportedErrorMessage(message)) { 51 return null; 52 } 53 54 saveReportedErrorMessage(message); 55 } 56 } catch { 57 // Ignore the local storage exceptions 58 return event; 59 } 60 61 return event; 62} 63 64// https://gist.github.com/paulirish/5558557 65function isLocalStorageAvailable(): boolean { 66 try { 67 if (!window.localStorage || localStorage === null || typeof localStorage === 'undefined') { 68 return false; 69 } 70 71 localStorage.setItem('localStorage:test', 'value'); 72 if (localStorage.getItem('localStorage:test') !== 'value') { 73 return false; 74 } 75 localStorage.removeItem('localStorage:test'); 76 return true; 77 } catch { 78 return false; 79 } 80} 81 82// https://github.com/getsentry/sentry-javascript/issues/435 83const rateLimiter: Record<string, number> = {}; 84function isRateLimited(message: string) { 85 if (rateLimiter[message] && rateLimiter[message] > Date.now()) { 86 return true; 87 } 88 89 rateLimiter[message] = Date.now() + HALF_HOUR_MS; 90 return false; 91} 92 93// Extract a stable event error message out of the Sentry event object 94function getMessage(event: Event) { 95 if (event.message) { 96 return event.message; 97 } 98 99 if (event.exception && event.exception.values) { 100 const value = event.exception.values[0].value; 101 if (value) { 102 return value; 103 } 104 } 105 106 return null; 107} 108 109function maybeResetReportedErrorsCache() { 110 const timestamp = parseInt(localStorage.getItem(TIMESTAMP_KEY) || '', 10); 111 const now = new Date().getTime(); 112 113 if (!timestamp) { 114 localStorage.setItem(TIMESTAMP_KEY, new Date().getTime().toString()); 115 } else if (now - timestamp >= ONE_DAY_MS) { 116 localStorage.removeItem(REPORTED_ERRORS_KEY); 117 localStorage.removeItem(TIMESTAMP_KEY); 118 } 119} 120 121function userHasReportedErrorMessage(message: string) { 122 const messages = getReportedErrorMessages(); 123 if (messages.includes(message)) { 124 return true; 125 } else { 126 return false; 127 } 128} 129 130function saveReportedErrorMessage(message: string) { 131 const messages = getReportedErrorMessages(); 132 localStorage.setItem(REPORTED_ERRORS_KEY, JSON.stringify([...messages, message])); 133} 134 135function getReportedErrorMessages(): string[] { 136 const messages = localStorage.getItem(REPORTED_ERRORS_KEY); 137 if (!messages) { 138 return []; 139 } 140 141 return JSON.parse(messages); 142} 143