From 5ba5c26fcd96d011aa4c8344696b37547f0d75f1 Mon Sep 17 00:00:00 2001 From: codytseng Date: Sat, 15 Nov 2025 13:58:20 +0800 Subject: [PATCH] feat: add option to disable filtering for onion relays --- src/components/Settings/index.tsx | 14 ++++++------- src/constants.ts | 1 + src/i18n/locales/ar.ts | 3 ++- src/i18n/locales/de.ts | 3 ++- src/i18n/locales/en.ts | 3 ++- src/i18n/locales/es.ts | 3 ++- src/i18n/locales/fa.ts | 3 ++- src/i18n/locales/fr.ts | 3 ++- src/i18n/locales/hi.ts | 3 ++- src/i18n/locales/hu.ts | 3 ++- src/i18n/locales/it.ts | 3 ++- src/i18n/locales/ja.ts | 3 ++- src/i18n/locales/ko.ts | 3 ++- src/i18n/locales/pl.ts | 3 ++- src/i18n/locales/pt-BR.ts | 3 ++- src/i18n/locales/pt-PT.ts | 3 ++- src/i18n/locales/ru.ts | 3 ++- src/i18n/locales/th.ts | 3 ++- src/i18n/locales/zh.ts | 3 ++- src/lib/event-metadata.ts | 14 ++++++------- src/lib/url.ts | 9 +++++++++ .../secondary/SystemSettingsPage/index.tsx | 20 ++++++++++++++++++- src/providers/NostrProvider/index.tsx | 6 +++--- src/services/client.service.ts | 3 ++- src/services/local-storage.service.ts | 16 +++++++++++++++ 25 files changed, 98 insertions(+), 36 deletions(-) diff --git a/src/components/Settings/index.tsx b/src/components/Settings/index.tsx index 7943a50..a1444dd 100644 --- a/src/components/Settings/index.tsx +++ b/src/components/Settings/index.tsx @@ -129,6 +129,13 @@ export default function Settings() { {copiedNcryptsec ? : } )} + push(toSystemSettings())}> +
+ +
{t('System')}
+
+ +
@@ -143,13 +150,6 @@ export default function Settings() {
- push(toSystemSettings())}> -
- -
{t('System')}
-
- -
diff --git a/src/constants.ts b/src/constants.ts index 40076a9..7ab4cc2 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -40,6 +40,7 @@ export const StorageKey = { PRIMARY_COLOR: 'primaryColor', ENABLE_SINGLE_COLUMN_LAYOUT: 'enableSingleColumnLayout', FAVICON_URL_TEMPLATE: 'faviconUrlTemplate', + FILTER_OUT_ONION_RELAYS: 'filterOutOnionRelays', MEDIA_UPLOAD_SERVICE: 'mediaUploadService', // deprecated HIDE_UNTRUSTED_EVENTS: 'hideUntrustedEvents', // deprecated ACCOUNT_RELAY_LIST_EVENT_MAP: 'accountRelayListEventMap', // deprecated diff --git a/src/i18n/locales/ar.ts b/src/i18n/locales/ar.ts index f866f4f..fcd359f 100644 --- a/src/i18n/locales/ar.ts +++ b/src/i18n/locales/ar.ts @@ -532,6 +532,7 @@ export default { 'Failed to get invite code from relay': 'فشل الحصول على رمز الدعوة من المرحل', 'Failed to get invite code': 'فشل الحصول على رمز الدعوة', 'Invite code copied to clipboard': 'تم نسخ رمز الدعوة إلى الحافظة', - 'Favicon URL': 'رابط الأيقونة المفضلة' + 'Favicon URL': 'رابط الأيقونة المفضلة', + 'Filter out onion relays': 'تصفية مرحلات onion' } } diff --git a/src/i18n/locales/de.ts b/src/i18n/locales/de.ts index ca8720a..0ff71ab 100644 --- a/src/i18n/locales/de.ts +++ b/src/i18n/locales/de.ts @@ -548,6 +548,7 @@ export default { 'Failed to get invite code from relay': 'Fehler beim Abrufen des Einladungscodes vom Relay', 'Failed to get invite code': 'Fehler beim Abrufen des Einladungscodes', 'Invite code copied to clipboard': 'Einladungscode in die Zwischenablage kopiert', - 'Favicon URL': 'Favicon-URL' + 'Favicon URL': 'Favicon-URL', + 'Filter out onion relays': 'Onion-Relays herausfiltern' } } diff --git a/src/i18n/locales/en.ts b/src/i18n/locales/en.ts index 6b6f93c..e711503 100644 --- a/src/i18n/locales/en.ts +++ b/src/i18n/locales/en.ts @@ -533,6 +533,7 @@ export default { 'Failed to get invite code from relay': 'Failed to get invite code from relay', 'Failed to get invite code': 'Failed to get invite code', 'Invite code copied to clipboard': 'Invite code copied to clipboard', - 'Favicon URL': 'Favicon URL' + 'Favicon URL': 'Favicon URL', + 'Filter out onion relays': 'Filter out onion relays' } } diff --git a/src/i18n/locales/es.ts b/src/i18n/locales/es.ts index 421288e..74d92e8 100644 --- a/src/i18n/locales/es.ts +++ b/src/i18n/locales/es.ts @@ -542,6 +542,7 @@ export default { 'Failed to get invite code from relay': 'Error al obtener código de invitación del relay', 'Failed to get invite code': 'Error al obtener código de invitación', 'Invite code copied to clipboard': 'Código de invitación copiado al portapapeles', - 'Favicon URL': 'URL del Favicon' + 'Favicon URL': 'URL del Favicon', + 'Filter out onion relays': 'Filtrar relés onion' } } diff --git a/src/i18n/locales/fa.ts b/src/i18n/locales/fa.ts index 7762659..ba9f349 100644 --- a/src/i18n/locales/fa.ts +++ b/src/i18n/locales/fa.ts @@ -537,6 +537,7 @@ export default { 'Failed to get invite code from relay': 'دریافت کد دعوت از رله ناموفق بود', 'Failed to get invite code': 'دریافت کد دعوت ناموفق بود', 'Invite code copied to clipboard': 'کد دعوت در کلیپ‌بورد کپی شد', - 'Favicon URL': 'آدرس نماد سایت' + 'Favicon URL': 'آدرس نماد سایت', + 'Filter out onion relays': 'فیلتر کردن رله‌های onion' } } diff --git a/src/i18n/locales/fr.ts b/src/i18n/locales/fr.ts index b1ef862..d776a1b 100644 --- a/src/i18n/locales/fr.ts +++ b/src/i18n/locales/fr.ts @@ -547,6 +547,7 @@ export default { 'Failed to get invite code from relay': "Échec de l'obtention du code d'invitation du relay", 'Failed to get invite code': "Échec de l'obtention du code d'invitation", 'Invite code copied to clipboard': "Code d'invitation copié dans le presse-papiers", - 'Favicon URL': 'URL du Favicon' + 'Favicon URL': 'URL du Favicon', + 'Filter out onion relays': 'Filtrer les relais onion' } } diff --git a/src/i18n/locales/hi.ts b/src/i18n/locales/hi.ts index 0e70d60..0f22d71 100644 --- a/src/i18n/locales/hi.ts +++ b/src/i18n/locales/hi.ts @@ -539,6 +539,7 @@ export default { 'Failed to get invite code from relay': 'रिले से निमंत्रण कोड प्राप्त करने में विफल', 'Failed to get invite code': 'निमंत्रण कोड प्राप्त करने में विफल', 'Invite code copied to clipboard': 'निमंत्रण कोड क्लिपबोर्ड पर कॉपी किया गया', - 'Favicon URL': 'फ़ेविकॉन URL' + 'Favicon URL': 'फ़ेविकॉन URL', + 'Filter out onion relays': 'ओनियन रिले फ़िल्टर करें' } } diff --git a/src/i18n/locales/hu.ts b/src/i18n/locales/hu.ts index dd4457e..409f04c 100644 --- a/src/i18n/locales/hu.ts +++ b/src/i18n/locales/hu.ts @@ -534,6 +534,7 @@ export default { 'Failed to get invite code from relay': 'Nem sikerült lekérni a meghívókódot a relay-től', 'Failed to get invite code': 'Nem sikerült lekérni a meghívókódot', 'Invite code copied to clipboard': 'Meghívókód vágólapra másolva', - 'Favicon URL': 'Favicon URL' + 'Favicon URL': 'Favicon URL', + 'Filter out onion relays': 'Onion relay-ek kiszűrése' } } diff --git a/src/i18n/locales/it.ts b/src/i18n/locales/it.ts index 10ac08f..7cf0d17 100644 --- a/src/i18n/locales/it.ts +++ b/src/i18n/locales/it.ts @@ -542,6 +542,7 @@ export default { 'Failed to get invite code from relay': 'Impossibile ottenere il codice di invito dal relay', 'Failed to get invite code': 'Impossibile ottenere il codice di invito', 'Invite code copied to clipboard': 'Codice di invito copiato negli appunti', - 'Favicon URL': 'URL Favicon' + 'Favicon URL': 'URL Favicon', + 'Filter out onion relays': 'Filtra relay onion' } } diff --git a/src/i18n/locales/ja.ts b/src/i18n/locales/ja.ts index eed9853..9a49546 100644 --- a/src/i18n/locales/ja.ts +++ b/src/i18n/locales/ja.ts @@ -536,6 +536,7 @@ export default { 'Failed to get invite code from relay': 'リレーから招待コードの取得に失敗しました', 'Failed to get invite code': '招待コードの取得に失敗しました', 'Invite code copied to clipboard': '招待コードをクリップボードにコピーしました', - 'Favicon URL': 'ファビコンURL' + 'Favicon URL': 'ファビコンURL', + 'Filter out onion relays': 'Onionリレーを除外' } } diff --git a/src/i18n/locales/ko.ts b/src/i18n/locales/ko.ts index 499f7f9..2a3c735 100644 --- a/src/i18n/locales/ko.ts +++ b/src/i18n/locales/ko.ts @@ -536,6 +536,7 @@ export default { 'Failed to get invite code from relay': '릴레이에서 초대 코드 가져오기 실패', 'Failed to get invite code': '초대 코드 가져오기 실패', 'Invite code copied to clipboard': '초대 코드가 클립보드에 복사되었습니다', - 'Favicon URL': '파비콘 URL' + 'Favicon URL': '파비콘 URL', + 'Filter out onion relays': '어니언 릴레이 필터링' } } diff --git a/src/i18n/locales/pl.ts b/src/i18n/locales/pl.ts index 1181c03..f40de91 100644 --- a/src/i18n/locales/pl.ts +++ b/src/i18n/locales/pl.ts @@ -542,6 +542,7 @@ export default { 'Failed to get invite code from relay': 'Nie udało się uzyskać kodu zaproszenia z przekaźnika', 'Failed to get invite code': 'Nie udało się uzyskać kodu zaproszenia', 'Invite code copied to clipboard': 'Kod zaproszenia skopiowany do schowka', - 'Favicon URL': 'URL Favicon' + 'Favicon URL': 'URL Favicon', + 'Filter out onion relays': 'Filtruj przekaźniki onion' } } diff --git a/src/i18n/locales/pt-BR.ts b/src/i18n/locales/pt-BR.ts index 881f229..7d7edcf 100644 --- a/src/i18n/locales/pt-BR.ts +++ b/src/i18n/locales/pt-BR.ts @@ -539,6 +539,7 @@ export default { 'Failed to get invite code from relay': 'Falha ao obter código de convite do relay', 'Failed to get invite code': 'Falha ao obter código de convite', 'Invite code copied to clipboard': 'Código de convite copiado para a área de transferência', - 'Favicon URL': 'URL do Favicon' + 'Favicon URL': 'URL do Favicon', + 'Filter out onion relays': 'Filtrar relays onion' } } diff --git a/src/i18n/locales/pt-PT.ts b/src/i18n/locales/pt-PT.ts index f643deb..74419f4 100644 --- a/src/i18n/locales/pt-PT.ts +++ b/src/i18n/locales/pt-PT.ts @@ -542,6 +542,7 @@ export default { 'Failed to get invite code from relay': 'Falha ao obter código de convite do relay', 'Failed to get invite code': 'Falha ao obter código de convite', 'Invite code copied to clipboard': 'Código de convite copiado para a área de transferência', - 'Favicon URL': 'URL do Favicon' + 'Favicon URL': 'URL do Favicon', + 'Filter out onion relays': 'Filtrar relays onion' } } diff --git a/src/i18n/locales/ru.ts b/src/i18n/locales/ru.ts index b530d18..f5457ad 100644 --- a/src/i18n/locales/ru.ts +++ b/src/i18n/locales/ru.ts @@ -544,6 +544,7 @@ export default { 'Failed to get invite code from relay': 'Не удалось получить код приглашения от релея', 'Failed to get invite code': 'Не удалось получить код приглашения', 'Invite code copied to clipboard': 'Код приглашения скопирован в буфер обмена', - 'Favicon URL': 'URL фавикона' + 'Favicon URL': 'URL фавикона', + 'Filter out onion relays': 'Фильтровать onion-релеи' } } diff --git a/src/i18n/locales/th.ts b/src/i18n/locales/th.ts index d9502e5..5c78ee9 100644 --- a/src/i18n/locales/th.ts +++ b/src/i18n/locales/th.ts @@ -530,6 +530,7 @@ export default { 'Failed to get invite code from relay': 'ไม่สามารถรับรหัสเชิญจากรีเลย์', 'Failed to get invite code': 'ไม่สามารถรับรหัสเชิญ', 'Invite code copied to clipboard': 'คัดลอกรหัสเชิญไปยังคลิปบอร์ดแล้ว', - 'Favicon URL': 'URL ไอคอน' + 'Favicon URL': 'URL ไอคอน', + 'Filter out onion relays': 'กรองรีเลย์ onion' } } diff --git a/src/i18n/locales/zh.ts b/src/i18n/locales/zh.ts index 7e2bfd0..a6343c1 100644 --- a/src/i18n/locales/zh.ts +++ b/src/i18n/locales/zh.ts @@ -527,6 +527,7 @@ export default { 'Failed to get invite code from relay': '从中继器获取邀请码失败', 'Failed to get invite code': '获取邀请码失败', 'Invite code copied to clipboard': '邀请码已复制到剪贴板', - 'Favicon URL': '网站图标 URL' + 'Favicon URL': '网站图标 URL', + 'Filter out onion relays': '过滤洋葱中继' } } diff --git a/src/lib/event-metadata.ts b/src/lib/event-metadata.ts index e667bc7..e02c8e5 100644 --- a/src/lib/event-metadata.ts +++ b/src/lib/event-metadata.ts @@ -5,16 +5,17 @@ import { buildATag } from './draft-event' import { getReplaceableEventIdentifier } from './event' import { getAmountFromInvoice, getLightningAddressFromProfile } from './lightning' import { formatPubkey, pubkeyToNpub } from './pubkey' -import { getEmojiInfosFromEmojiTags, generateBech32IdFromETag, tagNameEquals } from './tag' -import { isWebsocketUrl, normalizeHttpUrl, normalizeUrl } from './url' -import { isTorBrowser } from './utils' +import { generateBech32IdFromETag, getEmojiInfosFromEmojiTags, tagNameEquals } from './tag' +import { isOnionUrl, isWebsocketUrl, normalizeHttpUrl, normalizeUrl } from './url' -export function getRelayListFromEvent(event?: Event | null) { +export function getRelayListFromEvent( + event?: Event | null, + filterOutOnionRelays: boolean = true +): TRelayList { if (!event) { return { write: BIG_RELAY_URLS, read: BIG_RELAY_URLS, originalRelays: [] } } - const torBrowserDetected = isTorBrowser() const relayList = { write: [], read: [], originalRelays: [] } as TRelayList event.tags.filter(tagNameEquals('r')).forEach(([, url, type]) => { if (!url || !isWebsocketUrl(url)) return @@ -25,8 +26,7 @@ export function getRelayListFromEvent(event?: Event | null) { const scope = type === 'read' ? 'read' : type === 'write' ? 'write' : 'both' relayList.originalRelays.push({ url: normalizedUrl, scope }) - // Filter out .onion URLs if not using Tor browser - if (normalizedUrl.endsWith('.onion/') && !torBrowserDetected) return + if (filterOutOnionRelays && isOnionUrl(normalizedUrl)) return if (type === 'write') { relayList.write.push(normalizedUrl) diff --git a/src/lib/url.ts b/src/lib/url.ts index 9676dbc..9218b36 100644 --- a/src/lib/url.ts +++ b/src/lib/url.ts @@ -2,6 +2,15 @@ export function isWebsocketUrl(url: string): boolean { return /^wss?:\/\/.+$/.test(url) } +export function isOnionUrl(url: string): boolean { + try { + const hostname = new URL(url).hostname + return hostname.endsWith('.onion') + } catch { + return false + } +} + // copy from nostr-tools/utils export function normalizeUrl(url: string): string { try { diff --git a/src/pages/secondary/SystemSettingsPage/index.tsx b/src/pages/secondary/SystemSettingsPage/index.tsx index 9a71add..8a949c5 100644 --- a/src/pages/secondary/SystemSettingsPage/index.tsx +++ b/src/pages/secondary/SystemSettingsPage/index.tsx @@ -1,14 +1,19 @@ import { Input } from '@/components/ui/input' import { Label } from '@/components/ui/label' +import { Switch } from '@/components/ui/switch' import { DEFAULT_FAVICON_URL_TEMPLATE } from '@/constants' import SecondaryPageLayout from '@/layouts/SecondaryPageLayout' import { useContentPolicy } from '@/providers/ContentPolicyProvider' -import { forwardRef } from 'react' +import storage from '@/services/local-storage.service' +import { forwardRef, useState } from 'react' import { useTranslation } from 'react-i18next' const SystemSettingsPage = forwardRef(({ index }: { index?: number }, ref) => { const { t } = useTranslation() const { faviconUrlTemplate, setFaviconUrlTemplate } = useContentPolicy() + const [filterOutOnionRelays, setFilterOutOnionRelays] = useState( + storage.getFilterOutOnionRelays() + ) return ( @@ -25,6 +30,19 @@ const SystemSettingsPage = forwardRef(({ index }: { index?: number }, ref) => { placeholder={DEFAULT_FAVICON_URL_TEMPLATE} /> +
+ + { + storage.setFilterOutOnionRelays(checked) + setFilterOutOnionRelays(checked) + }} + /> +
) diff --git a/src/providers/NostrProvider/index.tsx b/src/providers/NostrProvider/index.tsx index 01cd1f3..47bf336 100644 --- a/src/providers/NostrProvider/index.tsx +++ b/src/providers/NostrProvider/index.tsx @@ -207,7 +207,7 @@ export function NostrProvider({ children }: { children: React.ReactNode }) { indexedDb.getReplaceableEvent(account.pubkey, kinds.Pinlist) ]) if (storedRelayListEvent) { - setRelayList(getRelayListFromEvent(storedRelayListEvent)) + setRelayList(getRelayListFromEvent(storedRelayListEvent, storage.getFilterOutOnionRelays())) } if (storedProfileEvent) { setProfileEvent(storedProfileEvent) @@ -237,7 +237,7 @@ export function NostrProvider({ children }: { children: React.ReactNode }) { authors: [account.pubkey] }) const relayListEvent = getLatestEvent(relayListEvents) ?? storedRelayListEvent - const relayList = getRelayListFromEvent(relayListEvent) + const relayList = getRelayListFromEvent(relayListEvent, storage.getFilterOutOnionRelays()) if (relayListEvent) { client.updateRelayListCache(relayListEvent) await indexedDb.putReplaceableEvent(relayListEvent) @@ -705,7 +705,7 @@ export function NostrProvider({ children }: { children: React.ReactNode }) { const updateRelayListEvent = async (relayListEvent: Event) => { const newRelayList = await client.updateRelayListCache(relayListEvent) - setRelayList(getRelayListFromEvent(newRelayList)) + setRelayList(getRelayListFromEvent(newRelayList, storage.getFilterOutOnionRelays())) } const updateProfileEvent = async (profileEvent: Event) => { diff --git a/src/services/client.service.ts b/src/services/client.service.ts index 13a5aa8..6be6796 100644 --- a/src/services/client.service.ts +++ b/src/services/client.service.ts @@ -31,6 +31,7 @@ import { } from 'nostr-tools' import { AbstractRelay } from 'nostr-tools/abstract-relay' import indexedDb from './indexed-db.service' +import storage from './local-storage.service' type TTimelineRef = [string, number] @@ -1131,7 +1132,7 @@ class ClientService extends EventTarget { return relayEvents.map((event) => { if (event) { - return getRelayListFromEvent(event) + return getRelayListFromEvent(event, storage.getFilterOutOnionRelays()) } return { write: BIG_RELAY_URLS, diff --git a/src/services/local-storage.service.ts b/src/services/local-storage.service.ts index a3d2e11..82ebe43 100644 --- a/src/services/local-storage.service.ts +++ b/src/services/local-storage.service.ts @@ -10,6 +10,7 @@ import { } from '@/constants' import { isSameAccount } from '@/lib/account' import { randomString } from '@/lib/random' +import { isTorBrowser } from '@/lib/utils' import { TAccount, TAccountPointer, @@ -54,6 +55,7 @@ class LocalStorageService { private primaryColor: TPrimaryColor = 'DEFAULT' private enableSingleColumnLayout: boolean = true private faviconUrlTemplate: string = DEFAULT_FAVICON_URL_TEMPLATE + private filterOutOnionRelays: boolean = !isTorBrowser() constructor() { if (!LocalStorageService.instance) { @@ -210,6 +212,11 @@ class LocalStorageService { this.faviconUrlTemplate = window.localStorage.getItem(StorageKey.FAVICON_URL_TEMPLATE) ?? DEFAULT_FAVICON_URL_TEMPLATE + const filterOutOnionRelaysStr = window.localStorage.getItem(StorageKey.FILTER_OUT_ONION_RELAYS) + if (filterOutOnionRelaysStr) { + this.filterOutOnionRelays = filterOutOnionRelaysStr !== 'false' + } + // Clean up deprecated data window.localStorage.removeItem(StorageKey.ACCOUNT_PROFILE_EVENT_MAP) window.localStorage.removeItem(StorageKey.ACCOUNT_FOLLOW_LIST_EVENT_MAP) @@ -529,6 +536,15 @@ class LocalStorageService { this.faviconUrlTemplate = template window.localStorage.setItem(StorageKey.FAVICON_URL_TEMPLATE, template) } + + getFilterOutOnionRelays() { + return this.filterOutOnionRelays + } + + setFilterOutOnionRelays(filterOut: boolean) { + this.filterOutOnionRelays = filterOut + window.localStorage.setItem(StorageKey.FILTER_OUT_ONION_RELAYS, filterOut.toString()) + } } const instance = new LocalStorageService()