Stop showing raw chunk load errors (#23571)
On iOS Safari, a failed chunk load showed a snackbar containing the raw browser string `Importing a module script failed.` `PromiseRejectionEffect` snackbars the raw `error.message` of any unhandled rejection, so a floating dynamic import leaked browser internals to the user. It now skips the snackbar for stale chunk errors, which are still captured by Sentry. This only changes what the user sees, not the underlying fetch failure.
This commit is contained in:
+6
-1
@@ -1,5 +1,6 @@
|
||||
import { useCallback, useEffect } from 'react';
|
||||
|
||||
import { checkIfItsAViteStaleChunkLazyLoadingError } from '@/error-handler/utils/checkIfItsAViteStaleChunkLazyLoadingError';
|
||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||
import {
|
||||
CombinedGraphQLErrors,
|
||||
@@ -44,7 +45,11 @@ export const PromiseRejectionEffect = () => {
|
||||
error?.networkError?.name === 'AbortError' ||
|
||||
error?.name === 'AbortError';
|
||||
|
||||
if (!isAbortError) {
|
||||
const isViteStaleChunkLazyLoadingError =
|
||||
error instanceof Error &&
|
||||
checkIfItsAViteStaleChunkLazyLoadingError(error);
|
||||
|
||||
if (!isAbortError && !isViteStaleChunkLazyLoadingError) {
|
||||
enqueueErrorSnackBar(
|
||||
error instanceof Error ? { message: error.message } : {},
|
||||
);
|
||||
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
import { render, waitFor } from '@testing-library/react';
|
||||
|
||||
import { PromiseRejectionEffect } from '@/error-handler/components/PromiseRejectionEffect';
|
||||
|
||||
jest.mock('@sentry/react', () => ({
|
||||
captureException: jest.fn(),
|
||||
}));
|
||||
|
||||
const enqueueErrorSnackBar = jest.fn();
|
||||
|
||||
jest.mock('@/ui/feedback/snack-bar-manager/hooks/useSnackBar', () => ({
|
||||
useSnackBar: () => ({ enqueueErrorSnackBar }),
|
||||
}));
|
||||
|
||||
const { captureException } = jest.requireMock('@sentry/react');
|
||||
|
||||
const dispatchUnhandledRejection = (reason: unknown) => {
|
||||
const event = new Event('unhandledrejection');
|
||||
Object.defineProperty(event, 'reason', { value: reason });
|
||||
|
||||
window.dispatchEvent(event);
|
||||
};
|
||||
|
||||
describe('PromiseRejectionEffect', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
render(<PromiseRejectionEffect />);
|
||||
});
|
||||
|
||||
it('should not snackbar a stale chunk error', async () => {
|
||||
dispatchUnhandledRejection(new Error('Importing a module script failed.'));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(captureException).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
expect(enqueueErrorSnackBar).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should still snackbar an unrelated error', async () => {
|
||||
dispatchUnhandledRejection(new Error('Some unrelated error'));
|
||||
|
||||
expect(enqueueErrorSnackBar).toHaveBeenCalledWith({
|
||||
message: 'Some unrelated error',
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(captureException).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
it('should not snackbar an abort error', async () => {
|
||||
dispatchUnhandledRejection({ name: 'AbortError' });
|
||||
|
||||
await waitFor(() => {
|
||||
expect(captureException).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
expect(enqueueErrorSnackBar).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should snackbar a generic message when the reason is not an Error', async () => {
|
||||
dispatchUnhandledRejection('something went wrong');
|
||||
|
||||
expect(enqueueErrorSnackBar).toHaveBeenCalledWith({});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(captureException).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user