From b1043b6bd8e7dd7834162c8b48da20dd321774ef Mon Sep 17 00:00:00 2001 From: Anthony Ettinger Date: Mon, 19 Oct 2026 18:39:15 +0000 Subject: [PATCH 1/2] fix(config): keep watchBase when the config is read (0.45.2) readConfig whitelists its fields, so a watchBase written to config.json was dropped and the guide linked rooms at the server's own :4321 address instead of live.streaming. Co-Authored-By: Claude Opus 5.5 --- package.json | 2 +- src/library.ts | 1 + test/guide-rooms.test.ts | 17 ++++++++++++++++- 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 548df08..22830d6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "nixamp", - "version": "0.45.1", + "version": "0.45.2", "description": "It really whips the terminal's ass. A Winamp-shaped audio player for your terminal.", "license": "MIT", "type": "module", diff --git a/src/library.ts b/src/library.ts index 572985d..42b6a08 100644 --- a/src/library.ts +++ b/src/library.ts @@ -59,6 +59,7 @@ export function readConfig(): Config { ...(Array.isArray(record["guideHosts"]) ? { guideHosts: (record["guideHosts"] as unknown[]).filter((one): one is string => typeof one === "string") } : {}), ...(typeof record["guideAds"] === "object" && record["guideAds"] !== null ? { guideAds: record["guideAds"] as Config["guideAds"] } : {}), ...(Array.isArray(record["guidePeers"]) ? { guidePeers: (record["guidePeers"] as unknown[]).filter((one): one is string => typeof one === "string") } : {}), + ...(typeof record["watchBase"] === "string" ? { watchBase: record["watchBase"] } : {}), }; } catch { return {}; diff --git a/test/guide-rooms.test.ts b/test/guide-rooms.test.ts index be00304..f82239b 100644 --- a/test/guide-rooms.test.ts +++ b/test/guide-rooms.test.ts @@ -5,7 +5,8 @@ import type { AddressInfo } from "node:net"; import { mkdtempSync, rmSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; -import { handleRooms, RoomProbe, roomAddress, roomEntries, RoomStore, ROOMS_PER_MEMBER, watchPage } from "../src/guide-rooms.ts"; +import { handleRooms, RoomProbe, roomAddress, roomEntries, RoomStore, ROOMS_PER_MEMBER, watchBase, watchPage } from "../src/guide-rooms.ts"; +import { readConfig, writeConfig } from "../src/library.ts"; import { renderGuide } from "../src/station-guide.ts"; const PLAY = "https://server1.chovy.nixamp.com:4321/api/channels/folder-b19b85542264?k=JV5m_XbnQFD0K9_JEnGEXw"; @@ -25,6 +26,20 @@ test("a room address is a channel or the live stream, with a key and nothing els ]) assert.equal(roomAddress(bad), null, bad); }); +test("watchBase survives a config read, so the guide links rooms where it was told", () => { + const dir = mkdtempSync(join(tmpdir(), "rooms-config-")); + const saved = process.env["XDG_STATE_HOME"]; + process.env["XDG_STATE_HOME"] = dir; + try { + writeConfig({ watchBase: "https://live.streaming.pit.world" }); + assert.equal(readConfig().watchBase, "https://live.streaming.pit.world"); + assert.equal(watchBase("https://fallback.example", {}), "https://live.streaming.pit.world"); + } finally { + if (saved === undefined) delete process.env["XDG_STATE_HOME"]; else process.env["XDG_STATE_HOME"] = saved; + rmSync(dir, { recursive: true, force: true }); + } +}); + test("the room page carries the stream address", () => { assert.equal(watchPage("https://live.streaming.pit.world", PLAY), `https://live.streaming.pit.world/watch?play=${encodeURIComponent(PLAY)}`); }); From eb8cb33d713a4f45470b906a20fda374f3047f5f Mon Sep 17 00:00:00 2001 From: Anthony Ettinger Date: Mon, 19 Oct 2026 18:46:17 +0000 Subject: [PATCH 2/2] fix(guide): parse the room publish body; the server hands it over as text Every Publish on live.streaming failed with 'play must be a nixamp room's stream address': server.ts readBody returns a string, and the handler read .play off it. The test now feeds text the same way. Co-Authored-By: Claude Opus 5.5 --- src/guide-rooms.ts | 8 +++++++- test/guide-rooms.test.ts | 3 ++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/guide-rooms.ts b/src/guide-rooms.ts index 1a78653..12e2110 100644 --- a/src/guide-rooms.ts +++ b/src/guide-rooms.ts @@ -243,7 +243,13 @@ export async function handleRooms(request: IncomingMessage, response: ServerResp return true; } if (path === "/api/v1/guide/rooms" && method === "POST") { - const body = (await ctx.readBody(request).catch(() => null)) as { play?: unknown; basis?: unknown; note?: unknown; title?: unknown; agree?: unknown } | null; + // The server hands the body over as text; parsed here, and anything unreadable is no body. + const raw = await ctx.readBody(request).catch(() => null); + let parsed: unknown = raw; + if (typeof raw === "string") { + try { parsed = JSON.parse(raw); } catch { parsed = null; } + } + const body = (parsed && typeof parsed === "object" ? parsed : null) as { play?: unknown; basis?: unknown; note?: unknown; title?: unknown; agree?: unknown } | null; const address = roomAddress(typeof body?.play === "string" ? body.play : ""); if (!address) { ctx.json(response, 400, { error: "play must be a nixamp room's stream address: https://host/api/channels/?k=KEY", code: "bad_address" }); diff --git a/test/guide-rooms.test.ts b/test/guide-rooms.test.ts index f82239b..ffe6907 100644 --- a/test/guide-rooms.test.ts +++ b/test/guide-rooms.test.ts @@ -69,7 +69,8 @@ async function serve(ctx: Omit[3], "json" | "read void handleRooms(request, response, new URL(request.url ?? "/", "http://x").pathname, { ...ctx, json: (res, status, body) => { res.writeHead(status, { "content-type": "application/json" }); res.end(JSON.stringify(body)); }, - readBody: async (req) => { let text = ""; for await (const chunk of req) text += chunk; return JSON.parse(text); }, + // Text, as the server's own readBody hands it over (a parsed object here hid a broken publish). + readBody: async (req) => { let text = ""; for await (const chunk of req) text += chunk; return text; }, }).then((handled) => { if (!handled) { response.writeHead(404); response.end(); } }); }); await new Promise((done) => server.listen(0, "127.0.0.1", done));