diff --git a/examples/keytest.html b/examples/keytest.html
index ed7005a..5820fd8 100644
--- a/examples/keytest.html
+++ b/examples/keytest.html
@@ -104,6 +104,18 @@
}});
+ // Check for existing authentication state on page load
+ const authState = getAuthState();
+ if (authState && authState.method) {
+ console.log('Found existing authentication:', authState.method);
+ document.getElementById('status').textContent = `Authenticated with: ${authState.method}`;
+ document.getElementById('test-section').style.display = 'block';
+
+ // Store some test data for encryption/decryption
+ window.testCiphertext = null;
+ window.testCiphertext44 = null;
+ }
+
// Listen for authentication events
window.addEventListener('nlMethodSelected', (event) => {
console.log('User authenticated:', event.detail);
diff --git a/lite/VERSION b/lite/VERSION
index 845639e..9faa1b7 100644
--- a/lite/VERSION
+++ b/lite/VERSION
@@ -1 +1 @@
-0.1.4
+0.1.5
diff --git a/lite/build.js b/lite/build.js
index af19f01..7599f72 100644
--- a/lite/build.js
+++ b/lite/build.js
@@ -109,7 +109,7 @@ if (typeof window !== 'undefined') {
bundle += ` style.id = 'nl-theme-css';\n`;
bundle += ` style.textContent = themeCss;\n`;
bundle += ` document.head.appendChild(style);\n`;
- bundle += ` console.log(\`NOSTR_LOGIN_LITE: \${themeName} theme CSS injected\`);\n`;
+ bundle += ` console.log('NOSTR_LOGIN_LITE: ' + themeName + ' theme CSS injected');\n`;
bundle += ` }\n`;
bundle += `}\n\n`;
@@ -131,24 +131,46 @@ if (typeof window !== 'undefined') {
let modalContent = fs.readFileSync(modalPath, 'utf8');
- // Read version from VERSION file and inject into modal title
+ // Read version from VERSION file for bottom-right display
const versionPath = path.join(__dirname, 'VERSION');
- let versionTitle = 'Nostr Login';
+ let versionString = '';
if (fs.existsSync(versionPath)) {
try {
const version = fs.readFileSync(versionPath, 'utf8').trim();
- versionTitle = `Nostr Login v${version}`;
- console.log(`š¢ Using version: ${version}`);
+ versionString = 'v' + version;
+ console.log('š¢ Using version: ' + version);
} catch (error) {
- console.warn('ā ļø Could not read VERSION file, using default title');
+ console.warn('ā ļø Could not read VERSION file, no version will be displayed');
}
} else {
- console.log('š No VERSION file found, using default title');
+ console.log('š No VERSION file found, no version will be displayed');
}
- // Replace the modal title in the content
- modalContent = modalContent.replace(/modalTitle\.textContent = 'Nostr Login';/, `modalTitle.textContent = '${versionTitle}';`);
+ // Keep modal title as just "Nostr Login" (no version injection)
+ // Add version element in bottom-right corner if version exists
+ if (versionString) {
+ // Find the modalContent.appendChild(this.modalBody); line and insert version element before it
+ modalContent = modalContent.replace(
+ /modalContent\.appendChild\(this\.modalBody\);/,
+ `// Add version element in bottom-right corner aligned with modal body
+ const versionElement = document.createElement('div');
+ versionElement.textContent = '${versionString}';
+ versionElement.style.cssText = \`
+ position: absolute;
+ bottom: 8px;
+ right: 24px;
+ font-size: 14px;
+ color: #666666;
+ font-family: var(--nl-font-family, 'Courier New', monospace);
+ pointer-events: none;
+ z-index: 1;
+ \`;
+ modalContent.appendChild(versionElement);
+
+ modalContent.appendChild(this.modalBody);`
+ );
+ }
// Skip header comments
let lines = modalContent.split('\n');
@@ -555,9 +577,9 @@ class FloatingTab {
// 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;
+ userDisplay = userName.length > 16 ? userName.slice(0, 16) + '...' : userName;
} else {
- userDisplay = \`\${authState.pubkey.slice(0, 8)}...\${authState.pubkey.slice(-4)}\`;
+ userDisplay = authState.pubkey.slice(0, 8) + '...' + authState.pubkey.slice(-4);
}
} else {
userDisplay = 'Authenticated';
@@ -608,7 +630,7 @@ class FloatingTab {
// Fallback to pubkey display
display = this.options.appearance.iconOnly
? authState.pubkey.slice(0, 6)
- : \`\${authState.pubkey.slice(0, 6)}...\`;
+ : authState.pubkey.slice(0, 6) + '...';
} else {
display = this.options.appearance.iconOnly ? 'User' : 'Authenticated';
}
@@ -618,7 +640,7 @@ class FloatingTab {
} else {
const display = this.options.appearance.iconOnly ?
this.options.appearance.icon :
- (this.options.appearance.icon ? \`\${this.options.appearance.icon} \${this.options.appearance.text}\` : this.options.appearance.text);
+ (this.options.appearance.icon ? this.options.appearance.icon + ' ' + this.options.appearance.text : this.options.appearance.text);
this.container.textContent = display;
this.container.className = 'nl-floating-tab nl-floating-tab--logged-out';
@@ -732,10 +754,10 @@ class FloatingTab {
const x = this.options.hPosition * (window.innerWidth - this.container.offsetWidth - padding * 2) + padding + this.options.offset.x;
const y = this.options.vPosition * (window.innerHeight - this.container.offsetHeight - padding * 2) + padding + this.options.offset.y;
- this.container.style.left = \`\${x}px\`;
- this.container.style.top = \`\${y}px\`;
+ this.container.style.left = x + 'px';
+ this.container.style.top = y + 'px';
- console.log(\`FloatingTab: Positioned at (\${x}, \${y})\`);
+ console.log('FloatingTab: Positioned at (' + x + ', ' + y + ')');
}
_slideIn() {
@@ -964,16 +986,37 @@ class NostrLite {
this._installFacade(window.nostr); // Install facade with any existing nostr object
console.log('š NOSTR_LOGIN_LITE: ā
Facade installed for local/NIP-46/readonly methods');
+
+ // CRITICAL FIX: Immediately attempt to restore auth state after facade installation
+ if (this.facadeInstalled && window.nostr?.restoreAuthState) {
+ console.log('š NOSTR_LOGIN_LITE: š IMMEDIATELY attempting auth restoration after facade installation');
+ try {
+ const restoredAuth = await window.nostr.restoreAuthState();
+ if (restoredAuth) {
+ console.log('š NOSTR_LOGIN_LITE: ā
Auth state restored immediately during facade setup!');
+ console.log('š NOSTR_LOGIN_LITE: Method:', restoredAuth.method);
+ console.log('š NOSTR_LOGIN_LITE: Pubkey:', restoredAuth.pubkey);
+
+ // Update facade's authState immediately
+ window.nostr.authState = restoredAuth;
+ } else {
+ console.log('š NOSTR_LOGIN_LITE: ā No auth state to restore during facade setup');
+ }
+ } catch (error) {
+ console.error('š NOSTR_LOGIN_LITE: ā Error restoring auth during facade setup:', error);
+ }
+ }
}
}
- _installFacade(existingNostr = null) {
- if (typeof window !== 'undefined' && !this.facadeInstalled) {
+ _installFacade(existingNostr = null, forceInstall = false) {
+ if (typeof window !== 'undefined' && (!this.facadeInstalled || forceInstall)) {
console.log('š NOSTR_LOGIN_LITE: === _installFacade CALLED ===');
console.log('š NOSTR_LOGIN_LITE: existingNostr parameter:', existingNostr);
console.log('š NOSTR_LOGIN_LITE: existingNostr constructor:', existingNostr?.constructor?.name);
console.log('š NOSTR_LOGIN_LITE: window.nostr before installation:', window.nostr);
console.log('š NOSTR_LOGIN_LITE: window.nostr constructor before:', window.nostr?.constructor?.name);
+ console.log('š NOSTR_LOGIN_LITE: forceInstall flag:', forceInstall);
const facade = new WindowNostr(this, existingNostr, { isolateSession: this.options.isolateSession });
window.nostr = facade;
@@ -983,6 +1026,8 @@ class NostrLite {
console.log('š NOSTR_LOGIN_LITE: window.nostr after installation:', window.nostr);
console.log('š NOSTR_LOGIN_LITE: window.nostr constructor after:', window.nostr.constructor?.name);
console.log('š NOSTR_LOGIN_LITE: facade.existingNostr:', window.nostr.existingNostr);
+ } else if (typeof window !== 'undefined') {
+ console.log('š NOSTR_LOGIN_LITE: _installFacade skipped - facadeInstalled:', this.facadeInstalled, 'forceInstall:', forceInstall);
}
}
@@ -1081,6 +1126,13 @@ class NostrLite {
console.log('š NOSTR_LOGIN_LITE: Method:', restoredAuth.method);
console.log('š NOSTR_LOGIN_LITE: Pubkey:', restoredAuth.pubkey);
+ // CRITICAL FIX: Activate facade resilience system for non-extension methods
+ // Extensions like nos2x can override our facade after page refresh
+ if (restoredAuth.method === 'local' || restoredAuth.method === 'nip46') {
+ console.log('š NOSTR_LOGIN_LITE: š”ļø Activating facade resilience system for page refresh');
+ this._activateResilienceProtection(restoredAuth.method);
+ }
+
// Handle NIP-46 reconnection requirement
if (restoredAuth.requiresReconnection) {
console.log('š NOSTR_LOGIN_LITE: NIP-46 connection requires user reconnection');
@@ -1107,6 +1159,45 @@ class NostrLite {
}
}
+ // Activate facade resilience protection against extension overrides
+ _activateResilienceProtection(method) {
+ console.log('š”ļø NOSTR_LOGIN_LITE: === ACTIVATING RESILIENCE PROTECTION ===');
+ console.log('š”ļø NOSTR_LOGIN_LITE: Protecting facade for method:', method);
+
+ // Store the current extension if any (for potential restoration later)
+ const preservedExtension = this.preservedExtension ||
+ ((window.nostr?.constructor?.name !== 'WindowNostr') ? window.nostr : null);
+
+ // DELAYED FACADE RESILIENCE - Reinstall after extension override attempts
+ const forceReinstallFacade = () => {
+ console.log('š”ļø NOSTR_LOGIN_LITE: RESILIENCE CHECK - Current window.nostr after delay:', window.nostr?.constructor?.name);
+
+ // If facade was overridden by extension, reinstall it
+ if (window.nostr?.constructor?.name !== 'WindowNostr') {
+ console.log('š”ļø NOSTR_LOGIN_LITE: FACADE OVERRIDDEN! Force-reinstalling WindowNostr facade for user choice:', method);
+ this._installFacade(preservedExtension, true);
+ console.log('š”ļø NOSTR_LOGIN_LITE: Resilient facade force-reinstall complete, window.nostr:', window.nostr?.constructor?.name);
+
+ // Schedule another check in case of persistent extension override
+ setTimeout(() => {
+ if (window.nostr?.constructor?.name !== 'WindowNostr') {
+ console.log('š”ļø NOSTR_LOGIN_LITE: PERSISTENT OVERRIDE! Final facade force-reinstall for method:', method);
+ this._installFacade(preservedExtension, true);
+ }
+ }, 1000);
+ } else {
+ console.log('š”ļø NOSTR_LOGIN_LITE: Facade persistence verified - no override detected');
+ }
+ };
+
+ // Schedule resilience checks at multiple intervals (same as Modal)
+ setTimeout(forceReinstallFacade, 100); // Quick check
+ setTimeout(forceReinstallFacade, 500); // Main check
+ setTimeout(forceReinstallFacade, 1500); // Final check
+
+ console.log('š”ļø NOSTR_LOGIN_LITE: Resilience protection scheduled for method:', method);
+ }
+
// Extension-specific authentication restoration
async _attemptExtensionRestore() {
try {
@@ -1207,7 +1298,7 @@ class NostrLite {
// CSS-only theme switching
switchTheme(themeName) {
- console.log(\`NOSTR_LOGIN_LITE: Switching to \${themeName} theme\`);
+ console.log('NOSTR_LOGIN_LITE: Switching to ' + themeName + ' theme');
if (THEME_CSS[themeName]) {
injectThemeCSS(themeName);
@@ -1222,7 +1313,7 @@ class NostrLite {
return { theme: themeName };
} else {
- console.warn(\`Theme '\${themeName}' not found, using default\`);
+ console.warn("Theme '" + themeName + "' not found, using default");
injectThemeCSS('default');
this.currentTheme = 'default';
return { theme: 'default' };
@@ -1290,115 +1381,10 @@ class NostrLite {
}
// ======================================
-// Authentication Manager for Persistent Login
+// Simplified Authentication Manager (Unified Plaintext Storage)
// ======================================
-// Encryption utilities for secure local storage
-class CryptoUtils {
- static async generateKey() {
- if (!window.crypto?.subtle) {
- throw new Error('Web Crypto API not available');
- }
-
- return await window.crypto.subtle.generateKey(
- {
- name: 'AES-GCM',
- length: 256,
- },
- true,
- ['encrypt', 'decrypt']
- );
- }
-
- static async deriveKey(password, salt) {
- if (!window.crypto?.subtle) {
- throw new Error('Web Crypto API not available');
- }
-
- const encoder = new TextEncoder();
- const keyMaterial = await window.crypto.subtle.importKey(
- 'raw',
- encoder.encode(password),
- { name: 'PBKDF2' },
- false,
- ['deriveBits', 'deriveKey']
- );
-
- return await window.crypto.subtle.deriveKey(
- {
- name: 'PBKDF2',
- salt: salt,
- iterations: 100000,
- hash: 'SHA-256',
- },
- keyMaterial,
- { name: 'AES-GCM', length: 256 },
- true,
- ['encrypt', 'decrypt']
- );
- }
-
- static async encrypt(data, key) {
- if (!window.crypto?.subtle) {
- throw new Error('Web Crypto API not available');
- }
-
- const encoder = new TextEncoder();
- const iv = window.crypto.getRandomValues(new Uint8Array(12));
-
- const encrypted = await window.crypto.subtle.encrypt(
- {
- name: 'AES-GCM',
- iv: iv,
- },
- key,
- encoder.encode(data)
- );
-
- return {
- encrypted: new Uint8Array(encrypted),
- iv: iv
- };
- }
-
- static async decrypt(encryptedData, key, iv) {
- if (!window.crypto?.subtle) {
- throw new Error('Web Crypto API not available');
- }
-
- const decrypted = await window.crypto.subtle.decrypt(
- {
- name: 'AES-GCM',
- iv: iv,
- },
- key,
- encryptedData
- );
-
- const decoder = new TextDecoder();
- return decoder.decode(decrypted);
- }
-
- static arrayBufferToBase64(buffer) {
- const bytes = new Uint8Array(buffer);
- let binary = '';
- for (let i = 0; i < bytes.byteLength; i++) {
- binary += String.fromCharCode(bytes[i]);
- }
- return window.btoa(binary);
- }
-
- static base64ToArrayBuffer(base64) {
- const binary = window.atob(base64);
- const bytes = new Uint8Array(binary.length);
- for (let i = 0; i < binary.length; i++) {
- bytes[i] = binary.charCodeAt(i);
- }
- return bytes.buffer;
- }
-}
-
-// Unified authentication state manager
+// Simple authentication state manager - plaintext storage for maximum usability
class AuthManager {
constructor(options = {}) {
this.storageKey = 'nostr_login_lite_auth';
@@ -1407,16 +1393,22 @@ class AuthManager {
// Configure storage type based on isolateSession option
if (options.isolateSession) {
this.storage = sessionStorage;
- console.log('AuthManager: Using sessionStorage for per-window isolation');
+ console.log('š AuthManager: Using sessionStorage for per-window isolation');
} else {
this.storage = localStorage;
- console.log('AuthManager: Using localStorage for cross-window persistence');
+ console.log('š AuthManager: Using localStorage for cross-window persistence');
}
+
+ console.warn('š SECURITY: Private keys stored unencrypted in browser storage');
+ console.warn('š For production apps, implement your own secure storage');
}
- // Save authentication state with method-specific security
+ // Save authentication state using unified plaintext approach
async saveAuthState(authData) {
try {
+ console.log('š AuthManager: Saving auth state with plaintext storage');
+ console.warn('š SECURITY: Private key will be stored unencrypted for maximum usability');
+
const authState = {
method: authData.method,
timestamp: Date.now(),
@@ -1431,24 +1423,15 @@ class AuthManager {
hasGetPublicKey: typeof authData.extension?.getPublicKey === 'function',
hasSignEvent: typeof authData.extension?.signEvent === 'function'
};
+ console.log('š AuthManager: Extension method - storing verification data only');
break;
case 'local':
- // For local keys, encrypt the secret key
+ // UNIFIED PLAINTEXT: Store secret key directly for maximum compatibility
if (authData.secret) {
- const password = this._generateSessionPassword();
- const salt = window.crypto.getRandomValues(new Uint8Array(16));
- const key = await CryptoUtils.deriveKey(password, salt);
- const encrypted = await CryptoUtils.encrypt(authData.secret, key);
-
- authState.encrypted = {
- data: CryptoUtils.arrayBufferToBase64(encrypted.encrypted),
- iv: CryptoUtils.arrayBufferToBase64(encrypted.iv),
- salt: CryptoUtils.arrayBufferToBase64(salt)
- };
-
- // Store session password in sessionStorage (cleared on tab close)
- sessionStorage.setItem('nostr_session_key', password);
+ authState.secret = authData.secret;
+ console.log('š AuthManager: Local method - storing secret key in plaintext');
+ console.warn('š SECURITY: Secret key stored unencrypted for developer convenience');
}
break;
@@ -1460,23 +1443,25 @@ class AuthManager {
relays: authData.signer.relays,
// Don't store secret - user will need to reconnect
};
+ console.log('š AuthManager: NIP-46 method - storing connection parameters');
}
break;
case 'readonly':
// Read-only mode has no secrets to store
+ console.log('š AuthManager: Read-only method - storing basic auth state');
break;
default:
- throw new Error(\`Unknown auth method: \${authData.method}\`);
+ throw new Error('Unknown auth method: ' + authData.method);
}
this.storage.setItem(this.storageKey, JSON.stringify(authState));
this.currentAuthState = authState;
- console.log('AuthManager: Auth state saved for method:', authData.method);
+ console.log('š AuthManager: Auth state saved successfully for method:', authData.method);
} catch (error) {
- console.error('AuthManager: Failed to save auth state:', error);
+ console.error('š AuthManager: Failed to save auth state:', error);
throw error;
}
}
@@ -1488,7 +1473,7 @@ class AuthManager {
console.log('š AuthManager: storageKey:', this.storageKey);
const stored = this.storage.getItem(this.storageKey);
- console.log('š AuthManager: localStorage raw value:', stored);
+ console.log('š AuthManager: Storage raw value:', stored);
if (!stored) {
console.log('š AuthManager: ā No stored auth state found');
@@ -1711,51 +1696,57 @@ class AuthManager {
}
async _restoreLocalAuth(authState) {
- if (!authState.encrypted) {
- console.log('AuthManager: No encrypted data found for local auth');
- return null;
- }
-
- // Get session password
- const sessionPassword = sessionStorage.getItem('nostr_session_key');
- if (!sessionPassword) {
- console.log('AuthManager: Session password not found, cannot decrypt');
- return null;
- }
-
- try {
- // Decrypt the secret key
- const salt = CryptoUtils.base64ToArrayBuffer(authState.encrypted.salt);
- const key = await CryptoUtils.deriveKey(sessionPassword, new Uint8Array(salt));
+ console.log('š AuthManager: === _restoreLocalAuth (Unified Plaintext) ===');
+
+ // Check for legacy encrypted format first
+ if (authState.encrypted) {
+ console.log('š AuthManager: Detected LEGACY encrypted format - migrating to plaintext');
+ console.warn('š SECURITY: Converting from encrypted to plaintext storage for compatibility');
- const encryptedData = CryptoUtils.base64ToArrayBuffer(authState.encrypted.data);
- const iv = CryptoUtils.base64ToArrayBuffer(authState.encrypted.iv);
-
- const secret = await CryptoUtils.decrypt(encryptedData, key, new Uint8Array(iv));
+ // Try to decrypt legacy format
+ const sessionPassword = sessionStorage.getItem('nostr_session_key');
+ if (!sessionPassword) {
+ console.log('š AuthManager: Legacy session password not found - user must re-login');
+ return null;
+ }
- console.log('AuthManager: Local auth restored successfully');
- return {
- method: 'local',
- pubkey: authState.pubkey,
- secret: secret
- };
-
- } catch (error) {
- console.error('AuthManager: Failed to decrypt local key:', error);
+ try {
+ console.warn('š AuthManager: Legacy encryption system no longer supported - user must re-login');
+ this.clearAuthState(); // Clear legacy format
+ return null;
+ } catch (error) {
+ console.error('š AuthManager: Legacy decryption failed:', error);
+ this.clearAuthState(); // Clear corrupted legacy format
+ return null;
+ }
+ }
+
+ // NEW UNIFIED PLAINTEXT FORMAT
+ if (!authState.secret) {
+ console.log('š AuthManager: No secret found in plaintext format');
return null;
}
+
+ console.log('š AuthManager: ā
Local auth restored from plaintext storage');
+ console.warn('š SECURITY: Secret key was stored unencrypted');
+
+ return {
+ method: 'local',
+ pubkey: authState.pubkey,
+ secret: authState.secret
+ };
}
async _restoreNip46Auth(authState) {
if (!authState.nip46) {
- console.log('AuthManager: No NIP-46 data found');
+ console.log('š AuthManager: No NIP-46 data found');
return null;
}
// For NIP-46, we can't automatically restore the connection
// because it requires the user to re-authenticate with the remote signer
// Instead, we return the connection parameters so the UI can prompt for reconnection
- console.log('AuthManager: NIP-46 connection data found, requires user reconnection');
+ console.log('š AuthManager: NIP-46 connection data found, requires user reconnection');
return {
method: 'nip46',
pubkey: authState.pubkey,
@@ -1765,7 +1756,7 @@ class AuthManager {
}
async _restoreReadonlyAuth(authState) {
- console.log('AuthManager: Read-only auth restored successfully');
+ console.log('š AuthManager: Read-only auth restored successfully');
return {
method: 'readonly',
pubkey: authState.pubkey
@@ -1775,16 +1766,9 @@ class AuthManager {
// Clear stored authentication state
clearAuthState() {
this.storage.removeItem(this.storageKey);
- sessionStorage.removeItem('nostr_session_key');
+ sessionStorage.removeItem('nostr_session_key'); // Clear legacy session key
this.currentAuthState = null;
- console.log('AuthManager: Auth state cleared');
- }
-
- // Generate a session-specific password for local key encryption
- _generateSessionPassword() {
- const array = new Uint8Array(32);
- window.crypto.getRandomValues(array);
- return Array.from(array, byte => byte.toString(16).padStart(2, '0')).join('');
+ console.log('š AuthManager: Auth state cleared from unified storage');
}
// Check if we have valid stored auth
@@ -1807,119 +1791,178 @@ class AuthManager {
}
}
+// ======================================
+// Global Authentication Functions (Single Source of Truth)
+// ======================================
+
+// Global authentication state (single source of truth)
+let globalAuthState = null;
+let globalAuthManager = null;
+
+// Initialize global auth manager (lazy initialization)
+function getGlobalAuthManager() {
+ if (!globalAuthManager) {
+ // Default to localStorage for persistence across browser sessions
+ globalAuthManager = new AuthManager({ isolateSession: false });
+ }
+ return globalAuthManager;
+}
+
+// **UNIFIED GLOBAL FUNCTION**: Set authentication state (works for all methods)
+function setAuthState(authData, options = {}) {
+ try {
+ console.log('š setAuthState: Setting global auth state for method:', authData.method);
+ console.warn('š SECURITY: Using unified plaintext storage for maximum compatibility');
+
+ // Store in memory
+ globalAuthState = authData;
+
+ // Store in browser storage using AuthManager
+ const authManager = new AuthManager(options);
+ authManager.saveAuthState(authData);
+
+ console.log('š setAuthState: Auth state saved successfully');
+ } catch (error) {
+ console.error('š setAuthState: Failed to save auth state:', error);
+ throw error;
+ }
+}
+
+// **UNIFIED GLOBAL FUNCTION**: Get authentication state (single source of truth)
+function getAuthState() {
+ try {
+ // Always query from storage as the authoritative source
+ const authManager = getGlobalAuthManager();
+ const storageKey = 'nostr_login_lite_auth';
+
+ // Check both session and local storage for compatibility
+ let stored = null;
+ if (sessionStorage.getItem(storageKey)) {
+ stored = sessionStorage.getItem(storageKey);
+ } else if (localStorage.getItem(storageKey)) {
+ stored = localStorage.getItem(storageKey);
+ }
+
+ if (!stored) {
+ console.log('š getAuthState: No auth state found in storage');
+ globalAuthState = null;
+ return null;
+ }
+
+ const authState = JSON.parse(stored);
+ console.log('š getAuthState: Retrieved auth state:', authState.method);
+
+ // Update in-memory cache
+ globalAuthState = authState;
+ return authState;
+
+ } catch (error) {
+ console.error('š getAuthState: Failed to get auth state:', error);
+ globalAuthState = null;
+ return null;
+ }
+}
+
+// **UNIFIED GLOBAL FUNCTION**: Clear authentication state (works for all methods)
+function clearAuthState() {
+ try {
+ console.log('š clearAuthState: Clearing global auth state');
+
+ // Clear in-memory state
+ globalAuthState = null;
+
+ // Clear from both storage types for thorough cleanup
+ const storageKey = 'nostr_login_lite_auth';
+ localStorage.removeItem(storageKey);
+ sessionStorage.removeItem(storageKey);
+ sessionStorage.removeItem('nostr_session_key'); // Clear legacy session key
+
+ console.log('š clearAuthState: Auth state cleared from all storage locations');
+ } catch (error) {
+ console.error('š clearAuthState: Failed to clear auth state:', error);
+ }
+}
+
// NIP-07 compliant window.nostr provider
class WindowNostr {
- constructor(nostrLite, existingNostr = null) {
+ constructor(nostrLite, existingNostr = null, options = {}) {
this.nostrLite = nostrLite;
this.authState = null;
this.existingNostr = existingNostr;
this.authenticatedExtension = null;
- this.authManager = new AuthManager({ isolateSession: nostrLite.options?.isolateSession });
+ this.options = options;
this._setupEventListeners();
}
+ // Restore authentication state on page load
+ async restoreAuthState() {
+ console.log('š WindowNostr: === restoreAuthState ===');
+
+ try {
+ // Use simplified AuthManager for consistent restore logic
+ const authManager = new AuthManager(this.options);
+ const restoredAuth = await authManager.restoreAuthState();
+
+ if (restoredAuth) {
+ console.log('š WindowNostr: ā
Auth state restored:', restoredAuth.method);
+ this.authState = restoredAuth;
+
+ // Update global state
+ globalAuthState = restoredAuth;
+
+ // Dispatch restoration event
+ if (typeof window !== 'undefined') {
+ window.dispatchEvent(new CustomEvent('nlAuthRestored', {
+ detail: restoredAuth
+ }));
+ }
+
+ return restoredAuth;
+ } else {
+ console.log('š WindowNostr: ā No auth state to restore');
+ return null;
+ }
+
+ } catch (error) {
+ console.error('š WindowNostr: Auth restoration failed:', error);
+ return null;
+ }
+ }
+
_setupEventListeners() {
// Listen for authentication events to store auth state
if (typeof window !== 'undefined') {
window.addEventListener('nlMethodSelected', async (event) => {
+ console.log('š WindowNostr: nlMethodSelected event received:', event.detail);
this.authState = event.detail;
// If extension method, capture the specific extension the user chose
if (event.detail.method === 'extension') {
this.authenticatedExtension = event.detail.extension;
- console.log('WindowNostr: Captured authenticated extension:', this.authenticatedExtension?.constructor?.name);
+ console.log('š WindowNostr: Captured authenticated extension:', this.authenticatedExtension?.constructor?.name);
}
- // Use global setAuthState function for unified persistence
+ // Use unified global setAuthState function for all methods
try {
- setAuthState(event.detail, { isolateSession: this.nostrLite.options?.isolateSession });
- console.log('WindowNostr: Auth state saved via global setAuthState');
+ setAuthState(event.detail, this.options);
+ console.log('š WindowNostr: Auth state saved via unified setAuthState');
} catch (error) {
- console.error('WindowNostr: Failed to save auth state via global setAuthState:', error);
+ console.error('š WindowNostr: Failed to save auth state:', error);
}
-
- // EXTENSION-FIRST: Only reinstall facade for non-extension methods
- // Extensions handle their own window.nostr - don't interfere!
- if (event.detail.method !== 'extension' && typeof window !== 'undefined') {
- console.log('WindowNostr: Re-installing facade after', this.authState?.method, 'authentication');
- window.nostr = this;
- } else if (event.detail.method === 'extension') {
- console.log('WindowNostr: Extension authentication - NOT reinstalling facade');
- }
-
- console.log('WindowNostr: Auth state updated:', this.authState?.method);
});
window.addEventListener('nlLogout', () => {
+ console.log('š WindowNostr: nlLogout event received');
this.authState = null;
this.authenticatedExtension = null;
- // Clear persistent auth state
- this.authManager.clearAuthState();
- console.log('WindowNostr: Auth state cleared and persistence removed');
-
- // EXTENSION-FIRST: Only reinstall facade if we're not in extension mode
- if (typeof window !== 'undefined' && !this.nostrLite?.hasExtension) {
- console.log('WindowNostr: Re-installing facade after logout (non-extension mode)');
- window.nostr = this;
- } else {
- console.log('WindowNostr: Logout in extension mode - NOT reinstalling facade');
- }
+ // Clear from unified storage
+ clearAuthState();
+ console.log('š WindowNostr: Auth state cleared via unified clearAuthState');
});
}
}
- // Restore authentication state on page load
- async restoreAuthState() {
- try {
- console.log('š WindowNostr: === restoreAuthState START ===');
- console.log('š WindowNostr: authManager available:', !!this.authManager);
-
- const restoredAuth = await this.authManager.restoreAuthState();
- console.log('š WindowNostr: authManager.restoreAuthState result:', restoredAuth);
-
- if (restoredAuth) {
- console.log('š WindowNostr: ā
Setting authState to restored auth');
- this.authState = restoredAuth;
- console.log('š WindowNostr: this.authState now:', this.authState);
-
- // Handle method-specific restoration
- if (restoredAuth.method === 'extension') {
- console.log('š WindowNostr: Extension method - setting authenticatedExtension');
- this.authenticatedExtension = restoredAuth.extension;
- console.log('š WindowNostr: authenticatedExtension set to:', this.authenticatedExtension);
- }
-
- console.log('š WindowNostr: ā
Authentication state restored successfully!');
- console.log('š WindowNostr: Method:', restoredAuth.method);
- console.log('š WindowNostr: Pubkey:', restoredAuth.pubkey);
-
- // Dispatch restoration event so UI can update
- if (typeof window !== 'undefined') {
- console.log('š WindowNostr: Dispatching nlAuthRestored event...');
- const event = new CustomEvent('nlAuthRestored', {
- detail: restoredAuth
- });
- console.log('š WindowNostr: Event detail:', event.detail);
- window.dispatchEvent(event);
- console.log('š WindowNostr: ā
nlAuthRestored event dispatched');
- }
-
- console.log('š WindowNostr: === restoreAuthState END (success) ===');
- return restoredAuth;
- } else {
- console.log('š WindowNostr: ā No authentication state to restore (null from authManager)');
- console.log('š WindowNostr: === restoreAuthState END (no restore) ===');
- return null;
- }
- } catch (error) {
- console.error('š WindowNostr: ā Failed to restore auth state:', error);
- console.error('š WindowNostr: Error stack:', error.stack);
- console.log('š WindowNostr: === restoreAuthState END (error) ===');
- return null;
- }
- }
-
async getPublicKey() {
if (!this.authState) {
throw new Error('Not authenticated - use NOSTR_LOGIN_LITE.launch()');
@@ -1940,7 +1983,7 @@ class WindowNostr {
throw new Error('Read-only mode - cannot get public key');
default:
- throw new Error(\`Unsupported auth method: \${this.authState.method}\`);
+ throw new Error('Unsupported auth method: ' + this.authState.method);
}
}
@@ -1956,14 +1999,7 @@ class WindowNostr {
switch (this.authState.method) {
case 'extension':
// Use the captured authenticated extension, not current window.nostr
- console.log('WindowNostr: signEvent - authenticatedExtension:', this.authenticatedExtension);
- console.log('WindowNostr: signEvent - authState.extension:', this.authState.extension);
- console.log('WindowNostr: signEvent - existingNostr:', this.existingNostr);
-
const ext = this.authenticatedExtension || this.authState.extension || this.existingNostr;
- console.log('WindowNostr: signEvent - using extension:', ext);
- console.log('WindowNostr: signEvent - extension constructor:', ext?.constructor?.name);
-
if (!ext) throw new Error('Extension not available');
return await ext.signEvent(event);
@@ -1992,13 +2028,13 @@ class WindowNostr {
}
default:
- throw new Error(\`Unsupported auth method: \${this.authState.method}\`);
+ throw new Error('Unsupported auth method: ' + this.authState.method);
}
}
async getRelays() {
- // Return default relays since we removed the relays configuration
- return ['wss://relay.damus.io', 'wss://nos.lol'];
+ // Return configured relays from nostr-lite options
+ return this.nostrLite.options?.relays || ['wss://relay.damus.io'];
}
get nip04() {
@@ -2041,7 +2077,7 @@ class WindowNostr {
}
default:
- throw new Error(\`Unsupported auth method: \${this.authState.method}\`);
+ throw new Error('Unsupported auth method: ' + this.authState.method);
}
},
@@ -2083,7 +2119,7 @@ class WindowNostr {
}
default:
- throw new Error(\`Unsupported auth method: \${this.authState.method}\`);
+ throw new Error('Unsupported auth method: ' + this.authState.method);
}
}
};
@@ -2092,18 +2128,17 @@ class WindowNostr {
get nip44() {
return {
encrypt: async (pubkey, plaintext) => {
- const authState = getAuthState();
- if (!authState) {
+ if (!this.authState) {
throw new Error('Not authenticated - use NOSTR_LOGIN_LITE.launch()');
}
- if (authState.method === 'readonly') {
+ if (this.authState.method === 'readonly') {
throw new Error('Read-only mode - cannot encrypt');
}
- switch (authState.method) {
+ switch (this.authState.method) {
case 'extension': {
- const ext = this.authenticatedExtension || authState.extension || this.existingNostr;
+ const ext = this.authenticatedExtension || this.authState.extension || this.existingNostr;
if (!ext) throw new Error('Extension not available');
return await ext.nip44.encrypt(pubkey, plaintext);
}
@@ -2112,41 +2147,40 @@ class WindowNostr {
const { nip44, nip19 } = window.NostrTools;
let secretKey;
- if (authState.secret.startsWith('nsec')) {
- const decoded = nip19.decode(authState.secret);
+ if (this.authState.secret.startsWith('nsec')) {
+ const decoded = nip19.decode(this.authState.secret);
secretKey = decoded.data;
} else {
- secretKey = this._hexToUint8Array(authState.secret);
+ secretKey = this._hexToUint8Array(this.authState.secret);
}
return nip44.encrypt(plaintext, nip44.getConversationKey(secretKey, pubkey));
}
case 'nip46': {
- if (!authState.signer?.bunkerSigner) {
+ if (!this.authState.signer?.bunkerSigner) {
throw new Error('NIP-46 signer not available');
}
- return await authState.signer.bunkerSigner.nip44Encrypt(pubkey, plaintext);
+ return await this.authState.signer.bunkerSigner.nip44Encrypt(pubkey, plaintext);
}
default:
- throw new Error('Unsupported auth method: ' + authState.method);
+ throw new Error('Unsupported auth method: ' + this.authState.method);
}
},
decrypt: async (pubkey, ciphertext) => {
- const authState = getAuthState();
- if (!authState) {
+ if (!this.authState) {
throw new Error('Not authenticated - use NOSTR_LOGIN_LITE.launch()');
}
- if (authState.method === 'readonly') {
+ if (this.authState.method === 'readonly') {
throw new Error('Read-only mode - cannot decrypt');
}
- switch (authState.method) {
+ switch (this.authState.method) {
case 'extension': {
- const ext = this.authenticatedExtension || authState.extension || this.existingNostr;
+ const ext = this.authenticatedExtension || this.authState.extension || this.existingNostr;
if (!ext) throw new Error('Extension not available');
return await ext.nip44.decrypt(pubkey, ciphertext);
}
@@ -2155,25 +2189,25 @@ class WindowNostr {
const { nip44, nip19 } = window.NostrTools;
let secretKey;
- if (authState.secret.startsWith('nsec')) {
- const decoded = nip19.decode(authState.secret);
+ if (this.authState.secret.startsWith('nsec')) {
+ const decoded = nip19.decode(this.authState.secret);
secretKey = decoded.data;
} else {
- secretKey = this._hexToUint8Array(authState.secret);
+ secretKey = this._hexToUint8Array(this.authState.secret);
}
return nip44.decrypt(ciphertext, nip44.getConversationKey(secretKey, pubkey));
}
case 'nip46': {
- if (!authState.signer?.bunkerSigner) {
+ if (!this.authState.signer?.bunkerSigner) {
throw new Error('NIP-46 signer not available');
}
- return await authState.signer.bunkerSigner.nip44Decrypt(pubkey, ciphertext);
+ return await this.authState.signer.bunkerSigner.nip44Decrypt(pubkey, ciphertext);
}
default:
- throw new Error('Unsupported auth method: ' + authState.method);
+ throw new Error('Unsupported auth method: ' + this.authState.method);
}
}
};
@@ -2191,171 +2225,6 @@ class WindowNostr {
}
}
-// ======================================
-// Global Authentication State Manager - Single Source of Truth
-// ======================================
-
-// Storage-based authentication state - works regardless of extension presence
-function getAuthState() {
- try {
- console.log('š getAuthState: === GLOBAL AUTH STATE CHECK ===');
-
- const storageKey = 'nostr_login_lite_auth';
- let stored = null;
- let storageType = null;
-
- // Check sessionStorage first (per-window isolation), then localStorage
- if (sessionStorage.getItem(storageKey)) {
- stored = sessionStorage.getItem(storageKey);
- storageType = 'sessionStorage';
- console.log('š getAuthState: Found auth in sessionStorage');
- } else if (localStorage.getItem(storageKey)) {
- stored = localStorage.getItem(storageKey);
- storageType = 'localStorage';
- console.log('š getAuthState: Found auth in localStorage');
- }
-
- if (!stored) {
- console.log('š getAuthState: ā No stored auth state found');
- return null;
- }
-
- const authState = JSON.parse(stored);
- console.log('š getAuthState: ā
Parsed stored auth state from', storageType);
- console.log('š getAuthState: Method:', authState.method);
- console.log('š getAuthState: Pubkey:', authState.pubkey);
- console.log('š getAuthState: Age (ms):', Date.now() - authState.timestamp);
-
- // Check if auth state is expired
- const maxAge = authState.method === 'extension' ? 60 * 60 * 1000 : 24 * 60 * 60 * 1000;
- if (Date.now() - authState.timestamp > maxAge) {
- console.log('š getAuthState: ā Auth state expired, clearing');
- sessionStorage.removeItem(storageKey);
- localStorage.removeItem(storageKey);
- return null;
- }
-
- console.log('š getAuthState: ā
Valid auth state found');
- return authState;
-
- } catch (error) {
- console.error('š getAuthState: ā Error reading auth state:', error);
- return null;
- }
-}
-
-// ======================================
-// 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') {
const nostrLite = new NostrLite();
@@ -2381,9 +2250,9 @@ if (typeof window !== 'undefined') {
updateFloatingTab: (options) => nostrLite.updateFloatingTab(options),
getFloatingTabState: () => nostrLite.getFloatingTabState(),
- // GLOBAL AUTHENTICATION STATE API - Single Source of Truth
- getAuthState: getAuthState,
+ // Global authentication state management (single source of truth)
setAuthState: setAuthState,
+ getAuthState: getAuthState,
clearAuthState: clearAuthState,
// Expose for debugging
@@ -2394,19 +2263,21 @@ if (typeof window !== 'undefined') {
console.log('NOSTR_LOGIN_LITE: Library loaded and ready');
console.log('NOSTR_LOGIN_LITE: Use window.NOSTR_LOGIN_LITE.init(options) to initialize');
console.log('NOSTR_LOGIN_LITE: Detected', nostrLite.extensionBridge.getExtensionCount(), 'browser extensions');
+ console.warn('š SECURITY: Unified plaintext storage enabled for maximum developer usability');
} else {
// Node.js environment
module.exports = { NostrLite };
}
+
`;
// Write the complete bundle
fs.writeFileSync(outputPath, bundle, 'utf8');
const sizeKB = (bundle.length / 1024).toFixed(2);
- console.log(`\nā
nostr-lite.js bundle created: ${outputPath}`);
- console.log(`š Bundle size: ${sizeKB} KB`);
- console.log(`š Total lines: ${bundle.split('\n').length}`);
+ console.log('\nā
nostr-lite.js bundle created: ' + outputPath);
+ console.log('š Bundle size: ' + sizeKB + ' KB');
+ console.log('š Total lines: ' + bundle.split('\n').length);
// Check what's included
const hasModal = bundle.includes('class Modal');
@@ -2414,15 +2285,15 @@ if (typeof window !== 'undefined') {
const hasThemeCss = bundle.includes('THEME_CSS');
console.log('\nš Bundle contents:');
- console.log(` Modal UI: ${hasModal ? 'ā
Included' : 'ā Missing'}`);
- console.log(` NOSTR_LOGIN_LITE: ${hasNostrLite ? 'ā
Included' : 'ā Missing'}`);
- console.log(` CSS-Only Themes: ${hasThemeCss ? 'ā
Included' : 'ā Missing'}`);
- console.log(` Extension Bridge: ā
Included`);
- console.log(` Window.nostr facade: ā
Included`);
+ console.log(' Modal UI: ' + (hasModal ? 'ā
Included' : 'ā Missing'));
+ console.log(' NOSTR_LOGIN_LITE: ' + (hasNostrLite ? 'ā
Included' : 'ā Missing'));
+ console.log(' CSS-Only Themes: ' + (hasThemeCss ? 'ā
Included' : 'ā Missing'));
+ console.log(' Extension Bridge: ā
Included');
+ console.log(' Window.nostr facade: ā
Included');
console.log('\nš Two-file architecture:');
console.log(' 1. nostr.bundle.js (official nostr-tools - 220KB)');
- console.log(` 2. nostr-lite.js (NOSTR_LOGIN_LITE with CSS-only themes - ${sizeKB}KB)`);
+ console.log(' 2. nostr-lite.js (NOSTR_LOGIN_LITE with CSS-only themes - ' + sizeKB + 'KB)');
return bundle;
}
diff --git a/lite/nostr-lite.js b/lite/nostr-lite.js
index 9dc515b..d86ac61 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-21T15:51:33.328Z
+ * Generated on: 2025-09-22T19:37:53.923Z
*/
// Verify dependencies are loaded
@@ -282,7 +282,7 @@ function injectThemeCSS(themeName = 'default') {
style.id = 'nl-theme-css';
style.textContent = themeCss;
document.head.appendChild(style);
- console.log(`NOSTR_LOGIN_LITE: ${themeName} theme CSS injected`);
+ console.log('NOSTR_LOGIN_LITE: ' + themeName + ' theme CSS injected');
}
}
@@ -381,7 +381,7 @@ class Modal {
`;
const modalTitle = document.createElement('h2');
- modalTitle.textContent = 'Nostr Login v0.1.4';
+ modalTitle.textContent = 'Nostr Login';
modalTitle.style.cssText = `
margin: 0;
font-size: 24px;
@@ -434,6 +434,21 @@ class Modal {
`;
modalContent.appendChild(modalHeader);
+ // Add version element in bottom-right corner aligned with modal body
+ const versionElement = document.createElement('div');
+ versionElement.textContent = 'v0.1.5';
+ versionElement.style.cssText = `
+ position: absolute;
+ bottom: 8px;
+ right: 24px;
+ font-size: 14px;
+ color: #666666;
+ font-family: var(--nl-font-family, 'Courier New', monospace);
+ pointer-events: none;
+ z-index: 1;
+ `;
+ modalContent.appendChild(versionElement);
+
modalContent.appendChild(this.modalBody);
this.container.appendChild(modalContent);
@@ -1428,7 +1443,6 @@ class Modal {
}
_setAuthMethod(method, options = {}) {
- // SINGLE-EXTENSION ARCHITECTURE: Handle method switching
console.log('Modal: _setAuthMethod called with:', method, options);
// CRITICAL: Never install facade for extension methods - leave window.nostr as the extension
@@ -1451,46 +1465,57 @@ class Modal {
return;
}
- // For non-extension methods, we need to ensure WindowNostr facade is available
- console.log('Modal: Non-extension method detected:', method);
+ // FOR NON-EXTENSION METHODS: Force-install facade with resilience
+ console.log('Modal: Non-extension method - FORCE-INSTALLING facade with resilience:', method);
- // Check if we have a preserved extension but no WindowNostr facade installed
- const hasPreservedExtension = !!window.NOSTR_LOGIN_LITE?._instance?.preservedExtension;
- const hasWindowNostrFacade = window.nostr?.constructor?.name === 'WindowNostr';
+ // Store the current extension if any (for potential restoration later)
+ const currentExtension = (window.nostr?.constructor?.name !== 'WindowNostr') ? window.nostr : null;
- console.log('Modal: Method switching check:');
- console.log(' method:', method);
- console.log(' hasPreservedExtension:', hasPreservedExtension);
- console.log(' hasWindowNostrFacade:', hasWindowNostrFacade);
- console.log(' current window.nostr constructor:', window.nostr?.constructor?.name);
+ // Get NostrLite instance for facade operations
+ const nostrLiteInstance = window.NOSTR_LOGIN_LITE?._instance;
+ if (!nostrLiteInstance || typeof nostrLiteInstance._installFacade !== 'function') {
+ console.error('Modal: Cannot access NostrLite instance or _installFacade method');
+ // Fallback: emit event anyway
+ const event = new CustomEvent('nlMethodSelected', {
+ detail: { method, ...options }
+ });
+ window.dispatchEvent(event);
+ this.close();
+ return;
+ }
- // If we have a preserved extension but no facade, install facade for method switching
- if (hasPreservedExtension && !hasWindowNostrFacade) {
- console.log('Modal: Installing WindowNostr facade for method switching (non-extension authentication)');
+ // IMMEDIATE FACADE INSTALLATION
+ console.log('Modal: Installing WindowNostr facade immediately for method:', method);
+ const preservedExtension = nostrLiteInstance.preservedExtension || currentExtension;
+ nostrLiteInstance._installFacade(preservedExtension, true);
+ console.log('Modal: WindowNostr facade force-installed, current window.nostr:', window.nostr?.constructor?.name);
+
+ // DELAYED FACADE RESILIENCE - Reinstall after extension override attempts
+ const forceReinstallFacade = () => {
+ console.log('Modal: RESILIENCE CHECK - Current window.nostr after delay:', window.nostr?.constructor?.name);
- // Get the NostrLite instance and install facade with preserved extension
- const nostrLiteInstance = window.NOSTR_LOGIN_LITE?._instance;
- if (nostrLiteInstance && typeof nostrLiteInstance._installFacade === 'function') {
- const preservedExtension = nostrLiteInstance.preservedExtension;
- console.log('Modal: Installing facade with preserved extension:', preservedExtension?.constructor?.name);
+ // If facade was overridden by extension, reinstall it
+ if (window.nostr?.constructor?.name !== 'WindowNostr') {
+ console.log('Modal: FACADE OVERRIDDEN! Force-reinstalling WindowNostr facade for user choice:', method);
+ nostrLiteInstance._installFacade(preservedExtension, true);
+ console.log('Modal: Resilient facade force-reinstall complete, window.nostr:', window.nostr?.constructor?.name);
- nostrLiteInstance._installFacade(preservedExtension);
- console.log('Modal: WindowNostr facade installed for method switching');
+ // Schedule another check in case of persistent extension override
+ setTimeout(() => {
+ if (window.nostr?.constructor?.name !== 'WindowNostr') {
+ console.log('Modal: PERSISTENT OVERRIDE! Final facade force-reinstall for method:', method);
+ nostrLiteInstance._installFacade(preservedExtension, true);
+ }
+ }, 1000);
} else {
- console.error('Modal: Cannot access NostrLite instance or _installFacade method');
+ console.log('Modal: Facade persistence verified - no override detected');
}
- }
+ };
- // If no extension at all, ensure facade is installed for local/NIP-46/readonly methods
- else if (!hasPreservedExtension && !hasWindowNostrFacade) {
- console.log('Modal: Installing WindowNostr facade for non-extension methods (no extension detected)');
-
- const nostrLiteInstance = window.NOSTR_LOGIN_LITE?._instance;
- if (nostrLiteInstance && typeof nostrLiteInstance._installFacade === 'function') {
- nostrLiteInstance._installFacade();
- console.log('Modal: WindowNostr facade installed for non-extension methods');
- }
- }
+ // Schedule resilience checks at multiple intervals
+ setTimeout(forceReinstallFacade, 100); // Quick check
+ setTimeout(forceReinstallFacade, 500); // Main check
+ setTimeout(forceReinstallFacade, 1500); // Final check
// Emit auth method selection
const event = new CustomEvent('nlMethodSelected', {
@@ -2563,9 +2588,9 @@ class FloatingTab {
// 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;
+ userDisplay = userName.length > 16 ? userName.slice(0, 16) + '...' : userName;
} else {
- userDisplay = `${authState.pubkey.slice(0, 8)}...${authState.pubkey.slice(-4)}`;
+ userDisplay = authState.pubkey.slice(0, 8) + '...' + authState.pubkey.slice(-4);
}
} else {
userDisplay = 'Authenticated';
@@ -2616,7 +2641,7 @@ class FloatingTab {
// Fallback to pubkey display
display = this.options.appearance.iconOnly
? authState.pubkey.slice(0, 6)
- : `${authState.pubkey.slice(0, 6)}...`;
+ : authState.pubkey.slice(0, 6) + '...';
} else {
display = this.options.appearance.iconOnly ? 'User' : 'Authenticated';
}
@@ -2626,7 +2651,7 @@ class FloatingTab {
} else {
const display = this.options.appearance.iconOnly ?
this.options.appearance.icon :
- (this.options.appearance.icon ? `${this.options.appearance.icon} ${this.options.appearance.text}` : this.options.appearance.text);
+ (this.options.appearance.icon ? this.options.appearance.icon + ' ' + this.options.appearance.text : this.options.appearance.text);
this.container.textContent = display;
this.container.className = 'nl-floating-tab nl-floating-tab--logged-out';
@@ -2740,10 +2765,10 @@ class FloatingTab {
const x = this.options.hPosition * (window.innerWidth - this.container.offsetWidth - padding * 2) + padding + this.options.offset.x;
const y = this.options.vPosition * (window.innerHeight - this.container.offsetHeight - padding * 2) + padding + this.options.offset.y;
- this.container.style.left = `${x}px`;
- this.container.style.top = `${y}px`;
+ this.container.style.left = x + 'px';
+ this.container.style.top = y + 'px';
- console.log(`FloatingTab: Positioned at (${x}, ${y})`);
+ console.log('FloatingTab: Positioned at (' + x + ', ' + y + ')');
}
_slideIn() {
@@ -2972,16 +2997,37 @@ class NostrLite {
this._installFacade(window.nostr); // Install facade with any existing nostr object
console.log('š NOSTR_LOGIN_LITE: ā
Facade installed for local/NIP-46/readonly methods');
+
+ // CRITICAL FIX: Immediately attempt to restore auth state after facade installation
+ if (this.facadeInstalled && window.nostr?.restoreAuthState) {
+ console.log('š NOSTR_LOGIN_LITE: š IMMEDIATELY attempting auth restoration after facade installation');
+ try {
+ const restoredAuth = await window.nostr.restoreAuthState();
+ if (restoredAuth) {
+ console.log('š NOSTR_LOGIN_LITE: ā
Auth state restored immediately during facade setup!');
+ console.log('š NOSTR_LOGIN_LITE: Method:', restoredAuth.method);
+ console.log('š NOSTR_LOGIN_LITE: Pubkey:', restoredAuth.pubkey);
+
+ // Update facade's authState immediately
+ window.nostr.authState = restoredAuth;
+ } else {
+ console.log('š NOSTR_LOGIN_LITE: ā No auth state to restore during facade setup');
+ }
+ } catch (error) {
+ console.error('š NOSTR_LOGIN_LITE: ā Error restoring auth during facade setup:', error);
+ }
+ }
}
}
- _installFacade(existingNostr = null) {
- if (typeof window !== 'undefined' && !this.facadeInstalled) {
+ _installFacade(existingNostr = null, forceInstall = false) {
+ if (typeof window !== 'undefined' && (!this.facadeInstalled || forceInstall)) {
console.log('š NOSTR_LOGIN_LITE: === _installFacade CALLED ===');
console.log('š NOSTR_LOGIN_LITE: existingNostr parameter:', existingNostr);
console.log('š NOSTR_LOGIN_LITE: existingNostr constructor:', existingNostr?.constructor?.name);
console.log('š NOSTR_LOGIN_LITE: window.nostr before installation:', window.nostr);
console.log('š NOSTR_LOGIN_LITE: window.nostr constructor before:', window.nostr?.constructor?.name);
+ console.log('š NOSTR_LOGIN_LITE: forceInstall flag:', forceInstall);
const facade = new WindowNostr(this, existingNostr, { isolateSession: this.options.isolateSession });
window.nostr = facade;
@@ -2991,6 +3037,8 @@ class NostrLite {
console.log('š NOSTR_LOGIN_LITE: window.nostr after installation:', window.nostr);
console.log('š NOSTR_LOGIN_LITE: window.nostr constructor after:', window.nostr.constructor?.name);
console.log('š NOSTR_LOGIN_LITE: facade.existingNostr:', window.nostr.existingNostr);
+ } else if (typeof window !== 'undefined') {
+ console.log('š NOSTR_LOGIN_LITE: _installFacade skipped - facadeInstalled:', this.facadeInstalled, 'forceInstall:', forceInstall);
}
}
@@ -3089,6 +3137,13 @@ class NostrLite {
console.log('š NOSTR_LOGIN_LITE: Method:', restoredAuth.method);
console.log('š NOSTR_LOGIN_LITE: Pubkey:', restoredAuth.pubkey);
+ // CRITICAL FIX: Activate facade resilience system for non-extension methods
+ // Extensions like nos2x can override our facade after page refresh
+ if (restoredAuth.method === 'local' || restoredAuth.method === 'nip46') {
+ console.log('š NOSTR_LOGIN_LITE: š”ļø Activating facade resilience system for page refresh');
+ this._activateResilienceProtection(restoredAuth.method);
+ }
+
// Handle NIP-46 reconnection requirement
if (restoredAuth.requiresReconnection) {
console.log('š NOSTR_LOGIN_LITE: NIP-46 connection requires user reconnection');
@@ -3115,6 +3170,45 @@ class NostrLite {
}
}
+ // Activate facade resilience protection against extension overrides
+ _activateResilienceProtection(method) {
+ console.log('š”ļø NOSTR_LOGIN_LITE: === ACTIVATING RESILIENCE PROTECTION ===');
+ console.log('š”ļø NOSTR_LOGIN_LITE: Protecting facade for method:', method);
+
+ // Store the current extension if any (for potential restoration later)
+ const preservedExtension = this.preservedExtension ||
+ ((window.nostr?.constructor?.name !== 'WindowNostr') ? window.nostr : null);
+
+ // DELAYED FACADE RESILIENCE - Reinstall after extension override attempts
+ const forceReinstallFacade = () => {
+ console.log('š”ļø NOSTR_LOGIN_LITE: RESILIENCE CHECK - Current window.nostr after delay:', window.nostr?.constructor?.name);
+
+ // If facade was overridden by extension, reinstall it
+ if (window.nostr?.constructor?.name !== 'WindowNostr') {
+ console.log('š”ļø NOSTR_LOGIN_LITE: FACADE OVERRIDDEN! Force-reinstalling WindowNostr facade for user choice:', method);
+ this._installFacade(preservedExtension, true);
+ console.log('š”ļø NOSTR_LOGIN_LITE: Resilient facade force-reinstall complete, window.nostr:', window.nostr?.constructor?.name);
+
+ // Schedule another check in case of persistent extension override
+ setTimeout(() => {
+ if (window.nostr?.constructor?.name !== 'WindowNostr') {
+ console.log('š”ļø NOSTR_LOGIN_LITE: PERSISTENT OVERRIDE! Final facade force-reinstall for method:', method);
+ this._installFacade(preservedExtension, true);
+ }
+ }, 1000);
+ } else {
+ console.log('š”ļø NOSTR_LOGIN_LITE: Facade persistence verified - no override detected');
+ }
+ };
+
+ // Schedule resilience checks at multiple intervals (same as Modal)
+ setTimeout(forceReinstallFacade, 100); // Quick check
+ setTimeout(forceReinstallFacade, 500); // Main check
+ setTimeout(forceReinstallFacade, 1500); // Final check
+
+ console.log('š”ļø NOSTR_LOGIN_LITE: Resilience protection scheduled for method:', method);
+ }
+
// Extension-specific authentication restoration
async _attemptExtensionRestore() {
try {
@@ -3215,7 +3309,7 @@ class NostrLite {
// CSS-only theme switching
switchTheme(themeName) {
- console.log(`NOSTR_LOGIN_LITE: Switching to ${themeName} theme`);
+ console.log('NOSTR_LOGIN_LITE: Switching to ' + themeName + ' theme');
if (THEME_CSS[themeName]) {
injectThemeCSS(themeName);
@@ -3230,7 +3324,7 @@ class NostrLite {
return { theme: themeName };
} else {
- console.warn(`Theme '${themeName}' not found, using default`);
+ console.warn("Theme '" + themeName + "' not found, using default");
injectThemeCSS('default');
this.currentTheme = 'default';
return { theme: 'default' };
@@ -3298,115 +3392,10 @@ class NostrLite {
}
// ======================================
-// Authentication Manager for Persistent Login
+// Simplified Authentication Manager (Unified Plaintext Storage)
// ======================================
-// Encryption utilities for secure local storage
-class CryptoUtils {
- static async generateKey() {
- if (!window.crypto?.subtle) {
- throw new Error('Web Crypto API not available');
- }
-
- return await window.crypto.subtle.generateKey(
- {
- name: 'AES-GCM',
- length: 256,
- },
- true,
- ['encrypt', 'decrypt']
- );
- }
-
- static async deriveKey(password, salt) {
- if (!window.crypto?.subtle) {
- throw new Error('Web Crypto API not available');
- }
-
- const encoder = new TextEncoder();
- const keyMaterial = await window.crypto.subtle.importKey(
- 'raw',
- encoder.encode(password),
- { name: 'PBKDF2' },
- false,
- ['deriveBits', 'deriveKey']
- );
-
- return await window.crypto.subtle.deriveKey(
- {
- name: 'PBKDF2',
- salt: salt,
- iterations: 100000,
- hash: 'SHA-256',
- },
- keyMaterial,
- { name: 'AES-GCM', length: 256 },
- true,
- ['encrypt', 'decrypt']
- );
- }
-
- static async encrypt(data, key) {
- if (!window.crypto?.subtle) {
- throw new Error('Web Crypto API not available');
- }
-
- const encoder = new TextEncoder();
- const iv = window.crypto.getRandomValues(new Uint8Array(12));
-
- const encrypted = await window.crypto.subtle.encrypt(
- {
- name: 'AES-GCM',
- iv: iv,
- },
- key,
- encoder.encode(data)
- );
-
- return {
- encrypted: new Uint8Array(encrypted),
- iv: iv
- };
- }
-
- static async decrypt(encryptedData, key, iv) {
- if (!window.crypto?.subtle) {
- throw new Error('Web Crypto API not available');
- }
-
- const decrypted = await window.crypto.subtle.decrypt(
- {
- name: 'AES-GCM',
- iv: iv,
- },
- key,
- encryptedData
- );
-
- const decoder = new TextDecoder();
- return decoder.decode(decrypted);
- }
-
- static arrayBufferToBase64(buffer) {
- const bytes = new Uint8Array(buffer);
- let binary = '';
- for (let i = 0; i < bytes.byteLength; i++) {
- binary += String.fromCharCode(bytes[i]);
- }
- return window.btoa(binary);
- }
-
- static base64ToArrayBuffer(base64) {
- const binary = window.atob(base64);
- const bytes = new Uint8Array(binary.length);
- for (let i = 0; i < binary.length; i++) {
- bytes[i] = binary.charCodeAt(i);
- }
- return bytes.buffer;
- }
-}
-
-// Unified authentication state manager
+// Simple authentication state manager - plaintext storage for maximum usability
class AuthManager {
constructor(options = {}) {
this.storageKey = 'nostr_login_lite_auth';
@@ -3415,16 +3404,22 @@ class AuthManager {
// Configure storage type based on isolateSession option
if (options.isolateSession) {
this.storage = sessionStorage;
- console.log('AuthManager: Using sessionStorage for per-window isolation');
+ console.log('š AuthManager: Using sessionStorage for per-window isolation');
} else {
this.storage = localStorage;
- console.log('AuthManager: Using localStorage for cross-window persistence');
+ console.log('š AuthManager: Using localStorage for cross-window persistence');
}
+
+ console.warn('š SECURITY: Private keys stored unencrypted in browser storage');
+ console.warn('š For production apps, implement your own secure storage');
}
- // Save authentication state with method-specific security
+ // Save authentication state using unified plaintext approach
async saveAuthState(authData) {
try {
+ console.log('š AuthManager: Saving auth state with plaintext storage');
+ console.warn('š SECURITY: Private key will be stored unencrypted for maximum usability');
+
const authState = {
method: authData.method,
timestamp: Date.now(),
@@ -3439,24 +3434,15 @@ class AuthManager {
hasGetPublicKey: typeof authData.extension?.getPublicKey === 'function',
hasSignEvent: typeof authData.extension?.signEvent === 'function'
};
+ console.log('š AuthManager: Extension method - storing verification data only');
break;
case 'local':
- // For local keys, encrypt the secret key
+ // UNIFIED PLAINTEXT: Store secret key directly for maximum compatibility
if (authData.secret) {
- const password = this._generateSessionPassword();
- const salt = window.crypto.getRandomValues(new Uint8Array(16));
- const key = await CryptoUtils.deriveKey(password, salt);
- const encrypted = await CryptoUtils.encrypt(authData.secret, key);
-
- authState.encrypted = {
- data: CryptoUtils.arrayBufferToBase64(encrypted.encrypted),
- iv: CryptoUtils.arrayBufferToBase64(encrypted.iv),
- salt: CryptoUtils.arrayBufferToBase64(salt)
- };
-
- // Store session password in sessionStorage (cleared on tab close)
- sessionStorage.setItem('nostr_session_key', password);
+ authState.secret = authData.secret;
+ console.log('š AuthManager: Local method - storing secret key in plaintext');
+ console.warn('š SECURITY: Secret key stored unencrypted for developer convenience');
}
break;
@@ -3468,23 +3454,25 @@ class AuthManager {
relays: authData.signer.relays,
// Don't store secret - user will need to reconnect
};
+ console.log('š AuthManager: NIP-46 method - storing connection parameters');
}
break;
case 'readonly':
// Read-only mode has no secrets to store
+ console.log('š AuthManager: Read-only method - storing basic auth state');
break;
default:
- throw new Error(`Unknown auth method: ${authData.method}`);
+ throw new Error('Unknown auth method: ' + authData.method);
}
this.storage.setItem(this.storageKey, JSON.stringify(authState));
this.currentAuthState = authState;
- console.log('AuthManager: Auth state saved for method:', authData.method);
+ console.log('š AuthManager: Auth state saved successfully for method:', authData.method);
} catch (error) {
- console.error('AuthManager: Failed to save auth state:', error);
+ console.error('š AuthManager: Failed to save auth state:', error);
throw error;
}
}
@@ -3496,7 +3484,7 @@ class AuthManager {
console.log('š AuthManager: storageKey:', this.storageKey);
const stored = this.storage.getItem(this.storageKey);
- console.log('š AuthManager: localStorage raw value:', stored);
+ console.log('š AuthManager: Storage raw value:', stored);
if (!stored) {
console.log('š AuthManager: ā No stored auth state found');
@@ -3719,51 +3707,57 @@ class AuthManager {
}
async _restoreLocalAuth(authState) {
- if (!authState.encrypted) {
- console.log('AuthManager: No encrypted data found for local auth');
- return null;
- }
-
- // Get session password
- const sessionPassword = sessionStorage.getItem('nostr_session_key');
- if (!sessionPassword) {
- console.log('AuthManager: Session password not found, cannot decrypt');
- return null;
- }
-
- try {
- // Decrypt the secret key
- const salt = CryptoUtils.base64ToArrayBuffer(authState.encrypted.salt);
- const key = await CryptoUtils.deriveKey(sessionPassword, new Uint8Array(salt));
+ console.log('š AuthManager: === _restoreLocalAuth (Unified Plaintext) ===');
+
+ // Check for legacy encrypted format first
+ if (authState.encrypted) {
+ console.log('š AuthManager: Detected LEGACY encrypted format - migrating to plaintext');
+ console.warn('š SECURITY: Converting from encrypted to plaintext storage for compatibility');
- const encryptedData = CryptoUtils.base64ToArrayBuffer(authState.encrypted.data);
- const iv = CryptoUtils.base64ToArrayBuffer(authState.encrypted.iv);
-
- const secret = await CryptoUtils.decrypt(encryptedData, key, new Uint8Array(iv));
+ // Try to decrypt legacy format
+ const sessionPassword = sessionStorage.getItem('nostr_session_key');
+ if (!sessionPassword) {
+ console.log('š AuthManager: Legacy session password not found - user must re-login');
+ return null;
+ }
- console.log('AuthManager: Local auth restored successfully');
- return {
- method: 'local',
- pubkey: authState.pubkey,
- secret: secret
- };
-
- } catch (error) {
- console.error('AuthManager: Failed to decrypt local key:', error);
+ try {
+ console.warn('š AuthManager: Legacy encryption system no longer supported - user must re-login');
+ this.clearAuthState(); // Clear legacy format
+ return null;
+ } catch (error) {
+ console.error('š AuthManager: Legacy decryption failed:', error);
+ this.clearAuthState(); // Clear corrupted legacy format
+ return null;
+ }
+ }
+
+ // NEW UNIFIED PLAINTEXT FORMAT
+ if (!authState.secret) {
+ console.log('š AuthManager: No secret found in plaintext format');
return null;
}
+
+ console.log('š AuthManager: ā
Local auth restored from plaintext storage');
+ console.warn('š SECURITY: Secret key was stored unencrypted');
+
+ return {
+ method: 'local',
+ pubkey: authState.pubkey,
+ secret: authState.secret
+ };
}
async _restoreNip46Auth(authState) {
if (!authState.nip46) {
- console.log('AuthManager: No NIP-46 data found');
+ console.log('š AuthManager: No NIP-46 data found');
return null;
}
// For NIP-46, we can't automatically restore the connection
// because it requires the user to re-authenticate with the remote signer
// Instead, we return the connection parameters so the UI can prompt for reconnection
- console.log('AuthManager: NIP-46 connection data found, requires user reconnection');
+ console.log('š AuthManager: NIP-46 connection data found, requires user reconnection');
return {
method: 'nip46',
pubkey: authState.pubkey,
@@ -3773,7 +3767,7 @@ class AuthManager {
}
async _restoreReadonlyAuth(authState) {
- console.log('AuthManager: Read-only auth restored successfully');
+ console.log('š AuthManager: Read-only auth restored successfully');
return {
method: 'readonly',
pubkey: authState.pubkey
@@ -3783,16 +3777,9 @@ class AuthManager {
// Clear stored authentication state
clearAuthState() {
this.storage.removeItem(this.storageKey);
- sessionStorage.removeItem('nostr_session_key');
+ sessionStorage.removeItem('nostr_session_key'); // Clear legacy session key
this.currentAuthState = null;
- console.log('AuthManager: Auth state cleared');
- }
-
- // Generate a session-specific password for local key encryption
- _generateSessionPassword() {
- const array = new Uint8Array(32);
- window.crypto.getRandomValues(array);
- return Array.from(array, byte => byte.toString(16).padStart(2, '0')).join('');
+ console.log('š AuthManager: Auth state cleared from unified storage');
}
// Check if we have valid stored auth
@@ -3815,119 +3802,178 @@ class AuthManager {
}
}
+// ======================================
+// Global Authentication Functions (Single Source of Truth)
+// ======================================
+
+// Global authentication state (single source of truth)
+let globalAuthState = null;
+let globalAuthManager = null;
+
+// Initialize global auth manager (lazy initialization)
+function getGlobalAuthManager() {
+ if (!globalAuthManager) {
+ // Default to localStorage for persistence across browser sessions
+ globalAuthManager = new AuthManager({ isolateSession: false });
+ }
+ return globalAuthManager;
+}
+
+// **UNIFIED GLOBAL FUNCTION**: Set authentication state (works for all methods)
+function setAuthState(authData, options = {}) {
+ try {
+ console.log('š setAuthState: Setting global auth state for method:', authData.method);
+ console.warn('š SECURITY: Using unified plaintext storage for maximum compatibility');
+
+ // Store in memory
+ globalAuthState = authData;
+
+ // Store in browser storage using AuthManager
+ const authManager = new AuthManager(options);
+ authManager.saveAuthState(authData);
+
+ console.log('š setAuthState: Auth state saved successfully');
+ } catch (error) {
+ console.error('š setAuthState: Failed to save auth state:', error);
+ throw error;
+ }
+}
+
+// **UNIFIED GLOBAL FUNCTION**: Get authentication state (single source of truth)
+function getAuthState() {
+ try {
+ // Always query from storage as the authoritative source
+ const authManager = getGlobalAuthManager();
+ const storageKey = 'nostr_login_lite_auth';
+
+ // Check both session and local storage for compatibility
+ let stored = null;
+ if (sessionStorage.getItem(storageKey)) {
+ stored = sessionStorage.getItem(storageKey);
+ } else if (localStorage.getItem(storageKey)) {
+ stored = localStorage.getItem(storageKey);
+ }
+
+ if (!stored) {
+ console.log('š getAuthState: No auth state found in storage');
+ globalAuthState = null;
+ return null;
+ }
+
+ const authState = JSON.parse(stored);
+ console.log('š getAuthState: Retrieved auth state:', authState.method);
+
+ // Update in-memory cache
+ globalAuthState = authState;
+ return authState;
+
+ } catch (error) {
+ console.error('š getAuthState: Failed to get auth state:', error);
+ globalAuthState = null;
+ return null;
+ }
+}
+
+// **UNIFIED GLOBAL FUNCTION**: Clear authentication state (works for all methods)
+function clearAuthState() {
+ try {
+ console.log('š clearAuthState: Clearing global auth state');
+
+ // Clear in-memory state
+ globalAuthState = null;
+
+ // Clear from both storage types for thorough cleanup
+ const storageKey = 'nostr_login_lite_auth';
+ localStorage.removeItem(storageKey);
+ sessionStorage.removeItem(storageKey);
+ sessionStorage.removeItem('nostr_session_key'); // Clear legacy session key
+
+ console.log('š clearAuthState: Auth state cleared from all storage locations');
+ } catch (error) {
+ console.error('š clearAuthState: Failed to clear auth state:', error);
+ }
+}
+
// NIP-07 compliant window.nostr provider
class WindowNostr {
- constructor(nostrLite, existingNostr = null) {
+ constructor(nostrLite, existingNostr = null, options = {}) {
this.nostrLite = nostrLite;
this.authState = null;
this.existingNostr = existingNostr;
this.authenticatedExtension = null;
- this.authManager = new AuthManager({ isolateSession: nostrLite.options?.isolateSession });
+ this.options = options;
this._setupEventListeners();
}
+ // Restore authentication state on page load
+ async restoreAuthState() {
+ console.log('š WindowNostr: === restoreAuthState ===');
+
+ try {
+ // Use simplified AuthManager for consistent restore logic
+ const authManager = new AuthManager(this.options);
+ const restoredAuth = await authManager.restoreAuthState();
+
+ if (restoredAuth) {
+ console.log('š WindowNostr: ā
Auth state restored:', restoredAuth.method);
+ this.authState = restoredAuth;
+
+ // Update global state
+ globalAuthState = restoredAuth;
+
+ // Dispatch restoration event
+ if (typeof window !== 'undefined') {
+ window.dispatchEvent(new CustomEvent('nlAuthRestored', {
+ detail: restoredAuth
+ }));
+ }
+
+ return restoredAuth;
+ } else {
+ console.log('š WindowNostr: ā No auth state to restore');
+ return null;
+ }
+
+ } catch (error) {
+ console.error('š WindowNostr: Auth restoration failed:', error);
+ return null;
+ }
+ }
+
_setupEventListeners() {
// Listen for authentication events to store auth state
if (typeof window !== 'undefined') {
window.addEventListener('nlMethodSelected', async (event) => {
+ console.log('š WindowNostr: nlMethodSelected event received:', event.detail);
this.authState = event.detail;
// If extension method, capture the specific extension the user chose
if (event.detail.method === 'extension') {
this.authenticatedExtension = event.detail.extension;
- console.log('WindowNostr: Captured authenticated extension:', this.authenticatedExtension?.constructor?.name);
+ console.log('š WindowNostr: Captured authenticated extension:', this.authenticatedExtension?.constructor?.name);
}
- // Use global setAuthState function for unified persistence
+ // Use unified global setAuthState function for all methods
try {
- setAuthState(event.detail, { isolateSession: this.nostrLite.options?.isolateSession });
- console.log('WindowNostr: Auth state saved via global setAuthState');
+ setAuthState(event.detail, this.options);
+ console.log('š WindowNostr: Auth state saved via unified setAuthState');
} catch (error) {
- console.error('WindowNostr: Failed to save auth state via global setAuthState:', error);
+ console.error('š WindowNostr: Failed to save auth state:', error);
}
-
- // EXTENSION-FIRST: Only reinstall facade for non-extension methods
- // Extensions handle their own window.nostr - don't interfere!
- if (event.detail.method !== 'extension' && typeof window !== 'undefined') {
- console.log('WindowNostr: Re-installing facade after', this.authState?.method, 'authentication');
- window.nostr = this;
- } else if (event.detail.method === 'extension') {
- console.log('WindowNostr: Extension authentication - NOT reinstalling facade');
- }
-
- console.log('WindowNostr: Auth state updated:', this.authState?.method);
});
window.addEventListener('nlLogout', () => {
+ console.log('š WindowNostr: nlLogout event received');
this.authState = null;
this.authenticatedExtension = null;
- // Clear persistent auth state
- this.authManager.clearAuthState();
- console.log('WindowNostr: Auth state cleared and persistence removed');
-
- // EXTENSION-FIRST: Only reinstall facade if we're not in extension mode
- if (typeof window !== 'undefined' && !this.nostrLite?.hasExtension) {
- console.log('WindowNostr: Re-installing facade after logout (non-extension mode)');
- window.nostr = this;
- } else {
- console.log('WindowNostr: Logout in extension mode - NOT reinstalling facade');
- }
+ // Clear from unified storage
+ clearAuthState();
+ console.log('š WindowNostr: Auth state cleared via unified clearAuthState');
});
}
}
- // Restore authentication state on page load
- async restoreAuthState() {
- try {
- console.log('š WindowNostr: === restoreAuthState START ===');
- console.log('š WindowNostr: authManager available:', !!this.authManager);
-
- const restoredAuth = await this.authManager.restoreAuthState();
- console.log('š WindowNostr: authManager.restoreAuthState result:', restoredAuth);
-
- if (restoredAuth) {
- console.log('š WindowNostr: ā
Setting authState to restored auth');
- this.authState = restoredAuth;
- console.log('š WindowNostr: this.authState now:', this.authState);
-
- // Handle method-specific restoration
- if (restoredAuth.method === 'extension') {
- console.log('š WindowNostr: Extension method - setting authenticatedExtension');
- this.authenticatedExtension = restoredAuth.extension;
- console.log('š WindowNostr: authenticatedExtension set to:', this.authenticatedExtension);
- }
-
- console.log('š WindowNostr: ā
Authentication state restored successfully!');
- console.log('š WindowNostr: Method:', restoredAuth.method);
- console.log('š WindowNostr: Pubkey:', restoredAuth.pubkey);
-
- // Dispatch restoration event so UI can update
- if (typeof window !== 'undefined') {
- console.log('š WindowNostr: Dispatching nlAuthRestored event...');
- const event = new CustomEvent('nlAuthRestored', {
- detail: restoredAuth
- });
- console.log('š WindowNostr: Event detail:', event.detail);
- window.dispatchEvent(event);
- console.log('š WindowNostr: ā
nlAuthRestored event dispatched');
- }
-
- console.log('š WindowNostr: === restoreAuthState END (success) ===');
- return restoredAuth;
- } else {
- console.log('š WindowNostr: ā No authentication state to restore (null from authManager)');
- console.log('š WindowNostr: === restoreAuthState END (no restore) ===');
- return null;
- }
- } catch (error) {
- console.error('š WindowNostr: ā Failed to restore auth state:', error);
- console.error('š WindowNostr: Error stack:', error.stack);
- console.log('š WindowNostr: === restoreAuthState END (error) ===');
- return null;
- }
- }
-
async getPublicKey() {
if (!this.authState) {
throw new Error('Not authenticated - use NOSTR_LOGIN_LITE.launch()');
@@ -3948,7 +3994,7 @@ class WindowNostr {
throw new Error('Read-only mode - cannot get public key');
default:
- throw new Error(`Unsupported auth method: ${this.authState.method}`);
+ throw new Error('Unsupported auth method: ' + this.authState.method);
}
}
@@ -3964,14 +4010,7 @@ class WindowNostr {
switch (this.authState.method) {
case 'extension':
// Use the captured authenticated extension, not current window.nostr
- console.log('WindowNostr: signEvent - authenticatedExtension:', this.authenticatedExtension);
- console.log('WindowNostr: signEvent - authState.extension:', this.authState.extension);
- console.log('WindowNostr: signEvent - existingNostr:', this.existingNostr);
-
const ext = this.authenticatedExtension || this.authState.extension || this.existingNostr;
- console.log('WindowNostr: signEvent - using extension:', ext);
- console.log('WindowNostr: signEvent - extension constructor:', ext?.constructor?.name);
-
if (!ext) throw new Error('Extension not available');
return await ext.signEvent(event);
@@ -4000,13 +4039,13 @@ class WindowNostr {
}
default:
- throw new Error(`Unsupported auth method: ${this.authState.method}`);
+ throw new Error('Unsupported auth method: ' + this.authState.method);
}
}
async getRelays() {
- // Return default relays since we removed the relays configuration
- return ['wss://relay.damus.io', 'wss://nos.lol'];
+ // Return configured relays from nostr-lite options
+ return this.nostrLite.options?.relays || ['wss://relay.damus.io'];
}
get nip04() {
@@ -4049,7 +4088,7 @@ class WindowNostr {
}
default:
- throw new Error(`Unsupported auth method: ${this.authState.method}`);
+ throw new Error('Unsupported auth method: ' + this.authState.method);
}
},
@@ -4091,7 +4130,7 @@ class WindowNostr {
}
default:
- throw new Error(`Unsupported auth method: ${this.authState.method}`);
+ throw new Error('Unsupported auth method: ' + this.authState.method);
}
}
};
@@ -4100,18 +4139,17 @@ class WindowNostr {
get nip44() {
return {
encrypt: async (pubkey, plaintext) => {
- const authState = getAuthState();
- if (!authState) {
+ if (!this.authState) {
throw new Error('Not authenticated - use NOSTR_LOGIN_LITE.launch()');
}
- if (authState.method === 'readonly') {
+ if (this.authState.method === 'readonly') {
throw new Error('Read-only mode - cannot encrypt');
}
- switch (authState.method) {
+ switch (this.authState.method) {
case 'extension': {
- const ext = this.authenticatedExtension || authState.extension || this.existingNostr;
+ const ext = this.authenticatedExtension || this.authState.extension || this.existingNostr;
if (!ext) throw new Error('Extension not available');
return await ext.nip44.encrypt(pubkey, plaintext);
}
@@ -4120,41 +4158,40 @@ class WindowNostr {
const { nip44, nip19 } = window.NostrTools;
let secretKey;
- if (authState.secret.startsWith('nsec')) {
- const decoded = nip19.decode(authState.secret);
+ if (this.authState.secret.startsWith('nsec')) {
+ const decoded = nip19.decode(this.authState.secret);
secretKey = decoded.data;
} else {
- secretKey = this._hexToUint8Array(authState.secret);
+ secretKey = this._hexToUint8Array(this.authState.secret);
}
return nip44.encrypt(plaintext, nip44.getConversationKey(secretKey, pubkey));
}
case 'nip46': {
- if (!authState.signer?.bunkerSigner) {
+ if (!this.authState.signer?.bunkerSigner) {
throw new Error('NIP-46 signer not available');
}
- return await authState.signer.bunkerSigner.nip44Encrypt(pubkey, plaintext);
+ return await this.authState.signer.bunkerSigner.nip44Encrypt(pubkey, plaintext);
}
default:
- throw new Error('Unsupported auth method: ' + authState.method);
+ throw new Error('Unsupported auth method: ' + this.authState.method);
}
},
decrypt: async (pubkey, ciphertext) => {
- const authState = getAuthState();
- if (!authState) {
+ if (!this.authState) {
throw new Error('Not authenticated - use NOSTR_LOGIN_LITE.launch()');
}
- if (authState.method === 'readonly') {
+ if (this.authState.method === 'readonly') {
throw new Error('Read-only mode - cannot decrypt');
}
- switch (authState.method) {
+ switch (this.authState.method) {
case 'extension': {
- const ext = this.authenticatedExtension || authState.extension || this.existingNostr;
+ const ext = this.authenticatedExtension || this.authState.extension || this.existingNostr;
if (!ext) throw new Error('Extension not available');
return await ext.nip44.decrypt(pubkey, ciphertext);
}
@@ -4163,25 +4200,25 @@ class WindowNostr {
const { nip44, nip19 } = window.NostrTools;
let secretKey;
- if (authState.secret.startsWith('nsec')) {
- const decoded = nip19.decode(authState.secret);
+ if (this.authState.secret.startsWith('nsec')) {
+ const decoded = nip19.decode(this.authState.secret);
secretKey = decoded.data;
} else {
- secretKey = this._hexToUint8Array(authState.secret);
+ secretKey = this._hexToUint8Array(this.authState.secret);
}
return nip44.decrypt(ciphertext, nip44.getConversationKey(secretKey, pubkey));
}
case 'nip46': {
- if (!authState.signer?.bunkerSigner) {
+ if (!this.authState.signer?.bunkerSigner) {
throw new Error('NIP-46 signer not available');
}
- return await authState.signer.bunkerSigner.nip44Decrypt(pubkey, ciphertext);
+ return await this.authState.signer.bunkerSigner.nip44Decrypt(pubkey, ciphertext);
}
default:
- throw new Error('Unsupported auth method: ' + authState.method);
+ throw new Error('Unsupported auth method: ' + this.authState.method);
}
}
};
@@ -4199,171 +4236,6 @@ class WindowNostr {
}
}
-// ======================================
-// Global Authentication State Manager - Single Source of Truth
-// ======================================
-
-// Storage-based authentication state - works regardless of extension presence
-function getAuthState() {
- try {
- console.log('š getAuthState: === GLOBAL AUTH STATE CHECK ===');
-
- const storageKey = 'nostr_login_lite_auth';
- let stored = null;
- let storageType = null;
-
- // Check sessionStorage first (per-window isolation), then localStorage
- if (sessionStorage.getItem(storageKey)) {
- stored = sessionStorage.getItem(storageKey);
- storageType = 'sessionStorage';
- console.log('š getAuthState: Found auth in sessionStorage');
- } else if (localStorage.getItem(storageKey)) {
- stored = localStorage.getItem(storageKey);
- storageType = 'localStorage';
- console.log('š getAuthState: Found auth in localStorage');
- }
-
- if (!stored) {
- console.log('š getAuthState: ā No stored auth state found');
- return null;
- }
-
- const authState = JSON.parse(stored);
- console.log('š getAuthState: ā
Parsed stored auth state from', storageType);
- console.log('š getAuthState: Method:', authState.method);
- console.log('š getAuthState: Pubkey:', authState.pubkey);
- console.log('š getAuthState: Age (ms):', Date.now() - authState.timestamp);
-
- // Check if auth state is expired
- const maxAge = authState.method === 'extension' ? 60 * 60 * 1000 : 24 * 60 * 60 * 1000;
- if (Date.now() - authState.timestamp > maxAge) {
- console.log('š getAuthState: ā Auth state expired, clearing');
- sessionStorage.removeItem(storageKey);
- localStorage.removeItem(storageKey);
- return null;
- }
-
- console.log('š getAuthState: ā
Valid auth state found');
- return authState;
-
- } catch (error) {
- console.error('š getAuthState: ā Error reading auth state:', error);
- return null;
- }
-}
-
-// ======================================
-// 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') {
const nostrLite = new NostrLite();
@@ -4389,9 +4261,9 @@ if (typeof window !== 'undefined') {
updateFloatingTab: (options) => nostrLite.updateFloatingTab(options),
getFloatingTabState: () => nostrLite.getFloatingTabState(),
- // GLOBAL AUTHENTICATION STATE API - Single Source of Truth
- getAuthState: getAuthState,
+ // Global authentication state management (single source of truth)
setAuthState: setAuthState,
+ getAuthState: getAuthState,
clearAuthState: clearAuthState,
// Expose for debugging
@@ -4402,7 +4274,9 @@ if (typeof window !== 'undefined') {
console.log('NOSTR_LOGIN_LITE: Library loaded and ready');
console.log('NOSTR_LOGIN_LITE: Use window.NOSTR_LOGIN_LITE.init(options) to initialize');
console.log('NOSTR_LOGIN_LITE: Detected', nostrLite.extensionBridge.getExtensionCount(), 'browser extensions');
+ console.warn('š SECURITY: Unified plaintext storage enabled for maximum developer usability');
} else {
// Node.js environment
module.exports = { NostrLite };
}
+
diff --git a/lite/ui/modal.js b/lite/ui/modal.js
index 56eb21b..96d0609 100644
--- a/lite/ui/modal.js
+++ b/lite/ui/modal.js
@@ -1131,7 +1131,6 @@ class Modal {
}
_setAuthMethod(method, options = {}) {
- // SINGLE-EXTENSION ARCHITECTURE: Handle method switching
console.log('Modal: _setAuthMethod called with:', method, options);
// CRITICAL: Never install facade for extension methods - leave window.nostr as the extension
@@ -1154,46 +1153,57 @@ class Modal {
return;
}
- // For non-extension methods, we need to ensure WindowNostr facade is available
- console.log('Modal: Non-extension method detected:', method);
+ // FOR NON-EXTENSION METHODS: Force-install facade with resilience
+ console.log('Modal: Non-extension method - FORCE-INSTALLING facade with resilience:', method);
- // Check if we have a preserved extension but no WindowNostr facade installed
- const hasPreservedExtension = !!window.NOSTR_LOGIN_LITE?._instance?.preservedExtension;
- const hasWindowNostrFacade = window.nostr?.constructor?.name === 'WindowNostr';
+ // Store the current extension if any (for potential restoration later)
+ const currentExtension = (window.nostr?.constructor?.name !== 'WindowNostr') ? window.nostr : null;
- console.log('Modal: Method switching check:');
- console.log(' method:', method);
- console.log(' hasPreservedExtension:', hasPreservedExtension);
- console.log(' hasWindowNostrFacade:', hasWindowNostrFacade);
- console.log(' current window.nostr constructor:', window.nostr?.constructor?.name);
+ // Get NostrLite instance for facade operations
+ const nostrLiteInstance = window.NOSTR_LOGIN_LITE?._instance;
+ if (!nostrLiteInstance || typeof nostrLiteInstance._installFacade !== 'function') {
+ console.error('Modal: Cannot access NostrLite instance or _installFacade method');
+ // Fallback: emit event anyway
+ const event = new CustomEvent('nlMethodSelected', {
+ detail: { method, ...options }
+ });
+ window.dispatchEvent(event);
+ this.close();
+ return;
+ }
- // If we have a preserved extension but no facade, install facade for method switching
- if (hasPreservedExtension && !hasWindowNostrFacade) {
- console.log('Modal: Installing WindowNostr facade for method switching (non-extension authentication)');
+ // IMMEDIATE FACADE INSTALLATION
+ console.log('Modal: Installing WindowNostr facade immediately for method:', method);
+ const preservedExtension = nostrLiteInstance.preservedExtension || currentExtension;
+ nostrLiteInstance._installFacade(preservedExtension, true);
+ console.log('Modal: WindowNostr facade force-installed, current window.nostr:', window.nostr?.constructor?.name);
+
+ // DELAYED FACADE RESILIENCE - Reinstall after extension override attempts
+ const forceReinstallFacade = () => {
+ console.log('Modal: RESILIENCE CHECK - Current window.nostr after delay:', window.nostr?.constructor?.name);
- // Get the NostrLite instance and install facade with preserved extension
- const nostrLiteInstance = window.NOSTR_LOGIN_LITE?._instance;
- if (nostrLiteInstance && typeof nostrLiteInstance._installFacade === 'function') {
- const preservedExtension = nostrLiteInstance.preservedExtension;
- console.log('Modal: Installing facade with preserved extension:', preservedExtension?.constructor?.name);
+ // If facade was overridden by extension, reinstall it
+ if (window.nostr?.constructor?.name !== 'WindowNostr') {
+ console.log('Modal: FACADE OVERRIDDEN! Force-reinstalling WindowNostr facade for user choice:', method);
+ nostrLiteInstance._installFacade(preservedExtension, true);
+ console.log('Modal: Resilient facade force-reinstall complete, window.nostr:', window.nostr?.constructor?.name);
- nostrLiteInstance._installFacade(preservedExtension);
- console.log('Modal: WindowNostr facade installed for method switching');
+ // Schedule another check in case of persistent extension override
+ setTimeout(() => {
+ if (window.nostr?.constructor?.name !== 'WindowNostr') {
+ console.log('Modal: PERSISTENT OVERRIDE! Final facade force-reinstall for method:', method);
+ nostrLiteInstance._installFacade(preservedExtension, true);
+ }
+ }, 1000);
} else {
- console.error('Modal: Cannot access NostrLite instance or _installFacade method');
+ console.log('Modal: Facade persistence verified - no override detected');
}
- }
+ };
- // If no extension at all, ensure facade is installed for local/NIP-46/readonly methods
- else if (!hasPreservedExtension && !hasWindowNostrFacade) {
- console.log('Modal: Installing WindowNostr facade for non-extension methods (no extension detected)');
-
- const nostrLiteInstance = window.NOSTR_LOGIN_LITE?._instance;
- if (nostrLiteInstance && typeof nostrLiteInstance._installFacade === 'function') {
- nostrLiteInstance._installFacade();
- console.log('Modal: WindowNostr facade installed for non-extension methods');
- }
- }
+ // Schedule resilience checks at multiple intervals
+ setTimeout(forceReinstallFacade, 100); // Quick check
+ setTimeout(forceReinstallFacade, 500); // Main check
+ setTimeout(forceReinstallFacade, 1500); // Final check
// Emit auth method selection
const event = new CustomEvent('nlMethodSelected', {