Fixed persistance issues

This commit is contained in:
Your Name
2025-09-20 15:33:14 -04:00
parent 966d9d0456
commit a7dceb1156
3 changed files with 289 additions and 19 deletions

View File

@@ -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 = \`
<div style="margin-bottom: 8px; font-weight: bold; color: var(--nl-primary-color);">\${userDisplay}</div>
@@ -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,

View File

@@ -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 = `
<div style="margin-bottom: 8px; font-weight: bold; color: var(--nl-primary-color);">${userDisplay}</div>
@@ -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,

View File

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