Files
bitvid/js/services/trustBootstrap.js
T
google-labs-jules[bot] acd2104067 Fix(startup): Defer trusted seed bootstrapping until after Nostr init
- Moves `bootstrapTrustedSeeds` logic from `js/bootstrap.js` to a new service `js/services/trustBootstrap.js`.
- Updates `js/app.js` to explicitly await `nostrClient.init()` before calling `bootstrapTrustedSeeds()`, resolving race conditions where admin lists were fetched before the relay pool was ready.
- Ensures `waitForRelaysReady` checks for `nostrClient.pool` existence.
2026-01-05 21:12:04 +00:00

182 lines
4.5 KiB
JavaScript

// js/services/trustBootstrap.js
import {
DEFAULT_TRUST_SEED_NPUBS,
FEATURE_TRUST_SEEDS,
} from "../constants.js";
import { ADMIN_SUPER_NPUB } from "../config.js";
import { accessControl } from "../accessControl.js";
import moderationService from "./moderationService.js";
import nostrService from "./nostrService.js";
import { devLogger, userLogger } from "../utils/logger.js";
const TRUST_SEED_READY_TIMEOUT_MS = 3500;
function normalizeNpub(value) {
return typeof value === "string" ? value.trim() : "";
}
function buildTrustedSeeds({ superAdmin, editors, fallbackSeeds }) {
const seeds = new Set();
const addSeed = (value) => {
const normalized = normalizeNpub(value);
if (normalized) {
seeds.add(normalized);
}
};
addSeed(superAdmin);
if (editors && typeof editors[Symbol.iterator] === "function") {
for (const value of editors) {
addSeed(value);
}
}
if (!seeds.size && fallbackSeeds && typeof fallbackSeeds[Symbol.iterator] === "function") {
for (const value of fallbackSeeds) {
addSeed(value);
}
}
return seeds;
}
function delay(ms = 0) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
async function waitForRelaysReady({ timeoutMs = TRUST_SEED_READY_TIMEOUT_MS } = {}) {
const start = Date.now();
while (Date.now() - start < timeoutMs) {
if (
nostrService?.nostrClient?.pool &&
nostrService?.nostrClient?.relays &&
Array.isArray(nostrService.nostrClient.relays) &&
nostrService.nostrClient.relays.length > 0
) {
return true;
}
await delay(125);
}
return false;
}
async function waitForAccessControl({ timeoutMs = TRUST_SEED_READY_TIMEOUT_MS } = {}) {
if (!accessControl || typeof accessControl.ensureReady !== "function") {
return { ok: true, timedOut: false };
}
let timedOut = false;
const timeoutPromise = new Promise((_, reject) => {
setTimeout(() => {
timedOut = true;
reject(new Error("accessControl ensureReady timed out"));
}, timeoutMs);
});
try {
await Promise.race([accessControl.ensureReady(), timeoutPromise]);
return { ok: true, timedOut: false };
} catch (error) {
if (timedOut) {
devLogger.warn(
"[bootstrap] access control hydration timed out; falling back to default trust seeds.",
error,
);
return { ok: true, timedOut: true };
}
throw error;
}
}
export async function bootstrapTrustedSeeds() {
if (
!(
FEATURE_TRUST_SEEDS &&
moderationService &&
typeof moderationService.setTrustedSeeds === "function"
)
) {
return;
}
const applySeeds = () => {
try {
const editors =
accessControl && typeof accessControl.getEditors === "function"
? accessControl.getEditors()
: [];
const seeds = buildTrustedSeeds({
superAdmin: ADMIN_SUPER_NPUB,
editors,
fallbackSeeds: DEFAULT_TRUST_SEED_NPUBS,
});
moderationService.setTrustedSeeds(seeds);
if (
moderationService &&
typeof moderationService.recomputeAllSummaries === "function"
) {
moderationService.recomputeAllSummaries();
}
} catch (error) {
userLogger.warn("[bootstrap] Failed to apply trusted seeds", error);
}
};
let attempts = 0;
const hydrate = async () => {
attempts += 1;
try {
const result = await waitForAccessControl();
applySeeds();
return result;
} catch (error) {
userLogger.warn(
"[bootstrap] Failed to hydrate admin lists for trusted seeds",
error,
);
return { ok: false, timedOut: false, error };
}
};
const hydrateResult = await hydrate();
const runAsyncRetry = async () => {
if (hydrateResult?.ok || attempts >= 2) {
return;
}
const relaysReady = await waitForRelaysReady();
if (relaysReady) {
await hydrate();
} else {
devLogger.warn(
"[bootstrap] Skipping trusted seed retry because relays were not ready in time.",
);
applySeeds();
}
};
runAsyncRetry().catch((error) => {
userLogger.warn("[bootstrap] Trusted seed retry failed", error);
});
if (!hydrateResult?.ok) {
applySeeds();
}
const applyOnChange = () => {
applySeeds();
};
if (accessControl && typeof accessControl.onWhitelistChange === "function") {
accessControl.onWhitelistChange(applyOnChange);
}
if (accessControl && typeof accessControl.onEditorsChange === "function") {
accessControl.onEditorsChange(applyOnChange);
}
}