whisper-money/resources/js/lib/chunk-load-recovery.ts

124 lines
3.1 KiB
TypeScript

const CHUNK_LOAD_RELOAD_STORAGE_KEY =
'whisper-money:chunk-load-reload-asset-signature';
const CHUNK_LOAD_ERROR_PATTERNS = [
/Failed to fetch dynamically imported module/i,
/error loading dynamically imported module/i,
/Importing a module script failed/i,
/Load failed for module script/i,
/Unable to preload CSS/i,
/ChunkLoadError/i,
/Loading chunk \d+ failed/i,
];
let inMemoryReloadedAssetSignature: string | null = null;
interface ChunkLoadRecoveryOptions {
assetSignature?: string;
reload?: () => void;
storage?: Storage;
}
export function getCurrentAssetSignature(): string {
const buildAssetScripts = Array.from(document.scripts)
.map((script) => script.src)
.filter((src) => src.includes('/build/assets/'))
.sort();
return buildAssetScripts.join('|') || window.location.href;
}
export function isChunkLoadError(value: unknown): boolean {
const message = getErrorMessage(value);
return CHUNK_LOAD_ERROR_PATTERNS.some((pattern) => pattern.test(message));
}
export function reloadOnChunkLoadError(
value: unknown,
options: ChunkLoadRecoveryOptions = {},
): boolean {
if (!isChunkLoadError(value)) {
return false;
}
const assetSignature = options.assetSignature ?? getCurrentAssetSignature();
if (hasAlreadyReloadedForAssetSignature(assetSignature, options.storage)) {
return false;
}
markReloadedForAssetSignature(assetSignature, options.storage);
(options.reload ?? (() => window.location.reload()))();
return true;
}
export function installChunkLoadRecovery(): void {
window.addEventListener('unhandledrejection', (event) => {
if (
reloadOnChunkLoadError(event.reason, {
storage: window.sessionStorage,
})
) {
event.preventDefault();
}
});
window.addEventListener('error', (event) => {
if (
reloadOnChunkLoadError(event.error ?? event.message, {
storage: window.sessionStorage,
})
) {
event.preventDefault();
}
});
}
function getErrorMessage(value: unknown): string {
if (value instanceof Error) {
return `${value.name}: ${value.message}`;
}
if (typeof value === 'string') {
return value;
}
if (typeof value === 'object' && value !== null && 'message' in value) {
return String(value.message);
}
return '';
}
function hasAlreadyReloadedForAssetSignature(
assetSignature: string,
storage?: Storage,
): boolean {
if (inMemoryReloadedAssetSignature === assetSignature) {
return true;
}
try {
return (
storage?.getItem(CHUNK_LOAD_RELOAD_STORAGE_KEY) === assetSignature
);
} catch {
return false;
}
}
function markReloadedForAssetSignature(
assetSignature: string,
storage?: Storage,
): void {
inMemoryReloadedAssetSignature = assetSignature;
try {
storage?.setItem(CHUNK_LOAD_RELOAD_STORAGE_KEY, assetSignature);
} catch {
return;
}
}