using Astralis; using Invercargill.DataStructures; using Inversion; namespace Spry { public class ContinuationProvider : SseEndpoint { private Dictionary components = new Dictionary(); private Dictionary active_channels = new Dictionary(); public override uint retry_interval { get { return 500; } } public async override void new_connection(HttpContext http_context, RouteContext route_context, SseStream stream) { var continuation_id = route_context.mapped_parameters.get_or_default("token"); Component component = null; lock(active_channels) { if(components.try_get(continuation_id, out component)) { active_channels[continuation_id] = true; } } try { if(component == null) { yield stream.send_event(new SseEvent.with_type("_spry-close", "404")); } try { yield component.continuation(stream); } catch(Error e) { warning(@"Component $(component.get_type().name()) threw exception in follow up: $(e.message)"); yield stream.send_event(new SseEvent.with_type("_spry-close", "500")); } yield stream.send_event(new SseEvent.with_type("_spry-close", "200")); } catch(Error e) { warning("Failed to send '_spry-close' event: " + e.message); return; } } public string get_continuation_path(Component component) { var id = Uuid.string_random(); components[id] = component; active_channels[id] = false; // Remove component after 60 seconds if no connection has been made Timeout.add_seconds_once(60, () => clean(id)); return @"/_spry/cnu/$id"; } private void clean(string id) { bool active; lock(active_channels){ if(!active_channels.try_get(id, out active)) { components.remove(id); return; } if(!active) { components.remove(id); active_channels.remove(id); } } } } }