2026-10-16 07:40:29 -06:00
|
|
|
"""`GET /api/events`: the server-sent event stream (session required; locked sessions allowed)."""
|
|
|
|
|
|
|
|
|
|
from __future__ import annotations
|
|
|
|
|
|
|
|
|
|
from fastapi import APIRouter, Query, Request
|
|
|
|
|
from fastapi.responses import StreamingResponse
|
|
|
|
|
from starlette.concurrency import run_in_threadpool
|
|
|
|
|
|
2026-10-18 15:08:34 -06:00
|
|
|
from ..auth.groups import SCREEN_LABELS, SCREENS, role_label
|
2026-10-16 07:40:29 -06:00
|
|
|
from ..events import EventHub, stream
|
|
|
|
|
from .deps import resolve_standalone, settings_of
|
|
|
|
|
|
|
|
|
|
router = APIRouter(prefix="/api", tags=["events"])
|
|
|
|
|
|
|
|
|
|
SSE_HEADERS = {"Cache-Control": "no-cache", "X-Accel-Buffering": "no", "Connection": "keep-alive"}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.get("/events")
|
2026-10-16 22:46:56 -06:00
|
|
|
async def events(
|
|
|
|
|
request: Request,
|
|
|
|
|
screen: str | None = Query(None, max_length=32),
|
|
|
|
|
packet_id: int | None = Query(None, ge=1, le=2**62),
|
|
|
|
|
) -> StreamingResponse:
|
|
|
|
|
"""The event stream. `packet_id` is the packet the page has open; presence carries it, so the packet page can
|
2026-10-18 15:08:34 -06:00
|
|
|
show who else is looking at the same packet. The client reopens the stream when the open packet changes.
|
|
|
|
|
Presence names the person with the label of the screen they have open (a screen their roles allow), else their
|
|
|
|
|
primary role. A locked session is left out of presence until it unlocks."""
|
2026-10-16 07:40:29 -06:00
|
|
|
# The session check runs on its own pooled connection, returned before the stream starts
|
|
|
|
|
p = await run_in_threadpool(resolve_standalone, request)
|
|
|
|
|
hub: EventHub = request.app.state.events
|
2026-10-18 15:08:34 -06:00
|
|
|
open_screen = screen if screen in SCREENS and screen in p.screens else None
|
2026-10-16 07:40:29 -06:00
|
|
|
sub = hub.subscribe(
|
|
|
|
|
session_id=p.session_id,
|
|
|
|
|
user_id=p.user_id,
|
|
|
|
|
display_name=p.user["display_name"],
|
2026-10-18 15:08:34 -06:00
|
|
|
screen=open_screen,
|
2026-10-16 22:46:56 -06:00
|
|
|
packet_id=packet_id,
|
2026-10-18 15:08:34 -06:00
|
|
|
role=SCREEN_LABELS[open_screen] if open_screen else role_label(p.roles),
|
|
|
|
|
locked=p.locked,
|
2026-10-16 07:40:29 -06:00
|
|
|
)
|
|
|
|
|
hello = {"session_id": str(p.session_id), "build": settings_of(request).ps_build}
|
|
|
|
|
return StreamingResponse(
|
|
|
|
|
stream(hub, sub, hello=hello, is_disconnected=request.is_disconnected),
|
|
|
|
|
media_type="text/event-stream",
|
|
|
|
|
headers=SSE_HEADERS,
|
|
|
|
|
)
|