7.1 KiB
7.1 KiB
Migration Plan: Replace fetchEventsFromAllRelays with NDK fetchEvents
Problem
fetchEventsFromAllRelays() in www/js/init-ndk.mjs is a custom function that bypasses NDK entirely. It:
- Calls
getRelayData()to get all relays (including write-only) - Iterates over every relay and sends raw WebSocket
REQmessages viahandleFetchEventsFromRelay()in the worker - Returns events grouped by relay:
{ "wss://relay1/": [...], "wss://relay2/": [...] }
This causes:
- Write-only relays receive read requests (violates NIP-65)
- No outbox model routing — NDK's smart relay selection is bypassed
- No caching — results don't go through NDK's Dexie cache
- Redundant deduplication — every caller does
Object.values(result).flat()then deduplicates manually
Solution
Replace fetchEventsFromAllRelays with a new ndkFetchEvents() function that uses NDK's ndk.fetchEvents() internally. NDK handles relay routing, caching, and deduplication automatically.
Architecture
graph LR
subgraph Before
A[UI Page] -->|fetchEventsFromAllRelays| B[init-ndk.mjs]
B -->|getRelayData| C[Worker]
C -->|returns ALL relays| B
B -->|fetchEvents per relay| D[Worker: raw WebSocket REQ]
D -->|intercepts WS messages| E[Each Relay]
end
subgraph After
F[UI Page] -->|ndkFetchEvents| G[init-ndk.mjs]
G -->|ndkFetchEvents message| H[Worker]
H -->|ndk.fetchEvents| I[NDK outbox routing]
I -->|smart relay selection| J[Read/Both Relays Only]
end
New API
Worker side (ndk-worker.js)
Add a new message handler ndkFetchEvents:
case 'ndkFetchEvents':
await handleNdkFetchEvents(requestId, filters, port);
break;
async function handleNdkFetchEvents(requestId, filters, port) {
try {
const eventSet = await ndk.fetchEvents(filters);
const events = Array.from(eventSet).map(e => e.rawEvent());
port.postMessage({
type: 'ndkFetchEventsResult',
requestId,
events,
error: null
});
} catch (err) {
port.postMessage({
type: 'ndkFetchEventsResult',
requestId,
events: [],
error: err?.message || String(err)
});
}
}
UI side (init-ndk.mjs)
Add a new exported function:
export async function ndkFetchEvents(filters) {
return new Promise((resolve, reject) => {
const requestId = `ndkFetch_${Date.now()}_${++requestCounter}`;
pendingRequests.set(requestId, { resolve, reject });
setTimeout(() => {
if (pendingRequests.has(requestId)) {
pendingRequests.delete(requestId);
reject(new Error('ndkFetchEvents timeout'));
}
}, 15000);
ndkWorker.port.postMessage({
type: 'ndkFetchEvents',
requestId,
filters
});
});
}
Returns: Array<RawEvent> — a flat, deduplicated array of raw nostr events.
Response handler in init-ndk.mjs
} else if (message.type === 'ndkFetchEventsResult') {
const pending = pendingRequests.get(message.requestId);
if (pending) {
pendingRequests.delete(message.requestId);
if (message.error) {
pending.reject(new Error(message.error));
} else {
pending.resolve(message.events || []);
}
}
}
Migration Per File
Every call site currently does:
const eventsByRelay = await fetchEventsFromAllRelays(filters);
const events = Object.values(eventsByRelay).flat();
After migration:
const events = await ndkFetchEvents(filters);
Files to migrate
| File | Call sites | Notes |
|---|---|---|
www/js/init-ndk.mjs |
Definition of fetchEventsFromAllRelays |
Keep for event.html, add ndkFetchEvents |
www/js/profile-cache.mjs |
fetchEventsFromAllRelaysFn(filters) |
Replace option name to ndkFetchEvents |
www/js/post-interactions.mjs |
fetchEventsFromAllRelaysFn(filter), fetchExistingInteractions |
Replace option + internal calls |
www/js/post-interactions2.mjs |
Imports and passes fetchEventsFromAllRelays |
Update imports to ndkFetchEvents |
www/feed.html |
fetchEventsFromAllRelays(filters) |
Direct replacement |
www/post.html |
fetchEventsFromAllRelays(filters) |
Direct replacement |
www/post2.html |
Multiple calls | Direct replacement, remove .flat() patterns |
www/notifications.html |
Multiple calls | Direct replacement |
www/people.html |
Multiple calls | Direct replacement |
www/cashu.html |
3 parallel calls | Direct replacement |
www/msg.html |
1 call | Direct replacement |
www/ai.html |
1 call | Direct replacement |
www/event.html |
Uses fetchEventsFromAllRelays |
Keep as-is (needs per-relay attribution) |
Detailed call-site transformations
Pattern A: Simple fetch + flatten (most common)
// BEFORE
const eventsByRelay = await fetchEventsFromAllRelays(filters);
const events = Object.values(eventsByRelay).flat();
// AFTER
const events = await ndkFetchEvents(filters);
Pattern B: Parallel with queryCache
// BEFORE (feed.html, post.html)
const [cached, freshByRelay] = await Promise.all([
queryCache(filters).catch(() => []),
fetchEventsFromAllRelays(filters).catch(() => ({}))
]);
// AFTER
const [cached, fresh] = await Promise.all([
queryCache(filters).catch(() => []),
ndkFetchEvents(filters).catch(() => [])
]);
// Note: NDK fetchEvents already checks cache, so queryCache may be redundant
// but keeping it for now ensures backward compatibility
Pattern C: Passed as option/callback
// BEFORE (post-interactions.mjs, profile-cache.mjs)
fetchExistingInteractions(postIds, { fetchEventsFromAllRelays, ... });
// AFTER
fetchExistingInteractions(postIds, { ndkFetchEvents, ... });
// Internal: const events = await ndkFetchEvents(filters);
// (no more Object.values().flat())
Execution Order
- Add worker handler —
handleNdkFetchEventsinndk-worker.js - Add UI function —
ndkFetchEvents+ response handler ininit-ndk.mjs - Migrate shared modules —
profile-cache.mjs,post-interactions.mjs,post-interactions2.mjs - Migrate pages one at a time —
feed.html,post.html,post2.html,notifications.html,people.html,cashu.html,msg.html,ai.html - Keep
event.htmlunchanged — it needs per-relay attribution - Clean up — remove unused
fetchEventsFromAllRelaysexport (keep only for event.html import)
Risk Mitigation
- NDK's
fetchEventsreturnsSet<NDKEvent>which we convert to raw events via.rawEvent()— this preserves the same data shape callers expect - The 15-second timeout on
ndkFetchEventsis generous; NDK handles per-relay timeouts internally - If NDK's outbox model hasn't loaded relay lists yet, it falls back to explicit pool relays (same as current behavior for read/both relays)
queryCachecalls alongsidendkFetchEventsare redundant (NDK checks cache first) but harmless — can be removed in a follow-up cleanup