mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-10-27 07:52:24 +00:00
Optimize admin list publish flow
This commit is contained in:
1 parent
0f0f2b2cb1
commit
ab4b80470f
2 files changed
+190
-19
No files matched your search
+127
-17
@@ -16,7 +16,7 @@ import {
|
||||
buildAdminListEvent,
|
||||
ADMIN_LIST_IDENTIFIERS,
|
||||
} from "./nostrEventSchemas.js";
|
||||
import { publishEventToRelays } from "./nostrPublish.js";
|
||||
import { publishEventToRelay } from "./nostrPublish.js";
|
||||
|
||||
const LEGACY_STORAGE_KEYS = {
|
||||
editors: "bitvid_admin_editors",
|
||||
@@ -304,7 +304,16 @@ function ensureNostrReady() {
|
||||
);
|
||||
}
|
||||
|
||||
const relays = Array.isArray(nostrClient.relays) ? nostrClient.relays : [];
|
||||
const writeRelays =
|
||||
Array.isArray(nostrClient.writeRelays) && nostrClient.writeRelays.length
|
||||
? nostrClient.writeRelays
|
||||
: [];
|
||||
const relays = writeRelays.length
|
||||
? writeRelays
|
||||
: Array.isArray(nostrClient.relays)
|
||||
? nostrClient.relays
|
||||
: [];
|
||||
|
||||
if (!relays.length) {
|
||||
throw createError("nostr-unavailable", "No Nostr relays configured.");
|
||||
}
|
||||
@@ -533,6 +542,94 @@ function buildListEvent(listKey, npubs, actorHex) {
|
||||
});
|
||||
}
|
||||
|
||||
function publishListWithFirstAcceptance(pool, relays, event, options = {}) {
|
||||
const publishRelay =
|
||||
typeof options.publishRelay === "function"
|
||||
? options.publishRelay
|
||||
: publishEventToRelay;
|
||||
const listKey = typeof options.listKey === "string" ? options.listKey : "";
|
||||
const publishOptions =
|
||||
options && typeof options.publishOptions === "object"
|
||||
? options.publishOptions
|
||||
: undefined;
|
||||
|
||||
const relayPromises = Array.isArray(relays)
|
||||
? relays.map((url) => publishRelay(pool, url, event, publishOptions))
|
||||
: [];
|
||||
const resultsBuffer = new Array(relayPromises.length);
|
||||
|
||||
let resolved = false;
|
||||
let remaining = relayPromises.length;
|
||||
|
||||
const firstAcceptance = new Promise((resolve, reject) => {
|
||||
if (!relayPromises.length) {
|
||||
reject(
|
||||
createError(
|
||||
"publish-failed",
|
||||
listKey
|
||||
? `Failed to publish ${listKey} list to any relay.`
|
||||
: "Failed to publish admin list to any relay.",
|
||||
)
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
relayPromises.forEach((promise, index) => {
|
||||
promise
|
||||
.then((result) => {
|
||||
resultsBuffer[index] = result;
|
||||
if (result?.success && !resolved) {
|
||||
resolved = true;
|
||||
resolve(result);
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
resultsBuffer[index] = {
|
||||
url: Array.isArray(relays) ? relays[index] : "",
|
||||
success: false,
|
||||
error,
|
||||
};
|
||||
})
|
||||
.finally(() => {
|
||||
remaining -= 1;
|
||||
if (!resolved && remaining === 0) {
|
||||
reject(
|
||||
createError(
|
||||
"publish-failed",
|
||||
listKey
|
||||
? `Failed to publish ${listKey} list to any relay.`
|
||||
: "Failed to publish admin list to any relay.",
|
||||
)
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
const allResults = Promise.allSettled(relayPromises).then((entries) => {
|
||||
for (let index = 0; index < entries.length; index += 1) {
|
||||
const entry = entries[index];
|
||||
const fallbackUrl = Array.isArray(relays) ? relays[index] : "";
|
||||
const result =
|
||||
entry?.status === "fulfilled"
|
||||
? entry.value
|
||||
: {
|
||||
url: fallbackUrl,
|
||||
success: false,
|
||||
error: entry?.reason || new Error("publish failed"),
|
||||
};
|
||||
|
||||
if (typeof resultsBuffer[index] === "undefined") {
|
||||
resultsBuffer[index] = result;
|
||||
}
|
||||
}
|
||||
|
||||
return resultsBuffer;
|
||||
});
|
||||
|
||||
return { firstAcceptance, allResults, resultsBuffer };
|
||||
}
|
||||
|
||||
async function persistNostrState(actorNpub, updates = {}) {
|
||||
ensureNostrReady();
|
||||
const permissionResult = await requestDefaultExtensionPermissions();
|
||||
@@ -620,29 +717,41 @@ async function persistNostrState(actorNpub, updates = {}) {
|
||||
}
|
||||
|
||||
const relays = ensureNostrReady();
|
||||
const publishResults = await publishEventToRelays(
|
||||
const { firstAcceptance, allResults } = publishListWithFirstAcceptance(
|
||||
nostrClient.pool,
|
||||
relays,
|
||||
signedEvent,
|
||||
{ listKey },
|
||||
);
|
||||
|
||||
if (isDevMode) {
|
||||
publishResults
|
||||
.filter((result) => !result.success)
|
||||
.forEach((result) => {
|
||||
userLogger.warn(
|
||||
`[adminListStore] Publish failed for ${listKey} on ${result.url}:`,
|
||||
result.error,
|
||||
);
|
||||
});
|
||||
const logSettledResults = allResults.then((results) => {
|
||||
if (isDevMode) {
|
||||
results
|
||||
.filter((result) => !result?.success)
|
||||
.forEach((result) => {
|
||||
userLogger.warn(
|
||||
`[adminListStore] Publish failed for ${listKey} on ${result.url}:`,
|
||||
result.error,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
return results;
|
||||
});
|
||||
|
||||
try {
|
||||
await firstAcceptance;
|
||||
} catch (error) {
|
||||
await logSettledResults.catch(() => {});
|
||||
throw error;
|
||||
}
|
||||
|
||||
if (!publishResults.some((result) => result.success)) {
|
||||
throw createError(
|
||||
"publish-failed",
|
||||
`Failed to publish ${listKey} list to any relay.`
|
||||
logSettledResults.catch((loggingError) => {
|
||||
devLogger.warn(
|
||||
`[adminListStore] Failed to summarize publish results for ${listKey}:`,
|
||||
loggingError,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
clearLegacyStorageFor(listKey);
|
||||
}
|
||||
@@ -682,4 +791,5 @@ export async function persistAdminState(actorNpub, updates) {
|
||||
export const __adminListStoreTestHooks = Object.freeze({
|
||||
extractNpubsFromEvent,
|
||||
normalizeParticipantTagValue,
|
||||
publishListWithFirstAcceptance,
|
||||
});
|
||||
@@ -37,8 +37,11 @@ globalThis.window.NostrTools.nip19 = mockNip19;
|
||||
|
||||
const { __adminListStoreTestHooks } = await import("../js/adminListStore.js");
|
||||
|
||||
const { extractNpubsFromEvent, normalizeParticipantTagValue } =
|
||||
__adminListStoreTestHooks;
|
||||
const {
|
||||
extractNpubsFromEvent,
|
||||
normalizeParticipantTagValue,
|
||||
publishListWithFirstAcceptance,
|
||||
} = __adminListStoreTestHooks;
|
||||
|
||||
const sampleHex = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef";
|
||||
const sampleNpub = "npub1existingparticipant";
|
||||
@@ -86,4 +89,62 @@ assert.equal(
|
||||
"should preserve hex when nip19 helpers are unavailable"
|
||||
);
|
||||
|
||||
const relays = [
|
||||
"wss://fast.example",
|
||||
"wss://slow.example",
|
||||
];
|
||||
|
||||
for (const listKey of ["whitelist", "blacklist"]) {
|
||||
const start = Date.now();
|
||||
const publishBehavior = publishListWithFirstAcceptance(
|
||||
{},
|
||||
relays,
|
||||
{ id: `event-${listKey}` },
|
||||
{
|
||||
listKey,
|
||||
publishRelay: (_pool, url) =>
|
||||
new Promise((resolve) => {
|
||||
const isFast = url === relays[0];
|
||||
const delayMs = isFast ? 5 : 200;
|
||||
setTimeout(() => {
|
||||
resolve({
|
||||
url,
|
||||
success: isFast,
|
||||
error: isFast ? null : new Error("relay rejected"),
|
||||
});
|
||||
}, delayMs);
|
||||
}),
|
||||
},
|
||||
);
|
||||
|
||||
const acceptanceResult = await publishBehavior.firstAcceptance;
|
||||
const elapsed = Date.now() - start;
|
||||
|
||||
assert.equal(
|
||||
acceptanceResult.url,
|
||||
relays[0],
|
||||
`should resolve using the fast relay for ${listKey}`,
|
||||
);
|
||||
|
||||
assert.ok(
|
||||
elapsed < 150,
|
||||
`expected ${listKey} publish to resolve before slow relay timeout (elapsed ${elapsed}ms)`,
|
||||
);
|
||||
|
||||
const settledResults = await publishBehavior.allResults;
|
||||
|
||||
assert.equal(
|
||||
settledResults.length,
|
||||
relays.length,
|
||||
`should collect all publish results for ${listKey}`,
|
||||
);
|
||||
|
||||
const acceptedCount = settledResults.filter((entry) => entry.success).length;
|
||||
assert.equal(
|
||||
acceptedCount,
|
||||
1,
|
||||
`should mark exactly one relay as successful for ${listKey}`,
|
||||
);
|
||||
}
|
||||
|
||||
console.log("admin-list-store tests passed");
|
||||
Reference in new issue
Block a user