- Rust 72.7%
- TypeScript 24.6%
- HTML 1.5%
- CSS 0.8%
- JavaScript 0.4%
Builds Phase 3 (voice calls) and Phase 4 (peer-to-peer file transfer) on the initial codebase, plus a native multi-tab shell, a Settings/Help surface, and the relay-side work all of it needs. Voice calls (Phase 3): a relay-promoted call-leader mirrors the room leader, with call membership riding a `call` flag on the room roster (no separate call-roster kind). Adds the call-* wire kinds, the sans-IO protocol layer (engine/src/call.rs `plan_call_mode` + engine.rs), and the browser P2P audio-mesh transport (ui/src/call.rs, call_bar.rs): DTLS, forward-secret, DM-encrypted SDP/ICE, per-peer WebAudio volume, Opus-only <=16kbps. Leader-validated admission (S2) gates joiners on a WebRTC probe. SFU (Cloudflare Realtime) is wired through the protocol but stubbed. /dcc file transfer (Phase 4): IRC-style P2P send over a WebRTC DataChannel, streamed and inline in the chat log. Adds the dcc-* wire kinds, the pure engine layer (engine/src/dcc.rs — mime whitelist, magic-byte sniff, batch planning), and the transport + inline render (ui/src/dcc.rs, dcc_panel.rs). Offer/accept/reject bodies and all signaling are 1-1 DM-encrypted (relay-blind: only routing metadata is plaintext). Media within the size limit auto-downloads; everything else prompts. Send to a user or multicast to the room via one fanned-out dcc-offers map. Native tabs: up to 9 numbered room tabs plus a pinned menu tab, each running its own irc-engine::Engine in the worker, multiplexed by a tab_id envelope (ui/src/tabmsg.rs) at the host boundary only — the engine stays tab-agnostic. Open tabs persist and reconnect on refresh. Settings + Help: replaces key_manager.rs with components/settings.rs (General/Calls/Keys subtabs — connection pref, DCC auto-download, message font size, contacts, key import/export) and adds components/help.rs (Overview/Usage/FAQ for non-technical readers). meta join ack: every join now gets a per-client `meta` frame carrying room facts (KV-authoritative public, colo, count, sfu capability) that drive the public/private tab glyph, plus per-client ray/eyeball debug facts. Relay: liveness sweep is now write-failure-only reaping (an unanswered-but-writable probe is never reaped — the previous guess flapped healthy clients); the public-room directory is KV-authoritative with room colo and operator "stapled" (pinned) entries; call leadership/keying and failure-aware DCC routing are added. Updates Specification.md, the docs/ set, and CLAUDE.md; extends the worker test suites. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|---|---|---|
| docs | ||
| engine | ||
| ui | ||
| web | ||
| worker | ||
| .gitignore | ||
| Cargo.lock | ||
| Cargo.toml | ||
| CLAUDE.md | ||
| README.md | ||
| Specification.md | ||
| wrangler.toml | ||
++IRC
End-to-end encrypted group chat in the style of IRC.
Clients are Rust/Dioxus apps — web (PWA), desktop, and mobile — sharing one UI crate and one sans-IO protocol engine. Rooms run as Cloudflare Durable Objects. All encryption is client-side using pure-Rust RustCrypto crates (ED25519 identity keys, ephemeral X25519 ECDHE session keys for forward secrecy, ChaCha20-Poly1305, BLAKE3).
Repository layout
++IRC/
├── engine/ irc-engine — sans-IO protocol core: crypto, keying/leadership,
│ connection supervisor, UI⇄engine protocol
├── ui/ irc-ui — ALL shared Dioxus components, display state,
│ platform façade, theming assets (tailwind.css,
│ Twemoji) + the npm tooling that generates them
├── web/ irc-web — browser app (PWA; engine in a Web Worker)
├── desktop/ irc-desktop — macOS/Windows/Linux app (engine on a tokio thread)
├── mobile/ irc-mobile — Android + iOS app (same native engine host)
├── worker/ Cloudflare Worker + Durable Object Relay (TypeScript)
├── wrangler.toml Cloudflare deployment configuration (root-level)
└── Cargo.toml Rust workspace
A single Cloudflare Worker serves both halves of the web app in production:
the static WASM frontend (via Cloudflare Static Assets) and the realtime backend
(Durable Object rooms + the KV public-room directory). One deploy, one origin.
The desktop and mobile apps talk to the same backend (set IRCPP_RELAY).
Prerequisites
| Tool | Purpose | Install |
|---|---|---|
| Rust stable | Client compilation | rustup update stable |
wasm32-unknown-unknown target |
Web build | rustup target add wasm32-unknown-unknown |
dx (Dioxus CLI) 0.7+ |
Dev server & builds, all platforms | cargo install dioxus-cli (if another tool's dx shadows it, call ~/.cargo/bin/dx) |
| Node.js 18+ | Worker tooling + CSS/emoji generation | nodejs.org |
| wrangler 3.78+ | Cloudflare deploy / local dev | npm install in worker/ |
Mobile additionally needs the Android SDK/NDK or Xcode — see
mobile/README.md.
Local development (web)
Two processes run side-by-side: the Worker on :8787 and the Dioxus dev server
on :8080. They are different origins in dev, so the client hard-codes
ws://localhost:8787 / http://localhost:8787 for local connections (see
ui/src/platform/web.rs) and the Worker sends
Access-Control-Allow-Origin: * on /rooms. In production everything is one
origin, so neither special case applies.
1 — Start the backend
cd worker
npm install # first time only
npx wrangler dev # Worker + DO emulation + KV on :8787
2 — Start the web app
cd web
dx serve # hot-reload dev server on :8080
Open http://localhost:8080. To test room sharing, open a second tab at
http://localhost:8080/#my-room.
Desktop / mobile dev
cd desktop && dx serve --platform desktop # IRCPP_RELAY to pick a relay
cd mobile && dx serve --platform android # or --platform ios
Details and platform prerequisites: desktop/README.md,
mobile/README.md.
Production deployment (one Worker serves everything)
In production the Worker serves the WASM frontend and the WebSocket rooms
from the same origin. Because of that, no frontend URL needs to change: the
client derives its endpoints from window.location, so when the page is served
from https://chat.example.com, room sockets go to wss://chat.example.com/<room>
and the directory to https://chat.example.com/rooms automatically. The
localhost:8787 hard-coding only triggers when the page host is
localhost/127.0.0.1.
Quick version:
# 1. Build the web bundle (output: target/dx/irc-web/release/web/public).
# No npm step needed: the generated assets (tailwind.css, emojis.json,
# twemoji/) are committed in ui/assets. (Only run `npm run build:css` /
# `npm run build:emoji` — both from ui/ — if you changed styles or the
# emoji set; see ui/README.md.)
cd web
dx build --release
cd ..
# 2. One-time: authenticate + create the KV namespace, paste ids into wrangler.toml
cd worker && npx wrangler login
npx wrangler kv namespace create ROOMS # paste id → wrangler.toml
npx wrangler kv namespace create ROOMS --preview # paste preview_id → wrangler.toml
cd ..
# 3. Deploy from the repo root (where wrangler.toml lives)
npx wrangler deploy
The full, annotated walkthrough — including the asset/Worker routing model and
the config pitfalls to avoid — is in worker/README.md.
Web build details are in web/README.md; shared UI and
theming in ui/README.md.
Desktop and mobile releases are built with
dx build --release --platform desktop|android|ios from their crate
directories (dx bundle produces installers with the ++IRC icon); point them
at your deployed relay with IRCPP_RELAY=wss://chat.example.com.
Production caveats (please read)
- KV ids are placeholders.
wrangler.tomlships withREPLACE_WITH_YOUR_KV_NAMESPACE_ID. Localwrangler devemulates KV so the public-room directory works locally, but you must paste real ids beforewrangler deployor public rooms silently won't register. - Self-hosted assets, no CDNs. Styling (Tailwind, compiled to
ui/assets/tailwind.css), icons (inline SVGs), and emoji (Twemoji SVGs inui/assets/twemoji/+ theassets/emojis.jsonshortcode table) are fully self-hosted, so no third-party request leaks a visitor's IP/referrer. Every platform app bundles these same committed assets — one brand everywhere. Regenerate only when their inputs change —cd ui && npm run build:cssafter editing utility classes (a CI/production build should run it beforedx build --release),npm run build:emojionly when changing the emoji set or upgrading Twemoji.
Running tests
# All native unit tests: the engine's protocol/crypto/supervisor/leadership
# suite + the shared UI's display logic (no WASM toolchain needed)
cargo test --workspace # 128 tests (76 engine + 52 ui)
# Worker: TypeScript type-check + test suites (mock harness, pure logic,
# and a real-workerd Durable Object integration suite via vitest-pool-workers)
cd worker
npx tsc --noEmit
npx vitest run # 100 tests