diff --git a/src/lib/swarm/discovery.ts b/src/lib/swarm/discovery.ts index 52ea44e..d826e4d 100644 --- a/src/lib/swarm/discovery.ts +++ b/src/lib/swarm/discovery.ts @@ -12,19 +12,11 @@ import { getCanonicalSwarmSeedDomain, getPublicSwarmDomain, isPublicSwarmDomain, + resolveNodeAssetUrl, } from './node-domain'; const PUBLIC_SWARM_DOMAIN_ERROR = 'Public swarm participation requires a real ICANN domain'; -function normalizeOptionalUrl(value: string | null | undefined): string | undefined { - if (!value) { - return undefined; - } - - const trimmed = value.trim(); - return trimmed.length > 0 ? trimmed : undefined; -} - /** * Build this node's announcement payload */ @@ -48,7 +40,7 @@ export async function buildAnnouncement(): Promise { if (node) { name = node.name; description = node.description ?? undefined; - logoUrl = normalizeOptionalUrl(node.logoUrl); + logoUrl = resolveNodeAssetUrl(node.logoUrl, domain); publicKey = node.publicKey ?? ''; isNsfw = node.isNsfw; } diff --git a/src/lib/swarm/node-domain.test.ts b/src/lib/swarm/node-domain.test.ts index cf2edaa..095f713 100644 --- a/src/lib/swarm/node-domain.test.ts +++ b/src/lib/swarm/node-domain.test.ts @@ -3,6 +3,7 @@ import { getCanonicalSwarmSeedDomain, getPublicSwarmDomain, isPublicSwarmDomain, + resolveNodeAssetUrl, } from './node-domain'; describe('public swarm domains', () => { @@ -49,3 +50,17 @@ describe('swarm seed domains', () => { expect(getCanonicalSwarmSeedDomain('localhost:43821')).toBeNull(); }); }); + +describe('node asset URLs', () => { + it('resolves a relative logo path against the node domain', () => { + expect( + resolveNodeAssetUrl('/api/node/logo?v=1784095823196', 'batorbros.bond') + ).toBe('https://batorbros.bond/api/node/logo?v=1784095823196'); + }); + + it('preserves an absolute HTTPS asset URL', () => { + expect( + resolveNodeAssetUrl('https://media.example.org/node/logo.png', 'batorbros.bond') + ).toBe('https://media.example.org/node/logo.png'); + }); +}); diff --git a/src/lib/swarm/node-domain.ts b/src/lib/swarm/node-domain.ts index ae87de7..a4d8a07 100644 --- a/src/lib/swarm/node-domain.ts +++ b/src/lib/swarm/node-domain.ts @@ -56,6 +56,23 @@ export function isPublicSwarmDomain(value: string | null | undefined): boolean { return getPublicSwarmDomain(value) !== null; } +export function resolveNodeAssetUrl( + value: string | null | undefined, + nodeDomain: string +): string | undefined { + const trimmed = value?.trim(); + if (!trimmed) return undefined; + + try { + const resolved = new URL(trimmed, `https://${normalizeNodeDomain(nodeDomain)}`); + return resolved.protocol === 'http:' || resolved.protocol === 'https:' + ? resolved.toString() + : undefined; + } catch { + return undefined; + } +} + /** * Resolve retired bootstrap hostnames to the node identity they represent. * This keeps upgraded nodes working even before their persisted seed rows are