monacousa-portal/utils/reload-loop-prevention.ts

311 lines
7.9 KiB
TypeScript
Raw Normal View History

/**
* Reload Loop Detection and Prevention Utility
* Advanced mobile Safari reload loop prevention system
*/
interface PageLoadInfo {
url: string;
timestamp: number;
userAgent: string;
loadCount: number;
}
interface ReloadLoopState {
enabled: boolean;
pageLoads: PageLoadInfo[];
blockedPages: Set<string>;
emergencyMode: boolean;
debugMode: boolean;
}
const RELOAD_LOOP_THRESHOLD = 5; // Max page loads in time window
const TIME_WINDOW = 10000; // 10 seconds
const EMERGENCY_BLOCK_TIME = 30000; // 30 seconds
const STORAGE_KEY = 'reload_loop_prevention';
/**
* Get or initialize reload loop state
*/
function getReloadLoopState(): ReloadLoopState {
if (typeof window === 'undefined') {
return {
enabled: false,
pageLoads: [],
blockedPages: new Set(),
emergencyMode: false,
debugMode: false
};
}
// Use sessionStorage for persistence across reloads
try {
const stored = sessionStorage.getItem(STORAGE_KEY);
if (stored) {
const parsed = JSON.parse(stored);
return {
...parsed,
blockedPages: new Set(parsed.blockedPages || [])
};
}
} catch (error) {
console.warn('[reload-prevention] Failed to parse stored state:', error);
}
// Initialize new state
const initialState: ReloadLoopState = {
enabled: true,
pageLoads: [],
blockedPages: new Set(),
emergencyMode: false,
debugMode: process.env.NODE_ENV === 'development'
};
saveReloadLoopState(initialState);
return initialState;
}
/**
* Save reload loop state to sessionStorage
*/
function saveReloadLoopState(state: ReloadLoopState): void {
if (typeof window === 'undefined') return;
try {
const toStore = {
...state,
blockedPages: Array.from(state.blockedPages)
};
sessionStorage.setItem(STORAGE_KEY, JSON.stringify(toStore));
} catch (error) {
console.warn('[reload-prevention] Failed to save state:', error);
}
}
/**
* Record a page load and check for reload loops
*/
export function recordPageLoad(url: string): boolean {
const state = getReloadLoopState();
if (!state.enabled) {
return true; // Allow load
}
const now = Date.now();
const userAgent = navigator.userAgent || '';
// Clean old page loads
state.pageLoads = state.pageLoads.filter(
load => now - load.timestamp < TIME_WINDOW
);
// Find existing load for this URL
const existingLoad = state.pageLoads.find(load => load.url === url);
if (existingLoad) {
existingLoad.loadCount++;
existingLoad.timestamp = now;
} else {
state.pageLoads.push({
url,
timestamp: now,
userAgent,
loadCount: 1
});
}
// Check for reload loop
const urlLoads = state.pageLoads.filter(load => load.url === url);
const totalLoads = urlLoads.reduce((sum, load) => sum + load.loadCount, 0);
if (totalLoads >= RELOAD_LOOP_THRESHOLD) {
console.error(`[reload-prevention] Reload loop detected for ${url} (${totalLoads} loads)`);
// Block this page
state.blockedPages.add(url);
state.emergencyMode = true;
// Set emergency timeout
setTimeout(() => {
state.emergencyMode = false;
state.blockedPages.delete(url);
saveReloadLoopState(state);
console.log(`[reload-prevention] Emergency block lifted for ${url}`);
}, EMERGENCY_BLOCK_TIME);
saveReloadLoopState(state);
return false; // Block load
}
saveReloadLoopState(state);
return true; // Allow load
}
/**
* Check if a page is currently blocked
*/
export function isPageBlocked(url: string): boolean {
const state = getReloadLoopState();
return state.blockedPages.has(url);
}
/**
* Initialize reload loop prevention for a page
*/
export function initReloadLoopPrevention(pageName: string): boolean {
if (typeof window === 'undefined') {
return true;
}
const currentUrl = window.location.pathname;
const canLoad = recordPageLoad(currentUrl);
if (!canLoad) {
console.error(`[reload-prevention] Page load blocked: ${pageName} (${currentUrl})`);
// Show emergency message
showEmergencyMessage(pageName);
return false;
}
if (getReloadLoopState().debugMode) {
console.log(`[reload-prevention] Page load allowed: ${pageName} (${currentUrl})`);
}
return true;
}
/**
* Show emergency message when page is blocked
*/
function showEmergencyMessage(pageName: string): void {
const message = `
<div style="
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(163, 21, 21, 0.95);
color: white;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
z-index: 10000;
font-family: Arial, sans-serif;
text-align: center;
padding: 20px;
">
<h1 style="font-size: 24px; margin-bottom: 20px;">Page Loading Temporarily Blocked</h1>
<p style="font-size: 16px; margin-bottom: 20px;">
Multiple rapid page loads detected for ${pageName}.<br>
This is a safety measure to prevent infinite loading loops.
</p>
<p style="font-size: 14px; margin-bottom: 30px;">
The block will be automatically lifted in 30 seconds.
</p>
<button onclick="location.href='/'" style="
background: white;
color: #a31515;
border: none;
padding: 12px 24px;
border-radius: 6px;
font-size: 16px;
cursor: pointer;
margin-right: 15px;
">Return to Home</button>
<button onclick="window.history.back()" style="
background: transparent;
color: white;
border: 2px solid white;
padding: 12px 24px;
border-radius: 6px;
font-size: 16px;
cursor: pointer;
">Go Back</button>
</div>
`;
document.body.innerHTML = message;
}
/**
* Clear reload loop prevention state (for testing)
*/
export function clearReloadLoopPrevention(): void {
if (typeof window === 'undefined') return;
try {
sessionStorage.removeItem(STORAGE_KEY);
console.log('[reload-prevention] State cleared');
} catch (error) {
console.warn('[reload-prevention] Failed to clear state:', error);
}
}
/**
* Get current reload loop prevention status
*/
export function getReloadLoopStatus(): {
enabled: boolean;
emergencyMode: boolean;
blockedPages: string[];
pageLoads: PageLoadInfo[];
} {
const state = getReloadLoopState();
return {
enabled: state.enabled,
emergencyMode: state.emergencyMode,
blockedPages: Array.from(state.blockedPages),
pageLoads: state.pageLoads
};
}
/**
* Mobile Safari specific optimizations
*/
export function applyMobileSafariReloadLoopFixes(): void {
if (typeof window === 'undefined') return;
const userAgent = navigator.userAgent || '';
const isMobileSafari = /iPad|iPhone|iPod/.test(userAgent) && /Safari/i.test(userAgent) && !/Chrome/i.test(userAgent);
if (!isMobileSafari) return;
console.log('[reload-prevention] Applying mobile Safari specific fixes');
// Prevent Safari's aggressive caching that can cause reload loops
window.addEventListener('pageshow', (event) => {
if (event.persisted) {
console.log('[reload-prevention] Page restored from bfcache');
// Force a small delay to prevent immediate re-execution
setTimeout(() => {
console.log('[reload-prevention] bfcache restore handled');
}, 100);
}
});
// Handle Safari's navigation timing issues
window.addEventListener('beforeunload', () => {
// Mark navigation in progress to prevent reload loop detection
const state = getReloadLoopState();
state.enabled = false;
saveReloadLoopState(state);
});
// Re-enable after navigation completes
window.addEventListener('load', () => {
setTimeout(() => {
const state = getReloadLoopState();
state.enabled = true;
saveReloadLoopState(state);
}, 500);
});
}
// Auto-initialize on module load
if (typeof window !== 'undefined') {
applyMobileSafariReloadLoopFixes();
}