using Astralis; using Invercargill; using Invercargill.DataStructures; using Inversion; using Spry; /** * AuroraSseEndpoint - Singleton SSE endpoint for aurora updates * * Broadcasts aurora canvas and stats updates to all connected clients * when aurora actions are triggered (BoostSolarWind, CalmAurora, etc.) * * Uses the Astralis SseEndpoint pattern - NOT continuations, since * continuations are for per-connection streaming, not broadcasting. */ public class AuroraSseEndpoint : SseEndpoint { private AuroraState aurora_state; public AuroraSseEndpoint(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 state public override async void new_connection(HttpContext http_context, RouteContext route_context, SseStream stream) { print(@"Aurora SSE client connected (total: $(get_open_streams().length))\n"); // Send current aurora state to new client try { yield send_aurora_update(stream); } catch (Error e) { print(@"Failed to send aurora update: $(e.message)\n"); } stream.disconnected.connect(() => { print(@"Aurora SSE client disconnected\n"); }); } /// Boost solar wind and broadcast update public async void boost_solar_wind() { aurora_state.boost_solar_wind(); yield broadcast_aurora_update(); } /// Calm aurora and broadcast update public async void calm() { aurora_state.calm(); yield broadcast_aurora_update(); } /// Shift colors and broadcast update public async void shift_colors() { aurora_state.shift_colors(); yield broadcast_aurora_update(); } /// Add wave and broadcast update public async void add_wave() { aurora_state.add_wave(); yield broadcast_aurora_update(); } /// Broadcast aurora update to all connected clients private async void broadcast_aurora_update() { try { // Create canvas HTML string canvas_html = build_canvas_html(); var canvas_event = new SseEvent.with_type("aurora-canvas", canvas_html); // Create stats HTML string stats_html = build_stats_html(); var stats_event = new SseEvent.with_type("aurora-stats", stats_html); // Broadcast both events yield broadcast_event(canvas_event); yield broadcast_event(stats_event); } catch (Error e) { printerr(@"Failed to broadcast aurora update: $(e.message)\n"); } } /// Send aurora update to a single stream (for new connections) private async void send_aurora_update(SseStream stream) throws Error { string canvas_html = build_canvas_html(); yield stream.send_event(new SseEvent.with_type("aurora-canvas", canvas_html)); string stats_html = build_stats_html(); yield stream.send_event(new SseEvent.with_type("aurora-stats", stats_html)); } /// Build the canvas HTML with all wave components private string build_canvas_html() { var builder = new StringBuilder(); int wave_num = 0; foreach (var wave in aurora_state.get_waves()) { // Build the wave div with inline styles builder.append(@"
"); wave_num++; } return builder.str; } /// Build the stats HTML private string build_stats_html() { return @"
%.1f km/s
Solar Wind
$(aurora_state.wave_count)
Waves
%.0f%%
Intensity
$(aurora_state.color_mode)
Color Mode
".printf( aurora_state.solar_wind_speed, aurora_state.intensity * 100 ); } }