mirror of
https://github.com/hoornet/vega.git
synced 2026-05-10 14:19:12 -07:00
Fix relay dedup: normalize URLs to prevent trailing-slash duplicates
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { getNDK, getStoredRelayUrls, addRelay, removeRelay, publishRelayList, fetchRelayRecommendations } from "../../lib/nostr";
|
import { getNDK, getStoredRelayUrls, addRelay, removeRelay, publishRelayList, fetchRelayRecommendations, normalizeRelayUrl } from "../../lib/nostr";
|
||||||
import { useRelayHealthStore } from "../../stores/relayHealth";
|
import { useRelayHealthStore } from "../../stores/relayHealth";
|
||||||
import { useUserStore } from "../../stores/user";
|
import { useUserStore } from "../../stores/user";
|
||||||
import type { RelayHealthResult } from "../../lib/nostr/relayHealth";
|
import type { RelayHealthResult } from "../../lib/nostr/relayHealth";
|
||||||
@@ -154,7 +154,7 @@ export function RelaysView() {
|
|||||||
const { loggedIn } = useUserStore();
|
const { loggedIn } = useUserStore();
|
||||||
const ndk = getNDK();
|
const ndk = getNDK();
|
||||||
const poolRelays = Array.from(ndk.pool?.relays?.values() ?? []);
|
const poolRelays = Array.from(ndk.pool?.relays?.values() ?? []);
|
||||||
const poolConnectedUrls = new Set(poolRelays.filter((r) => r.connected).map((r) => r.url));
|
const poolConnectedUrls = new Set(poolRelays.filter((r) => r.connected).map((r) => normalizeRelayUrl(r.url)));
|
||||||
|
|
||||||
const [input, setInput] = useState("");
|
const [input, setInput] = useState("");
|
||||||
const [addError, setAddError] = useState<string | null>(null);
|
const [addError, setAddError] = useState<string | null>(null);
|
||||||
@@ -180,7 +180,7 @@ export function RelaysView() {
|
|||||||
setAddError("URL must start with ws:// or wss://");
|
setAddError("URL must start with ws:// or wss://");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (getStoredRelayUrls().includes(url)) {
|
if (getStoredRelayUrls().includes(normalizeRelayUrl(url))) {
|
||||||
setAddError("Already in list");
|
setAddError("Already in list");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -218,8 +218,8 @@ export function RelaysView() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Merge: show health results first, then any pool relays not yet checked
|
// Merge: show health results first, then any pool relays not yet checked
|
||||||
const checkedUrls = new Set(results.map((r) => r.url));
|
const checkedUrls = new Set(results.map((r) => normalizeRelayUrl(r.url)));
|
||||||
const uncheckedPoolRelays = poolRelays.filter((r) => !checkedUrls.has(r.url));
|
const uncheckedPoolRelays = poolRelays.filter((r) => !checkedUrls.has(normalizeRelayUrl(r.url)));
|
||||||
|
|
||||||
// Sort: online first, then slow, then offline
|
// Sort: online first, then slow, then offline
|
||||||
const sortedResults = [...results].sort((a, b) => {
|
const sortedResults = [...results].sort((a, b) => {
|
||||||
|
|||||||
@@ -51,16 +51,30 @@ export const OUTBOX_RELAYS = [
|
|||||||
"wss://relay.nostr.band/",
|
"wss://relay.nostr.band/",
|
||||||
];
|
];
|
||||||
|
|
||||||
|
/** Normalize relay URL: lowercase host, strip trailing slash, deduplicate. */
|
||||||
|
export function normalizeRelayUrl(url: string): string {
|
||||||
|
return url.replace(/\/+$/, "");
|
||||||
|
}
|
||||||
|
|
||||||
export function getStoredRelayUrls(): string[] {
|
export function getStoredRelayUrls(): string[] {
|
||||||
try {
|
try {
|
||||||
const stored = localStorage.getItem(RELAY_STORAGE_KEY);
|
const stored = localStorage.getItem(RELAY_STORAGE_KEY);
|
||||||
if (stored) return JSON.parse(stored);
|
if (stored) {
|
||||||
|
// Deduplicate on load (handles legacy duplicates from trailing-slash mismatch)
|
||||||
|
const urls: string[] = JSON.parse(stored);
|
||||||
|
const seen = new Set<string>();
|
||||||
|
return urls.map(normalizeRelayUrl).filter((u) => {
|
||||||
|
if (seen.has(u)) return false;
|
||||||
|
seen.add(u);
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
} catch { /* ignore */ }
|
} catch { /* ignore */ }
|
||||||
return FALLBACK_RELAYS;
|
return FALLBACK_RELAYS;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function saveRelayUrls(urls: string[]) {
|
export function saveRelayUrls(urls: string[]) {
|
||||||
localStorage.setItem(RELAY_STORAGE_KEY, JSON.stringify(urls));
|
localStorage.setItem(RELAY_STORAGE_KEY, JSON.stringify(urls.map(normalizeRelayUrl)));
|
||||||
}
|
}
|
||||||
|
|
||||||
let ndk: NDK | null = null;
|
let ndk: NDK | null = null;
|
||||||
@@ -119,13 +133,15 @@ export async function resetNDK(): Promise<void> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function addRelay(url: string): void {
|
export function addRelay(url: string): void {
|
||||||
|
const normalized = normalizeRelayUrl(url);
|
||||||
const instance = getNDK();
|
const instance = getNDK();
|
||||||
const urls = getStoredRelayUrls();
|
const urls = getStoredRelayUrls();
|
||||||
if (!urls.includes(url)) {
|
if (!urls.includes(normalized)) {
|
||||||
saveRelayUrls([...urls, url]);
|
saveRelayUrls([...urls, normalized]);
|
||||||
}
|
}
|
||||||
if (!instance.pool?.relays.has(url)) {
|
// Check both with and without trailing slash since NDK may use either
|
||||||
const relay = new NDKRelay(url, undefined, instance);
|
if (!instance.pool?.relays.has(normalized) && !instance.pool?.relays.has(normalized + "/")) {
|
||||||
|
const relay = new NDKRelay(normalized, undefined, instance);
|
||||||
instance.pool?.addRelay(relay, true);
|
instance.pool?.addRelay(relay, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
export { getNDK, getNDKUptimeMs, connectToRelays, ensureConnected, resetNDK, getStoredRelayUrls, addRelay, removeRelay, fetchWithTimeout, withTimeout, FEED_TIMEOUT, THREAD_TIMEOUT, SINGLE_TIMEOUT } from "./core";
|
export { getNDK, getNDKUptimeMs, connectToRelays, ensureConnected, resetNDK, getStoredRelayUrls, normalizeRelayUrl, addRelay, removeRelay, fetchWithTimeout, withTimeout, FEED_TIMEOUT, THREAD_TIMEOUT, SINGLE_TIMEOUT } from "./core";
|
||||||
export { fetchGlobalFeed, fetchFollowFeed, fetchUserNotes, fetchUserNotesNIP65, fetchNoteById, fetchReplies, publishNote, publishReply, publishRepost, publishQuote, fetchHashtagFeed, fetchThreadEvents, fetchAncestors } from "./notes";
|
export { fetchGlobalFeed, fetchFollowFeed, fetchUserNotes, fetchUserNotesNIP65, fetchNoteById, fetchReplies, publishNote, publishReply, publishRepost, publishQuote, fetchHashtagFeed, fetchThreadEvents, fetchAncestors } from "./notes";
|
||||||
export { publishProfile, publishContactList, fetchProfile, fetchFollowSuggestions, fetchMentions, fetchFollowers, fetchNewFollowers } from "./social";
|
export { publishProfile, publishContactList, fetchProfile, fetchFollowSuggestions, fetchMentions, fetchFollowers, fetchNewFollowers } from "./social";
|
||||||
export { publishArticle, fetchArticle, fetchAuthorArticles, fetchArticleFeed, searchArticles, fetchByAddr } from "./articles";
|
export { publishArticle, fetchArticle, fetchAuthorArticles, fetchArticleFeed, searchArticles, fetchByAddr } from "./articles";
|
||||||
|
|||||||
Reference in New Issue
Block a user