using Astralis; using Invercargill; using Invercargill.DataStructures; using Inversion; using Spry; /** * CounterSseEndpoint - Singleton SSE endpoint for counter updates * * Broadcasts counter value updates to all connected clients * when counter actions are triggered (Increment, Decrement, Reset) * * Uses the Astralis SseEndpoint pattern - NOT continuations. */ public class CounterSseEndpoint : SseEndpoint { private AuroraState aurora_state; public CounterSseEndpoint(AuroraState aurora_state) { this.aurora_state = aurora_state; } /// Retry interval: clients should wait 2 seconds before reconnecting public override uint retry_interval { get { return 2000; } } /// Called when a new client connects - send current counter value public override async void new_connection(HttpContext http_context, RouteContext route_context, SseStream stream) { print(@"Counter SSE client connected (total: $(get_open_streams().length))\n"); // Send current counter value to new client try { yield send_counter_update(stream); } catch (Error e) { print(@"Failed to send counter update: $(e.message)\n"); } stream.disconnected.connect(() => { print(@"Counter SSE client disconnected\n"); }); } /// Increment counter and broadcast update public async void increment() { aurora_state.counter++; yield broadcast_counter_update(); } /// Decrement counter and broadcast update public async void decrement() { aurora_state.counter--; yield broadcast_counter_update(); } /// Reset counter and broadcast update public async void reset() { aurora_state.counter = 0; yield broadcast_counter_update(); } /// Broadcast counter update to all connected clients private async void broadcast_counter_update() { try { string counter_html = build_counter_html(); var counter_event = new SseEvent.with_type("counter-value", counter_html); yield broadcast_event(counter_event); } catch (Error e) { printerr(@"Failed to broadcast counter update: $(e.message)\n"); } } /// Send counter update to a single stream (for new connections) private async void send_counter_update(SseStream stream) throws Error { string counter_html = build_counter_html(); yield stream.send_event(new SseEvent.with_type("counter-value", counter_html)); } /// Build the counter value HTML private string build_counter_html() { return aurora_state.counter.to_string(); } }