chat / README.md
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
# sueta

Multi-user **p2p, end-to-end encrypted** chat PWA at **https://chat.ardegazu.ro** —
no origin server, no accounts, no server-side storage.

Public mirror (served from IPFS via git's dumb-HTTP protocol — no git server):

```sh
git clone https://git.ardegazu.ro/chat.git
```

MIT licensed — fork it and build your own p2p apps on the stack
(start at [client/src/sueta/lib/README.md](client/src/sueta/lib/README.md)).

```
you ⇄ friends     libp2p: direct browser⇄browser WebRTC (noise + app-layer AES-GCM),
                  bootstrapped through a circuit relay that carries encrypted
                  handshakes — room traffic bypasses it once pairs upgrade to
                  direct WebRTC (until then it transits as opaque noise frames)
room secret       lives only in the URL fragment (#…) — never sent anywhere
the room          an OrbitDB append-only log, encrypted end-to-end, replicated
                  member⇄member; history lives in members' devices, images ride
                  bitswap as encrypted blocks — no server ever stores a byte
calls             media-only RTCPeerConnections (DTLS-SRTP), SDP sealed over
                  libp2p streams; a live call survives losing the relay
NAT traversal     stun/turn with ephemeral per-app credentials
                  (relay + TURN served by ird's managed p2p service — or
                  self-host: deploy/relay + coturn, see docs/RELAY.md)
hosting           IPFS — the build is pinned and named by IPNS; the domain is
                  an IPFS gateway domain (DNSLink), no origin server at all
```

Features: room lobby (your rooms live only on your device; the link IS the
room), threads, emoji reactions, image sharing (attach / paste / drop —
client-side re-encode strips EXIF+GPS and converts iPhone HEIC), persistent
Ed25519 identities with emoji fingerprints signing every message in the log,
TOFU + key-change warnings, and a password-manager-friendly identity key (save
it once, keep the same identity on any device), installable PWA, mobile-first
dark UI. History is member-held and replicated: reopening a room offline
replays your device's copy, joining replays everyone else's, and reactions —
including removals — converge on every device.

## v13 — hardening for reuse

(The running per-version log for forks — v9 through today — lives in
[docs/CHANGELOG.md](docs/CHANGELOG.md).)

- **Authorship binding closed**: OrbitDB's stock IPFS access controller never
  checked that an entry's signing key IS the referenced identity's key, so any
  member could publish entries attributed to another member. v13 wraps the
  controller (today `ardegazu.rooms.lib.access` in
  [rooms-kit](https://git.ardegazu.ro/rooms-kit/), compiled off chat's
  classpath; address-unchanged — old and new
  clients share the same DBs) and rejects such entries; pre-v13 clients render
  the forged badge, so upgrade.
- **Derived storage namespace**: all localStorage/IndexedDB names now derive
  from the app salt (`VITE_STORAGE_NS` to override) — two apps built on this
  template can share an origin. Existing installs migrate automatically once.
- **Per-device authorship without an identity**: identity-less users' entries
  chain to a stable per-device key — their reactions toggle correctly (no
  fingerprint badge, as before).
- Plus: getUserMedia re-entrancy guards (no orphaned live mic), replication
  entries no longer racing the projector, image fetch timeouts, relay per-IP
  connection cap, TURN hairpin guidance, exact-pinned fragile libp2p deps.

## Layout

| Path | What |
|---|---|
| `docs/PROTOCOL.md` | crypto + protocol spec, v2 (the contract) |
| `docs/CHANGELOG.md` | per-version log from v9, fork-oriented: what changed, what you must do to follow |
| `docs/RELAY.md` | the relay: what it is, dev usage, self-hosting |
| `client/` | shadow-cljs ClojureScript PWA (`shadow-cljs.edn` + `deps.edn`, built by `node scripts/build.mjs`); sources under `src/sueta/`; the reusable p2p core is `ardegazu-rooms-kit`, sha-pinned and compiled off the classpath |
| `deploy/relay/` | ~100-line libp2p relay (websocket + circuit-relay-v2 + gossipsub discovery) — dev server AND self-host example |
| `deploy/` | turnserver.conf (self-host TURN), repo-mirror publisher |

## Dev

```sh
# terminal 1 — dev relay on :9090 (deterministic PeerId, baked into the client's dev default)
cd deploy/relay && npm install && npm run dev

# terminal 2 — app on :5173
cd client && npm install && npm run dev

# tests (each spawns its own relay — stop the terminal-1 relay first)
cd client && node e2e/mesh.e2e.mjs      # 16 scenarios: mesh, log replication, blind relay, outage
cd client && node e2e/call.e2e.mjs      # audio/video calls (fake media devices)
cd client && node e2e/migrate.e2e.mjs   # v1 → v2 history migration
cd client && node e2e/relay.e2e.mjs     # TURN-forced chat + call against production infra
```

## Deploy

```sh
cd client && npm run release   # bumps version.json + builds
# then: ird ipfs add client/dist → ird ipfs ipns publish chat.ardegazu.ro <cid>
```

The relay + TURN are a managed [ird p2p app](https://ird.ro) — the libp2p
relay multiaddr and TURN endpoint are advertised in the host's
`/.well-known/ap2p` descriptor, which the client re-reads at boot (so the
operator can rotate the relay key without a client release). Self-hosting:
run `deploy/relay/` behind any TLS proxy + coturn, and point the build at it
with `VITE_RELAY_MULTIADDR` / `VITE_TURN_CREDS_URL` (see `docs/RELAY.md`).

Every release also republishes the anonymous open-source mirror:
commit (repo-local git identity is `sueta <sueta@noreply.local>`) →
`deploy/publish-repo.sh` (hard-fails unless the mirror has a single anonymous
author, zero identity bytes in any object, and no nested site build) →
`ardegazu-git/assemble.sh chat` (pins the ecosystem source root and re-points
the `git.ardegazu.ro` IPNS name at it).

Versioning: integer builds from `client/version.json`, bumped by
`npm run release`. Running apps poll for updates (5 min + on focus) and show a
one-tap "version N is ready" banner. Protocol v2 (the libp2p/OrbitDB cutover)
is a one-way door: v1 and v2 clients never see each other — room links keep
working, people just need the update. The last v1 state is tagged
`v1-ws-final`.

Rooms are a full mesh, comfortable up to ~12 peers. Bigger would need an SFU,
which would no longer be this project.

static mirror of HEAD · about · clone: git clone https://git.ardegazu.ro/chat.git