From 894a163111451eb7c23dd56419eea89e7255b9e4 Mon Sep 17 00:00:00 2001 From: Laan Tungir Date: Fri, 26 Jun 2026 10:52:01 -0400 Subject: [PATCH] Fix relay disappearing: make port.onmessage synchronous (all handlers fire-and-forget). Fix discovered relays serving data: replace broken LRU cache iteration with kind 3 cache query + per-pubkey trackerData.get(). Remove debug logging. --- www/js/version.json | 6 +- www/ndk-worker.js | 197 +++++++++++++++----------------------------- 2 files changed, 70 insertions(+), 133 deletions(-) diff --git a/www/js/version.json b/www/js/version.json index 8c67546..78fce7c 100644 --- a/www/js/version.json +++ b/www/js/version.json @@ -1,5 +1,5 @@ { - "VERSION": "v0.7.49", - "VERSION_NUMBER": "0.7.49", - "BUILD_DATE": "2026-06-26T14:27:42.415Z" + "VERSION": "v0.7.50", + "VERSION_NUMBER": "0.7.50", + "BUILD_DATE": "2026-06-26T14:52:01.942Z" } diff --git a/www/ndk-worker.js b/www/ndk-worker.js index 6274cbc..f5785f1 100644 --- a/www/ndk-worker.js +++ b/www/ndk-worker.js @@ -6464,89 +6464,34 @@ async function handleGetDiscoveredRelays(requestId, port) { } // Build relayUrl -> [pubkeys] map by inverting the outbox tracker. - // ndk.outboxTracker.data is an LRUCache (typescript-lru-cache). - // We try a few iteration strategies to be resilient to API differences. + // The typescript-lru-cache iteration API is broken (it yields millions + // of phantom keys from only a handful of real entries), so instead of + // iterating the LRU cache we query the NDK cache for the user's kind 3 + // contact list and then look up each followed pubkey by key. const relayServesPubkeys = new Map(); // normalizedUrl -> Set try { - const trackerData = ndk?.outboxTracker?.data; - if (trackerData) { - // Debug: log what the LRU cache looks like - const trackerKeys = typeof trackerData.keys === 'function' ? Array.from(trackerData.keys()) : - (trackerData instanceof Map ? Array.from(trackerData.keys()) : []); - console.log('[Worker] getDiscoveredRelays: tracker has', trackerKeys.length, 'entries'); + const kind3Events = await ndk.fetchEvents( + { kinds: [3], authors: [currentPubkey], limit: 1 }, + { cacheUsage: 'ONLY_CACHE' } + ); + const latestKind3 = kind3Events && kind3Events.size > 0 + ? Array.from(kind3Events).sort((a, b) => (b.created_at || 0) - (a.created_at || 0))[0] + : null; + const followedPubkeys = latestKind3 ? extractFollowedPubkeysFromKind3(latestKind3) : []; - let pubkeysProcessed = 0; - let pubkeysWithWriteRelays = 0; - - const collectForPubkey = (pubkey) => { - pubkeysProcessed++; - try { - const outboxItem = trackerData.get(pubkey); - if (!outboxItem) return; - const writeRelays = outboxItem.writeRelays; - if (!writeRelays || writeRelays.size === 0) return; - pubkeysWithWriteRelays++; - for (const r of writeRelays) { - const rUrl = r?.url ? normalizeRelayUrl(r.url) : (typeof r === 'string' ? normalizeRelayUrl(r) : null); - if (!rUrl) continue; - if (!relayServesPubkeys.has(rUrl)) { - relayServesPubkeys.set(rUrl, new Set()); - } - relayServesPubkeys.get(rUrl).add(pubkey); - } - } catch (_) { - // ignore per-pubkey errors - } - }; - - // Strategy 1: standard Map-like entries() - let iterated = false; + for (const pubkey of followedPubkeys) { try { - if (typeof trackerData.entries === 'function') { - for (const [pubkey] of trackerData.entries()) { - collectForPubkey(pubkey); + const outboxItem = ndk?.outboxTracker?.data?.get(pubkey); + if (!outboxItem?.writeRelays) continue; + for (const relayUrl of outboxItem.writeRelays) { + const normalized = normalizeRelayUrl(relayUrl); + if (!normalized) continue; + if (!relayServesPubkeys.has(normalized)) { + relayServesPubkeys.set(normalized, new Set()); } - iterated = true; + relayServesPubkeys.get(normalized).add(pubkey); } } catch (_) {} - - // Strategy 2: keys() + get() - if (!iterated) { - try { - if (typeof trackerData.keys === 'function') { - for (const pubkey of trackerData.keys()) { - collectForPubkey(pubkey); - } - iterated = true; - } - } catch (_) {} - } - - // Strategy 3: iterate as Map directly - if (!iterated && trackerData instanceof Map) { - for (const pubkey of trackerData.keys()) { - collectForPubkey(pubkey); - } - iterated = true; - } - - // Strategy 4: try internal storage if LRU cache has it - if (!iterated) { - try { - // Some LRU caches store entries in an internal Map or object - const internal = trackerData._map || trackerData.cache || trackerData._cache; - if (internal instanceof Map) { - for (const [pubkey] of internal) { - collectForPubkey(pubkey); - } - iterated = true; - } - } catch (_) {} - } - - console.log('[Worker] getDiscoveredRelays: processed', pubkeysProcessed, 'pubkeys,', pubkeysWithWriteRelays, 'with write relays, iterated:', iterated, ', relayServesPubkeys size:', relayServesPubkeys.size); - } else { - console.log('[Worker] getDiscoveredRelays: no outbox tracker data available'); } } catch (err) { console.warn('[Worker] getDiscoveredRelays: outbox tracker inversion failed:', err?.message || err); @@ -6852,7 +6797,7 @@ self.onconnect = (event) => { connectedPorts.push(port); - port.onmessage = async (e) => { + port.onmessage = (e) => { const { type, pubkey, @@ -6894,11 +6839,11 @@ self.onconnect = (event) => { switch(type) { case 'init': - await handleInit(pubkey, port); + void handleInit(pubkey, port); break; case 'subscribe': - await handleSubscribe(subId, filters, opts, port); + void handleSubscribe(subId, filters, opts, port); break; case 'unsubscribe': @@ -6906,27 +6851,27 @@ self.onconnect = (event) => { break; case 'publish': - await handlePublish(requestId, event, port); + void handlePublish(requestId, event, port); break; case 'publishRaw': - await handlePublishRaw(requestId, event, port); + void handlePublishRaw(requestId, event, port); break; case 'publishRawToRelay': - await handlePublishRawToRelay(requestId, event, relayUrl, port); + void handlePublishRawToRelay(requestId, event, relayUrl, port); break; case 'sendNip17Message': - await handleSendNip17Message(requestId, recipientPubkey, content, port); + void handleSendNip17Message(requestId, recipientPubkey, content, port); break; case 'fetchEvents': - await handleFetchEvents(requestId, filters, relayUrl, port); + void handleFetchEvents(requestId, filters, relayUrl, port); break; case 'ndkFetchEvents': - await handleNdkFetchEvents(requestId, filters, port); + void handleNdkFetchEvents(requestId, filters, port); break; case 'warmOutbox': @@ -6937,7 +6882,7 @@ self.onconnect = (event) => { break; case 'setRelayEventLogging': - await handleSetRelayEventLogging(requestId, enabled, port); + void handleSetRelayEventLogging(requestId, enabled, port); break; case 'getDiscoveredRelays': @@ -6973,109 +6918,101 @@ self.onconnect = (event) => { break; case 'queryCache': - await handleQueryCache(requestId, filters, port); + void handleQueryCache(requestId, filters, port); break; case 'purgePlaylistCache': - await handlePurgePlaylistCache(requestId, playlistIdentifier, ownerPubkey, playlistKind, eventIds, port); + void handlePurgePlaylistCache(requestId, playlistIdentifier, ownerPubkey, playlistKind, eventIds, port); break; - case 'syncMuteList': { - try { - const result = await loadWorkerMuteList(currentPubkey, { reason: 'sync-request' }); - port.postMessage({ - type: 'response', - requestId, - data: { success: true, result } + case 'syncMuteList': + void loadWorkerMuteList(currentPubkey, { reason: 'sync-request' }) + .then((result) => { + port.postMessage({ type: 'response', requestId, data: { success: true, result } }); + }) + .catch((error) => { + port.postMessage({ type: 'response', requestId, error: error?.message || String(error) }); }); - } catch (error) { - port.postMessage({ - type: 'response', - requestId, - error: error?.message || String(error) - }); - } break; - } case 'fetchCachedProfile': - await handleFetchCachedProfile(requestId, e.data.pubkey, port); + void handleFetchCachedProfile(requestId, e.data.pubkey, port); break; case 'storeProfile': - await handleStoreProfile(e.data.pubkey, e.data.profile); + void handleStoreProfile(e.data.pubkey, e.data.profile); break; case 'getAllCachedProfiles': - await handleGetAllCachedProfiles(requestId, port); + void handleGetAllCachedProfiles(requestId, port); break; case 'getUserSettings': - await handleGetUserSettings(requestId, port); + void handleGetUserSettings(requestId, port); break; case 'patchUserSettings': - await handlePatchUserSettings(requestId, patch, options, port); + void handlePatchUserSettings(requestId, patch, options, port); break; case 'setUserSettings': - await handleSetUserSettings(requestId, settings, options, port); + void handleSetUserSettings(requestId, settings, options, port); break; case 'resetUserSettings': - await handleResetUserSettings(requestId, options, port); + void handleResetUserSettings(requestId, options, port); break; case 'encryptContent': - await handleEncryptContent(requestId, e.data.recipientPubkey, e.data.plaintext, e.data.scheme, port); + void handleEncryptContent(requestId, e.data.recipientPubkey, e.data.plaintext, e.data.scheme, port); break; case 'decryptContent': - await handleDecryptContent(requestId, e.data.senderPubkey, e.data.ciphertext, e.data.scheme, port); + void handleDecryptContent(requestId, e.data.senderPubkey, e.data.ciphertext, e.data.scheme, port); break; case 'walletPublishMintList': - await handleWalletPublishMintList(requestId, relays, receiveMints, port); + void handleWalletPublishMintList(requestId, relays, receiveMints, port); break; case 'walletFetchMintList': - await handleWalletFetchMintList(requestId, targetPubkey, port); + void handleWalletFetchMintList(requestId, targetPubkey, port); break; case 'walletInit': - await handleWalletInit(requestId, port); + void handleWalletInit(requestId, port); break; case 'walletStart': - await handleWalletStart(requestId, pubkey, port); + void handleWalletStart(requestId, pubkey, port); break; case 'walletCreate': - await handleWalletCreate(requestId, mints, relays, port); + void handleWalletCreate(requestId, mints, relays, port); break; case 'walletHasWallet': - await handleWalletHasWallet(requestId, port); + void handleWalletHasWallet(requestId, port); break; case 'walletGetBalance': - await handleWalletGetBalance(requestId, port); + void handleWalletGetBalance(requestId, port); break; case 'walletGetMints': - await handleWalletGetMints(requestId, port); + void handleWalletGetMints(requestId, port); break; case 'walletReceiveToken': - await handleWalletReceiveToken(requestId, token, description, port); + void handleWalletReceiveToken(requestId, token, description, port); break; case 'walletSendToken': - await handleWalletSendToken(requestId, amount, memo, mint, port); + void handleWalletSendToken(requestId, amount, memo, mint, port); break; case 'walletSendNutzap': - await handleWalletSendNutzap( + void handleWalletSendNutzap( requestId, amount, memo, @@ -7090,23 +7027,23 @@ self.onconnect = (event) => { break; case 'walletCreateDeposit': - await handleWalletCreateDeposit(requestId, amount, mint, port); + void handleWalletCreateDeposit(requestId, amount, mint, port); break; case 'walletPayInvoice': - await handleWalletPayInvoice(requestId, invoice, mint, port); + void handleWalletPayInvoice(requestId, invoice, mint, port); break; case 'walletAddMint': - await handleWalletAddMint(requestId, mintUrl, port); + void handleWalletAddMint(requestId, mintUrl, port); break; case 'walletRemoveMint': - await handleWalletRemoveMint(requestId, mintUrl, port); + void handleWalletRemoveMint(requestId, mintUrl, port); break; case 'walletGetTransactions': - await handleWalletGetTransactions(requestId, port); + void handleWalletGetTransactions(requestId, port); break; case 'walletShutdown': @@ -7114,11 +7051,11 @@ self.onconnect = (event) => { break; case 'walletCheckProofs': - await handleWalletCheckProofs(requestId, port); + void handleWalletCheckProofs(requestId, port); break; case 'walletRepublish': - await handleWalletRepublish(requestId, port); + void handleWalletRepublish(requestId, port); break; default: