using Astralis; using Invercargill; using Invercargill.DataStructures; /// ClockEndpoint is a singleton SSE endpoint that broadcasts the current time /// to all connected clients every second. /// /// It demonstrates: /// - Singleton pattern for SSE endpoints (shared state across connections) /// - Implementing retry_interval property /// - Using new_connection for initial welcome message /// - Public method to broadcast events public class ClockEndpoint : SseEndpoint { private int connection_counter = 0; private Mutex counter_mutex = Mutex(); /// Retry interval: clients should wait 3 seconds before reconnecting public override uint retry_interval { get { return 3000; } } /// Called when a new client connects - send welcome message public override async void new_connection(SseStream stream) { // Assign a unique connection ID int connection_id; counter_mutex.lock(); connection_id = ++connection_counter; counter_mutex.unlock(); print(@"SSE client connected (connection #$connection_id, total: $(get_open_streams().length))\n"); // Send welcome message try { yield stream.send_event(new SseEvent.with_type("connected", @"You are connection #$connection_id")); } catch (Error e) { print(@"Failed to send welcome: $(e.message)\n"); } // Listen for disconnection stream.disconnected.connect(() => { print(@"SSE client disconnected (connection #$connection_id)\n"); }); } /// Public method to broadcast the current time to all connected clients. /// This can be called from anywhere (e.g., from another endpoint or service). public async void broadcast_time() { var now = new DateTime.now_local(); var time_str = now.format("%H:%M:%S"); var date_str = now.format("%Y-%m-%d"); // Create event with current time var time_event = new SseEvent.with_type("time", time_str); // Also send a JSON-formatted message var json_data = @"{\"time\":\"$time_str\",\"date\":\"$date_str\"}"; var json_event = new SseEvent.with_type("datetime", json_data); // Broadcast to all connected clients yield broadcast_event(time_event); yield broadcast_event(json_event); } /// Start the broadcast loop. Called externally after construction. public void start_broadcast_loop() { broadcast_loop_iteration.begin(); } private async void broadcast_loop_iteration() { // Broadcast current time yield broadcast_time(); // Schedule next iteration in 1 second Timeout.add(1000, () => { broadcast_loop_iteration.begin(); return false; // Don't repeat, we'll reschedule manually }); } } /// IndexEndpoint serves a simple HTML page that connects to the SSE endpoints class IndexEndpoint : Object, Endpoint { public async HttpResult handle_request(HttpContext http_context, RouteContext route_context) throws Error { var html = """