Still don't have dm working because I can't decrypt at primal.

This commit is contained in:
Your Name
2025-09-13 14:36:21 -04:00
parent bad361a686
commit 72b0d9b102
144 changed files with 5808 additions and 26105 deletions

315
lite/build.js Normal file
View File

@@ -0,0 +1,315 @@
/**
* Simple script to create NOSTR_LOGIN_LITE bundle
* For the new two-file architecture:
* 1. nostr.bundle.js (official nostr-tools bundle - static file)
* 2. nip46-extension.js (NIP-46 extension - static file)
* 3. nostr-lite.js (NOSTR_LOGIN_LITE library - built by this script)
*/
const fs = require('fs');
const path = require('path');
function createNostrLoginLiteBundle() {
console.log('🔧 Creating NOSTR_LOGIN_LITE bundle for two-file architecture...');
const outputPath = path.join(__dirname, 'nostr-lite.js');
// Remove old bundle
try {
if (fs.existsSync(outputPath)) {
fs.unlinkSync(outputPath);
}
} catch (e) {
console.log('No old bundle to remove');
}
// Start with the bundle header
let bundle = `/**
* NOSTR_LOGIN_LITE - Authentication Library
* Two-file architecture:
* 1. Load nostr.bundle.js (official nostr-tools bundle)
* 2. Load nip46-extension.js (extends NostrTools with NIP-46)
* 3. Load nostr-lite.js (this file - NOSTR_LOGIN_LITE library)
* Generated on: ${new Date().toISOString()}
*/
// Verify dependencies are loaded
if (typeof window !== 'undefined') {
if (!window.NostrTools) {
console.error('NOSTR_LOGIN_LITE: nostr.bundle.js must be loaded first');
throw new Error('Missing dependency: nostr.bundle.js');
}
if (!window.NostrTools.nip46) {
console.error('NOSTR_LOGIN_LITE: nip46-extension.js must be loaded after nostr.bundle.js');
throw new Error('Missing dependency: nip46-extension.js');
}
console.log('NOSTR_LOGIN_LITE: Dependencies verified ✓');
console.log('NOSTR_LOGIN_LITE: NostrTools available with keys:', Object.keys(window.NostrTools));
console.log('NOSTR_LOGIN_LITE: NIP-46 available:', !!window.NostrTools.nip46);
}
// ======================================
// NOSTR_LOGIN_LITE Components
// ======================================
`;
// Add Modal UI
const modalPath = path.join(__dirname, 'ui/modal.js');
if (fs.existsSync(modalPath)) {
console.log('📄 Adding Modal UI...');
let modalContent = fs.readFileSync(modalPath, 'utf8');
// Skip header comments
let lines = modalContent.split('\n');
let contentStartIndex = 0;
for (let i = 0; i < Math.min(15, lines.length); i++) {
const line = lines[i].trim();
if (line.startsWith('/**') || line.startsWith('*') ||
line.startsWith('/*') || line.startsWith('//')) {
contentStartIndex = i + 1;
} else if (line && !line.startsWith('*') && !line.startsWith('//')) {
break;
}
}
if (contentStartIndex > 0) {
lines = lines.slice(contentStartIndex);
}
bundle += `// ======================================\n`;
bundle += `// Modal UI Component\n`;
bundle += `// ======================================\n\n`;
bundle += lines.join('\n');
bundle += '\n\n';
} else {
console.warn('⚠️ Modal UI not found: ui/modal.js');
}
// Add main library code
console.log('📄 Adding Main Library...');
bundle += `
// ======================================
// Main NOSTR_LOGIN_LITE Library
// ======================================
// Extension Bridge for managing browser extensions
class ExtensionBridge {
constructor() {
this.extensions = new Map();
this.primaryExtension = null;
this._detectExtensions();
}
_detectExtensions() {
// Common extension locations
const locations = [
{ path: 'window.nostr', name: 'Generic' },
{ path: 'window.alby?.nostr', name: 'Alby' },
{ path: 'window.nos2x?.nostr', name: 'nos2x' },
{ path: 'window.flamingo?.nostr', name: 'Flamingo' },
{ path: 'window.getAlby?.nostr', name: 'Alby Legacy' },
{ path: 'window.mutiny?.nostr', name: 'Mutiny' }
];
for (const location of locations) {
try {
const obj = eval(location.path);
if (obj && typeof obj.getPublicKey === 'function') {
this.extensions.set(location.name, {
name: location.name,
extension: obj,
constructor: obj.constructor?.name || 'Unknown'
});
if (!this.primaryExtension) {
this.primaryExtension = this.extensions.get(location.name);
}
}
} catch (e) {
// Extension not available
}
}
}
getAllExtensions() {
return Array.from(this.extensions.values());
}
getExtensionCount() {
return this.extensions.size;
}
}
// Main NostrLite class
class NostrLite {
constructor() {
this.options = {};
this.extensionBridge = new ExtensionBridge();
this.initialized = false;
}
async init(options = {}) {
console.log('NOSTR_LOGIN_LITE: Initializing with options:', options);
this.options = {
theme: 'light',
darkMode: false,
relays: ['wss://relay.damus.io', 'wss://nos.lol'],
methods: {
extension: true,
local: true,
readonly: true,
connect: false,
otp: false
},
...options
};
// Set up window.nostr facade if no extension detected
if (this.extensionBridge.getExtensionCount() === 0) {
this._setupWindowNostrFacade();
}
this.initialized = true;
console.log('NOSTR_LOGIN_LITE: Initialization complete');
return this;
}
_setupWindowNostrFacade() {
if (typeof window !== 'undefined' && !window.nostr) {
window.nostr = new WindowNostr(this);
console.log('NOSTR_LOGIN_LITE: window.nostr facade installed');
}
}
launch(startScreen = 'login') {
console.log('NOSTR_LOGIN_LITE: Launching with screen:', startScreen);
if (typeof Modal !== 'undefined') {
const modal = new Modal(this.options);
modal.open({ startScreen });
} else {
console.error('NOSTR_LOGIN_LITE: Modal component not available');
}
}
logout() {
console.log('NOSTR_LOGIN_LITE: Logout called');
// Clear stored data
if (typeof localStorage !== 'undefined') {
localStorage.removeItem('nl_current');
}
// Dispatch logout event
if (typeof window !== 'undefined') {
window.dispatchEvent(new CustomEvent('nlLogout', {
detail: { timestamp: Date.now() }
}));
}
}
}
// Window.nostr facade for when no extension is available
class WindowNostr {
constructor(nostrLite) {
this.nostrLite = nostrLite;
}
async getPublicKey() {
throw new Error('Authentication required - use NOSTR_LOGIN_LITE.launch()');
}
async signEvent(event) {
throw new Error('Authentication required - use NOSTR_LOGIN_LITE.launch()');
}
async getRelays() {
throw new Error('Authentication required - use NOSTR_LOGIN_LITE.launch()');
}
get nip04() {
return {
async encrypt(pubkey, plaintext) {
throw new Error('Authentication required - use NOSTR_LOGIN_LITE.launch()');
},
async decrypt(pubkey, ciphertext) {
throw new Error('Authentication required - use NOSTR_LOGIN_LITE.launch()');
}
};
}
get nip44() {
return {
async encrypt(pubkey, plaintext) {
throw new Error('Authentication required - use NOSTR_LOGIN_LITE.launch()');
},
async decrypt(pubkey, ciphertext) {
throw new Error('Authentication required - use NOSTR_LOGIN_LITE.launch()');
}
};
}
}
// Initialize and export
if (typeof window !== 'undefined') {
const nostrLite = new NostrLite();
// Export main API
window.NOSTR_LOGIN_LITE = {
init: (options) => nostrLite.init(options),
launch: (startScreen) => nostrLite.launch(startScreen),
logout: () => nostrLite.logout(),
// Expose for debugging
_extensionBridge: nostrLite.extensionBridge,
_instance: nostrLite
};
console.log('NOSTR_LOGIN_LITE: Library loaded and ready');
console.log('NOSTR_LOGIN_LITE: Use window.NOSTR_LOGIN_LITE.init(options) to initialize');
console.log('NOSTR_LOGIN_LITE: Detected', nostrLite.extensionBridge.getExtensionCount(), 'browser extensions');
} else {
// Node.js environment
module.exports = { NostrLite };
}
`;
// Write the complete bundle
fs.writeFileSync(outputPath, bundle, 'utf8');
const sizeKB = (bundle.length / 1024).toFixed(2);
console.log(`\n✅ nostr-lite.js bundle created: ${outputPath}`);
console.log(`📏 Bundle size: ${sizeKB} KB`);
console.log(`📄 Total lines: ${bundle.split('\n').length}`);
// Check what's included
const hasModal = bundle.includes('class Modal');
const hasNostrLite = bundle.includes('NOSTR_LOGIN_LITE');
console.log('\n📋 Bundle contents:');
console.log(` Modal UI: ${hasModal ? '✅ Included' : '❌ Missing'}`);
console.log(` NOSTR_LOGIN_LITE: ${hasNostrLite ? '✅ Included' : '❌ Missing'}`);
console.log(` Extension Bridge: ✅ Included`);
console.log(` Window.nostr facade: ✅ Included`);
console.log('\n📋 Two-file architecture:');
console.log(' 1. nostr.bundle.js (official nostr-tools - 220KB)');
console.log(' 2. nip46-extension.js (NIP-46 support - ~15KB)');
console.log(` 3. nostr-lite.js (NOSTR_LOGIN_LITE - ${sizeKB}KB)`);
return bundle;
}
// Run if called directly
if (typeof require !== 'undefined' && require.main === module) {
createNostrLoginLiteBundle();
}
module.exports = { createNostrLoginLiteBundle };

View File

@@ -1,97 +0,0 @@
/**
* Clean bundler for NOSTR_LOGIN_LITE
* Removes problematic files and recreates bundle
*/
const fs = require('fs');
const path = require('path');
async function createCleanBundle() {
// First, remove the old bundle if it exists
const outputPath = path.join(__dirname, 'nostr-login-lite.bundle.js');
try {
if (fs.existsSync(outputPath)) {
fs.unlinkSync(outputPath);
}
} catch (e) {
console.log('No old bundle to remove');
}
const mainFile = path.join(__dirname, 'nostr-login-lite.js');
const nip46File = path.join(__dirname, 'core/nip46-client.js');
const modalFile = path.join(__dirname, 'ui/modal.js');
// Start with a clean header
let bundle = `/**
* NOSTR_LOGIN_LITE
* Single-file Nostr authentication library
* Generated on: ${new Date().toISOString()}
*/
`;
// Add section markers and combine files
const files = [
{ path: modalFile, name: 'modal.js' },
{ path: nip46File, name: 'nip46-client.js' },
{ path: mainFile, name: 'nostr-login-lite.js' }
];
for (const file of files) {
if (fs.existsSync(file.path)) {
const content = fs.readFileSync(file.path, 'utf8');
bundle += `\n// ======================================\n`;
bundle += `// ${file.name}\n`;
bundle += `// ======================================\n\n`;
// Clean the content by removing initial header comments
let lines = content.split('\n');
let contentStartIndex = 0;
// Skip the first 10 lines if they contain file headers
for (let i = 0; i < Math.min(10, lines.length); i++) {
const line = lines[i].trim();
if (line.startsWith('/**') || line.startsWith('*') || line.startsWith('/*') || line.startsWith('//') ||
line.includes('Copyright') || line.includes('@license') || line.includes('Licensed') || line.includes('©')) {
contentStartIndex = i + 1;
}
}
if (contentStartIndex > 0) {
lines = lines.slice(contentStartIndex);
}
bundle += lines.join('\n');
bundle += '\n\n';
console.log(`Added ${file.name}`);
} else {
console.warn(`File not found: ${file.path}`);
}
}
// Write the bundled file
fs.writeFileSync(outputPath, bundle, 'utf8');
const sizeKB = (bundle.length / 1024).toFixed(2);
console.log(`\n✅ Clean bundle created: ${outputPath}`);
console.log(`📏 Bundle size: ${sizeKB} KB`);
console.log(`📄 Total lines: ${bundle.split('\n').length}`);
// Verify the bundle starts correctly
const firstLines = bundle.split('\n').slice(0, 20).join('\n');
console.log('\n📋 First 20 lines:');
console.log(firstLines);
return bundle;
}
if (typeof module !== 'undefined' && module.exports) {
module.exports = { createCleanBundle };
}
// Run if called directly
if (typeof require !== 'undefined' && require.main === module) {
createCleanBundle().catch(console.error);
}

View File

@@ -1,86 +0,0 @@
/**
* Simple bundler for NOSTR_LOGIN_LITE
* Combines all files into a single distributable script
*/
const fs = require('fs');
const path = require('path');
async function bundleLite() {
const mainFile = path.join(__dirname, 'nostr-login-lite.js');
const nip46File = path.join(__dirname, 'core/nip46-client.js');
const modalFile = path.join(__dirname, 'ui/modal.js');
let bundle = `/**
* NOSTR_LOGIN_LITE
* Single-file Nostr authentication library
* Generated on: ${new Date().toISOString()}
*/
// ======================================
// Core Classes and Components
// ======================================
`;
// Read and combine files
const files = [modalFile, nip46File, mainFile];
for (const file of files) {
if (fs.existsSync(file)) {
let content = fs.readFileSync(file, 'utf8');
// Skip the initial comment and license if present
let lines = content.split('\n');
// Find and skip complete JSDoc blocks at the beginning
let skipUntil = 0;
let inJSDocBlock = false;
for (let i = 0; i < lines.length; i++) {
const line = lines[i].trim();
if (line.startsWith('/**')) {
inJSDocBlock = true;
skipUntil = i;
} else if (inJSDocBlock && line.startsWith('*/')) {
skipUntil = i;
break;
} else if (i < 10 && (line.startsWith('const') || line.startsWith('class') || line.startsWith('function'))) {
// Hit actual code before finding end of JSDoc block
inJSDocBlock = false;
break;
}
}
if (inJSDocBlock) {
lines = lines.slice(skipUntil + 1); // Skip the entire JSDoc block
} else {
// Fallback to old filtering (skip comment-like lines in first 10)
lines = lines.filter((line, index) => {
return index >= 10 || !line.trim().startsWith('*') && !line.trim().startsWith('//');
});
}
bundle += '\n// ======================================\n';
bundle += `// ${path.basename(file)}\n`;
bundle += '// ======================================\n\n';
bundle += lines.join('\n');
bundle += '\n\n';
}
}
// Write the bundled file
const outputPath = path.join(__dirname, 'nostr-login-lite.bundle.js');
fs.writeFileSync(outputPath, bundle);
console.log('Bundle created:', outputPath);
console.log('Bundle size:', (bundle.length / 1024).toFixed(2), 'KB');
}
if (typeof module !== 'undefined' && module.exports) {
module.exports = { bundleLite };
}
// Run if called directly
if (typeof require !== 'undefined' && require.main === module) {
bundleLite().catch(console.error);
}

View File

@@ -1,398 +0,0 @@
/**
* NOSTR NIP-46 Client Implementation
* Minimal RPC over NostrTools.SimplePool for NOSTR_LOGIN_LITE
*/
class NIP46Client {
constructor() {
this.pool = null;
this.localSk = null;
this.localPk = null;
this.remotePk = null;
this.relays = [];
this.sub = null;
this.pendingRequests = {};
this.useNip44 = false;
this.iframeOrigin = null;
this.iframePort = null;
}
init(localSk, remotePk, relays, iframeOrigin) {
// Create SimplePool
this.pool = new window.NostrTools.SimplePool();
// Setup keys
this.localSk = localSk;
if (this.localSk) {
this.localPk = window.NostrTools.getPublicKey(this.localSk);
}
this.remotePk = remotePk;
this.relays = [...relays];
// Store iframe origin for future use
this.iframeOrigin = iframeOrigin;
console.log('NIP46Client initialized for', this.remotePk ? 'remote signer' : 'listening mode');
}
setUseNip44(use) {
this.useNip44 = use;
}
subscribeReplies() {
if (!this.pool || !this.localPk) return;
// Subscribe to replies to our pubkey on kind 24133 (NIP-46 methods)
this.sub = this.pool.sub(this.relays, [{
kinds: [24133],
'#p': [this.localPk]
}]);
this.sub.on('event', (event) => this.onEvent(event));
this.sub.on('eose', () => {
console.log('NIP-46 subscription caught up');
});
console.log('Subscribed to NIP-46 replies on relays:', this.relays);
}
unsubscribe() {
if (this.sub) {
this.sub.unsub();
this.sub = null;
}
}
async onEvent(event) {
console.log('NIP-46 event received:', event);
try {
const parsed = await this.parseEvent(event);
if (parsed) {
if (parsed.id && this.pendingRequests[parsed.id]) {
// Handle response
const handler = this.pendingRequests[parsed.id];
delete this.pendingRequests[parsed.id];
if (parsed.result !== undefined) {
handler.resolve(parsed.result);
} else if (parsed.error) {
handler.reject(new Error(parsed.error));
} else {
handler.reject(new Error('Invalid response format'));
}
} else if (parsed.method === 'auth_url') {
// Handle auth_url emissions (deduplication required)
this.emitAuthUrlIfNeeded(parsed.params[0]);
}
}
} catch (error) {
console.error('Error processing NIP-46 event:', error);
}
}
emitAuthUrlIfNeeded(url) {
// Deduplicate auth_url emissions - only emit if not recently shown
const lastUrl = sessionStorage.getItem('nl-last-auth-url');
if (lastUrl === url) {
console.log('Auth URL already shown, skipping duplicate:', url);
return;
}
sessionStorage.setItem('nl-last-auth-url', url);
console.log('New auth URL:', url);
// Emit event for UI
window.dispatchEvent(new CustomEvent('nlAuthUrl', { detail: { url } }));
}
async parseEvent(event) {
try {
let content = event.content;
// Determine encryption method based on content structure
if (content.length > 44) {
// Likely NIP-44 (encrypted)
if (this.localSk && event.pubkey) {
try {
content = window.NostrTools.nip44?.decrypt(this.localSk, event.pubkey, content);
} catch (e) {
console.warn('NIP-44 decryption failed, trying NIP-04...');
content = await window.NostrTools.nip04.decrypt(this.localSk, event.pubkey, content);
}
}
} else {
// Likely NIP-04
if (this.localSk && event.pubkey) {
content = await window.NostrTools.nip04.decrypt(this.localSk, event.pubkey, content);
}
}
const payload = JSON.parse(content);
console.log('Decrypted NIP-46 payload:', payload);
return {
id: payload.id,
method: payload.method,
params: payload.params,
result: payload.result,
error: payload.error,
event: event
};
} catch (e) {
console.error('Failed to parse event:', e);
return null;
}
}
async listen(nostrConnectSecret) {
return new Promise((resolve, reject) => {
if (!this.localPk) {
reject(new Error('No local pubkey available for listening'));
return;
}
// Subscribe to unsolicited events to our pubkey
let foundSecretOrAck = false;
const listenSub = this.pool.sub(this.relays, [{
kinds: [24133],
'#p': [this.localPk]
}]);
listenSub.on('event', async (event) => {
try {
const parsed = await this.parseEvent(event);
if (parsed && parsed.method === 'connect') {
// Accept if it's an ack or matches our secret
const [userPubkey, token] = parsed.params || [];
if (token === '' && parsed.result === 'ack') {
// Ack received
foundSecretOrAck = true;
listenSub.unsub();
resolve(event.pubkey);
} else if (token === nostrConnectSecret) {
// Secret match
foundSecretOrAck = true;
listenSub.unsub();
resolve(event.pubkey);
}
}
} catch (error) {
console.error('Error in listen mode:', error);
}
});
// Timeout after 5 minutes
setTimeout(() => {
if (!foundSecretOrAck) {
listenSub.unsub();
reject(new Error('Listen timeout - no signer connected'));
}
}, 300000);
});
}
async connect(token, perms) {
return new Promise(async (resolve, reject) => {
try {
const result = await this.sendRequest(
this.remotePk,
'connect',
[this.localPk, token || '', perms || ''],
24133,
(response) => {
if (response === 'ack') {
resolve(true);
} else {
reject(new Error('Connection not acknowledged'));
}
}
);
// Set 30 second timeout
setTimeout(() => reject(new Error('Connection timeout')), 30000);
} catch (error) {
reject(error);
}
});
}
async initUserPubkey(hint) {
if (hint) {
this.remotePk = hint;
return hint;
}
if (!this.remotePk) {
// Request get_public_key
return new Promise(async (resolve, reject) => {
try {
const pubkey = await this.sendRequest(
this.remotePk,
'get_public_key',
[],
24133
);
this.remotePk = pubkey;
resolve(pubkey);
} catch (error) {
reject(error);
}
});
}
return this.remotePk;
}
async sendRequest(remotePubkey, method, params, kind = 24133, cb) {
if (!this.pool || !this.localSk || !this.localPk) {
throw new Error('NIP46Client not properly initialized');
}
if (!remotePubkey) {
throw new Error('No remote pubkey specified');
}
const id = this._generateId();
// Create request event
const event = await this.createRequestEvent(id, remotePubkey, method, params, kind);
console.log('Sending NIP-46 request:', { id, method, params });
// Publish to relays
const pubs = await this.pool.publish(this.relays, event);
console.log('Published to relays, waiting for response...');
return new Promise((resolve, reject) => {
// Set timeout
const timeout = setTimeout(() => {
console.error('NIP-46 request timeout for id:', id);
delete this.pendingRequests[id];
reject(new Error(`Request timeout for ${method}`));
}, 60000); // 1 minute timeout
// Store handler
this.pendingRequests[id] = {
resolve: (result) => {
clearTimeout(timeout);
resolve(result);
},
reject: (error) => {
clearTimeout(timeout);
reject(error);
},
timestamp: Date.now()
};
// If callback provided, override resolve
if (cb) {
const originalResolve = this.pendingRequests[id].resolve;
this.pendingRequests[id].resolve = (result) => {
cb(result);
originalResolve(result);
};
}
});
}
async createRequestEvent(id, remotePubkey, method, params, kind = 24133) {
let content = JSON.stringify({
id,
method,
params
});
// Choose encryption method
let encrypted = content;
if (method !== 'create_account') {
// Use NIP-44 for non-account creation methods if available
if (this.useNip44 && window.NostrTools.nip44) {
encrypted = window.NostrTools.nip44.encrypt(this.localSk, remotePubkey, content);
} else {
// Fallback to NIP-04
encrypted = await window.NostrTools.nip04.encrypt(this.localSk, remotePubkey, content);
}
}
// Create event structure
const event = {
kind: kind,
content: encrypted,
tags: [
['p', remotePubkey]
],
created_at: Math.floor(Date.now() / 1000),
pubkey: this.localPk,
id: '', // Will be set by finalizeEvent
sig: '' // Will be set by finalizeEvent
};
// Sign the event
const signedEvent = window.NostrTools.finalizeEvent(event, this.localSk);
return signedEvent;
}
_generateId() {
return 'nl-' + Date.now() + '-' + Math.random().toString(36).substring(2, 15);
}
setWorkerIframePort(port) {
this.iframePort = port;
// Set up postMessage routing if needed
if (this.iframePort && this.iframeOrigin) {
this.iframePort.onmessage = (event) => {
if (event.origin !== this.iframeOrigin) {
console.warn('Ignoring message from unknown origin:', event.origin);
return;
}
console.log('Received iframe message:', event.data);
// Handle iframe messages
};
// Send keepalive
setInterval(() => {
if (this.iframePort) {
try {
this.iframePort.postMessage({ type: 'ping' });
} catch (e) {
console.warn('Iframe port closed');
this.iframePort = null;
}
}
}, 30000); // 30 seconds
}
}
teardown() {
this.unsubscribe();
if (this.iframePort) {
try {
this.iframePort.close();
} catch (e) {
console.warn('Error closing iframe port:', e);
}
this.iframePort = null;
}
if (this.pool) {
this.pool.close(this.relays);
this.pool = null;
}
// Clear all pending requests
for (const id in this.pendingRequests) {
this.pendingRequests[id].reject(new Error('Client teardown'));
}
this.pendingRequests = {};
}
}

2379
lite/nostr-lite.js Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -165,8 +165,8 @@ class Modal {
});
}
// Nostr Connect option
if (this.options?.methods?.connect !== false) {
// Nostr Connect option (check both 'connect' and 'remote' for compatibility)
if (this.options?.methods?.connect !== false && this.options?.methods?.remote !== false) {
options.push({
type: 'connect',
title: 'Nostr Connect',
@@ -186,7 +186,7 @@ class Modal {
}
// OTP/DM option
if (this.options?.methods?.otp !== false && this.options?.otp) {
if (this.options?.methods?.otp !== false) {
options.push({
type: 'otp',
title: 'DM/OTP',
@@ -858,11 +858,7 @@ class Modal {
box-sizing: border-box;
`;
// Pre-fill with our bunker config if available
if (window.NIP46_BUNKER_CONFIG) {
pubkeyInput.value = window.NIP46_BUNKER_CONFIG.remoteSigner.pubkey;
urlInput.value = window.NIP46_BUNKER_CONFIG.remoteSigner.url;
}
// Users will enter the bunker URL manually from their bunker setup
const connectButton = document.createElement('button');
connectButton.textContent = 'Connect to Bunker';
@@ -956,9 +952,9 @@ class Modal {
const localSecretKey = window.NostrTools.generateSecretKey();
console.log('Generated local client keypair for NIP-46 session');
// Use nostr-tools BunkerSigner.fromBunker() for bunker:// connections
// Use nostr-tools BunkerSigner constructor
console.log('Creating nip46 BunkerSigner...');
const signer = window.NostrTools.nip46.BunkerSigner.fromBunker(localSecretKey, bunkerPointer, {
const signer = new window.NostrTools.nip46.BunkerSigner(localSecretKey, bunkerPointer, {
onauth: (url) => {
console.log('Received auth URL from bunker:', url);
// Open auth URL in popup or redirect
@@ -968,12 +964,8 @@ class Modal {
console.log('NIP-46 BunkerSigner created successfully');
// Attempt initial ping to verify connection
console.log('Testing bunker connection with ping...');
await signer.ping();
console.log('NIP-46 ping successful - bunker is reachable');
// Try to connect (this may trigger auth flow)
// Skip ping test - NIP-46 works through relays, not direct connection
// Try to connect directly (this may trigger auth flow)
console.log('Attempting NIP-46 connect...');
await signer.connect();
console.log('NIP-46 connect successful');