133 lines
3.9 KiB
TypeScript
Executable File
133 lines
3.9 KiB
TypeScript
Executable File
import { useEffect, useRef, useState } from 'react';
|
|
import { getOrCreateClientId } from './app/main/clientIdentity';
|
|
import { reportClientError } from './app/main/errorLogApi';
|
|
import { AppShell } from './app/main';
|
|
import { InitialLoadingOverlay } from './app/main/InitialLoadingOverlay';
|
|
import { ReleasePendingMainModal } from './app/main/ReleasePendingMainModal';
|
|
import { reportVisitorPageView } from './features/history/api';
|
|
import { useAppStore } from './store';
|
|
|
|
const CHUNK_LOAD_RETRY_SESSION_KEY = 'ai-code-app.chunk-load-retried';
|
|
const INITIAL_LOADING_MIN_VISIBLE_MS = 450;
|
|
|
|
function shouldRetryChunkLoad(errorMessage: string) {
|
|
return /Failed to fetch dynamically imported module|Importing a module script failed|Load failed|ChunkLoadError/i.test(
|
|
errorMessage,
|
|
);
|
|
}
|
|
|
|
function retryChunkLoadOnce(errorMessage: string) {
|
|
if (typeof window === 'undefined' || typeof sessionStorage === 'undefined') {
|
|
return false;
|
|
}
|
|
|
|
if (!shouldRetryChunkLoad(errorMessage)) {
|
|
return false;
|
|
}
|
|
|
|
if (sessionStorage.getItem(CHUNK_LOAD_RETRY_SESSION_KEY) === '1') {
|
|
return false;
|
|
}
|
|
|
|
sessionStorage.setItem(CHUNK_LOAD_RETRY_SESSION_KEY, '1');
|
|
window.location.reload();
|
|
return true;
|
|
}
|
|
|
|
function App() {
|
|
const { currentPage } = useAppStore();
|
|
const lastTrackedPageIdRef = useRef<string | null>(null);
|
|
const [showInitialLoading, setShowInitialLoading] = useState(true);
|
|
|
|
useEffect(() => {
|
|
if (typeof window === 'undefined') {
|
|
return undefined;
|
|
}
|
|
|
|
const handleError = (event: ErrorEvent) => {
|
|
const reportedError = event.error instanceof Error ? event.error : null;
|
|
const errorMessage = event.message || reportedError?.message || '클라이언트 오류가 발생했습니다.';
|
|
|
|
if (retryChunkLoadOnce(errorMessage)) {
|
|
return;
|
|
}
|
|
|
|
void reportClientError({
|
|
errorType: 'window.error',
|
|
errorName: reportedError?.name ?? null,
|
|
errorMessage,
|
|
stackTrace: reportedError?.stack ?? null,
|
|
requestPath: `${window.location.pathname}${window.location.search}${window.location.hash}`,
|
|
context: {
|
|
filename: event.filename || null,
|
|
line: event.lineno || null,
|
|
column: event.colno || null,
|
|
},
|
|
});
|
|
};
|
|
|
|
const handleUnhandledRejection = (event: PromiseRejectionEvent) => {
|
|
const reason = event.reason;
|
|
const reportedError = reason instanceof Error ? reason : null;
|
|
const errorMessage =
|
|
reportedError?.message || (typeof reason === 'string' ? reason : '처리되지 않은 Promise 거절이 발생했습니다.');
|
|
|
|
if (retryChunkLoadOnce(errorMessage)) {
|
|
return;
|
|
}
|
|
|
|
void reportClientError({
|
|
errorType: 'unhandledrejection',
|
|
errorName: reportedError?.name ?? null,
|
|
errorMessage,
|
|
stackTrace: reportedError?.stack ?? null,
|
|
requestPath: `${window.location.pathname}${window.location.search}${window.location.hash}`,
|
|
context: {
|
|
reasonType: typeof reason,
|
|
},
|
|
});
|
|
};
|
|
|
|
window.addEventListener('error', handleError);
|
|
window.addEventListener('unhandledrejection', handleUnhandledRejection);
|
|
|
|
return () => {
|
|
window.removeEventListener('error', handleError);
|
|
window.removeEventListener('unhandledrejection', handleUnhandledRejection);
|
|
};
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
getOrCreateClientId();
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
const hideTimer = window.setTimeout(() => {
|
|
setShowInitialLoading(false);
|
|
}, INITIAL_LOADING_MIN_VISIBLE_MS);
|
|
|
|
return () => {
|
|
window.clearTimeout(hideTimer);
|
|
};
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
if (lastTrackedPageIdRef.current === currentPage.id) {
|
|
return;
|
|
}
|
|
|
|
lastTrackedPageIdRef.current = currentPage.id;
|
|
void reportVisitorPageView(currentPage);
|
|
}, [currentPage]);
|
|
|
|
return (
|
|
<>
|
|
<AppShell />
|
|
<ReleasePendingMainModal />
|
|
{showInitialLoading ? <InitialLoadingOverlay /> : null}
|
|
</>
|
|
);
|
|
}
|
|
|
|
export default App;
|