From a7dceb115626f3cab558802a753e52b34a527c2b Mon Sep 17 00:00:00 2001 From: Your Name Date: Sat, 20 Sep 2025 15:33:14 -0400 Subject: [PATCH] Fixed persistance issues --- lite/build.js | 146 +++++++++++++++++++++++++++++++++++++++--- lite/nostr-lite.js | 155 ++++++++++++++++++++++++++++++++++++++++++--- lite/ui/modal.js | 7 ++ 3 files changed, 289 insertions(+), 19 deletions(-) diff --git a/lite/build.js b/lite/build.js index abbe37d..c7ee131 100644 --- a/lite/build.js +++ b/lite/build.js @@ -528,10 +528,21 @@ class FloatingTab { } menu.style.top = tabRect.top + 'px'; - // Menu content - const userDisplay = this.userInfo?.pubkey ? - \`\${this.userInfo.pubkey.slice(0, 8)}...\${this.userInfo.pubkey.slice(-4)}\` : - 'Authenticated'; + // Menu content - use _getAuthState() as single source of truth + const authState = this._getAuthState(); + let userDisplay; + + if (authState?.pubkey) { + // Use profile name if available, otherwise pubkey + if (this.userProfile?.name || this.userProfile?.display_name) { + const userName = this.userProfile.name || this.userProfile.display_name; + userDisplay = userName.length > 16 ? \`\${userName.slice(0, 16)}...\` : userName; + } else { + userDisplay = \`\${authState.pubkey.slice(0, 8)}...\${authState.pubkey.slice(-4)}\`; + } + } else { + userDisplay = 'Authenticated'; + } menu.innerHTML = \`
\${userDisplay}
@@ -1163,7 +1174,11 @@ class NostrLite { localStorage.removeItem('nl_current'); } - // Dispatch logout event (AuthManager will clear its own data via WindowNostr listener) + // Clear current authentication state directly from storage + // This works for ALL methods including extensions (fixes the bug) + clearAuthState(); + + // Dispatch logout event for UI updates if (typeof window !== 'undefined') { window.dispatchEvent(new CustomEvent('nlLogout', { detail: { timestamp: Date.now() } @@ -1796,12 +1811,12 @@ class WindowNostr { console.log('WindowNostr: Captured authenticated extension:', this.authenticatedExtension?.constructor?.name); } - // Save authentication state for persistence + // Use global setAuthState function for unified persistence try { - await this.authManager.saveAuthState(event.detail); - console.log('WindowNostr: Auth state saved for persistence'); + setAuthState(event.detail, { isolateSession: this.nostrLite.options?.isolateSession }); + console.log('WindowNostr: Auth state saved via global setAuthState'); } catch (error) { - console.error('WindowNostr: Failed to save auth state:', error); + console.error('WindowNostr: Failed to save auth state via global setAuthState:', error); } // EXTENSION-FIRST: Only reinstall facade for non-extension methods @@ -2210,6 +2225,117 @@ function getAuthState() { } } +// ====================================== +// Global Authentication State Management - Unified Persistence +// ====================================== + +// Global setAuthState function for unified persistence across all authentication methods +function setAuthState(authData, options = {}) { + try { + console.log('🔍 setAuthState: === GLOBAL AUTH STATE SAVE ==='); + console.log('🔍 setAuthState: authData:', authData); + console.log('🔍 setAuthState: options:', options); + + const storageKey = 'nostr_login_lite_auth'; + + // Determine which storage to use based on isolateSession option + const storage = options.isolateSession ? sessionStorage : localStorage; + const storageType = options.isolateSession ? 'sessionStorage' : 'localStorage'; + + console.log('🔍 setAuthState: Using', storageType, 'for persistence'); + + // Create auth state object + const authState = { + method: authData.method, + timestamp: Date.now(), + pubkey: authData.pubkey + }; + + // Add method-specific data (but no secrets for extension method) + switch (authData.method) { + case 'extension': + // For extensions, only store verification data - no secrets + authState.extensionVerification = { + constructor: authData.extension?.constructor?.name, + hasGetPublicKey: typeof authData.extension?.getPublicKey === 'function', + hasSignEvent: typeof authData.extension?.signEvent === 'function' + }; + console.log('🔍 setAuthState: Extension method - storing verification data only'); + break; + + case 'local': + // For local keys, store the secret (will be encrypted by AuthManager if needed) + if (authData.secret) { + authState.secret = authData.secret; + console.log('🔍 setAuthState: Local method - storing secret key'); + } + break; + + case 'nip46': + // For NIP-46, store connection parameters + if (authData.signer) { + authState.nip46 = { + remotePubkey: authData.signer.remotePubkey, + relays: authData.signer.relays, + // Don't store secret - user will need to reconnect + }; + console.log('🔍 setAuthState: NIP-46 method - storing connection parameters'); + } + break; + + case 'readonly': + // Read-only mode has no additional data to store + console.log('🔍 setAuthState: Read-only method - storing basic auth state'); + break; + + default: + console.warn('🔍 setAuthState: Unknown auth method:', authData.method); + break; + } + + // Store the auth state + storage.setItem(storageKey, JSON.stringify(authState)); + console.log('🔍 setAuthState: ✅ Auth state saved successfully'); + console.log('🔍 setAuthState: Final auth state:', authState); + + return authState; + + } catch (error) { + console.error('🔍 setAuthState: ❌ Error saving auth state:', error); + throw error; + } +} + +// ====================================== +// Global Authentication State Clearing +// ====================================== + +// Global clearAuthState function for unified auth state clearing +function clearAuthState() { + try { + console.log('🔍 clearAuthState: === GLOBAL AUTH STATE CLEAR ==='); + + const storageKey = 'nostr_login_lite_auth'; + + // Clear from both storage types to ensure complete cleanup + if (typeof sessionStorage !== 'undefined') { + sessionStorage.removeItem(storageKey); + sessionStorage.removeItem('nostr_session_key'); + console.log('🔍 clearAuthState: ✅ Cleared auth state from sessionStorage'); + } + + if (typeof localStorage !== 'undefined') { + localStorage.removeItem(storageKey); + console.log('🔍 clearAuthState: ✅ Cleared auth state from localStorage'); + } + + console.log('🔍 clearAuthState: ✅ All auth state cleared successfully'); + + } catch (error) { + console.error('🔍 clearAuthState: ❌ Error clearing auth state:', error); + } +} + // Initialize and export if (typeof window !== 'undefined') { @@ -2238,6 +2364,8 @@ if (typeof window !== 'undefined') { // GLOBAL AUTHENTICATION STATE API - Single Source of Truth getAuthState: getAuthState, + setAuthState: setAuthState, + clearAuthState: clearAuthState, // Expose for debugging _extensionBridge: nostrLite.extensionBridge, diff --git a/lite/nostr-lite.js b/lite/nostr-lite.js index f6696d3..ca61418 100644 --- a/lite/nostr-lite.js +++ b/lite/nostr-lite.js @@ -8,7 +8,7 @@ * Two-file architecture: * 1. Load nostr.bundle.js (official nostr-tools bundle) * 2. Load nostr-lite.js (this file - NOSTR_LOGIN_LITE library with CSS-only themes) - * Generated on: 2025-09-20T14:23:53.897Z + * Generated on: 2025-09-20T19:25:01.143Z */ // Verify dependencies are loaded @@ -1382,11 +1382,18 @@ class Modal { if (method === 'extension') { console.log('Modal: Extension method - NOT installing facade, leaving window.nostr as extension'); + // Save extension authentication state using global setAuthState function + if (typeof window.setAuthState === 'function') { + console.log('Modal: Saving extension auth state to storage'); + window.setAuthState({ method, ...options }, { isolateSession: this.options?.isolateSession }); + } + // Emit auth method selection directly for extension const event = new CustomEvent('nlMethodSelected', { detail: { method, ...options } }); window.dispatchEvent(event); + this.close(); return; } @@ -2495,10 +2502,21 @@ class FloatingTab { } menu.style.top = tabRect.top + 'px'; - // Menu content - const userDisplay = this.userInfo?.pubkey ? - `${this.userInfo.pubkey.slice(0, 8)}...${this.userInfo.pubkey.slice(-4)}` : - 'Authenticated'; + // Menu content - use _getAuthState() as single source of truth + const authState = this._getAuthState(); + let userDisplay; + + if (authState?.pubkey) { + // Use profile name if available, otherwise pubkey + if (this.userProfile?.name || this.userProfile?.display_name) { + const userName = this.userProfile.name || this.userProfile.display_name; + userDisplay = userName.length > 16 ? `${userName.slice(0, 16)}...` : userName; + } else { + userDisplay = `${authState.pubkey.slice(0, 8)}...${authState.pubkey.slice(-4)}`; + } + } else { + userDisplay = 'Authenticated'; + } menu.innerHTML = `
${userDisplay}
@@ -3130,7 +3148,11 @@ class NostrLite { localStorage.removeItem('nl_current'); } - // Dispatch logout event (AuthManager will clear its own data via WindowNostr listener) + // Clear current authentication state directly from storage + // This works for ALL methods including extensions (fixes the bug) + clearAuthState(); + + // Dispatch logout event for UI updates if (typeof window !== 'undefined') { window.dispatchEvent(new CustomEvent('nlLogout', { detail: { timestamp: Date.now() } @@ -3763,12 +3785,12 @@ class WindowNostr { console.log('WindowNostr: Captured authenticated extension:', this.authenticatedExtension?.constructor?.name); } - // Save authentication state for persistence + // Use global setAuthState function for unified persistence try { - await this.authManager.saveAuthState(event.detail); - console.log('WindowNostr: Auth state saved for persistence'); + setAuthState(event.detail, { isolateSession: this.nostrLite.options?.isolateSession }); + console.log('WindowNostr: Auth state saved via global setAuthState'); } catch (error) { - console.error('WindowNostr: Failed to save auth state:', error); + console.error('WindowNostr: Failed to save auth state via global setAuthState:', error); } // EXTENSION-FIRST: Only reinstall facade for non-extension methods @@ -4177,6 +4199,117 @@ function getAuthState() { } } +// ====================================== +// Global Authentication State Management - Unified Persistence +// ====================================== + +// Global setAuthState function for unified persistence across all authentication methods +function setAuthState(authData, options = {}) { + try { + console.log('🔍 setAuthState: === GLOBAL AUTH STATE SAVE ==='); + console.log('🔍 setAuthState: authData:', authData); + console.log('🔍 setAuthState: options:', options); + + const storageKey = 'nostr_login_lite_auth'; + + // Determine which storage to use based on isolateSession option + const storage = options.isolateSession ? sessionStorage : localStorage; + const storageType = options.isolateSession ? 'sessionStorage' : 'localStorage'; + + console.log('🔍 setAuthState: Using', storageType, 'for persistence'); + + // Create auth state object + const authState = { + method: authData.method, + timestamp: Date.now(), + pubkey: authData.pubkey + }; + + // Add method-specific data (but no secrets for extension method) + switch (authData.method) { + case 'extension': + // For extensions, only store verification data - no secrets + authState.extensionVerification = { + constructor: authData.extension?.constructor?.name, + hasGetPublicKey: typeof authData.extension?.getPublicKey === 'function', + hasSignEvent: typeof authData.extension?.signEvent === 'function' + }; + console.log('🔍 setAuthState: Extension method - storing verification data only'); + break; + + case 'local': + // For local keys, store the secret (will be encrypted by AuthManager if needed) + if (authData.secret) { + authState.secret = authData.secret; + console.log('🔍 setAuthState: Local method - storing secret key'); + } + break; + + case 'nip46': + // For NIP-46, store connection parameters + if (authData.signer) { + authState.nip46 = { + remotePubkey: authData.signer.remotePubkey, + relays: authData.signer.relays, + // Don't store secret - user will need to reconnect + }; + console.log('🔍 setAuthState: NIP-46 method - storing connection parameters'); + } + break; + + case 'readonly': + // Read-only mode has no additional data to store + console.log('🔍 setAuthState: Read-only method - storing basic auth state'); + break; + + default: + console.warn('🔍 setAuthState: Unknown auth method:', authData.method); + break; + } + + // Store the auth state + storage.setItem(storageKey, JSON.stringify(authState)); + console.log('🔍 setAuthState: ✅ Auth state saved successfully'); + console.log('🔍 setAuthState: Final auth state:', authState); + + return authState; + + } catch (error) { + console.error('🔍 setAuthState: ❌ Error saving auth state:', error); + throw error; + } +} + +// ====================================== +// Global Authentication State Clearing +// ====================================== + +// Global clearAuthState function for unified auth state clearing +function clearAuthState() { + try { + console.log('🔍 clearAuthState: === GLOBAL AUTH STATE CLEAR ==='); + + const storageKey = 'nostr_login_lite_auth'; + + // Clear from both storage types to ensure complete cleanup + if (typeof sessionStorage !== 'undefined') { + sessionStorage.removeItem(storageKey); + sessionStorage.removeItem('nostr_session_key'); + console.log('🔍 clearAuthState: ✅ Cleared auth state from sessionStorage'); + } + + if (typeof localStorage !== 'undefined') { + localStorage.removeItem(storageKey); + console.log('🔍 clearAuthState: ✅ Cleared auth state from localStorage'); + } + + console.log('🔍 clearAuthState: ✅ All auth state cleared successfully'); + + } catch (error) { + console.error('🔍 clearAuthState: ❌ Error clearing auth state:', error); + } +} + // Initialize and export if (typeof window !== 'undefined') { @@ -4205,6 +4338,8 @@ if (typeof window !== 'undefined') { // GLOBAL AUTHENTICATION STATE API - Single Source of Truth getAuthState: getAuthState, + setAuthState: setAuthState, + clearAuthState: clearAuthState, // Expose for debugging _extensionBridge: nostrLite.extensionBridge, diff --git a/lite/ui/modal.js b/lite/ui/modal.js index a790c07..aa4b3fe 100644 --- a/lite/ui/modal.js +++ b/lite/ui/modal.js @@ -1085,11 +1085,18 @@ class Modal { if (method === 'extension') { console.log('Modal: Extension method - NOT installing facade, leaving window.nostr as extension'); + // Save extension authentication state using global setAuthState function + if (typeof window.setAuthState === 'function') { + console.log('Modal: Saving extension auth state to storage'); + window.setAuthState({ method, ...options }, { isolateSession: this.options?.isolateSession }); + } + // Emit auth method selection directly for extension const event = new CustomEvent('nlMethodSelected', { detail: { method, ...options } }); window.dispatchEvent(event); + this.close(); return; }