using System.Collections.Specialized; using System.Text.Json; using Serilog; using Websocket.Client; namespace Discord.API; public class GatewayClient : AbstractGateway { private const int ApiVersion = 10; private string ApiKey; private ulong? Sequence = null; public GatewayClient(string url, string api_key, TimeProvider time_provider) : this(url, api_key, time_provider, uri => new WebsocketClient(uri)) { } internal GatewayClient(string url, string api_key, TimeProvider time_provider, Func websocket_client_factory) : base(websocket_client_factory.Invoke(BuildUrl(url)), time_provider){ this.ApiKey=api_key; Log.Debug("GATEWAY: Created new gateway, with url: {url}", WebsocketClient.Url); } private static Uri BuildUrl(string url){ UriBuilder uriBuilder = new(url); NameValueCollection query = System.Web.HttpUtility.ParseQueryString(""); query.Add("v", ApiVersion.ToString()); query.Add("encoding", "json"); uriBuilder.Query=query.ToString(); return uriBuilder.Uri; } protected override void DisconnectHandler(DisconnectionInfo info){ Log.Information("GATEWAY: Disconnected. Type: {DisconnectionType}", info.Type); } protected override void ReconnectHandler(ReconnectionInfo info){ Log.Information("GATEWAY: (Re)Connected to server. Url: {url}, Type: {Type}", WebsocketClient.Url, info.Type); } protected override void MessageReceivedHandler(ResponseMessage msg){ if(msg.MessageType != System.Net.WebSockets.WebSocketMessageType.Text) return; try{ GatewayPacket packet = JsonSerializer.Deserialize(msg.Text!, SourceGenerationContext.Default.GatewayPacket) ?? throw new Exception("Failed to deserialize packet"); // This can be optimized //TODO Log.Debug("GATEWAY: Packet received"); switch(packet){ case HelloPacket helloPacket: HelloPacketHandler(helloPacket); break; case HeartbeatAckPacket: HeartbeatAckHandler(); break; default: Log.Debug("GATEWAY: Packet not handled"); break; } }catch(Exception ex){ Log.Warning(ex, "GATEWAY: Error processing gateway event"); } } private void HelloPacketHandler(HelloPacket packet){ StartHeartbeat((int)packet.Data.HeartbeatInterval); Log.Debug("GATEWAY: Hello packet received, heartbeat interval: {heartbeat_ms}", packet.Data.HeartbeatInterval); } private void HeartbeatAckHandler(){ Log.Debug("GATEWAY: Heartbeat ACK received"); Log.Information("GATEWAY: Heartbeat ping: {ping_ms} ms", HeartbeatAckReceived()); } protected override Task SendHeartbeat() { HeartbeatPacket packet = new(){ Sequence = Sequence }; WebsocketClient.Send(JsonSerializer.Serialize(packet, SourceGenerationContext.Default.HeartbeatPacket)); Log.Debug("GATEWAY: Heartbeat sent"); return Task.CompletedTask; } protected override Task MissingHeartbeatAckHandler() { _ = WebsocketClient.Reconnect(); Log.Debug("GATEWAY: Heartbeat ack missed. Reconnecting"); return Task.FromResult(true); } }