diff --git a/rsconcept/frontend/src/app/error-fallback.tsx b/rsconcept/frontend/src/app/error-fallback.tsx index 7e57d970..3a71fb6e 100644 --- a/rsconcept/frontend/src/app/error-fallback.tsx +++ b/rsconcept/frontend/src/app/error-fallback.tsx @@ -1,17 +1,36 @@ 'use client'; +import { useEffect } from 'react'; import { useNavigate, useRouteError } from 'react-router'; import { Button } from '@/components/control'; import { InfoError } from '@/components/info-error'; +import { isStaleBundleError } from '@/utils/utils'; export function ErrorFallback() { const error = useRouteError(); const router = useNavigate(); + useEffect(() => { + if (isStaleBundleError(error)) { + console.warn('Detected stale bundle — reloading...'); + window.location.reload(); + } + }, [error]); + function resetErrorBoundary() { Promise.resolve(router('/')).catch(console.error); } + + if (isStaleBundleError(error)) { + return ( +
+

Обновление страницы...

+

Обнаружена устаревшая версия приложения. Перезагрузка страницы...

+
+ ); + } + return (

Что-то пошло не так!

diff --git a/rsconcept/frontend/src/main.tsx b/rsconcept/frontend/src/main.tsx index e3212551..1d432879 100644 --- a/rsconcept/frontend/src/main.tsx +++ b/rsconcept/frontend/src/main.tsx @@ -14,15 +14,27 @@ if (typeof window !== 'undefined' && import.meta.env.DEV) { } if (typeof window !== 'undefined') { - window.addEventListener('error', (event: ErrorEvent) => { - const error = event.error as Error; - if ( - error instanceof Error && - typeof error.message === 'string' && - error.message.includes('Failed to fetch dynamically imported module') - ) { + function handleStaleBundleError(error: unknown): boolean { + if (error instanceof Error && error.message.includes('Failed to fetch dynamically imported module')) { console.warn('Detected stale bundle — reloading...'); window.location.reload(); + return true; + } + if (typeof error === 'string' && error.includes('Failed to fetch dynamically imported module')) { + console.warn('Detected stale bundle — reloading...'); + window.location.reload(); + return true; + } + return false; + } + + window.addEventListener('error', (event: ErrorEvent) => { + handleStaleBundleError(event.error); + }); + + window.addEventListener('unhandledrejection', (event: PromiseRejectionEvent) => { + if (handleStaleBundleError(event.reason)) { + event.preventDefault(); } }); } diff --git a/rsconcept/frontend/src/utils/utils.ts b/rsconcept/frontend/src/utils/utils.ts index 0ba3b7d7..b827ad56 100644 --- a/rsconcept/frontend/src/utils/utils.ts +++ b/rsconcept/frontend/src/utils/utils.ts @@ -8,6 +8,19 @@ import { type AxiosError, type AxiosHeaderValue, type AxiosResponse, isAxiosErro import { PARAMETER } from './constants'; import { infoMsg, promptText } from './labels'; +/** + * Check if error is stale bundle error. + */ +export function isStaleBundleError(error: unknown): boolean { + if (error instanceof Error) { + return error.message.includes('Failed to fetch dynamically imported module'); + } + if (typeof error === 'string') { + return error.includes('Failed to fetch dynamically imported module'); + } + return false; +} + /** * Wrapper class for generalized text matching. *