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
Every call in the web client now goes through MatrixRTC with a LiveKit
SFU, the model Element Call and Element X use; the legacy 1:1 m.call.*
calls are gone. In a room of two the other person is rung and can answer
or decline; in a bigger room a banner offers to join the call. Calls show
a grid of participant tiles with mute, camera, screen sharing and leave,
and media is end-to-end encrypted in encrypted rooms.
Server:
- MSC4140 delayed events (send/state with a delay, plus the
delayed_events list/restart/cancel/send API), stored and scheduled, so
a member whose client drops off is taken out of the call.
- POST /user/{userId}/openid/request_token, which lk-jwt-service uses to
hand out LiveKit tokens.
- org.matrix.msc4143.rtc_foci in /.well-known/matrix/client, configured
under client_api.rtc.
- Room presets let members send call membership state.
- Migration 0014 for both SQL dialects.
Deploy: LiveKit and lk-jwt-service in compose (calls.yml) and the helm
chart. Docs: docs/calls.md, configuration and deployment.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
199 lines
9.2 KiB
Markdown
199 lines
9.2 KiB
Markdown
# 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, with
|
|
an encrypted thumbnail for large images). A message this session can't
|
|
decrypt says why, such as "sent before this session signed in", not
|
|
just "encrypted".
|
|
- Trust on every message: one from a session you haven't verified says
|
|
*Not verified*; one with something wrong (an unsigned session,
|
|
mismatched keys, plain text in an encrypted room) says what.
|
|
- Verification: verify a person once (in a room you share, your direct
|
|
message first) and every session they verify is trusted; or one session
|
|
at a time; or this session from one of your others. Either side scans
|
|
the other's QR code (the camera, in the browser) or both compare emoji.
|
|
- Warnings when someone's identity changes: a reset since you first saw
|
|
them asks you to acknowledge it; a reset since you *verified* them says
|
|
their sessions are no longer trusted, with Verify again or Withdraw
|
|
verification.
|
|
- Voice and video calls in any room, all through a LiveKit SFU (MatrixRTC,
|
|
the Element Call model; the old Matrix 1:1 VoIP is not used). Voice and
|
|
video call buttons in the room header. In a room with one other person
|
|
the call rings them: an incoming-call card with ring tone, answer, answer
|
|
without video and decline, and "Calling…" with a ringback tone for the
|
|
caller until they answer, decline or a minute passes. A call of two shows
|
|
the other person large and you in the corner; a bigger one is a grid
|
|
with a tile per person, their camera or screen, and a ring around
|
|
whoever is speaking. Mute, camera, screen sharing, leave, and a panel
|
|
that stays up while you move between rooms. A "Join call" banner while
|
|
one is going on in a room you're not in it. In an encrypted room the media
|
|
is end-to-end encrypted with per-participant keys. The room list says
|
|
when a room has a call going on, and the timeline says when one started
|
|
and ended. It needs `client_api.rtc` on the server and a LiveKit JWT
|
|
service; see [docs/calls.md](../../docs/calls.md). warp-web proxies
|
|
`/.well-known/matrix/client` for this, and `-call-origins`
|
|
(`WARP_WEB_CALL_ORIGINS`) lets the browser reach the JWT service and
|
|
LiveKit through the Content-Security-Policy.
|
|
- 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 rooms (public ones too) and direct messages are end-to-end
|
|
encrypted by default; Create room has the switch for turning it off.
|
|
- Settings: display name, avatar, theme, encryption.
|
|
- Phone layout: one pane at a time, the room list is the home screen.
|
|
|
|
**Not yet:** 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>`). 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) encrypts its store with a random key that lives
|
|
with its credentials in `sessionStorage`: when the tab closes the key goes
|
|
with it, and the store left behind can't be read; it is deleted after two
|
|
weeks unused. A "Stay signed in" session's store is not encrypted at rest,
|
|
like any web client's without a passphrase.
|
|
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.
|
|
- Serve it over HTTPS: browsers only give the page a camera (for scanning
|
|
QR codes when verifying) and WebCrypto on a secure origin.
|
|
- 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`), calls (`call.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.
|