191 lines
6.1 KiB
Markdown
191 lines
6.1 KiB
Markdown
|
|
# 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:
|
||
|
|
```typescript
|
||
|
|
// WRONG - causes reactivity issues
|
||
|
|
const cardClasses = ref(() => {
|
||
|
|
const classes = ['signup-card'];
|
||
|
|
// ...
|
||
|
|
return classes.join(' ');
|
||
|
|
});
|
||
|
|
```
|
||
|
|
|
||
|
|
**Fix**: Execute the function immediately and store the result:
|
||
|
|
```typescript
|
||
|
|
// 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:
|
||
|
|
```typescript
|
||
|
|
// 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:
|
||
|
|
```typescript
|
||
|
|
const deviceInfo = getStaticDeviceInfo(); // Called once, never reactive
|
||
|
|
const containerClasses = ref(getDeviceCssClasses('page-name')); // Computed once
|
||
|
|
```
|
||
|
|
|
||
|
|
### 2. Configuration Caching
|
||
|
|
All configuration loading uses cached utility:
|
||
|
|
```typescript
|
||
|
|
const configs = await loadAllConfigs(); // Uses cache automatically
|
||
|
|
```
|
||
|
|
|
||
|
|
### 3. Initialization Flags
|
||
|
|
Prevent multiple initializations:
|
||
|
|
```typescript
|
||
|
|
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:
|
||
|
|
```javascript
|
||
|
|
// 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.
|