From 43e54f0042df64d8cf105029dfde47e767f3eed5 Mon Sep 17 00:00:00 2001 From: Brandon Smith Date: Sun, 6 Sep 2015 04:54:37 -0300 Subject: [PATCH] Add basic websocket redirect support --- .../API/Models/TextWebSocketCommands.cs | 17 +++++++-- .../API/Models/TextWebSocketEvents.cs | 6 ++++ src/Discord.Net/DiscordDataSocket.cs | 35 +++++++++++++++++-- src/Discord.Net/DiscordVoiceSocket.cs | 2 +- src/Discord.Net/DiscordWebSocket.cs | 7 ++-- 5 files changed, 58 insertions(+), 9 deletions(-) diff --git a/src/Discord.Net/API/Models/TextWebSocketCommands.cs b/src/Discord.Net/API/Models/TextWebSocketCommands.cs index a1e8edcdd..552f1a38e 100644 --- a/src/Discord.Net/API/Models/TextWebSocketCommands.cs +++ b/src/Discord.Net/API/Models/TextWebSocketCommands.cs @@ -15,9 +15,11 @@ namespace Discord.API.Models { [JsonProperty(PropertyName = "op")] public int Operation; - [JsonProperty(PropertyName = "t")] + [JsonProperty(PropertyName = "t", NullValueHandling = NullValueHandling.Ignore)] public string Type; - [JsonProperty(PropertyName = "d")] + [JsonProperty(PropertyName = "s", NullValueHandling = NullValueHandling.Ignore)] + public int? Sequence; + [JsonProperty(PropertyName = "d", NullValueHandling = NullValueHandling.Ignore)] public object Payload; } internal abstract class WebSocketMessage : WebSocketMessage @@ -78,5 +80,16 @@ namespace Discord.API.Models public string SelfDeaf; } } + public sealed class Resume : WebSocketMessage + { + public Resume() : base(6) { } + public class Data + { + [JsonProperty(PropertyName = "session_id")] + public string SessionId; + [JsonProperty(PropertyName = "seq")] + public int Sequence; + } + } } } diff --git a/src/Discord.Net/API/Models/TextWebSocketEvents.cs b/src/Discord.Net/API/Models/TextWebSocketEvents.cs index 1100d6d60..b5497fb5d 100644 --- a/src/Discord.Net/API/Models/TextWebSocketEvents.cs +++ b/src/Discord.Net/API/Models/TextWebSocketEvents.cs @@ -36,6 +36,12 @@ namespace Discord.API.Models public int HeartbeatInterval; } + public sealed class Redirect + { + [JsonProperty(PropertyName = "url")] + public string Url; + } + //Servers public sealed class GuildCreate : ExtendedServerInfo { } public sealed class GuildUpdate : ServerInfo { } diff --git a/src/Discord.Net/DiscordDataSocket.cs b/src/Discord.Net/DiscordDataSocket.cs index b84c09a33..4430867ca 100644 --- a/src/Discord.Net/DiscordDataSocket.cs +++ b/src/Discord.Net/DiscordDataSocket.cs @@ -12,6 +12,8 @@ namespace Discord internal sealed partial class DiscordDataSocket : DiscordWebSocket { private readonly ManualResetEventSlim _connectWaitOnLogin, _connectWaitOnLogin2; + private string _lastSession, _redirectServer; + private int _lastSeq; public DiscordDataSocket(DiscordClient client, int timeout, int interval, bool isDebug) : base(client, timeout, interval, isDebug) @@ -20,10 +22,13 @@ namespace Discord _connectWaitOnLogin2 = new ManualResetEventSlim(false); } - public override Task ConnectAsync(string url) + public override async Task ConnectAsync(string url) { - BeginConnect(); - return base.ConnectAsync(url); + _lastSeq = 0; + _lastSession = null; + _redirectServer = null; + await BeginConnect(); + await base.ConnectAsync(url); } public async Task Login(string token) { @@ -65,6 +70,8 @@ namespace Discord protected override Task ProcessMessage(string json) { var msg = JsonConvert.DeserializeObject(json); + if (msg.Sequence.HasValue) + _lastSeq = msg.Sequence.Value; switch (msg.Operation) { case 0: @@ -72,6 +79,7 @@ namespace Discord if (msg.Type == "READY") { var payload = (msg.Payload as JToken).ToObject(); + _lastSession = payload.SessionId; _heartbeatInterval = payload.HeartbeatInterval; QueueMessage(new TextWebSocketCommands.UpdateStatus()); //QueueMessage(GetKeepAlive()); @@ -82,6 +90,15 @@ namespace Discord _connectWaitOnLogin2.Set(); //Post-Event } break; + case 7: + { + var payload = (msg.Payload as JToken).ToObject(); + if (_isDebug) + RaiseOnDebugMessage(DebugMessageType.Connection, $"Redirected to {payload.Url}."); + _host = payload.Url; + DisconnectInternal(new Exception("Server is redirecting."), true); + } + break; default: if (_isDebug) RaiseOnDebugMessage(DebugMessageType.WebSocketUnknownOpCode, "Unknown Opcode: " + msg.Operation); @@ -107,5 +124,17 @@ namespace Discord var joinVoice = new TextWebSocketCommands.JoinVoice(); QueueMessage(joinVoice); } + + protected override void OnConnect() + { + if (_redirectServer != null) + { + var resumeMsg = new TextWebSocketCommands.Resume(); + resumeMsg.Payload.SessionId = _lastSession; + resumeMsg.Payload.Sequence = _lastSeq; + SendMessage(resumeMsg, _disconnectToken.Token); + } + _redirectServer = null; + } } } diff --git a/src/Discord.Net/DiscordVoiceSocket.cs b/src/Discord.Net/DiscordVoiceSocket.cs index cd205c1c3..190cc2229 100644 --- a/src/Discord.Net/DiscordVoiceSocket.cs +++ b/src/Discord.Net/DiscordVoiceSocket.cs @@ -128,7 +128,7 @@ namespace Discord public new async Task BeginConnect() { - base.BeginConnect(); + await base.BeginConnect(); var cancelToken = _disconnectToken.Token; await Task.Yield(); diff --git a/src/Discord.Net/DiscordWebSocket.cs b/src/Discord.Net/DiscordWebSocket.cs index b8d4bca62..8a5acdc0e 100644 --- a/src/Discord.Net/DiscordWebSocket.cs +++ b/src/Discord.Net/DiscordWebSocket.cs @@ -40,14 +40,15 @@ namespace Discord _sendQueue = new ConcurrentQueue(); } - protected void BeginConnect() + protected virtual async Task BeginConnect() { + await DisconnectAsync(); _disconnectToken = new CancellationTokenSource(); _disconnectReason = null; } public virtual async Task ConnectAsync(string url) { - await DisconnectAsync(); + //await DisconnectAsync(); var cancelToken = _disconnectToken.Token; @@ -152,7 +153,7 @@ namespace Discord try { result = await _webSocket.ReceiveAsync(new ArraySegment(buffer), cancelToken); - } + } catch (Win32Exception ex) when (ex.HResult == HR_TIMEOUT) {