diff --git a/api/src/main.rs b/api/src/main.rs index 0aeb9b3..8e43e92 100644 --- a/api/src/main.rs +++ b/api/src/main.rs @@ -64,7 +64,7 @@ async fn main() -> Result<(), rocket::Error> { let rocket = rocket::build() .manage(state) - .mount("/", routes![healthz, login, logout, events]) + .mount("/", routes![healthz, login, logout, events, public_events]) .mount("/persons", routes::persons::routes()) .mount("/tournament", routes::tournaments::routes()); @@ -157,3 +157,25 @@ fn events(_user: AuthUser, state: &State) -> EventStream![Event + '_] } } } + +#[get("/events/public")] +fn public_events(state: &State) -> EventStream![Event + '_] { + let mut receiver = state.event_sender.subscribe(); + + EventStream! { + loop { + match receiver.recv().await { + Ok(event) => { + match &event { + AppEvent::TournamentUpserted { .. } | AppEvent::TournamentDeleted { .. } => { + yield Event::json(&event); + } + _ => continue, + } + } + Err(RecvError::Closed) => break, + Err(RecvError::Lagged(_)) => continue, + } + } + } +} diff --git a/web/src/lib/client/event-stream.ts b/web/src/lib/client/event-stream.ts index bc75805..a5514cb 100644 --- a/web/src/lib/client/event-stream.ts +++ b/web/src/lib/client/event-stream.ts @@ -5,13 +5,13 @@ export type AppEvent = | { type: 'tournament_upserted'; tournament: TournamentInfo } | { type: 'tournament_deleted'; tournament_id: number }; -export function listenToEvents(onEvent: (event: AppEvent) => void) { +export function listenToEvents(onEvent: (event: AppEvent) => void, endpoint = '/api/events') { let stopped = false; let source: EventSource | null = null; function connect() { if (stopped) return; - source = new EventSource('/api/events'); + source = new EventSource(endpoint); source.onmessage = (event) => { try { const data = JSON.parse(event.data) as AppEvent; diff --git a/web/src/lib/client/tournament-events.ts b/web/src/lib/client/tournament-events.ts index f6ce13f..255fd71 100644 --- a/web/src/lib/client/tournament-events.ts +++ b/web/src/lib/client/tournament-events.ts @@ -5,10 +5,16 @@ export type TournamentEvent = | { type: 'tournament_upserted'; tournament: TournamentInfo } | { type: 'tournament_deleted'; tournament_id: number }; +type TournamentEventOptions = { + endpoint?: string; +}; + export function listenToTournamentEvents( onUpsert: (tournament: TournamentInfo) => void, - onDelete: (tournamentId: number) => void + onDelete: (tournamentId: number) => void, + options: TournamentEventOptions = {} ) { + const endpoint = options.endpoint ?? '/api/events'; return listenToEvents((event) => { if (event.type === 'tournament_upserted') { onUpsert(event.tournament); @@ -17,5 +23,5 @@ export function listenToTournamentEvents( if (event.type === 'tournament_deleted') { onDelete(event.tournament_id); } - }); + }, endpoint); } diff --git a/web/src/routes/(tournament)/tournament/+page.svelte b/web/src/routes/(tournament)/tournament/+page.svelte index da3e78c..5e076c9 100644 --- a/web/src/routes/(tournament)/tournament/+page.svelte +++ b/web/src/routes/(tournament)/tournament/+page.svelte @@ -63,8 +63,10 @@ return `${count} ${count === 1 ? 'spelare' : 'spelare'}`; } - onMount(() => { - const stop = listenToTournamentEvents(upsertTournament, removeTournament); +onMount(() => { + const stop = listenToTournamentEvents(upsertTournament, removeTournament, { + endpoint: '/api/public-events' + }); return () => { stop(); }; diff --git a/web/src/routes/api/public-events/+server.ts b/web/src/routes/api/public-events/+server.ts new file mode 100644 index 0000000..205d967 --- /dev/null +++ b/web/src/routes/api/public-events/+server.ts @@ -0,0 +1,26 @@ +import type { RequestHandler } from './$types'; +import { proxyRequest } from '$lib/server/backend'; + +export const GET: RequestHandler = async (event) => { + const { response, setCookies } = await proxyRequest(event, '/events/public', { + method: 'GET' + }); + + const headers = new Headers(); + const contentType = response.headers.get('content-type'); + if (contentType) { + headers.set('content-type', contentType); + } else { + headers.set('content-type', 'text/event-stream'); + } + for (const cookie of setCookies) { + headers.append('set-cookie', cookie); + } + headers.set('cache-control', 'no-cache'); + + return new Response(response.body, { + status: response.status, + statusText: response.statusText, + headers + }); +};