This repository has no description
TypeScript 84%
Nix 10%
JavaScript 5%
1%
HTML <1%
CSS <1%
Shell <1%

readme.org
#+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.