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) {