|
|
|
@ -86,19 +86,26 @@ export default function PostRelaySelector({ |
|
|
|
/** Auto-picked relays from {@link relaySelectionService}; used to detect manual relay-picker changes. */ |
|
|
|
/** Auto-picked relays from {@link relaySelectionService}; used to detect manual relay-picker changes. */ |
|
|
|
const autoSelectedRelayUrlsRef = useRef<string[]>([]) |
|
|
|
const autoSelectedRelayUrlsRef = useRef<string[]>([]) |
|
|
|
const [previousSelectableCount, setPreviousSelectableCount] = useState(0) |
|
|
|
const [previousSelectableCount, setPreviousSelectableCount] = useState(0) |
|
|
|
// Generation counter: incremented every time the effect fires; async callback checks whether
|
|
|
|
const hasManualSelectionRef = useRef(false) |
|
|
|
// it's still the latest invocation before committing state, preventing stale races.
|
|
|
|
const previousSelectableCountRef = useRef(0) |
|
|
|
const selectionGenRef = useRef(0) |
|
|
|
const publicLivelyDebounceRef = useRef<ReturnType<typeof setTimeout> | null>(null) |
|
|
|
|
|
|
|
|
|
|
|
useEffect(() => { |
|
|
|
useEffect(() => { |
|
|
|
return nip66Service.subscribePublicLivelyUpdated(() => { |
|
|
|
hasManualSelectionRef.current = hasManualSelection |
|
|
|
setPublicLivelyRevision((v) => v + 1) |
|
|
|
}, [hasManualSelection]) |
|
|
|
}) |
|
|
|
|
|
|
|
}, []) |
|
|
|
useEffect(() => { |
|
|
|
|
|
|
|
previousSelectableCountRef.current = previousSelectableCount |
|
|
|
|
|
|
|
}, [previousSelectableCount]) |
|
|
|
|
|
|
|
|
|
|
|
useEffect(() => { |
|
|
|
useEffect(() => { |
|
|
|
void nip66Service.getPublicLivelyRelayUrls().then(() => { |
|
|
|
return nip66Service.subscribePublicLivelyUpdated(() => { |
|
|
|
setPublicLivelyRevision((v) => v + 1) |
|
|
|
if (publicLivelyDebounceRef.current) clearTimeout(publicLivelyDebounceRef.current) |
|
|
|
|
|
|
|
// Debounce: NIP-66 can emit many updates during discovery; batch them so selection
|
|
|
|
|
|
|
|
// is not restarted before the prior run finishes (which left "Loading…" stuck).
|
|
|
|
|
|
|
|
publicLivelyDebounceRef.current = setTimeout(() => { |
|
|
|
|
|
|
|
setPublicLivelyRevision((v) => v + 1) |
|
|
|
|
|
|
|
}, 600) |
|
|
|
}) |
|
|
|
}) |
|
|
|
}, []) |
|
|
|
}, []) |
|
|
|
|
|
|
|
|
|
|
|
@ -159,6 +166,12 @@ export default function PostRelaySelector({ |
|
|
|
return [...new Set(matches)].sort().join('\n') |
|
|
|
return [...new Set(matches)].sort().join('\n') |
|
|
|
}, [postContent, isDiscussionReply, isPublicMessage, mentions]) |
|
|
|
}, [postContent, isDiscussionReply, isPublicMessage, mentions]) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** Stable dep for PM recipient changes — raw `mentions` array identity changes every extract. */ |
|
|
|
|
|
|
|
const mentionsRelaySignature = useMemo( |
|
|
|
|
|
|
|
() => (isPublicMessage && mentions.length > 0 ? [...mentions].sort().join('\n') : ''), |
|
|
|
|
|
|
|
[isPublicMessage, mentions] |
|
|
|
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
|
|
// Memoize arrays to prevent unnecessary re-renders
|
|
|
|
// Memoize arrays to prevent unnecessary re-renders
|
|
|
|
const memoizedFavoriteRelays = useMemo(() => favoriteRelays, [favoriteRelays]) |
|
|
|
const memoizedFavoriteRelays = useMemo(() => favoriteRelays, [favoriteRelays]) |
|
|
|
const memoizedBlockedRelays = useMemo(() => { |
|
|
|
const memoizedBlockedRelays = useMemo(() => { |
|
|
|
@ -175,13 +188,13 @@ export default function PostRelaySelector({ |
|
|
|
const memoizedRelaySets = useMemo(() => relaySets, [relaySets]) |
|
|
|
const memoizedRelaySets = useMemo(() => relaySets, [relaySets]) |
|
|
|
const memoizedOpenFrom = useMemo(() => openFrom, [openFrom]) |
|
|
|
const memoizedOpenFrom = useMemo(() => openFrom, [openFrom]) |
|
|
|
|
|
|
|
|
|
|
|
// Single relay-selection effect. The generation counter (selectionGenRef) guards against
|
|
|
|
// Single relay-selection effect. Cleanup sets `active = false` so superseded runs never
|
|
|
|
// stale async completions: if a newer invocation has started, the older one discards its results.
|
|
|
|
// commit stale state; only the latest run clears the loading indicator.
|
|
|
|
useEffect(() => { |
|
|
|
useEffect(() => { |
|
|
|
const gen = ++selectionGenRef.current |
|
|
|
let active = true |
|
|
|
|
|
|
|
setIsLoading(true) |
|
|
|
|
|
|
|
|
|
|
|
const updateRelaySelection = async () => { |
|
|
|
const updateRelaySelection = async () => { |
|
|
|
setIsLoading(true) |
|
|
|
|
|
|
|
try { |
|
|
|
try { |
|
|
|
let userWriteRelays: string[] = [] |
|
|
|
let userWriteRelays: string[] = [] |
|
|
|
if (pubkey && relayList) { |
|
|
|
if (pubkey && relayList) { |
|
|
|
@ -203,41 +216,43 @@ export default function PostRelaySelector({ |
|
|
|
openFrom: memoizedOpenFrom |
|
|
|
openFrom: memoizedOpenFrom |
|
|
|
}) |
|
|
|
}) |
|
|
|
|
|
|
|
|
|
|
|
// Discard results from a superseded invocation
|
|
|
|
if (!active) return |
|
|
|
if (gen !== selectionGenRef.current) return |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const newSelectableCount = result.selectableRelays.length |
|
|
|
const newSelectableCount = result.selectableRelays.length |
|
|
|
const selectableRelaysChanged = newSelectableCount !== previousSelectableCount |
|
|
|
const selectableRelaysChanged = newSelectableCount !== previousSelectableCountRef.current |
|
|
|
|
|
|
|
|
|
|
|
setSelectableRelays(result.selectableRelays) |
|
|
|
setSelectableRelays(result.selectableRelays) |
|
|
|
setRelayTypes(result.relayTypes ?? {}) |
|
|
|
setRelayTypes(result.relayTypes ?? {}) |
|
|
|
setPreviousSelectableCount(newSelectableCount) |
|
|
|
setPreviousSelectableCount(newSelectableCount) |
|
|
|
|
|
|
|
|
|
|
|
if (!hasManualSelection || selectableRelaysChanged) { |
|
|
|
if (!hasManualSelectionRef.current || selectableRelaysChanged) { |
|
|
|
const cacheRelays = result.selectableRelays.filter(url => isLocalNetworkUrl(url)) |
|
|
|
const cacheRelays = result.selectableRelays.filter(url => isLocalNetworkUrl(url)) |
|
|
|
const selectedWithCache = Array.from(new Set([...result.selectedRelays, ...cacheRelays])) |
|
|
|
const selectedWithCache = Array.from(new Set([...result.selectedRelays, ...cacheRelays])) |
|
|
|
const capped = capAutoSelectedRelays(result.selectableRelays, selectedWithCache) |
|
|
|
const capped = capAutoSelectedRelays(result.selectableRelays, selectedWithCache) |
|
|
|
autoSelectedRelayUrlsRef.current = capped |
|
|
|
autoSelectedRelayUrlsRef.current = capped |
|
|
|
setSelectedRelayUrls(capped) |
|
|
|
setSelectedRelayUrls(capped) |
|
|
|
setDescription(describeRelaySelection(capped)) |
|
|
|
setDescription(describeRelaySelection(capped)) |
|
|
|
if (selectableRelaysChanged && hasManualSelection) { |
|
|
|
if (selectableRelaysChanged && hasManualSelectionRef.current) { |
|
|
|
setHasManualSelection(false) |
|
|
|
setHasManualSelection(false) |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} catch (error) { |
|
|
|
} catch (error) { |
|
|
|
if (gen !== selectionGenRef.current) return |
|
|
|
if (!active) return |
|
|
|
logger.error('Failed to update relay selection', { error }) |
|
|
|
logger.error('Failed to update relay selection', { error }) |
|
|
|
setSelectableRelays([]) |
|
|
|
setSelectableRelays([]) |
|
|
|
if (!hasManualSelection) { |
|
|
|
if (!hasManualSelectionRef.current) { |
|
|
|
setSelectedRelayUrls([]) |
|
|
|
setSelectedRelayUrls([]) |
|
|
|
setDescription(t('No relays selected')) |
|
|
|
setDescription(t('No relays selected')) |
|
|
|
} |
|
|
|
} |
|
|
|
} finally { |
|
|
|
} finally { |
|
|
|
if (gen === selectionGenRef.current) setIsLoading(false) |
|
|
|
if (active) setIsLoading(false) |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
updateRelaySelection() |
|
|
|
void updateRelaySelection() |
|
|
|
|
|
|
|
return () => { |
|
|
|
|
|
|
|
active = false |
|
|
|
|
|
|
|
} |
|
|
|
}, [ |
|
|
|
}, [ |
|
|
|
memoizedOpenFrom, |
|
|
|
memoizedOpenFrom, |
|
|
|
_parentEvent, |
|
|
|
_parentEvent, |
|
|
|
@ -247,9 +262,10 @@ export default function PostRelaySelector({ |
|
|
|
isPublicMessage, |
|
|
|
isPublicMessage, |
|
|
|
pubkey, |
|
|
|
pubkey, |
|
|
|
relayList, |
|
|
|
relayList, |
|
|
|
|
|
|
|
userReadRelaysForSelection, |
|
|
|
isDiscussionReply, |
|
|
|
isDiscussionReply, |
|
|
|
contentRelaySignature, |
|
|
|
contentRelaySignature, |
|
|
|
mentions, |
|
|
|
mentionsRelaySignature, |
|
|
|
describeRelaySelection, |
|
|
|
describeRelaySelection, |
|
|
|
addRandomRelaysToPublish, |
|
|
|
addRandomRelaysToPublish, |
|
|
|
publicLivelyRevision, |
|
|
|
publicLivelyRevision, |
|
|
|
|