mirror of
https://github.com/hoornet/vega.git
synced 2026-05-07 20:59:12 -07:00
Fix relay connectivity: remove aggressive liveness probe, add fetch timeouts
The liveness probe in ensureConnected was causing a death spiral — it force-disconnected working relays when a 3s probe timed out, then resetNDK killed new connections before they could establish. Now ensureConnected trusts relay.connected and only reconnects when zero relays are connected. All fetchEvents calls have timeouts (5-10s) so nothing hangs. resetNDK kept as background-only recovery in the connection monitor after 30s of continuous failure. Also adds feed diagnostics (feedDiagnostics.ts) for tracking fetch timing, event freshness, and relay states via console helpers.
This commit is contained in:
@@ -3,7 +3,8 @@ import { useFeedStore } from "../../stores/feed";
|
||||
import { useUserStore } from "../../stores/user";
|
||||
import { useMuteStore } from "../../stores/mute";
|
||||
import { useUIStore } from "../../stores/ui";
|
||||
import { fetchFollowFeed, getNDK } from "../../lib/nostr";
|
||||
import { fetchFollowFeed, getNDK, ensureConnected } from "../../lib/nostr";
|
||||
import { diagWrapFetch, logDiag } from "../../lib/feedDiagnostics";
|
||||
import { detectScript, getEventLanguageTag, FILTER_SCRIPTS } from "../../lib/language";
|
||||
import { NoteCard } from "./NoteCard";
|
||||
import { ArticleCard } from "../article/ArticleCard";
|
||||
@@ -38,7 +39,8 @@ export function Feed() {
|
||||
const loadFollowFeed = async () => {
|
||||
setFollowLoading(true);
|
||||
try {
|
||||
const events = await fetchFollowFeed(follows);
|
||||
await ensureConnected();
|
||||
const events = await diagWrapFetch("follow_fetch", () => fetchFollowFeed(follows));
|
||||
setFollowNotes(events);
|
||||
} finally {
|
||||
setFollowLoading(false);
|
||||
@@ -140,7 +142,10 @@ export function Feed() {
|
||||
</span>
|
||||
)}
|
||||
<button
|
||||
onClick={isTrending ? () => loadTrendingFeed(true) : isFollowing ? loadFollowFeed : loadFeed}
|
||||
onClick={() => {
|
||||
logDiag({ ts: new Date().toISOString(), action: "refresh_click", details: `tab=${tab}` });
|
||||
(isTrending ? () => loadTrendingFeed(true) : isFollowing ? loadFollowFeed : loadFeed)();
|
||||
}}
|
||||
disabled={isLoading}
|
||||
className="text-text-muted hover:text-text text-[11px] px-2 py-1 border border-border hover:border-text-dim transition-colors disabled:opacity-40"
|
||||
>
|
||||
|
||||
@@ -3,7 +3,7 @@ import { NDKEvent } from "@nostr-dev-kit/ndk";
|
||||
import { useUIStore } from "../../stores/ui";
|
||||
import { useUserStore } from "../../stores/user";
|
||||
import { useMuteStore } from "../../stores/mute";
|
||||
import { fetchNoteById, fetchThreadEvents, fetchAncestors, publishReply, getNDK } from "../../lib/nostr";
|
||||
import { fetchNoteById, fetchThreadEvents, fetchAncestors, publishReply, getNDK, ensureConnected } from "../../lib/nostr";
|
||||
import { buildThreadTree, getRootEventId } from "../../lib/threadTree";
|
||||
import type { ThreadNode } from "../../lib/threadTree";
|
||||
import { AncestorChain } from "./AncestorChain";
|
||||
@@ -22,6 +22,7 @@ export function ThreadView() {
|
||||
const [ancestors, setAncestors] = useState<NDKEvent[]>([]);
|
||||
const [tree, setTree] = useState<ThreadNode | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [loadError, setLoadError] = useState<string | null>(null);
|
||||
const [showRootReply, setShowRootReply] = useState(false);
|
||||
const [replyText, setReplyText] = useState("");
|
||||
const [replying, setReplying] = useState(false);
|
||||
@@ -30,17 +31,26 @@ export function ThreadView() {
|
||||
const replyRef = useRef<HTMLTextAreaElement>(null);
|
||||
const scrollRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const [retryCount, setRetryCount] = useState(0);
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
|
||||
async function loadThread() {
|
||||
setLoading(true);
|
||||
setLoadError(null);
|
||||
setTree(null);
|
||||
setAncestors([]);
|
||||
setRootEvent(null);
|
||||
setShowRootReply(false);
|
||||
|
||||
try {
|
||||
// Ensure we have relay connectivity before fetching
|
||||
const connected = await ensureConnected();
|
||||
if (!connected && !cancelled) {
|
||||
setLoadError("No relay connections available. Check your network.");
|
||||
}
|
||||
|
||||
const rootId = getRootEventId(focusedEvent);
|
||||
let root: NDKEvent;
|
||||
|
||||
@@ -54,6 +64,7 @@ export function ThreadView() {
|
||||
if (!cancelled) setAncestors(anc.filter((a) => a.id !== root.id));
|
||||
} else {
|
||||
root = focusedEvent;
|
||||
if (!cancelled) setLoadError("Could not fetch the root note — relay may be slow.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,8 +77,14 @@ export function ThreadView() {
|
||||
const allEvents = [root, ...events.filter((e) => e.id !== root.id)];
|
||||
const built = buildThreadTree(root.id, allEvents);
|
||||
setTree(built);
|
||||
|
||||
// Warn if we got zero replies (possible timeout)
|
||||
if (events.length === 0 && !loadError) {
|
||||
console.warn("[Wrystr] Thread fetch returned 0 replies — possible timeout or empty thread");
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Failed to load thread:", err);
|
||||
if (!cancelled) setLoadError(`Failed to load: ${err}`);
|
||||
} finally {
|
||||
if (!cancelled) setLoading(false);
|
||||
}
|
||||
@@ -75,7 +92,7 @@ export function ThreadView() {
|
||||
|
||||
loadThread();
|
||||
return () => { cancelled = true; };
|
||||
}, [focusedEvent.id]);
|
||||
}, [focusedEvent.id, retryCount]);
|
||||
|
||||
// Scroll to focused note after tree renders (if not root)
|
||||
useEffect(() => {
|
||||
@@ -133,6 +150,19 @@ export function ThreadView() {
|
||||
</header>
|
||||
|
||||
<div ref={scrollRef} className="flex-1 overflow-y-auto">
|
||||
{/* Error banner */}
|
||||
{loadError && !loading && (
|
||||
<div className="px-4 py-2 bg-danger/10 border-b border-danger/20 flex items-center justify-between">
|
||||
<span className="text-danger text-[11px]">{loadError}</span>
|
||||
<button
|
||||
onClick={() => setRetryCount((c) => c + 1)}
|
||||
className="text-[11px] text-accent hover:text-accent-hover transition-colors px-2 py-0.5 border border-accent/30 hover:border-accent"
|
||||
>
|
||||
retry
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Loading shimmer */}
|
||||
{loading && (
|
||||
<div className="px-4 py-6 space-y-4">
|
||||
@@ -234,8 +264,14 @@ export function ThreadView() {
|
||||
)}
|
||||
|
||||
{!loading && !tree && (
|
||||
<div className="px-4 py-6 text-text-dim text-[12px] text-center">
|
||||
Could not load thread.
|
||||
<div className="px-4 py-6 text-center space-y-2">
|
||||
<p className="text-text-dim text-[12px]">Could not load thread.</p>
|
||||
<button
|
||||
onClick={() => setRetryCount((c) => c + 1)}
|
||||
className="text-[11px] text-accent hover:text-accent-hover transition-colors px-3 py-1 border border-accent/30 hover:border-accent"
|
||||
>
|
||||
retry
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user