monacousa-portal/docs-archive/MOBILE_SAFARI_RELOAD_LOOP_F...

6.1 KiB

Mobile Safari Reload Loop - Final Fix

Problem Description

Users on Safari iPhone experienced endless reload loops on:

  • Signup page (/signup)
  • Email verification page (/auth/verify)
  • Password setup page (/auth/setup-password)

The server logs showed repeated calls to:

  • /api/recaptcha-config
  • /api/registration-config

Root Causes Identified

1. Incorrect Reactive Reference in Signup Page

Issue: cardClasses was defined as a ref containing a function instead of the function's result:

// WRONG - causes reactivity issues
const cardClasses = ref(() => {
  const classes = ['signup-card'];
  // ...
  return classes.join(' ');
});

Fix: Execute the function immediately and store the result:

// CORRECT
const cardClasses = ref((() => {
  const classes = ['signup-card'];
  // ...
  return classes.join(' ');
})()); // Note the immediate execution with ()

2. Config Cache Not Persisting Across Component Lifecycles

Issue: The global config cache was using module-level variables that could be reset during Vue's reactivity cycles, causing repeated API calls.

Fix: Use window object for true persistence:

// Use window object for true persistence across component lifecycle
function getGlobalCache(): ConfigCache {
  if (typeof window === 'undefined') {
    return defaultCache;
  }
  
  if (!(window as any).__configCache) {
    (window as any).__configCache = defaultCache;
  }
  
  return (window as any).__configCache;
}

3. Missing Circuit Breaker Protection

Issue: No protection against rapid successive API calls that could trigger reload loops.

Fix: Implemented circuit breaker with threshold protection:

  • Max 5 calls in 10-second window
  • Automatic blocking when threshold reached
  • Fallback to default values when blocked

Complete Solution Implementation

Files Modified

  1. pages/signup.vue

    • Fixed cardClasses ref definition
    • Ensured static device detection
    • Added initialization flag to prevent multiple setups
  2. utils/config-cache.ts

    • Moved cache storage to window object
    • Added getGlobalCache() function for persistent storage
    • Improved circuit breaker implementation
    • Added proper logging for debugging
  3. plugins/04.config-cache-init.client.ts (NEW)

    • Pre-initializes config cache structure
    • Sets up global error handlers to catch reload loops
    • Prevents multiple initializations
    • Adds unhandled rejection handler

How The Fix Works

1. Plugin Initialization (runs first)

  • 04.config-cache-init.client.ts runs before other plugins
  • Initializes window.__configCache structure
  • Sets up error handlers to catch potential reload loops
  • Marks initialization complete with window.__configCacheInitialized

2. Config Loading (on-demand)

  • When pages need config, they call loadAllConfigs()
  • Cache is checked first via getGlobalCache()
  • If cached, returns immediately (no API call)
  • If not cached, makes API call with circuit breaker protection
  • Results stored in window.__configCache for persistence

3. Circuit Breaker Protection

  • Tracks API call history in time windows
  • Blocks calls if threshold exceeded (5 calls in 10 seconds)
  • Returns fallback values when blocked
  • Prevents cascade failures and reload loops

Testing Instructions

Test on Safari iPhone:

  1. Clear Safari cache and cookies
  2. Navigate to /signup - should load without reload loop
  3. Navigate to /auth/verify?token=test - should show error without loop
  4. Navigate to /auth/setup-password?email=test@test.com - should load without loop

Monitor Console Logs:

  • Look for [config-cache-init] messages confirming initialization
  • Check for [config-cache] Returning cached messages on subsequent loads
  • Watch for Circuit breaker activated if threshold reached

Server Logs:

  • Should see initial calls to /api/recaptcha-config and /api/registration-config
  • Should NOT see repeated calls in quick succession
  • Maximum 2-3 calls per page load (initial + retry if needed)

Prevention Measures

1. Static Detection Pattern

All device detection uses static, non-reactive patterns:

const deviceInfo = getStaticDeviceInfo(); // Called once, never reactive
const containerClasses = ref(getDeviceCssClasses('page-name')); // Computed once

2. Configuration Caching

All configuration loading uses cached utility:

const configs = await loadAllConfigs(); // Uses cache automatically

3. Initialization Flags

Prevent multiple initializations:

let initialized = false;
onMounted(() => {
  if (initialized) return;
  initialized = true;
  // ... initialization code
});

Monitoring

Key Metrics to Watch:

  1. API Call Frequency: /api/recaptcha-config and /api/registration-config should be called max once per session
  2. Page Load Time: Should be under 2 seconds on mobile
  3. Error Rate: No "Maximum call stack" or recursion errors
  4. User Reports: No complaints about infinite loading

Debug Commands:

// Check cache status in browser console
console.log(window.__configCache);
console.log(window.__configCacheInitialized);

// Force clear cache (for testing)
window.__configCache = null;
window.__configCacheInitialized = false;

Rollback Plan

If issues persist, rollback changes:

  1. Remove plugins/04.config-cache-init.client.ts
  2. Revert utils/config-cache.ts to previous version
  3. Revert pages/signup.vue changes

Long-term Improvements

  1. Server-side caching: Cache config in Redis/memory on server
  2. SSR config injection: Inject config during SSR to avoid client calls
  3. PWA service worker: Cache config in service worker
  4. Config versioning: Add version check to invalidate stale cache

Conclusion

The mobile Safari reload loop has been resolved through:

  1. Fixing reactive reference bugs
  2. Implementing proper persistent caching
  3. Adding circuit breaker protection
  4. Setting up global error handlers

The solution is backward compatible and doesn't affect desktop users or other browsers. The fix specifically targets the root causes while maintaining the existing functionality.