DUNGEONS-34: implement Webka local streaming app
Co-authored-by: multica-agent <github@multica.ai>
This commit is contained in:
37
docs/API.md
Normal file
37
docs/API.md
Normal file
@@ -0,0 +1,37 @@
|
||||
# API and transport
|
||||
|
||||
All API paths are same-origin. Mutations and WebSocket upgrades require an `Origin` matching `WEBKA_ORIGIN` exactly. JSON requests use `Content-Type: application/json`. Authentication is the signed `webka_session` cookie. Error responses are `{ "error": "translationKey" }`; framework validation errors are shown as a translated generic error by the frontend.
|
||||
|
||||
| Method | Path | Access | Result |
|
||||
|---|---|---|---|
|
||||
| GET | `/api/bootstrap` | Public | `{ needs_setup }` |
|
||||
| POST | `/api/bootstrap` | Setup token, first run only | `{ username, password, setup_token }` → 201 |
|
||||
| POST | `/api/login` | Public, throttled | `{ username, password }` → signed cookie |
|
||||
| POST | `/api/logout` | Signed in | Deletes current session and closes its transport |
|
||||
| GET | `/api/session` | Signed in | `{ id, username, role, session_id }`; refreshes activity |
|
||||
| GET/POST | `/api/accounts` | Admin | List accounts / create viewer `{ username, password }` |
|
||||
| DELETE | `/api/accounts/:id` | Admin | Delete viewer and cascade sessions; admin protected |
|
||||
| POST | `/api/accounts/:id/password` | Admin | `{ password }`; revokes all target sessions |
|
||||
| GET | `/api/sessions` | Admin | Active sessions and last activity |
|
||||
| DELETE | `/api/sessions/:id` | Admin | Force revoke session |
|
||||
| GET | `/api/status` | Signed in | Live state, uptime origin, count, share URL; identities visible only to admin |
|
||||
| GET upgrade | `/api/signal?mode=host\|viewer` | Signed in; host mode admin only | WebSocket JSON signaling |
|
||||
|
||||
One active host and one transport per login session. Up to 32 sockets, 64 KiB per WebSocket message, 120 signaling messages per second per socket; API bodies capped at 8 KiB. Roles are always enforced server-side. Login/setup permit ten attempts per IP per minute. Argon2 work is limited to two concurrent blocking jobs.
|
||||
|
||||
Signaling is not a media relay. The browser uses `RTCPeerConnection({ iceServers: [] })`, video-only tracks, trickle ICE and peer-to-peer encrypted WebRTC. The server chooses the sender identity from the authenticated connection; it never accepts a claimed `peer` field from a client.
|
||||
|
||||
Server events:
|
||||
|
||||
- `welcome { peer, live }`
|
||||
- `viewer-joined { peer }`, `viewer-left { peer }` (host only)
|
||||
- `host-online`, `host-offline` (viewers)
|
||||
- `error { error }` (capacity/session/host conflict)
|
||||
|
||||
Routed messages: `{ type, target, data }` become `{ type, peer, data }`.
|
||||
|
||||
- Host → its viewers: `offer` (SDP), `ice` (ICE candidate).
|
||||
- Viewer → current host: `answer`, `ice`, `renegotiate`.
|
||||
- Viewer-to-viewer and forged role messages are dropped.
|
||||
|
||||
The server revalidates the session before each inbound signal and every two seconds. Logout, account deletion, password reset and session revocation remove affected connections and notify the host/viewers to close media peers. The frontend clears peers when signaling disconnects, retries with 1–10 second exponential backoff, and rechecks authentication before reconnecting. There is no recording or server-side video storage.
|
||||
41
docs/MANUAL_TEST_PLAN.md
Normal file
41
docs/MANUAL_TEST_PLAN.md
Normal file
@@ -0,0 +1,41 @@
|
||||
# Real-device acceptance test
|
||||
|
||||
These are required manual checks, not a claim that physical devices have been tested. Use a trusted HTTPS origin, two separate browser profiles/devices on one LAN, and the Rust service with a fresh database.
|
||||
|
||||
## Setup and access
|
||||
|
||||
1. Open `/login` in a clean profile. Confirm all labels render in English. Switch to Russian and Spanish, refresh, and confirm the choice persists and the login card fits.
|
||||
2. Try a wrong setup token and a weak password. Confirm rejection. Create the owner using the local setup token and a 12+ character password containing letters and digits. Try setup again: it must be closed.
|
||||
3. Add two viewer accounts. Try a duplicate username with different casing: rejected. Sign in as a viewer; direct navigation and direct API calls to account management must be rejected.
|
||||
4. Try incorrect credentials 11 times within one minute from the same IP: the login endpoint returns 429. Wait a minute; valid login works.
|
||||
|
||||
## Capture and source switching
|
||||
|
||||
1. On `/host`, no camera should activate before user interaction. Enable preview; accept permission. A local video appears, but another viewer remains waiting until **Go live**.
|
||||
2. Deny permission in another profile. Confirm an actionable translated error and no false Live status. Open a LAN HTTP URL: confirm the secure-context error rather than a broken capture attempt.
|
||||
3. Grant camera access and inspect front/back controls on a dual-camera phone. Select back camera, then front. Confirm the correct lens, front preview mirroring only, and no duplicate media tracks. With one camera, unavailable back camera stays disabled.
|
||||
4. Start broadcasting. Join in a separate viewer session via the QR code. Confirm moving video and the host's viewer count. Compare host and viewer clocks/frame motion for LAN latency.
|
||||
5. While live, switch front → back → screen → front. Select a screen/window in the browser prompt. Confirm the existing viewer updates without signing in again. On platforms unable to open both lenses concurrently, note the error; stop preview and retry the other lens.
|
||||
6. Cancel the screen picker: the previous source must continue. Click the browser’s **Stop sharing** control: hosting stops, camera indicator turns off, and viewer shows waiting.
|
||||
7. Switch bitrate between low/adaptive/high. Inspect browser WebRTC stats: max bitrate changes (actual resolution depends on capture and congestion).
|
||||
8. Stop hosting, navigate to settings, close the tab, and log out separately. Each must stop the host camera and close outgoing peer streams.
|
||||
|
||||
## Viewer lifecycle and session revocation
|
||||
|
||||
1. Join before the host is live; automatic playback should start once the host starts. Check fullscreen, language switching while fullscreen, quality/resolution, and disconnect.
|
||||
2. Join from two viewers and revoke one session. Within two seconds its signaling closes; the host removes that peer and the revoked viewer returns to login. Other viewers continue normally.
|
||||
3. Reset a viewer password; all its sessions stop and old credentials fail. Delete that viewer account and confirm subsequent login is denied.
|
||||
4. Reset the administrator password while streaming. All its sessions are logged out and viewers stop receiving media.
|
||||
5. Interrupt Wi-Fi briefly. Confirm reconnect state and bounded exponential retry. Restore connectivity; playback should recover. Reload/stop/start the host and confirm waiting viewers recover.
|
||||
6. Attempt a second host tab with the same or another administrator session: it must show a conflict, not replace the active broadcast.
|
||||
7. Leave a session hidden/inactive for 30 minutes and reopen it; it must return to login. Absolute expiry after 12 hours applies even with heartbeats (also covered by automated backend tests).
|
||||
|
||||
## Visual, accessibility, and LAN checks
|
||||
|
||||
- Check all five routes at 390px and 1440px widths, portrait/landscape, and 200% text enlargement. No clipped dialogs or unreachable controls; the account table may scroll horizontally.
|
||||
- Check keyboard-only navigation, skip link, focus indicators, native dialog focus containment/Escape/return focus, language dropdown, and form error announcements.
|
||||
- Check dark/light modes and all four accents. Enable OS reduced motion: highlights and morph/ripple animations stop.
|
||||
- Scroll account rows to observe compact navigation. Check notches and fullscreen bottom safe areas.
|
||||
- Scan the QR on a second device; its URL must match the configured HTTPS origin. Test `webka.local` with mDNS on/off and the documented local DNS/IP fallback.
|
||||
- Inspect browser network traffic: assets, API and WebSocket stay at the configured origin; media connects only between LAN peers. There should be no public STUN/TURN, external image/font, telemetry, or cloud requests.
|
||||
- Confirm behavior on desktop Chromium/Firefox/Safari, Android Chrome, and iOS Safari where available. Record unsupported screen share, background suspension, camera switching, and client-isolation limits.
|
||||
Reference in New Issue
Block a user