/** * Session manager for active user sessions */ import type { NostrEvent } from '../../types/nostr.js'; export type AuthMethod = 'nip07' | 'nsec' | 'bunker' | 'anonymous'; export interface UserSession { pubkey: string; method: AuthMethod; signer: (event: Omit) => Promise; createdAt: number; } // Simple store implementation for Svelte reactivity function createStore(initial: T) { let value = initial; const subscribers = new Set<(value: T) => void>(); return { get value() { return value; }, set(newValue: T) { value = newValue; subscribers.forEach((fn) => fn(value)); }, subscribe(fn: (value: T) => void) { subscribers.add(fn); fn(value); return () => subscribers.delete(fn); } }; } class SessionManager { private currentSession: UserSession | null = null; public session = createStore(null); /** * Set current session */ setSession(session: UserSession, metadata?: Record): void { this.currentSession = session; this.session.set(session); // Store in localStorage for persistence if (typeof window !== 'undefined') { const sessionData: any = { pubkey: session.pubkey, method: session.method, createdAt: session.createdAt }; // Store method-specific metadata for restoration if (metadata) { sessionData.metadata = metadata; } localStorage.setItem('aitherboard_session', JSON.stringify(sessionData)); } } /** * Get current session */ getSession(): UserSession | null { return this.currentSession; } /** * Check if user is logged in */ isLoggedIn(): boolean { return this.currentSession !== null; } /** * Get current pubkey */ getCurrentPubkey(): string | null { return this.currentSession?.pubkey || null; } /** * Sign event with current session */ async signEvent(event: Omit): Promise { if (!this.currentSession) { throw new Error('No active session'); } return this.currentSession.signer(event); } /** * Clear session */ clearSession(): void { this.currentSession = null; this.session.set(null); if (typeof window !== 'undefined') { localStorage.removeItem('aitherboard_session'); } } /** * Restore session from localStorage * This will attempt to restore the session based on the auth method */ async restoreSession(): Promise { if (typeof window === 'undefined') return false; const stored = localStorage.getItem('aitherboard_session'); if (!stored) return false; try { const data = JSON.parse(stored); const { pubkey, method, metadata } = data; if (!pubkey || !method) return false; // Import auth handlers dynamically to avoid circular dependencies switch (method) { case 'nip07': { // For NIP-07, we can restore by checking if extension is still available const { isNIP07Available, getPublicKeyWithNIP07, signEventWithNIP07 } = await import('./nip07-signer.js'); if (isNIP07Available()) { try { // Verify the extension still has the same pubkey const currentPubkey = await getPublicKeyWithNIP07(); if (currentPubkey === pubkey) { this.setSession({ pubkey, method: 'nip07', signer: signEventWithNIP07, createdAt: data.createdAt || Date.now() }); return true; } } catch { // Extension error, can't restore return false; } } return false; } case 'bunker': { // For bunker, restore if we have the bunker URI if (metadata?.bunkerUri) { const { connectBunker, signEventWithBunker } = await import('./bunker-signer.js'); try { const connection = await connectBunker(metadata.bunkerUri); if (connection.pubkey === pubkey) { this.setSession({ pubkey, method: 'bunker', signer: async (event) => signEventWithBunker(event, connection), createdAt: data.createdAt || Date.now() }, { bunkerUri: metadata.bunkerUri }); return true; } } catch { // Bunker connection failed return false; } } return false; } case 'anonymous': { // For anonymous, we can restore if the encrypted key is stored // The key is stored in IndexedDB, we just need to verify it exists const { getStoredAnonymousKey } = await import('./anonymous-signer.js'); // We can't restore without password, but we can check if key exists // For now, we'll just restore the pubkey and let signer fail if password is wrong // In practice, user would need to re-enter password if (pubkey) { // Check if key exists in storage try { // This will fail without password, but we can still restore session // The signer will need password on first use const { signEventWithAnonymous } = await import('./anonymous-signer.js'); this.setSession({ pubkey, method: 'anonymous', signer: async (event) => { // This will fail without password - user needs to re-authenticate throw new Error('Anonymous session requires password. Please log in again.'); }, createdAt: data.createdAt || Date.now() }); // Note: This session won't work until user re-authenticates return true; } catch { return false; } } return false; } case 'nsec': { // nsec can't be restored without password (security) // Clear the stored session localStorage.removeItem('aitherboard_session'); return false; } default: return false; } } catch (error) { console.error('Error restoring session:', error); // Clear corrupted session data localStorage.removeItem('aitherboard_session'); return false; } } } export const sessionManager = new SessionManager();