Life's hectic. Let's get funding for the big one: https://github.com/stylehouse which is a direct attack on the display problem of the computer. Lately I think we should use cytoscape in a big way! We can make stuff float out of stuff on tendrils. This is the reality of things. Anyway...
A wee side project now: https://github.com/stylehouse/jamsend which integrates a bunch of tech I need for stylehouse to be an internet phenomenon, but also might get impressive (for funding) fast enough to nice up my life, because I need it.
This webapp will, soon, use the Directory API to turn your local music pile (and that of your peers) into your own private Napster!
Or (completely different UI) a radio station (or spectrums of), which you can share with your old people. a bit of a DJ system maybe.
It's hard to tell how complex to go. Layers are:
- p2p via off-the-shelf abstractions on WebRTC
- pubkeys and trust enabling certain other features
- Any Other Features (eg directory sharing, music streaming)
Lets begin:
Beginning
Lets use the Web Crypto API to verify the stream distributing through a p2p network.
But then, use ed25519 instead, because:
- available Web Crypto seems to be End-Of-Life-ing (some say) at the end of this decade (5 years away)
- the pubkeys are much shorter... Of course, you don't need to give out the whole pubkey in a URL, just enough to prevent hash collisions (pubkeys are derived unpredictably), so that someone in the room can claim to own the room, etc.
Explains a lot
A static page
may have an identity to find in the URL
And|or you begin a place!
And...
Then I start to gather peers, but using Trystero (modern-but-perhaps-2016-looking WebRTC p2p party plumbing) with the bittorrent peer finding strategy seems flaky and slow, so it's mqtt during development -> needs crypto.subtle... -> needs TLS -> my public Caddy server (d) needs a new... port? to put this other app over?
Hoping to make a Minimum Viable Product this week, I swap the idea of peer-sourced media for an app server with "demo music" on it via websocket. We can gradually swap in peer-sourcing and p2p-transit some time later.
Then, the vite dev server's other websocket connection to the client, which it uses to notify of code changes (hot module reload, HMR), is closing nearly a second after the page loads, which spurs a location.reload! So it's tailspinning.
And stays that way, until I swap bun for npm, then turns into a "idealTree already exists" error, while doing npm i.
What the hell does that mean? This is bad, in retrospect it could have complained about a lack of package.json before pretending to be all good, and I doubt idealTree already exists...
Well apparently, it'll be working in (aka WORKDIR) /, in the container's short-lived overlay filesystem (per run), whereas we want it working in /app, where we've got this mount... Anyway, the readme should say:
setup
First time,
docker compose build
docker run -v .:/app jamsend-jamsend-dev bash -c "cd /app && npm i"
Thence,
docker compose up
And this took all goddamn day!
Then!
I don't know how it's going to go decoding these blobs that are just chopped pieces of music files.
They (decoders) tend to skip forward until some kind of frame, but how would it even know the format of the stream, which might vary...
Tomorrow's trickiness!
More here soon.
And there was more...
But then the editor lost my write up! How can I be proud of my work on such a machine?
And then loss happened again! I had described getting to...
Prod
It isn't obvious how to include the socket.io part of the server with the svelte|vite build. It doesn't just happen for prod. In dev, with vite, the webSocketServer was given in vite.config.ts.
It turns out you have to bundle an extra webserver around it as a separate build (the build:server action in package.json), that becomes the top-level and includes the other|inner build|webserver as such see: https://github.com/stylehouse/jamsend/commit/40f5d856b214d278b9fa3305a6666329d47be11c
Then the prod-ing process itself is just a pull and a couple of configs changes: https://github.com/stylehouse/jamsend/commit/df7a361e0dff33808b8aed6a41fa554923910fe1
There was this last minute bug I got around to:
ffloudness timeout, SIGINT, still get some output
this helps a lot with diversity in the first 30 seconds
or lots of aud wait for ffloudness to etc etc
https://github.com/stylehouse/jamsend/commit/b2da348062e8e967b5b57ce1ea76e0cb5c61af5a
Anyway, the same way we have our dev instance on https and visible to the world, via Sydney, for $6.9 per month:
We now have website like:
Which is totally going to get me in trouble for giving out music. Those guys on the drums. That music might be owned by a hedge fund now, and have people actively scrubbing their property off other people's property. It could get messy.
Peering
Something I've already got! I'm torn between putting it straight in here, and fixing jamola a bit more...
I think I'll just go for it. Back soon.
Back. Things are just barely p2ping now:
Is this playing out:
I went for PeerJS rather than something Claude talked me into building myself.
It was one of the big fuckups of last year, not drilling all the way through to PeerJS. I have no idea why I failed to use it for jamola, it wasn't that far away... I also tried Trystero again but it seems to be loads of trouble with modern bundlers. There was a big wave of javascript culture around 2014, and now in 2025 some significant-looking projects could be integration hell. Projects talking about script tags instead of imports is a sign of age.
Sysadminy
Various details now about how to run your own peerjs-server behind Caddy, because I got some arguments wrong somewhere and mistook the whole situation for getting rate-limited by 0.peerjs.com ...:
leproxy Caddy is now configured to:
that's a Caddy config in a docker compose being made by a perl script.
It takes some of our traffic over here:
which is in the jamsend docker compose, incidentally. So leproxy knows about peerjs-servers, but only if they've got those kinds of names...
from here:
Etc!
Now, diring... Got so slapped by work today. Sheesh. Back in a mo.Peerily
Turns out to need a binary-safe emit(), which PeerJS provides, but also we want to sign everything:
So we've made every emit() into 2-3 send()s. The signatures, the signed data, and perhaps signed binary data.
So now we have a connection-maker that we can't seem to kick over - it retries!
Peerily.svelte.ts is now useful and elegant!
Going forward to Wednesday, I expect:
Remembering
But first! Javascript that doesn't complain about rogue variables now?
This on_dist was red-underlined, by typescript in vscode, and simply evaluates to undefined.
Bizarre. The art of catering to the moment when something's gotta blow up in someone's face a little bit about something...
Anyway. We needed to test remembering, because it:
- wasn't working too good...
- iterating on it involves testing labour: refreshes, UI interaction, and logging objects into DevTools to Expand Recursively
On the side of our project, I added a src/routes/repro-reactive-stashed-hierarchy/+page.svelte route, which includes the test component src/lib/p2p/ui/repro-reactive-stashed-hierarchy/Top.svelte, and the test case starts like this:
That begins to:
Then, a bit of a CRUD for the eer or pier, they're pretty similar:
Lots to unpack.
The eer.stashed = {id} is when a proxy object is created, and that is what we then put into stashed.
It must be done on a separate line!
The proxy object goes into the enclosing stashed state, which should get it into this bit of Top.svelte:
So the stash is entirely not-too-much as a graph (since svelte's proxy objects seem to go fine into JSON.stringify), whereas its live-object counterpart addresses is full of objects that might link to way too much stuff. It's easy to log proxy objects into DevTools that will infinitely Expand Recursively. The need for attention is everywhere!
{#each P?.addresses as [pub,eer] (pub)}<NotPeering {pub} {eer} />{/each}
Which is basic.
The tricky bit was getting changes from this function to this effect:
Just if (pier.stashed) { isn't enough - you have to look for individual things that changed. Or it's just the same stuff in a new container! How basic.
Finding the above tricks took a week off my life. It works:
And it works fine for deep objects, I guess:
These experiments are on this branch: https://github.com/stylehouse/jamsend/tree/repro-reactive-stashed-hierarchy
Now to take these learnings back to the app itself!
Sharing
Is coming...