2 Commits

Author SHA1 Message Date
Your Name
a0e18c34d6 Add comprehensive sign.html test and update documentation
- Add examples/sign.html with comprehensive extension compatibility testing
- Update README.md with profile fetching API documentation
- Update .gitignore for better file management
- Update examples/button.html and examples/modal.html with latest features

This completes the single-extension architecture implementation with:
- nos2x compatibility through true single-extension mode
- Method switching between extension/local/NIP-46/readonly
- Enhanced profile fetching for floating tab
- Comprehensive debugging and testing capabilities
2025-09-16 12:40:15 -04:00
Your Name
995c3f526c Removed interference with extensions. Had to go back to only allowing handling single extension. 2025-09-16 11:55:47 -04:00
8 changed files with 791 additions and 137 deletions

8
.gitignore vendored
View File

@@ -18,10 +18,4 @@ Thumbs.db
log.txt log.txt
Trash/ Trash/
# Environment files nostr-login/
.env
# Aider files
.aider.chat.history.md
.aider.input.history
.aider.tags.cache.v3/

View File

@@ -24,6 +24,11 @@ await NOSTR_LOGIN_LITE.init({
enabled: true, enabled: true,
hPosition: 0.95, // 0.0-1.0 or '95%' from left hPosition: 0.95, // 0.0-1.0 or '95%' from left
vPosition: 0.5, // 0.0-1.0 or '50%' from top vPosition: 0.5, // 0.0-1.0 or '50%' from top
getUserInfo: true, // Fetch user profile name from relays
getUserRelay: [ // Relays for profile queries
'wss://relay.damus.io',
'wss://nos.lol'
],
appearance: { appearance: {
style: 'pill', // 'pill', 'square', 'circle', 'minimal' style: 'pill', // 'pill', 'square', 'circle', 'minimal'
theme: 'auto', // 'auto' follows main theme theme: 'auto', // 'auto' follows main theme

View File

@@ -51,7 +51,7 @@
<script> <script>
document.addEventListener('DOMContentLoaded', async () => { document.addEventListener('DOMContentLoaded', async () => {
await window.NOSTR_LOGIN_LITE.init({ await window.NOSTR_LOGIN_LITE.init({
theme: 'default',
methods: { methods: {
extension: true, extension: true,
local: true, local: true,

View File

@@ -37,7 +37,7 @@
<script> <script>
document.addEventListener('DOMContentLoaded', async () => { document.addEventListener('DOMContentLoaded', async () => {
await window.NOSTR_LOGIN_LITE.init({ await window.NOSTR_LOGIN_LITE.init({
theme: 'dark', theme: 'default',
methods: { methods: {
extension: true, extension: true,
local: true, local: true,
@@ -48,12 +48,11 @@
}, },
floatingTab: { floatingTab: {
enabled: true, enabled: true,
hPosition: 0.7, // 0.0-1.0 or '95%' from left hPosition: 1, // 0.0-1.0 or '95%' from left
vPosition: 0.5, // 0.0-1.0 or '50%' from top vPosition: 0, // 0.0-1.0 or '50%' from top
appearance: { appearance: {
style: 'pill', // 'pill', 'square', 'circle', 'minimal' style: 'square', // 'pill', 'square', 'circle', 'minimal'
theme: 'auto', // 'auto' follows main theme // icon: '[LOGIN]', // Now uses text-based icons like [LOGIN], [KEY], [NET]
icon: '[LOGIN]', // Now uses text-based icons like [LOGIN], [KEY], [NET]
text: 'Login' text: 'Login'
}, },
behavior: { behavior: {

184
examples/sign.html Normal file
View File

@@ -0,0 +1,184 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>NIP-07 Signing Test</title>
</head>
<body>
<div>
<div id="status"></div>
<div id="test-section" style="display:none;">
<button id="sign-button">Sign Event</button>
<button id="encrypt-button">Test NIP-04 Encrypt</button>
<button id="decrypt-button">Test NIP-04 Decrypt</button>
<div id="results"></div>
</div>
</div>
<script src="../lite/nostr.bundle.js"></script>
<script src="../lite/nostr-lite.js"></script>
<script>
let testPubkey = 'npub1damus9dqe7g7jqn45kjcjgsv0vxjqnk8cxjkf8gqjwm8t8qjm7cqm3z7l';
let ciphertext = '';
document.addEventListener('DOMContentLoaded', async () => {
await window.NOSTR_LOGIN_LITE.init({
theme: 'default',
methods: {
extension: true,
local: true,
readonly: true,
connect: true,
remote: true,
otp: true
},
floatingTab: {
enabled: true,
hPosition: 1, // 0.0-1.0 or '95%' from left
vPosition: 0, // 0.0-1.0 or '50%' from top
appearance: {
style: 'pill', // 'pill', 'square', 'circle', 'minimal'
icon: '', // Clean display without icon placeholders
text: 'Login'
},
behavior: {
hideWhenAuthenticated: false,
showUserInfo: true,
autoSlide: true
},
getUserInfo: true, // Enable profile fetching
getUserRelay: [ // Specific relays for profile fetching
'wss://relay.laantungir.net'
]
}});
// document.getElementById('login-button').addEventListener('click', () => {
// window.NOSTR_LOGIN_LITE.launch('login');
// });
window.addEventListener('nlMethodSelected', (event) => {
document.getElementById('status').textContent = `Authenticated with: ${event.detail.method}`;
document.getElementById('test-section').style.display = 'block';
});
document.getElementById('sign-button').addEventListener('click', testSigning);
document.getElementById('encrypt-button').addEventListener('click', testEncryption);
document.getElementById('decrypt-button').addEventListener('click', testDecryption);
});
async function testSigning() {
try {
console.log('=== DEBUGGING SIGN EVENT START ===');
console.log('testSigning: window.nostr is:', window.nostr);
console.log('testSigning: window.nostr constructor:', window.nostr?.constructor?.name);
console.log('testSigning: window.nostr === our facade?', window.nostr?.constructor?.name === 'WindowNostr');
// Get user public key for comparison
const userPubkey = await window.nostr.getPublicKey();
console.log('User public key:', userPubkey);
// Check auth state if our facade
if (window.nostr?.constructor?.name === 'WindowNostr') {
console.log('WindowNostr authState:', window.nostr.authState);
console.log('WindowNostr authenticatedExtension:', window.nostr.authenticatedExtension);
console.log('WindowNostr existingNostr:', window.nostr.existingNostr);
}
const event = {
kind: 1,
content: 'Hello from NIP-07!',
tags: [],
created_at: Math.floor(Date.now() / 1000)
};
console.log('=== EVENT BEING SENT TO EXTENSION ===');
console.log('Event object:', JSON.stringify(event, null, 2));
console.log('Event keys:', Object.keys(event));
console.log('Event kind type:', typeof event.kind, event.kind);
console.log('Event content type:', typeof event.content, event.content);
console.log('Event tags type:', typeof event.tags, event.tags);
console.log('Event created_at type:', typeof event.created_at, event.created_at);
console.log('Event created_at value:', event.created_at);
// Check if created_at is within reasonable bounds
const now = Math.floor(Date.now() / 1000);
const timeDiff = Math.abs(event.created_at - now);
console.log('Time difference from now (seconds):', timeDiff);
console.log('Event timestamp as Date:', new Date(event.created_at * 1000));
// Additional debugging for user-specific issues
console.log('=== USER-SPECIFIC DEBUG INFO ===');
console.log('User pubkey length:', userPubkey?.length);
console.log('User pubkey format check (hex):', /^[a-fA-F0-9]{64}$/.test(userPubkey));
// Try to get user profile info if available
try {
const profileEvent = {
kinds: [0],
authors: [userPubkey],
limit: 1
};
console.log('Would query profile with filter:', profileEvent);
} catch (profileErr) {
console.log('Profile query setup failed:', profileErr);
}
console.log('=== ABOUT TO CALL EXTENSION SIGN EVENT ===');
const signedEvent = await window.nostr.signEvent(event);
console.log('=== SIGN EVENT SUCCESSFUL ===');
console.log('Signed event:', JSON.stringify(signedEvent, null, 2));
console.log('Signed event keys:', Object.keys(signedEvent));
console.log('Signature present:', !!signedEvent.sig);
console.log('ID present:', !!signedEvent.id);
console.log('Pubkey matches user:', signedEvent.pubkey === userPubkey);
document.getElementById('results').innerHTML = `<h3>Signed Event:</h3><pre>${JSON.stringify(signedEvent, null, 2)}</pre>`;
console.log('=== DEBUGGING SIGN EVENT END ===');
} catch (error) {
console.error('=== SIGN EVENT ERROR ===');
console.error('Error message:', error.message);
console.error('Error stack:', error.stack);
console.error('Error object:', error);
document.getElementById('results').innerHTML = `<h3>Sign Error:</h3><pre>${error.message}</pre><pre>${error.stack}</pre>`;
}
}
async function testEncryption() {
try {
const plaintext = 'Secret message for testing';
const pubkey = await window.nostr.getPublicKey();
ciphertext = await window.nostr.nip04.encrypt(pubkey, plaintext);
document.getElementById('results').innerHTML = `<h3>Encrypted:</h3><pre>${ciphertext}</pre>`;
} catch (error) {
document.getElementById('results').innerHTML = `<h3>Encrypt Error:</h3><pre>${error.message}</pre>`;
}
}
async function testDecryption() {
try {
if (!ciphertext) {
document.getElementById('results').innerHTML = `<h3>Decrypt Error:</h3><pre>No ciphertext available. Run encrypt first.</pre>`;
return;
}
const pubkey = await window.nostr.getPublicKey();
const decrypted = await window.nostr.nip04.decrypt(pubkey, ciphertext);
document.getElementById('results').innerHTML = `<h3>Decrypted:</h3><pre>${decrypted}</pre>`;
} catch (error) {
document.getElementById('results').innerHTML = `<h3>Decrypt Error:</h3><pre>${error.message}</pre>`;
}
}
</script>
</body>
</html>

View File

@@ -1200,16 +1200,149 @@ class NostrLite {
_setupWindowNostrFacade() { _setupWindowNostrFacade() {
if (typeof window !== 'undefined') { 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) // Store existing window.nostr if it exists (from extensions)
const existingNostr = window.nostr; const existingNostr = window.nostr;
// Always install our facade // TRUE SINGLE-EXTENSION ARCHITECTURE: Don't install facade when extensions detected
window.nostr = new WindowNostr(this, existingNostr); if (this._isRealExtension(existingNostr)) {
console.log('NOSTR_LOGIN_LITE: window.nostr facade installed', console.log('NOSTR_LOGIN_LITE: ✓ REAL EXTENSION DETECTED IMMEDIATELY - PRESERVING WITHOUT FACADE');
existingNostr ? '(with extension passthrough)' : '(no existing extension)'); 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') { launch(startScreen = 'login') {
console.log('NOSTR_LOGIN_LITE: Launching with screen:', startScreen); console.log('NOSTR_LOGIN_LITE: Launching with screen:', startScreen);

View File

@@ -8,7 +8,7 @@
* Two-file architecture: * Two-file architecture:
* 1. Load nostr.bundle.js (official nostr-tools bundle) * 1. Load nostr.bundle.js (official nostr-tools bundle)
* 2. Load nostr-lite.js (this file - NOSTR_LOGIN_LITE library with CSS-only themes) * 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 // Verify dependencies are loaded
@@ -1128,23 +1128,62 @@ class Modal {
} }
_handleExtension() { _handleExtension() {
// Detect all available real extensions // SIMPLIFIED ARCHITECTURE: Check for single extension at window.nostr or preserved extension
const availableExtensions = this._detectAllExtensions(); let extension = null;
console.log(`Modal: Found ${availableExtensions.length} extensions:`, availableExtensions.map(e => e.displayName)); // Check if NostrLite instance has a preserved extension (real extension detected at init)
if (window.NOSTR_LOGIN_LITE?._instance?.preservedExtension) {
if (availableExtensions.length === 0) { extension = window.NOSTR_LOGIN_LITE._instance.preservedExtension;
console.log('Modal: No real extensions found'); console.log('Modal: Using preserved extension:', extension.constructor?.name);
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);
} }
// 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() { _detectAllExtensions() {
@@ -1190,17 +1229,38 @@ class Modal {
// Also check window.nostr but be extra careful to avoid our library // Also check window.nostr but be extra careful to avoid our library
console.log('Modal: Checking window.nostr:', !!window.nostr, window.nostr?.constructor?.name); 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({ if (window.nostr) {
name: 'window.nostr', // Check if window.nostr is our WindowNostr facade with a preserved extension
displayName: 'Extension (window.nostr)', if (window.nostr.constructor?.name === 'WindowNostr' && window.nostr.existingNostr) {
icon: '🔑', console.log('Modal: Found WindowNostr facade, checking existingNostr for preserved extension');
extension: window.nostr const preservedExtension = window.nostr.existingNostr;
}); console.log('Modal: Preserved extension:', !!preservedExtension, preservedExtension?.constructor?.name);
seenExtensions.add(window.nostr);
console.log(`Modal: ✓ Detected extension at window.nostr: ${window.nostr.constructor?.name}`); if (preservedExtension && this._isRealExtension(preservedExtension) && !seenExtensions.has(preservedExtension)) {
} else if (window.nostr) { extensions.push({
console.log(`Modal: ✗ Filtered out window.nostr (${window.nostr.constructor?.name}) - likely our library`); 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; return extensions;
@@ -1790,6 +1850,63 @@ class Modal {
} }
_setAuthMethod(method, options = {}) { _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 // Emit auth method selection
const event = new CustomEvent('nlMethodSelected', { const event = new CustomEvent('nlMethodSelected', {
detail: { method, ...options } detail: { method, ...options }
@@ -1823,8 +1940,13 @@ class Modal {
title.style.cssText = 'margin: 0 0 16px 0; font-size: 18px; font-weight: 600;'; title.style.cssText = 'margin: 0 0 16px 0; font-size: 18px; font-weight: 600;';
const message = document.createElement('p'); const message = document.createElement('p');
message.textContent = 'Please install a Nostr browser extension like Alby or getflattr and refresh the page.'; message.innerHTML = `
message.style.cssText = 'margin-bottom: 20px; color: #6b7280;'; Please install a Nostr browser extension and refresh the page.<br><br>
<strong>Important:</strong> If you have multiple extensions installed, please disable all but one to avoid conflicts.
<br><br>
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'); const backButton = document.createElement('button');
backButton.textContent = 'Back'; backButton.textContent = 'Back';
@@ -1867,27 +1989,11 @@ class Modal {
box-sizing: border-box; box-sizing: border-box;
`; `;
const urlLabel = document.createElement('label'); // Users will enter the complete bunker connection string with relay info
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
const connectButton = document.createElement('button'); const connectButton = document.createElement('button');
connectButton.textContent = 'Connect to Bunker'; connectButton.textContent = 'Connect to Bunker';
connectButton.onclick = () => this._handleNip46Connect(pubkeyInput.value, urlInput.value); connectButton.onclick = () => this._handleNip46Connect(pubkeyInput.value);
connectButton.style.cssText = this._getButtonStyle(); connectButton.style.cssText = this._getButtonStyle();
const backButton = document.createElement('button'); const backButton = document.createElement('button');
@@ -1897,8 +2003,6 @@ class Modal {
formGroup.appendChild(label); formGroup.appendChild(label);
formGroup.appendChild(pubkeyInput); formGroup.appendChild(pubkeyInput);
formGroup.appendChild(urlLabel);
formGroup.appendChild(urlInput);
this.modalBody.appendChild(title); this.modalBody.appendChild(title);
this.modalBody.appendChild(description); this.modalBody.appendChild(description);
@@ -1907,17 +2011,17 @@ class Modal {
this.modalBody.appendChild(backButton); this.modalBody.appendChild(backButton);
} }
_handleNip46Connect(bunkerPubkey, bunkerUrl) { _handleNip46Connect(bunkerPubkey) {
if (!bunkerPubkey || !bunkerPubkey.length) { if (!bunkerPubkey || !bunkerPubkey.length) {
this._showError('Bunker pubkey is required'); this._showError('Bunker pubkey is required');
return; return;
} }
this._showNip46Connecting(bunkerPubkey, bunkerUrl); this._showNip46Connecting(bunkerPubkey);
this._performNip46Connect(bunkerPubkey, bunkerUrl); this._performNip46Connect(bunkerPubkey);
} }
_showNip46Connecting(bunkerPubkey, bunkerUrl) { _showNip46Connecting(bunkerPubkey) {
this.modalBody.innerHTML = ''; this.modalBody.innerHTML = '';
const title = document.createElement('h3'); 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.style.cssText = 'background: #f1f5f9; padding: 12px; border-radius: 6px; margin-bottom: 20px; font-size: 14px;';
bunkerInfo.innerHTML = ` bunkerInfo.innerHTML = `
<strong>Connecting to bunker:</strong><br> <strong>Connecting to bunker:</strong><br>
Pubkey: <code style="word-break: break-all;">${displayPubkey}</code><br> Connection: <code style="word-break: break-all;">${displayPubkey}</code><br>
Relay: <code style="word-break: break-all;">${bunkerUrl || 'ws://localhost:8080'}</code><br> <small style="color: #6b7280;">Connection string contains all necessary relay information.</small>
<small style="color: #6b7280;">If this relay is offline, the bunker server may be unavailable.</small>
`; `;
const connectingDiv = document.createElement('div'); const connectingDiv = document.createElement('div');
@@ -1954,9 +2057,9 @@ class Modal {
this.modalBody.appendChild(connectingDiv); this.modalBody.appendChild(connectingDiv);
} }
async _performNip46Connect(bunkerPubkey, bunkerUrl) { async _performNip46Connect(bunkerPubkey) {
try { 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 // Check if nostr-tools NIP-46 is available
if (!window.NostrTools?.nip46) { if (!window.NostrTools?.nip46) {
@@ -2648,16 +2751,149 @@ class NostrLite {
_setupWindowNostrFacade() { _setupWindowNostrFacade() {
if (typeof window !== 'undefined') { 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) // Store existing window.nostr if it exists (from extensions)
const existingNostr = window.nostr; const existingNostr = window.nostr;
// Always install our facade // TRUE SINGLE-EXTENSION ARCHITECTURE: Don't install facade when extensions detected
window.nostr = new WindowNostr(this, existingNostr); if (this._isRealExtension(existingNostr)) {
console.log('NOSTR_LOGIN_LITE: window.nostr facade installed', console.log('NOSTR_LOGIN_LITE: ✓ REAL EXTENSION DETECTED IMMEDIATELY - PRESERVING WITHOUT FACADE');
existingNostr ? '(with extension passthrough)' : '(no existing extension)'); 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') { launch(startScreen = 'login') {
console.log('NOSTR_LOGIN_LITE: Launching with screen:', startScreen); console.log('NOSTR_LOGIN_LITE: Launching with screen:', startScreen);

View File

@@ -332,23 +332,62 @@ class Modal {
} }
_handleExtension() { _handleExtension() {
// Detect all available real extensions // SIMPLIFIED ARCHITECTURE: Check for single extension at window.nostr or preserved extension
const availableExtensions = this._detectAllExtensions(); let extension = null;
console.log(`Modal: Found ${availableExtensions.length} extensions:`, availableExtensions.map(e => e.displayName)); // Check if NostrLite instance has a preserved extension (real extension detected at init)
if (window.NOSTR_LOGIN_LITE?._instance?.preservedExtension) {
if (availableExtensions.length === 0) { extension = window.NOSTR_LOGIN_LITE._instance.preservedExtension;
console.log('Modal: No real extensions found'); console.log('Modal: Using preserved extension:', extension.constructor?.name);
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);
} }
// 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() { _detectAllExtensions() {
@@ -394,17 +433,38 @@ class Modal {
// Also check window.nostr but be extra careful to avoid our library // Also check window.nostr but be extra careful to avoid our library
console.log('Modal: Checking window.nostr:', !!window.nostr, window.nostr?.constructor?.name); 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({ if (window.nostr) {
name: 'window.nostr', // Check if window.nostr is our WindowNostr facade with a preserved extension
displayName: 'Extension (window.nostr)', if (window.nostr.constructor?.name === 'WindowNostr' && window.nostr.existingNostr) {
icon: '🔑', console.log('Modal: Found WindowNostr facade, checking existingNostr for preserved extension');
extension: window.nostr const preservedExtension = window.nostr.existingNostr;
}); console.log('Modal: Preserved extension:', !!preservedExtension, preservedExtension?.constructor?.name);
seenExtensions.add(window.nostr);
console.log(`Modal: ✓ Detected extension at window.nostr: ${window.nostr.constructor?.name}`); if (preservedExtension && this._isRealExtension(preservedExtension) && !seenExtensions.has(preservedExtension)) {
} else if (window.nostr) { extensions.push({
console.log(`Modal: ✗ Filtered out window.nostr (${window.nostr.constructor?.name}) - likely our library`); 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; return extensions;
@@ -994,6 +1054,63 @@ class Modal {
} }
_setAuthMethod(method, options = {}) { _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 // Emit auth method selection
const event = new CustomEvent('nlMethodSelected', { const event = new CustomEvent('nlMethodSelected', {
detail: { method, ...options } detail: { method, ...options }
@@ -1027,8 +1144,13 @@ class Modal {
title.style.cssText = 'margin: 0 0 16px 0; font-size: 18px; font-weight: 600;'; title.style.cssText = 'margin: 0 0 16px 0; font-size: 18px; font-weight: 600;';
const message = document.createElement('p'); const message = document.createElement('p');
message.textContent = 'Please install a Nostr browser extension like Alby or getflattr and refresh the page.'; message.innerHTML = `
message.style.cssText = 'margin-bottom: 20px; color: #6b7280;'; Please install a Nostr browser extension and refresh the page.<br><br>
<strong>Important:</strong> If you have multiple extensions installed, please disable all but one to avoid conflicts.
<br><br>
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'); const backButton = document.createElement('button');
backButton.textContent = 'Back'; backButton.textContent = 'Back';
@@ -1071,27 +1193,11 @@ class Modal {
box-sizing: border-box; box-sizing: border-box;
`; `;
const urlLabel = document.createElement('label'); // Users will enter the complete bunker connection string with relay info
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
const connectButton = document.createElement('button'); const connectButton = document.createElement('button');
connectButton.textContent = 'Connect to Bunker'; connectButton.textContent = 'Connect to Bunker';
connectButton.onclick = () => this._handleNip46Connect(pubkeyInput.value, urlInput.value); connectButton.onclick = () => this._handleNip46Connect(pubkeyInput.value);
connectButton.style.cssText = this._getButtonStyle(); connectButton.style.cssText = this._getButtonStyle();
const backButton = document.createElement('button'); const backButton = document.createElement('button');
@@ -1101,8 +1207,6 @@ class Modal {
formGroup.appendChild(label); formGroup.appendChild(label);
formGroup.appendChild(pubkeyInput); formGroup.appendChild(pubkeyInput);
formGroup.appendChild(urlLabel);
formGroup.appendChild(urlInput);
this.modalBody.appendChild(title); this.modalBody.appendChild(title);
this.modalBody.appendChild(description); this.modalBody.appendChild(description);
@@ -1111,17 +1215,17 @@ class Modal {
this.modalBody.appendChild(backButton); this.modalBody.appendChild(backButton);
} }
_handleNip46Connect(bunkerPubkey, bunkerUrl) { _handleNip46Connect(bunkerPubkey) {
if (!bunkerPubkey || !bunkerPubkey.length) { if (!bunkerPubkey || !bunkerPubkey.length) {
this._showError('Bunker pubkey is required'); this._showError('Bunker pubkey is required');
return; return;
} }
this._showNip46Connecting(bunkerPubkey, bunkerUrl); this._showNip46Connecting(bunkerPubkey);
this._performNip46Connect(bunkerPubkey, bunkerUrl); this._performNip46Connect(bunkerPubkey);
} }
_showNip46Connecting(bunkerPubkey, bunkerUrl) { _showNip46Connecting(bunkerPubkey) {
this.modalBody.innerHTML = ''; this.modalBody.innerHTML = '';
const title = document.createElement('h3'); 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.style.cssText = 'background: #f1f5f9; padding: 12px; border-radius: 6px; margin-bottom: 20px; font-size: 14px;';
bunkerInfo.innerHTML = ` bunkerInfo.innerHTML = `
<strong>Connecting to bunker:</strong><br> <strong>Connecting to bunker:</strong><br>
Pubkey: <code style="word-break: break-all;">${displayPubkey}</code><br> Connection: <code style="word-break: break-all;">${displayPubkey}</code><br>
Relay: <code style="word-break: break-all;">${bunkerUrl || 'ws://localhost:8080'}</code><br> <small style="color: #6b7280;">Connection string contains all necessary relay information.</small>
<small style="color: #6b7280;">If this relay is offline, the bunker server may be unavailable.</small>
`; `;
const connectingDiv = document.createElement('div'); const connectingDiv = document.createElement('div');
@@ -1158,9 +1261,9 @@ class Modal {
this.modalBody.appendChild(connectingDiv); this.modalBody.appendChild(connectingDiv);
} }
async _performNip46Connect(bunkerPubkey, bunkerUrl) { async _performNip46Connect(bunkerPubkey) {
try { 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 // Check if nostr-tools NIP-46 is available
if (!window.NostrTools?.nip46) { if (!window.NostrTools?.nip46) {