mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-20 07:51:13 -05:00
195 lines
6.6 KiB
TypeScript
195 lines
6.6 KiB
TypeScript
import { authLog, authWarn, authError } from './authLogger';
|
|
|
|
/**
|
|
* Custom storage adapter for Supabase authentication that handles iframe localStorage restrictions.
|
|
* Falls back to sessionStorage or in-memory storage if localStorage is blocked.
|
|
*/
|
|
class AuthStorage {
|
|
private storage: Storage | null = null;
|
|
private memoryStorage: Map<string, string> = new Map();
|
|
private storageType: 'localStorage' | 'sessionStorage' | 'memory' = 'memory';
|
|
private sessionRecoveryAttempted = false;
|
|
|
|
constructor() {
|
|
// Try localStorage first
|
|
try {
|
|
localStorage.setItem('__supabase_test__', 'test');
|
|
localStorage.removeItem('__supabase_test__');
|
|
this.storage = localStorage;
|
|
this.storageType = 'localStorage';
|
|
authLog('[AuthStorage] Using localStorage ✓');
|
|
} catch {
|
|
// Try sessionStorage as fallback
|
|
try {
|
|
sessionStorage.setItem('__supabase_test__', 'test');
|
|
sessionStorage.removeItem('__supabase_test__');
|
|
this.storage = sessionStorage;
|
|
this.storageType = 'sessionStorage';
|
|
authWarn('[AuthStorage] localStorage blocked, using sessionStorage ⚠️');
|
|
} catch {
|
|
// Use in-memory storage as last resort
|
|
this.storageType = 'memory';
|
|
authError('[AuthStorage] Both localStorage and sessionStorage blocked, using in-memory storage ⛔');
|
|
authError('[AuthStorage] Sessions will NOT persist across page reloads!');
|
|
|
|
// Attempt to recover session from URL
|
|
this.attemptSessionRecoveryFromURL();
|
|
}
|
|
}
|
|
|
|
// Listen for storage events to sync across tabs (when possible)
|
|
if (this.storage) {
|
|
window.addEventListener('storage', this.handleStorageChange.bind(this));
|
|
}
|
|
}
|
|
|
|
private attemptSessionRecoveryFromURL() {
|
|
if (this.sessionRecoveryAttempted) return;
|
|
this.sessionRecoveryAttempted = true;
|
|
|
|
try {
|
|
const urlParams = new URLSearchParams(window.location.hash.substring(1));
|
|
const accessToken = urlParams.get('access_token');
|
|
const refreshToken = urlParams.get('refresh_token');
|
|
|
|
if (accessToken && refreshToken) {
|
|
authLog('[AuthStorage] Recovering session from URL parameters');
|
|
// Store in memory
|
|
this.memoryStorage.set('sb-auth-token', JSON.stringify({
|
|
access_token: accessToken,
|
|
refresh_token: refreshToken,
|
|
expires_at: Date.now() + 3600000, // 1 hour
|
|
}));
|
|
|
|
// Clean URL
|
|
window.history.replaceState({}, document.title, window.location.pathname);
|
|
}
|
|
} catch (error: unknown) {
|
|
authError('[AuthStorage] Failed to recover session from URL:', error);
|
|
}
|
|
}
|
|
|
|
private handleStorageChange(event: StorageEvent) {
|
|
// Sync auth state across tabs
|
|
if (event.key?.startsWith('sb-') && event.newValue) {
|
|
authLog('[AuthStorage] Syncing auth state across tabs');
|
|
}
|
|
}
|
|
|
|
getItem(key: string): string | null {
|
|
authLog('[AuthStorage] Getting key:', key);
|
|
try {
|
|
if (this.storage) {
|
|
const value = this.storage.getItem(key);
|
|
authLog('[AuthStorage] Retrieved from storage:', !!value);
|
|
|
|
if (value) {
|
|
// Verify it's not expired
|
|
if (key.includes('auth-token')) {
|
|
try {
|
|
const parsed = JSON.parse(value);
|
|
|
|
// Supabase stores expires_at in seconds, Date.now() is in milliseconds
|
|
// Check if expires_at is in seconds (< year 3000 in milliseconds)
|
|
const expiryTime = parsed.expires_at > 10000000000
|
|
? parsed.expires_at // Already in milliseconds
|
|
: parsed.expires_at * 1000; // Convert from seconds to milliseconds
|
|
|
|
if (parsed.expires_at && expiryTime < Date.now()) {
|
|
authWarn('[AuthStorage] Token expired, removing', {
|
|
expires_at: parsed.expires_at,
|
|
expiryTime: new Date(expiryTime),
|
|
now: new Date()
|
|
});
|
|
this.removeItem(key);
|
|
return null;
|
|
}
|
|
|
|
authLog('[AuthStorage] Token valid, expires:', new Date(expiryTime));
|
|
} catch (e) {
|
|
authWarn('[AuthStorage] Could not parse token for expiry check:', e);
|
|
}
|
|
}
|
|
}
|
|
return value;
|
|
}
|
|
authLog('[AuthStorage] Using memory storage');
|
|
return this.memoryStorage.get(key) || null;
|
|
} catch (error: unknown) {
|
|
authError('[AuthStorage] Error reading from storage:', error);
|
|
return this.memoryStorage.get(key) || null;
|
|
}
|
|
}
|
|
|
|
setItem(key: string, value: string): void {
|
|
authLog('[AuthStorage] Setting key:', key);
|
|
try {
|
|
if (this.storage) {
|
|
this.storage.setItem(key, value);
|
|
}
|
|
// Always keep in memory as backup
|
|
this.memoryStorage.set(key, value);
|
|
} catch (error: unknown) {
|
|
authError('[AuthStorage] Error writing to storage:', error);
|
|
// Fallback to memory only
|
|
this.memoryStorage.set(key, value);
|
|
}
|
|
}
|
|
|
|
removeItem(key: string): void {
|
|
try {
|
|
if (this.storage) {
|
|
this.storage.removeItem(key);
|
|
}
|
|
this.memoryStorage.delete(key);
|
|
} catch (error: unknown) {
|
|
authError('[AuthStorage] Error removing from storage:', error);
|
|
this.memoryStorage.delete(key);
|
|
}
|
|
}
|
|
|
|
// Get storage status for diagnostics
|
|
getStorageStatus(): { type: string; persistent: boolean; warning: string | null } {
|
|
return {
|
|
type: this.storageType,
|
|
persistent: this.storageType !== 'memory',
|
|
warning: this.storageType === 'memory'
|
|
? 'Sessions will not persist across page reloads. Please enable cookies/storage for this site.'
|
|
: null
|
|
};
|
|
}
|
|
|
|
// Clear all auth-related storage (for force logout)
|
|
clearAll(): void {
|
|
authLog('[AuthStorage] Clearing all auth storage');
|
|
try {
|
|
if (this.storage) {
|
|
// Get all keys from storage
|
|
const keys: string[] = [];
|
|
for (let i = 0; i < this.storage.length; i++) {
|
|
const key = this.storage.key(i);
|
|
if (key?.startsWith('sb-')) {
|
|
keys.push(key);
|
|
}
|
|
}
|
|
|
|
// Remove all Supabase auth keys
|
|
keys.forEach(key => {
|
|
authLog('[AuthStorage] Removing key:', key);
|
|
this.storage!.removeItem(key);
|
|
});
|
|
}
|
|
|
|
// Clear memory storage
|
|
this.memoryStorage.clear();
|
|
authLog('[AuthStorage] ✓ All auth storage cleared');
|
|
} catch (error: unknown) {
|
|
authError('[AuthStorage] Error clearing storage:', error);
|
|
// Still clear memory storage as fallback
|
|
this.memoryStorage.clear();
|
|
}
|
|
}
|
|
}
|
|
|
|
export const authStorage = new AuthStorage();
|