# Streaming chat demo

[server.cjs](server.cjs) is a small local "AI chat" for checking whether a sharing tool carries a streamed reply to someone else's browser. It needs Node.js and nothing else. Replies are fixture text sent one word at a time as Server-Sent Events, the way model APIs stream tokens, so no model or API key is involved.

```bash
node server.cjs            # http://127.0.0.1:47614
PORT=8080 node server.cjs  # another port
```

Open it, send a message, and the reply should appear word by word, about 150 ms apart. Stop ends the request, and the server stops generating as soon as the connection closes.

The server accepts only loopback Host headers and subdomains of `qurl.site` to block DNS rebinding. To test another sharing tool, configure it to forward `Host: localhost`, or add its exact assigned hostname to the check in `server.cjs`. A 403 "Host not allowed" is this sample's check, not a streaming failure. Keep the Host check enabled. POST requests must use `Content-Type: application/json`; the server rejects simple cross-site content types and does not enable CORS. Keep this check when adding a model provider.

To use a real model, replace `fixtureReply()` with a call to your provider inside `server.cjs`. The API key stays on the server and the browser only talks to this app. That is the difference between sharing a chat interface and exposing a model's API. Set `CHAT_LOG=chat.log` to log each reply's start, finish and early stop.

## What has been checked

| Date | Path | Result |
| --- | --- | --- |
| 2026-10-18 | Headless Chromium on the same machine, direct to `http://127.0.0.1:47614` (Node 20.18.3, macOS) | 49 words arrived 150–153 ms apart, never two within 20 ms. Stop after 5 words aborted the request, and the server logged `chat-aborted` with 5 sent. |
| 2026-10-18 | Through a real qURL link on production (CLI 3.5.0, an existing local route on port 4173), opened in headless Chromium | The page loaded on `r_<id>.qurl.site`. 49 words arrived 152 ms apart at the median, at most 204 ms, never two within 20 ms. Stop after 5 words aborted the request, and the server logged the connection closing after 6 words sent. Details: [results-2026-10-18.json](../qurl-behaviour/results-2026-10-18.json). |

These October 18 rows used the earlier `server.js`, before the Host and body checks, keepalive handling, partial-error retention, JSON-only POST check and `.cjs` rename. They are historical measurements, not proof of this exact file.

Nothing has been checked through ngrok or Cloudflare. Cloudflare's own documentation says Quick Tunnels do not support Server-Sent Events.
