diff --git a/src/app/common/sentry.ts b/src/app/common/sentry.ts index bd4f66dbb..798e4c3c6 100644 --- a/src/app/common/sentry.ts +++ b/src/app/common/sentry.ts @@ -27,13 +27,28 @@ export function reportError(error: unknown) { if (_queue && _queue.length < MAX_QUEUED_ERRORS) _queue.push(error); } +/** + * True for the error Monaco uses to cancel pending work. Monaco does not + * handle some of these rejections, for example when an editor is disposed + * while a word highlight is pending. They are not failures. + * Same check as Monaco's `isCancellationError`. + */ +function isMonacoCancellation(error: unknown) { + return ( + error instanceof Error && + error.name === 'Canceled' && + error.message === 'Canceled' + ); +} + /** * Angular error handler. Logs errors to the console as the default handler - * does, and sends them to Sentry. + * does, and sends them to Sentry. Ignores Monaco cancellations. */ @Injectable() export class SentryReportingErrorHandler implements ErrorHandler { public handleError(error: unknown) { + if (isMonacoCancellation(error)) return; console.error(error); reportError(error); } diff --git a/src/tests/common/sentry.spec.ts b/src/tests/common/sentry.spec.ts index 260a16715..f3be3fe25 100644 --- a/src/tests/common/sentry.spec.ts +++ b/src/tests/common/sentry.spec.ts @@ -63,6 +63,25 @@ describe('sentry', () => { expect(handle_error).toHaveBeenCalledWith('late'); }); + it('ignores Monaco cancellation errors', async () => { + const { initSentry, SentryReportingErrorHandler } = await loadSentry(); + setAuthorityDsn(undefined); + await initSentry(injector, 'user-1'); + const console_error = vi + .spyOn(console, 'error') + .mockImplementation(() => undefined); + const handler = new SentryReportingErrorHandler(); + const canceled = new Error('Canceled'); + canceled.name = 'Canceled'; + handler.handleError(canceled); + expect(handle_error).not.toHaveBeenCalled(); + expect(console_error).not.toHaveBeenCalled(); + const error = new Error('Canceled'); + handler.handleError(error); + expect(handle_error).toHaveBeenCalledWith(error); + console_error.mockRestore(); + }); + it('does not load Sentry when the authority turns it off', async () => { const { initSentry, reportError } = await loadSentry(); setAuthorityDsn('');