fix(relays): prune retired/dead relays from stored lists (runtime + load + sync)
Adds RETIRED_RELAY_HOSTS (goskatespots.nostr1.com, wotr.relatr.xyz) with isRetiredRelay/pruneRetiredRelays. Applied in resolveRelays (runtime), AppProvider (one-time cleanup on load) and NostrSync (when applying a fetched NIP-65), so existing users stop showing/using the retired relay without manual edits.
This commit is contained in:
parent
7611f3d266
commit
37cc0d5d68
4 changed files with 63 additions and 3 deletions
|
|
@ -2125,6 +2125,11 @@ The community relay moved off the hosted relay.tools instance
|
||||||
relay will lose skate content once the relay.tools subscription is cancelled —
|
relay will lose skate content once the relay.tools subscription is cancelled —
|
||||||
cancel only after the Zapstore update has propagated. Historical newsletter text
|
cancel only after the Zapstore update has propagated. Historical newsletter text
|
||||||
still names the old relay (left as-is on purpose).
|
still names the old relay (left as-is on purpose).
|
||||||
|
- **Stored relay lists are migrated (2026-10-12):** `RETIRED_RELAY_HOSTS` in
|
||||||
|
`lib/relays.ts` (`goskatespots.nostr1.com`, dead `wotr.relatr.xyz`) are pruned at
|
||||||
|
runtime (`resolveRelays`), on config load (`AppProvider`) and on NIP-65 sync
|
||||||
|
(`NostrSync`), so existing users stop showing/using them without manual edits.
|
||||||
|
Saving the Relays screen republishes the cleaned NIP-65 list.
|
||||||
- ⚠️ `relay.skate-spots.org` can show a stale NXDOMAIN in a local resolver cache
|
- ⚠️ `relay.skate-spots.org` can show a stale NXDOMAIN in a local resolver cache
|
||||||
right after creation (our dev box did). Verify with
|
right after creation (our dev box did). Verify with
|
||||||
`curl --resolve relay.skate-spots.org:443:185.18.221.2 …` or from another host.
|
`curl --resolve relay.skate-spots.org:443:185.18.221.2 …` or from another host.
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ import { z } from 'zod';
|
||||||
import { useLocalStorage } from '@/hooks/useLocalStorage';
|
import { useLocalStorage } from '@/hooks/useLocalStorage';
|
||||||
import { safeJson } from '@/lib/safeJson';
|
import { safeJson } from '@/lib/safeJson';
|
||||||
import { AppContext, type AppConfig, type AppContextType, type Theme, type RelayMetadata } from '@/contexts/AppContext';
|
import { AppContext, type AppConfig, type AppContextType, type Theme, type RelayMetadata } from '@/contexts/AppContext';
|
||||||
|
import { pruneRetiredRelays } from '@/lib/relays';
|
||||||
|
|
||||||
interface AppProviderProps {
|
interface AppProviderProps {
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
|
|
@ -84,6 +85,17 @@ export function AppProvider(props: AppProviderProps) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// One-time cleanup: drop retired/dead relays from an existing user's saved list.
|
||||||
|
// Code defaults change over time, but a user's stored relay list is otherwise
|
||||||
|
// never rewritten — so a retired relay would linger forever.
|
||||||
|
if (!updates.relayMetadata) {
|
||||||
|
const pruned = pruneRetiredRelays(config.relayMetadata);
|
||||||
|
if (pruned) {
|
||||||
|
updates.relayMetadata = pruned;
|
||||||
|
needsUpdate = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Force dark theme (app is dark-only)
|
// Force dark theme (app is dark-only)
|
||||||
if (config.theme !== 'dark') {
|
if (config.theme !== 'dark') {
|
||||||
updates.theme = 'dark';
|
updates.theme = 'dark';
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import { useCurrentUser } from '@/hooks/useCurrentUser';
|
||||||
import { useAppContext } from '@/hooks/useAppContext';
|
import { useAppContext } from '@/hooks/useAppContext';
|
||||||
import { useToast } from '@/hooks/useToast';
|
import { useToast } from '@/hooks/useToast';
|
||||||
import { useTranslation } from '@/contexts/language';
|
import { useTranslation } from '@/contexts/language';
|
||||||
|
import { isRetiredRelay } from '@/lib/relays';
|
||||||
|
|
||||||
// Citrine local relay (Android app by greenart7c3)
|
// Citrine local relay (Android app by greenart7c3)
|
||||||
const CITRINE_URL = 'ws://localhost:4869';
|
const CITRINE_URL = 'ws://localhost:4869';
|
||||||
|
|
@ -137,7 +138,8 @@ export function NostrSync() {
|
||||||
url,
|
url,
|
||||||
read: !marker || marker === 'read',
|
read: !marker || marker === 'read',
|
||||||
write: !marker || marker === 'write',
|
write: !marker || marker === 'write',
|
||||||
}));
|
}))
|
||||||
|
.filter((r) => !isRetiredRelay(r.url));
|
||||||
if (fetchedRelays.length > 0) {
|
if (fetchedRelays.length > 0) {
|
||||||
updateConfig(c => ({
|
updateConfig(c => ({
|
||||||
...c,
|
...c,
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,43 @@ export function normalizeRelayUrl(input: string): string | null {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Hosts that are retired or dead and are pruned from every relay list. */
|
||||||
|
export const RETIRED_RELAY_HOSTS = new Set([
|
||||||
|
'goskatespots.nostr1.com', // legacy hosted relay — retired 2026-10-12
|
||||||
|
'wotr.relatr.xyz', // dead (does not resolve)
|
||||||
|
]);
|
||||||
|
|
||||||
|
/** True when `url` points at a retired/dead relay host. */
|
||||||
|
export function isRetiredRelay(url: string): boolean {
|
||||||
|
try {
|
||||||
|
return RETIRED_RELAY_HOSTS.has(new URL(url).host.toLowerCase());
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove retired/dead relays from a stored RelayMetadata. Returns a new object
|
||||||
|
* only when something actually changed (so callers can skip a no-op write), else
|
||||||
|
* null.
|
||||||
|
*/
|
||||||
|
export function pruneRetiredRelays(metadata: RelayMetadata): RelayMetadata | null {
|
||||||
|
const relays = metadata.relays.filter((r) => !isRetiredRelay(r.url));
|
||||||
|
const local = metadata.localRelays?.filter((u) => !isRetiredRelay(u));
|
||||||
|
const dm = metadata.dmRelays?.filter((u) => !isRetiredRelay(u));
|
||||||
|
const changed =
|
||||||
|
relays.length !== metadata.relays.length ||
|
||||||
|
(local?.length ?? 0) !== (metadata.localRelays?.length ?? 0) ||
|
||||||
|
(dm?.length ?? 0) !== (metadata.dmRelays?.length ?? 0);
|
||||||
|
if (!changed) return null;
|
||||||
|
return {
|
||||||
|
...metadata,
|
||||||
|
relays,
|
||||||
|
...(metadata.localRelays ? { localRelays: local } : {}),
|
||||||
|
...(metadata.dmRelays ? { dmRelays: dm } : {}),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export interface ResolvedRelays {
|
export interface ResolvedRelays {
|
||||||
/** NIP-65 relays with blocked ones removed (read/write flags kept). */
|
/** NIP-65 relays with blocked ones removed (read/write flags kept). */
|
||||||
configured: { url: string; read: boolean; write: boolean }[];
|
configured: { url: string; read: boolean; write: boolean }[];
|
||||||
|
|
@ -46,8 +83,12 @@ export interface ResolvedRelays {
|
||||||
*/
|
*/
|
||||||
export function resolveRelays(metadata: RelayMetadata): ResolvedRelays {
|
export function resolveRelays(metadata: RelayMetadata): ResolvedRelays {
|
||||||
const blocked = new Set((metadata.blockedRelays ?? []).map((u) => u.toLowerCase()));
|
const blocked = new Set((metadata.blockedRelays ?? []).map((u) => u.toLowerCase()));
|
||||||
const local = (metadata.localRelays ?? []).filter((u) => !blocked.has(u.toLowerCase()));
|
const local = (metadata.localRelays ?? []).filter(
|
||||||
const configured = metadata.relays.filter((r) => !blocked.has(r.url.toLowerCase()));
|
(u) => !blocked.has(u.toLowerCase()) && !isRetiredRelay(u),
|
||||||
|
);
|
||||||
|
const configured = metadata.relays.filter(
|
||||||
|
(r) => !blocked.has(r.url.toLowerCase()) && !isRetiredRelay(r.url),
|
||||||
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
configured,
|
configured,
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue