Harden Nostr startup performance

This commit is contained in:
thePR0M3TH3AN committed 2026-07-26 17:20:52 -04:00
1 parent 4525c1c657
commit fe976b3ab7
17 files changed
+828 -93

No files matched your search

+68 -3
View File
@@ -41,6 +41,9 @@ const MAX_OPEN_ATTEMPTS = 30; // ~60s of retries
// (kind 30000), which during cold-start competes with the initial feed load; a
// fallback covers the empty-feed case where no videos:updated ever fires.
const FEED_READY_FALLBACK_MS = 12000;
// Some relays do not reliably send EOSE. Do not hold the first visible update
// forever, but give the initial relay burst time to settle before painting it.
const INITIAL_SYNC_FALLBACK_MS = 5000;
export function createNip71IngestService({
nostrClient,
@@ -54,6 +57,7 @@ export function createNip71IngestService({
maxOpenAttempts = MAX_OPEN_ATTEMPTS,
refreshThrottleMs = REFRESH_THROTTLE_MS,
feedReadyFallbackMs = FEED_READY_FALLBACK_MS,
initialSyncFallbackMs = INITIAL_SYNC_FALLBACK_MS,
} = {}) {
let subscription = null;
let buffer = [];
@@ -67,6 +71,9 @@ export function createNip71IngestService({
let deferArmed = false;
let deferTimer = null;
let offFeedReady = null;
let initialSyncing = false;
let initialRefreshPending = false;
let initialSyncTimer = null;
function isAvailable() {
return (
@@ -183,7 +190,13 @@ export function createNip71IngestService({
);
if (injected > 0) {
requestRefresh();
if (initialSyncing) {
// Keep native BitVid, NIP-71 videos, and future NIP-71 media types in
// the store immediately; defer only the expensive full-feed repaint.
initialRefreshPending = true;
} else {
requestRefresh();
}
}
return injected;
@@ -211,6 +224,10 @@ export function createNip71IngestService({
function emitRefresh() {
lastRefreshAt = Date.now();
const performanceHarness = globalThis?.__bitvidPerformance;
const performanceRef = globalThis?.performance;
const startedAt = typeof performanceRef?.now === "function" ? performanceRef.now() : 0;
performanceHarness?.mark?.("nip71-refresh-filter-start");
let videos = [];
try {
videos =
@@ -220,6 +237,11 @@ export function createNip71IngestService({
} catch (error) {
logger?.warn?.("[nip71Ingest] getFilteredActiveVideos failed", error);
}
const filteredAt = typeof performanceRef?.now === "function" ? performanceRef.now() : startedAt;
performanceHarness?.mark?.("nip71-refresh-filter-end", {
durationMs: Math.max(0, filteredAt - startedAt),
videos: videos.length,
});
// Confirm ingested videos survive the feed filter (whitelist/NSFW/etc.).
const foreignCount = Array.isArray(videos)
? videos.filter((v) => v?.source === "nip71-ingest").length
@@ -234,6 +256,37 @@ export function createNip71IngestService({
reason: "nip71-ingest",
});
}
const emittedAt = typeof performanceRef?.now === "function" ? performanceRef.now() : filteredAt;
performanceHarness?.mark?.("nip71-refresh-dispatch-end", {
filterMs: Math.max(0, filteredAt - startedAt),
dispatchMs: Math.max(0, emittedAt - filteredAt),
videos: videos.length,
});
if (performanceHarness && (filteredAt - startedAt > 50 || emittedAt - filteredAt > 50)) {
userLogger.info(
`[nip71Ingest] refresh timing: filter ${Math.round(filteredAt - startedAt)}ms, dispatch ${Math.round(emittedAt - filteredAt)}ms, videos ${videos.length}`,
);
}
}
function finishInitialSync() {
if (!initialSyncing) {
return;
}
if (initialSyncTimer) {
clearTimeout(initialSyncTimer);
initialSyncTimer = null;
}
if (flushTimer) {
clearTimeout(flushTimer);
flushTimer = null;
flush();
}
initialSyncing = false;
if (initialRefreshPending) {
initialRefreshPending = false;
requestRefresh();
}
}
// Defer the first start until the native feed has rendered (first
@@ -255,7 +308,7 @@ export function createNip71IngestService({
deferTimer = null;
}
deferArmed = false;
start();
start({ coalesceInitialSync: true });
};
if (typeof nostrService?.on === "function") {
@@ -265,12 +318,17 @@ export function createNip71IngestService({
return true;
}
function start() {
function start({ coalesceInitialSync = false } = {}) {
if (started || !isAvailable()) {
return false;
}
started = true;
attemptsLeft = maxOpenAttempts;
initialSyncing = coalesceInitialSync;
initialRefreshPending = false;
if (initialSyncing) {
initialSyncTimer = setTimeout(finishInitialSync, initialSyncFallbackMs);
}
// Re-subscribe when the whitelist changes so the author scope stays current.
if (typeof accessControl?.onWhitelistChange === "function") {
@@ -320,6 +378,7 @@ export function createNip71IngestService({
scheduleFlush();
}
},
onEose: finishInitialSync,
});
const authorCount = Array.isArray(filters[0]?.authors)
? filters[0].authors.length
@@ -348,6 +407,12 @@ export function createNip71IngestService({
clearTimeout(pendingRefreshTimer);
pendingRefreshTimer = null;
}
if (initialSyncTimer) {
clearTimeout(initialSyncTimer);
initialSyncTimer = null;
}
initialSyncing = false;
initialRefreshPending = false;
buffer = [];
if (subscription && typeof subscription.close === "function") {
try {
+49 -29
View File
@@ -1071,6 +1071,12 @@ export class NostrService {
this.dmActorPubkey = normalizedActor;
const relaySelection = await this.resolveDirectMessageRelaySelection(
normalizedActor,
relays,
);
const resolvedRelays = relaySelection.relays;
let resolvedSince =
Number.isFinite(sinceCandidate) && sinceCandidate >= 0
? Math.floor(sinceCandidate)
@@ -1095,7 +1101,7 @@ export class NostrService {
let messages = [];
try {
messages = await this.nostrClient.listDirectMessages(normalizedActor, {
relays,
relays: resolvedRelays,
...options,
limit: resolvedLimit,
decryptLimit: resolvedDecryptLimit,
@@ -1133,6 +1139,14 @@ export class NostrService {
messages: snapshot,
actorPubkey: normalizedActor,
});
if (relaySelection.warning === DM_RELAY_WARNING_FALLBACK) {
this.emit("directMessages:relayWarning", {
actorPubkey: normalizedActor,
warning: relaySelection.warning,
relays: resolvedRelays,
source: relaySelection.source,
});
}
this.emit("directMessages:updated", {
messages: snapshot,
reason: "load",
@@ -1156,6 +1170,35 @@ export class NostrService {
return snapshot;
}
async resolveDirectMessageRelaySelection(actorPubkey, relays = null) {
if (Array.isArray(relays)) {
return { relays, source: "explicit", warning: null };
}
const discoveryRelays = Array.isArray(this.nostrClient?.readRelays)
? this.nostrClient.readRelays
: Array.isArray(this.nostrClient?.relays)
? this.nostrClient.relays
: [];
let pool = this.nostrClient?.pool || null;
if (!pool && typeof this.nostrClient?.ensurePool === "function") {
try {
pool = await this.nostrClient.ensurePool();
} catch (error) {
devLogger.warn("[nostrService] Failed to initialize DM relay pool.", error);
}
}
return resolveDmRelaySelection({
pubkey: actorPubkey,
discoveryRelays,
fallbackRelays: discoveryRelays,
pool,
log: { dev: devLogger, user: userLogger },
});
}
async ensureDirectMessageSubscription({ actorPubkey, relays, ...handlers } = {}) {
const activeActor = actorPubkey || this.resolveActiveDmActor();
const normalizedActor = normalizeHexPubkey(activeActor);
@@ -1189,34 +1232,11 @@ export class NostrService {
this.stopDirectMessageSubscription();
}
let resolvedRelays = Array.isArray(relays) ? relays : null;
let relaySelection = null;
if (!resolvedRelays) {
const discoveryRelays = Array.isArray(this.nostrClient?.readRelays)
? this.nostrClient.readRelays
: Array.isArray(this.nostrClient?.relays)
? this.nostrClient.relays
: [];
let pool = this.nostrClient?.pool || null;
if (!pool && typeof this.nostrClient?.ensurePool === "function") {
try {
pool = await this.nostrClient.ensurePool();
} catch (error) {
devLogger.warn("[nostrService] Failed to initialize DM relay pool.", error);
}
}
relaySelection = await resolveDmRelaySelection({
pubkey: normalizedActor,
discoveryRelays,
fallbackRelays: discoveryRelays,
pool,
log: { dev: devLogger, user: userLogger },
});
resolvedRelays = relaySelection.relays;
}
const relaySelection = await this.resolveDirectMessageRelaySelection(
normalizedActor,
relays,
);
const resolvedRelays = relaySelection.relays;
try {
const subscription = this.nostrClient.subscribeDirectMessages(