diff --git a/js/accessControl.js b/js/accessControl.js index 0ac932dc..47b04a5f 100644 --- a/js/accessControl.js +++ b/js/accessControl.js @@ -117,6 +117,7 @@ class AccessControl { this._hydratedFromCache = false; this._whitelistListeners = new Set(); this._editorListeners = new Set(); + this._blacklistListeners = new Set(); this._scheduleHydrateFromCache(); } @@ -251,6 +252,8 @@ class AccessControl { this.editors instanceof Set ? new Set(this.editors) : new Set(); const previousWhitelist = this.whitelist instanceof Set ? new Set(this.whitelist) : new Set(); + const previousBlacklist = + this.blacklist instanceof Set ? new Set(this.blacklist) : new Set(); this.editors = new Set( dedupeNpubs([...ADMIN_EDITORS_NPUBS, ...editors]) @@ -280,6 +283,7 @@ class AccessControl { return true; }); this.blacklist = new Set(sanitizedBlacklist); + const blacklistChanged = !areSetsEqual(previousBlacklist, this.blacklist); const toolkitCandidate = (typeof window !== "undefined" ? window?.NostrTools : null) || @@ -326,6 +330,9 @@ class AccessControl { if (editorsChanged) { this._emitEditorsChange(Array.from(this.editors)); } + if (blacklistChanged) { + this._emitBlacklistChange(Array.from(this.blacklist)); + } } _emitWhitelistChange(whitelistValues) { @@ -346,6 +353,24 @@ class AccessControl { } } + _emitBlacklistChange(blacklistValues) { + if (!this._blacklistListeners.size) { + return; + } + + const snapshot = Array.isArray(blacklistValues) + ? [...blacklistValues] + : this.getBlacklist(); + + for (const listener of Array.from(this._blacklistListeners)) { + try { + listener(snapshot); + } catch (error) { + userLogger.error("accessControl blacklist listener failed", error); + } + } + } + _emitEditorsChange(editorsValues) { if (!this._editorListeners.size) { return; @@ -485,6 +510,18 @@ class AccessControl { }; } + onBlacklistChange(listener) { + if (typeof listener !== "function") { + return () => {}; + } + + this._blacklistListeners.add(listener); + + return () => { + this._blacklistListeners.delete(listener); + }; + } + async addModerator(requestorNpub, moderatorNpub) { if (!this.isSuperAdmin(requestorNpub)) { return { ok: false, error: "forbidden" }; diff --git a/js/services/moderationService.js b/js/services/moderationService.js index 6562567c..3e0e5ad5 100644 --- a/js/services/moderationService.js +++ b/js/services/moderationService.js @@ -520,9 +520,11 @@ export class ModerationService { } if (this.trustedSeedContacts instanceof Set) { + const adminSnapshot = this.getAdminListSnapshot(); + const blacklistHex = adminSnapshot.blacklistHex; for (const seed of this.trustedSeedContacts) { const normalized = normalizeHex(seed); - if (normalized) { + if (normalized && (!blacklistHex || !blacklistHex.has(normalized))) { merged.add(normalized); } } @@ -635,18 +637,20 @@ export class ModerationService { setTrustedSeeds(seeds = []) { const sanitizedSeeds = new Set(); + const adminSnapshot = this.getAdminListSnapshot(); + const blacklistHex = adminSnapshot.blacklistHex; if (seeds instanceof Set || Array.isArray(seeds)) { for (const candidate of seeds) { const normalized = normalizeToHex(candidate); - if (normalized) { + if (normalized && (!blacklistHex || !blacklistHex.has(normalized))) { sanitizedSeeds.add(normalized); } } } else if (seeds && typeof seeds[Symbol.iterator] === "function") { for (const candidate of seeds) { const normalized = normalizeToHex(candidate); - if (normalized) { + if (normalized && (!blacklistHex || !blacklistHex.has(normalized))) { sanitizedSeeds.add(normalized); } } @@ -845,7 +849,7 @@ export class ModerationService { this.emit("trusted-mutes", { total: 0 }); - this.rebuildTrustedContacts(new Set(), { previous }); + this.rebuildTrustedContacts(new Set(), { previous: new Set() }); } isTrustedMuteOwner(pubkey) { diff --git a/js/services/trustBootstrap.js b/js/services/trustBootstrap.js index 8ac79c6a..d878d2a2 100644 --- a/js/services/trustBootstrap.js +++ b/js/services/trustBootstrap.js @@ -178,4 +178,8 @@ export async function bootstrapTrustedSeeds() { if (accessControl && typeof accessControl.onEditorsChange === "function") { accessControl.onEditorsChange(applyOnChange); } + + if (accessControl && typeof accessControl.onBlacklistChange === "function") { + accessControl.onBlacklistChange(applyOnChange); + } }