Skip to content

Repository files navigation

Deep Chat

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.

Getting started

pnpm install
pnpm dev          # http://localhost:5173 — the real chat server runs in dev too

Open 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 --check

Configuration

All 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.

Hosting on Tor

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-chat

Then 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.

How it is put together

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.

Tests

  • 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 :3111 and a Firefox binary; see the top of test/ui.smoke.mjs).

What this is not

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.