Server on an isolate, dedicated server binary, room -> server (proto 5)
- serve(Map config, events) is the one server entrypoint: the app hosts it via Isolate.spawn (crash surfaced on the Host card, logs forwarded as [server]); bin/server.dart is the dedicated server, compiled into the bundle as watch-together-server. --headless gone. - Host identity via a per-hosting hostKey in hello, replacing the loopback heuristic. - Rename room -> server across code, files and wire (t:"state", /health name); protocol bumped to 5. - CI compiles the server binary; isolate smoke tests in server and desktop suites. Docs updated. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
d8a0729029
commit
81cac1214d
48 files changed
+1073
-423
No files matched your search
+19
-14
@@ -1,31 +1,32 @@
|
||||
# Protocol (proto = 4)
|
||||
# Protocol (proto = 5)
|
||||
|
||||
Wire format only. Behavior (gate, clock, timeouts) is in
|
||||
[ARCHITECTURE.md](ARCHITECTURE.md). Source of truth:
|
||||
`packages/core/lib/src/protocol.dart` and `packages/server/lib/src/room_hub.dart`.
|
||||
`packages/core/lib/src/protocol.dart` and `packages/server/lib/src/server_hub.dart`.
|
||||
|
||||
Any wire change bumps `RelayConstants.protoVersion` and edits this file in
|
||||
the same commit. Changes are additive.
|
||||
the same commit. Changes are additive where possible; a client and server
|
||||
with different `proto` refuse each other at `hello` (`proto mismatch`).
|
||||
|
||||
## Transport
|
||||
|
||||
- WebSocket at `/sync`, JSON text frames, each an object with a `t` field.
|
||||
- The first client frame must be `hello` within 10 s. Server pings every 5 s.
|
||||
- Match incoming frames by `t`, never by order: `welcome` can race `room`.
|
||||
- Match incoming frames by `t`, never by order: `welcome` can race `state`.
|
||||
- Failures: server sends `{"t":"err","message":...}` and closes.
|
||||
Messages: `bad hello: ...`, `proto mismatch: got X, want Y`, `wrong secret`,
|
||||
`rejected: <reason>` (e.g. avatar too large), `bad message: ...` (a frame
|
||||
with a non-finite number, e.g. NaN position), `bad swap media`,
|
||||
`swap needs a url or the hosted file` (empty swap).
|
||||
- `GET /health` returns JSON (`ok`, `room`, `proto`, `viewers`, `bytesServed`,
|
||||
- `GET /health` returns JSON (`ok`, `name`, `proto`, `viewers`, `bytesServed`,
|
||||
`uptimeSecs`; unauthenticated). `GET /m/<sid>/<token>/<name>`
|
||||
serves the host's file (Range table in ARCHITECTURE.md).
|
||||
|
||||
## Client to server
|
||||
|
||||
```
|
||||
{"t":"hello","proto":4,"secret":"","user":{"name":"","avatar":""},
|
||||
"media":{"name":"","size":0,"duration":0,"url":null}}
|
||||
{"t":"hello","proto":5,"secret":"","user":{"name":"","avatar":""},
|
||||
"media":{"name":"","size":0,"duration":0,"url":null},"hostKey":"..."}
|
||||
{"t":"ready","ready":true,"source":"manual"} source: manual | auto
|
||||
{"t":"force","target":"all"|"<memberId>","ready":true}
|
||||
{"t":"seek","position":123.4}
|
||||
@@ -37,8 +38,12 @@ the same commit. Changes are additive.
|
||||
|
||||
Field notes:
|
||||
- `hello.secret`: the server password; a server with an empty secret is open.
|
||||
The member connecting from a loopback address is the host.
|
||||
`hello.media` is carried but not used by the server today.
|
||||
- `hello.hostKey` (optional, omitted when empty): the hosting app
|
||||
generates a random key per hosting, gives it to its server and sends it
|
||||
from its own client. The member whose key equals the server's is the host
|
||||
and cannot be kicked. A server without a key (the dedicated server) has no
|
||||
host. Where the connection comes from does not matter.
|
||||
- `user.avatar`: base64 image, at most 256 KiB of characters. `name` is
|
||||
trimmed, empty becomes `?`, cut to 32 characters.
|
||||
- `chat.text` is cut to 500 characters.
|
||||
@@ -49,8 +54,8 @@ Field notes:
|
||||
## Server to client
|
||||
|
||||
```
|
||||
{"t":"welcome","token":"...","memberId":"m1","mediaUrl":"...","state":{<room>}}
|
||||
{"t":"room","eventId":7,"transport":"playing"|"held","position":12.3,
|
||||
{"t":"welcome","token":"...","memberId":"m1","mediaUrl":"...","state":{<state>}}
|
||||
{"t":"state","eventId":7,"transport":"playing"|"held","position":12.3,
|
||||
"serverTime":"<ISO-8601 UTC>","mediaId":1,
|
||||
"media":{name,size,duration,url?}|absent,
|
||||
"members":[{"id","name","avatar","ready","inSet","buffered","stale"}]}
|
||||
@@ -64,8 +69,8 @@ Field notes:
|
||||
media URL. `mediaUrl` is omitted when there is no media; it is a built
|
||||
`/m/...` URL for a host file (host part = what the client dialled), otherwise
|
||||
the shared link.
|
||||
`welcome.state` has the same shape as `room`.
|
||||
- `room` is sent every second and on every change. `position` is the value at
|
||||
`welcome.state` has the same shape as `state`.
|
||||
- `state` is sent every second and on every change. `position` is the value at
|
||||
`serverTime`; clients extrapolate while `transport` is `playing`.
|
||||
`eventId` increases on seek, swap and transport changes.
|
||||
`mediaId` increases on swap.
|
||||
@@ -80,7 +85,7 @@ Field notes:
|
||||
|
||||
## Join and reconnect
|
||||
|
||||
`hello` then `welcome`, then the app opens `mediaUrl`, seeks to the room
|
||||
`hello` then `welcome`, then the app opens `mediaUrl`, seeks to the server
|
||||
position and buffers. A joining member is `ready=false, inSet=false` until
|
||||
the first `ready`. A socket that dies loses its token; reconnect is a fresh
|
||||
`hello`, after which the client re-sends its prior ready flag.
|
||||
@@ -90,4 +95,4 @@ the first `ready`. A socket that dies loses its token; reconnect is a fresh
|
||||
Server-held queue (`queue-add`, `queue-skip`) would auto-advance like a swap.
|
||||
`RelayConstants.chunkBytes`, `creditWindowChunks`, `maxConcurrentOpen` and the
|
||||
`open`, `ack`, `cancel`, `eof`, `buffered`, `state`, `event` kinds in
|
||||
`MessageKind` are unused leftovers.
|
||||
`MessageKind` are unused leftovers (the `state` frame does not use the enum).
|
||||
Reference in new issue
Block a user