• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Freegle / iznik-nuxt3 / c116036c-55fb-4d4e-a732-06fbe0906fc1

13 Oct 2025 12:31PM UTC coverage: 34.681% (-11.0%) from 45.694%
c116036c-55fb-4d4e-a732-06fbe0906fc1

push

circleci

edwh
Migrate logo API call from v1 to v2

1053 of 3928 branches covered (26.81%)

Branch coverage included in aggregate %.

0 of 1 new or added line in 1 file covered. (0.0%)

723 existing lines in 55 files now uncovered.

2722 of 6957 relevant lines covered (39.13%)

38.05 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

9.12
/plugins/sentry.client.ts
1
import * as Sentry from '@sentry/vue'
2
import { Integrations } from '@sentry/tracing'
3
import {
4
  HttpClient as HttpClientIntegration,
5
  ExtraErrorData as ExtraErrorDataIntegration,
6
} from '@sentry/integrations'
7
import { defineNuxtPlugin, useRuntimeConfig } from '#app'
8
import { useRouter } from '#imports'
9
import { useMiscStore } from '~/stores/misc'
10
import { suppressException } from '~/composables/useSuppressException'
11

12
export default defineNuxtPlugin((nuxtApp) => {
6✔
13
  const config = useRuntimeConfig()
21✔
14
  const { vueApp } = nuxtApp
21✔
15
  const router = useRouter()
21✔
16

17
  window.onbeforeunload = function () {
21✔
18
    console.log('Window unloading...')
18✔
19
    useMiscStore().unloading = true
18✔
20
  }
21

22
  // If we initialise Sentry before CookieYes then it seems to attach a click handler which blocks clicking on the
23
  // CookieYes banner.
24
  function checkCMPComplete() {
21✔
25
    const runTimeConfig = useRuntimeConfig()
21✔
26

27
    // Skip Sentry initialization if DSN is empty (disabled)
28
    if (!config.public.SENTRY_DSN) {
21!
29
      console.log('Sentry disabled - skipping initialization')
×
30
      return
×
31
    }
32

33
    if (runTimeConfig.public.COOKIEYES && !window.weHaveLoadedGPT) {
21!
34
      setTimeout(checkCMPComplete, 100)
×
35
    } else {
36
      console.log('Init Sentry', config.public.SENTRY_DSN?.substring(0,12))
21!
37
      Sentry.init({
21✔
38
        app: [vueApp],
39
        dsn: config.public.SENTRY_DSN,
40
        // Some errors seem benign, and so we ignore them on the client side rather than clutter our sentry logs.
41
        ignoreErrors: [
42
          'ResizeObserver loop limit exceeded', // Benign - see https://stackoverflow.com/questions/49384120/resizeobserver-loop-limit-exceeded
43
          'ResizeObserver loop completed with undelivered notifications.',
44
          'Navigation cancelled from ', // This can happen if someone clicks twice in quick succession
45

46
          // These are very commonly errors caused by fetch() being aborted during page navigation.  See for example
47
          // https://forum.sentry.io/t/typeerror-failed-to-fetch-reported-over-and-overe/8447
48
          'TypeError: Failed to fetch',
49
          'TypeError: NetworkError when attempting to fetch resource.',
50
          'TypeError: Unable to preload',
51
          'Window closed',
52
        ],
53
        integrations: [
54
          new Integrations.BrowserTracing({
55
            routingInstrumentation: Sentry.vueRouterInstrumentation(router),
56
            tracePropagationTargets: ['localhost', 'ilovefreegle.org', 'modtools.org', /^\//],
57
          }),
58
          new HttpClientIntegration(),
59
          new ExtraErrorDataIntegration(),
60
        ],
61
        logErrors: false, // Note that this doesn't seem to work with nuxt 3
62
        tracesSampleRate: config.public.SENTRY_TRACES_SAMPLE_RATE || 1.0, // Sentry recommends adjusting this value in production
42✔
63
        debug: config.public.SENTRY_ENABLE_DEBUG || false, // Enable debug mode
42✔
64
        environment: config.public.ENVIRONMENT || 'dev', // Set environment
42✔
65
        // The following enables exceptions to be logged to console despite logErrors being set to false (preventing them from being passed to the default Vue err handler)
66
        beforeSend(event, hint) {
UNCOV
67
          if (useMiscStore()?.unloading) {
×
68
            // All network requests are aborted during unload, and so we'll get spurious errors.  Ignore them.
UNCOV
69
            console.log('Ignore error in unload')
×
UNCOV
70
            return null
×
71
          }
72

73
          // Ignore crawlers, which seems to abort pre-fetching of some assets.
UNCOV
74
          const userAgent = window.navigator.userAgent?.toLowerCase()
×
75

UNCOV
76
          if (
×
77
            userAgent.includes('bingpreview') ||
×
78
            userAgent.includes('bingbot') ||
79
            userAgent.includes('linespider') ||
80
            userAgent.includes('yisou')
81
          ) {
82
            return null
×
83
          }
84

85
          // HeadlessChrome triggers an error in Google sign-in.  It's not a real user.
UNCOV
86
          if (userAgent.includes('headlesschrome')) {
×
87
            return null
×
88
          }
89

90
          // Check if it is an exception, and if so, log it.
UNCOV
91
          if (event.exception) {
×
92
            console.error(
×
93
              `[Exeption for Sentry]: (${hint.originalException})`,
94
              {
95
                event,
96
                hint,
97
              }
98
            )
99
          }
100

UNCOV
101
          if (hint) {
×
UNCOV
102
            const originalException = hint?.originalException
×
UNCOV
103
            const originalExceptionString = originalException?.toString()
×
UNCOV
104
            const originalExceptionStack = originalException?.stack
×
UNCOV
105
            const originalExceptionMessage = originalException?.message
×
UNCOV
106
            const originalExceptionName = originalException?.name
×
107

108
            // Add some more detail if we can.
UNCOV
109
            if (originalException instanceof Event) {
×
110
              event.extra.isTrusted = originalException.isTrusted
×
111
              event.extra.detail = originalException.detail
×
112
              event.extra.type = originalException.type
×
113
            }
114

UNCOV
115
            console.log(
×
116
              'Original exception was',
117
              originalException,
118
              typeof originalException,
119
              originalExceptionString,
120
              originalExceptionStack,
121
              originalExceptionMessage
122
            )
123

UNCOV
124
            if (!originalException) {
×
125
              // There's basically no info to report, so there's nothing we can do.  Suppress it.
126
              console.log('No info - suppress exception')
×
127
              return null
×
UNCOV
128
            } else if (originalExceptionStack?.includes('frame_ant')) {
×
129
              // Chrome extension giving errors.
130
              return null
×
UNCOV
131
            } else if (originalExceptionStack?.includes('/gpt/')) {
×
132
              // Google ads are not our problem.
133
              console.log('Google ads - suppress exception')
×
UNCOV
134
              return null
×
135
            } else if (
136
              originalExceptionStack?.includes('Cannot unmountComponent') &&
×
137
              originalExceptionString?.includes('destructure')
×
138
            ) {
139
              console.log('Vue unmount error - suppress')
×
140
              return null
×
141
            } else if (
UNCOV
142
              originalExceptionStack?.includes('/pageFold/') ||
×
143
              originalExceptionStack?.includes('/strikeforce/') ||
×
144
              originalExceptionStack?.includes('/ads/js/')
×
145
            ) {
146
              // This is a flaky ad library
147
              console.log('Pagefold, ads - suppress exception')
×
148
              return null
×
149
            } else if (
UNCOV
150
              (originalExceptionStack?.includes('bootstrap-vue-next') &&
×
151
                originalExceptionString?.match('removeAttribute')) ||
×
152
              originalExceptionStack?.match('_isWithActiveTrigger ')
×
153
            ) {
154
              // This seems to be a bug in bootstrap, and doesn't affect the user.
155
              console.log('Suppress Bootstrap tooltip exception')
×
156
              return null
×
UNCOV
157
            } else if (originalExceptionString?.match(/Down for maintenance/)) {
×
158
              console.log('Maintenance - suppress exception', this)
×
159
              return null
×
160
            } else if (
UNCOV
161
              originalExceptionString?.match(/Piwik undefined after waiting/)
×
162
            ) {
163
              // Some privacy blockers can cause this.
164
              console.log('Suppress Piwik/Matomo exception')
×
165
              return null
×
166
            } else if (
UNCOV
167
              originalExceptionString?.match(/Google ad script blocked/)
×
168
            ) {
169
              console.log('AdBlocker - no need to log.', this)
×
UNCOV
170
              return null
×
171
            } else if (
172
              originalExceptionString?.match(
×
173
                /Attempt to use history.replaceState/
174
              )
175
            ) {
176
              console.log('History.replaceState too often')
×
UNCOV
177
              return null
×
178
            } else if (suppressException(originalException)) {
179
              console.log('Suppress exception')
×
UNCOV
180
              return null
×
181
            } else if (originalExceptionName === 'TypeError') {
182
              console.log('TypeError')
×
183
              if (
×
184
                originalExceptionMessage?.match(
×
185
                  /can't redefine non-configurable property "userAgent"/
186
                )
187
              ) {
188
                // This exception happens a lot, and the best guess I can find is that it is a bugged browser
189
                // extension.
190
                console.log('Suppress userAgent')
×
191
                return null
×
192
              } else if (originalExceptionMessage?.match(/cancelled/)) {
×
193
                // This probably happens due to the user changing their mind and navigating away immediately.
194
                console.log('Suppress cancelled')
×
195
                return null
×
196
              } else if (
197
                originalExceptionString?.match(/Object.checkLanguage/)
×
198
              ) {
199
                // Some auto translation thing.
200
                console.log('Translate exception cancelled')
×
201
                return null
×
202
              } else if (originalExceptionString?.match(/\/js\/gpt/)) {
×
203
                // Error inside Google Ads.
204
                console.log('Ad error ignored')
×
205
                return null
×
206
              } else if (
207
                originalExceptionString?.match(
×
208
                  /NetworkError when attempting to fetch resource./
209
                )
210
              ) {
211
                // Flaky network.
212
                console.log('Suppress flaky network')
×
213
                return null
×
UNCOV
214
              }
×
215
            } else if (originalExceptionName === 'ReferenceError') {
216
              console.log('ReferenceError')
×
217
              if (
218
                originalExceptionMessage?.match(/Can't find variable: fieldset/)
×
219
              ) {
220
                // This happens because of an old bug which is now fixed:
221
                // https://codereview.chromium.org/2343013005
222
                console.log('Old Chrome fieldset bug')
×
223
                return null
×
UNCOV
224
              }
×
225
            } else if (originalExceptionName === 'SecurityError') {
226
              if (
227
                (originalExceptionMessage?.match('Blocked a frame') &&
×
228
                  originalExceptionStack?.match('isRef')) ||
×
229
                originalExceptionStack?.match('popupInterval')
×
230
              ) {
231
                // See https://stackoverflow.com/questions/39081098/close-a-window-opened-with-window-open-after-clicking-a-button
232
                console.log(
×
233
                  'Suppress error caused by a bug in vue-social-sharing.'
234
                )
235
                return null
×
UNCOV
236
              }
×
237
            } else if (
238
              originalExceptionStack?.includes('_.ae') &&
×
239
              originalExceptionStack?.includes('/gsi/client')
×
240
            ) {
241
              // This is an error in Google One Tap sign-in, often preceded by a console log about malformed JSON
242
              // response.  It's possible that it relates to multiple account sign in.  I've failed to reproduce it, and
243
              // it's not really clear that it's our fault so there's no point beating ourselves up about it.
244
              console.log('Suppress odd Google One Tap error')
×
UNCOV
245
              return null
×
246
            } else if (
247
              originalExceptionMessage?.includes(
×
248
                'Looks like your website URL has changed'
249
              ) &&
250
              originalExceptionStack?.includes('cookieyes')
×
251
            ) {
252
              // This is a common error that is not our fault.
253
              console.log('CookieYes domain error - probably test deployment')
×
254
              if (window.postCookieYes) {
×
255
                window.postCookieYes()
×
256
              }
257

UNCOV
258
              return null
×
259
            } else if (
260
              originalExceptionMessage?.includes(
×
261
                'Attempted to load an infinite number of tiles'
262
              ) ||
263
              originalExceptionMessage?.includes(
×
264
                "Cannot read properties of null (reading 'latLngToLayerPoint')"
265
              ) ||
266
              originalExceptionMessage?.includes('latLngToLayerPoint')
×
267
            ) {
268
              // This is a leaflet error - don't understand it, but not our fault.
UNCOV
269
              return null
×
270
            } else if (
271
              originalExceptionMessage?.includes(
×
272
                "All 'ins' elements in the DOM with class=adsbygoogle already have ads"
273
              )
274
            ) {
275
              // This is a Google Ads error, and not our fault.
276
              return null
×
277
            } else if (
UNCOV
278
              originalExceptionMessage?.includes('@webkit-masked-url://hidden/')
×
279
            ) {
280
              // Webkit issue - see https://github.com/getsentry/sentry-javascript/discussions/5875
281
              return null
×
282
            }
283
          }
284

285
          // Continue sending to Sentry
UNCOV
286
          return event
×
287
        },
288
      })
289

290
      vueApp.mixin(
21✔
291
        Sentry.createTracingMixins({
292
          trackComponents: true,
293
          timeout: 2000,
294
          hooks: ['activate', 'mount', 'update'],
295
        })
296
      )
297
      Sentry.attachErrorHandler(vueApp, {
21✔
298
        logErrors: false,
299
        attachProps: true,
300
        trackComponents: true,
301
        timeout: 2000,
302
        hooks: ['activate', 'mount', 'update'],
303
      })
304
    }
305
  }
306

307
  checkCMPComplete()
308

309
  return {
21✔
310
    provide: {
311
      sentrySetContext: (n, context) => Sentry.setContext(n, context),
312
      sentrySetUser: (user) => Sentry.setUser(user),
313
      sentrySetTag: (tagName, value) => Sentry.setTag(tagName, value),
314
      sentryAddBreadcrumb: (breadcrumb) => Sentry.addBreadcrumb(breadcrumb),
315
      sentryCaptureException: (e) => Sentry.captureException(e),
316
    },
317
  }
318
})
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc