From 0b28001b42611b5bf474b9a13ce686cc32d36d34 Mon Sep 17 00:00:00 2001 From: Anthony Ettinger Date: Sun, 6 Sep 2026 03:34:13 +0000 Subject: [PATCH] The hub record advertises its public base, not localhost Caught the moment it went live. Behind Railway the request origin is the internal one, so the record built from it told every client to call back on https://localhost:3000, which makes the whole thing useless to anyone reading it. It now takes NEXT_PUBLIC_APP_URL first and the request origin only as the local fallback, exactly as the seedbox, IPTV and VOD rails already do it. Regression test on both paths. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01SKAohrRkqLKVQL2cGCAkR5 --- src/lib/openswarm/openswarm.test.ts | 18 ++++++++++++++++++ src/lib/openswarm/service.ts | 15 ++++++++++++++- 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/src/lib/openswarm/openswarm.test.ts b/src/lib/openswarm/openswarm.test.ts index b85645b..5397a03 100644 --- a/src/lib/openswarm/openswarm.test.ts +++ b/src/lib/openswarm/openswarm.test.ts @@ -246,3 +246,21 @@ describe('the README renderer', () => { expect(escapeHtml(`<&>"'`)).toBe('<&>"''); }); }); + +describe('the hub record', () => { + it('advertises the public base, not whatever origin the proxy handed it', async () => { + // Behind Railway the request origin is internal, and a record built from + // it told every client to call back on localhost. + const { publicOrigin } = await import('./service'); + const previous = process.env.NEXT_PUBLIC_APP_URL; + + process.env.NEXT_PUBLIC_APP_URL = 'https://bittorrented.com/'; + expect(publicOrigin('https://localhost:3000')).toBe('https://bittorrented.com'); + + delete process.env.NEXT_PUBLIC_APP_URL; + expect(publicOrigin('http://localhost:3000')).toBe('http://localhost:3000'); + + if (previous === undefined) delete process.env.NEXT_PUBLIC_APP_URL; + else process.env.NEXT_PUBLIC_APP_URL = previous; + }); +}); diff --git a/src/lib/openswarm/service.ts b/src/lib/openswarm/service.ts index 15e2315..2760dd2 100644 --- a/src/lib/openswarm/service.ts +++ b/src/lib/openswarm/service.ts @@ -890,8 +890,21 @@ export function hubKey(): string { return isKey(key) ? key : 'ed25519:0000000000000000000000000000000000000000000000000000000000000000'; } +/** + * The public base a client should call back on. + * + * Behind Railway's proxy the request's own origin is the internal one, so a + * record built from it told everybody to talk to localhost. The configured app + * URL wins, exactly as the seedbox and IPTV rails already do it, and the + * request origin is only the fallback for a local run. + */ +export function publicOrigin(fallbackOrigin?: string): string { + return (process.env.NEXT_PUBLIC_APP_URL || fallbackOrigin || 'https://bittorrented.com').replace(/\/+$/, ''); +} + /** `GET /.well-known/openswarm-hub.json` (ippay §5.1, pay2seed §6.1). */ -export function hubRecord(origin: string): Record { +export function hubRecord(requestOrigin: string): Record { + const origin = publicOrigin(requestOrigin); return { openswarm: OPENSWARM_VERSION, type: 'ippay.hub',