mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-11-04 11:52:25 +00:00
Merge pull request #390 from PR0M3TH3AN/codex/new-task
Speed up profile and relay fetches
This commit is contained in:
3 files changed
+547
-103
No files matched your search
+170
-30
@@ -8,6 +8,10 @@ import {
|
||||
|
||||
const MODE_SEQUENCE = ["both", "read", "write"];
|
||||
|
||||
const FAST_RELAY_FETCH_LIMIT = 3;
|
||||
const FAST_RELAY_TIMEOUT_MS = 2500;
|
||||
const BACKGROUND_RELAY_TIMEOUT_MS = 6000;
|
||||
|
||||
function normalizeHexPubkey(pubkey) {
|
||||
if (typeof pubkey !== "string") {
|
||||
return null;
|
||||
@@ -413,44 +417,180 @@ class RelayPreferencesManager {
|
||||
|
||||
const filter = { kinds: [10002], authors: [normalizedPubkey], limit: 1 };
|
||||
const targetRelays = this.getPublishTargets();
|
||||
const events = [];
|
||||
|
||||
await Promise.all(
|
||||
targetRelays.map(async (relayUrl) => {
|
||||
try {
|
||||
const result = await nostrClient.pool.list([relayUrl], [filter]);
|
||||
if (Array.isArray(result) && result.length) {
|
||||
events.push(...result);
|
||||
}
|
||||
} catch (error) {
|
||||
if (isDevMode) {
|
||||
console.warn(`[relayManager] Failed to fetch relay list from ${relayUrl}:`, error);
|
||||
}
|
||||
const applyEvents = (events, { skipIfEmpty = false } = {}) => {
|
||||
if (!Array.isArray(events) || !events.length) {
|
||||
if (skipIfEmpty) {
|
||||
return null;
|
||||
}
|
||||
})
|
||||
);
|
||||
this.lastEvent = null;
|
||||
this.lastLoadSource = "default";
|
||||
this.setEntries(this.defaultEntries, { allowEmpty: false });
|
||||
return { ok: true, source: "default", events: [] };
|
||||
}
|
||||
|
||||
const sorted = events
|
||||
.filter((event) => event && event.pubkey === normalizedPubkey)
|
||||
.sort((a, b) => (b?.created_at || 0) - (a?.created_at || 0));
|
||||
|
||||
if (!sorted.length) {
|
||||
if (skipIfEmpty) {
|
||||
return null;
|
||||
}
|
||||
this.lastEvent = null;
|
||||
this.lastLoadSource = "default";
|
||||
this.setEntries(this.defaultEntries, { allowEmpty: false });
|
||||
return { ok: true, source: "default", events: [] };
|
||||
}
|
||||
|
||||
const newest = sorted[0];
|
||||
this.lastEvent = newest;
|
||||
|
||||
const parsed = parseRelayTags(newest?.tags);
|
||||
if (parsed.length) {
|
||||
this.setEntries(parsed, { allowEmpty: false });
|
||||
this.lastLoadSource = "event";
|
||||
return { ok: true, source: "event", event: newest };
|
||||
}
|
||||
|
||||
if (!events.length) {
|
||||
this.lastEvent = null;
|
||||
this.lastLoadSource = "default";
|
||||
this.setEntries(this.defaultEntries, { allowEmpty: false });
|
||||
return { ok: true, source: "default", events: [] };
|
||||
return { ok: true, source: "default", event: newest };
|
||||
};
|
||||
|
||||
const fetchFromRelay = (relayUrl, timeoutMs, requireEvent) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
let settled = false;
|
||||
const timer = setTimeout(() => {
|
||||
if (settled) {
|
||||
return;
|
||||
}
|
||||
settled = true;
|
||||
const timeoutError = new Error(
|
||||
`Timed out fetching relay list from ${relayUrl} after ${timeoutMs}ms`
|
||||
);
|
||||
timeoutError.code = "timeout";
|
||||
timeoutError.relay = relayUrl;
|
||||
timeoutError.timeoutMs = timeoutMs;
|
||||
reject(timeoutError);
|
||||
}, timeoutMs);
|
||||
|
||||
Promise.resolve()
|
||||
.then(() => nostrClient.pool.list([relayUrl], [filter]))
|
||||
.then((result) => {
|
||||
if (settled) {
|
||||
return;
|
||||
}
|
||||
settled = true;
|
||||
clearTimeout(timer);
|
||||
const events = Array.isArray(result)
|
||||
? result.filter((event) => event && event.pubkey === normalizedPubkey)
|
||||
: [];
|
||||
if (requireEvent && !events.length) {
|
||||
const emptyError = new Error(
|
||||
`No relay list events returned from ${relayUrl}`
|
||||
);
|
||||
emptyError.code = "empty";
|
||||
emptyError.relay = relayUrl;
|
||||
reject(emptyError);
|
||||
return;
|
||||
}
|
||||
resolve({ relayUrl, events });
|
||||
})
|
||||
.catch((error) => {
|
||||
if (settled) {
|
||||
return;
|
||||
}
|
||||
settled = true;
|
||||
clearTimeout(timer);
|
||||
const wrapped =
|
||||
error instanceof Error ? error : new Error(String(error));
|
||||
wrapped.relay = relayUrl;
|
||||
reject(wrapped);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const fastRelays = targetRelays.slice(0, FAST_RELAY_FETCH_LIMIT);
|
||||
const backgroundRelays = targetRelays.slice(fastRelays.length);
|
||||
|
||||
const fastPromises = fastRelays.map((relayUrl) =>
|
||||
fetchFromRelay(relayUrl, FAST_RELAY_TIMEOUT_MS, true)
|
||||
);
|
||||
const backgroundPromises = backgroundRelays.map((relayUrl) =>
|
||||
fetchFromRelay(relayUrl, BACKGROUND_RELAY_TIMEOUT_MS, false)
|
||||
);
|
||||
|
||||
const background = Promise.allSettled([
|
||||
...fastPromises,
|
||||
...backgroundPromises,
|
||||
])
|
||||
.then((outcomes) => {
|
||||
const aggregated = [];
|
||||
outcomes.forEach((outcome) => {
|
||||
if (outcome.status === "fulfilled") {
|
||||
const events = Array.isArray(outcome.value?.events)
|
||||
? outcome.value.events
|
||||
: [];
|
||||
if (events.length) {
|
||||
aggregated.push(...events);
|
||||
}
|
||||
} else {
|
||||
const reason = outcome.reason;
|
||||
if (reason?.code === "timeout") {
|
||||
if (isDevMode) {
|
||||
console.warn(
|
||||
`[relayManager] Relay ${reason.relay} timed out while loading relay list (${reason.timeoutMs}ms)`
|
||||
);
|
||||
}
|
||||
} else if (isDevMode) {
|
||||
console.warn(
|
||||
`[relayManager] Relay ${reason?.relay || "unknown"} failed while loading relay list:`,
|
||||
reason
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (!aggregated.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
applyEvents(aggregated, { skipIfEmpty: true });
|
||||
})
|
||||
.catch((error) => {
|
||||
if (isDevMode) {
|
||||
console.warn("[relayManager] Background relay refresh failed", error);
|
||||
}
|
||||
});
|
||||
|
||||
let fastResult = null;
|
||||
if (fastPromises.length) {
|
||||
try {
|
||||
fastResult = await Promise.any(fastPromises);
|
||||
} catch (error) {
|
||||
if (error instanceof AggregateError) {
|
||||
error.errors?.forEach((err) => {
|
||||
if (err?.code === "timeout" && isDevMode) {
|
||||
console.warn(
|
||||
`[relayManager] Relay ${err.relay} timed out while loading relay list (${err.timeoutMs}ms)`
|
||||
);
|
||||
}
|
||||
});
|
||||
} else if (isDevMode) {
|
||||
console.warn("[relayManager] Fast relay fetch failed", error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
events.sort((a, b) => (b?.created_at || 0) - (a?.created_at || 0));
|
||||
const newest = events[0];
|
||||
this.lastEvent = newest;
|
||||
|
||||
const parsed = parseRelayTags(newest?.tags);
|
||||
if (parsed.length) {
|
||||
this.setEntries(parsed, { allowEmpty: false });
|
||||
this.lastLoadSource = "event";
|
||||
return { ok: true, source: "event", event: newest };
|
||||
if (fastResult?.events?.length) {
|
||||
const result = applyEvents(fastResult.events);
|
||||
background.catch(() => {});
|
||||
return result;
|
||||
}
|
||||
|
||||
this.lastLoadSource = "default";
|
||||
this.setEntries(this.defaultEntries, { allowEmpty: false });
|
||||
return { ok: true, source: "default", event: newest };
|
||||
const fallback = applyEvents([], { skipIfEmpty: false });
|
||||
background.catch(() => {});
|
||||
return fallback;
|
||||
}
|
||||
|
||||
async publishRelayList(pubkey, options = {}) {
|
||||
|
||||
+178
-10
@@ -72,6 +72,10 @@ const FALLBACK_PROFILE = {
|
||||
lud06: "",
|
||||
};
|
||||
|
||||
const FAST_PROFILE_RELAY_LIMIT = 3;
|
||||
const FAST_PROFILE_TIMEOUT_MS = 2500;
|
||||
const BACKGROUND_PROFILE_TIMEOUT_MS = 6000;
|
||||
|
||||
export default class AuthService {
|
||||
constructor({ nostrClient, userBlocks, relayManager, logger } = {}) {
|
||||
this.nostrClient = nostrClient || null;
|
||||
@@ -655,15 +659,37 @@ export default class AuthService {
|
||||
return FALLBACK_PROFILE;
|
||||
}
|
||||
|
||||
const events = await this.nostrClient.pool.list(this.nostrClient.relays, [
|
||||
{ kinds: [0], authors: [normalized], limit: 1 },
|
||||
]);
|
||||
const relays = Array.isArray(this.nostrClient.relays)
|
||||
? this.nostrClient.relays.filter((url) => typeof url === "string" && url)
|
||||
: [];
|
||||
|
||||
let profile = FALLBACK_PROFILE;
|
||||
if (Array.isArray(events) && events.length && events[0]?.content) {
|
||||
if (!relays.length) {
|
||||
this.setProfileCacheEntry(normalized, FALLBACK_PROFILE, {
|
||||
persist: true,
|
||||
reason: "load-own-profile:fallback",
|
||||
});
|
||||
return FALLBACK_PROFILE;
|
||||
}
|
||||
|
||||
const filter = [{ kinds: [0], authors: [normalized], limit: 1 }];
|
||||
|
||||
const selectNewestEvent = (eventList) => {
|
||||
if (!Array.isArray(eventList) || !eventList.length) {
|
||||
return null;
|
||||
}
|
||||
const sorted = eventList
|
||||
.filter((event) => event && event.pubkey === normalized)
|
||||
.sort((a, b) => (b?.created_at || 0) - (a?.created_at || 0));
|
||||
return sorted.length ? sorted[0] : null;
|
||||
};
|
||||
|
||||
const buildProfileFromEvent = (event) => {
|
||||
if (!event?.content) {
|
||||
return FALLBACK_PROFILE;
|
||||
}
|
||||
try {
|
||||
const data = JSON.parse(events[0].content);
|
||||
profile = {
|
||||
const data = JSON.parse(event.content);
|
||||
return {
|
||||
name: data.display_name || data.name || FALLBACK_PROFILE.name,
|
||||
picture: data.picture || FALLBACK_PROFILE.picture,
|
||||
about: typeof data.about === "string" ? data.about : FALLBACK_PROFILE.about,
|
||||
@@ -676,15 +702,157 @@ export default class AuthService {
|
||||
};
|
||||
} catch (error) {
|
||||
this.log("[AuthService] Failed to parse profile metadata", error);
|
||||
return FALLBACK_PROFILE;
|
||||
}
|
||||
};
|
||||
|
||||
const fetchFromRelay = (relayUrl, timeoutMs, requireEvent) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
let settled = false;
|
||||
const timer = setTimeout(() => {
|
||||
if (settled) {
|
||||
return;
|
||||
}
|
||||
settled = true;
|
||||
const timeoutError = new Error(
|
||||
`Timed out fetching profile from ${relayUrl} after ${timeoutMs}ms`
|
||||
);
|
||||
timeoutError.code = "timeout";
|
||||
timeoutError.relay = relayUrl;
|
||||
timeoutError.timeoutMs = timeoutMs;
|
||||
reject(timeoutError);
|
||||
}, timeoutMs);
|
||||
|
||||
Promise.resolve()
|
||||
.then(() => this.nostrClient.pool.list([relayUrl], filter))
|
||||
.then((result) => {
|
||||
if (settled) {
|
||||
return;
|
||||
}
|
||||
settled = true;
|
||||
clearTimeout(timer);
|
||||
const events = Array.isArray(result)
|
||||
? result.filter((event) => event && event.pubkey === normalized)
|
||||
: [];
|
||||
if (requireEvent && !events.length) {
|
||||
const emptyError = new Error(
|
||||
`No profile events returned from ${relayUrl}`
|
||||
);
|
||||
emptyError.code = "empty";
|
||||
emptyError.relay = relayUrl;
|
||||
reject(emptyError);
|
||||
return;
|
||||
}
|
||||
resolve({ relayUrl, events });
|
||||
})
|
||||
.catch((error) => {
|
||||
if (settled) {
|
||||
return;
|
||||
}
|
||||
settled = true;
|
||||
clearTimeout(timer);
|
||||
const wrapped =
|
||||
error instanceof Error ? error : new Error(String(error));
|
||||
wrapped.relay = relayUrl;
|
||||
reject(wrapped);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const fastRelays = relays.slice(0, FAST_PROFILE_RELAY_LIMIT);
|
||||
const backgroundRelays = relays.slice(fastRelays.length);
|
||||
|
||||
const fastPromises = fastRelays.map((relayUrl) =>
|
||||
fetchFromRelay(relayUrl, FAST_PROFILE_TIMEOUT_MS, true)
|
||||
);
|
||||
const backgroundPromises = backgroundRelays.map((relayUrl) =>
|
||||
fetchFromRelay(relayUrl, BACKGROUND_PROFILE_TIMEOUT_MS, false)
|
||||
);
|
||||
|
||||
const background = Promise.allSettled([
|
||||
...fastPromises,
|
||||
...backgroundPromises,
|
||||
])
|
||||
.then((outcomes) => {
|
||||
const aggregated = [];
|
||||
outcomes.forEach((outcome) => {
|
||||
if (outcome.status === "fulfilled") {
|
||||
const events = Array.isArray(outcome.value?.events)
|
||||
? outcome.value.events
|
||||
: [];
|
||||
if (events.length) {
|
||||
aggregated.push(...events);
|
||||
}
|
||||
} else if (outcome.status === "rejected") {
|
||||
const reason = outcome.reason;
|
||||
if (reason?.code === "timeout") {
|
||||
this.log(
|
||||
`[AuthService] Relay ${reason.relay} timed out loading profile (background)`
|
||||
);
|
||||
} else {
|
||||
this.log(
|
||||
`[AuthService] Relay ${reason?.relay || "unknown"} failed loading profile (background)`,
|
||||
reason
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const newest = selectNewestEvent(aggregated);
|
||||
if (!newest) {
|
||||
return;
|
||||
}
|
||||
|
||||
const profile = buildProfileFromEvent(newest);
|
||||
this.setProfileCacheEntry(normalized, profile, {
|
||||
persist: true,
|
||||
reason: "load-own-profile:background",
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
this.log("[AuthService] Background profile refresh failed", error);
|
||||
});
|
||||
|
||||
let fastResult = null;
|
||||
if (fastPromises.length) {
|
||||
try {
|
||||
fastResult = await Promise.any(fastPromises);
|
||||
} catch (error) {
|
||||
if (error instanceof AggregateError) {
|
||||
error.errors?.forEach((err) => {
|
||||
if (err?.code === "timeout") {
|
||||
this.log(
|
||||
`[AuthService] Relay ${err.relay} timed out loading profile`
|
||||
);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
this.log("[AuthService] Failed to load profile from fast relays", error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.setProfileCacheEntry(normalized, profile, {
|
||||
if (fastResult?.events?.length) {
|
||||
const newest = selectNewestEvent(fastResult.events);
|
||||
if (newest) {
|
||||
const profile = buildProfileFromEvent(newest);
|
||||
this.setProfileCacheEntry(normalized, profile, {
|
||||
persist: true,
|
||||
reason: "load-own-profile:fast",
|
||||
});
|
||||
background.catch(() => {});
|
||||
return profile;
|
||||
}
|
||||
}
|
||||
|
||||
this.setProfileCacheEntry(normalized, FALLBACK_PROFILE, {
|
||||
persist: true,
|
||||
reason: "load-own-profile",
|
||||
reason: "load-own-profile:fallback",
|
||||
});
|
||||
|
||||
return profile;
|
||||
background.catch(() => {});
|
||||
|
||||
return FALLBACK_PROFILE;
|
||||
}
|
||||
|
||||
async fetchProfile(pubkey, { forceRefresh = false } = {}) {
|
||||
|
||||
+199
-63
@@ -6,6 +6,10 @@ import {
|
||||
assertAnyRelayAccepted,
|
||||
} from "./nostrPublish.js";
|
||||
|
||||
const FAST_BLOCKLIST_RELAY_LIMIT = 3;
|
||||
const FAST_BLOCKLIST_TIMEOUT_MS = 2500;
|
||||
const BACKGROUND_BLOCKLIST_TIMEOUT_MS = 6000;
|
||||
|
||||
function normalizeHex(pubkey) {
|
||||
if (typeof pubkey !== "string") {
|
||||
return null;
|
||||
@@ -86,82 +90,214 @@ class UserBlockListManager {
|
||||
limit: 1,
|
||||
};
|
||||
|
||||
const events = [];
|
||||
const relays = Array.isArray(nostrClient.relays)
|
||||
? nostrClient.relays
|
||||
? nostrClient.relays.filter((relay) => typeof relay === "string" && relay)
|
||||
: [];
|
||||
|
||||
const results = await Promise.allSettled(
|
||||
relays.map((relay) =>
|
||||
Promise.resolve()
|
||||
.then(() => nostrClient.pool.list([relay], [filter]))
|
||||
.then((res) => ({ relay, res }))
|
||||
.catch((error) => {
|
||||
throw { relay, error };
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
for (const outcome of results) {
|
||||
if (outcome.status === "fulfilled") {
|
||||
const { res } = outcome.value || {};
|
||||
if (Array.isArray(res) && res.length) {
|
||||
events.push(...res);
|
||||
}
|
||||
} else {
|
||||
const { relay, error } = outcome.reason || {};
|
||||
console.error(
|
||||
`[UserBlockList] Relay error at ${relay}:`,
|
||||
error ?? outcome.reason
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (!events.length) {
|
||||
if (!relays.length) {
|
||||
this.blockedPubkeys.clear();
|
||||
this.blockEventId = null;
|
||||
this.loaded = true;
|
||||
return;
|
||||
}
|
||||
|
||||
events.sort((a, b) => b.created_at - a.created_at);
|
||||
const newest = events[0];
|
||||
this.blockEventId = newest.id;
|
||||
const fastRelays = relays.slice(0, FAST_BLOCKLIST_RELAY_LIMIT);
|
||||
const backgroundRelays = relays.slice(fastRelays.length);
|
||||
|
||||
let decrypted = "";
|
||||
try {
|
||||
decrypted = await window.nostr.nip04.decrypt(
|
||||
normalized,
|
||||
newest.content
|
||||
);
|
||||
} catch (err) {
|
||||
console.error("[UserBlockList] Failed to decrypt block list:", err);
|
||||
this.blockedPubkeys.clear();
|
||||
this.loaded = true;
|
||||
const fetchFromRelay = (relayUrl, timeoutMs, requireEvent) =>
|
||||
new Promise((resolve, reject) => {
|
||||
let settled = false;
|
||||
const timer = setTimeout(() => {
|
||||
if (settled) {
|
||||
return;
|
||||
}
|
||||
settled = true;
|
||||
const timeoutError = new Error(
|
||||
`Timed out fetching block list from ${relayUrl} after ${timeoutMs}ms`
|
||||
);
|
||||
timeoutError.code = "timeout";
|
||||
timeoutError.relay = relayUrl;
|
||||
timeoutError.timeoutMs = timeoutMs;
|
||||
reject(timeoutError);
|
||||
}, timeoutMs);
|
||||
|
||||
Promise.resolve()
|
||||
.then(() => nostrClient.pool.list([relayUrl], [filter]))
|
||||
.then((result) => {
|
||||
if (settled) {
|
||||
return;
|
||||
}
|
||||
settled = true;
|
||||
clearTimeout(timer);
|
||||
const events = Array.isArray(result)
|
||||
? result.filter((event) => event && event.pubkey === normalized)
|
||||
: [];
|
||||
if (requireEvent && !events.length) {
|
||||
const emptyError = new Error(
|
||||
`No block list events returned from ${relayUrl}`
|
||||
);
|
||||
emptyError.code = "empty";
|
||||
emptyError.relay = relayUrl;
|
||||
reject(emptyError);
|
||||
return;
|
||||
}
|
||||
resolve({ relayUrl, events });
|
||||
})
|
||||
.catch((error) => {
|
||||
if (settled) {
|
||||
return;
|
||||
}
|
||||
settled = true;
|
||||
clearTimeout(timer);
|
||||
const wrapped =
|
||||
error instanceof Error ? error : new Error(String(error));
|
||||
wrapped.relay = relayUrl;
|
||||
reject(wrapped);
|
||||
});
|
||||
});
|
||||
|
||||
const fastPromises = fastRelays.map((relayUrl) =>
|
||||
fetchFromRelay(relayUrl, FAST_BLOCKLIST_TIMEOUT_MS, true)
|
||||
);
|
||||
const backgroundPromises = backgroundRelays.map((relayUrl) =>
|
||||
fetchFromRelay(relayUrl, BACKGROUND_BLOCKLIST_TIMEOUT_MS, false)
|
||||
);
|
||||
|
||||
const applyEvents = async (events, { skipIfEmpty = false } = {}) => {
|
||||
if (!Array.isArray(events) || !events.length) {
|
||||
if (skipIfEmpty) {
|
||||
return;
|
||||
}
|
||||
this.blockedPubkeys.clear();
|
||||
this.blockEventId = null;
|
||||
return;
|
||||
}
|
||||
|
||||
const sorted = events
|
||||
.filter((event) => event && event.pubkey === normalized)
|
||||
.sort((a, b) => (b?.created_at || 0) - (a?.created_at || 0));
|
||||
|
||||
if (!sorted.length) {
|
||||
if (skipIfEmpty) {
|
||||
return;
|
||||
}
|
||||
this.blockedPubkeys.clear();
|
||||
this.blockEventId = null;
|
||||
return;
|
||||
}
|
||||
|
||||
const newest = sorted[0];
|
||||
if (newest?.id && newest.id === this.blockEventId && this.blockedPubkeys.size) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.blockEventId = newest?.id || null;
|
||||
|
||||
if (!newest?.content) {
|
||||
this.blockedPubkeys.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
let decrypted = "";
|
||||
try {
|
||||
decrypted = await window.nostr.nip04.decrypt(normalized, newest.content);
|
||||
} catch (err) {
|
||||
console.error("[UserBlockList] Failed to decrypt block list:", err);
|
||||
this.blockedPubkeys.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const parsed = JSON.parse(decrypted);
|
||||
const list = Array.isArray(parsed?.blockedPubkeys)
|
||||
? parsed.blockedPubkeys
|
||||
: [];
|
||||
const sanitized = list
|
||||
.map((entry) => normalizeHex(entry))
|
||||
.filter((candidate) => {
|
||||
if (!candidate) {
|
||||
return false;
|
||||
}
|
||||
if (candidate === normalized) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
this.blockedPubkeys = new Set(sanitized);
|
||||
} catch (err) {
|
||||
console.error("[UserBlockList] Failed to parse block list:", err);
|
||||
this.blockedPubkeys.clear();
|
||||
}
|
||||
};
|
||||
|
||||
const background = Promise.allSettled([
|
||||
...fastPromises,
|
||||
...backgroundPromises,
|
||||
])
|
||||
.then(async (outcomes) => {
|
||||
const aggregated = [];
|
||||
for (const outcome of outcomes) {
|
||||
if (outcome.status === "fulfilled") {
|
||||
const events = Array.isArray(outcome.value?.events)
|
||||
? outcome.value.events
|
||||
: [];
|
||||
if (events.length) {
|
||||
aggregated.push(...events);
|
||||
}
|
||||
} else {
|
||||
const reason = outcome.reason;
|
||||
if (reason?.code === "timeout") {
|
||||
console.warn(
|
||||
`[UserBlockList] Relay ${reason.relay} timed out while loading block list (${reason.timeoutMs}ms)`
|
||||
);
|
||||
} else {
|
||||
const relay = reason?.relay || reason?.relayUrl;
|
||||
console.error(
|
||||
`[UserBlockList] Relay error at ${relay}:`,
|
||||
reason?.error ?? reason
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!aggregated.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
await applyEvents(aggregated, { skipIfEmpty: true });
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("[UserBlockList] background block list refresh failed:", error);
|
||||
});
|
||||
|
||||
let fastResult = null;
|
||||
if (fastPromises.length) {
|
||||
try {
|
||||
fastResult = await Promise.any(fastPromises);
|
||||
} catch (error) {
|
||||
if (error instanceof AggregateError) {
|
||||
error.errors?.forEach((err) => {
|
||||
if (err?.code === "timeout") {
|
||||
console.warn(
|
||||
`[UserBlockList] Relay ${err.relay} timed out while loading block list (${err.timeoutMs}ms)`
|
||||
);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
console.error("[UserBlockList] Fast block list fetch failed:", error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (fastResult?.events?.length) {
|
||||
await applyEvents(fastResult.events);
|
||||
background.catch(() => {});
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const parsed = JSON.parse(decrypted);
|
||||
const list = Array.isArray(parsed?.blockedPubkeys)
|
||||
? parsed.blockedPubkeys
|
||||
: [];
|
||||
const sanitized = list
|
||||
.map((entry) => normalizeHex(entry))
|
||||
.filter((candidate) => {
|
||||
if (!candidate) {
|
||||
return false;
|
||||
}
|
||||
if (candidate === normalized) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
this.blockedPubkeys = new Set(sanitized);
|
||||
} catch (err) {
|
||||
console.error("[UserBlockList] Failed to parse block list:", err);
|
||||
this.blockedPubkeys.clear();
|
||||
}
|
||||
this.blockedPubkeys.clear();
|
||||
this.blockEventId = null;
|
||||
background.catch(() => {});
|
||||
} catch (error) {
|
||||
console.error("[UserBlockList] loadBlocks failed:", error);
|
||||
this.blockedPubkeys.clear();
|
||||
|
||||
Reference in new issue
Block a user