Files
nostr_login_lite/examples/comprehensive-test.html

1134 lines
41 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>🔬 NOSTR_LOGIN_LITE Comprehensive Test & Debug</title>
<style>
* { box-sizing: border-box; }
body {
font-family: 'SF Pro Display', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
margin: 0;
padding: 20px;
background: #f8fafc;
color: #1e293b;
line-height: 1.6;
}
.container {
max-width: 1200px;
margin: 0 auto;
}
h1 {
color: #0f172a;
margin-bottom: 30px;
text-align: center;
font-size: 32px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.test-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 20px;
margin-bottom: 30px;
}
.test-section {
background: white;
border-radius: 12px;
padding: 20px;
box-shadow: 0 4px 6px -1px rgba(0,0,0,0.1);
border: 1px solid #e2e8f0;
}
.test-section h2 {
margin: 0 0 15px 0;
color: #1e293b;
font-size: 18px;
display: flex;
align-items: center;
gap: 8px;
}
.status-indicator {
width: 12px;
height: 12px;
border-radius: 50%;
background: #94a3b8;
}
.status-indicator.success { background: #10b981; }
.status-indicator.error { background: #ef4444; }
.status-indicator.warning { background: #f59e0b; }
.status-indicator.loading {
background: #3b82f6;
animation: pulse 2s infinite;
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.5; }
}
.button {
background: #3b82f6;
color: white;
border: none;
padding: 10px 20px;
border-radius: 8px;
cursor: pointer;
font-size: 14px;
margin: 5px;
transition: all 0.2s;
}
.button:hover { background: #2563eb; transform: translateY(-1px); }
.button:disabled { background: #94a3b8; cursor: not-allowed; transform: none; }
.button.secondary { background: #6b7280; }
.button.secondary:hover { background: #4b5563; }
.output-area {
background: #1e293b;
color: #e2e8f0;
padding: 20px;
border-radius: 8px;
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
font-size: 13px;
max-height: 600px;
overflow-y: auto;
margin-top: 20px;
white-space: pre-wrap;
word-wrap: break-word;
}
.output-area .timestamp {
color: #64748b;
margin-right: 8px;
}
.output-area .success { color: #10b981; }
.output-area .error { color: #ef4444; }
.output-area .warning { color: #f59e0b; }
.output-area .info { color: #3b82f6; }
.full-width {
grid-column: 1 / -1;
}
.control-panel {
background: white;
padding: 20px;
border-radius: 12px;
box-shadow: 0 4px 6px -1px rgba(0,0,0,0.1);
border: 1px solid #e2e8f0;
margin-bottom: 20px;
text-align: center;
}
.quick-info {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 15px;
margin-bottom: 20px;
}
.info-card {
background: #f1f5f9;
padding: 15px;
border-radius: 8px;
text-align: center;
border-left: 4px solid #3b82f6;
}
.info-card h3 {
margin: 0 0 5px 0;
font-size: 14px;
color: #64748b;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.info-card .value {
font-size: 18px;
font-weight: 600;
color: #1e293b;
word-break: break-all;
}
.copy-btn {
background: #10b981;
color: white;
border: none;
padding: 5px 10px;
border-radius: 4px;
font-size: 12px;
cursor: pointer;
margin-left: 10px;
}
</style>
</head>
<body>
<div class="container">
<h1>🔬 NOSTR_LOGIN_LITE Comprehensive Test</h1>
<div class="control-panel">
<h2>Test Controls</h2>
<button class="button" onclick="runAllTests()">🚀 Run All Tests</button>
<button class="button secondary" onclick="clearOutput()">🗑️ Clear Output</button>
<button class="button secondary" onclick="copyAllOutput()">📋 Copy All Output</button>
<button class="button secondary" onclick="location.reload()">🔄 Reload Page</button>
</div>
<div class="quick-info">
<div class="info-card">
<h3>Test Environment</h3>
<div class="value" id="env-info">Loading...</div>
</div>
<div class="info-card">
<h3>Bundle Status</h3>
<div class="value" id="bundle-status">Checking...</div>
</div>
<div class="info-card">
<h3>Window.nostr</h3>
<div class="value" id="nostr-status">Checking...</div>
</div>
<div class="info-card">
<h3>Current User</h3>
<div class="value" id="user-status">None</div>
</div>
</div>
<div class="test-grid">
<div class="test-section">
<h2>
<span class="status-indicator" id="deps-status"></span>
📚 Dependencies
</h2>
<div id="deps-output">Testing dependencies...</div>
<button class="button" onclick="testDependencies()">Test Dependencies</button>
</div>
<div class="test-section">
<h2>
<span class="status-indicator" id="extension-status"></span>
🔌 Extension Detection
</h2>
<div id="extension-output">Extension detection pending...</div>
<button class="button" onclick="testExtensionDetection()">Detect Extensions</button>
<button class="button secondary" onclick="debugWindowNostr()">Debug window.nostr</button>
</div>
<div class="test-section">
<h2>
<span class="status-indicator" id="init-status"></span>
⚙️ Initialization
</h2>
<div id="init-output">Ready to initialize...</div>
<button class="button" onclick="testInitialization()">Initialize Library</button>
</div>
<div class="test-section">
<h2>
<span class="status-indicator" id="api-status"></span>
🔐 API Tests
</h2>
<div id="api-output">API tests pending...</div>
<button class="button" onclick="testAPI()">Test API</button>
<button class="button secondary" onclick="testLocalKey()">Generate Local Key</button>
</div>
<div class="test-section">
<h2>
<span class="status-indicator" id="modal-status"></span>
🎨 Modal & UI
</h2>
<div id="modal-output">UI tests pending...</div>
<button class="button" onclick="testModal()">Test Modal</button>
<button class="button secondary" onclick="testAuth()">Test Auth Flow</button>
</div>
<div class="test-section">
<h2>
<span class="status-indicator" id="crypto-status"></span>
🔒 Cryptography
</h2>
<div id="crypto-output">Crypto tests pending...</div>
<button class="button" onclick="testCrypto()">Test NIP-04/44</button>
</div>
<div class="test-section">
<h2>
<span class="status-indicator" id="storage-status"></span>
💾 Storage & State
</h2>
<div id="storage-output">Storage tests pending...</div>
<button class="button" onclick="testStorage()">Test Storage</button>
</div>
</div>
<div class="test-section full-width">
<h2>
📊 Complete Debug Output
<button class="copy-btn" onclick="copyAllOutput()">Copy All</button>
</h2>
<div class="output-area" id="debug-output">
<div class="timestamp">[${new Date().toLocaleTimeString()}]</div> Debug output will appear here...
Click "Run All Tests" to begin comprehensive testing.
All results will be displayed here for easy copying and debugging.
</div>
</div>
</div>
<!-- Load the official nostr-tools bundle first -->
<script src="../lite/nostr.bundle.js"></script>
<!-- Load NOSTR_LOGIN_LITE main library (now includes NIP-46 extension) -->
<script src="../lite/nostr-lite.js"></script>
<script>
// Global test state
let testState = {
dependencies: false,
initialized: false,
authenticated: false,
currentUser: null,
testResults: {}
};
// Utility functions
function log(message, type = 'info') {
const timestamp = new Date().toLocaleTimeString();
const output = document.getElementById('debug-output');
const className = type === 'error' ? 'error' :
type === 'success' ? 'success' :
type === 'warning' ? 'warning' : 'info';
output.innerHTML += `\n<span class="timestamp">[${timestamp}]</span> <span class="${className}">${message}</span>`;
output.scrollTop = output.scrollHeight;
// Also log to console for debugging
console.log(`[${timestamp}] ${message}`);
}
function updateStatus(section, status, message = '') {
const indicator = document.getElementById(`${section}-status`);
const output = document.getElementById(`${section}-output`);
indicator.className = `status-indicator ${status}`;
if (output && message) {
output.textContent = message;
}
}
function updateQuickInfo() {
// Environment info
document.getElementById('env-info').textContent =
`${navigator.userAgent.includes('Chrome') ? 'Chrome' : 'Other'} @ 127.0.0.1:5501`;
// Bundle status
const nostrTools = !!window.NostrTools;
const nostrLite = !!window.NOSTR_LOGIN_LITE;
document.getElementById('bundle-status').textContent =
`NostrTools: ${nostrTools ? '✓' : '✗'}, Lite: ${nostrLite ? '✓' : '✗'}`;
// window.nostr status
const hasNostr = !!window.nostr;
const nostrType = hasNostr ? (window.nostr.getPublicKey ? 'functional' : 'partial') : 'missing';
document.getElementById('nostr-status').textContent = nostrType;
// Current user
const current = testState.currentUser;
document.getElementById('user-status').textContent =
current ? current.substring(0, 8) + '...' : 'None';
}
// Test functions
async function testDependencies() {
log('=== DEPENDENCY TEST START ===');
updateStatus('deps', 'loading', 'Testing...');
try {
// Test NostrTools
log(`Testing NostrTools availability...`);
if (!window.NostrTools) {
throw new Error('window.NostrTools not found');
}
log(`✓ NostrTools found: ${typeof window.NostrTools}`, 'success');
// Test required functions
const required = ['SimplePool', 'getPublicKey', 'finalizeEvent', 'nip04'];
for (const fn of required) {
if (!window.NostrTools[fn]) {
throw new Error(`Missing NostrTools.${fn}`);
}
log(`${fn}: ${typeof window.NostrTools[fn]}`, 'success');
}
// Test NOSTR_LOGIN_LITE
log(`Testing NOSTR_LOGIN_LITE availability...`);
if (!window.NOSTR_LOGIN_LITE) {
throw new Error('window.NOSTR_LOGIN_LITE not found');
}
log(`✓ NOSTR_LOGIN_LITE found: ${typeof window.NOSTR_LOGIN_LITE}`, 'success');
// Test API methods
const liteRequired = ['init', 'launch', 'logout'];
for (const method of liteRequired) {
if (!window.NOSTR_LOGIN_LITE[method]) {
throw new Error(`Missing NOSTR_LOGIN_LITE.${method}`);
}
log(`${method}: ${typeof window.NOSTR_LOGIN_LITE[method]}`, 'success');
}
testState.dependencies = true;
updateStatus('deps', 'success', 'All dependencies loaded');
log('=== DEPENDENCY TEST PASSED ===', 'success');
} catch (error) {
updateStatus('deps', 'error', error.message);
log(`❌ Dependency test failed: ${error.message}`, 'error');
log(`Error stack: ${error.stack}`, 'error');
}
}
async function testInitialization() {
log('=== INITIALIZATION TEST START ===');
updateStatus('init', 'loading', 'Initializing...');
try {
if (!testState.dependencies) {
throw new Error('Dependencies not loaded - run dependency test first');
}
const options = {
theme: 'light',
darkMode: false,
relays: ['wss://relay.damus.io', 'wss://nos.lol'],
methods: {
extension: true,
local: true,
readonly: true,
connect: false, // NIP-46 disabled for basic test
otp: false
}
};
log('Calling NOSTR_LOGIN_LITE.init() with options:');
log(JSON.stringify(options, null, 2));
await window.NOSTR_LOGIN_LITE.init(options);
// Verify window.nostr was set up
if (!window.nostr) {
throw new Error('window.nostr not created after init');
}
log('✓ Library initialized successfully', 'success');
log(`✓ window.nostr available: ${typeof window.nostr}`, 'success');
// Debug window.nostr structure
log(`window.nostr type: ${typeof window.nostr}`, 'info');
log(`window.nostr constructor: ${window.nostr?.constructor?.name}`, 'info');
log(`window.nostr keys: [${Object.keys(window.nostr || {}).join(', ')}]`, 'info');
log(`window.nostr prototype keys: [${Object.getOwnPropertyNames(Object.getPrototypeOf(window.nostr || {})).join(', ')}]`, 'info');
// Try different access patterns
const nostr = window.nostr;
log(`Direct access - getPublicKey: ${typeof nostr?.getPublicKey}`, 'info');
log(`Direct access - signEvent: ${typeof nostr?.signEvent}`, 'info');
log(`Direct access - nip04: ${typeof nostr?.nip04}`, 'info');
// Check if it's a function that needs to be called or has methods
if (typeof nostr === 'function') {
log(`window.nostr is a function - checking function properties`, 'info');
log(`Function keys: [${Object.getOwnPropertyNames(nostr).join(', ')}]`, 'info');
}
if (window.nostr && typeof window.nostr.getPublicKey === 'function') {
log(`✓ getPublicKey: function available`, 'success');
} else {
log(`⚠ getPublicKey: ${typeof window.nostr?.getPublicKey} (expected function)`, 'warning');
}
if (window.nostr && typeof window.nostr.signEvent === 'function') {
log(`✓ signEvent: function available`, 'success');
} else {
log(`⚠ signEvent: ${typeof window.nostr?.signEvent} (expected function)`, 'warning');
}
if (window.nostr && window.nostr.nip04 && typeof window.nostr.nip04 === 'object') {
log(`✓ nip04 object: available`, 'success');
log(` nip04.encrypt: ${typeof window.nostr.nip04.encrypt}`, 'info');
log(` nip04.decrypt: ${typeof window.nostr.nip04.decrypt}`, 'info');
} else {
log(`⚠ nip04: ${typeof window.nostr?.nip04} (expected object)`, 'warning');
}
testState.initialized = true;
updateStatus('init', 'success', 'Initialized successfully');
log('=== INITIALIZATION TEST PASSED ===', 'success');
} catch (error) {
updateStatus('init', 'error', error.message);
log(`❌ Initialization failed: ${error.message}`, 'error');
log(`Error stack: ${error.stack}`, 'error');
}
}
async function testAPI() {
log('=== API TEST START ===');
updateStatus('api', 'loading', 'Testing API...');
try {
if (!testState.initialized) {
throw new Error('Library not initialized - run initialization test first');
}
log('Testing window.nostr API methods...');
// Test that functions exist but don't call them yet (would trigger auth)
const methods = ['getPublicKey', 'signEvent'];
for (const method of methods) {
if (!window.nostr || typeof window.nostr[method] !== 'function') {
log(`❌ window.nostr.${method} is not a function (type: ${typeof window.nostr?.[method]})`, 'error');
log(` window.nostr available: ${!!window.nostr}`, 'error');
log(` window.nostr keys: [${Object.keys(window.nostr || {}).join(', ')}]`, 'error');
throw new Error(`window.nostr.${method} is not a function - got ${typeof window.nostr?.[method]}`);
}
log(`${method}: function available`, 'success');
}
// Test NIP-04 object
if (!window.nostr || !window.nostr.nip04 || typeof window.nostr.nip04.encrypt !== 'function') {
log(`❌ NIP-04 not properly available:`, 'error');
log(` window.nostr: ${!!window.nostr}`, 'error');
log(` window.nostr.nip04: ${!!window.nostr?.nip04} (type: ${typeof window.nostr?.nip04})`, 'error');
log(` window.nostr.nip04.encrypt: ${typeof window.nostr?.nip04?.encrypt}`, 'error');
throw new Error(`window.nostr.nip04.encrypt not available - nip04: ${typeof window.nostr?.nip04}, encrypt: ${typeof window.nostr?.nip04?.encrypt}`);
}
log(' ✓ nip04.encrypt: function available', 'success');
if (typeof window.nostr.nip04.decrypt !== 'function') {
log(`❌ window.nostr.nip04.decrypt not available (type: ${typeof window.nostr.nip04.decrypt})`, 'error');
throw new Error(`window.nostr.nip04.decrypt not available - got ${typeof window.nostr.nip04.decrypt}`);
}
log(' ✓ nip04.decrypt: function available', 'success');
// Test NIP-44 (optional)
if (window.nostr.nip44) {
log(' ✓ nip44: available (optional)', 'success');
} else {
log(' nip44: not available (optional)', 'warning');
}
updateStatus('api', 'success', 'API methods available');
log('=== API TEST PASSED ===', 'success');
} catch (error) {
updateStatus('api', 'error', error.message);
log(`❌ API test failed: ${error.message}`, 'error');
}
}
async function testLocalKey() {
log('=== LOCAL KEY GENERATION TEST START ===');
try {
if (!testState.initialized) {
throw new Error('Library not initialized');
}
log('Generating local keypair with NostrTools...');
const sk = window.NostrTools.generateSecretKey();
const pk = window.NostrTools.getPublicKey(sk);
log(`✓ Secret key generated: ${sk.length} bytes`, 'success');
log(`✓ Public key derived: ${pk}`, 'success');
// Test bech32 encoding
const nsec = window.NostrTools.nip19.nsecEncode(sk);
const npub = window.NostrTools.nip19.npubEncode(pk);
log(`✓ nsec: ${nsec}`, 'success');
log(`✓ npub: ${npub}`, 'success');
// Test event signing
const testEvent = {
kind: 1,
content: 'Test event from comprehensive test',
tags: [],
created_at: Math.floor(Date.now() / 1000)
};
const signedEvent = await window.NostrTools.finalizeEvent(testEvent, sk);
log(`✓ Event signed: ${signedEvent.id}`, 'success');
log(`✓ Signature: ${signedEvent.sig.substring(0, 20)}...`, 'success');
testState.currentUser = pk;
updateQuickInfo();
} catch (error) {
log(`❌ Local key test failed: ${error.message}`, 'error');
}
}
async function testModal() {
log('=== MODAL TEST START ===');
updateStatus('modal', 'loading', 'Testing modal...');
try {
if (!testState.initialized) {
throw new Error('Library not initialized');
}
log('Testing modal launch...');
window.NOSTR_LOGIN_LITE.launch('login');
log('✓ Modal launched successfully (should be visible)', 'success');
updateStatus('modal', 'success', 'Modal can be launched');
} catch (error) {
updateStatus('modal', 'error', error.message);
log(`❌ Modal test failed: ${error.message}`, 'error');
}
}
async function testAuth() {
log('=== AUTHENTICATION TEST START ===');
try {
if (!testState.initialized) {
throw new Error('Library not initialized');
}
log('Testing authentication flow...');
log('This will trigger the modal - follow the authentication process');
// Set up event listener
const authPromise = new Promise((resolve, reject) => {
const timeout = setTimeout(() => {
window.removeEventListener('nlAuth', handler);
reject(new Error('Authentication timeout'));
}, 60000);
const handler = (event) => {
clearTimeout(timeout);
window.removeEventListener('nlAuth', handler);
log(`Auth event received: ${JSON.stringify(event.detail)}`, 'success');
resolve(event.detail);
};
window.addEventListener('nlAuth', handler);
});
// Trigger authentication
const pubkey = await window.nostr.getPublicKey();
log(`✓ Authentication successful: ${pubkey}`, 'success');
testState.authenticated = true;
testState.currentUser = pubkey;
updateQuickInfo();
} catch (error) {
log(`❌ Authentication test failed: ${error.message}`, 'error');
}
}
async function testCrypto() {
log('=== CRYPTOGRAPHY TEST START ===');
updateStatus('crypto', 'loading', 'Testing crypto...');
try {
if (!testState.authenticated && !testState.currentUser) {
throw new Error('Not authenticated - run auth test or generate local key first');
}
// Generate test keys
const recipientSk = window.NostrTools.generateSecretKey();
const recipientPk = window.NostrTools.getPublicKey(recipientSk);
const message = 'Test message for NIP-04 encryption';
log('Testing NIP-04 encryption/decryption...');
log(`Test message: "${message}"`);
log(`Recipient PK: ${recipientPk}`);
// Test NIP-04
const encrypted04 = await window.nostr.nip04.encrypt(recipientPk, message);
log(`✓ NIP-04 encrypted: ${encrypted04.substring(0, 40)}...`, 'success');
const decrypted04 = await window.nostr.nip04.decrypt(recipientPk, encrypted04);
log(`✓ NIP-04 decrypted: "${decrypted04}"`, 'success');
if (decrypted04 === message) {
log('✓ NIP-04 encryption/decryption successful', 'success');
} else {
throw new Error('NIP-04 decryption mismatch');
}
// Test NIP-44 if available
if (window.nostr.nip44) {
try {
log('Testing NIP-44 encryption/decryption...');
const encrypted44 = await window.nostr.nip44.encrypt(recipientPk, message);
log(`✓ NIP-44 encrypted: ${encrypted44.substring(0, 40)}...`, 'success');
const decrypted44 = await window.nostr.nip44.decrypt(recipientPk, encrypted44);
log(`✓ NIP-44 decrypted: "${decrypted44}"`, 'success');
if (decrypted44 === message) {
log('✓ NIP-44 encryption/decryption successful', 'success');
} else {
throw new Error('NIP-44 decryption mismatch');
}
} catch (nip44Error) {
log(`⚠ NIP-44 test failed: ${nip44Error.message}`, 'warning');
}
} else {
log(' NIP-44 not available in this version', 'info');
}
updateStatus('crypto', 'success', 'Crypto tests passed');
log('=== CRYPTOGRAPHY TEST PASSED ===', 'success');
} catch (error) {
updateStatus('crypto', 'error', error.message);
log(`❌ Cryptography test failed: ${error.message}`, 'error');
}
}
async function testStorage() {
log('=== STORAGE TEST START ===');
updateStatus('storage', 'loading', 'Testing storage...');
try {
log('Testing localStorage access...');
// Test basic localStorage
const testKey = 'nl-test-' + Date.now();
const testValue = 'test-value-' + Math.random();
localStorage.setItem(testKey, testValue);
const retrieved = localStorage.getItem(testKey);
if (retrieved === testValue) {
log('✓ localStorage read/write works', 'success');
localStorage.removeItem(testKey);
} else {
throw new Error('localStorage read/write failed');
}
// Check for existing NOSTR_LOGIN_LITE data
const nlKeys = [];
for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i);
if (key && key.startsWith('nl')) {
nlKeys.push(key);
}
}
log(`Found ${nlKeys.length} NOSTR_LOGIN_LITE storage keys:`, 'info');
nlKeys.forEach(key => {
const value = localStorage.getItem(key);
const preview = value ? value.substring(0, 50) + '...' : 'null';
log(` ${key}: ${preview}`, 'info');
});
// Test current account state
const current = localStorage.getItem('nl_current');
if (current) {
try {
const parsed = JSON.parse(current);
log(`Current account: ${parsed.pubkey || 'no pubkey'}`, 'success');
log(`Current method: ${parsed.signer?.method || 'no method'}`, 'success');
} catch (e) {
log(`⚠ Current account data corrupted: ${e.message}`, 'warning');
}
} else {
log('No current account stored', 'info');
}
updateStatus('storage', 'success', 'Storage accessible');
log('=== STORAGE TEST PASSED ===', 'success');
} catch (error) {
updateStatus('storage', 'error', error.message);
log(`❌ Storage test failed: ${error.message}`, 'error');
}
}
// Extension detection debugging
async function testExtensionDetection() {
log('=== EXTENSION DETECTION DEBUG START ===');
updateStatus('extension', 'loading', 'Detecting extensions...');
try {
log('Starting comprehensive extension detection...');
// 1. Check window.nostr directly
log('1. Direct window.nostr check:', 'info');
log(` window.nostr exists: ${!!window.nostr}`, 'info');
log(` window.nostr type: ${typeof window.nostr}`, 'info');
log(` window.nostr constructor: ${window.nostr?.constructor?.name || 'undefined'}`, 'info');
// 2. Check common extension locations
log('2. Common extension locations:', 'info');
const extensionLocations = [
'window.nostr',
'window.alby?.nostr',
'window.nos2x?.nostr',
'window.flamingo?.nostr',
'window.getAlby?.nostr',
'window.mutiny?.nostr',
'window.nostrich?.nostr'
];
const foundExtensions = [];
for (const location of extensionLocations) {
try {
const obj = eval(location);
if (obj && typeof obj === 'object') {
log(` ✓ Found at ${location}:`, 'success');
log(` Type: ${typeof obj}`, 'info');
log(` Constructor: ${obj.constructor?.name || 'undefined'}`, 'info');
log(` Has getPublicKey: ${typeof obj.getPublicKey === 'function'}`, 'info');
log(` Has signEvent: ${typeof obj.signEvent === 'function'}`, 'info');
log(` Has nip04: ${typeof obj.nip04 === 'object'}`, 'info');
log(` Keys: [${Object.keys(obj).join(', ')}]`, 'info');
// Check if it's a real extension (not our library)
const isRealExtension = (
typeof obj.getPublicKey === 'function' &&
typeof obj.signEvent === 'function' &&
obj.constructor?.name !== 'WindowNostr' && // Our library class
obj.constructor?.name !== 'NostrLite' // Our main class
);
if (isRealExtension) {
foundExtensions.push({
location,
obj,
name: obj.name || location.split('.').pop() || 'Unknown'
});
log(` ✓ Looks like a real extension!`, 'success');
} else {
log(` ⚠ Might be our library object`, 'warning');
}
}
} catch (e) {
// Location doesn't exist, that's fine
}
}
log(`3. Extension detection summary:`, 'info');
log(` Total found: ${foundExtensions.length}`, 'info');
if (foundExtensions.length === 0) {
log(' No browser extensions detected', 'warning');
log(' This could mean:', 'warning');
log(' - No Nostr extensions installed', 'warning');
log(' - Extensions not loaded yet', 'warning');
log(' - Extensions using non-standard locations', 'warning');
} else {
foundExtensions.forEach((ext, index) => {
log(` Extension ${index + 1}: ${ext.name} at ${ext.location}`, 'success');
// Test basic functionality
if (typeof ext.obj.getPublicKey === 'function') {
log(` ✓ getPublicKey available`, 'success');
}
if (typeof ext.obj.signEvent === 'function') {
log(` ✓ signEvent available`, 'success');
}
if (ext.obj.nip04 && typeof ext.obj.nip04.encrypt === 'function') {
log(` ✓ NIP-04 available`, 'success');
}
});
}
// 4. Deep window scan for any nostr-like objects
log('4. Deep window scan for nostr-like objects:', 'info');
const nostrLikeObjects = [];
function scanObject(obj, path, maxDepth = 2) {
if (maxDepth <= 0 || !obj || typeof obj !== 'object') return;
try {
Object.keys(obj).forEach(key => {
const value = obj[key];
const currentPath = path ? `${path}.${key}` : key;
if (value && typeof value === 'object') {
// Check if this looks like a nostr object
const hasNostrMethods = (
typeof value.getPublicKey === 'function' ||
typeof value.signEvent === 'function' ||
(value.nip04 && typeof value.nip04.encrypt === 'function')
);
if (hasNostrMethods && !nostrLikeObjects.find(item => item.path === currentPath)) {
nostrLikeObjects.push({
path: currentPath,
obj: value,
hasGetPublicKey: typeof value.getPublicKey === 'function',
hasSignEvent: typeof value.signEvent === 'function',
hasNip04: !!(value.nip04 && typeof value.nip04.encrypt === 'function')
});
}
// Recurse
if (maxDepth > 1) {
scanObject(value, currentPath, maxDepth - 1);
}
}
});
} catch (e) {
// Skip objects that can't be enumerated
}
}
scanObject(window, 'window', 2);
log(` Found ${nostrLikeObjects.length} nostr-like objects:`, 'info');
nostrLikeObjects.forEach(item => {
log(` ${item.path}: getPublicKey=${item.hasGetPublicKey}, signEvent=${item.hasSignEvent}, nip04=${item.hasNip04}`, 'info');
});
// 5. Test our library's extension detection
if (window.NOSTR_LOGIN_LITE && testState.initialized) {
log('5. Testing our library\'s extension detection:', 'info');
// Access ExtensionBridge via the exposed _extensionBridge
const bridge = window.NOSTR_LOGIN_LITE._extensionBridge;
if (bridge) {
log(' ExtensionBridge found - analyzing detected extensions...', 'info');
const allExtensions = bridge.getAllExtensions();
const extensionCount = bridge.getExtensionCount();
const primaryExtension = bridge.primaryExtension;
log(` Library detected ${extensionCount} extensions total:`, 'info');
allExtensions.forEach((ext, index) => {
const isPrimary = ext === primaryExtension;
log(` ${index + 1}. ${ext.name} (${ext.constructor}) ${isPrimary ? '[PRIMARY]' : ''}`, 'success');
});
if (primaryExtension) {
log(` Primary extension: ${primaryExtension.name}`, 'success');
log(` Primary extension object keys: [${Object.keys(primaryExtension.extension).join(', ')}]`, 'info');
} else {
log(' No primary extension set', 'warning');
}
// Compare with our manual detection
log(' Comparing with manual scan results...', 'info');
if (foundExtensions.length !== extensionCount) {
log(` ⚠ Mismatch: Manual found ${foundExtensions.length}, Library found ${extensionCount}`, 'warning');
} else {
log(` ✓ Match: Both found ${extensionCount} extensions`, 'success');
}
} else {
log(' ❌ ExtensionBridge not accessible', 'error');
}
}
// 6. Test modal extension detection
log('6. Testing modal extension detection:', 'info');
if (typeof Modal !== 'undefined') {
// Create temporary modal instance to test extension detection
const tempModal = new Modal({ methods: { extension: true } });
const modalExtensions = tempModal._detectAllExtensions();
log(` Modal detected ${modalExtensions.length} extensions:`, 'info');
modalExtensions.forEach((ext, index) => {
log(` ${index + 1}. ${ext.displayName} (${ext.name})`, 'success');
log(` Constructor: ${ext.extension.constructor?.name}`, 'info');
log(` Icon: ${ext.icon}`, 'info');
});
// Compare modal vs library detection
const libraryCount = window.NOSTR_LOGIN_LITE?._extensionBridge?.getExtensionCount() || 0;
if (modalExtensions.length !== libraryCount) {
log(` ⚠ Modal vs Library mismatch: Modal=${modalExtensions.length}, Library=${libraryCount}`, 'warning');
} else {
log(` ✓ Modal and Library agree on extension count: ${modalExtensions.length}`, 'success');
}
} else {
log(' Modal class not available for testing', 'warning');
}
updateStatus('extension', 'success', `Found ${foundExtensions.length} extensions`);
log('=== EXTENSION DETECTION DEBUG COMPLETE ===', 'success');
} catch (error) {
updateStatus('extension', 'error', error.message);
log(`❌ Extension detection debug failed: ${error.message}`, 'error');
log(`Error stack: ${error.stack}`, 'error');
}
}
async function debugWindowNostr() {
log('=== WINDOW.NOSTR DEEP DEBUG START ===');
try {
if (!window.nostr) {
log('❌ window.nostr is not available', 'error');
return;
}
log('window.nostr deep analysis:', 'info');
log(`Type: ${typeof window.nostr}`, 'info');
log(`Constructor: ${window.nostr.constructor?.name}`, 'info');
log(`Prototype: ${Object.getPrototypeOf(window.nostr).constructor?.name}`, 'info');
// Check all properties
log('Direct properties:', 'info');
Object.getOwnPropertyNames(window.nostr).forEach(prop => {
try {
const value = window.nostr[prop];
log(` ${prop}: ${typeof value}`, 'info');
} catch (e) {
log(` ${prop}: [error accessing]`, 'warning');
}
});
// Check prototype properties
log('Prototype properties:', 'info');
Object.getOwnPropertyNames(Object.getPrototypeOf(window.nostr)).forEach(prop => {
if (prop !== 'constructor') {
try {
const value = window.nostr[prop];
log(` ${prop}: ${typeof value}`, 'info');
} catch (e) {
log(` ${prop}: [error accessing]`, 'warning');
}
}
});
// Test method calls
log('Method availability test:', 'info');
const methods = ['getPublicKey', 'signEvent', 'getRelays', 'nip04', 'nip44'];
methods.forEach(method => {
const available = window.nostr[method];
if (typeof available === 'function') {
log(`${method}: function`, 'success');
} else if (typeof available === 'object' && available !== null) {
log(`${method}: object`, 'success');
if (method === 'nip04' || method === 'nip44') {
const encrypt = available.encrypt;
const decrypt = available.decrypt;
log(` encrypt: ${typeof encrypt}`, 'info');
log(` decrypt: ${typeof decrypt}`, 'info');
}
} else {
log(`${method}: ${typeof available}`, 'warning');
}
});
// Check for extension-specific properties
log('Extension-specific properties check:', 'info');
const extensionProps = ['_source', '_name', '_version', 'kind', 'enabled'];
extensionProps.forEach(prop => {
if (window.nostr.hasOwnProperty(prop)) {
log(` ${prop}: ${window.nostr[prop]}`, 'info');
}
});
log('=== WINDOW.NOSTR DEEP DEBUG COMPLETE ===', 'success');
} catch (error) {
log(`❌ window.nostr debug failed: ${error.message}`, 'error');
}
}
// Main test runner
async function runAllTests() {
log('🚀 STARTING COMPREHENSIVE TEST SUITE 🚀');
log(`Timestamp: ${new Date().toISOString()}`);
log(`User Agent: ${navigator.userAgent}`);
log(`URL: ${window.location.href}`);
log('==================================================');
updateQuickInfo();
// Run tests in sequence
await testDependencies();
if (testState.dependencies) {
await testInitialization();
if (testState.initialized) {
// Wait a moment for extension detection to complete
await new Promise(resolve => setTimeout(resolve, 1000));
await testExtensionDetection(); // Test extension detection after init
await testAPI();
await testLocalKey(); // Generate a key for crypto tests
await testCrypto();
await testStorage();
}
}
log('==================================================');
log('🏁 COMPREHENSIVE TEST SUITE COMPLETE 🏁');
log(`Dependencies: ${testState.dependencies ? '✓' : '✗'}`);
log(`Initialized: ${testState.initialized ? '✓' : '✗'}`);
log(`User: ${testState.currentUser ? testState.currentUser.substring(0, 8) + '...' : 'None'}`);
updateQuickInfo();
}
// Utility functions
function clearOutput() {
document.getElementById('debug-output').innerHTML =
`<div class="timestamp">[${new Date().toLocaleTimeString()}]</div> Debug output cleared.`;
}
function copyAllOutput() {
const output = document.getElementById('debug-output');
const text = output.textContent || output.innerText;
navigator.clipboard.writeText(text).then(() => {
log('✓ All output copied to clipboard', 'success');
}).catch(err => {
log(`❌ Copy failed: ${err.message}`, 'error');
});
}
// Event listeners for auth events
window.addEventListener('nlAuth', (event) => {
log(`🔐 Auth event: ${JSON.stringify(event.detail)}`, 'success');
if (event.detail.pubkey) {
testState.authenticated = true;
testState.currentUser = event.detail.pubkey;
updateQuickInfo();
}
});
window.addEventListener('nlLogout', () => {
log('👋 Logout event received', 'info');
testState.authenticated = false;
testState.currentUser = null;
updateQuickInfo();
});
window.addEventListener('nlError', (event) => {
log(`❌ Error event: ${JSON.stringify(event.detail)}`, 'error');
});
// Initialize page
document.addEventListener('DOMContentLoaded', () => {
log('📄 Page loaded, ready for testing');
updateQuickInfo();
// Auto-run dependency test
setTimeout(() => {
testDependencies();
}, 500);
});
</script>
</body>
</html>