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;
}