fix: IndexedDB cache broken on page reload - root cause of mobile login failure (refs #190)
All checks were successful
Deploy to Staging / Build Images (pull_request) Successful in 3m25s
Deploy to Staging / Deploy to Staging (pull_request) Successful in 51s
Deploy to Staging / Verify Staging (pull_request) Successful in 8s
Deploy to Staging / Notify Staging Ready (pull_request) Successful in 7s
Deploy to Staging / Notify Staging Failure (pull_request) Has been skipped

loadCacheFromDB used store.getAll() which returns raw values, not
key-value pairs. The item.key check always failed, so memoryCache
was empty after every page reload. Auth0 SDK state stored before
redirect was lost on mobile Safari (no bfcache).

Also fixed set()/remove() to await IDB persistence so Auth0 state
is fully written before loginWithRedirect() navigates away.

Added 10s timeout on callback loading state as safety net.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Eric Gullickson
2026-02-14 22:20:34 -06:00
parent 38debaad5d
commit da59168d7b
2 changed files with 31 additions and 13 deletions

View File

@@ -496,6 +496,16 @@ function App() {
const isAuthRoute = isSignupRoute || isVerifyEmailRoute || isOnboardingRoute;
const shouldShowHomePage = !isGarageRoute && !isCallbackRoute && !isAuthRoute;
const [callbackTimedOut, setCallbackTimedOut] = useState(false);
useEffect(() => {
if (isCallbackRoute && !isAuthenticated && !isLoading) {
const timer = setTimeout(() => setCallbackTimedOut(true), 10000);
return () => clearTimeout(timer);
}
setCallbackTimedOut(false);
return undefined;
}, [isCallbackRoute, isAuthenticated, isLoading]);
// Enhanced navigation handlers for mobile
const handleVehicleSelect = useCallback((vehicle: Vehicle) => {
setSelectedVehicle(vehicle);
@@ -572,6 +582,9 @@ function App() {
</ThemeProvider>
);
}
if (callbackTimedOut) {
return <Navigate to="/" replace />;
}
if (mobileMode) {
return (
<ThemeProvider>

View File

@@ -71,25 +71,27 @@ class IndexedDBStorage implements StorageAdapter, Auth0Cache {
return new Promise((resolve) => {
const transaction = this.db!.transaction([this.storeName], 'readonly');
const store = transaction.objectStore(this.storeName);
const request = store.getAll();
request.onsuccess = () => {
const results = request.result;
const request = store.openCursor();
this.memoryCache.clear();
for (const item of results) {
if (item.key && typeof item.value === 'string') {
this.memoryCache.set(item.key, item.value);
request.onsuccess = () => {
const cursor = request.result;
if (cursor) {
const key = cursor.key as string;
const value = cursor.value;
if (typeof key === 'string' && typeof value === 'string') {
this.memoryCache.set(key, value);
}
}
cursor.continue();
} else {
console.log(`[IndexedDB] Loaded ${this.memoryCache.size} items into cache`);
resolve();
}
};
request.onerror = () => {
console.warn('[IndexedDB] Failed to load cache from DB:', request.error);
resolve(); // Don't fail initialization
resolve();
};
});
}
@@ -199,12 +201,15 @@ class IndexedDBStorage implements StorageAdapter, Auth0Cache {
async set(key: string, value: any): Promise<void> {
await this.initPromise;
this.setItem(key, JSON.stringify(value));
const stringValue = JSON.stringify(value);
this.memoryCache.set(key, stringValue);
await this.persistToDB(key, stringValue);
}
async remove(key: string): Promise<void> {
await this.initPromise;
this.removeItem(key);
this.memoryCache.delete(key);
await this.persistToDB(key, null);
}
// Additional methods for enhanced functionality