diff --git a/www/event-management.html b/www/event-management.html index a5822df..b14d999 100644 --- a/www/event-management.html +++ b/www/event-management.html @@ -342,6 +342,7 @@
Kinds
+
Loading...
@@ -683,6 +684,9 @@ let selectedKindSub = null; let currentDetailEvent = null; let lastDecryptResult = null; + let allEventsFetchSub = null; + let allEventsFetchInFlight = false; + let allEventsFetchTimeoutId = null; const eventsByKind = new Map(); // kind -> Map(eventKey, event) const selectedEventKeys = new Set(); @@ -703,6 +707,7 @@ const divFooterRight = document.getElementById('divFooterRight'); const btnRefreshKinds = document.getElementById('btnRefreshKinds'); + const btnGetAllEvents = document.getElementById('btnGetAllEvents'); const divKindsStatus = document.getElementById('divKindsStatus'); const divKindsList = document.getElementById('divKindsList'); @@ -1396,16 +1401,22 @@ }; } + function getCachedKindsAndEventTotals() { + const kinds = getKindsSorted(); + const totalEvents = kinds.reduce((sum, k) => sum + getEventsForKind(k).length, 0); + return { kindsCount: kinds.length, totalEvents }; + } + function renderKinds() { const kinds = getKindsSorted(); if (!kinds.length) { divKindsList.innerHTML = '
No cached events found.
'; - divKindsStatus.textContent = '0 kinds · 0 events'; + if (!allEventsFetchInFlight) divKindsStatus.textContent = '0 kinds · 0 events'; return; } const totalEvents = kinds.reduce((sum, k) => sum + getEventsForKind(k).length, 0); - divKindsStatus.textContent = `${kinds.length} kinds · ${totalEvents.toLocaleString()} cached events`; + if (!allEventsFetchInFlight) divKindsStatus.textContent = `${kinds.length} kinds · ${totalEvents.toLocaleString()} cached events`; divKindsList.innerHTML = kinds.map((kind) => { const count = getEventsForKind(kind).length; @@ -1682,6 +1693,63 @@ selectedKindSub = subscribe({ kinds: [Number(kind)], authors: [currentPubkey], limit: 500 }, { closeOnEose: false, cacheUsage: 'CACHE_FIRST' }); } + function clearAllEventsFetchTimeout() { + if (allEventsFetchTimeoutId) { + clearTimeout(allEventsFetchTimeoutId); + allEventsFetchTimeoutId = null; + } + } + + function closeAllEventsFetchSubscription() { + try { + if (allEventsFetchSub && typeof allEventsFetchSub.close === 'function') allEventsFetchSub.close(); + } catch (_error) { } + allEventsFetchSub = null; + } + + function finalizeGetAllEventsFetch(statusText) { + clearAllEventsFetchTimeout(); + closeAllEventsFetchSubscription(); + allEventsFetchInFlight = false; + if (btnGetAllEvents) btnGetAllEvents.disabled = false; + if (statusText) { + divKindsStatus.textContent = statusText; + } else { + const { kindsCount, totalEvents } = getCachedKindsAndEventTotals(); + divKindsStatus.textContent = `${kindsCount} kinds · ${totalEvents.toLocaleString()} cached events`; + } + } + + async function runOneShotGetAllEventsFetch() { + if (allEventsFetchInFlight) return; + if (!currentPubkey) { + divKindsStatus.textContent = 'Get all events failed: user pubkey unavailable'; + return; + } + + allEventsFetchInFlight = true; + if (btnGetAllEvents) btnGetAllEvents.disabled = true; + + const beforeTotals = getCachedKindsAndEventTotals(); + divKindsStatus.textContent = `Getting all events from relays for ${shortHex(currentPubkey)}...`; + + try { + closeAllEventsFetchSubscription(); + allEventsFetchSub = subscribe( + { authors: [currentPubkey] }, + { closeOnEose: true, cacheUsage: 'CACHE_FIRST' } + ); + + allEventsFetchTimeoutId = setTimeout(() => { + const afterTotals = getCachedKindsAndEventTotals(); + const added = Math.max(0, afterTotals.totalEvents - beforeTotals.totalEvents); + finalizeGetAllEventsFetch(`Get all events timed out · +${added.toLocaleString()} events (${afterTotals.totalEvents.toLocaleString()} total)`); + }, 30000); + } catch (error) { + finalizeGetAllEventsFetch(`Get all events failed: ${error?.message || String(error)}`); + } + } + async function selectKind(kind, options = {}) { const { autoRefresh = true } = options; @@ -1992,6 +2060,8 @@ }); logoutButton?.addEventListener('click', async () => { + closeAllEventsFetchSubscription(); + clearAllEventsFetchTimeout(); try { await logout(); } catch (error) { console.error('[event-management.html] logout failed', error); } }); @@ -2004,6 +2074,10 @@ } }); + btnGetAllEvents?.addEventListener('click', async () => { + await runOneShotGetAllEventsFetch(); + }); + inpEventSearch.addEventListener('input', renderEvents); chkShowSuperseded?.addEventListener('change', renderEvents); @@ -2319,6 +2393,15 @@ renderEvents(); }); + window.addEventListener('ndkEose', (event) => { + if (!allEventsFetchInFlight || !allEventsFetchSub?.subId) return; + const eoseSubId = String(event?.detail?.subId || ''); + if (!eoseSubId || eoseSubId !== String(allEventsFetchSub.subId)) return; + + const { kindsCount, totalEvents } = getCachedKindsAndEventTotals(); + finalizeGetAllEventsFetch(`Get all events complete · ${kindsCount} kinds · ${totalEvents.toLocaleString()} cached events`); + }); + window.addEventListener('ndkEvent', (event) => { const evt = event?.detail; if (!evt || !Number.isFinite(Number(evt.kind)) || !evt.id) return; diff --git a/www/js/version.json b/www/js/version.json index 1f5c21d..f27ed5b 100644 --- a/www/js/version.json +++ b/www/js/version.json @@ -1,5 +1,5 @@ { - "VERSION": "v0.7.17", - "VERSION_NUMBER": "0.7.17", - "BUILD_DATE": "2026-06-09T19:15:08.620Z" + "VERSION": "v0.7.18", + "VERSION_NUMBER": "0.7.18", + "BUILD_DATE": "2026-06-14T12:07:35.533Z" } diff --git a/www/ndk-worker.js b/www/ndk-worker.js index e494a3b..c4ab07c 100644 --- a/www/ndk-worker.js +++ b/www/ndk-worker.js @@ -1179,6 +1179,7 @@ function startRelayHealthCheck() { class MessageBasedSigner { constructor() { this.pubkey = null; + this.signedEventsBySig = new Map(); // sig -> signed raw event from signer page } async user() { @@ -1196,12 +1197,59 @@ class MessageBasedSigner { async sign(event) { console.log('[Worker] MessageBasedSigner: Signing event kind:', event.kind); - + // Request signing from page const signedEvent = await this.requestFromPage('signEvent', { event }); - + + const sig = String(signedEvent?.sig || '').trim(); + if (!sig) { + throw new Error('signEvent returned no signature'); + } + + // Keep a snapshot so caller can align NDKEvent fields to exactly what was signed. + const snapshot = { + id: String(signedEvent?.id || ''), + pubkey: String(signedEvent?.pubkey || ''), + created_at: Number(signedEvent?.created_at || 0), + kind: Number(signedEvent?.kind), + content: String(signedEvent?.content || ''), + tags: Array.isArray(signedEvent?.tags) + ? signedEvent.tags.map((tag) => Array.isArray(tag) ? tag.map((v) => String(v ?? '')) : [String(tag ?? '')]) + : [], + sig + }; + + this.signedEventsBySig.set(sig, snapshot); + if (this.signedEventsBySig.size > 200) { + const oldestKey = this.signedEventsBySig.keys().next().value; + if (oldestKey) this.signedEventsBySig.delete(oldestKey); + } + console.log('[Worker] MessageBasedSigner: Event signed'); - return signedEvent.sig; + return sig; + } + + applySignedSnapshotToEvent(ndkEvent) { + if (!ndkEvent) return false; + + const sig = String(ndkEvent?.sig || '').trim(); + if (!sig) return false; + + const snapshot = this.signedEventsBySig.get(sig); + if (!snapshot) return false; + + this.signedEventsBySig.delete(sig); + + if (snapshot.id) ndkEvent.id = snapshot.id; + if (snapshot.pubkey) ndkEvent.pubkey = snapshot.pubkey; + if (Number.isFinite(snapshot.created_at) && snapshot.created_at > 0) ndkEvent.created_at = snapshot.created_at; + if (Number.isFinite(snapshot.kind)) ndkEvent.kind = snapshot.kind; + ndkEvent.content = String(snapshot.content || ''); + ndkEvent.tags = Array.isArray(snapshot.tags) + ? snapshot.tags.map((tag) => Array.isArray(tag) ? tag.map((v) => String(v ?? '')) : [String(tag ?? '')]) + : []; + ndkEvent.sig = snapshot.sig; + return true; } async encrypt(recipient, plaintext) { @@ -1307,6 +1355,16 @@ class MessageBasedSigner { // Create message-based signer instance let messageSigner = null; +async function signEventWithMessageSigner(event, opts) { + if (!event) throw new Error('Missing event to sign'); + await event.sign(messageSigner, opts); + try { + messageSigner?.applySignedSnapshotToEvent?.(event); + } catch (error) { + console.warn('[Worker] Failed applying signed snapshot to event:', error?.message || error); + } +} + // Initialize NDK with Dexie cache and message-based signer async function initNDK() { console.log('[Worker] Initializing NDK...'); @@ -2662,7 +2720,7 @@ async function publishDirectProofs(debugContext = null) { }); log('sign 7375 start', { mintUrl }); - await tokenEvent.sign(messageSigner); + await signEventWithMessageSigner(tokenEvent); log('sign 7375 done', { mintUrl }); log('publish 7375 start', { mintUrl }); @@ -2687,7 +2745,7 @@ async function publishDirectProofs(debugContext = null) { content: '', created_at: Math.floor(Date.now() / 1000) }); - await deletionEvent.sign(messageSigner); + await signEventWithMessageSigner(deletionEvent); await deletionEvent.publish(); } log('delete old 7375 done'); @@ -2726,7 +2784,7 @@ async function publishDirectProofs(debugContext = null) { }); log('sign 17375 start'); - await walletEvent.sign(messageSigner); + await signEventWithMessageSigner(walletEvent); log('sign 17375 done'); log('publish 17375 start'); @@ -2750,7 +2808,7 @@ async function publishDirectProofs(debugContext = null) { content: '', created_at: Math.floor(Date.now() / 1000) }); - await deletionEvent.sign(messageSigner); + await signEventWithMessageSigner(deletionEvent); await deletionEvent.publish(); } log('delete old 17375 done'); @@ -2951,7 +3009,7 @@ async function publishSpendingHistoryEvent(entry = {}) { created_at: Math.floor(Date.now() / 1000) }); - await historyEvent.sign(messageSigner); + await signEventWithMessageSigner(historyEvent); const relaySet = await historyEvent.publish(); if (relaySet && relaySet.size > 0) { for (const relay of relaySet) { @@ -3003,7 +3061,7 @@ async function handleWalletPublishMintList(requestId, relays, receiveMints, port mintListEvent.mints = mints; mintListEvent.relays = relayUrls; if (p2pk) mintListEvent.p2pk = p2pk; - await mintListEvent.sign(messageSigner); + await signEventWithMessageSigner(mintListEvent); const relaySet = await mintListEvent.publishReplaceable(); eventId = mintListEvent.id || null; publishedRelayCount = relaySet ? relaySet.size : 0; @@ -3019,7 +3077,7 @@ async function handleWalletPublishMintList(requestId, relays, receiveMints, port content: '', created_at: Math.floor(Date.now() / 1000) }); - await mintListEvent.sign(messageSigner); + await signEventWithMessageSigner(mintListEvent); const relaySet = await mintListEvent.publish(); eventId = mintListEvent.id || null; publishedRelayCount = relaySet ? relaySet.size : 0; @@ -3233,7 +3291,7 @@ async function handleWalletSendNutzap( created_at: Math.floor(Date.now() / 1000) }); - await nutzapEvent.sign(messageSigner); + await signEventWithMessageSigner(nutzapEvent); let relaySet = null; if (recipientRelayUrls.length > 0 && NDKRelaySet?.fromRelayUrls) { @@ -4256,7 +4314,7 @@ async function publishUserSettingsNow() { created_at: now }); - await ndkEvent.sign(messageSigner); + await signEventWithMessageSigner(ndkEvent); const relaySet = await ndkEvent.publish(); if (relaySet && relaySet.size > 0) { for (const relay of relaySet) { @@ -5304,7 +5362,7 @@ async function handlePublish(requestId, event, port) { // Sign using message-based signer (will request signing from page) console.log('[Worker] Requesting signature from page...'); - await ndkEvent.sign(messageSigner); + await signEventWithMessageSigner(ndkEvent); console.log('[Worker] Event signed, publishing to relays...'); // Publish to relays