From eaa83d8e37b2d844c87c6d47d724d5d918616908 Mon Sep 17 00:00:00 2001 From: Silberengel Date: Sun, 31 May 2026 21:41:12 +0200 Subject: [PATCH] bug-fix --- src/components/SearchBar/index.tsx | 6 ++++-- src/lib/url-relay-input.test.ts | 24 ++++++++++++++++++++++++ src/lib/url.ts | 16 +++++++++++++++- 3 files changed, 43 insertions(+), 3 deletions(-) create mode 100644 src/lib/url-relay-input.test.ts diff --git a/src/components/SearchBar/index.tsx b/src/components/SearchBar/index.tsx index caa9ac6b..5fe8a224 100644 --- a/src/components/SearchBar/index.tsx +++ b/src/components/SearchBar/index.tsx @@ -4,7 +4,7 @@ import { toNote, toNoteList } from '@/lib/link' import client from '@/services/client.service' import { eventService } from '@/services/client.service' import { randomString } from '@/lib/random' -import { isKind10243HttpRelayTagUrl, isWebsocketUrl, looksLikeNostrBech32Identifier, normalizeAnyRelayUrl, normalizeHttpRelayUrl } from '@/lib/url' +import { isKind10243HttpRelayTagUrl, isWebsocketUrl, looksLikeNostrBech32Identifier, looksLikeRelayUrlInput, normalizeAnyRelayUrl, normalizeHttpRelayUrl } from '@/lib/url' import { normalizeToDTag } from '@/lib/search-parser' import { cn } from '@/lib/utils' import { useSmartNoteNavigation, useSmartHashtagNavigation } from '@/PageManager' @@ -56,7 +56,9 @@ const SearchBar = forwardRef< return undefined } const trimmed = input.trim() - if (!trimmed || looksLikeNostrBech32Identifier(trimmed)) return undefined + if (!trimmed || looksLikeNostrBech32Identifier(trimmed) || !looksLikeRelayUrlInput(trimmed)) { + return undefined + } try { const n = normalizeAnyRelayUrl(trimmed) || normalizeHttpRelayUrl(trimmed) if (!n || (!isWebsocketUrl(n) && !isKind10243HttpRelayTagUrl(n))) return undefined diff --git a/src/lib/url-relay-input.test.ts b/src/lib/url-relay-input.test.ts new file mode 100644 index 00000000..2acd1bf0 --- /dev/null +++ b/src/lib/url-relay-input.test.ts @@ -0,0 +1,24 @@ +import { describe, expect, it } from 'vitest' +import { looksLikeRelayUrlInput } from '@/lib/url' + +describe('looksLikeRelayUrlInput', () => { + it('rejects profile names and partial username typing', () => { + for (const name of ['N', 'Nu', 'Nus', 'Nusa', 'alice', '@bob']) { + expect(looksLikeRelayUrlInput(name)).toBe(false) + } + }) + + it('accepts relay URL shapes', () => { + expect(looksLikeRelayUrlInput('wss://relay.example.com/')).toBe(true) + expect(looksLikeRelayUrlInput('ws://localhost:4869/')).toBe(true) + expect(looksLikeRelayUrlInput('https://index.example.com/')).toBe(true) + expect(looksLikeRelayUrlInput('relay.nostr1.com')).toBe(true) + expect(looksLikeRelayUrlInput('nostr.wine')).toBe(true) + }) + + it('rejects bech32 identifiers', () => { + expect( + looksLikeRelayUrlInput('npub1uq6dv4yq94704gk5r22jsqg9gy2wpxkk5dft9q5gugc8tj53nq2qg5q22d') + ).toBe(false) + }) +}) diff --git a/src/lib/url.ts b/src/lib/url.ts index 2fb6ddbf..3511508a 100644 --- a/src/lib/url.ts +++ b/src/lib/url.ts @@ -41,6 +41,20 @@ export function looksLikeNostrBech32Identifier(value: string): boolean { return /^(npub|nprofile|nevent|note|naddr)1[a-z0-9]+$/i.test(v) } +/** + * True when free-text input plausibly targets a relay URL (scheme, `://`, or hostname shape). + * Usernames, hashtags, and partial profile names must not trigger relay normalization. + */ +export function looksLikeRelayUrlInput(value: string): boolean { + const v = value.trim() + if (!v || looksLikeNostrBech32Identifier(v)) return false + if (/^(wss?|https?):?\/?/i.test(v)) return true + if (v.includes('://')) return true + // hostname.tld — e.g. relay.example.com, nostr.wine (not bare names like "Nusa") + if (/^[a-z0-9][a-z0-9.-]*\.[a-z]{2,}(?:[:/].*)?$/i.test(v)) return true + return false +} + /** True when normalized to a WebSocket relay or kind-10243 HTTP index base. */ export function isValidRelayFetchUrl(url: string): boolean { const trimmed = url.trim() @@ -235,7 +249,7 @@ export function normalizeUrl(url: string): string { const trimmed = url.trim() if (!trimmed) return '' if (!trimmed.includes('://')) { - if (!looksLikeNostrBech32Identifier(trimmed)) { + if (!looksLikeNostrBech32Identifier(trimmed) && looksLikeRelayUrlInput(trimmed)) { logger.warn('WebSocket relay URL requires ws: or wss: prefix', { url: trimmed }) } return ''