Browse Source

fix websocket normalization

imwald
Silberengel 3 weeks ago
parent
commit
3cc1921aa9
  1. 4
      package-lock.json
  2. 2
      package.json
  3. 10
      src/components/MailboxSetting/DiscoveredRelays.tsx
  4. 4
      src/components/MailboxSetting/index.tsx
  5. 6
      src/lib/pre-publish-relay-cap.ts
  6. 16
      src/lib/relay-list-sanitize.ts
  7. 9
      src/lib/relay-url-normalize.test.ts
  8. 4
      src/lib/relay-url-priority.ts
  9. 13
      src/lib/url.ts
  10. 7
      src/services/relay-selection.service.ts

4
package-lock.json generated

@ -1,12 +1,12 @@ @@ -1,12 +1,12 @@
{
"name": "imwald",
"version": "23.15.1",
"version": "23.16.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "imwald",
"version": "23.15.1",
"version": "23.16.1",
"license": "MIT",
"dependencies": {
"@asciidoctor/core": "^3.0.4",

2
package.json

@ -1,6 +1,6 @@ @@ -1,6 +1,6 @@
{
"name": "imwald",
"version": "23.16.0",
"version": "23.16.1",
"description": "Imwald — a user-friendly Nostr client focused on relay feed browsing, publications, and relay discovery",
"private": true,
"type": "module",

10
src/components/MailboxSetting/DiscoveredRelays.tsx

@ -1,7 +1,7 @@ @@ -1,7 +1,7 @@
import { Button } from '@/components/ui/button'
import { Skeleton } from '@/components/ui/skeleton'
import { Checkbox } from '@/components/ui/checkbox'
import { isLocalNetworkUrl, normalizeHttpRelayUrl } from '@/lib/url'
import { isLocalNetworkUrl, isWebsocketUrl, normalizeRelayUrlByScheme } from '@/lib/url'
import { getRelaysFromNip07Extension, verifyNip05 } from '@/lib/nip05'
import { useNostr } from '@/providers/NostrProvider'
import { TMailboxRelay } from '@/types'
@ -44,8 +44,8 @@ export default function DiscoveredRelays({ onAdd, localOnly = false }: { onAdd: @@ -44,8 +44,8 @@ export default function DiscoveredRelays({ onAdd, localOnly = false }: { onAdd:
const nip05Result = await verifyNip05(profile.nip05, account.pubkey)
if (nip05Result.isVerified && nip05Result.relays) {
nip05Result.relays.forEach(url => {
const normalized = normalizeHttpRelayUrl(url)
if (normalized && !discovered.has(normalized)) {
const normalized = normalizeRelayUrlByScheme(url)
if (normalized && isWebsocketUrl(normalized) && !discovered.has(normalized)) {
discovered.set(normalized, {
url: normalized,
source: 'nip05',
@ -64,8 +64,8 @@ export default function DiscoveredRelays({ onAdd, localOnly = false }: { onAdd: @@ -64,8 +64,8 @@ export default function DiscoveredRelays({ onAdd, localOnly = false }: { onAdd:
try {
const extensionRelays = await getRelaysFromNip07Extension()
extensionRelays.forEach(url => {
const normalized = normalizeHttpRelayUrl(url)
if (normalized && !discovered.has(normalized)) {
const normalized = normalizeRelayUrlByScheme(url)
if (normalized && isWebsocketUrl(normalized) && !discovered.has(normalized)) {
discovered.set(normalized, {
url: normalized,
source: 'nip07',

4
src/components/MailboxSetting/index.tsx

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
import { Button } from '@/components/ui/button'
import { isLocalNetworkUrl, normalizeHttpRelayUrl } from '@/lib/url'
import { isLocalNetworkUrl, normalizeAnyRelayUrl } from '@/lib/url'
import { useNostr } from '@/providers/NostrProvider'
import { TMailboxRelay, TMailboxRelayScope } from '@/types'
import { useEffect, useState } from 'react'
@ -98,7 +98,7 @@ export default function MailboxSetting() { @@ -98,7 +98,7 @@ export default function MailboxSetting() {
const saveNewMailboxRelay = (url: string) => {
if (url === '') return null
const normalizedUrl = normalizeHttpRelayUrl(url)
const normalizedUrl = normalizeAnyRelayUrl(url)
if (!normalizedUrl) {
return t('Invalid relay URL')
}

6
src/lib/pre-publish-relay-cap.ts

@ -2,7 +2,7 @@ import { isSocialKindBlockedKind, MAX_PUBLISH_RELAYS, SOCIAL_KIND_BLOCKED_RELAY_ @@ -2,7 +2,7 @@ import { isSocialKindBlockedKind, MAX_PUBLISH_RELAYS, SOCIAL_KIND_BLOCKED_RELAY_
import { kinds } from 'nostr-tools'
import { filterRelaysForEventPublish } from '@/lib/relay-publish-filter'
import { dedupeNormalizeRelayUrlsOrdered } from '@/lib/relay-url-priority'
import { normalizeAnyRelayUrl, normalizeHttpRelayUrl, normalizeUrl } from '@/lib/url'
import { normalizeHttpRelayUrl, normalizeRelayUrlByScheme, normalizeUrl } from '@/lib/url'
import type { NostrEvent } from 'nostr-tools'
export type TPrePublishRelayCapPreview = {
@ -50,7 +50,7 @@ export function computePrePublishRelayCapPreview({ @@ -50,7 +50,7 @@ export function computePrePublishRelayCapPreview({
const socialBlockedSet = new Set(SOCIAL_KIND_BLOCKED_RELAY_URLS.map((u) => normalizeUrl(u) || u))
outbox = dedupeNormalizeRelayUrlsOrdered(
filterRelaysForEventPublish(outbox, previewKind).filter((url) => {
const n = normalizeAnyRelayUrl(url) || url
const n = normalizeRelayUrlByScheme(url) || url
if (applySocialOutboxFilter && socialBlockedSet.has(n)) return false
return true
})
@ -64,7 +64,7 @@ export function computePrePublishRelayCapPreview({ @@ -64,7 +64,7 @@ export function computePrePublishRelayCapPreview({
const outboxNormSet = new Set(outbox)
const outboxSlotsInPublish =
selectedRelayUrls.length > 0 ? 0 : capped.filter((u) => outboxNormSet.has(u)).length
const selectedNorm = selectedRelayUrls.map((u) => normalizeAnyRelayUrl(u) || u)
const selectedNorm = selectedRelayUrls.map((u) => normalizeRelayUrlByScheme(u) || u)
const selectedContacted = selectedNorm.filter((u) => capped.includes(u)).length
const showCapHint =

16
src/lib/relay-list-sanitize.ts

@ -1,4 +1,10 @@ @@ -1,4 +1,10 @@
import { isLocalNetworkUrl, normalizeAnyRelayUrl, normalizeHttpRelayUrl, normalizeUrl } from '@/lib/url'
import {
isLocalNetworkUrl,
normalizeAnyRelayUrl,
normalizeHttpRelayUrl,
normalizeRelayUrlByScheme,
normalizeUrl
} from '@/lib/url'
import type { TMailboxRelay, TMailboxRelayScope, TRelayList } from '@/types'
/** True if this URL is not loopback / LAN (safe to open from another user's browser as a REQ target). */
@ -81,13 +87,7 @@ export function stripLocalNetworkRelaysForWssReq(urls: readonly string[]): strin @@ -81,13 +87,7 @@ export function stripLocalNetworkRelaysForWssReq(urls: readonly string[]): strin
return out
}
const normRelayKey = (u: string): string => {
const t = typeof u === 'string' ? u.trim() : ''
if (!t) return ''
if (/^wss?:\/\//i.test(t)) return normalizeUrl(t) || t
if (/^https?:\/\//i.test(t)) return normalizeHttpRelayUrl(t) || t
return normalizeUrl(t) || normalizeHttpRelayUrl(t) || t
}
const normRelayKey = (u: string): string => normalizeRelayUrlByScheme(u) || u.trim()
/**
* When NIP-65 `originalRelays` is empty but `read` / `write` URL lists are filled (e.g. PROFILE_FETCH fallback),

9
src/lib/relay-url-normalize.test.ts

@ -5,6 +5,7 @@ import { @@ -5,6 +5,7 @@ import {
httpIndexRelayBasesInUrlBatch,
normalizeAnyRelayUrl,
normalizeHttpRelayUrl,
normalizeRelayUrlByScheme,
normalizeRelayUrlForPage,
normalizeUrl
} from '@/lib/url'
@ -27,6 +28,14 @@ describe('relay URL normalization', () => { @@ -27,6 +28,14 @@ describe('relay URL normalization', () => {
expect(normalizeUrl('wss://nostr.land/')).toMatch(/^wss:\/\/nostr\.land\/?$/)
})
it('normalizeRelayUrlByScheme routes by scheme', () => {
expect(normalizeRelayUrlByScheme('wss://nostr.land/')).toMatch(/^wss:\/\/nostr\.land\/?$/)
expect(normalizeRelayUrlByScheme('https://mercury-relay.imwald.eu/')).toMatch(
/^https:\/\/mercury-relay\.imwald\.eu\/?$/
)
expect(normalizeRelayUrlByScheme('mercury-relay.imwald.eu')).toBe('')
})
it('rejects bare hostnames', () => {
expect(normalizeAnyRelayUrl('mercury-relay.imwald.eu')).toBe('')
expect(normalizeAnyRelayUrl('nostr.land')).toBe('')

4
src/lib/relay-url-priority.ts

@ -5,7 +5,7 @@ import { @@ -5,7 +5,7 @@ import {
MAX_REQ_RELAY_URLS
} from '@/constants'
import { feedRelayPolicyUrls, type FeedRelayLayer } from '@/features/feed/relay-policy'
import { isLocalNetworkUrl, normalizeAnyRelayUrl, normalizeUrl } from '@/lib/url'
import { isLocalNetworkUrl, normalizeAnyRelayUrl, normalizeRelayUrlByScheme, normalizeUrl } from '@/lib/url'
export { MAX_REQ_RELAY_URLS }
@ -13,7 +13,7 @@ export function dedupeNormalizeRelayUrlsOrdered(urls: readonly string[]): string @@ -13,7 +13,7 @@ export function dedupeNormalizeRelayUrlsOrdered(urls: readonly string[]): string
const seen = new Set<string>()
const out: string[] = []
for (const u of urls) {
const n = normalizeAnyRelayUrl(u) || u.trim()
const n = normalizeRelayUrlByScheme(u) || u.trim()
if (!n || seen.has(n)) continue
seen.add(n)
out.push(n)

13
src/lib/url.ts

@ -118,9 +118,20 @@ export function normalizeAnyRelayUrl(url: string): string { @@ -118,9 +118,20 @@ export function normalizeAnyRelayUrl(url: string): string {
return normalizeUrl(url)
}
/**
* Normalize a relay URL using the route for its scheme: `http(s)` index relays (kind 10243)
* vs `ws(s)` NIP-01 relays (kind 10002). Bare hostnames are rejected by both routes.
*/
export function normalizeRelayUrlByScheme(url: string): string {
const trimmed = url.trim()
if (!trimmed) return ''
if (isHttpOrHttpsScheme(trimmed)) return normalizeHttpRelayUrl(trimmed)
return normalizeAnyRelayUrl(trimmed)
}
/** Relay explore/detail routes accept WebSocket relays or kind-10243 HTTP index bases. */
export function normalizeRelayUrlForPage(url: string): string {
return normalizeAnyRelayUrl(url) || normalizeHttpRelayUrl(url)
return normalizeRelayUrlByScheme(url)
}
/** Stable key for per-relay session stats (scheme preserved; no https→wss aliasing). */

7
src/services/relay-selection.service.ts

@ -11,10 +11,9 @@ import client from '@/services/client.service' @@ -11,10 +11,9 @@ import client from '@/services/client.service'
import { eventService } from '@/services/client.service'
import {
canonicalRelaySessionKey,
isHttpOrHttpsScheme,
isLocalNetworkUrl,
normalizeAnyRelayUrl,
normalizeHttpRelayUrl
normalizeRelayUrlByScheme
} from '@/lib/url'
import { TRelaySet, TRelayList } from '@/types'
import logger from '@/lib/logger'
@ -144,9 +143,7 @@ class RelaySelectionService { @@ -144,9 +143,7 @@ class RelaySelectionService {
const addRelay = (url: string, type: RelaySourceType) => {
if (!url) return
const normalized = isHttpOrHttpsScheme(url)
? normalizeHttpRelayUrl(url)
: normalizeAnyRelayUrl(url)
const normalized = normalizeRelayUrlByScheme(url)
const key = normalized ? canonicalRelaySessionKey(normalized) : ''
if (key && !seen.has(key)) {
seen.add(key)

Loading…
Cancel
Save