last commit before bundling nostr-tools ourselves
This commit is contained in:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user