mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-10-28 00:12:25 +00:00
feat: seed anonymous session actors with baseline block list
- Added `seedLocalBaseline` to `js/userBlocks.js` to merge admin blacklist into local block list for session actors. - Implemented `_saveLocal` and `_loadLocal` in `js/userBlocks.js` to persist local-only block lists to `localStorage`. - Updated `loadBlocks` to prioritize local storage for session actors, bypassing relay fetching when local data exists. - Modified `js/app.js` to call `seedLocalBaseline` with the admin blacklist during initialization if the user is anonymous. - Ensured `UserBlockListManager` correctly resolves `localStorage` in various environments (browser/test). - Verified changes with `tests/user-blocks-local-seed.test.mjs`.
This commit is contained in:
1 parent
760eeb906e
commit
c080b83108
2 files changed
+182
-5
No files matched your search
@@ -482,6 +482,21 @@ class Application {
|
||||
|
||||
await Promise.all([accessControlPromise, adminPanePromise]);
|
||||
|
||||
if (!this.pubkey && nostrClient.sessionActor?.pubkey) {
|
||||
const blacklist = accessControl.getBlacklist();
|
||||
try {
|
||||
await userBlocks.seedLocalBaseline(
|
||||
nostrClient.sessionActor.pubkey,
|
||||
Array.from(blacklist || []),
|
||||
);
|
||||
} catch (error) {
|
||||
devLogger.warn(
|
||||
"[app.init()] Failed to seed local baseline for session actor:",
|
||||
error,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Grab the "Subscriptions" link by its id in the sidebar
|
||||
this.subscriptionsLink = document.getElementById("subscriptionsLink");
|
||||
|
||||
|
||||
+167
-5
@@ -67,6 +67,7 @@ const BACKGROUND_BLOCKLIST_TIMEOUT_MS = 6000;
|
||||
const BLOCKLIST_STORAGE_PREFIX = "bitvid:user-blocks";
|
||||
const BLOCKLIST_SEEDED_KEY_PREFIX = `${BLOCKLIST_STORAGE_PREFIX}:seeded:v1`;
|
||||
const BLOCKLIST_REMOVALS_KEY_PREFIX = `${BLOCKLIST_STORAGE_PREFIX}:removals:v1`;
|
||||
const BLOCKLIST_LOCAL_KEY_PREFIX = `${BLOCKLIST_STORAGE_PREFIX}:local:v1`;
|
||||
|
||||
function normalizeEncryptionToken(value) {
|
||||
if (typeof value !== "string") {
|
||||
@@ -434,7 +435,8 @@ function writeSeededFlag(actorHex, seeded) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof localStorage === "undefined") {
|
||||
const storage = resolveStorage();
|
||||
if (!storage) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -539,6 +541,85 @@ function normalizeHex(pubkey) {
|
||||
return null;
|
||||
}
|
||||
|
||||
function resolveStorage() {
|
||||
if (typeof localStorage !== "undefined") {
|
||||
return localStorage;
|
||||
}
|
||||
if (typeof window !== "undefined" && window.localStorage) {
|
||||
return window.localStorage;
|
||||
}
|
||||
if (typeof globalThis !== "undefined" && globalThis.localStorage) {
|
||||
return globalThis.localStorage;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function readLocalBlocks(actorHex) {
|
||||
if (typeof actorHex !== "string" || !actorHex) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const storage = resolveStorage();
|
||||
if (!storage) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const key = `${BLOCKLIST_LOCAL_KEY_PREFIX}:${actorHex}`;
|
||||
|
||||
try {
|
||||
const raw = storage.getItem(key);
|
||||
if (raw === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const parsed = JSON.parse(raw);
|
||||
if (!Array.isArray(parsed)) {
|
||||
return new Set();
|
||||
}
|
||||
|
||||
const normalized = parsed
|
||||
.map((entry) =>
|
||||
typeof entry === "string" ? entry.trim().toLowerCase() : "",
|
||||
)
|
||||
.filter((entry) => /^[0-9a-f]{64}$/.test(entry));
|
||||
|
||||
return new Set(normalized);
|
||||
} catch (error) {
|
||||
userLogger.warn(
|
||||
`[UserBlockList] Failed to read local blocks for ${actorHex}:`,
|
||||
error,
|
||||
);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function writeLocalBlocks(actorHex, blocks) {
|
||||
if (typeof actorHex !== "string" || !actorHex) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof localStorage === "undefined") {
|
||||
return;
|
||||
}
|
||||
|
||||
const key = `${BLOCKLIST_LOCAL_KEY_PREFIX}:${actorHex}`;
|
||||
|
||||
try {
|
||||
if (!blocks || !blocks.size) {
|
||||
// We persist empty sets as [] to distinguish from "not found" (null)
|
||||
storage.setItem(key, "[]");
|
||||
return;
|
||||
}
|
||||
|
||||
storage.setItem(key, JSON.stringify(Array.from(blocks)));
|
||||
} catch (error) {
|
||||
userLogger.warn(
|
||||
`[UserBlockList] Failed to persist local blocks for ${actorHex}:`,
|
||||
error,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class UserBlockListManager {
|
||||
constructor() {
|
||||
this.blockedPubkeys = new Set();
|
||||
@@ -631,6 +712,19 @@ class UserBlockListManager {
|
||||
|
||||
emitStatus({ status: "loading", relays: Array.from(nostrClient.relays || []) });
|
||||
|
||||
const localBlocks = this._loadLocal(normalized);
|
||||
if (localBlocks) {
|
||||
this.blockedPubkeys = localBlocks;
|
||||
this.loaded = true;
|
||||
this.emitter.emit(USER_BLOCK_EVENTS.CHANGE, {
|
||||
action: "sync",
|
||||
blockedPubkeys: Array.from(this.blockedPubkeys),
|
||||
source: "local",
|
||||
});
|
||||
emitStatus({ status: "settled" });
|
||||
return;
|
||||
}
|
||||
|
||||
const applyBlockedPubkeys = (nextValues, meta = {}) => {
|
||||
const nextSet = new Set(Array.isArray(nextValues) ? nextValues : []);
|
||||
this.blockedPubkeys = nextSet;
|
||||
@@ -1158,6 +1252,14 @@ class UserBlockListManager {
|
||||
}
|
||||
}
|
||||
|
||||
_loadLocal(actorHex) {
|
||||
return readLocalBlocks(actorHex);
|
||||
}
|
||||
|
||||
_saveLocal(actorHex) {
|
||||
writeLocalBlocks(actorHex, this.blockedPubkeys);
|
||||
}
|
||||
|
||||
_getSeedState(actorHex) {
|
||||
const normalized = normalizeHex(actorHex);
|
||||
if (!normalized) {
|
||||
@@ -1226,10 +1328,6 @@ class UserBlockListManager {
|
||||
return { ok: true, seeded: false, reason: "already-seeded" };
|
||||
}
|
||||
|
||||
if (this.blockedPubkeys.size > 0) {
|
||||
return { ok: true, seeded: false, reason: "non-empty" };
|
||||
}
|
||||
|
||||
const removals = state.removals;
|
||||
const additions = new Set();
|
||||
|
||||
@@ -1242,6 +1340,9 @@ class UserBlockListManager {
|
||||
if (candidateHex === actorHex) {
|
||||
continue;
|
||||
}
|
||||
if (this.blockedPubkeys.has(candidateHex)) {
|
||||
continue;
|
||||
}
|
||||
if (removals.has(candidateHex)) {
|
||||
continue;
|
||||
}
|
||||
@@ -1264,6 +1365,7 @@ class UserBlockListManager {
|
||||
|
||||
this._setSeeded(actorHex, true);
|
||||
additions.forEach((hex) => this._clearSeedRemoval(actorHex, hex));
|
||||
this._saveLocal(actorHex);
|
||||
|
||||
try {
|
||||
this.emitter.emit(USER_BLOCK_EVENTS.CHANGE, {
|
||||
@@ -1279,6 +1381,66 @@ class UserBlockListManager {
|
||||
return { ok: true, seeded: true, addedPubkeys: Array.from(additions) };
|
||||
}
|
||||
|
||||
async seedLocalBaseline(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);
|
||||
if (state.seeded) {
|
||||
return { ok: true, seeded: false, reason: "already-seeded" };
|
||||
}
|
||||
|
||||
if (this.blockedPubkeys.size > 0) {
|
||||
return { ok: true, seeded: false, reason: "non-empty" };
|
||||
}
|
||||
|
||||
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 (removals.has(candidateHex)) {
|
||||
continue;
|
||||
}
|
||||
additions.add(candidateHex);
|
||||
}
|
||||
|
||||
if (!additions.size) {
|
||||
this._setSeeded(actorHex, true);
|
||||
return { ok: true, seeded: true, addedPubkeys: [] };
|
||||
}
|
||||
|
||||
additions.forEach((hex) => this.blockedPubkeys.add(hex));
|
||||
|
||||
this._setSeeded(actorHex, true);
|
||||
additions.forEach((hex) => this._clearSeedRemoval(actorHex, hex));
|
||||
|
||||
try {
|
||||
this.emitter.emit(USER_BLOCK_EVENTS.CHANGE, {
|
||||
action: "seed",
|
||||
actorPubkey: actorHex,
|
||||
blockedPubkeys: Array.from(this.blockedPubkeys),
|
||||
addedPubkeys: Array.from(additions),
|
||||
localOnly: true,
|
||||
});
|
||||
} catch (error) {
|
||||
userLogger.warn("[UserBlockList] Failed to emit local seed change event:", error);
|
||||
}
|
||||
|
||||
return { ok: true, seeded: true, addedPubkeys: Array.from(additions) };
|
||||
}
|
||||
|
||||
async addBlock(targetPubkey, userPubkey) {
|
||||
const actorHex = normalizeHex(userPubkey);
|
||||
if (!actorHex) {
|
||||
|
||||
Reference in new issue
Block a user