From 09d0e045e828b8d0c7085c8cd0bc383cdfd214b6 Mon Sep 17 00:00:00 2001 From: thePR0M3TH3AN <53631862+PR0M3TH3AN@users.noreply.github.com> Date: Sun, 14 Jun 2026 16:08:54 -0400 Subject: [PATCH] refactor(lists): route follows + hashtag-prefs subs through SubscriptionManager (P4) The two feed-driving list subscriptions (kind-30000 follows, kind-30015/30005 hashtag prefs) now go through the L1 SubscriptionManager instead of the global relaySubscriptionService singleton: centralized dedup + reconnect re-issue (the lists self-heal after a relay drop). Handles are stored and closed on teardown; relaySubscriptionService is retained as the manager-less fallback. Verified: subscriptions-manager/-feed + hashtag-preferences suites pass (32); flaky multi-relay harness still all-PASS; pool-access lint clean. Co-Authored-By: Claude Opus 4.8 --- js/services/hashtagPreferencesService.js | 25 +++++++++++++++++++++++ js/subscriptions.js | 26 ++++++++++++++++++++++++ 2 files changed, 51 insertions(+) diff --git a/js/services/hashtagPreferencesService.js b/js/services/hashtagPreferencesService.js index 94176763..b3afa4a1 100644 --- a/js/services/hashtagPreferencesService.js +++ b/js/services/hashtagPreferencesService.js @@ -325,6 +325,14 @@ class HashtagPreferencesService { } stopHashtagPreferencesSubscription() { + if (this.subscriptionHandle) { + try { + this.subscriptionHandle.close(); + } catch (error) { + devLogger.warn(`${LOG_PREFIX} Failed to close hashtag subscription handle`, error); + } + this.subscriptionHandle = null; + } if (this.subscriptionKey) { relaySubscriptionService.stopSubscription(this.subscriptionKey, "reset"); this.subscriptionKey = null; @@ -353,6 +361,23 @@ class HashtagPreferencesService { const key = `hashtags:${normalized}`; this.subscriptionKey = key; + // Route through the L1 SubscriptionManager when available (dedup + reconnect + // re-issue); fall back to relaySubscriptionService in manager-less envs. + const manager = + typeof nostrClient.getSubscriptionManager === "function" + ? nostrClient.getSubscriptionManager() + : null; + if (manager) { + this.subscriptionHandle = manager.subscribe({ + key, + relays, + filters, + label: "hashtag-preferences", + onEvent: (event) => this.handleHashtagPreferencesEvent(event), + }); + return this.subscriptionHandle; + } + return relaySubscriptionService.ensureSubscription({ key, pool: nostrClient.pool, diff --git a/js/subscriptions.js b/js/subscriptions.js index 28033165..5a2f2d10 100644 --- a/js/subscriptions.js +++ b/js/subscriptions.js @@ -851,6 +851,14 @@ class SubscriptionsManager { } stopSubscriptionListSubscription() { + if (this.subscriptionHandle) { + try { + this.subscriptionHandle.close(); + } catch (error) { + devLogger.warn("[SubscriptionsManager] Failed to close subscription handle", error); + } + this.subscriptionHandle = null; + } if (this.subscriptionKey) { relaySubscriptionService.stopSubscription(this.subscriptionKey, "reset"); this.subscriptionKey = null; @@ -874,6 +882,24 @@ class SubscriptionsManager { }, ]; + // Route through the L1 SubscriptionManager when available: centralized + // dedup + reconnect re-issue (so the follows list self-heals after a relay + // drop). Falls back to relaySubscriptionService in manager-less envs. + const manager = + typeof nostrClient.getSubscriptionManager === "function" + ? nostrClient.getSubscriptionManager() + : null; + if (manager) { + this.subscriptionHandle = manager.subscribe({ + key, + relays, + filters, + label: "subscription-list", + onEvent: (event) => this.handleSubscriptionListEvent(event), + }); + return this.subscriptionHandle; + } + return relaySubscriptionService.ensureSubscription({ key, pool: nostrClient.pool,