From 995c3f526cef71b8c8169b73e6549d9881146f52 Mon Sep 17 00:00:00 2001 From: Your Name Date: Tue, 16 Sep 2025 11:55:47 -0400 Subject: [PATCH] Removed interference with extensions. Had to go back to only allowing handling single extension. --- lite/build.js | 141 +++++++++++++++++- lite/nostr-lite.js | 360 +++++++++++++++++++++++++++++++++++++-------- lite/ui/modal.js | 217 ++++++++++++++++++++------- 3 files changed, 595 insertions(+), 123 deletions(-) diff --git a/lite/build.js b/lite/build.js index 2764fa5..d360c1a 100644 --- a/lite/build.js +++ b/lite/build.js @@ -1200,16 +1200,149 @@ class NostrLite { _setupWindowNostrFacade() { if (typeof window !== 'undefined') { + console.log('NOSTR_LOGIN_LITE: === TRUE SINGLE-EXTENSION ARCHITECTURE ==='); + console.log('NOSTR_LOGIN_LITE: Initial window.nostr:', window.nostr); + console.log('NOSTR_LOGIN_LITE: Initial window.nostr constructor:', window.nostr?.constructor?.name); + // Store existing window.nostr if it exists (from extensions) const existingNostr = window.nostr; - // Always install our facade - window.nostr = new WindowNostr(this, existingNostr); - console.log('NOSTR_LOGIN_LITE: window.nostr facade installed', - existingNostr ? '(with extension passthrough)' : '(no existing extension)'); + // TRUE SINGLE-EXTENSION ARCHITECTURE: Don't install facade when extensions detected + if (this._isRealExtension(existingNostr)) { + console.log('NOSTR_LOGIN_LITE: ✓ REAL EXTENSION DETECTED IMMEDIATELY - PRESERVING WITHOUT FACADE'); + console.log('NOSTR_LOGIN_LITE: Extension constructor:', existingNostr.constructor?.name); + console.log('NOSTR_LOGIN_LITE: Extension keys:', Object.keys(existingNostr)); + console.log('NOSTR_LOGIN_LITE: Leaving window.nostr untouched for extension compatibility'); + this.preservedExtension = existingNostr; + this.facadeInstalled = false; + // DON'T install facade - leave window.nostr as the extension + return; + } + + // DEFERRED EXTENSION DETECTION: Extensions like nos2x may load after us + console.log('NOSTR_LOGIN_LITE: No real extension detected initially, starting deferred detection...'); + this.facadeInstalled = false; + + let checkCount = 0; + const maxChecks = 10; // Check for up to 2 seconds + const checkInterval = setInterval(() => { + checkCount++; + const currentNostr = window.nostr; + + console.log('NOSTR_LOGIN_LITE: === DEFERRED CHECK ' + checkCount + '/' + maxChecks + ' ==='); + console.log('NOSTR_LOGIN_LITE: Current window.nostr:', currentNostr); + console.log('NOSTR_LOGIN_LITE: Constructor:', currentNostr?.constructor?.name); + + // Skip if it's our facade + if (currentNostr?.constructor?.name === 'WindowNostr') { + console.log('NOSTR_LOGIN_LITE: Skipping - this is our facade'); + return; + } + + if (this._isRealExtension(currentNostr)) { + console.log('NOSTR_LOGIN_LITE: ✓✓✓ LATE EXTENSION DETECTED - PRESERVING WITHOUT FACADE ✓✓✓'); + console.log('NOSTR_LOGIN_LITE: Extension detected after ' + (checkCount * 200) + 'ms!'); + console.log('NOSTR_LOGIN_LITE: Extension constructor:', currentNostr.constructor?.name); + console.log('NOSTR_LOGIN_LITE: Extension keys:', Object.keys(currentNostr)); + console.log('NOSTR_LOGIN_LITE: Leaving window.nostr untouched for extension compatibility'); + this.preservedExtension = currentNostr; + this.facadeInstalled = false; + clearInterval(checkInterval); + // DON'T install facade - leave window.nostr as the extension + return; + } + + // Stop checking after max attempts - no extension found + if (checkCount >= maxChecks) { + console.log('NOSTR_LOGIN_LITE: ⚠️ MAX CHECKS REACHED - NO EXTENSION FOUND'); + clearInterval(checkInterval); + console.log('NOSTR_LOGIN_LITE: Installing facade for local/NIP-46/readonly methods'); + this._installFacade(); + } + }, 200); // Check every 200ms + + console.log('NOSTR_LOGIN_LITE: Waiting for deferred detection to complete...'); } } + _installFacade(existingNostr = null) { + if (typeof window !== 'undefined' && !this.facadeInstalled) { + 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); + + const facade = new WindowNostr(this, existingNostr); + window.nostr = facade; + this.facadeInstalled = true; + + console.log('NOSTR_LOGIN_LITE: === FACADE INSTALLED WITH EXTENSION ==='); + 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); + } + } + + // Helper method to identify real browser extensions + _isRealExtension(obj) { + console.log('NOSTR_LOGIN_LITE: === _isRealExtension DEBUG ==='); + console.log('NOSTR_LOGIN_LITE: obj:', obj); + console.log('NOSTR_LOGIN_LITE: typeof obj:', typeof obj); + + if (!obj || typeof obj !== 'object') { + console.log('NOSTR_LOGIN_LITE: ✗ Not an object'); + return false; + } + + console.log('NOSTR_LOGIN_LITE: Object keys:', Object.keys(obj)); + console.log('NOSTR_LOGIN_LITE: getPublicKey type:', typeof obj.getPublicKey); + console.log('NOSTR_LOGIN_LITE: signEvent type:', typeof obj.signEvent); + + // Must have required Nostr methods + if (typeof obj.getPublicKey !== 'function' || typeof obj.signEvent !== 'function') { + console.log('NOSTR_LOGIN_LITE: ✗ Missing required methods'); + return false; + } + + // Exclude our own library classes + const constructorName = obj.constructor?.name; + console.log('NOSTR_LOGIN_LITE: Constructor name:', constructorName); + + if (constructorName === 'WindowNostr' || constructorName === 'NostrLite') { + console.log('NOSTR_LOGIN_LITE: ✗ Is our library class'); + return false; + } + + // Exclude NostrTools library object + if (obj === window.NostrTools) { + console.log('NOSTR_LOGIN_LITE: ✗ Is NostrTools object'); + return false; + } + + // Real extensions typically have internal properties or specific characteristics + console.log('NOSTR_LOGIN_LITE: Extension property check:'); + console.log(' _isEnabled:', !!obj._isEnabled); + console.log(' enabled:', !!obj.enabled); + console.log(' kind:', !!obj.kind); + console.log(' _eventEmitter:', !!obj._eventEmitter); + console.log(' _scope:', !!obj._scope); + console.log(' _requests:', !!obj._requests); + console.log(' _pubkey:', !!obj._pubkey); + console.log(' name:', !!obj.name); + console.log(' version:', !!obj.version); + console.log(' description:', !!obj.description); + + const hasExtensionProps = !!( + obj._isEnabled || obj.enabled || obj.kind || + obj._eventEmitter || obj._scope || obj._requests || obj._pubkey || + obj.name || obj.version || obj.description + ); + + console.log('NOSTR_LOGIN_LITE: Extension detection result for', constructorName, ':', hasExtensionProps); + return hasExtensionProps; + } + launch(startScreen = 'login') { console.log('NOSTR_LOGIN_LITE: Launching with screen:', startScreen); diff --git a/lite/nostr-lite.js b/lite/nostr-lite.js index 2717e91..87a81ee 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-15T18:50:50.789Z + * Generated on: 2025-09-16T15:52:30.145Z */ // Verify dependencies are loaded @@ -1128,23 +1128,62 @@ class Modal { } _handleExtension() { - // Detect all available real extensions - const availableExtensions = this._detectAllExtensions(); + // SIMPLIFIED ARCHITECTURE: Check for single extension at window.nostr or preserved extension + let extension = null; - console.log(`Modal: Found ${availableExtensions.length} extensions:`, availableExtensions.map(e => e.displayName)); - - if (availableExtensions.length === 0) { - console.log('Modal: No real extensions found'); - this._showExtensionRequired(); - } else if (availableExtensions.length === 1) { - // Single extension - use it directly without showing choice UI - console.log('Modal: Single extension detected, using it directly:', availableExtensions[0].displayName); - this._tryExtensionLogin(availableExtensions[0].extension); - } else { - // Multiple extensions - show choice UI - console.log('Modal: Multiple extensions detected, showing choice UI for', availableExtensions.length, 'extensions'); - this._showExtensionChoice(availableExtensions); + // Check if NostrLite instance has a preserved extension (real extension detected at init) + if (window.NOSTR_LOGIN_LITE?._instance?.preservedExtension) { + extension = window.NOSTR_LOGIN_LITE._instance.preservedExtension; + console.log('Modal: Using preserved extension:', extension.constructor?.name); } + // Otherwise check current window.nostr + else if (window.nostr && this._isRealExtension(window.nostr)) { + extension = window.nostr; + console.log('Modal: Using current window.nostr extension:', extension.constructor?.name); + } + + if (!extension) { + console.log('Modal: No extension detected yet, waiting for deferred detection...'); + + // DEFERRED EXTENSION CHECK: Extensions like nos2x might load after our library + let attempts = 0; + const maxAttempts = 10; // Try for 2 seconds + const checkForExtension = () => { + attempts++; + + // Check again for preserved extension (might be set by deferred detection) + if (window.NOSTR_LOGIN_LITE?._instance?.preservedExtension) { + extension = window.NOSTR_LOGIN_LITE._instance.preservedExtension; + console.log('Modal: Found preserved extension after waiting:', extension.constructor?.name); + this._tryExtensionLogin(extension); + return; + } + + // Check current window.nostr again + if (window.nostr && this._isRealExtension(window.nostr)) { + extension = window.nostr; + console.log('Modal: Found extension at window.nostr after waiting:', extension.constructor?.name); + this._tryExtensionLogin(extension); + return; + } + + // Keep trying or give up + if (attempts < maxAttempts) { + setTimeout(checkForExtension, 200); + } else { + console.log('Modal: No browser extension found after waiting 2 seconds'); + this._showExtensionRequired(); + } + }; + + // Start checking after a brief delay + setTimeout(checkForExtension, 200); + return; + } + + // Use the single detected extension directly - no choice UI + console.log('Modal: Single extension mode - using extension directly'); + this._tryExtensionLogin(extension); } _detectAllExtensions() { @@ -1190,17 +1229,38 @@ class Modal { // Also check window.nostr but be extra careful to avoid our library console.log('Modal: Checking window.nostr:', !!window.nostr, window.nostr?.constructor?.name); - if (window.nostr && this._isRealExtension(window.nostr) && !seenExtensions.has(window.nostr)) { - extensions.push({ - name: 'window.nostr', - displayName: 'Extension (window.nostr)', - icon: '🔑', - extension: window.nostr - }); - seenExtensions.add(window.nostr); - console.log(`Modal: ✓ Detected extension at window.nostr: ${window.nostr.constructor?.name}`); - } else if (window.nostr) { - console.log(`Modal: ✗ Filtered out window.nostr (${window.nostr.constructor?.name}) - likely our library`); + + if (window.nostr) { + // Check if window.nostr is our WindowNostr facade with a preserved extension + if (window.nostr.constructor?.name === 'WindowNostr' && window.nostr.existingNostr) { + console.log('Modal: Found WindowNostr facade, checking existingNostr for preserved extension'); + const preservedExtension = window.nostr.existingNostr; + console.log('Modal: Preserved extension:', !!preservedExtension, preservedExtension?.constructor?.name); + + if (preservedExtension && this._isRealExtension(preservedExtension) && !seenExtensions.has(preservedExtension)) { + extensions.push({ + name: 'window.nostr.existingNostr', + displayName: 'Extension (preserved by WindowNostr)', + icon: '🔑', + extension: preservedExtension + }); + seenExtensions.add(preservedExtension); + console.log(`Modal: ✓ Detected preserved extension: ${preservedExtension.constructor?.name}`); + } + } + // Check if window.nostr is directly a real extension (not our facade) + else if (this._isRealExtension(window.nostr) && !seenExtensions.has(window.nostr)) { + extensions.push({ + name: 'window.nostr', + displayName: 'Extension (window.nostr)', + icon: '🔑', + extension: window.nostr + }); + seenExtensions.add(window.nostr); + console.log(`Modal: ✓ Detected extension at window.nostr: ${window.nostr.constructor?.name}`); + } else { + console.log(`Modal: ✗ Filtered out window.nostr (${window.nostr.constructor?.name}) - not a real extension`); + } } return extensions; @@ -1790,6 +1850,63 @@ 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 + if (method === 'extension') { + console.log('Modal: Extension method - NOT installing facade, leaving window.nostr as extension'); + + // Emit auth method selection directly for extension + const event = new CustomEvent('nlMethodSelected', { + detail: { method, ...options } + }); + window.dispatchEvent(event); + this.close(); + return; + } + + // For non-extension methods, we need to ensure WindowNostr facade is available + console.log('Modal: Non-extension method detected:', 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'; + + 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); + + // 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)'); + + // 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); + + nostrLiteInstance._installFacade(preservedExtension); + console.log('Modal: WindowNostr facade installed for method switching'); + } else { + console.error('Modal: Cannot access NostrLite instance or _installFacade method'); + } + } + + // 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'); + } + } + // Emit auth method selection const event = new CustomEvent('nlMethodSelected', { detail: { method, ...options } @@ -1823,8 +1940,13 @@ class Modal { title.style.cssText = 'margin: 0 0 16px 0; font-size: 18px; font-weight: 600;'; const message = document.createElement('p'); - message.textContent = 'Please install a Nostr browser extension like Alby or getflattr and refresh the page.'; - message.style.cssText = 'margin-bottom: 20px; color: #6b7280;'; + message.innerHTML = ` + Please install a Nostr browser extension and refresh the page.

+ Important: If you have multiple extensions installed, please disable all but one to avoid conflicts. +

+ Popular extensions: Alby, nos2x, Flamingo + `; + message.style.cssText = 'margin-bottom: 20px; color: #6b7280; font-size: 14px; line-height: 1.4;'; const backButton = document.createElement('button'); backButton.textContent = 'Back'; @@ -1867,27 +1989,11 @@ class Modal { box-sizing: border-box; `; - const urlLabel = document.createElement('label'); - urlLabel.textContent = 'Remote URL (optional):'; - urlLabel.style.cssText = 'display: block; margin-bottom: 8px; font-weight: 500;'; - - const urlInput = document.createElement('input'); - urlInput.type = 'url'; - urlInput.placeholder = 'ws://localhost:8080 (default)'; - urlInput.style.cssText = ` - width: 100%; - padding: 12px; - border: 1px solid #d1d5db; - border-radius: 6px; - margin-bottom: 16px; - box-sizing: border-box; - `; - - // Users will enter the bunker URL manually from their bunker setup + // Users will enter the complete bunker connection string with relay info const connectButton = document.createElement('button'); connectButton.textContent = 'Connect to Bunker'; - connectButton.onclick = () => this._handleNip46Connect(pubkeyInput.value, urlInput.value); + connectButton.onclick = () => this._handleNip46Connect(pubkeyInput.value); connectButton.style.cssText = this._getButtonStyle(); const backButton = document.createElement('button'); @@ -1897,8 +2003,6 @@ class Modal { formGroup.appendChild(label); formGroup.appendChild(pubkeyInput); - formGroup.appendChild(urlLabel); - formGroup.appendChild(urlInput); this.modalBody.appendChild(title); this.modalBody.appendChild(description); @@ -1907,17 +2011,17 @@ class Modal { this.modalBody.appendChild(backButton); } - _handleNip46Connect(bunkerPubkey, bunkerUrl) { + _handleNip46Connect(bunkerPubkey) { if (!bunkerPubkey || !bunkerPubkey.length) { this._showError('Bunker pubkey is required'); return; } - this._showNip46Connecting(bunkerPubkey, bunkerUrl); - this._performNip46Connect(bunkerPubkey, bunkerUrl); + this._showNip46Connecting(bunkerPubkey); + this._performNip46Connect(bunkerPubkey); } - _showNip46Connecting(bunkerPubkey, bunkerUrl) { + _showNip46Connecting(bunkerPubkey) { this.modalBody.innerHTML = ''; const title = document.createElement('h3'); @@ -1935,9 +2039,8 @@ class Modal { bunkerInfo.style.cssText = 'background: #f1f5f9; padding: 12px; border-radius: 6px; margin-bottom: 20px; font-size: 14px;'; bunkerInfo.innerHTML = ` Connecting to bunker:
- Pubkey: ${displayPubkey}
- Relay: ${bunkerUrl || 'ws://localhost:8080'}
- If this relay is offline, the bunker server may be unavailable. + Connection: ${displayPubkey}
+ Connection string contains all necessary relay information. `; const connectingDiv = document.createElement('div'); @@ -1954,9 +2057,9 @@ class Modal { this.modalBody.appendChild(connectingDiv); } - async _performNip46Connect(bunkerPubkey, bunkerUrl) { + async _performNip46Connect(bunkerPubkey) { try { - console.log('Starting NIP-46 connection to bunker:', bunkerPubkey, bunkerUrl); + console.log('Starting NIP-46 connection to bunker:', bunkerPubkey); // Check if nostr-tools NIP-46 is available if (!window.NostrTools?.nip46) { @@ -2648,16 +2751,149 @@ class NostrLite { _setupWindowNostrFacade() { if (typeof window !== 'undefined') { + console.log('NOSTR_LOGIN_LITE: === TRUE SINGLE-EXTENSION ARCHITECTURE ==='); + console.log('NOSTR_LOGIN_LITE: Initial window.nostr:', window.nostr); + console.log('NOSTR_LOGIN_LITE: Initial window.nostr constructor:', window.nostr?.constructor?.name); + // Store existing window.nostr if it exists (from extensions) const existingNostr = window.nostr; - // Always install our facade - window.nostr = new WindowNostr(this, existingNostr); - console.log('NOSTR_LOGIN_LITE: window.nostr facade installed', - existingNostr ? '(with extension passthrough)' : '(no existing extension)'); + // TRUE SINGLE-EXTENSION ARCHITECTURE: Don't install facade when extensions detected + if (this._isRealExtension(existingNostr)) { + console.log('NOSTR_LOGIN_LITE: ✓ REAL EXTENSION DETECTED IMMEDIATELY - PRESERVING WITHOUT FACADE'); + console.log('NOSTR_LOGIN_LITE: Extension constructor:', existingNostr.constructor?.name); + console.log('NOSTR_LOGIN_LITE: Extension keys:', Object.keys(existingNostr)); + console.log('NOSTR_LOGIN_LITE: Leaving window.nostr untouched for extension compatibility'); + this.preservedExtension = existingNostr; + this.facadeInstalled = false; + // DON'T install facade - leave window.nostr as the extension + return; + } + + // DEFERRED EXTENSION DETECTION: Extensions like nos2x may load after us + console.log('NOSTR_LOGIN_LITE: No real extension detected initially, starting deferred detection...'); + this.facadeInstalled = false; + + let checkCount = 0; + const maxChecks = 10; // Check for up to 2 seconds + const checkInterval = setInterval(() => { + checkCount++; + const currentNostr = window.nostr; + + console.log('NOSTR_LOGIN_LITE: === DEFERRED CHECK ' + checkCount + '/' + maxChecks + ' ==='); + console.log('NOSTR_LOGIN_LITE: Current window.nostr:', currentNostr); + console.log('NOSTR_LOGIN_LITE: Constructor:', currentNostr?.constructor?.name); + + // Skip if it's our facade + if (currentNostr?.constructor?.name === 'WindowNostr') { + console.log('NOSTR_LOGIN_LITE: Skipping - this is our facade'); + return; + } + + if (this._isRealExtension(currentNostr)) { + console.log('NOSTR_LOGIN_LITE: ✓✓✓ LATE EXTENSION DETECTED - PRESERVING WITHOUT FACADE ✓✓✓'); + console.log('NOSTR_LOGIN_LITE: Extension detected after ' + (checkCount * 200) + 'ms!'); + console.log('NOSTR_LOGIN_LITE: Extension constructor:', currentNostr.constructor?.name); + console.log('NOSTR_LOGIN_LITE: Extension keys:', Object.keys(currentNostr)); + console.log('NOSTR_LOGIN_LITE: Leaving window.nostr untouched for extension compatibility'); + this.preservedExtension = currentNostr; + this.facadeInstalled = false; + clearInterval(checkInterval); + // DON'T install facade - leave window.nostr as the extension + return; + } + + // Stop checking after max attempts - no extension found + if (checkCount >= maxChecks) { + console.log('NOSTR_LOGIN_LITE: ⚠️ MAX CHECKS REACHED - NO EXTENSION FOUND'); + clearInterval(checkInterval); + console.log('NOSTR_LOGIN_LITE: Installing facade for local/NIP-46/readonly methods'); + this._installFacade(); + } + }, 200); // Check every 200ms + + console.log('NOSTR_LOGIN_LITE: Waiting for deferred detection to complete...'); } } + _installFacade(existingNostr = null) { + if (typeof window !== 'undefined' && !this.facadeInstalled) { + 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); + + const facade = new WindowNostr(this, existingNostr); + window.nostr = facade; + this.facadeInstalled = true; + + console.log('NOSTR_LOGIN_LITE: === FACADE INSTALLED WITH EXTENSION ==='); + 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); + } + } + + // Helper method to identify real browser extensions + _isRealExtension(obj) { + console.log('NOSTR_LOGIN_LITE: === _isRealExtension DEBUG ==='); + console.log('NOSTR_LOGIN_LITE: obj:', obj); + console.log('NOSTR_LOGIN_LITE: typeof obj:', typeof obj); + + if (!obj || typeof obj !== 'object') { + console.log('NOSTR_LOGIN_LITE: ✗ Not an object'); + return false; + } + + console.log('NOSTR_LOGIN_LITE: Object keys:', Object.keys(obj)); + console.log('NOSTR_LOGIN_LITE: getPublicKey type:', typeof obj.getPublicKey); + console.log('NOSTR_LOGIN_LITE: signEvent type:', typeof obj.signEvent); + + // Must have required Nostr methods + if (typeof obj.getPublicKey !== 'function' || typeof obj.signEvent !== 'function') { + console.log('NOSTR_LOGIN_LITE: ✗ Missing required methods'); + return false; + } + + // Exclude our own library classes + const constructorName = obj.constructor?.name; + console.log('NOSTR_LOGIN_LITE: Constructor name:', constructorName); + + if (constructorName === 'WindowNostr' || constructorName === 'NostrLite') { + console.log('NOSTR_LOGIN_LITE: ✗ Is our library class'); + return false; + } + + // Exclude NostrTools library object + if (obj === window.NostrTools) { + console.log('NOSTR_LOGIN_LITE: ✗ Is NostrTools object'); + return false; + } + + // Real extensions typically have internal properties or specific characteristics + console.log('NOSTR_LOGIN_LITE: Extension property check:'); + console.log(' _isEnabled:', !!obj._isEnabled); + console.log(' enabled:', !!obj.enabled); + console.log(' kind:', !!obj.kind); + console.log(' _eventEmitter:', !!obj._eventEmitter); + console.log(' _scope:', !!obj._scope); + console.log(' _requests:', !!obj._requests); + console.log(' _pubkey:', !!obj._pubkey); + console.log(' name:', !!obj.name); + console.log(' version:', !!obj.version); + console.log(' description:', !!obj.description); + + const hasExtensionProps = !!( + obj._isEnabled || obj.enabled || obj.kind || + obj._eventEmitter || obj._scope || obj._requests || obj._pubkey || + obj.name || obj.version || obj.description + ); + + console.log('NOSTR_LOGIN_LITE: Extension detection result for', constructorName, ':', hasExtensionProps); + return hasExtensionProps; + } + launch(startScreen = 'login') { console.log('NOSTR_LOGIN_LITE: Launching with screen:', startScreen); diff --git a/lite/ui/modal.js b/lite/ui/modal.js index 8177b5e..1bed701 100644 --- a/lite/ui/modal.js +++ b/lite/ui/modal.js @@ -332,23 +332,62 @@ class Modal { } _handleExtension() { - // Detect all available real extensions - const availableExtensions = this._detectAllExtensions(); + // SIMPLIFIED ARCHITECTURE: Check for single extension at window.nostr or preserved extension + let extension = null; - console.log(`Modal: Found ${availableExtensions.length} extensions:`, availableExtensions.map(e => e.displayName)); - - if (availableExtensions.length === 0) { - console.log('Modal: No real extensions found'); - this._showExtensionRequired(); - } else if (availableExtensions.length === 1) { - // Single extension - use it directly without showing choice UI - console.log('Modal: Single extension detected, using it directly:', availableExtensions[0].displayName); - this._tryExtensionLogin(availableExtensions[0].extension); - } else { - // Multiple extensions - show choice UI - console.log('Modal: Multiple extensions detected, showing choice UI for', availableExtensions.length, 'extensions'); - this._showExtensionChoice(availableExtensions); + // Check if NostrLite instance has a preserved extension (real extension detected at init) + if (window.NOSTR_LOGIN_LITE?._instance?.preservedExtension) { + extension = window.NOSTR_LOGIN_LITE._instance.preservedExtension; + console.log('Modal: Using preserved extension:', extension.constructor?.name); } + // Otherwise check current window.nostr + else if (window.nostr && this._isRealExtension(window.nostr)) { + extension = window.nostr; + console.log('Modal: Using current window.nostr extension:', extension.constructor?.name); + } + + if (!extension) { + console.log('Modal: No extension detected yet, waiting for deferred detection...'); + + // DEFERRED EXTENSION CHECK: Extensions like nos2x might load after our library + let attempts = 0; + const maxAttempts = 10; // Try for 2 seconds + const checkForExtension = () => { + attempts++; + + // Check again for preserved extension (might be set by deferred detection) + if (window.NOSTR_LOGIN_LITE?._instance?.preservedExtension) { + extension = window.NOSTR_LOGIN_LITE._instance.preservedExtension; + console.log('Modal: Found preserved extension after waiting:', extension.constructor?.name); + this._tryExtensionLogin(extension); + return; + } + + // Check current window.nostr again + if (window.nostr && this._isRealExtension(window.nostr)) { + extension = window.nostr; + console.log('Modal: Found extension at window.nostr after waiting:', extension.constructor?.name); + this._tryExtensionLogin(extension); + return; + } + + // Keep trying or give up + if (attempts < maxAttempts) { + setTimeout(checkForExtension, 200); + } else { + console.log('Modal: No browser extension found after waiting 2 seconds'); + this._showExtensionRequired(); + } + }; + + // Start checking after a brief delay + setTimeout(checkForExtension, 200); + return; + } + + // Use the single detected extension directly - no choice UI + console.log('Modal: Single extension mode - using extension directly'); + this._tryExtensionLogin(extension); } _detectAllExtensions() { @@ -394,17 +433,38 @@ class Modal { // Also check window.nostr but be extra careful to avoid our library console.log('Modal: Checking window.nostr:', !!window.nostr, window.nostr?.constructor?.name); - if (window.nostr && this._isRealExtension(window.nostr) && !seenExtensions.has(window.nostr)) { - extensions.push({ - name: 'window.nostr', - displayName: 'Extension (window.nostr)', - icon: '🔑', - extension: window.nostr - }); - seenExtensions.add(window.nostr); - console.log(`Modal: ✓ Detected extension at window.nostr: ${window.nostr.constructor?.name}`); - } else if (window.nostr) { - console.log(`Modal: ✗ Filtered out window.nostr (${window.nostr.constructor?.name}) - likely our library`); + + if (window.nostr) { + // Check if window.nostr is our WindowNostr facade with a preserved extension + if (window.nostr.constructor?.name === 'WindowNostr' && window.nostr.existingNostr) { + console.log('Modal: Found WindowNostr facade, checking existingNostr for preserved extension'); + const preservedExtension = window.nostr.existingNostr; + console.log('Modal: Preserved extension:', !!preservedExtension, preservedExtension?.constructor?.name); + + if (preservedExtension && this._isRealExtension(preservedExtension) && !seenExtensions.has(preservedExtension)) { + extensions.push({ + name: 'window.nostr.existingNostr', + displayName: 'Extension (preserved by WindowNostr)', + icon: '🔑', + extension: preservedExtension + }); + seenExtensions.add(preservedExtension); + console.log(`Modal: ✓ Detected preserved extension: ${preservedExtension.constructor?.name}`); + } + } + // Check if window.nostr is directly a real extension (not our facade) + else if (this._isRealExtension(window.nostr) && !seenExtensions.has(window.nostr)) { + extensions.push({ + name: 'window.nostr', + displayName: 'Extension (window.nostr)', + icon: '🔑', + extension: window.nostr + }); + seenExtensions.add(window.nostr); + console.log(`Modal: ✓ Detected extension at window.nostr: ${window.nostr.constructor?.name}`); + } else { + console.log(`Modal: ✗ Filtered out window.nostr (${window.nostr.constructor?.name}) - not a real extension`); + } } return extensions; @@ -994,6 +1054,63 @@ 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 + if (method === 'extension') { + console.log('Modal: Extension method - NOT installing facade, leaving window.nostr as extension'); + + // Emit auth method selection directly for extension + const event = new CustomEvent('nlMethodSelected', { + detail: { method, ...options } + }); + window.dispatchEvent(event); + this.close(); + return; + } + + // For non-extension methods, we need to ensure WindowNostr facade is available + console.log('Modal: Non-extension method detected:', 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'; + + 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); + + // 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)'); + + // 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); + + nostrLiteInstance._installFacade(preservedExtension); + console.log('Modal: WindowNostr facade installed for method switching'); + } else { + console.error('Modal: Cannot access NostrLite instance or _installFacade method'); + } + } + + // 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'); + } + } + // Emit auth method selection const event = new CustomEvent('nlMethodSelected', { detail: { method, ...options } @@ -1027,8 +1144,13 @@ class Modal { title.style.cssText = 'margin: 0 0 16px 0; font-size: 18px; font-weight: 600;'; const message = document.createElement('p'); - message.textContent = 'Please install a Nostr browser extension like Alby or getflattr and refresh the page.'; - message.style.cssText = 'margin-bottom: 20px; color: #6b7280;'; + message.innerHTML = ` + Please install a Nostr browser extension and refresh the page.

+ Important: If you have multiple extensions installed, please disable all but one to avoid conflicts. +

+ Popular extensions: Alby, nos2x, Flamingo + `; + message.style.cssText = 'margin-bottom: 20px; color: #6b7280; font-size: 14px; line-height: 1.4;'; const backButton = document.createElement('button'); backButton.textContent = 'Back'; @@ -1071,27 +1193,11 @@ class Modal { box-sizing: border-box; `; - const urlLabel = document.createElement('label'); - urlLabel.textContent = 'Remote URL (optional):'; - urlLabel.style.cssText = 'display: block; margin-bottom: 8px; font-weight: 500;'; - - const urlInput = document.createElement('input'); - urlInput.type = 'url'; - urlInput.placeholder = 'ws://localhost:8080 (default)'; - urlInput.style.cssText = ` - width: 100%; - padding: 12px; - border: 1px solid #d1d5db; - border-radius: 6px; - margin-bottom: 16px; - box-sizing: border-box; - `; - - // Users will enter the bunker URL manually from their bunker setup + // Users will enter the complete bunker connection string with relay info const connectButton = document.createElement('button'); connectButton.textContent = 'Connect to Bunker'; - connectButton.onclick = () => this._handleNip46Connect(pubkeyInput.value, urlInput.value); + connectButton.onclick = () => this._handleNip46Connect(pubkeyInput.value); connectButton.style.cssText = this._getButtonStyle(); const backButton = document.createElement('button'); @@ -1101,8 +1207,6 @@ class Modal { formGroup.appendChild(label); formGroup.appendChild(pubkeyInput); - formGroup.appendChild(urlLabel); - formGroup.appendChild(urlInput); this.modalBody.appendChild(title); this.modalBody.appendChild(description); @@ -1111,17 +1215,17 @@ class Modal { this.modalBody.appendChild(backButton); } - _handleNip46Connect(bunkerPubkey, bunkerUrl) { + _handleNip46Connect(bunkerPubkey) { if (!bunkerPubkey || !bunkerPubkey.length) { this._showError('Bunker pubkey is required'); return; } - this._showNip46Connecting(bunkerPubkey, bunkerUrl); - this._performNip46Connect(bunkerPubkey, bunkerUrl); + this._showNip46Connecting(bunkerPubkey); + this._performNip46Connect(bunkerPubkey); } - _showNip46Connecting(bunkerPubkey, bunkerUrl) { + _showNip46Connecting(bunkerPubkey) { this.modalBody.innerHTML = ''; const title = document.createElement('h3'); @@ -1139,9 +1243,8 @@ class Modal { bunkerInfo.style.cssText = 'background: #f1f5f9; padding: 12px; border-radius: 6px; margin-bottom: 20px; font-size: 14px;'; bunkerInfo.innerHTML = ` Connecting to bunker:
- Pubkey: ${displayPubkey}
- Relay: ${bunkerUrl || 'ws://localhost:8080'}
- If this relay is offline, the bunker server may be unavailable. + Connection: ${displayPubkey}
+ Connection string contains all necessary relay information. `; const connectingDiv = document.createElement('div'); @@ -1158,9 +1261,9 @@ class Modal { this.modalBody.appendChild(connectingDiv); } - async _performNip46Connect(bunkerPubkey, bunkerUrl) { + async _performNip46Connect(bunkerPubkey) { try { - console.log('Starting NIP-46 connection to bunker:', bunkerPubkey, bunkerUrl); + console.log('Starting NIP-46 connection to bunker:', bunkerPubkey); // Check if nostr-tools NIP-46 is available if (!window.NostrTools?.nip46) {