Web client (warp-web) with E2EE, and two sync fixes
CI / Test (ubuntu-latest) (push) Canceled after 0s
CI / Test (windows-latest) (push) Canceled after 0s
CI / Storage tests on PostgreSQL (push) Canceled after 0s
CI / Complement (federation off) (push) Canceled after 0s
CI / golangci-lint (push) Canceled after 0s
CI / Smoke test the binary (push) Canceled after 0s
CI / Admin panel (push) Canceled after 0s
CI / Web client (push) Canceled after 0s
CI / Test (ubuntu-latest) (push) Canceled after 0s
CI / Test (windows-latest) (push) Canceled after 0s
CI / Storage tests on PostgreSQL (push) Canceled after 0s
CI / Complement (federation off) (push) Canceled after 0s
CI / golangci-lint (push) Canceled after 0s
CI / Smoke test the binary (push) Canceled after 0s
CI / Admin panel (push) Canceled after 0s
CI / Web client (push) Canceled after 0s
apps/web: a Matrix client for the browser on Vue 3, Tailwind and matrix-js-sdk, built on @warp/ui. Like the admin panel it is an optional, separate service: warp-web embeds the built app, serves /config.json and proxies only /_matrix/client and /_matrix/media to the homeserver (or, with -public-homeserver, lets the browser call it directly). Sign-in and sign-up with registration tokens, room list, timeline with back-pagination, text, images, video and files over authenticated media, local echo with sent/read/failed states, receipts, typing, invites, creating, joining and leaving rooms, room info, settings, phone layout. End-to-end encryption through the Rust crypto: encrypted messages and files, emoji verification, key backup setup and restore. apps/ui: WRoomItem, WMessageBubble and WComposer from the brand package, and the icons the client needs. syncapi fixes found with the client: - A room joined inside the sync window was sent as the join alone with limited: false, so clients thought they had the whole room. It is now read like an initial sync; a profile change is not mistaken for a join. - A live typing notification was reported on every sync, so every long poll returned at once while anybody typed. Typing now has a position in the sync token and is reported when it changes, including the empty list when the last person stops. Also: Dockerfile, a `web` compose profile, Makefile targets, a CI job with a proxy smoke test, docs. PLAN.md moves out of the repository. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
adc18e7cf3
commit
b6f3be6ac4
90 files changed
+6608
-472
No files matched your search
@@ -0,0 +1,178 @@
|
||||
# Warp web client
|
||||
|
||||
A Matrix client for the browser: the room list, the timeline, sending
|
||||
messages and files, invites, creating and joining rooms. It speaks the
|
||||
plain Client-Server API through [matrix-js-sdk](https://github.com/matrix-org/matrix-js-sdk),
|
||||
so it works against any Matrix homeserver, and it is built on Warp's design
|
||||
system, [`@warp/ui`](../ui/README.md).
|
||||
|
||||
The client is **optional and separate from the homeserver**, like the
|
||||
[admin panel](../admin/README.md). It ships as its own small binary,
|
||||
`warp-web`, with the built Vue app embedded. A Warp server runs the same
|
||||
whether or not you install it.
|
||||
|
||||
```
|
||||
browser ──► warp-web :8080 ──► warp :8008
|
||||
serves the client /_matrix/client/*
|
||||
/config.json /_matrix/media/*
|
||||
```
|
||||
|
||||
By default warp-web proxies the two path prefixes a client uses and
|
||||
answers `404` for everything else (federation, server keys, `/_warp/admin`),
|
||||
so it is not a second way into the rest of the homeserver. The browser
|
||||
only talks to warp-web: no CORS, and the homeserver URL is runtime
|
||||
configuration rather than baked into the build.
|
||||
|
||||
If the homeserver is already public behind its own proxy, set
|
||||
`-public-homeserver` instead. The browser then calls it directly,
|
||||
warp-web proxies nothing, and the Content-Security-Policy allows exactly
|
||||
that one origin.
|
||||
|
||||
## What it does
|
||||
|
||||
- Sign in with a password, and sign up when the homeserver has
|
||||
registration on (with a registration token when it asks for one).
|
||||
"Stay signed in" keeps the session in `localStorage`; without it the
|
||||
session goes with the tab. Signing out logs the session out on the
|
||||
homeserver.
|
||||
- Room list with unread counts and mentions, invites, a filter, direct
|
||||
messages with presence.
|
||||
- Timeline: grouped messages, day separators, room events as one-line
|
||||
notices, older history on scroll, images, video, audio and files.
|
||||
Media is fetched with the access token (authenticated media, Matrix 1.11)
|
||||
and shown from `blob:` URLs.
|
||||
- Sending: text (`/me` for emotes), files by the attach button, paste or
|
||||
drag and drop. Your message shows at once as *Sending*, then *Sent*,
|
||||
then *Read* when someone else has read it; a failed one says *Not sent*
|
||||
with Retry and Delete.
|
||||
- Read receipts, typing notifications, deleting messages.
|
||||
- End-to-end encryption, with the Rust crypto stack matrix-js-sdk loads
|
||||
(`matrix-sdk-crypto-wasm`): reading and sending in encrypted rooms,
|
||||
files included (encrypted in the browser before they are uploaded).
|
||||
A message this session can't decrypt says why, such as "sent before
|
||||
this session signed in", not just "encrypted".
|
||||
- Verification: verify someone's session from the room's member list,
|
||||
or this session from one of your others, by comparing emoji (SAS).
|
||||
Requests from others open a dialog to accept or decline.
|
||||
- Encryption setup in Settings: cross-signing keys, secret storage and a
|
||||
key backup in one step, behind a recovery key shown once. A new session
|
||||
verifies itself with the recovery key (or another session) and restores
|
||||
older messages from the backup. Reset encryption when the key is lost.
|
||||
- New private rooms and direct messages are end-to-end encrypted by
|
||||
default (a direct message only when the other person has a session with
|
||||
keys); Create room has the switch.
|
||||
- Create a room (private, or public with an address), start a direct chat,
|
||||
join by address or from the room directory, accept or decline invites,
|
||||
leave.
|
||||
- Room info: topic, members and their roles, invite, edit name and topic.
|
||||
- Settings: display name, avatar, theme, encryption.
|
||||
- Phone layout: one pane at a time, the room list is the home screen.
|
||||
|
||||
**Not yet:** per-message trust shields (a message from an unverified
|
||||
session looks like any other), verifying a person's identity in a direct
|
||||
message rather than one session at a time, QR-code verification, a warning
|
||||
when someone's identity changes, encrypted thumbnails for images you send
|
||||
(the receiving side shows the whole image), and a passphrase for the
|
||||
crypto store. After that: replies, reactions, threads, spaces,
|
||||
notifications.
|
||||
|
||||
### Encryption keys in the browser
|
||||
|
||||
Each session keeps its keys in IndexedDB under its own name
|
||||
(`warp-web-crypto:<user>:<session>`), unencrypted at rest like any web
|
||||
client's without a passphrase. Signing out deletes them; a session that
|
||||
ends some other way (signed out elsewhere, the token revoked) has them
|
||||
deleted when the page next starts. A session kept only for the tab
|
||||
("Stay signed in" off) leaves its store behind when the tab closes;
|
||||
the session can't be reused, but the keys stay until site data is
|
||||
cleared. The `.wasm` file is most of the download (about 2 MB
|
||||
compressed) and needs `script-src 'wasm-unsafe-eval'`, which warp-web's
|
||||
Content-Security-Policy allows; it allows no JavaScript `eval`.
|
||||
|
||||
## Run it
|
||||
|
||||
### With Docker Compose
|
||||
|
||||
```bash
|
||||
cd deploy/compose
|
||||
docker compose --profile web up -d
|
||||
```
|
||||
|
||||
The client is on <http://localhost:8080>. Leave out `--profile web` and it
|
||||
isn't installed.
|
||||
|
||||
### As a container
|
||||
|
||||
```bash
|
||||
docker build -t warp-web -f apps/web/Dockerfile .
|
||||
docker run -p 8080:8080 -e WARP_WEB_HOMESERVER=http://warp:8008 warp-web
|
||||
```
|
||||
|
||||
### As a binary
|
||||
|
||||
```bash
|
||||
make web
|
||||
./bin/warp-web -homeserver http://localhost:8008
|
||||
```
|
||||
|
||||
| Flag | Environment | Default |
|
||||
|---|---|---|
|
||||
| `-listen` | `WARP_WEB_LISTEN` | `:8080` |
|
||||
| `-homeserver` | `WARP_WEB_HOMESERVER` | `http://localhost:8008` |
|
||||
| `-public-homeserver` | `WARP_WEB_PUBLIC_HOMESERVER` | empty: proxy through warp-web |
|
||||
| `-log-format` | `WARP_WEB_LOG_FORMAT` | `text` (or `json`) |
|
||||
|
||||
`/healthz` answers `ok` for container health checks. `/config.json` is what
|
||||
the client reads on start: `{"homeserver_url": "", "version": "..."}`, an
|
||||
empty URL meaning "this origin".
|
||||
|
||||
## Deploying
|
||||
|
||||
- Put warp-web behind your TLS proxy. It sends a strict
|
||||
Content-Security-Policy, `X-Frame-Options: DENY` and
|
||||
`Referrer-Policy: no-referrer` with the client's pages.
|
||||
- Keep the proxy's read timeout above 30 seconds: `/sync` long-polls.
|
||||
- Allow request bodies as large as the homeserver's
|
||||
`media.max_upload_bytes`; uploads pass through warp-web.
|
||||
- warp-web adds `X-Forwarded-For`. The homeserver uses it for rate limits
|
||||
only with `client_api.trust_x_forwarded_for` on; without it, every
|
||||
client counts against warp-web's own address. Turn it on only when every
|
||||
client reaches the homeserver through proxies you control.
|
||||
|
||||
## Develop
|
||||
|
||||
```bash
|
||||
pnpm install
|
||||
pnpm web:dev # http://localhost:5175, proxied to localhost:8008
|
||||
```
|
||||
|
||||
`WARP_WEB_HOMESERVER` points the dev server at another homeserver. Vite
|
||||
proxies the same paths warp-web does, so the app code is the same in both.
|
||||
|
||||
| Command | What |
|
||||
|---|---|
|
||||
| `make web-web` | build the frontend into `internal/client/build/dist` |
|
||||
| `make web` | build the frontend, then `bin/warp-web` with it embedded |
|
||||
| `make web-test` | typecheck the frontend, vet and test the Go host |
|
||||
| `make web-docker` | build the container image |
|
||||
|
||||
A `warp-web` compiled before the frontend is built still runs. It serves a
|
||||
page saying the client is missing.
|
||||
|
||||
## Layout
|
||||
|
||||
| Path | What |
|
||||
|---|---|
|
||||
| `src/matrix/` | everything that touches matrix-js-sdk: the client's lifecycle, the timeline projection, sending, media, encryption (`crypto.ts`, `attachments.ts`) |
|
||||
| `src/stores/` | session, room list and connection state, encryption and verification (`security.ts`), profile, toasts (Pinia) |
|
||||
| `src/views/` | sign-in, sign-up, home, room |
|
||||
| `src/components/` | timeline, message body, room info, dialogs |
|
||||
| `cmd/warp-web/` | the binary |
|
||||
| `internal/host/` | static serving, `/config.json` and the allow-listed proxy |
|
||||
| `internal/client/` | the embedded build |
|
||||
|
||||
The SDK's objects change in place and emit events. The stores turn those
|
||||
events into a version counter (bumped at most once a frame), and the views
|
||||
are computed from the SDK on each bump. Keeping the SDK behind `src/matrix`
|
||||
and `src/stores` is what will let the desktop app put its Go backend
|
||||
behind the same stores.
|
||||
Reference in new issue
Block a user