1134 lines
41 KiB
HTML
1134 lines
41 KiB
HTML
<!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> |