last commit before bundling nostr-tools ourselves

This commit is contained in:
Your Name
2025-09-13 10:22:57 -04:00
parent 025d66c096
commit bad361a686
3 changed files with 579 additions and 175 deletions

View File

@@ -1,7 +1,7 @@
/**
* NOSTR_LOGIN_LITE
* Single-file Nostr authentication library
* Generated on: 2025-09-13T13:03:05.960Z
* Generated on: 2025-09-13T13:54:03.650Z
*/
// ======================================
@@ -291,85 +291,166 @@ class Modal {
// Detect all available real extensions
const availableExtensions = this._detectAllExtensions();
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) {
console.log('Modal detected single extension:', availableExtensions[0].name);
// 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 {
console.log('Modal detected multiple extensions:', availableExtensions.map(e => e.name));
// Multiple extensions - show choice UI
console.log('Modal: Multiple extensions detected, showing choice UI for', availableExtensions.length, 'extensions');
this._showExtensionChoice(availableExtensions);
}
}
_detectAllExtensions() {
const extensions = [];
const seenExtensions = new Set(); // Track extensions by object reference to avoid duplicates
// Check navigator.nostr (NIP-07 standard location)
if (window.navigator?.nostr && typeof window.navigator.nostr.getPublicKey === 'function') {
extensions.push({
name: 'navigator.nostr',
displayName: 'Standard Extension (navigator.nostr)',
icon: '🌐',
extension: window.navigator.nostr
});
}
// Check webln.nostr (Alby WebLN)
if (window.webln?.nostr && typeof window.webln.nostr.getPublicKey === 'function') {
extensions.push({
name: 'webln.nostr',
displayName: 'Alby WebLN Extension',
icon: '⚡',
extension: window.webln.nostr
});
}
// Check alby.nostr (Alby direct)
if (window.alby?.nostr && typeof window.alby.nostr.getPublicKey === 'function') {
extensions.push({
name: 'alby.nostr',
displayName: 'Alby Extension (Direct)',
icon: '🐝',
extension: window.alby.nostr
});
}
// Check nos2x
if (window.nos2x && typeof window.nos2x.getPublicKey === 'function') {
extensions.push({
name: 'nos2x',
displayName: 'nos2x Extension',
icon: '🔌',
extension: window.nos2x
});
}
// Check window.nostr but make sure it's not our library
if (window.nostr && typeof window.nostr.getPublicKey === 'function') {
const isRealExtension = (
typeof window.nostr._hexToUint8Array !== 'function' && // Our library has this method
window.nostr.constructor.name !== 'Object' // Real extensions usually have proper constructors
);
if (isRealExtension) {
// Don't add if we already detected it via another path
const alreadyDetected = extensions.some(ext => ext.extension === window.nostr);
if (!alreadyDetected) {
// Extension locations to check (in priority order)
const locations = [
{ path: 'window.navigator?.nostr', name: 'navigator.nostr', displayName: 'Standard Extension (navigator.nostr)', icon: '🌐', getter: () => window.navigator?.nostr },
{ path: 'window.webln?.nostr', name: 'webln.nostr', displayName: 'Alby WebLN Extension', icon: '⚡', getter: () => window.webln?.nostr },
{ path: 'window.alby?.nostr', name: 'alby.nostr', displayName: 'Alby Extension (Direct)', icon: '🐝', getter: () => window.alby?.nostr },
{ path: 'window.nos2x', name: 'nos2x', displayName: 'nos2x Extension', icon: '🔌', getter: () => window.nos2x },
{ path: 'window.flamingo?.nostr', name: 'flamingo.nostr', displayName: 'Flamingo Extension', icon: '🦩', getter: () => window.flamingo?.nostr },
{ path: 'window.mutiny?.nostr', name: 'mutiny.nostr', displayName: 'Mutiny Extension', icon: '⚔️', getter: () => window.mutiny?.nostr },
{ path: 'window.nostrich?.nostr', name: 'nostrich.nostr', displayName: 'Nostrich Extension', icon: '🐦', getter: () => window.nostrich?.nostr },
{ path: 'window.getAlby?.nostr', name: 'getAlby.nostr', displayName: 'getAlby Extension', icon: '🔧', getter: () => window.getAlby?.nostr }
];
// Check each location
for (const location of locations) {
try {
const obj = location.getter();
console.log(`Modal: Checking ${location.name}:`, !!obj, obj?.constructor?.name);
if (obj && this._isRealExtension(obj) && !seenExtensions.has(obj)) {
extensions.push({
name: 'window.nostr',
displayName: 'Extension (window.nostr)',
icon: '🔑',
extension: window.nostr
name: location.name,
displayName: location.displayName,
icon: location.icon,
extension: obj
});
seenExtensions.add(obj);
console.log(`Modal: ✓ Detected extension at ${location.name} (${obj.constructor?.name})`);
} else if (obj) {
console.log(`Modal: ✗ Filtered out ${location.name} (${obj.constructor?.name})`);
}
} catch (e) {
// Location doesn't exist or can't be accessed
console.log(`Modal: ${location.name} not accessible:`, e.message);
}
}
// 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`);
}
return extensions;
}
_isRealExtension(obj) {
console.log(`Modal: EXTENSIVE DEBUG - _isRealExtension called with:`, obj);
console.log(`Modal: Object type: ${typeof obj}`);
console.log(`Modal: Object truthy: ${!!obj}`);
if (!obj || typeof obj !== 'object') {
console.log(`Modal: REJECT - Not an object`);
return false;
}
console.log(`Modal: getPublicKey type: ${typeof obj.getPublicKey}`);
console.log(`Modal: signEvent type: ${typeof obj.signEvent}`);
// Must have required Nostr methods
if (typeof obj.getPublicKey !== 'function' || typeof obj.signEvent !== 'function') {
console.log(`Modal: REJECT - Missing required methods`);
return false;
}
// Exclude NostrTools library object
if (obj === window.NostrTools) {
console.log(`Modal: REJECT - Is NostrTools object`);
return false;
}
// Use the EXACT SAME logic as the comprehensive test (lines 804-809)
// This is the key fix - match the comprehensive test's successful detection logic
const constructorName = obj.constructor?.name;
const objectKeys = Object.keys(obj);
console.log(`Modal: Constructor name: "${constructorName}"`);
console.log(`Modal: Object keys: [${objectKeys.join(', ')}]`);
// COMPREHENSIVE TEST LOGIC - Accept anything with required methods that's not our specific library classes
const isRealExtension = (
typeof obj.getPublicKey === 'function' &&
typeof obj.signEvent === 'function' &&
constructorName !== 'WindowNostr' && // Our library class
constructorName !== 'NostrLite' // Our main class
);
console.log(`Modal: Using comprehensive test logic:`);
console.log(` Has getPublicKey: ${typeof obj.getPublicKey === 'function'}`);
console.log(` Has signEvent: ${typeof obj.signEvent === 'function'}`);
console.log(` Not WindowNostr: ${constructorName !== 'WindowNostr'}`);
console.log(` Not NostrLite: ${constructorName !== 'NostrLite'}`);
console.log(` Constructor: "${constructorName}"`);
// Additional debugging for comparison
const extensionPropChecks = {
_isEnabled: !!obj._isEnabled,
enabled: !!obj.enabled,
kind: !!obj.kind,
_eventEmitter: !!obj._eventEmitter,
_scope: !!obj._scope,
_requests: !!obj._requests,
_pubkey: !!obj._pubkey,
name: !!obj.name,
version: !!obj.version,
description: !!obj.description
};
console.log(`Modal: Extension property analysis:`, extensionPropChecks);
const hasExtensionProps = !!(
obj._isEnabled || obj.enabled || obj.kind ||
obj._eventEmitter || obj._scope || obj._requests || obj._pubkey ||
obj.name || obj.version || obj.description
);
const underscoreKeys = objectKeys.filter(key => key.startsWith('_'));
const hexToUint8Keys = objectKeys.filter(key => key.startsWith('_hex'));
console.log(`Modal: Underscore keys: [${underscoreKeys.join(', ')}]`);
console.log(`Modal: _hex* keys: [${hexToUint8Keys.join(', ')}]`);
console.log(`Modal: Additional analysis:`);
console.log(` hasExtensionProps: ${hasExtensionProps}`);
console.log(` hasLibraryMethod (_hexToUint8Array): ${objectKeys.includes('_hexToUint8Array')}`);
console.log(`Modal: COMPREHENSIVE TEST LOGIC RESULT: ${isRealExtension ? 'ACCEPT' : 'REJECT'}`);
console.log(`Modal: FINAL DECISION for ${constructorName}: ${isRealExtension ? 'ACCEPT' : 'REJECT'}`);
return isRealExtension;
}
_showExtensionChoice(extensions) {
this.modalBody.innerHTML = '';
@@ -1684,7 +1765,8 @@ class ExtensionBridge {
this.checking = false;
this.checkInterval = null;
this.originalNostr = null;
this.foundExtension = null;
this.foundExtensions = new Map(); // Store multiple extensions by location
this.primaryExtension = null; // The currently selected extension
}
startChecking(nostrLite) {
@@ -1692,7 +1774,7 @@ class ExtensionBridge {
this.checking = true;
const check = () => {
this.initExtension(nostrLite);
this.detectAllExtensions(nostrLite);
};
// Check immediately
@@ -1708,50 +1790,170 @@ class ExtensionBridge {
}, 30000);
}
initExtension(nostrLite, lastTry = false) {
const extension = window.nostr;
detectAllExtensions(nostrLite) {
// Extension locations to check (in priority order)
const locations = [
{ path: 'window.navigator?.nostr', name: 'navigator.nostr', getter: () => window.navigator?.nostr },
{ path: 'window.webln?.nostr', name: 'webln.nostr', getter: () => window.webln?.nostr },
{ path: 'window.alby?.nostr', name: 'alby.nostr', getter: () => window.alby?.nostr },
{ path: 'window.nos2x', name: 'nos2x', getter: () => window.nos2x },
{ path: 'window.flamingo?.nostr', name: 'flamingo.nostr', getter: () => window.flamingo?.nostr },
{ path: 'window.mutiny?.nostr', name: 'mutiny.nostr', getter: () => window.mutiny?.nostr },
{ path: 'window.nostrich?.nostr', name: 'nostrich.nostr', getter: () => window.nostrich?.nostr },
{ path: 'window.getAlby?.nostr', name: 'getAlby.nostr', getter: () => window.getAlby?.nostr }
];
if (extension && !this.foundExtension) {
// Check if this is actually a real extension, not our own library
const isRealExtension = (
extension !== nostrLite && // Not the same object we're about to assign
extension !== windowNostr && // Not our windowNostr object
typeof extension._hexToUint8Array !== 'function' && // Our library has this internal method
extension.constructor.name !== 'Object' // Real extensions usually have proper constructors
let foundNew = false;
// Check each location
for (const location of locations) {
try {
const obj = location.getter();
if (obj && this.isRealExtension(obj, nostrLite)) {
if (!this.foundExtensions.has(location.name)) {
this.foundExtensions.set(location.name, {
name: location.name,
path: location.path,
extension: obj,
constructor: obj.constructor?.name || 'Unknown'
});
console.log(`Real Nostr extension detected: ${location.name} (${obj.constructor?.name})`);
foundNew = true;
}
}
} catch (e) {
// Location doesn't exist or can't be accessed
}
}
// Also check window.nostr but be extra careful to avoid our library
if (window.nostr && this.isRealExtension(window.nostr, nostrLite)) {
// Make sure we haven't already detected this extension via another path
const existingExtension = Array.from(this.foundExtensions.values()).find(
ext => ext.extension === window.nostr
);
if (isRealExtension) {
this.foundExtension = extension;
if (!existingExtension && !this.foundExtensions.has('window.nostr')) {
this.foundExtensions.set('window.nostr', {
name: 'window.nostr',
path: 'window.nostr',
extension: window.nostr,
constructor: window.nostr.constructor?.name || 'Unknown'
});
console.log(`Real Nostr extension detected at window.nostr: ${window.nostr.constructor?.name}`);
foundNew = true;
}
}
// Cache the extension and reassign window.nostr to our lite version
this.originalNostr = window.nostr;
// Set primary extension if we don't have one and found extensions
if (!this.primaryExtension && this.foundExtensions.size > 0) {
// Prefer navigator.nostr if available, otherwise use first found
this.primaryExtension = this.foundExtensions.get('navigator.nostr') ||
Array.from(this.foundExtensions.values())[0];
// Cache the extension and reassign window.nostr to our lite version
this.originalNostr = this.primaryExtension.extension;
if (window.nostr !== nostrLite) {
window.nostr = nostrLite;
}
console.log('Real Nostr extension detected and bridged:', extension.constructor.name);
console.log(`Primary extension set: ${this.primaryExtension.name}`);
// If currently authenticated, reconcile state
if (LiteState.auth?.signer?.method === 'extension') {
this.reconcileExtension();
}
} else {
console.log('Skipping non-extension object on window.nostr:', extension.constructor.name);
// If currently authenticated, reconcile state
if (LiteState.auth?.signer?.method === 'extension') {
this.reconcileExtension();
}
}
}
isRealExtension(obj, nostrLite) {
if (!obj || typeof obj !== 'object') return false;
// Must have required Nostr methods
if (typeof obj.getPublicKey !== 'function' || typeof obj.signEvent !== 'function') {
return false;
}
// Exclude our own library objects
if (obj === nostrLite || obj === windowNostr) {
return false;
}
// Exclude objects with our library's internal methods
if (typeof obj._hexToUint8Array === 'function' || typeof obj._call === 'function') {
return false;
}
// Exclude NostrTools library object
if (obj === window.NostrTools) {
return false;
}
// Real extensions typically have proper constructors (not plain Object)
const constructorName = obj.constructor?.name;
if (constructorName === 'Object' && !obj._isEnabled && !obj.enabled) {
// Plain objects without extension-specific properties are likely our library
return false;
}
return true;
}
getAllExtensions() {
return Array.from(this.foundExtensions.values());
}
getExtensionCount() {
return this.foundExtensions.size;
}
hasExtension() {
return !!this.foundExtension;
return this.foundExtensions.size > 0;
}
// Legacy compatibility - return primary extension
get foundExtension() {
return this.primaryExtension?.extension || null;
}
// Method to properly set primary extension
setPrimaryExtension(extension, name = 'selected') {
// Find the extension in our map or create new entry
let extensionInfo = null;
// Check if this extension is already in our map
for (const [key, info] of this.foundExtensions) {
if (info.extension === extension) {
extensionInfo = info;
break;
}
}
// If not found, create a new entry
if (!extensionInfo) {
extensionInfo = {
name: name,
path: name,
extension: extension,
constructor: extension?.constructor?.name || 'Unknown'
};
this.foundExtensions.set(name, extensionInfo);
}
this.primaryExtension = extensionInfo;
console.log(`Primary extension set to: ${extensionInfo.name}`);
}
async setExtensionReadPubkey(expectedPubkey = null) {
if (!this.foundExtension) return false;
if (!this.primaryExtension) return false;
try {
// Temporarily set window.nostr to extension
const temp = window.nostr;
window.nostr = this.foundExtension;
window.nostr = this.primaryExtension.extension;
const pubkey = await this.foundExtension.getPublicKey();
const pubkey = await this.primaryExtension.extension.getPublicKey();
// Restore our lite implementation
window.nostr = temp;
@@ -1780,8 +1982,8 @@ class ExtensionBridge {
}
setExtension() {
if (!this.foundExtension) return;
window.nostr = this.foundExtension;
if (!this.primaryExtension) return;
window.nostr = this.primaryExtension.extension;
this.setExtensionReadPubkey().then(pubkey => {
if (pubkey) {
LiteState.bus?.emit('extensionSet', { pubkey });
@@ -1877,7 +2079,7 @@ class NostrLite {
// Handle extension detection
this.bus?.on('extensionDetected', (extension) => {
console.log('Extension detected');
LiteState.extensionBridge.foundExtension = extension;
LiteState.extensionBridge.setPrimaryExtension(extension, 'detected');
});
// Handle auth URL from NIP-46
@@ -1915,7 +2117,7 @@ class NostrLite {
case 'extension':
if (pubkey && extension) {
// Store the extension object in the ExtensionBridge for future use
LiteState.extensionBridge.foundExtension = extension;
LiteState.extensionBridge.setPrimaryExtension(extension, 'modal-selected');
LiteState.extensionBridge.originalNostr = extension;
// Set up extension authentication