#+TITLE: skypod #+AUTHOR: Jonathan Raphaelson #+EMAIL: jon@accidental.cc an offline-first RSS & podcast PWA application * what is skypod? - rss/podcast subscription management - offline first; cache audio locally, and p2p sync between devices - podcast playback with nice features; skip silence, volume correction, etc. - fun to hack on * how does it work? - PWA stores and displays cached feeds, subscription, and listening history data locally in IndexedDB - WebSocket signaling server for WebRTC peering, devices sync cached data and play state using a hybrid logical clock for causal ordering - feed proxy server provides a nice API for fetching feeds and extracting metadata * getting started with devenv this project uses [[https://devenv.sh][devenv.sh]] for reproducible development environments. if you have devenv installed: #+BEGIN_SRC bash $ devenv shell $ npm install $ npm run dev #+END_SRC if you don't have devenv, you can install it from [[https://devenv.sh][devenv.sh]] or just use node v24+ directly. * development run the pwa+server with: #+BEGIN_SRC bash $ npm install $ npm run dev # lots of stuff concurrently with wireit #+END_SRC this starts: - vite dev server at ~http://127.0.0.1:4000~ (frontend) - backend server at ~http://127.0.0.1:4001~ (WebSocket + API) - live type-checking and linting in watch mode ** technology stack *** common - typescript with strict mode - [[https://github.com/panva/jose][~jose~]] for cross-platform webcrypto and JWT management - [[https://zod.dev/][Zod]] describes schema and builds transformation pipelines *** backend - [[https://expressjs.com/][Express]] for HTTP and WebSocket servers - [[https://github.com/Level/level][Level]] for persistent realm kv storage *** frontend - [[https://vite.dev/][Vite]] for builds - [[https://preactjs.com/][Preact]] for UI - [[https://dexie.org/][Dexie]] for IndexedDB storage - [[https://github.com/feross/simple-peer][simple-peer]] for WebRTC *** build & dx - typescript for type-checking - [[https://github.com/google/wireit][Wireit]] does script dependencies and services - [[https://eslint.org][ESLint]] and prettier keep the code nice ** scripts - ~npm run dev~ :: alias for ~npm run start:dev~ - ~npm run lint~ :: runs ~eslint~ - ~npm run types~ :: runs ~tsc~ (no emitting, just typechecking) - ~npm run build~ :: build production frontend - ~npm run test~ :: runs ~jest~ as a one-off - ~npm run start:tests~ :: runs ~jest~ in watch mode - ~npm run start:dev~ :: runs BE/FE with live type-checking and linting - ~npm run start:prod~ :: builds and runs everything in production mode ** running tests There's not much here yet, I want to figure it out first. #+BEGIN_SRC bash $ npm run test # run all tests once $ npm run start:tests # run tests in watch mode #+END_SRC to run a single test file: #+BEGIN_SRC bash $ npx jest src/path/to/file.spec.ts #+END_SRC ** git hooks pre-commit hook runs type-checking and linting automatically. enable with: #+BEGIN_SRC bash $ git config core.hooksPath .githooks #+END_SRC * architecture the codebase is organized into modules with path aliases: - ~#client/*~ (~src/client/~) - preact frontend application - ~#server/*~ (~src/server/~) - node.js express backend - ~#common/*~ (~src/common/~) - shared code (protocol, crypto, utilities) - ~#realm/*~ (~src/realm/~) - p2p connection and sync protocol - ~#skypod/*~ (~src/skypod/~) - domain-specific schemas and actions ** key architectural components *** p2p synchronization uses a hybrid logical clock (HLC) for causal ordering of events across distributed peers. clients PULL complete action history when catching up, and PUSH tailored updates to each peer based on knowledge vectors. *** offline-first all user data lives in IndexedDB via dexie. the server is stateless regarding user data (only maintains ephemeral realm/peer state). *** webrtc for p2p peers communicate directly via WebRTC data channels. the WebSocket connection to the server is only used for signaling and as a fallback broadcast mechanism. *** realm system a realm is a collection of verified identities that can communicate securely. realms are not publicly routable; access requires the realm id and an invitation from an existing member. see [[./docs/readme-brainstorm.org]] for detailed architecture thoughts and connection flow diagrams. * contributing - run ~git config core.hooksPath .githooks~ to install pre-commit hooks for lint/fmt - honestly, not really open to contributions right now, come back later. * license & copyright copyright (C) 2025 jonathan raphaelson this program is free software: you can redistribute it and/or modify it under the terms of the **affero general public license version 3 or later** (AGPLv3+). see [[./readme-license.txt]] for a copy of the full license.