Radio starts empty and fills from the Store (Add to Radio, including The Journey FM); iCloud connect falls back to the calendar home beside the principal
build-container / build (push) Successful in 6m9s

- Store > Radio is the station finder with Add to Radio / Added; Radio under Listen lists only what was added
- Fix the Radio tab going blank (the router passed its arguments as the Store host)
- iCloud: when the account reports no calendar home, try /<id>/calendars/, and say what iCloud answered if that fails

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
nmemmertandClaude Sonnet 5.5 committed 2026-10-26 09:37:04 -04:00
1 parent a31fdcdabd
commit a804f9a8db
9 files changed
+76 -38

No files matched your search

+13
View File
@@ -0,0 +1,13 @@
// iCloud connect: when the account does not report its calendar home at the principal, the home beside it (/<id>/calendars/) is tried.
const http = require('http'), fs = require('fs'), os = require('os'), path = require('path');
process.env.DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), 'cd-')); process.env.CALDAV_TEST = '1';
const root = path.join(__dirname, '..');
const srv = http.createServer((rq, rs) => { const p = new URL(rq.url, 'http://x').pathname, x = b => { rs.writeHead(207, { 'content-type': 'application/xml' }); rs.end('<multistatus xmlns="DAV:">' + b + '</multistatus>'); };
if (p === '/') return x('<response><href>/</href><propstat><prop><current-user-principal><href>/9/principal/</href></current-user-principal></prop><status>HTTP/1.1 200 OK</status></propstat></response>');
if (p === '/9/principal/') return x('<response><href>/9/principal/</href><propstat><prop><calendar-home-set/></prop><status>HTTP/1.1 404 Not Found</status></propstat></response>');
if (p === '/9/calendars/') return x('<response><href>/9/calendars/home/</href><propstat><prop><displayname>Home</displayname><resourcetype><collection/><calendar xmlns="urn:ietf:params:xml:ns:caldav"/></resourcetype></prop><status>HTTP/1.1 200 OK</status></propstat></response>');
rs.writeHead(404); rs.end(); }).listen(0, async () => {
process.env.CALDAV_BASE = 'http://127.0.0.1:' + srv.address().port;
const users = require(root + '/users'), caldav = require(root + '/caldav'), me = users.create({ name: 'A', role: 'admin', handle: 'YQ' });
await users.runAs(me, async () => { try { const s = await caldav.connect('a@icloud.com', 'abcd-efgh'); require('assert').deepEqual(s.calendars.map(c => c.name), ['Home']); console.log('caldav fallback ok'); } catch (e) { console.error(e); process.exitCode = 1; } });
srv.close(); process.exit(process.exitCode || 0); });
+3 -1
View File
@@ -27,7 +27,7 @@ const fake = http.createServer((req, res) => {
const hub = http.createServer((req, res) => users.runAs(req.headers['x-user'] === 'b' ? people.b : people.a, () => radio.handle(req, res))).listen(0, async () => {
const base = 'http://127.0.0.1:' + hub.address().port + '/api/radio', call = (p, o = {}) => fetch(base + p, o), j = r => r.json();
try {
let r = await j(await call('/stations')); assert.equal(r.featured[0].name, 'The Journey FM'); assert.equal(r.featured[0].id, 'journeyfm'); assert.deepEqual(r.mine, []);
let r = await j(await call('/stations')); assert.equal(r.featured[0].name, 'The Journey FM'); assert.equal(r.featured[0].id, 'journeyfm'); assert.deepEqual(r.mine, []); assert.deepEqual(r.added, [], 'nothing is on Radio until it is added from the Store');
r = await j(await call('/search?q=good')); assert.deepEqual(r.items.map(s => s.name), ['Good FM'], 'streams a browser cannot play (HLS, odd codecs) and nameless ones are left out'); assert.equal(r.items[0].tags, 'christian,worship,pop,extra'); assert.equal(r.items[0].id, 'aaaaaaaa-1111');
assert.equal((await call('/search')).status, 200, 'no words: Christian stations');
// the stream
@@ -48,6 +48,8 @@ const fake = http.createServer((req, res) => {
assert.equal((await call('/stations', { method: 'POST', body: JSON.stringify({ id: 'aaaaaaaa-1111' }) })).status, 200); r = await j(await call('/stations')); assert.deepEqual(r.mine.map(s => s.name), ['Good FM']);
await call('/stations', { method: 'POST', body: JSON.stringify({ id: 'aaaaaaaa-1111' }) }); assert.equal((await j(await call('/stations'))).mine.length, 1, 'saving twice keeps one');
assert.deepEqual((await j(await call('/stations', { headers: { 'x-user': 'b' } }))).mine, [], "each person's list is their own");
await call('/stations', { method: 'POST', body: JSON.stringify({ id: 'journeyfm' }) }); assert.deepEqual((await j(await call('/stations'))).added, ['journeyfm'], 'the Journey is added like any other'); assert.deepEqual((await j(await call('/stations'))).mine.map(s => s.name), ['Good FM'], 'and is not mixed into the saved list'); assert.deepEqual((await j(await call('/stations', { headers: { 'x-user': 'b' } }))).added, []);
await call('/stations/journeyfm', { method: 'DELETE' }); assert.deepEqual((await j(await call('/stations'))).added, []);
await call('/stations/aaaaaaaa-1111', { method: 'DELETE' }); assert.deepEqual((await j(await call('/stations'))).mine, []);
r = await call('/icon?u=' + encodeURIComponent(`http://127.0.0.1:${port}/logo.png`)); assert.equal(r.headers.get('content-type'), 'image/png'); assert.equal((await call('/icon?u=' + encodeURIComponent(`http://127.0.0.1:${port}/stream`))).status, 404, 'only pictures come through as icons');
// private addresses