mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-11-09 06:12:56 +00:00
Harden Nostr startup performance
This commit is contained in:
1 parent
4525c1c657
commit
fe976b3ab7
17 files changed
+828
-93
No files matched your search
@@ -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
@@ -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(
|
||||
|
||||
Reference in new issue
Block a user