Browse Source

relay selector

imwald
Silberengel 1 month ago
parent
commit
5f7b5a31f5
  1. 81
      src/components/FavoriteRelaysFeedPicker/index.tsx
  2. 17
      src/providers/NostrProvider/index.tsx
  3. 31
      src/services/client.service.ts

81
src/components/FavoriteRelaysFeedPicker/index.tsx

@ -2,6 +2,7 @@ import { Button } from '@/components/ui/button' @@ -2,6 +2,7 @@ import { Button } from '@/components/ui/button'
import {
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectLabel,
SelectSeparator,
@ -31,7 +32,7 @@ function selectValueToRelaySetId(v: string) { @@ -31,7 +32,7 @@ function selectValueToRelaySetId(v: string) {
return decodeURIComponent(v.slice(3))
}
/** Top-of-feed control: all favorites, single relays, and relay sets. */
/** Top-of-feed control: all favorites, relay sets, then single relays. */
export default function FavoriteRelaysFeedPicker() {
const { t } = useTranslation()
const { isSmallScreen } = useScreenSize()
@ -71,6 +72,12 @@ export default function FavoriteRelaysFeedPicker() { @@ -71,6 +72,12 @@ export default function FavoriteRelaysFeedPicker() {
/** Values that exist in the mobile Select (for controlled `value` validation). */
const selectItems = useMemo(() => {
const items: { value: string }[] = [{ value: ALL_FAVORITES_VALUE }]
for (const set of relaySets) {
items.push({ value: relaySetToSelectValue(set.id) })
}
if (orphanRelaySetId) {
items.push({ value: relaySetToSelectValue(orphanRelaySetId) })
}
for (const url of urls) {
items.push({ value: normalizeUrl(url) || url })
}
@ -82,12 +89,6 @@ export default function FavoriteRelaysFeedPicker() { @@ -82,12 +89,6 @@ export default function FavoriteRelaysFeedPicker() {
) {
items.push({ value: normalizeUrl(feedInfo.id) || feedInfo.id })
}
for (const set of relaySets) {
items.push({ value: relaySetToSelectValue(set.id) })
}
if (orphanRelaySetId) {
items.push({ value: relaySetToSelectValue(orphanRelaySetId) })
}
return items
}, [
urls,
@ -157,17 +158,10 @@ export default function FavoriteRelaysFeedPicker() { @@ -157,17 +158,10 @@ export default function FavoriteRelaysFeedPicker() {
<SelectItem value={ALL_FAVORITES_VALUE} className="text-xs">
{t('All favorite relays')}
</SelectItem>
{urls.map((url) => {
const v = normalizeUrl(url) || url
return (
<SelectItem key={v} value={v} className="font-mono text-xs" title={url}>
{simplifyUrl(url)}
</SelectItem>
)
})}
{relaySets.length > 0 || orphanRelaySetId ? (
<>
<SelectSeparator />
<SelectGroup>
<SelectLabel className="pl-2">{t('Relay sets')}</SelectLabel>
{relaySets.map((set) => (
<SelectItem
@ -186,6 +180,20 @@ export default function FavoriteRelaysFeedPicker() { @@ -186,6 +180,20 @@ export default function FavoriteRelaysFeedPicker() {
{orphanRelaySetId}
</SelectItem>
) : null}
</SelectGroup>
</>
) : null}
{urls.length > 0 ? (
<>
{relaySets.length > 0 || orphanRelaySetId ? <SelectSeparator /> : null}
{urls.map((url) => {
const v = normalizeUrl(url) || url
return (
<SelectItem key={v} value={v} className="font-mono text-xs" title={url}>
{simplifyUrl(url)}
</SelectItem>
)
})}
</>
) : null}
</SelectContent>
@ -215,26 +223,6 @@ export default function FavoriteRelaysFeedPicker() { @@ -215,26 +223,6 @@ export default function FavoriteRelaysFeedPicker() {
>
{t('All favorite relays')}
</button>
{urls.map((url) => {
const key = normalizeUrl(url) || url
const active = feedInfo.feedType === 'relay' && currentRelayKey === key
return (
<button
key={key}
type="button"
className={cn(
'max-w-[11rem] shrink-0 truncate rounded-full border px-3 py-1 font-mono text-xs font-semibold transition-colors',
active
? 'border-primary bg-primary/15 text-foreground'
: 'border-border bg-muted/40 text-muted-foreground hover:bg-accent'
)}
title={url}
onClick={() => void switchFeed('relay', { relay: url })}
>
{simplifyUrl(url)}
</button>
)
})}
{(relaySets.length > 0 || orphanRelaySetId) && (
<div className="mx-0.5 shrink-0 self-stretch border-l border-border/80" aria-hidden />
)}
@ -270,6 +258,29 @@ export default function FavoriteRelaysFeedPicker() { @@ -270,6 +258,29 @@ export default function FavoriteRelaysFeedPicker() {
{orphanRelaySetId}
</button>
) : null}
{urls.length > 0 && (relaySets.length > 0 || orphanRelaySetId) && (
<div className="mx-0.5 shrink-0 self-stretch border-l border-border/80" aria-hidden />
)}
{urls.map((url) => {
const key = normalizeUrl(url) || url
const active = feedInfo.feedType === 'relay' && currentRelayKey === key
return (
<button
key={key}
type="button"
className={cn(
'max-w-[11rem] shrink-0 truncate rounded-full border px-3 py-1 font-mono text-xs font-semibold transition-colors',
active
? 'border-primary bg-primary/15 text-foreground'
: 'border-border bg-muted/40 text-muted-foreground hover:bg-accent'
)}
title={url}
onClick={() => void switchFeed('relay', { relay: url })}
>
{simplifyUrl(url)}
</button>
)
})}
</div>
{editSettingsButton}
</div>

17
src/providers/NostrProvider/index.tsx

@ -923,9 +923,11 @@ export function NostrProvider({ children }: { children: React.ReactNode }) { @@ -923,9 +923,11 @@ export function NostrProvider({ children }: { children: React.ReactNode }) {
void replaceableEventService.updateReplaceableEventCache(event).catch(() => {})
}
// If publishing failed completely, throw an error so the form doesn't close
if (!publishResult.success) {
logger.error('[Publish] Publishing failed to all relays!', {
// Replaceable events and notes: cache above uses successCount >= 1. publishEvent still sets
// success only when >=1/3 of relays OK (broad replication). Treat "zero accepts" as failure
// so we don't throw when a few relays worked but many timed out (common with large outbox lists).
if (publishResult.successCount < 1) {
logger.error('[Publish] Publishing failed on every relay', {
eventKind: event.kind,
eventId: event.id?.substring(0, 8),
relayStatuses: publishResult.relayStatuses,
@ -938,9 +940,16 @@ export function NostrProvider({ children }: { children: React.ReactNode }) { @@ -938,9 +940,16 @@ export function NostrProvider({ children }: { children: React.ReactNode }) {
'Failed to publish to any relay'
)
;(error as any).relayStatuses = publishResult.relayStatuses
if (publishResult.successCount >= 1) (error as any).event = event
throw error
}
if (!publishResult.success) {
logger.warn('[Publish] Partial publish: some relays failed or timed out', {
eventKind: event.kind,
eventId: event.id?.substring(0, 8),
successCount: publishResult.successCount,
totalCount: publishResult.totalCount
})
}
logger.debug('[Publish] Publishing successful, attaching relayStatuses to event')
// Attach relayStatuses only temporarily for UI feedback, then remove it

31
src/services/client.service.ts

@ -559,10 +559,11 @@ class ClientService extends EventTarget { @@ -559,10 +559,11 @@ class ClientService extends EventTarget {
profileFetchRelays: PROFILE_FETCH_RELAY_URLS,
additionalRelayCount: bootstrapExtras.length
})
} else if (event.kind === ExtendedKind.FAVORITE_RELAYS) {
// Use fast write relays for favorite relays to avoid timeouts and payment requirements
} else if (event.kind === ExtendedKind.FAVORITE_RELAYS || event.kind === kinds.Relaysets) {
// Use fast write relays for favorite-relays and kind 30002 relay-set replaceables to avoid
// timeouts and auth-only relays dominating the attempt list.
bootstrapExtras.push(...FAST_WRITE_RELAY_URLS)
logger.debug('[DetermineTargetRelays] Favorite relays event detected, adding FAST_WRITE_RELAY_URLS', {
logger.debug('[DetermineTargetRelays] Favorite relays or relay set event, adding FAST_WRITE_RELAY_URLS', {
kind: event.kind,
fastWriteRelays: FAST_WRITE_RELAY_URLS,
additionalRelayCount: bootstrapExtras.length
@ -571,7 +572,11 @@ class ClientService extends EventTarget { @@ -571,7 +572,11 @@ class ClientService extends EventTarget {
bootstrapExtras.push(...FAST_WRITE_RELAY_URLS, ...PROFILE_FETCH_RELAY_URLS)
}
if (event.kind === kinds.RelayList || event.kind === ExtendedKind.FAVORITE_RELAYS) {
if (
event.kind === kinds.RelayList ||
event.kind === ExtendedKind.FAVORITE_RELAYS ||
event.kind === kinds.Relaysets
) {
logger.debug('[DetermineTargetRelays] Fetching user relay list for event publication', {
pubkey: event.pubkey,
kind: event.kind
@ -587,7 +592,11 @@ class ClientService extends EventTarget { @@ -587,7 +592,11 @@ class ClientService extends EventTarget {
})
relayList = { write: [], read: [], originalRelays: [] }
}
if (event.kind === kinds.RelayList || event.kind === ExtendedKind.FAVORITE_RELAYS) {
if (
event.kind === kinds.RelayList ||
event.kind === ExtendedKind.FAVORITE_RELAYS ||
event.kind === kinds.Relaysets
) {
logger.debug('[DetermineTargetRelays] User relay list fetched', {
hasRelayList: !!relayList,
writeRelayCount: relayList?.write?.length ?? 0,
@ -609,7 +618,11 @@ class ClientService extends EventTarget { @@ -609,7 +618,11 @@ class ClientService extends EventTarget {
}),
event
)
if (event.kind === kinds.RelayList || event.kind === ExtendedKind.FAVORITE_RELAYS) {
if (
event.kind === kinds.RelayList ||
event.kind === ExtendedKind.FAVORITE_RELAYS ||
event.kind === kinds.Relaysets
) {
logger.info('[DetermineTargetRelays] Final relay list for event publication', {
kind: event.kind,
totalRelayCount: relays.length,
@ -786,7 +799,11 @@ class ClientService extends EventTarget { @@ -786,7 +799,11 @@ class ClientService extends EventTarget {
})
const uniqueRelayUrls = filtered
if (event.kind === kinds.RelayList || event.kind === ExtendedKind.FAVORITE_RELAYS) {
if (
event.kind === kinds.RelayList ||
event.kind === ExtendedKind.FAVORITE_RELAYS ||
event.kind === kinds.Relaysets
) {
logger.info('[PublishEvent] Publishing event to relays', {
eventId: event.id?.substring(0, 8),
kind: event.kind,

Loading…
Cancel
Save