A minimal, private, anonymous chat. No accounts, no names, no history — you press one button and you are talking to a random stranger, your Interlocutor.
Built with SvelteKit and TypeScript. The server is a single Node process that serves the page and the WebSocket on the same port, so it can be published as a Tor onion service with nothing else in the path.
- How many people are online — pushed by the server every 15 seconds, and the time of the last update is shown next to the count.
- One button to start searching — with a stopwatch so you know how long you have been waiting, and a button to stop.
- Up to 1000 characters per message. Going over is not silent: the message is not sent, your text stays in the box, and you are told why.
- Nothing is stored. No database, no message history, no logs, no cookies. The only thing the server learns is that a connection exists.
pnpm install
pnpm dev # http://localhost:5173 — the real chat server runs in dev tooOpen the app in two windows to see a match happen: press Find someone to chat with in one of them, then in the other.
pnpm build # builds the app and compiles the server to dist-server/
pnpm start # node dist-server/server/index.js
pnpm check # svelte-check + tsc, no errors allowed
pnpm test # builds, then runs the server integration tests
pnpm lint # prettier --checkAll optional; the defaults are meant for a private onion service.
| Variable | Default | Meaning |
|---|---|---|
HOST |
127.0.0.1 |
Bind address. Keep it on loopback behind nginx or Tor. |
PORT |
3000 |
Bind port. |
ORIGIN |
— | Public origin, e.g. http://xyz.onion. Set it in production. |
ALLOWED_ORIGINS |
same host | Comma separated list of origins allowed to open a socket. |
MAX_CLIENTS |
1000 |
Concurrent visitors before new connections are refused. |
Deep Chat is a plain HTTP/WS service, so an onion service in front of it is all
you need. Full walkthrough in deploy/README.md; the short
version:
pnpm build
sudo useradd --system --home /opt/deep-chat deep-chat
sudo -u deep-chat pnpm install --prod --frozen-lockfile
sudo cp -r build dist-server src/app.css /opt/deep-chat/ # see deploy/README.md
sudo cp deploy/deep-chat.service /etc/systemd/system/
sudo systemctl enable --now deep-chatThen publish the port as an onion service (deploy/torrc.append) and terminate
TLS with the self-signed certificate Tor expects
(deploy/nginx-onion.conf).
Use Tor Browser to reach it. It is the only client that guarantees the connection never leaves the Tor network, and it treats onion addresses as HTTPS-only by default.
src/lib/protocol.ts the wire protocol, shared by client and server
src/lib/chat/client.svelte.ts the browser state machine (runes)
src/lib/components/ the UI: presence, stopwatch, finder, room, composer
server/hub.ts presence, matchmaking, rate limits, ephemeral rooms
server/chat-server.ts WebSocket upgrade, origin check, per-IP limits
server/dev-plugin.ts runs the same chat server inside `vite dev`
server/index.ts production entry: SvelteKit handler + chat on one port
The client and the server validate everything independently: a message longer than 1000 characters is refused by the browser and by the server, and the server never trusts a client-supplied sender, timestamp or id.
pnpm test— 16 integration tests against a real HTTP server and real WebSocket clients: handshake and origin checks, presence, matchmaking, the 1000-character limit, rate limiting, leaving, and resuming a dropped chat.pnpm test:ui— drives two real browser tabs through a whole conversation (needs a running server on:3111and a Firefox binary; see the top oftest/ui.smoke.mjs).
Deep Chat hides who is talking to whom from the network, and it forgets everything afterwards. It is not end-to-end encrypted: the server can read messages in transit, which is also what lets it police them. See THREAT_MODEL.md for the full picture, including what an operator of the server can and cannot learn.