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