From 53a67d8233ed7059ef049cbc35d1c92b34b95c62 Mon Sep 17 00:00:00 2001 From: codytseng Date: Sun, 4 Jan 2026 23:40:43 +0800 Subject: [PATCH] feat: allow changing default relays --- src/components/DefaultRelaysSetting/index.tsx | 109 ++++++++++++++++++ .../PullRelaySetsButton.tsx | 6 +- src/components/NotificationList/index.tsx | 5 +- src/components/PostEditor/PostContent.tsx | 4 +- src/components/Profile/ProfileFeed.tsx | 9 +- src/components/QuoteList/index.tsx | 5 +- .../RelayInfo/RelayReviewsPreview.tsx | 5 +- src/components/SearchResult/index.tsx | 5 +- src/components/StuffStats/LikeButton.tsx | 5 +- src/components/StuffStats/Likes.tsx | 6 +- src/constants.ts | 1 + src/i18n/locales/ar.ts | 7 +- src/i18n/locales/de.ts | 7 +- src/i18n/locales/en.ts | 7 +- src/i18n/locales/es.ts | 7 +- src/i18n/locales/fa.ts | 7 +- src/i18n/locales/fr.ts | 7 +- src/i18n/locales/hi.ts | 7 +- src/i18n/locales/hu.ts | 7 +- src/i18n/locales/it.ts | 7 +- src/i18n/locales/ja.ts | 7 +- src/i18n/locales/ko.ts | 7 +- src/i18n/locales/pl.ts | 7 +- src/i18n/locales/pt-BR.ts | 7 +- src/i18n/locales/pt-PT.ts | 7 +- src/i18n/locales/ru.ts | 7 +- src/i18n/locales/th.ts | 7 +- src/i18n/locales/zh-TW.ts | 7 +- src/i18n/locales/zh.ts | 7 +- src/lib/event-metadata.ts | 13 ++- src/lib/relay.ts | 9 +- src/pages/primary/ExplorePage/index.tsx | 5 +- .../secondary/EmojiPackSettingsPage/index.tsx | 4 +- src/pages/secondary/NoteListPage/index.tsx | 5 +- .../secondary/RelayReviewsPage/index.tsx | 5 +- .../secondary/SystemSettingsPage/index.tsx | 4 + src/providers/FavoriteRelaysProvider.tsx | 4 +- src/providers/NostrProvider/index.tsx | 17 +-- src/providers/NotificationProvider.tsx | 6 +- src/services/client.service.ts | 38 +++--- src/services/lightning.service.ts | 9 +- src/services/local-storage.service.ts | 31 ++++- src/services/stuff-stats.service.ts | 7 +- src/services/thread.service.ts | 5 +- 44 files changed, 356 insertions(+), 92 deletions(-) create mode 100644 src/components/DefaultRelaysSetting/index.tsx diff --git a/src/components/DefaultRelaysSetting/index.tsx b/src/components/DefaultRelaysSetting/index.tsx new file mode 100644 index 0000000..0b3a989 --- /dev/null +++ b/src/components/DefaultRelaysSetting/index.tsx @@ -0,0 +1,109 @@ +import { Button } from '@/components/ui/button' +import { Input } from '@/components/ui/input' +import { Label } from '@/components/ui/label' +import { BIG_RELAY_URLS } from '@/constants' +import { isWebsocketUrl, normalizeUrl } from '@/lib/url' +import storage from '@/services/local-storage.service' +import { CircleX } from 'lucide-react' +import { useState } from 'react' +import { useTranslation } from 'react-i18next' +import InfoCard from '../InfoCard' +import RelayIcon from '../RelayIcon' + +export default function DefaultRelaysSetting() { + const { t } = useTranslation() + const [relayUrls, setRelayUrls] = useState(storage.getDefaultRelayUrls()) + const [newRelayUrl, setNewRelayUrl] = useState('') + const [newRelayUrlError, setNewRelayUrlError] = useState(null) + + const removeRelayUrl = (url: string) => { + const normalizedUrl = normalizeUrl(url) + if (!normalizedUrl) return + const newUrls = relayUrls.filter((u) => u !== normalizedUrl) + setRelayUrls(newUrls) + storage.setDefaultRelayUrls(newUrls) + } + + const saveNewRelayUrl = () => { + if (newRelayUrl === '') return + const normalizedUrl = normalizeUrl(newRelayUrl) + if (!normalizedUrl) { + return setNewRelayUrlError(t('Invalid relay URL')) + } + if (relayUrls.includes(normalizedUrl)) { + return setNewRelayUrlError(t('Relay already exists')) + } + if (!isWebsocketUrl(normalizedUrl)) { + return setNewRelayUrlError(t('invalid relay URL')) + } + const newUrls = [...relayUrls, normalizedUrl] + setRelayUrls(newUrls) + storage.setDefaultRelayUrls(newUrls) + setNewRelayUrl('') + } + + const handleRelayUrlInputChange = (e: React.ChangeEvent) => { + setNewRelayUrl(e.target.value) + setNewRelayUrlError(null) + } + + const handleRelayUrlInputKeyDown = (event: React.KeyboardEvent) => { + if (event.key === 'Enter') { + event.preventDefault() + saveNewRelayUrl() + } + } + + const resetToDefault = () => { + setRelayUrls(BIG_RELAY_URLS) + storage.setDefaultRelayUrls(BIG_RELAY_URLS) + } + + return ( +
+
+ + +
+
{t('Default relays description')}
+ +
+ {relayUrls.map((url, index) => ( + removeRelayUrl(url)} /> + ))} +
+
+ + +
+ {newRelayUrlError &&
{newRelayUrlError}
} +
+ ) +} + +function RelayUrl({ url, onRemove }: { url: string; onRemove: () => void }) { + return ( +
+
+ +
{url}
+
+
+ +
+
+ ) +} diff --git a/src/components/FavoriteRelaysSetting/PullRelaySetsButton.tsx b/src/components/FavoriteRelaysSetting/PullRelaySetsButton.tsx index 6d4ddbb..079333d 100644 --- a/src/components/FavoriteRelaysSetting/PullRelaySetsButton.tsx +++ b/src/components/FavoriteRelaysSetting/PullRelaySetsButton.tsx @@ -15,8 +15,9 @@ import { DrawerTitle, DrawerTrigger } from '@/components/ui/drawer' -import { BIG_RELAY_URLS } from '@/constants' +import { buildATag } from '@/lib/draft-event' import { getReplaceableEventIdentifier } from '@/lib/event' +import { getDefaultRelayUrls } from '@/lib/relay' import { tagNameEquals } from '@/lib/tag' import { isWebsocketUrl, simplifyUrl } from '@/lib/url' import { useFavoriteRelays } from '@/providers/FavoriteRelaysProvider' @@ -29,7 +30,6 @@ import { Event, kinds } from 'nostr-tools' import { useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' import RelaySetCard from '../RelaySetCard' -import { buildATag } from '@/lib/draft-event' export default function PullRelaySetsButton() { const { t } = useTranslation() @@ -94,7 +94,7 @@ function RemoteRelaySets({ close }: { close?: () => void }) { const init = async () => { setInitialed(false) const events = await client.fetchEvents( - (relayList?.write ?? []).concat(BIG_RELAY_URLS).slice(0, 4), + (relayList?.write ?? []).concat(getDefaultRelayUrls()).slice(0, 4), { kinds: [kinds.Relaysets], authors: [pubkey], diff --git a/src/components/NotificationList/index.tsx b/src/components/NotificationList/index.tsx index 7613cfa..00a1a23 100644 --- a/src/components/NotificationList/index.tsx +++ b/src/components/NotificationList/index.tsx @@ -1,6 +1,7 @@ -import { BIG_RELAY_URLS, ExtendedKind, NOTIFICATION_LIST_STYLE } from '@/constants' +import { ExtendedKind, NOTIFICATION_LIST_STYLE } from '@/constants' import { useInfiniteScroll } from '@/hooks' import { compareEvents } from '@/lib/event' +import { getDefaultRelayUrls } from '@/lib/relay' import { mergeTimelines } from '@/lib/timeline' import { isTouchDevice } from '@/lib/utils' import { usePrimaryPage } from '@/PageManager' @@ -137,7 +138,7 @@ const NotificationList = forwardRef((_, ref) => { const { closer, timelineKey } = await client.subscribeTimeline( [ { - urls: relayList.read.length > 0 ? relayList.read.slice(0, 5) : BIG_RELAY_URLS, + urls: relayList.read.length > 0 ? relayList.read.slice(0, 5) : getDefaultRelayUrls(), filter } ], diff --git a/src/components/PostEditor/PostContent.tsx b/src/components/PostEditor/PostContent.tsx index 4bf8598..b235274 100644 --- a/src/components/PostEditor/PostContent.tsx +++ b/src/components/PostEditor/PostContent.tsx @@ -1,7 +1,6 @@ import Note from '@/components/Note' import { Button } from '@/components/ui/button' import { ScrollArea } from '@/components/ui/scroll-area' -import { BIG_RELAY_URLS } from '@/constants' import { createCommentDraftEvent, createHighlightDraftEvent, @@ -9,6 +8,7 @@ import { createShortTextNoteDraftEvent, deleteDraftEventCache } from '@/lib/draft-event' +import { getDefaultRelayUrls } from '@/lib/relay' import { isTouchDevice } from '@/lib/utils' import { useNostr } from '@/providers/NostrProvider' import postEditorCache from '@/services/post-editor-cache.service' @@ -146,7 +146,7 @@ export default function PostContent({ const _additionalRelayUrls = [...additionalRelayUrls] if (parentStuff && typeof parentStuff === 'string') { - _additionalRelayUrls.push(...BIG_RELAY_URLS) + _additionalRelayUrls.push(...getDefaultRelayUrls()) } const newEvent = await publish(draftEvent, { diff --git a/src/components/Profile/ProfileFeed.tsx b/src/components/Profile/ProfileFeed.tsx index 25e94da..bc31902 100644 --- a/src/components/Profile/ProfileFeed.tsx +++ b/src/components/Profile/ProfileFeed.tsx @@ -1,7 +1,8 @@ import KindFilter from '@/components/KindFilter' import NoteList, { TNoteListRef } from '@/components/NoteList' import Tabs from '@/components/Tabs' -import { BIG_RELAY_URLS, MAX_PINNED_NOTES, SEARCHABLE_RELAY_URLS } from '@/constants' +import { MAX_PINNED_NOTES, SEARCHABLE_RELAY_URLS } from '@/constants' +import { getDefaultRelayUrls } from '@/lib/relay' import { generateBech32IdFromETag } from '@/lib/tag' import { isTouchDevice } from '@/lib/utils' import { useKindFilter } from '@/providers/KindFilterProvider' @@ -100,14 +101,14 @@ export default function ProfileFeed({ setSubRequests([ { - urls: myRelayList.write.concat(BIG_RELAY_URLS).slice(0, 5), + urls: myRelayList.write.concat(getDefaultRelayUrls()).slice(0, 5), filter: { authors: [myPubkey], '#p': [pubkey] } }, { - urls: relayList.write.concat(BIG_RELAY_URLS).slice(0, 5), + urls: relayList.write.concat(getDefaultRelayUrls()).slice(0, 5), filter: { authors: [pubkey], '#p': [myPubkey] @@ -134,7 +135,7 @@ export default function ProfileFeed({ } else { setSubRequests([ { - urls: relayList.write.concat(BIG_RELAY_URLS).slice(0, 8), + urls: relayList.write.concat(getDefaultRelayUrls()).slice(0, 8), filter: { authors: [pubkey] } diff --git a/src/components/QuoteList/index.tsx b/src/components/QuoteList/index.tsx index eb031b9..803cf1f 100644 --- a/src/components/QuoteList/index.tsx +++ b/src/components/QuoteList/index.tsx @@ -1,6 +1,7 @@ -import { BIG_RELAY_URLS, ExtendedKind } from '@/constants' +import { ExtendedKind } from '@/constants' import { useStuff } from '@/hooks/useStuff' import { getReplaceableCoordinateFromEvent, isReplaceableEvent } from '@/lib/event' +import { getDefaultRelayUrls } from '@/lib/relay' import client from '@/services/client.service' import { TFeedSubRequest } from '@/types' import { Event, Filter, kinds } from 'nostr-tools' @@ -13,7 +14,7 @@ export default function QuoteList({ stuff }: { stuff: Event | string }) { useEffect(() => { async function init() { - const relaySet = new Set(BIG_RELAY_URLS) + const relaySet = new Set(getDefaultRelayUrls()) const filters: Filter[] = [] if (event) { const relayList = await client.fetchRelayList(event.pubkey) diff --git a/src/components/RelayInfo/RelayReviewsPreview.tsx b/src/components/RelayInfo/RelayReviewsPreview.tsx index 1b016b4..4f22ad1 100644 --- a/src/components/RelayInfo/RelayReviewsPreview.tsx +++ b/src/components/RelayInfo/RelayReviewsPreview.tsx @@ -7,10 +7,11 @@ import { CarouselNext, CarouselPrevious } from '@/components/ui/carousel' -import { BIG_RELAY_URLS, ExtendedKind } from '@/constants' +import { ExtendedKind } from '@/constants' import { compareEvents } from '@/lib/event' import { getStarsFromRelayReviewEvent } from '@/lib/event-metadata' import { toRelayReviews } from '@/lib/link' +import { getDefaultRelayUrls } from '@/lib/relay' import { cn, isTouchDevice } from '@/lib/utils' import { useMuteList } from '@/providers/MuteListProvider' import { useNostr } from '@/providers/NostrProvider' @@ -60,7 +61,7 @@ export default function RelayReviewsPreview({ relayUrl }: { relayUrl: string }) if (pubkey) { filters.push({ kinds: [ExtendedKind.RELAY_REVIEW], authors: [pubkey], '#d': [relayUrl] }) } - const events = await client.fetchEvents([relayUrl, ...BIG_RELAY_URLS], filters, { + const events = await client.fetchEvents([relayUrl, ...getDefaultRelayUrls()], filters, { cache: true }) diff --git a/src/components/SearchResult/index.tsx b/src/components/SearchResult/index.tsx index 520be28..91671d3 100644 --- a/src/components/SearchResult/index.tsx +++ b/src/components/SearchResult/index.tsx @@ -1,4 +1,5 @@ -import { BIG_RELAY_URLS, SEARCHABLE_RELAY_URLS } from '@/constants' +import { SEARCHABLE_RELAY_URLS } from '@/constants' +import { getDefaultRelayUrls } from '@/lib/relay' import { TSearchParams } from '@/types' import NormalFeed from '../NormalFeed' import Profile from '../Profile' @@ -27,7 +28,7 @@ export default function SearchResult({ searchParams }: { searchParams: TSearchPa if (searchParams.type === 'hashtag') { return ( ) diff --git a/src/components/StuffStats/LikeButton.tsx b/src/components/StuffStats/LikeButton.tsx index f7f5376..de1729f 100644 --- a/src/components/StuffStats/LikeButton.tsx +++ b/src/components/StuffStats/LikeButton.tsx @@ -1,12 +1,13 @@ import { Drawer, DrawerContent, DrawerOverlay } from '@/components/ui/drawer' import { Popover, PopoverAnchor, PopoverContent } from '@/components/ui/popover' -import { BIG_RELAY_URLS, LONG_PRESS_THRESHOLD } from '@/constants' +import { LONG_PRESS_THRESHOLD } from '@/constants' import { useStuff } from '@/hooks/useStuff' import { useStuffStatsById } from '@/hooks/useStuffStatsById' import { createExternalContentReactionDraftEvent, createReactionDraftEvent } from '@/lib/draft-event' +import { getDefaultRelayUrls } from '@/lib/relay' import { useNostr } from '@/providers/NostrProvider' import { useScreenSize } from '@/providers/ScreenSizeProvider' import { useUserPreferences } from '@/providers/UserPreferencesProvider' @@ -79,7 +80,7 @@ export default function LikeButton({ stuff }: { stuff: Event | string }) { const reaction = event ? createReactionDraftEvent(event, emoji) : createExternalContentReactionDraftEvent(externalContent, emoji) - const seenOn = event ? client.getSeenEventRelayUrls(event.id) : BIG_RELAY_URLS + const seenOn = event ? client.getSeenEventRelayUrls(event.id) : getDefaultRelayUrls() const evt = await publish(reaction, { additionalRelayUrls: seenOn }) stuffStatsService.updateStuffStatsByEvents([evt]) } catch (error) { diff --git a/src/components/StuffStats/Likes.tsx b/src/components/StuffStats/Likes.tsx index 7cead52..e429120 100644 --- a/src/components/StuffStats/Likes.tsx +++ b/src/components/StuffStats/Likes.tsx @@ -1,11 +1,11 @@ import { ScrollArea, ScrollBar } from '@/components/ui/scroll-area' -import { BIG_RELAY_URLS } from '@/constants' -import { useStuffStatsById } from '@/hooks/useStuffStatsById' import { useStuff } from '@/hooks/useStuff' +import { useStuffStatsById } from '@/hooks/useStuffStatsById' import { createExternalContentReactionDraftEvent, createReactionDraftEvent } from '@/lib/draft-event' +import { getDefaultRelayUrls } from '@/lib/relay' import { cn } from '@/lib/utils' import { useNostr } from '@/providers/NostrProvider' import client from '@/services/client.service' @@ -53,7 +53,7 @@ export default function Likes({ stuff }: { stuff: Event | string }) { const reaction = event ? createReactionDraftEvent(event, emoji) : createExternalContentReactionDraftEvent(externalContent, emoji) - const seenOn = event ? client.getSeenEventRelayUrls(event.id) : BIG_RELAY_URLS + const seenOn = event ? client.getSeenEventRelayUrls(event.id) : getDefaultRelayUrls() const evt = await publish(reaction, { additionalRelayUrls: seenOn }) stuffStatsService.updateStuffStatsByEvents([evt]) } catch (error) { diff --git a/src/constants.ts b/src/constants.ts index bc8ff6e..86cf42d 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -43,6 +43,7 @@ export const StorageKey = { NSFW_DISPLAY_POLICY: 'nsfwDisplayPolicy', MIN_TRUST_SCORE: 'minTrustScore', ENABLE_LIVE_FEED: 'enableLiveFeed', + DEFAULT_RELAY_URLS: 'defaultRelayUrls', HIDE_UNTRUSTED_NOTES: 'hideUntrustedNotes', // deprecated HIDE_UNTRUSTED_INTERACTIONS: 'hideUntrustedInteractions', // deprecated HIDE_UNTRUSTED_NOTIFICATIONS: 'hideUntrustedNotifications', // deprecated diff --git a/src/i18n/locales/ar.ts b/src/i18n/locales/ar.ts index df5308c..f85bda7 100644 --- a/src/i18n/locales/ar.ts +++ b/src/i18n/locales/ar.ts @@ -653,6 +653,11 @@ export default { 'trust-filter.trust-score-description': 'محسوبة بناءً على سمعة المستخدم والنسبة المئوية للشبكة الاجتماعية', 'Auto-load profile pictures': 'تحميل صور الملف الشخصي تلقائيًا', 'Disable live feed': 'تعطيل التغذية المباشرة', - 'Enable live feed': 'تفعيل التغذية المباشرة' + 'Enable live feed': 'تفعيل التغذية المباشرة', + 'Default relays': 'المرحلات الافتراضية', + 'Reset to default': 'إعادة تعيين إلى الافتراضي', + 'Default relays description': 'تُستخدم للاستعلام عن تكوينات المرحلات للمستخدمين الآخرين وكبديل احتياطي عندما لا يكون لدى المستخدمين مرحلات مكوّنة.', + 'Default relays warning': 'تحذير: يرجى عدم تعديل هذه الإعدادات بشكل عشوائي، فقد يؤثر ذلك على تجربتك الأساسية.', + 'Invalid relay URL': 'عنوان URL للمرحل غير صالح' } } diff --git a/src/i18n/locales/de.ts b/src/i18n/locales/de.ts index aa690c9..43efe8d 100644 --- a/src/i18n/locales/de.ts +++ b/src/i18n/locales/de.ts @@ -675,6 +675,11 @@ export default { 'Berechnet basierend auf Benutzerreputation und sozialem Netzwerk-Perzentil', 'Auto-load profile pictures': 'Profilbilder automatisch laden', 'Disable live feed': 'Live-Feed deaktivieren', - 'Enable live feed': 'Live-Feed aktivieren' + 'Enable live feed': 'Live-Feed aktivieren', + 'Default relays': 'Standard-Relays', + 'Reset to default': 'Auf Standard zurücksetzen', + 'Default relays description': 'Werden verwendet, um die Relay-Konfigurationen anderer Benutzer abzufragen und als Fallback, wenn Benutzer keine Relays konfiguriert haben.', + 'Default relays warning': 'Warnung: Ändern Sie diese Einstellungen nicht leichtfertig, da dies Ihre grundlegende Erfahrung beeinträchtigen kann.', + 'Invalid relay URL': 'Ungültige Relay-URL' } } diff --git a/src/i18n/locales/en.ts b/src/i18n/locales/en.ts index c791db1..1258fe9 100644 --- a/src/i18n/locales/en.ts +++ b/src/i18n/locales/en.ts @@ -658,6 +658,11 @@ export default { 'trust-filter.trust-score-description': 'Calculated based on user reputation and social network percentile', 'Auto-load profile pictures': 'Auto-load profile pictures', 'Disable live feed': 'Disable live feed', - 'Enable live feed': 'Enable live feed' + 'Enable live feed': 'Enable live feed', + 'Default relays': 'Default relays', + 'Reset to default': 'Reset to default', + 'Default relays description': 'Used to query other users\' relay configurations and as a fallback when users have no relays configured.', + 'Default relays warning': 'Warning: Please do not modify these settings casually, as it may affect your basic experience.', + 'Invalid relay URL': 'Invalid relay URL' } } diff --git a/src/i18n/locales/es.ts b/src/i18n/locales/es.ts index 3629b8e..2df6d40 100644 --- a/src/i18n/locales/es.ts +++ b/src/i18n/locales/es.ts @@ -669,6 +669,11 @@ export default { 'Calculado según la reputación del usuario y el percentil de la red social', 'Auto-load profile pictures': 'Cargar imágenes de perfil automáticamente', 'Disable live feed': 'Desactivar feed en vivo', - 'Enable live feed': 'Activar feed en vivo' + 'Enable live feed': 'Activar feed en vivo', + 'Default relays': 'Relés predeterminados', + 'Reset to default': 'Restablecer valores predeterminados', + 'Default relays description': 'Se utilizan para consultar las configuraciones de relés de otros usuarios y como respaldo cuando los usuarios no tienen relés configurados.', + 'Default relays warning': 'Advertencia: No modifiques estas configuraciones a la ligera, ya que puede afectar tu experiencia básica.', + 'Invalid relay URL': 'URL de relé no válida' } } diff --git a/src/i18n/locales/fa.ts b/src/i18n/locales/fa.ts index 09199de..f0c09b5 100644 --- a/src/i18n/locales/fa.ts +++ b/src/i18n/locales/fa.ts @@ -665,6 +665,11 @@ export default { 'بر اساس شهرت کاربر و صدک شبکه اجتماعی محاسبه می‌شود', 'Auto-load profile pictures': 'بارگذاری خودکار تصاویر پروفایل', 'Disable live feed': 'غیرفعال کردن فید زنده', - 'Enable live feed': 'فعال کردن فید زنده' + 'Enable live feed': 'فعال کردن فید زنده', + 'Default relays': 'رله‌های پیش‌فرض', + 'Reset to default': 'بازنشانی به پیش‌فرض', + 'Default relays description': 'برای پرس‌وجو از پیکربندی‌های رله کاربران دیگر و به عنوان جایگزین زمانی که کاربران رله پیکربندی نکرده‌اند استفاده می‌شود.', + 'Default relays warning': 'هشدار: لطفاً این تنظیمات را به صورت تصادفی تغییر ندهید، ممکن است بر تجربه اولیه شما تأثیر بگذارد.', + 'Invalid relay URL': 'آدرس URL رله نامعتبر است' } } diff --git a/src/i18n/locales/fr.ts b/src/i18n/locales/fr.ts index 4f2c179..672c571 100644 --- a/src/i18n/locales/fr.ts +++ b/src/i18n/locales/fr.ts @@ -673,6 +673,11 @@ export default { "Calculé en fonction de la réputation et du réseau social de l'utilisateur", 'Auto-load profile pictures': 'Charger les images de profil automatiquement', 'Disable live feed': 'Désactiver le flux en direct', - 'Enable live feed': 'Activer le flux en direct' + 'Enable live feed': 'Activer le flux en direct', + 'Default relays': 'Relais par défaut', + 'Reset to default': 'Réinitialiser par défaut', + 'Default relays description': 'Utilisés pour interroger les configurations de relais d\'autres utilisateurs et comme solution de secours lorsque les utilisateurs n\'ont pas de relais configurés.', + 'Default relays warning': 'Attention : Ne modifiez pas ces paramètres à la légère, car cela pourrait affecter votre expérience de base.', + 'Invalid relay URL': 'URL de relais non valide' } } diff --git a/src/i18n/locales/hi.ts b/src/i18n/locales/hi.ts index 2b29179..97bf19c 100644 --- a/src/i18n/locales/hi.ts +++ b/src/i18n/locales/hi.ts @@ -665,6 +665,11 @@ export default { 'उपयोगकर्ता की प्रतिष्ठा और सामाजिक नेटवर्क प्रतिशतक के आधार पर गणना की गई', 'Auto-load profile pictures': 'प्रोफ़ाइल चित्र स्वतः लोड करें', 'Disable live feed': 'लाइव फ़ीड अक्षम करें', - 'Enable live feed': 'लाइव फ़ीड सक्षम करें' + 'Enable live feed': 'लाइव फ़ीड सक्षम करें', + 'Default relays': 'डिफ़ॉल्ट रिले', + 'Reset to default': 'डिफ़ॉल्ट पर रीसेट करें', + 'Default relays description': 'अन्य उपयोगकर्ताओं के रिले कॉन्फ़िगरेशन की जांच करने के लिए उपयोग किया जाता है और जब उपयोगकर्ताओं के पास रिले कॉन्फ़िगर नहीं है तो फ़ॉलबैक के रूप में।', + 'Default relays warning': 'चेतावनी: कृपया इन सेटिंग्स को बेतरतीब ढंग से संशोधित न करें, क्योंकि यह आपके बुनियादी अनुभव को प्रभावित कर सकता है।', + 'Invalid relay URL': 'अमान्य रिले URL' } } diff --git a/src/i18n/locales/hu.ts b/src/i18n/locales/hu.ts index f47fa89..1dd10a1 100644 --- a/src/i18n/locales/hu.ts +++ b/src/i18n/locales/hu.ts @@ -658,6 +658,11 @@ export default { 'A felhasználó hírneve és a közösségi hálózat percentilise alapján számítva', 'Auto-load profile pictures': 'Profilképek automatikus betöltése', 'Disable live feed': 'Élő hírfolyam letiltása', - 'Enable live feed': 'Élő hírfolyam engedélyezése' + 'Enable live feed': 'Élő hírfolyam engedélyezése', + 'Default relays': 'Alapértelmezett továbbítók', + 'Reset to default': 'Visszaállítás alapértelmezettre', + 'Default relays description': 'Más felhasználók továbbító konfigurációinak lekérdezésére használatos, és tartalékként szolgál, ha a felhasználóknak nincsenek továbbítóik beállítva.', + 'Default relays warning': 'Figyelmeztetés: Ne módosítsa ezeket a beállításokat meggondolatlanul, mert ez befolyásolhatja az alapvető élményt.', + 'Invalid relay URL': 'Érvénytelen továbbító URL' } } diff --git a/src/i18n/locales/it.ts b/src/i18n/locales/it.ts index 6097c8f..1a6d592 100644 --- a/src/i18n/locales/it.ts +++ b/src/i18n/locales/it.ts @@ -669,6 +669,11 @@ export default { "Calcolato in base alla reputazione dell'utente e al percentile del social network", 'Auto-load profile pictures': 'Caricamento automatico immagini di profilo', 'Disable live feed': 'Disattiva feed live', - 'Enable live feed': 'Attiva feed live' + 'Enable live feed': 'Attiva feed live', + 'Default relays': 'Relay predefiniti', + 'Reset to default': 'Ripristina predefiniti', + 'Default relays description': 'Utilizzati per interrogare le configurazioni dei relay di altri utenti e come fallback quando gli utenti non hanno relay configurati.', + 'Default relays warning': 'Attenzione: Non modificare queste impostazioni alla leggera, potrebbe influire sull\'esperienza di base.', + 'Invalid relay URL': 'URL relay non valido' } } diff --git a/src/i18n/locales/ja.ts b/src/i18n/locales/ja.ts index a37a83e..9ae0c9d 100644 --- a/src/i18n/locales/ja.ts +++ b/src/i18n/locales/ja.ts @@ -663,6 +663,11 @@ export default { 'ユーザーの評判とソーシャルネットワークに基づいて信頼度パーセンタイルを計算', 'Auto-load profile pictures': 'プロフィール画像を自動読み込み', 'Disable live feed': 'ライブフィードを無効にする', - 'Enable live feed': 'ライブフィードを有効にする' + 'Enable live feed': 'ライブフィードを有効にする', + 'Default relays': 'デフォルトリレー', + 'Reset to default': 'デフォルトにリセット', + 'Default relays description': '他のユーザーのリレー設定を照会するために使用され、ユーザーがリレーを設定していない場合のフォールバックとして機能します。', + 'Default relays warning': '警告:これらの設定を無闇に変更しないでください。基本的な体験に影響を与える可能性があります。', + 'Invalid relay URL': '無効なリレーURL' } } diff --git a/src/i18n/locales/ko.ts b/src/i18n/locales/ko.ts index 232cb9c..4e3e2ef 100644 --- a/src/i18n/locales/ko.ts +++ b/src/i18n/locales/ko.ts @@ -658,6 +658,11 @@ export default { 'trust-filter.trust-score-description': '사용자의 평판과 소셜 네트워크를 기반으로 신뢰도 백분위수 계산', 'Auto-load profile pictures': '프로필 사진 자동 로드', 'Disable live feed': '라이브 피드 비활성화', - 'Enable live feed': '라이브 피드 활성화' + 'Enable live feed': '라이브 피드 활성화', + 'Default relays': '기본 릴레이', + 'Reset to default': '기본값으로 재설정', + 'Default relays description': '다른 사용자의 릴레이 구성을 조회하는 데 사용되며, 사용자가 릴레이를 구성하지 않은 경우 대체 수단으로 사용됩니다.', + 'Default relays warning': '경고: 이러한 설정을 임의로 수정하지 마십시오. 기본 경험에 영향을 줄 수 있습니다.', + 'Invalid relay URL': '유효하지 않은 릴레이 URL' } } diff --git a/src/i18n/locales/pl.ts b/src/i18n/locales/pl.ts index c18f87c..90e8d61 100644 --- a/src/i18n/locales/pl.ts +++ b/src/i18n/locales/pl.ts @@ -670,6 +670,11 @@ export default { 'Obliczany na podstawie reputacji użytkownika i percentyla sieci społecznościowej', 'Auto-load profile pictures': 'Automatyczne ładowanie zdjęć profilowych', 'Disable live feed': 'Wyłącz kanał na żywo', - 'Enable live feed': 'Włącz kanał na żywo' + 'Enable live feed': 'Włącz kanał na żywo', + 'Default relays': 'Domyślne przekaźniki', + 'Reset to default': 'Przywróć domyślne', + 'Default relays description': 'Używane do odpytywania konfiguracji przekaźników innych użytkowników i jako rozwiązanie awaryjne, gdy użytkownicy nie mają skonfigurowanych przekaźników.', + 'Default relays warning': 'Ostrzeżenie: Nie modyfikuj tych ustawień pochopnie, może to wpłynąć na podstawowe doświadczenie.', + 'Invalid relay URL': 'Nieprawidłowy adres URL przekaźnika' } } diff --git a/src/i18n/locales/pt-BR.ts b/src/i18n/locales/pt-BR.ts index d69891c..4895fae 100644 --- a/src/i18n/locales/pt-BR.ts +++ b/src/i18n/locales/pt-BR.ts @@ -666,6 +666,11 @@ export default { 'Calculado com base na reputação do usuário e no percentil da rede social', 'Auto-load profile pictures': 'Carregar fotos de perfil automaticamente', 'Disable live feed': 'Desativar feed ao vivo', - 'Enable live feed': 'Ativar feed ao vivo' + 'Enable live feed': 'Ativar feed ao vivo', + 'Default relays': 'Relays padrão', + 'Reset to default': 'Redefinir para padrão', + 'Default relays description': 'Usados para consultar as configurações de relays de outros usuários e como alternativa quando os usuários não têm relays configurados.', + 'Default relays warning': 'Aviso: Não modifique essas configurações casualmente, pois pode afetar sua experiência básica.', + 'Invalid relay URL': 'URL de relay inválida' } } diff --git a/src/i18n/locales/pt-PT.ts b/src/i18n/locales/pt-PT.ts index d08411f..2af8304 100644 --- a/src/i18n/locales/pt-PT.ts +++ b/src/i18n/locales/pt-PT.ts @@ -669,6 +669,11 @@ export default { 'Calculado com base na reputação do utilizador e no percentil da rede social', 'Auto-load profile pictures': 'Carregar fotos de perfil automaticamente', 'Disable live feed': 'Desativar feed ao vivo', - 'Enable live feed': 'Ativar feed ao vivo' + 'Enable live feed': 'Ativar feed ao vivo', + 'Default relays': 'Relays predefinidos', + 'Reset to default': 'Repor predefinições', + 'Default relays description': 'Utilizados para consultar as configurações de relays de outros utilizadores e como alternativa quando os utilizadores não têm relays configurados.', + 'Default relays warning': 'Aviso: Não modifique estas configurações casualmente, pois pode afetar a sua experiência básica.', + 'Invalid relay URL': 'URL de relay inválido' } } diff --git a/src/i18n/locales/ru.ts b/src/i18n/locales/ru.ts index 3e303a7..4e71081 100644 --- a/src/i18n/locales/ru.ts +++ b/src/i18n/locales/ru.ts @@ -669,6 +669,11 @@ export default { 'Рассчитывается на основе репутации пользователя и процентиля социальной сети', 'Auto-load profile pictures': 'Автозагрузка аватаров', 'Disable live feed': 'Отключить прямую трансляцию', - 'Enable live feed': 'Включить прямую трансляцию' + 'Enable live feed': 'Включить прямую трансляцию', + 'Default relays': 'Реле по умолчанию', + 'Reset to default': 'Сбросить по умолчанию', + 'Default relays description': 'Используются для запроса конфигураций реле других пользователей и в качестве резервного варианта, когда у пользователей не настроены реле.', + 'Default relays warning': 'Предупреждение: Не изменяйте эти настройки без необходимости, это может повлиять на базовый опыт использования.', + 'Invalid relay URL': 'Неверный URL реле' } } diff --git a/src/i18n/locales/th.ts b/src/i18n/locales/th.ts index 75dcf26..8f52ae8 100644 --- a/src/i18n/locales/th.ts +++ b/src/i18n/locales/th.ts @@ -655,6 +655,11 @@ export default { 'คำนวณจากชื่อเสียงของผู้ใช้และเปอร์เซ็นไทล์ของเครือข่ายสังคม', 'Auto-load profile pictures': 'โหลดรูปโปรไฟล์อัตโนมัติ', 'Disable live feed': 'ปิดฟีดสด', - 'Enable live feed': 'เปิดฟีดสด' + 'Enable live feed': 'เปิดฟีดสด', + 'Default relays': 'รีเลย์เริ่มต้น', + 'Reset to default': 'รีเซ็ตเป็นค่าเริ่มต้น', + 'Default relays description': 'ใช้สำหรับสอบถามการกำหนดค่ารีเลย์ของผู้ใช้อื่นและเป็นทางเลือกสำรองเมื่อผู้ใช้ไม่ได้กำหนดค่ารีเลย์', + 'Default relays warning': 'คำเตือน: กรุณาอย่าแก้ไขการตั้งค่าเหล่านี้โดยไม่ระมัดระวัง เพราะอาจส่งผลต่อประสบการณ์พื้นฐานของคุณ', + 'Invalid relay URL': 'URL รีเลย์ไม่ถูกต้อง' } } diff --git a/src/i18n/locales/zh-TW.ts b/src/i18n/locales/zh-TW.ts index ec9dccf..b6cf94f 100644 --- a/src/i18n/locales/zh-TW.ts +++ b/src/i18n/locales/zh-TW.ts @@ -638,6 +638,11 @@ export default { 'trust-filter.trust-score-description': '基於使用者的聲譽和社交網路計算信任度百分位', 'Auto-load profile pictures': '自動載入大頭照', 'Disable live feed': '停用即時推送', - 'Enable live feed': '啟用即時推送' + 'Enable live feed': '啟用即時推送', + 'Default relays': '預設中繼', + 'Reset to default': '重置為預設', + 'Default relays description': '用於查詢其他使用者的中繼配置,並在使用者沒有配置中繼時作為回退策略。', + 'Default relays warning': '警告:請不要隨意修改這些設定,可能會影響基礎體驗。', + 'Invalid relay URL': '無效的中繼地址' } } diff --git a/src/i18n/locales/zh.ts b/src/i18n/locales/zh.ts index 23d3b3c..062f626 100644 --- a/src/i18n/locales/zh.ts +++ b/src/i18n/locales/zh.ts @@ -643,6 +643,11 @@ export default { 'trust-filter.trust-score-description': '基于用户的声誉和社交网络计算信任度百分位', 'Auto-load profile pictures': '自动加载头像', 'Disable live feed': '禁用实时推送', - 'Enable live feed': '启用实时推送' + 'Enable live feed': '启用实时推送', + 'Default relays': '默认中继', + 'Reset to default': '重置为默认', + 'Default relays description': '用于查询其他用户的中继配置,并在用户没有配置中继时作为回退策略。', + 'Default relays warning': '警告:请不要随意修改这些设置,可能会影响基础体验。', + 'Invalid relay URL': '无效的中继地址' } } diff --git a/src/lib/event-metadata.ts b/src/lib/event-metadata.ts index 9312f98..750a63b 100644 --- a/src/lib/event-metadata.ts +++ b/src/lib/event-metadata.ts @@ -1,10 +1,11 @@ -import { BIG_RELAY_URLS, MAX_PINNED_NOTES, POLL_TYPE } from '@/constants' +import { MAX_PINNED_NOTES, POLL_TYPE } from '@/constants' import { TEmoji, TPollType, TRelayList, TRelaySet } from '@/types' import { Event, kinds } from 'nostr-tools' import { buildATag } from './draft-event' import { getReplaceableEventIdentifier } from './event' import { getAmountFromInvoice, getLightningAddressFromProfile } from './lightning' import { formatPubkey, isValidPubkey, pubkeyToNpub } from './pubkey' +import { getDefaultRelayUrls } from './relay' import { generateBech32IdFromETag, getEmojiInfosFromEmojiTags, tagNameEquals } from './tag' import { isOnionUrl, isWebsocketUrl, normalizeHttpUrl, normalizeUrl } from './url' @@ -12,8 +13,10 @@ export function getRelayListFromEvent( event?: Event | null, filterOutOnionRelays: boolean = true ): TRelayList { + const defaultRelays = getDefaultRelayUrls() + if (!event) { - return { write: BIG_RELAY_URLS, read: BIG_RELAY_URLS, originalRelays: [] } + return { write: defaultRelays, read: defaultRelays, originalRelays: [] } } const relayList = { write: [], read: [], originalRelays: [] } as TRelayList @@ -38,11 +41,11 @@ export function getRelayListFromEvent( } }) - // If there are too many relays, use the default BIG_RELAY_URLS + // If there are too many relays, use the default relays // Because they don't know anything about relays, their settings cannot be trusted return { - write: relayList.write.length && relayList.write.length <= 8 ? relayList.write : BIG_RELAY_URLS, - read: relayList.read.length && relayList.write.length <= 8 ? relayList.read : BIG_RELAY_URLS, + write: relayList.write.length && relayList.write.length <= 8 ? relayList.write : defaultRelays, + read: relayList.read.length && relayList.write.length <= 8 ? relayList.read : defaultRelays, originalRelays: relayList.originalRelays } } diff --git a/src/lib/relay.ts b/src/lib/relay.ts index de6c733..fae5270 100644 --- a/src/lib/relay.ts +++ b/src/lib/relay.ts @@ -1,6 +1,10 @@ -import { BIG_RELAY_URLS } from '@/constants' +import storage from '@/services/local-storage.service' import { TRelayInfo } from '@/types' +export function getDefaultRelayUrls() { + return storage.getDefaultRelayUrls() +} + export function checkAlgoRelay(relayInfo: TRelayInfo | undefined) { return relayInfo?.software === 'https://github.com/bitvora/algo-relay' // hardcode for now } @@ -14,7 +18,8 @@ export function checkNip43Support(relayInfo: TRelayInfo | undefined) { } export function filterOutBigRelays(relayUrls: string[]) { - return relayUrls.filter((url) => !BIG_RELAY_URLS.includes(url)) + const defaultRelays = getDefaultRelayUrls() + return relayUrls.filter((url) => !defaultRelays.includes(url)) } export function recommendRelaysByLanguage(i18nLanguage: string) { diff --git a/src/pages/primary/ExplorePage/index.tsx b/src/pages/primary/ExplorePage/index.tsx index eaf9587..f621680 100644 --- a/src/pages/primary/ExplorePage/index.tsx +++ b/src/pages/primary/ExplorePage/index.tsx @@ -3,9 +3,10 @@ import FollowingFavoriteRelayList from '@/components/FollowingFavoriteRelayList' import NoteList from '@/components/NoteList' import Tabs from '@/components/Tabs' import { Button } from '@/components/ui/button' -import { BIG_RELAY_URLS, ExtendedKind } from '@/constants' +import { ExtendedKind } from '@/constants' import PrimaryPageLayout from '@/layouts/PrimaryPageLayout' import { getReplaceableEventIdentifier } from '@/lib/event' +import { getDefaultRelayUrls } from '@/lib/relay' import { isLocalNetworkUrl, isOnionUrl, isWebsocketUrl } from '@/lib/url' import storage from '@/services/local-storage.service' import { TPageRef } from '@/types' @@ -42,7 +43,7 @@ const ExplorePage = forwardRef((_, ref) => { ) : tab === 'reviews' ? ( ) : ( )} diff --git a/src/pages/secondary/NoteListPage/index.tsx b/src/pages/secondary/NoteListPage/index.tsx index 4ccb507..f8bde24 100644 --- a/src/pages/secondary/NoteListPage/index.tsx +++ b/src/pages/secondary/NoteListPage/index.tsx @@ -1,10 +1,11 @@ import { Favicon } from '@/components/Favicon' import NormalFeed from '@/components/NormalFeed' import { Button } from '@/components/ui/button' -import { BIG_RELAY_URLS, SEARCHABLE_RELAY_URLS } from '@/constants' +import { SEARCHABLE_RELAY_URLS } from '@/constants' import SecondaryPageLayout from '@/layouts/SecondaryPageLayout' import { toProfileList } from '@/lib/link' import { fetchPubkeysFromDomain, getWellKnownNip05Url } from '@/lib/nip05' +import { getDefaultRelayUrls } from '@/lib/relay' import { useSecondaryPage } from '@/PageManager' import { useNostr } from '@/providers/NostrProvider' import client from '@/services/client.service' @@ -47,7 +48,7 @@ const NoteListPage = forwardRef(({ index }: { index?: number }, ref) => { setSubRequests([ { filter: { '#t': [hashtag], ...(kinds.length > 0 ? { kinds } : {}) }, - urls: BIG_RELAY_URLS + urls: getDefaultRelayUrls() } ]) return diff --git a/src/pages/secondary/RelayReviewsPage/index.tsx b/src/pages/secondary/RelayReviewsPage/index.tsx index d3ac099..091f9c9 100644 --- a/src/pages/secondary/RelayReviewsPage/index.tsx +++ b/src/pages/secondary/RelayReviewsPage/index.tsx @@ -1,6 +1,7 @@ import NoteList from '@/components/NoteList' -import { BIG_RELAY_URLS, ExtendedKind } from '@/constants' +import { ExtendedKind } from '@/constants' import SecondaryPageLayout from '@/layouts/SecondaryPageLayout' +import { getDefaultRelayUrls } from '@/lib/relay' import { normalizeUrl, simplifyUrl } from '@/lib/url' import { forwardRef, useMemo } from 'react' import { useTranslation } from 'react-i18next' @@ -24,7 +25,7 @@ const RelayReviewsPage = forwardRef(({ url, index }: { url?: string; index?: num showKinds={[ExtendedKind.RELAY_REVIEW]} subRequests={[ { - urls: [normalizedUrl, ...BIG_RELAY_URLS], + urls: [normalizedUrl, ...getDefaultRelayUrls()], filter: { '#d': [normalizedUrl] } } ]} diff --git a/src/pages/secondary/SystemSettingsPage/index.tsx b/src/pages/secondary/SystemSettingsPage/index.tsx index 8a949c5..a043446 100644 --- a/src/pages/secondary/SystemSettingsPage/index.tsx +++ b/src/pages/secondary/SystemSettingsPage/index.tsx @@ -1,3 +1,4 @@ +import DefaultRelaysSetting from '@/components/DefaultRelaysSetting' import { Input } from '@/components/ui/input' import { Label } from '@/components/ui/label' import { Switch } from '@/components/ui/switch' @@ -43,6 +44,9 @@ const SystemSettingsPage = forwardRef(({ index }: { index?: number }, ref) => { }} /> +
+ +
) diff --git a/src/providers/FavoriteRelaysProvider.tsx b/src/providers/FavoriteRelaysProvider.tsx index 79fcbdc..6c8e438 100644 --- a/src/providers/FavoriteRelaysProvider.tsx +++ b/src/providers/FavoriteRelaysProvider.tsx @@ -1,8 +1,8 @@ -import { BIG_RELAY_URLS } from '@/constants' import { createFavoriteRelaysDraftEvent, createRelaySetDraftEvent } from '@/lib/draft-event' import { getReplaceableEventIdentifier } from '@/lib/event' import { getRelaySetFromEvent } from '@/lib/event-metadata' import { randomString } from '@/lib/random' +import { getDefaultRelayUrls } from '@/lib/relay' import { isWebsocketUrl, normalizeUrl } from '@/lib/url' import client from '@/services/client.service' import indexedDb from '@/services/indexed-db.service' @@ -94,7 +94,7 @@ export function FavoriteRelaysProvider({ children }: { children: React.ReactNode setRelaySetEvents(storedRelaySetEvents.filter(Boolean) as Event[]) const newRelaySetEvents = await client.fetchEvents( - (relayList?.write ?? []).concat(BIG_RELAY_URLS).slice(0, 5), + (relayList?.write ?? []).concat(getDefaultRelayUrls()).slice(0, 5), { kinds: [kinds.Relaysets], authors: [pubkey], diff --git a/src/providers/NostrProvider/index.tsx b/src/providers/NostrProvider/index.tsx index a6ac683..ba909ba 100644 --- a/src/providers/NostrProvider/index.tsx +++ b/src/providers/NostrProvider/index.tsx @@ -1,6 +1,6 @@ import LoginDialog from '@/components/LoginDialog' import PasswordInputDialog from '@/components/PasswordInputDialog' -import { ApplicationDataKey, BIG_RELAY_URLS, ExtendedKind } from '@/constants' +import { ApplicationDataKey, ExtendedKind } from '@/constants' import { createDeletionRequestDraftEvent, createFollowListDraftEvent, @@ -16,6 +16,7 @@ import { } from '@/lib/event' import { getProfileFromEvent, getRelayListFromEvent } from '@/lib/event-metadata' import { formatPubkey, pubkeyToNpub } from '@/lib/pubkey' +import { getDefaultRelayUrls } from '@/lib/relay' import client from '@/services/client.service' import customEmojiService from '@/services/custom-emoji.service' import indexedDb from '@/services/indexed-db.service' @@ -246,7 +247,8 @@ export function NostrProvider({ children }: { children: React.ReactNode }) { setPinnedUsersEvent(storedPinnedUsersEvent) } - const relayListEvents = await client.fetchEvents(BIG_RELAY_URLS, { + const defaultRelays = getDefaultRelayUrls() + const relayListEvents = await client.fetchEvents(defaultRelays, { kinds: [kinds.RelayList], authors: [account.pubkey] }) @@ -258,7 +260,7 @@ export function NostrProvider({ children }: { children: React.ReactNode }) { } setRelayList(relayList) - const events = await client.fetchEvents(relayList.write.concat(BIG_RELAY_URLS).slice(0, 4), [ + const events = await client.fetchEvents(relayList.write.concat(defaultRelays).slice(0, 4), [ { kinds: [ kinds.Metadata, @@ -637,13 +639,14 @@ export function NostrProvider({ children }: { children: React.ReactNode }) { } const setupNewUser = async (signer: ISigner) => { + const defaultRelays = getDefaultRelayUrls() await Promise.allSettled([ - client.publishEvent(BIG_RELAY_URLS, await signer.signEvent(createFollowListDraftEvent([]))), - client.publishEvent(BIG_RELAY_URLS, await signer.signEvent(createMuteListDraftEvent([]))), + client.publishEvent(defaultRelays, await signer.signEvent(createFollowListDraftEvent([]))), + client.publishEvent(defaultRelays, await signer.signEvent(createMuteListDraftEvent([]))), client.publishEvent( - BIG_RELAY_URLS, + defaultRelays, await signer.signEvent( - createRelayListDraftEvent(BIG_RELAY_URLS.map((url) => ({ url, scope: 'both' }))) + createRelayListDraftEvent(defaultRelays.map((url) => ({ url, scope: 'both' }))) ) ) ]) diff --git a/src/providers/NotificationProvider.tsx b/src/providers/NotificationProvider.tsx index 1f98835..fd1c884 100644 --- a/src/providers/NotificationProvider.tsx +++ b/src/providers/NotificationProvider.tsx @@ -1,6 +1,7 @@ -import { BIG_RELAY_URLS, ExtendedKind } from '@/constants' +import { ExtendedKind } from '@/constants' import { compareEvents } from '@/lib/event' import { notificationFilter } from '@/lib/notification' +import { getDefaultRelayUrls } from '@/lib/relay' import { usePrimaryPage } from '@/PageManager' import client from '@/services/client.service' import storage from '@/services/local-storage.service' @@ -103,7 +104,8 @@ export function NotificationProvider({ children }: { children: React.ReactNode } try { let eosed = false const relayList = await client.fetchRelayList(pubkey) - const relays = relayList.read.length > 0 ? relayList.read.slice(0, 5) : BIG_RELAY_URLS + const relays = + relayList.read.length > 0 ? relayList.read.slice(0, 5) : getDefaultRelayUrls() const subCloser = client.subscribe( relays, [ diff --git a/src/services/client.service.ts b/src/services/client.service.ts index 9550b9a..c9f9f96 100644 --- a/src/services/client.service.ts +++ b/src/services/client.service.ts @@ -1,4 +1,4 @@ -import { BIG_RELAY_URLS, ExtendedKind, SEARCHABLE_RELAY_URLS } from '@/constants' +import { ExtendedKind, SEARCHABLE_RELAY_URLS } from '@/constants' import { compareEvents, getReplaceableCoordinate, @@ -7,7 +7,7 @@ import { } from '@/lib/event' import { getProfileFromEvent, getRelayListFromEvent } from '@/lib/event-metadata' import { formatPubkey, isValidPubkey, pubkeyToNpub, userIdToPubkey } from '@/lib/pubkey' -import { filterOutBigRelays } from '@/lib/relay' +import { filterOutBigRelays, getDefaultRelayUrls } from '@/lib/relay' import { getPubkeysFromPTags, getServersFromServerTags, tagNameEquals } from '@/lib/tag' import { mergeTimelines } from '@/lib/timeline' import { isLocalNetworkUrl, isWebsocketUrl, normalizeUrl } from '@/lib/url' @@ -97,6 +97,7 @@ class ClientService extends EventTarget { } } + const defaultRelays = getDefaultRelayUrls() const relaySet = new Set() if (specifiedRelayUrls?.length) { specifiedRelayUrls.forEach((url) => relaySet.add(url)) @@ -137,20 +138,20 @@ class ClientService extends EventTarget { ExtendedKind.RELAY_REVIEW ].includes(event.kind) ) { - BIG_RELAY_URLS.forEach((url) => relaySet.add(url)) + defaultRelays.forEach((url) => relaySet.add(url)) } if (event.kind === ExtendedKind.COMMENT) { const rootITag = event.tags.find(tagNameEquals('I')) if (rootITag) { - // For external content comments, always publish to big relays - BIG_RELAY_URLS.forEach((url) => relaySet.add(url)) + // For external content comments, always publish to default relays + defaultRelays.forEach((url) => relaySet.add(url)) } } } if (!relaySet.size) { - BIG_RELAY_URLS.forEach((url) => relaySet.add(url)) + defaultRelays.forEach((url) => relaySet.add(url)) } return Array.from(relaySet) @@ -166,7 +167,7 @@ class ClientService extends EventTarget { const relayLists = await this.fetchRelayLists(filter['#p']) return Array.from(new Set(relayLists.flatMap((list) => list.read.slice(0, 5)))) } - return BIG_RELAY_URLS + return getDefaultRelayUrls() } async publishEvent(relayUrls: string[], event: NEvent) { @@ -807,7 +808,11 @@ class ClientService extends EventTarget { } = {} ) { const relays = Array.from(new Set(urls)) - const events = await this.query(relays.length > 0 ? relays : BIG_RELAY_URLS, filter, onevent) + const events = await this.query( + relays.length > 0 ? relays : getDefaultRelayUrls(), + filter, + onevent + ) if (cache) { events.forEach((evt) => { this.addEventToCache(evt) @@ -935,7 +940,7 @@ class ClientService extends EventTarget { } private async fetchEventsFromBigRelays(ids: readonly string[]) { - const events = await this.query(BIG_RELAY_URLS, { + const events = await this.query(getDefaultRelayUrls(), { ids: Array.from(new Set(ids)), limit: ids.length }) @@ -961,7 +966,7 @@ class ClientService extends EventTarget { private async _fetchFollowingFavoriteRelays(pubkey: string) { const fetchNewData = async () => { const followings = await this.fetchFollowings(pubkey) - const events = await this.fetchEvents(BIG_RELAY_URLS, { + const events = await this.fetchEvents(getDefaultRelayUrls(), { authors: followings, kinds: [ExtendedKind.FAVORITE_RELAYS, kinds.Relaysets], limit: 1000 @@ -1182,9 +1187,10 @@ class ClientService extends EventTarget { if (event) { return getRelayListFromEvent(event, storage.getFilterOutOnionRelays()) } + const defaultRelays = getDefaultRelayUrls() return { - write: BIG_RELAY_URLS, - read: BIG_RELAY_URLS, + write: defaultRelays, + read: defaultRelays, originalRelays: [] } }) @@ -1224,7 +1230,7 @@ class ClientService extends EventTarget { const eventsMap = new Map() await Promise.allSettled( Array.from(groups.entries()).map(async ([kind, pubkeys]) => { - const events = await this.query(BIG_RELAY_URLS, { + const events = await this.query(getDefaultRelayUrls(), { authors: pubkeys, kinds: [kind] }) @@ -1340,7 +1346,7 @@ class ClientService extends EventTarget { : { authors: [pubkey], kinds: [kind] }) as Filter ) const relayList = await this.fetchRelayList(pubkey) - const relays = relayList.write.concat(BIG_RELAY_URLS).slice(0, 5) + const relays = relayList.write.concat(getDefaultRelayUrls()).slice(0, 5) const events = await this.query(relays, filters) for (const event of events) { @@ -1471,10 +1477,10 @@ class ClientService extends EventTarget { // If many websocket connections are initiated simultaneously, it will be // very slow on Safari (for unknown reason) if (isSafari()) { - let urls = BIG_RELAY_URLS + let urls = getDefaultRelayUrls() if (myPubkey) { const relayList = await this.fetchRelayList(myPubkey) - urls = relayList.read.concat(BIG_RELAY_URLS).slice(0, 5) + urls = relayList.read.concat(getDefaultRelayUrls()).slice(0, 5) } return [{ urls, filter: { authors: pubkeys } }] } diff --git a/src/services/lightning.service.ts b/src/services/lightning.service.ts index f9c3287..d7c2eb5 100644 --- a/src/services/lightning.service.ts +++ b/src/services/lightning.service.ts @@ -1,5 +1,6 @@ -import { BIG_RELAY_URLS, CODY_PUBKEY, JUMBLE_PUBKEY } from '@/constants' +import { CODY_PUBKEY, JUMBLE_PUBKEY } from '@/constants' import { getZapInfoFromEvent } from '@/lib/event-metadata' +import { getDefaultRelayUrls } from '@/lib/relay' import { TProfile } from '@/types' import { init, launchPaymentModal } from '@getalby/bitcoin-connect-react' import { Invoice } from '@getalby/lightning-tools' @@ -52,7 +53,7 @@ class LightningService { client.fetchRelayList(recipient), sender ? client.fetchRelayList(sender) - : Promise.resolve({ read: BIG_RELAY_URLS, write: BIG_RELAY_URLS }) + : Promise.resolve({ read: getDefaultRelayUrls(), write: getDefaultRelayUrls() }) ]) if (!profile) { throw new Error('Recipient not found') @@ -69,7 +70,7 @@ class LightningService { relays: receiptRelayList.read .slice(0, 4) .concat(senderRelayList.write.slice(0, 3)) - .concat(BIG_RELAY_URLS), + .concat(getDefaultRelayUrls()), comment }) const zapRequest = await client.signer.signEvent(zapRequestDraft) @@ -134,7 +135,7 @@ class LightningService { filter['#e'] = [event.id] } subCloser = client.subscribe( - senderRelayList.write.concat(BIG_RELAY_URLS).slice(0, 4), + senderRelayList.write.concat(getDefaultRelayUrls()).slice(0, 4), filter, { onevent: (evt) => { diff --git a/src/services/local-storage.service.ts b/src/services/local-storage.service.ts index 6cb87ab..bbcf028 100644 --- a/src/services/local-storage.service.ts +++ b/src/services/local-storage.service.ts @@ -1,5 +1,6 @@ import { ALLOWED_FILTER_KINDS, + BIG_RELAY_URLS, DEFAULT_FAVICON_URL_TEMPLATE, DEFAULT_NIP_96_SERVICE, ExtendedKind, @@ -21,9 +22,9 @@ import { TMediaAutoLoadPolicy, TMediaUploadServiceConfig, TNoteListMode, - TProfilePictureAutoLoadPolicy, - TNsfwDisplayPolicy, TNotificationStyle, + TNsfwDisplayPolicy, + TProfilePictureAutoLoadPolicy, TRelaySet, TThemeSetting, TTranslationServiceConfig @@ -65,6 +66,7 @@ class LocalStorageService { private nsfwDisplayPolicy: TNsfwDisplayPolicy = NSFW_DISPLAY_POLICY.HIDE_CONTENT private minTrustScore: number = 40 private enableLiveFeed: boolean = false + private defaultRelayUrls: string[] = BIG_RELAY_URLS constructor() { if (!LocalStorageService.instance) { @@ -278,6 +280,22 @@ class LocalStorageService { this.enableLiveFeed = window.localStorage.getItem(StorageKey.ENABLE_LIVE_FEED) === 'true' + const defaultRelayUrlsStr = window.localStorage.getItem(StorageKey.DEFAULT_RELAY_URLS) + if (defaultRelayUrlsStr) { + try { + const urls = JSON.parse(defaultRelayUrlsStr) + if ( + Array.isArray(urls) && + urls.length > 0 && + urls.every((url) => typeof url === 'string') + ) { + this.defaultRelayUrls = urls + } + } catch { + // Invalid JSON, use default + } + } + // Clean up deprecated data window.localStorage.removeItem(StorageKey.PINNED_PUBKEYS) window.localStorage.removeItem(StorageKey.ACCOUNT_PROFILE_EVENT_MAP) @@ -624,6 +642,15 @@ class LocalStorageService { this.enableLiveFeed = enable window.localStorage.setItem(StorageKey.ENABLE_LIVE_FEED, enable.toString()) } + + getDefaultRelayUrls() { + return this.defaultRelayUrls + } + + setDefaultRelayUrls(urls: string[]) { + this.defaultRelayUrls = urls + window.localStorage.setItem(StorageKey.DEFAULT_RELAY_URLS, JSON.stringify(urls)) + } } const instance = new LocalStorageService() diff --git a/src/services/stuff-stats.service.ts b/src/services/stuff-stats.service.ts index e16666a..f0d3cc3 100644 --- a/src/services/stuff-stats.service.ts +++ b/src/services/stuff-stats.service.ts @@ -1,6 +1,7 @@ -import { BIG_RELAY_URLS, ExtendedKind } from '@/constants' +import { ExtendedKind } from '@/constants' import { getEventKey, getReplaceableCoordinateFromEvent, isReplaceableEvent } from '@/lib/event' import { getZapInfoFromEvent } from '@/lib/event-metadata' +import { getDefaultRelayUrls } from '@/lib/relay' import { getEmojiInfosFromEmojiTags, tagNameEquals } from '@/lib/tag' import client from '@/services/client.service' import { TEmoji } from '@/types' @@ -150,7 +151,9 @@ class StuffStatsService { }) } - const relays = relayList ? relayList.read.concat(BIG_RELAY_URLS).slice(0, 5) : BIG_RELAY_URLS + const relays = relayList + ? relayList.read.concat(getDefaultRelayUrls()).slice(0, 5) + : getDefaultRelayUrls() const events: Event[] = [] await client.fetchEvents(relays, filters, { diff --git a/src/services/thread.service.ts b/src/services/thread.service.ts index f4a4b7a..07d8ede 100644 --- a/src/services/thread.service.ts +++ b/src/services/thread.service.ts @@ -1,4 +1,4 @@ -import { BIG_RELAY_URLS, ExtendedKind } from '@/constants' +import { ExtendedKind } from '@/constants' import { getEventKey, getKeyFromTag, @@ -9,6 +9,7 @@ import { isReplaceableEvent, isReplyNoteEvent } from '@/lib/event' +import { getDefaultRelayUrls } from '@/lib/relay' import { generateBech32IdFromETag } from '@/lib/tag' import client from '@/services/client.service' import dayjs from 'dayjs' @@ -69,7 +70,7 @@ class ThreadService { const relayList = await client.fetchRelayList(rootPubkey) relayUrls = relayList.read } - relayUrls = relayUrls.concat(BIG_RELAY_URLS).slice(0, 4) + relayUrls = relayUrls.concat(getDefaultRelayUrls()).slice(0, 4) // If current event is protected, we can assume its replies are also protected and stored on the same relays if (event && isProtectedEvent(event)) {