xref: /expo/docs/common/sentry-utilities.ts (revision efd75dec)
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 = 60 * 60 * 24 * 1000;
23
24export function preprocessSentryError(event: Event) {
25  const message = getMessage(event);
26
27  // If we don't know about this particular type of event then just pass it along
28  if (!message) {
29    return event;
30  }
31
32  // Discard any errors that we know we do not care about
33  if (ERRORS_TO_DISCARD.includes(message)) {
34    return null;
35  }
36
37  // Only attempt to check against cached reported messages if we have localStorage
38  if (isLocalStorageAvailable()) {
39    // Clear the saved error messages every day
40    maybeResetReportedErrorsCache();
41
42    // Bail out if we have reported the error already
43    if (userHasReportedErrorMessage(message)) {
44      return null;
45    }
46
47    saveReportedErrorMessage(message);
48  }
49
50  return event;
51}
52
53// https://gist.github.com/paulirish/5558557
54function isLocalStorageAvailable(): boolean {
55  try {
56    if (!window.localStorage) {
57      return false;
58    }
59
60    localStorage.setItem('localStorage:test', 'value');
61    localStorage.removeItem('localStorage:test');
62    return true;
63  } catch {
64    return false;
65  }
66}
67
68// Extract a stable event error message out of the Sentry event object
69function getMessage(event: Event) {
70  if (event.message) {
71    return event.message;
72  }
73
74  if (event.exception && event.exception.values) {
75    const value = event.exception.values[0].value;
76    if (value) {
77      return value;
78    }
79  }
80
81  return null;
82}
83
84function maybeResetReportedErrorsCache() {
85  const timestamp = parseInt(localStorage.getItem(TIMESTAMP_KEY) || '', 10);
86  const now = new Date().getTime();
87
88  if (!timestamp) {
89    localStorage.setItem(TIMESTAMP_KEY, new Date().getTime().toString());
90  } else if (now - timestamp >= ONE_DAY_MS) {
91    localStorage.removeItem(REPORTED_ERRORS_KEY);
92    localStorage.removeItem(TIMESTAMP_KEY);
93  }
94}
95
96function userHasReportedErrorMessage(message: string) {
97  const messages = getReportedErrorMessages();
98  if (messages.includes(message)) {
99    return true;
100  } else {
101    return false;
102  }
103}
104
105function saveReportedErrorMessage(message: string) {
106  const messages = getReportedErrorMessages();
107  localStorage.setItem(REPORTED_ERRORS_KEY, JSON.stringify([...messages, message]));
108}
109
110function getReportedErrorMessages(): string[] {
111  const messages = localStorage.getItem(REPORTED_ERRORS_KEY);
112  if (!messages) {
113    return [];
114  }
115
116  return JSON.parse(messages);
117}
118