Resolve relative node logo paths before swarm announcements

Hop-State: A_06FP959CFWNA2D273CCHP4R
Hop-Proposal: R_06FP957R3SZA57Z7S68VGSG
Hop-Task: T_06FP94T21NC8DA4KTNYN5A8
Hop-Attempt: AT_06FP94T21QZ7GXMYNEQH85G
This commit is contained in:
2026-07-15 00:02:05 -07:00
committed by Hop
parent 6edf8991fa
commit cb7070244c
3 changed files with 34 additions and 10 deletions
+2 -10
View File
@@ -12,19 +12,11 @@ import {
getCanonicalSwarmSeedDomain, getCanonicalSwarmSeedDomain,
getPublicSwarmDomain, getPublicSwarmDomain,
isPublicSwarmDomain, isPublicSwarmDomain,
resolveNodeAssetUrl,
} from './node-domain'; } from './node-domain';
const PUBLIC_SWARM_DOMAIN_ERROR = 'Public swarm participation requires a real ICANN 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 * Build this node's announcement payload
*/ */
@@ -48,7 +40,7 @@ export async function buildAnnouncement(): Promise<SwarmAnnouncement> {
if (node) { if (node) {
name = node.name; name = node.name;
description = node.description ?? undefined; description = node.description ?? undefined;
logoUrl = normalizeOptionalUrl(node.logoUrl); logoUrl = resolveNodeAssetUrl(node.logoUrl, domain);
publicKey = node.publicKey ?? ''; publicKey = node.publicKey ?? '';
isNsfw = node.isNsfw; isNsfw = node.isNsfw;
} }
+15
View File
@@ -3,6 +3,7 @@ import {
getCanonicalSwarmSeedDomain, getCanonicalSwarmSeedDomain,
getPublicSwarmDomain, getPublicSwarmDomain,
isPublicSwarmDomain, isPublicSwarmDomain,
resolveNodeAssetUrl,
} from './node-domain'; } from './node-domain';
describe('public swarm domains', () => { describe('public swarm domains', () => {
@@ -49,3 +50,17 @@ describe('swarm seed domains', () => {
expect(getCanonicalSwarmSeedDomain('localhost:43821')).toBeNull(); 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');
});
});
+17
View File
@@ -56,6 +56,23 @@ export function isPublicSwarmDomain(value: string | null | undefined): boolean {
return getPublicSwarmDomain(value) !== null; 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. * Resolve retired bootstrap hostnames to the node identity they represent.
* This keeps upgraded nodes working even before their persisted seed rows are * This keeps upgraded nodes working even before their persisted seed rows are