diff --git a/js/app.js b/js/app.js index 922563f6..24317161 100644 --- a/js/app.js +++ b/js/app.js @@ -482,19 +482,38 @@ class Application { await Promise.all([accessControlPromise, adminPanePromise]); - if (!this.pubkey && nostrClient.sessionActor?.pubkey) { + const syncSessionActorBlacklist = async (trigger) => { + if (this.pubkey) { + return; + } + + const sessionActorPubkey = nostrClient.sessionActor?.pubkey; + if (!sessionActorPubkey) { + return; + } + const blacklist = accessControl.getBlacklist(); try { - await userBlocks.seedLocalBaseline( - nostrClient.sessionActor.pubkey, + await userBlocks.seedBaselineDelta( + sessionActorPubkey, Array.from(blacklist || []), ); } catch (error) { devLogger.warn( - "[app.init()] Failed to seed local baseline for session actor:", + `[app.init()] Failed to sync session actor blacklist${ + trigger ? ` (${trigger})` : "" + }:`, error, ); } + }; + + await syncSessionActorBlacklist("post-refresh"); + + if (typeof accessControl.onBlacklistChange === "function") { + accessControl.onBlacklistChange(() => { + syncSessionActorBlacklist("blacklist-change"); + }); } // Grab the "Subscriptions" link by its id in the sidebar diff --git a/js/userBlocks.js b/js/userBlocks.js index 57669725..3b4efc80 100644 --- a/js/userBlocks.js +++ b/js/userBlocks.js @@ -1443,6 +1443,63 @@ class UserBlockListManager { return { ok: true, seeded: true, addedPubkeys: Array.from(additions) }; } + async seedBaselineDelta(userPubkey, candidateNpubs = []) { + const actorHex = normalizeHex(userPubkey); + if (!actorHex) { + return { ok: false, seeded: false, reason: "invalid-user" }; + } + + await this.ensureLoaded(actorHex); + + const state = this._getSeedState(actorHex); + const removals = state.removals; + const additions = new Set(); + + const candidates = Array.isArray(candidateNpubs) ? candidateNpubs : []; + for (const candidate of candidates) { + const candidateHex = normalizeHex(candidate); + if (!candidateHex) { + continue; + } + if (candidateHex === actorHex) { + continue; + } + if (this.blockedPubkeys.has(candidateHex)) { + continue; + } + if (removals.has(candidateHex)) { + continue; + } + additions.add(candidateHex); + } + + if (!additions.size) { + if (!state.seeded) { + this._setSeeded(actorHex, true); + } + return { ok: true, seeded: state.seeded, reason: "no-candidates" }; + } + + additions.forEach((hex) => this.blockedPubkeys.add(hex)); + + this._setSeeded(actorHex, true); + this._saveLocal(actorHex); + + try { + this.emitter.emit(USER_BLOCK_EVENTS.CHANGE, { + action: "seed-delta", + actorPubkey: actorHex, + blockedPubkeys: Array.from(this.blockedPubkeys), + addedPubkeys: Array.from(additions), + localOnly: true, + }); + } catch (error) { + userLogger.warn("[UserBlockList] Failed to emit delta seed change event:", error); + } + + return { ok: true, seeded: true, addedPubkeys: Array.from(additions) }; + } + async addBlock(targetPubkey, userPubkey) { const actorHex = normalizeHex(userPubkey); if (!actorHex) {