SOURCE / PINNED RELEASE
Made of little things.
Funclet
- Release
- 0b497cfc2f66…
- Author-recorded commit
- 1e531b24f490…
- License
- LICENSE
- Author’s source reference
- nostr://npub182jczunncwe0jn6frpqwq3e0qjws7yqqnc3auccqv9nte2dnd63scjm4rf/wss%3A%2F%2Fgit.napplet.soy%2F/n-6b7a37cac1d
Archive hash verified: 71d97a89ed8c40c2…. The source-to-build association is the author’s claim; it has not been independently rebuilt.
import assert from 'node:assert/strict';
import { readFile } from 'node:fs/promises';
import { runInNewContext } from 'node:vm';
import test from 'node:test';
import ts from 'typescript';
async function loadModule(name, dependencies = {}) {
const source = await readFile(new URL(`../src/${name}.ts`, import.meta.url), 'utf8');
const output = ts.transpileModule(source, { compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2022 } }).outputText;
const module = { exports: {} };
runInNewContext(output, { module, exports: module.exports, URL, Error, require: name => {
if (!(name in dependencies)) throw new Error(`Unexpected dependency ${name}`);
return dependencies[name];
} });
return module.exports;
}
const domain = await loadModule('domain');
const author = 'a'.repeat(64);
const address = `31237:${author}:radio:one`;
const stationEvent = { id: 'f'.repeat(64), kind: 31237, pubkey: author, created_at: 123,
tags: [['d','radio:one'],['name','Radio One'],['c','Jazz','genre']],
content: JSON.stringify({ description:'All day jazz', streams:[{url:'https://radio.example/live.mp3',format:'mp3',primary:true}] }) };
const normal = v => JSON.parse(JSON.stringify(v));
test('parses Wavefunc stations, stream tags and legacy JSON without trusting HTML', () => {
const station = domain.parseStation(stationEvent);
assert.equal(station.address, address);
assert.equal(station.description,'All day jazz');
assert.equal(domain.playableStreams(station).length,1);
const tagged = domain.parseStation({...stationEvent, tags:[...stationEvent.tags,['name','<img onerror=bad>'], ['stream','https://radio.example/ogg','audio/ogg','{"bitrate":192}','primary']]});
assert.equal(tagged.streams[0].bitrate,192);
assert.equal(tagged.streams.length,1);
const codecOnly=domain.parseStation({...stationEvent,tags:[...stationEvent.tags,['stream','https://radio.example/live','','{"codec":"audio/mpeg"}','primary']]});
assert.equal(domain.playableStreams(codecOnly).length,1);
assert.equal(domain.parseStation({...stationEvent,kind:1}),null);
assert.equal(domain.parseStation({...stationEvent,tags:[]}),null);
assert.equal(domain.parseStation({...stationEvent,content:'malformed'}),null);
});
test('rejects kind collisions instead of inventing untitled radio stations', () => {
// Public kind-31237 disco-vault records are albums, not Wavefunc stations.
const album={...stationEvent,tags:[['d','disco-vault:785'],['title','The Sounds of Machines Our Parents Used'],['type','music'],['country','UK']],content:''};
assert.equal(domain.parseStation(album),null);
assert.equal(domain.parseStation({...stationEvent,tags:[['d','unnamed']]}),null);
assert.equal(domain.parseStation({...stationEvent,content:'{}'}),null);
});
test('keeps successful search results when an unrelated relay rejects NIP-50', async () => {
const services=await loadModule('services', {'./domain.js':domain,'@napplet/sdk':{outbox:{query:async()=>({events:[{event:stationEvent}],incomplete:true,error:'OUTBOX_READ: Primal does not support search'})}}});
const response=await services.searchStations('jazz');
assert.equal(response.stations[0].name,'Radio One');
assert.equal(response.incomplete,true);
});
test('retains station candidates before applying the display limit to a mixed relay response', async () => {
const calls=[];
const albums=Array.from({length:48},(_,i)=>({...stationEvent,id:`album-${i}`,created_at:200+i,tags:[['d',`disco-vault:${i}`],['title',`Album ${i}`]],content:''}));
const services=await loadModule('services', {'./domain.js':domain,'@napplet/sdk':{outbox:{query:async(filters,options)=>{
calls.push({filters,options});
// The production host applies options.limit to the union of relay results.
return {events:[...albums,stationEvent].slice(0,options.limit??filters[0].limit).map(event=>({event})),incomplete:false};
}}}});
const response=await services.searchStations('');
assert.equal(response.stations.length,1);
assert.equal(response.stations[0].name,'Radio One');
assert.equal(calls[0].filters[0].limit,48);
assert.equal(calls[0].options.limit,200);
assert.deepEqual(normal(calls[0].options.relays),[domain.WAVEFUNC_RELAY]);
});
test('retains partial favourite lists and stations with retry status', async () => {
const list={...stationEvent,kind:30078,tags:[['d','favs'],['l',domain.FAVOURITES_LABEL],['a',address]],content:'{"name":"Favourites"}'};
const services=await loadModule('services', {'./domain.js':domain,'@napplet/sdk':{outbox:{query:async filters=>({events:[{event:filters[0].kinds[0]===30078?list:stationEvent}],error:'One relay is offline'})}}});
const lists=await services.readFavourites(author);
assert.equal(lists.lists.length,1);assert.equal(lists.incomplete,true);
const stations=await services.readFavouriteStations([address]);
assert.equal(stations.stations.length,1);assert.equal(stations.incomplete,true);
});
test('does not offer insecure streams, HLS, playlists or credential URLs for playback', () => {
const station = domain.parseStation(stationEvent);
for (const [url,format] of [['http://example.org/a.mp3','mp3'],['https://u:p@example.org/a.mp3','mp3'],['https://example.org/radio.m3u8','mp3'],['https://example.org/live','application/vnd.apple.mpegurl'],['javascript:alert(1)','mp3'],['https://example.org/live','flac'],['https://example.org/live','']]) {
assert.equal(domain.playableStreams({...station, streams:[{url,format}]}).length,0);
}
});
test('HTTP links remain external, preserve ports, reject credentials and prefer the primary stream', () => {
const station=domain.parseStation(stationEvent);
const streams=[
{url:'http://example.org:8000/secondary',format:'mp3'},
{url:'HTTP://example.org:8000/primary',format:'audio/mpeg',primary:true},
{url:'https://example.org/live.mp3',format:'mp3'},
{url:'http://u:p@example.org/stream',format:'mp3'},
{url:'javascript:alert(1)',format:'mp3'},
{url:'//example.org/stream',format:'mp3'},
];
assert.deepEqual(normal(domain.httpStreams({...station,streams})).map(s=>s.url),['http://example.org:8000/primary','http://example.org:8000/secondary']);
assert.deepEqual(normal(domain.playableStreams({...station,streams})).map(s=>s.url),['https://example.org/live.mp3']);
assert.equal(domain.wavefuncStationUrl(station),`https://wavefunc.live/station/${stationEvent.id}`);
assert.equal(domain.wavefuncStationUrl({...station,id:'../../other'}),'');
});
test('reads only Wavefunc favourites, deduplicates valid addresses and keeps colon identifiers', () => {
const event={...stationEvent, kind:30078, tags:[['d','favs'],['l',domain.FAVOURITES_LABEL],['a',address],['a',address],['a','31237:invalid:foo']],content:'{"name":"Evening radio"}'};
const list=domain.parseFavouriteList(event);
assert.equal(list.name,'Evening radio');
assert.deepEqual(normal(list.addresses),[address]);
assert.equal(domain.stationReference(address).identifier,'radio:one');
assert.equal(domain.parseFavouriteList({...event,tags:[['d','favs']]}),null);
});
test('selects replaceable events by latest timestamp and lowest ID on ties', () => {
const items=[{id:'z',createdAt:1,key:'one'},{id:'b',createdAt:2,key:'one'},{id:'a',createdAt:2,key:'one'}];
assert.equal(domain.latestBy(items,x=>x.key)[0].id,'a');
});
test('accepts both MCP response shapes and rejects server errors/malformed payloads', () => {
const value={result:{title:'On air',artist:'Artist',source:'ICY'}};
assert.deepEqual(normal(domain.toolPayload({structuredContent:value})),value.result);
assert.deepEqual(normal(domain.toolPayload({content:[{type:'text',text:JSON.stringify(value)}]})),value.result);
assert.throws(()=>domain.toolPayload({isError:true,content:[]}));
assert.throws(()=>domain.toolPayload({content:[{type:'text',text:'oops'}]}));
assert.throws(()=>domain.toolPayload({structuredContent:{error:'No source'}}));
assert.equal(domain.parseNowPlaying({title:'No metadata available',source:'UNKNOWN'}),null);
assert.equal(domain.parseNowPlaying({song:'Raw title',enriched:{title:'Guess',confidence:'low'}}).title,'Raw title');
});
test('resolves favourite addresses in exact author/identifier batches within host limits', async () => {
const calls=[];
const services=await loadModule('services', {'./domain.js':domain,'@napplet/sdk':{outbox:{query: async (filters,options)=>{calls.push({filters,options});return {events:[],incomplete:calls.length===2};}}}});
const addresses=Array.from({length:9},(_,i)=>`31237:${author}:station-${i}`);
const response=await services.readFavouriteStations(addresses);
assert.equal(calls.length,3);
assert.equal(calls[0].filters.length,4);
assert.equal(calls[2].filters[0]['#d'][0],'station-8');
assert.equal(response.incomplete,true);
});
test('a failed favourite batch does not discard stations from another batch', async () => {
let calls=0;
const services=await loadModule('services', {'./domain.js':domain,'@napplet/sdk':{outbox:{query:async()=>++calls===1?{events:[],error:'relay unavailable'}:{events:[{event:stationEvent}]}}}});
const response=await services.readFavouriteStations([...Array.from({length:4},(_,i)=>`31237:${author}:missing-${i}`),address]);
assert.equal(response.stations.length,1);assert.equal(response.incomplete,true);
});
test('completed missing favourite lookups never substitute a different station', async () => {
const services=await loadModule('services', {'./domain.js':domain,'@napplet/sdk':{outbox:{query:async()=>({events:[{event:stationEvent}],incomplete:false})}}});
const response=await services.readFavouriteStations([`31237:${author}:missing`]);
assert.equal(response.stations.length,0);assert.equal(response.incomplete,false);
});
test('partial and failed station queries are not silently converted to empty success', async () => {
const services=await loadModule('services', {'./domain.js':domain,'@napplet/sdk':{outbox:{query:async()=>({events:[],incomplete:true})}}});
assert.equal((await services.searchStations('jazz')).incomplete,true);
const failed=await loadModule('services', {'./domain.js':domain,'@napplet/sdk':{outbox:{query:async()=>({events:[],error:'offline'})}}});
await assert.rejects(failed.searchStations('jazz'),/offline/);
});
test('player uses canonical shell IDs, advertised controls and actual playback state', async () => {
const commands=[]; let state,capabilities; let closed=0;
const media={createSession:async()=>({sessionId:'canonical',owner:'shell'}),onState:(id,cb)=>{assert.equal(id,'canonical');state=cb;return {close(){closed++;}};},onCapabilities:(id,cb)=>{capabilities=cb;return {close(){closed++;}};},sendCommand:(...args)=>commands.push(args),destroySession:id=>commands.push(['destroy',id])};
const {RadioPlayer}=await loadModule('player',{'@napplet/sdk':{media}});
const player=new RadioPlayer(()=>{});
const station=domain.parseStation(stationEvent);
await player.tune(station,station.streams[0]);
assert.equal(commands.length,0);
state({status:'stopped',volume:1});
capabilities(['play','pause','volume']);
assert.deepEqual(commands,[['canonical','volume',.75],['canonical','play']],'initial host state must not discard the requested volume');
assert.equal(player.status,'stopped');
state({status:'playing',volume:.5});
assert.equal(player.status,'playing');
player.command('seek',50);
assert.equal(commands.length,2);
player.command('pause');
assert.deepEqual(commands.at(-1),['canonical','pause',undefined]);
player.destroy();
assert.equal(closed,2);
});
test('late session creation is destroyed after a station switch or teardown', async () => {
const pending=[]; const destroyed=[];
const media={createSession:()=>new Promise(resolve=>pending.push(resolve)),destroySession:id=>destroyed.push(id),onState:()=>({close(){}}),onCapabilities:()=>({close(){}})};
const {RadioPlayer}=await loadModule('player',{'@napplet/sdk':{media}});
const player=new RadioPlayer(()=>{}); const station=domain.parseStation(stationEvent);
const first=player.tune(station,station.streams[0]);
const second=player.tune({...station,name:'Second'},station.streams[0]);
pending[1]({sessionId:'new',owner:'shell'}); await second;
pending[0]({sessionId:'old',owner:'shell'}); await first;
assert.equal(player.sessionId,'new'); assert.deepEqual(destroyed,['old']);
player.destroy(); assert.deepEqual(destroyed,['old','new']);
});
test('volume storage validates restores, ignores late reads, and keeps writes ordered', async () => {
const { VolumePreference } = await loadModule('volume-preference');
for (const [raw, expected] of [[null,null],['',null],['broken',null],['2',null],['-1',null],['0',0],['0.35',.35],['1',1]]) {
assert.equal(await new VolumePreference({getItem:async()=>raw}).load(),expected);
}
assert.equal(await new VolumePreference(undefined).load(),null);
const unavailable=new VolumePreference({getItem:async()=>{throw Error('denied');},setItem:async()=>{throw Error('denied');}});
assert.equal(await unavailable.load(),null);unavailable.save(.4);
let finishRead, finishWrite;
const writes=[];
const preference=new VolumePreference({getItem:()=>new Promise(resolve=>{finishRead=resolve;}),setItem:async(key,value)=>{
writes.push([key,value]);if(writes.length===1)await new Promise(resolve=>{finishWrite=resolve;});
}});
const loading=preference.load();preference.save(.4);preference.save(.5);preference.save(.6);
finishRead('.9');assert.equal(await loading,null,'a late read cannot undo user input');
assert.deepEqual(normal(writes),[['receiver-volume','0.4']]);
finishWrite();await new Promise(resolve=>setImmediate(resolve));
assert.deepEqual(normal(writes),[['receiver-volume','0.4'],['receiver-volume','0.6']]);
});
test('saved station addresses are validated before an OUTBOX lookup', async () => {
const { StationPreference }=await loadModule('volume-preference');
for(const raw of ['broken','https://example.org','31237:bad:id',`31237:${author}:`]) {
assert.equal(await new StationPreference({getItem:async()=>raw}).load(),null);
}
assert.equal(await new StationPreference({getItem:async()=>address}).load(),address);
});
