What it is
One Python file is the server, the client and the protocol. It serves a full chat room — real-time messages, file sharing, typing indicators, reactions, a host admin panel — over whatever local network already exists, and it never opens an outbound connection. 1,627 lines, zero pip installs, 27,971 bytes of client that arrives as a string. TLS with a certificate generated in pure Python when the host has no openssl, a room password, a stealth mode on port 443, and a panic close that blanks the page when the tab shuts.
Version history
Straight from the project’s own card data — what each version added.
SSE + POST chat on port 9001: messages, names, uploads, host panel, reconnect.
TLS, room password, stealth mode, typing, reactions, history, sidebar, delete, export.
Pure-Python certificate fallback, zero-network IP detection, Python 3.9 support, panic close. Fixed 204 leaked brace escapes and a nested-quote SyntaxError that made the page inert.
Files on the site
What broke, and what I’d change
Not written yet. This is the part only I can write — what actually went wrong, and what I’d do differently. It lives in builds/reflections/offline-chat.md (see _TEMPLATE.md); as soon as that file has words in it, this section fills in on the next build.
Everything else on this page — the spec, the version history, the file counts — is generated from the project’s own build notes and files, so nothing here is guesswork.
Build spec
The project’s own build notes, rendered. Open the raw .md ↗
Show the spec
Build spec — Offline Chat (LAN, zero-internet multi-user chat)
Goal
A multi-user chat with file sharing that runs with no internet at all: no tunnel, no relay, no external service. One host starts it; everyone else joins from a browser on the same local network. Python standard library only — no pip install, no virtualenv, no build step. Ship it as a new file beside the existing app, never editing the original.
Explicit constraint from the request: "make sure its seperate file dont delete the old ne".
Hard requirements
0.0.0.0, serve same-origin only; no outbound socket anywhere |http.server, ssl, json, secrets, socket, subprocess from stdlib |EventSource; the client is one HTML string |offline_chat.py (v1), offline_chat_v2.py, start_chat.py |Protocols
- Push:
GET /events?session_id=…→text/event-stream, held open per client. - Actions:
POST /sendwith{action: message | set_name | react | delete | claim_host | ping}. - Files:
POST /upload(multipart) → stored inuploads/, served at/files/<name>. - Discovery:
GET /link→{link, host}wherehostcarries a one-time admin token.
Security features (v2)
--tls— self-signed cert, generated on first run, cached in temp.
- Prefer
openssl req -x509 -newkey rsa:2048 -addext subjectAltName=IP:<lan>,DNS:localhost. - Fallback when
opensslis missing (stock Windows): pure-Python RSA-2048 keygen
(Miller–Rabin primes) + hand-built X.509 DER, signed sha256WithRSAEncryption (OID 1.2.840.113549.1.1.11), PKCS#1 v1.5 padding, PEM at 64 columns.
--password P— room password;/authreturns a token,/eventsand/sendrequire it.--stealth— port 443 + TLS + password, so flows look like ordinary HTTPS.--bind-ip IP— manual override when LAN IP detection picks the wrong adapter.- Panic close —
beforeunload+pagehideblankdocument.bodyand clear stored state.
Cross-platform rules (learned the hard way)
- Target Python 3.9+:
Optional[str], neverstr | None. - Do not assume
opensslexists; do not assume a POSIX shell. - Do not assume the host can create a hotspot or an ad-hoc network — many managed Macs cannot.
start_chat.py must explain the realistic options (existing LAN, phone hotspot, direct cable) instead of pretending to create a radio.
Traps that cost real time
- Do not build the HTML with an f-string. JavaScript object literals and CSS braces collide with
- If the template was migrated from an f-string, every brace is still doubled (
{{/}}). - In a non-raw Python string,
\'is consumed by Python. Never generate nested - Verify the client with
node --checkon the extracted inline script and a real headless
Python interpolation. Use a plain triple-quoted string plus __MARKER__ tokens and .replace().
Collapse them once at render time — 204 of them leaked into a shipped page and produced an unstyled page whose JS never ran.
onclick="window.open('…')" inside a JS single-quoted string; use window.open(this.src).
browser, not just curl returning 200. A page can serve fine and still be inert.
Verification checklist
- [ ]
python3 -m py_compileon both files - [ ]
/healthreturns{status, users, tls, stealth, password_protected} - [ ]
/returns the full shell;{{count is 0; extracted script passesnode --check - [ ] two concurrent SSE clients: second join emits
system+user_list; a message from one is - [ ] wrong password rejected; correct password yields a token that
/eventsaccepts - [ ]
--tls→/healthover HTTPS showstls: true - [ ] the pure-Python cert path exercised with
PATHstripped ofopenssl, under system Python 3.9 - [ ] headless Chrome on the static demo: message bubbles render, share link fills, 0 console errors
- [ ] original files untouched (
git status/ checksums)
received by the other and replayed in history for a later session
Deliverable shape
offline_chat_v2.py— server + client in one filestart_chat.py— guided launcherdemos/offline-chat.html— UI demo (real markup,fetch/EventSourcestubbed, labelled as demo)demos/offline-chat-analysis.html— build analysis with measured numbers and honest limitsdemos/offline-chat-mirror/— byte-identical copies of both.pyfiles + hashes + downloads