mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-10-27 16:02:26 +00:00
174 lines
4.4 KiB
JavaScript
174 lines
4.4 KiB
JavaScript
// js/utils/profileBatchFetcher.js
|
|
|
|
import { nostrClient } from "../nostrClientFacade.js";
|
|
import { devLogger } from "./logger.js";
|
|
|
|
const DEFAULT_PROFILE_IMAGE = "assets/svg/default-profile.svg";
|
|
const HEX64_REGEX = /^[0-9a-f]{64}$/i;
|
|
|
|
export async function batchFetchProfilesFromRelays({
|
|
authorSet,
|
|
getProfileCacheEntry,
|
|
setProfileCacheEntry,
|
|
updateProfileInDOM,
|
|
nostr = nostrClient,
|
|
logger = devLogger,
|
|
hex64Regex = HEX64_REGEX,
|
|
defaultProfileImage = DEFAULT_PROFILE_IMAGE,
|
|
} = {}) {
|
|
if (typeof getProfileCacheEntry !== "function") {
|
|
throw new TypeError("getProfileCacheEntry callback is required");
|
|
}
|
|
if (typeof setProfileCacheEntry !== "function") {
|
|
throw new TypeError("setProfileCacheEntry callback is required");
|
|
}
|
|
if (typeof updateProfileInDOM !== "function") {
|
|
throw new TypeError("updateProfileInDOM callback is required");
|
|
}
|
|
|
|
const pubkeys = Array.isArray(authorSet)
|
|
? authorSet
|
|
: Array.from(authorSet || []);
|
|
if (!pubkeys.length) {
|
|
return;
|
|
}
|
|
|
|
const toFetch = [];
|
|
|
|
pubkeys.forEach((pubkey) => {
|
|
const normalizedPubkey =
|
|
typeof pubkey === "string" ? pubkey.trim() : "";
|
|
const hasValidPubkey =
|
|
normalizedPubkey && hex64Regex.test(normalizedPubkey);
|
|
|
|
if (!hasValidPubkey) {
|
|
return;
|
|
}
|
|
|
|
const cacheEntry = getProfileCacheEntry(normalizedPubkey);
|
|
if (cacheEntry) {
|
|
updateProfileInDOM(normalizedPubkey, cacheEntry.profile);
|
|
} else {
|
|
toFetch.push(normalizedPubkey);
|
|
}
|
|
});
|
|
|
|
if (!toFetch.length) {
|
|
return;
|
|
}
|
|
|
|
if (!nostr?.pool || typeof nostr.pool.list !== "function") {
|
|
logger.warn(
|
|
"[batchFetchProfiles] Relay pool is not ready; skipping profile fetch.",
|
|
);
|
|
return;
|
|
}
|
|
|
|
const filter = {
|
|
kinds: [0],
|
|
authors: toFetch,
|
|
limit: toFetch.length,
|
|
};
|
|
|
|
const newestProfiles = new Map();
|
|
|
|
const applyProfileEvent = (evt, relayUrl) => {
|
|
if (!evt || typeof evt !== "object") {
|
|
return;
|
|
}
|
|
|
|
const pubkey =
|
|
typeof evt.pubkey === "string" && hex64Regex.test(evt.pubkey)
|
|
? evt.pubkey
|
|
: null;
|
|
if (!pubkey) {
|
|
return;
|
|
}
|
|
|
|
const createdAt = Number.isFinite(evt.created_at) ? evt.created_at : 0;
|
|
const previous = newestProfiles.get(pubkey);
|
|
if (previous && previous.createdAt >= createdAt) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const data = JSON.parse(evt.content);
|
|
const profile = {
|
|
name: data?.name || data?.display_name || "Unknown",
|
|
picture: data?.picture || defaultProfileImage,
|
|
};
|
|
|
|
newestProfiles.set(pubkey, { createdAt, profile });
|
|
setProfileCacheEntry(pubkey, profile);
|
|
updateProfileInDOM(pubkey, profile);
|
|
} catch (error) {
|
|
logger.warn(
|
|
`[batchFetchProfiles] Profile parse error for ${pubkey} from ${relayUrl}:`,
|
|
error,
|
|
);
|
|
}
|
|
};
|
|
|
|
const relayPromises = (Array.isArray(nostr.relays) ? nostr.relays : []).map(
|
|
(relayUrl) =>
|
|
nostr.pool
|
|
.list([relayUrl], [filter])
|
|
.then((events) => ({ relayUrl, events })),
|
|
);
|
|
|
|
if (!relayPromises.length) {
|
|
logger.warn(
|
|
"[batchFetchProfiles] No relays configured; skipping profile fetch.",
|
|
);
|
|
return;
|
|
}
|
|
|
|
const results = await Promise.allSettled(relayPromises);
|
|
|
|
results.forEach((result, index) => {
|
|
const relayUrl =
|
|
result.status === "fulfilled"
|
|
? result.value.relayUrl
|
|
: nostr.relays[index];
|
|
|
|
if (result.status === "fulfilled") {
|
|
const events = Array.isArray(result.value.events)
|
|
? result.value.events
|
|
: [];
|
|
if (!events.length) {
|
|
return;
|
|
}
|
|
|
|
const newestByPubkey = new Map();
|
|
for (const evt of events) {
|
|
if (!evt || typeof evt !== "object") {
|
|
continue;
|
|
}
|
|
const pubkey =
|
|
typeof evt.pubkey === "string" && hex64Regex.test(evt.pubkey)
|
|
? evt.pubkey
|
|
: null;
|
|
if (!pubkey) {
|
|
continue;
|
|
}
|
|
const createdAt = Number.isFinite(evt.created_at)
|
|
? evt.created_at
|
|
: 0;
|
|
const prior = newestByPubkey.get(pubkey);
|
|
if (!prior || createdAt > prior.createdAt) {
|
|
newestByPubkey.set(pubkey, { createdAt, event: evt });
|
|
}
|
|
}
|
|
|
|
newestByPubkey.forEach(({ event }) => {
|
|
applyProfileEvent(event, relayUrl);
|
|
});
|
|
} else if (relayUrl) {
|
|
logger.warn(
|
|
`[batchFetchProfiles] Failed to fetch profiles from relay ${relayUrl}:`,
|
|
result.reason,
|
|
);
|
|
}
|
|
});
|
|
}
|