fix: support Firefox and Safari network error messages (#8949)

Fixes #8912

The `isNetworkError` check in `useErrorHandling.ts` only matched
Chrome/Edge's `"Failed to fetch"` message, causing Firefox and Safari
users to see raw error text instead of the user-friendly
`disconnectedFromBackend` toast.

Updated the check to use a case-insensitive regex matching all three
browser variants:
- Chrome/Edge: `Failed to fetch`
- Firefox: `NetworkError when attempting to fetch resource.`
- Safari: `Load failed`

Added parameterized tests covering all three browsers plus a negative
case ensuring non-`TypeError` errors are not misclassified.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-8949-fix-support-Firefox-and-Safari-network-error-messages-30b6d73d36508185b2cbd6b5447d3795)
by [Unito](https://www.unito.io)
This commit is contained in:
Christian Byrne
2026-02-19 14:22:20 -08:00
committed by GitHub
parent 7060133ff9
commit 3b5c9762a4
2 changed files with 46 additions and 3 deletions

View File

@@ -1,15 +1,18 @@
import { createPinia, setActivePinia } from 'pinia'
import { createTestingPinia } from '@pinia/testing'
import { setActivePinia } from 'pinia'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import type { ErrorRecoveryStrategy } from '@/composables/useErrorHandling'
import { useErrorHandling } from '@/composables/useErrorHandling'
import { t } from '@/i18n'
import { useToastStore } from '@/platform/updates/common/toastStore'
describe('useErrorHandling', () => {
let errorHandler: ReturnType<typeof useErrorHandling>
beforeEach(() => {
vi.clearAllMocks()
setActivePinia(createPinia())
setActivePinia(createTestingPinia())
errorHandler = useErrorHandling()
})
@@ -320,6 +323,45 @@ describe('useErrorHandling', () => {
})
})
describe('network error detection', () => {
it.each([
['Failed to fetch', 'Chrome/Edge'],
['NetworkError when attempting to fetch resource.', 'Firefox'],
['Load failed', 'Safari']
])('should show disconnected toast for "%s" (%s)', async (message) => {
const action = vi.fn(async () => {
throw new TypeError(message)
})
const wrapped = errorHandler.wrapWithErrorHandlingAsync(action)
await wrapped()
const toastStore = useToastStore()
expect(toastStore.add).toHaveBeenCalledWith(
expect.objectContaining({
severity: 'error',
detail: t('g.disconnectedFromBackend')
})
)
})
it('should not treat non-TypeError as network error', async () => {
const action = vi.fn(async () => {
throw new Error('Failed to fetch')
})
const wrapped = errorHandler.wrapWithErrorHandlingAsync(action)
await wrapped()
const toastStore = useToastStore()
expect(toastStore.add).toHaveBeenCalledWith(
expect.objectContaining({
detail: 'Failed to fetch'
})
)
})
})
describe('backward compatibility', () => {
it('should work without recovery strategies parameter', async () => {
const action = vi.fn(async () => 'success')

View File

@@ -53,7 +53,8 @@ export function useErrorHandling() {
const toast = useToastStore()
const toastErrorHandler = (error: unknown) => {
const isNetworkError =
error instanceof TypeError && error.message === 'Failed to fetch'
error instanceof TypeError &&
/failed to fetch|networkerror|load failed/i.test(error.message)
const message = isNetworkError
? t('g.disconnectedFromBackend')
: error instanceof Error