Radio4000 on AT Protocol. Save your favorite music links (YouTube, SoundCloud, Vimeo, files) as custom records and play them in the app. (Shared timeline will return later.) The production instance lives at https://4000.radio.
Key features
- AT Protocol OAuth login (no passwords) with robust fallback.
- Custom atproto collection
com.radio4000.trackfor tracks. - Player for direct files; embedded players for YouTube/Vimeo/SoundCloud; auto-next.
- Routes inspired by Radio4000:
/home hub,/#/@handleauthor/my tracks, search, track view/edit. - Permissions re-consent flow (when server supports fine-grained scopes).
Quick start
npm install
npm run dev
# open http://127.0.0.1:5173Build & preview
npm run build
npm run previewTesting
npm testRouting
/— Home hub (timeline temporarily offline; quick actions)/#/add— Add a track/#/@alice.bsky.social— Author/My tracks/#/search— Search actors/#/t/:repo/:rkey— Track view/#/@:handle/:rkey/edit— Track edit (modal route)/#/settings— Manage permissions (re-consent)
Architecture (modular)
- Svelte 5 components (no styles in templates):
- Router:
src/svelte/Router.svelte, routes:src/svelte/routes.js, matcher:src/svelte/routing/match.js - Player:
src/svelte/components/Player.svelte, store:src/svelte/player/store.js
- Router:
- Pages:
src/svelte/pages/…(Author, Search, TrackView/Edit, Settings) - Services:
- OAuth/session:
src/libs/bsky-oauth.js - R4 data and social:
src/libs/r4-service.js - URL parsing/embeds:
src/libs/url-patterns.js - oEmbed/Discogs helpers:
src/libs/oembed.js,src/libs/discogs.js
- OAuth/session:
OAuth setup (prod vs dev)
- Dev (HTTP loopback): handled automatically using loopback client id. Note: AT Protocol OAuth requires HTTPS, so HTTP localhost may not work for actual login.
- Dev (HTTPS with Tailscale Funnel): Expose your local dev server publicly via Tailscale funnel:
- Start your dev server:
npm run dev - Run
tailscale funnel --bg 443:https+insecure://localhost:5174(adjust port if needed) - Update
public/client-metadata.jsonwith your Tailscale domain (e.g.,https://yourname.tailnet.ts.net) - Access your app via the Tailscale funnel URL and OAuth will work
- Start your dev server:
- Prod (HTTPS): expose
public/client-metadata.jsonat a stable URL.- If deploying under a subpath (e.g., GitHub Pages), include both with and without trailing slash in
redirect_uris. - The app passes a canonical
redirect_uri(with trailing slash) for both authorization and callback handling to avoid mismatches. - Some servers don't support
authorization_detailsyet; the app falls back to default atproto scope.
- If deploying under a subpath (e.g., GitHub Pages), include both with and without trailing slash in
- Optional (force HTTPS client metadata in dev): set
VITE_CLIENT_IDin your env to the full URL of a hostedclient-metadata.jsonto request fine-grained scopes even when running locally.
Custom record (com.radio4000.track)
- Fields:
url(string),title(string), optionaldescription, optionaldiscogsUrl,createdAt(ISO string). - Create/List/Update/Delete implemented in
src/libs/r4-service.jsvia atproto repo APIs.
Permissions & scopes
- Custom records only:
com.radio4000.track(create, update, delete). - Social (optional):
app.bsky.graph.follow(create, delete) for follow/unfollow. - No feed posting is attempted or requested.
- If reads or writes fail due to missing scope, you’ll see a clear message and an “Open Settings” button.
Troubleshooting
- Login redirect mismatch on GitHub Pages (subpath deploys): ensure your
public/client-metadata.jsonincludes both the base path with and without trailing slash. - "Invalid hostname" error during OAuth: if using Tailscale, make sure you're using
tailscale funnel(public) nottailscale serve(private). The OAuth server needs public access to fetch your client metadata. - OAuth callback not completing login: check browser console for errors. Ensure your
client-metadata.jsonredirect_uris match exactly (with and without trailing slash). - DPoP nonce 401 seen in devtools: we avoid eager profile fetch on hydration; it's harmless if seen intermittently.
License MIT