1
0

feat: add private office media signaling

This commit is contained in:
2026-08-11 20:48:19 -07:00
parent fc1f500019
commit d841575315
14 changed files with 2342 additions and 3 deletions
+39 -3
View File
@@ -35,10 +35,15 @@ without breaking the typecheck, which is the wrong order to find out.
| `POST /api/v1/realtime/events` | — | fetch-streamed SSE, opened with a strict `ResumeRequest` | **private; never cached** |
| `POST /api/v1/realtime/pose` | — | one owned `EntityPoseSnapshot` | **private; never cached** |
| `POST /api/v1/realtime/leave` | — | session id + opaque resume token | **private; never cached** |
| `POST /api/v1/media/join` | — | strict create or explicitly opted-in join request | **private; never cached** |
| `POST /api/v1/media/events` | — | fetch-streamed SSE opened with a strict resume request | **private; never cached** |
| `POST /api/v1/media/signal` | — | strict targeted SDP/ICE request | **private; never cached** |
| `POST /api/v1/media/leave` | — | strict presenter stop or revoke request | **private; never cached** |
Every body is declared once, in `src/server/wire.ts` in the **root** package
type-only, so it compiles to nothing and both the browser build and this service
import the same declarations without either becoming a dependency of the other.
Every body is declared once in the **root** package: ordinary feeds live in
`src/server/wire.ts`, realtime in `src/realtime`, and screen signaling in
`src/media/signalingTypes.ts`. The browser and service import the same strict
contracts without either transport becoming a dependency of the other.
Both location parameters are optional and omitting them answers for the default
region, which is the first entry in `TERA_REGIONS`. Giving both `city` and a
@@ -75,6 +80,37 @@ or room may be joined. The three public demo offices bundled into this repo may
use their office envelope without a duplicate server pack; that exception does
not invent floor/room access and does not apply to arbitrary tenant ids.
### Office screen signaling
Office screen sharing uses a separate, in-memory signaling service; it never
enters the game-state realtime service. The server stores only short-lived SDP
and ICE messages, hashed capability grants and server-only auth subjects. It
does not receive or store media, track data, recordings, stream URLs or source
locators. All capabilities travel in authenticated POST bodies, including the
fetch-streamed SSE request, never in a query string.
A share binds to an exact authored `{officeId, levelId, roomId, screenId}`. The
server resolves the office pack and confirms that the id is a monitor/display
prop at that level and in that room. The only no-disk exception is the exact
screen catalogue in the three public bundled demo packs. A viewer joins by that
binding and must send literal `viewerOptIn: true`; the server finds the active
presenter and issues opaque session/participant ids. One presenter and at most
seven viewers are allowed. Complete participant snapshots let the presenter
create and close one peer connection per authorized viewer.
This is currently an **authenticated deployment-member policy**, because the
existing auth model knows only signed-in member versus configured global admin.
It is not a tenant or per-office membership ACL. Add an authoritative
office-membership provider before using these routes for tenant-isolated offices.
Grants are random, stored only as SHA-256 hashes, rotated on resume and leased
for a short period. Signal queues, sessions, participants and request rates are
bounded. Stop, leave, expiry and revocation remove server capabilities and send
best-effort terminal/snapshot events to connected peers. They cannot instantly
terminate media already flowing through an established WebRTC connection;
clients must close missing/revoked peers, and the short lease bounds disconnected
clients that miss an event.
## Regions
**A caller's coordinate is never forwarded upstream. It only selects among the