diff --git a/src/Discord.Net.Commands.Net45/Discord.Net.Commands.csproj b/src/Discord.Net.Commands.Net45/Discord.Net.Commands.csproj
index 7798e056b..04f5487bc 100644
--- a/src/Discord.Net.Commands.Net45/Discord.Net.Commands.csproj
+++ b/src/Discord.Net.Commands.Net45/Discord.Net.Commands.csproj
@@ -7,7 +7,7 @@
{1B5603B4-6F8F-4289-B945-7BAAE523D740}
Library
Properties
- Discord.Net
+ Discord
Discord.Net.Commands
512
v4.5
diff --git a/src/Discord.Net.Net45/Discord.Net.csproj b/src/Discord.Net.Net45/Discord.Net.csproj
index 4bbf8e44a..ffcc5fd0f 100644
--- a/src/Discord.Net.Net45/Discord.Net.csproj
+++ b/src/Discord.Net.Net45/Discord.Net.csproj
@@ -7,7 +7,7 @@
{8D71A857-879A-4A10-859E-5FF824ED6688}
Library
Properties
- Discord.Net
+ Discord
Discord.Net
512
v4.5
@@ -159,6 +159,9 @@
Net\API\Endpoints.cs
+
+ Net\API\HttpException.cs
+
Net\API\Requests.cs
@@ -177,9 +180,6 @@
Net\API\RestClient.SharpRest.cs
-
- Net\HttpException.cs
-
Net\WebSockets\Commands.cs
@@ -187,7 +187,7 @@
Net\WebSockets\DataWebSocket.cs
- Net\DataWebSockets.Events.cs
+ Net\WebSockets\DataWebSockets.Events.cs
Net\WebSockets\Events.cs
@@ -216,6 +216,9 @@
Net\WebSockets\WebSocketMessage.cs
+
+ TimeoutException.cs
+
diff --git a/src/Discord.Net/DiscordClient.cs b/src/Discord.Net/DiscordClient.cs
index bdd0a13e0..9e0ad48d3 100644
--- a/src/Discord.Net/DiscordClient.cs
+++ b/src/Discord.Net/DiscordClient.cs
@@ -95,7 +95,7 @@ namespace Discord
_api = new DiscordAPIClient(_config.LogLevel);
_dataSocket = new DataWebSocket(this);
_dataSocket.Connected += (s, e) => { if (_state == (int)DiscordClientState.Connecting) CompleteConnect(); };
- _dataSocket.Disconnected += async (s, e) => { RaiseDisconnected(e); if (e.WasUnexpected) await Reconnect(_token); };
+ _dataSocket.Disconnected += async (s, e) => { RaiseDisconnected(e); if (e.WasUnexpected) await _dataSocket.Login(_token); };
if (_config.EnableVoice)
{
_voiceSocket = new VoiceWebSocket(this);
@@ -112,7 +112,7 @@ namespace Discord
}
RaiseVoiceDisconnected(e);
if (e.WasUnexpected)
- await _voiceSocket.Reconnect(_cancelToken);
+ await _voiceSocket.Reconnect();
};
_voiceSocket.IsSpeaking += (s, e) =>
{
@@ -292,6 +292,8 @@ namespace Discord
}
}
break;
+ case "RESUMED":
+ break;
//Servers
case "GUILD_CREATE":
@@ -358,7 +360,7 @@ namespace Discord
var user = _users.GetOrAdd(data.User.Id);
user.Update(data.User);
if (_config.TrackActivity)
- user.UpdateActivity(DateTime.UtcNow);
+ user.UpdateActivity();
var member = _members.GetOrAdd(data.User.Id, data.GuildId);
member.Update(data);
RaiseUserAdded(member);
@@ -536,7 +538,7 @@ namespace Discord
if (user != null)
{
if (_config.TrackActivity)
- user.UpdateActivity(DateTime.UtcNow);
+ user.UpdateActivity();
if (channel != null)
RaiseUserIsTyping(user, channel);
}
@@ -550,8 +552,8 @@ namespace Discord
var server = _servers[data.GuildId];
if (_config.EnableVoice)
{
- string host = "wss://" + data.Endpoint.Split(':')[0];
- await _voiceSocket.Login(host, data.GuildId, _currentUserId, _dataSocket.SessionId, data.Token, _cancelToken).ConfigureAwait(false);
+ _voiceSocket.Host = "wss://" + data.Endpoint.Split(':')[0];
+ await _voiceSocket.Login(data.GuildId, _currentUserId, _dataSocket.SessionId, data.Token, _cancelToken).ConfigureAwait(false);
}
}
break;
@@ -582,11 +584,6 @@ namespace Discord
};
}
- private void _dataSocket_Connected(object sender, EventArgs e)
- {
- throw new NotImplementedException();
- }
-
//Connection
/// Connects to the Discord server with the provided token.
public async Task Connect(string token)
@@ -594,46 +591,54 @@ namespace Discord
if (_state != (int)DiscordClientState.Disconnected)
await Disconnect().ConfigureAwait(false);
- if (_config.LogLevel >= LogMessageSeverity.Verbose)
- RaiseOnLog(LogMessageSeverity.Verbose, LogMessageSource.Authentication, $"Using cached token.");
-
- await ConnectInternal(token).ConfigureAwait(false);
- }
+ await ConnectInternal(token)
+ .Timeout(_config.ConnectionTimeout)
+ .ConfigureAwait(false);
+ }
/// Connects to the Discord server with the provided email and password.
/// Returns a token for future connections.
public async Task Connect(string email, string password)
{
if (_state != (int)DiscordClientState.Disconnected)
await Disconnect().ConfigureAwait(false);
-
- var response = await _api.Login(email, password).ConfigureAwait(false);
- if (_config.LogLevel >= LogMessageSeverity.Verbose)
- RaiseOnLog(LogMessageSeverity.Verbose, LogMessageSource.Authentication, "Login successful, got token.");
-
- return await ConnectInternal(response.Token).ConfigureAwait(false);
- }
- private Task Reconnect(string token)
- {
- if (_config.LogLevel >= LogMessageSeverity.Verbose)
- RaiseOnLog(LogMessageSeverity.Verbose, LogMessageSource.Authentication, $"Using cached token.");
- return ConnectInternal(token);
+ string token;
+ try
+ {
+ var cancelToken = new CancellationTokenSource();
+ cancelToken.CancelAfter(5000);
+ _api.CancelToken = cancelToken.Token;
+ var response = await _api.Login(email, password).ConfigureAwait(false);
+ token = response.Token;
+ if (_config.LogLevel >= LogMessageSeverity.Verbose)
+ RaiseOnLog(LogMessageSeverity.Verbose, LogMessageSource.Authentication, "Login successful, got token.");
+ }
+ catch (TaskCanceledException) { throw new TimeoutException(); }
+
+ return await ConnectInternal(token)
+ .Timeout(_config.ConnectionTimeout)
+ .ConfigureAwait(false);
}
private async Task ConnectInternal(string token)
{
- try
+ try
{
_disconnectedEvent.Reset();
_cancelTokenSource = new CancellationTokenSource();
_cancelToken = _cancelTokenSource.Token;
- _state = (int)DiscordClientState.Connecting;
-
_api.Token = token;
+ _api.CancelToken = _cancelToken;
+ _token = token;
+ _state = (int)DiscordClientState.Connecting;
+
string url = (await _api.GetWebSocketEndpoint().ConfigureAwait(false)).Url;
+ url = "wss://gateway-besaid.discord.gg/";
if (_config.LogLevel >= LogMessageSeverity.Verbose)
RaiseOnLog(LogMessageSeverity.Verbose, LogMessageSource.Authentication, $"Websocket endpoint: {url}");
-
- await _dataSocket.Login(url, token, _cancelToken).ConfigureAwait(false);
+
+ _dataSocket.Host = url;
+ _dataSocket.ParentCancelToken = _cancelToken;
+ await _dataSocket.Login(token).ConfigureAwait(false);
_runTask = RunTasks();
@@ -641,8 +646,7 @@ namespace Discord
{
//Cancel if either Disconnect is called, data socket errors or timeout is reached
var cancelToken = CancellationTokenSource.CreateLinkedTokenSource(_cancelToken, _dataSocket.CancelToken).Token;
- if (!_connectedEvent.Wait(_config.ConnectionTimeout, cancelToken))
- throw new Exception("Operation timed out.");
+ _connectedEvent.Wait(cancelToken);
}
catch (OperationCanceledException)
{
@@ -656,6 +660,7 @@ namespace Discord
}
catch
{
+
await Disconnect().ConfigureAwait(false);
throw;
}
diff --git a/src/Discord.Net/Helpers/TaskHelper.cs b/src/Discord.Net/Helpers/TaskHelper.cs
index 9c2fd2c77..27ff1c9ef 100644
--- a/src/Discord.Net/Helpers/TaskHelper.cs
+++ b/src/Discord.Net/Helpers/TaskHelper.cs
@@ -13,5 +13,26 @@ namespace Discord.Helpers
CompletedTask = Task.Delay(0);
#endif
}
+
+ public static async Task Timeout(this Task self, int milliseconds)
+ {
+ Task timeoutTask = Task.Delay(milliseconds);
+ Task finishedTask = await Task.WhenAny(self, timeoutTask);
+ if (finishedTask == timeoutTask)
+ {
+ throw new TimeoutException();
+ }
+ else
+ await self;
+ }
+ public static async Task Timeout(this Task self, int milliseconds)
+ {
+ Task timeoutTask = Task.Delay(milliseconds);
+ Task finishedTask = await Task.WhenAny(self, timeoutTask).ConfigureAwait(false);
+ if (finishedTask == timeoutTask)
+ throw new TimeoutException();
+ else
+ return await self.ConfigureAwait(false);
+ }
}
}
diff --git a/src/Discord.Net/Net/API/DiscordAPIClient.cs b/src/Discord.Net/Net/API/DiscordAPIClient.cs
index 426505634..b1e4b2c4b 100644
--- a/src/Discord.Net/Net/API/DiscordAPIClient.cs
+++ b/src/Discord.Net/Net/API/DiscordAPIClient.cs
@@ -1,4 +1,5 @@
using System;
+using System.Threading;
using System.Threading.Tasks;
namespace Discord.Net.API
@@ -21,6 +22,12 @@ namespace Discord.Net.API
get { return _token; }
set { _token = value; _rest.SetToken(value); }
}
+ private CancellationToken _cancelToken;
+ public CancellationToken CancelToken
+ {
+ get { return _cancelToken; }
+ set { _cancelToken = value; _rest.SetCancelToken(value); }
+ }
//Auth
public Task GetWebSocketEndpoint()
@@ -191,5 +198,15 @@ namespace Discord.Net.API
var request = new Requests.ChangeAvatar { Avatar = $"data:{type},/9j/{base64}", CurrentEmail = currentEmail, CurrentPassword = currentPassword };
return _rest.Patch(Endpoints.UserMe, request);
}
+
+ //Other
+ /*public Task GetUnresolvedIncidents()
+ {
+ return _rest.Get(Endpoints.StatusUnresolvedMaintenance);
+ }
+ public Task GetActiveIncidents()
+ {
+ return _rest.Get(Endpoints.StatusActiveMaintenance);
+ }*/
}
}
diff --git a/src/Discord.Net/Net/API/Endpoints.cs b/src/Discord.Net/Net/API/Endpoints.cs
index 36306af68..048b75c98 100644
--- a/src/Discord.Net/Net/API/Endpoints.cs
+++ b/src/Discord.Net/Net/API/Endpoints.cs
@@ -1,7 +1,8 @@
namespace Discord.Net.API
{
internal static class Endpoints
- {
+ {
+ public const string BaseStatusApi = "https://status.discordapp.com/api/v2/";
public const string BaseApi = "https://discordapp.com/api/";
//public const string Track = "track";
public const string Gateway = "gateway";
@@ -41,5 +42,8 @@
public const string Voice = "voice";
public const string VoiceRegions = "voice/regions";
public const string VoiceIce = "voice/ice";
- }
+
+ public const string StatusActiveMaintenance = "scheduled-maintenances/active.json";
+ public const string StatusUnresolvedMaintenance = "scheduled-maintenances/unresolved.json";
+ }
}
diff --git a/src/Discord.Net/Net/HttpException.cs b/src/Discord.Net/Net/API/HttpException.cs
similarity index 91%
rename from src/Discord.Net/Net/HttpException.cs
rename to src/Discord.Net/Net/API/HttpException.cs
index 88bbcee3d..6530b109f 100644
--- a/src/Discord.Net/Net/HttpException.cs
+++ b/src/Discord.Net/Net/API/HttpException.cs
@@ -1,7 +1,7 @@
using System;
using System.Net;
-namespace Discord.Net
+namespace Discord.Net.API
{
public class HttpException : Exception
{
diff --git a/src/Discord.Net/Net/WebSockets/DataWebSocket.cs b/src/Discord.Net/Net/WebSockets/DataWebSocket.cs
index 883401dcb..49da7bdb0 100644
--- a/src/Discord.Net/Net/WebSockets/DataWebSocket.cs
+++ b/src/Discord.Net/Net/WebSockets/DataWebSocket.cs
@@ -7,8 +7,7 @@ using System.Threading.Tasks;
namespace Discord.Net.WebSockets
{
internal partial class DataWebSocket : WebSocket
- {
- private string _redirectServer;
+ {
private int _lastSeq;
public string SessionId => _sessionId;
@@ -18,29 +17,25 @@ namespace Discord.Net.WebSockets
: base(client)
{
}
-
- public async Task Login(string host, string token, CancellationToken cancelToken)
+
+ public async Task Login(string token)
{
- await base.Connect(host, cancelToken);
+ await Connect();
Commands.Login msg = new Commands.Login();
msg.Payload.Token = token;
msg.Payload.Properties["$device"] = "Discord.Net";
QueueMessage(msg);
}
-
- protected override Task[] Run()
+ private async Task Redirect(string server)
{
- //Send resume session if we were transferred
- if (_redirectServer != null)
- {
- var resumeMsg = new Commands.Resume();
- resumeMsg.Payload.SessionId = _sessionId;
- resumeMsg.Payload.Sequence = _lastSeq;
- QueueMessage(resumeMsg);
- _redirectServer = null;
- }
- return base.Run();
+ await DisconnectInternal(isUnexpected: false);
+ await Connect();
+
+ var resumeMsg = new Commands.Resume();
+ resumeMsg.Payload.SessionId = _sessionId;
+ resumeMsg.Payload.Sequence = _lastSeq;
+ QueueMessage(resumeMsg);
}
protected override async Task ProcessMessage(string json)
@@ -54,27 +49,31 @@ namespace Discord.Net.WebSockets
case 0:
{
JToken token = msg.Payload as JToken;
- if (msg.Type == "READY")
+ if (msg.Type == "READY")
{
var payload = token.ToObject();
_sessionId = payload.SessionId;
_heartbeatInterval = payload.HeartbeatInterval;
QueueMessage(new Commands.UpdateStatus());
}
+ else if (msg.Type == "RESUMED")
+ {
+ var payload = token.ToObject();
+ _heartbeatInterval = payload.HeartbeatInterval;
+ QueueMessage(new Commands.UpdateStatus());
+ }
RaiseReceivedEvent(msg.Type, token);
- if (msg.Type == "READY")
+ if (msg.Type == "READY" || msg.Type == "RESUMED")
CompleteConnect();
- /*if (_logLevel >= LogMessageSeverity.Info)
- RaiseOnLog(LogMessageSeverity.Info, "Got Event: " + msg.Type);*/
}
break;
case 7: //Redirect
{
var payload = (msg.Payload as JToken).ToObject();
- _host = payload.Url;
+ Host = payload.Url;
if (_logLevel >= LogMessageSeverity.Info)
RaiseOnLog(LogMessageSeverity.Info, "Redirected to " + payload.Url);
- await DisconnectInternal(new Exception("Server is redirecting."), true);
+ await Redirect(payload.Url);
}
break;
default:
diff --git a/src/Discord.Net/Net/WebSockets/Events.cs b/src/Discord.Net/Net/WebSockets/Events.cs
index 98f52334d..e1cd54184 100644
--- a/src/Discord.Net/Net/WebSockets/Events.cs
+++ b/src/Discord.Net/Net/WebSockets/Events.cs
@@ -36,6 +36,11 @@ namespace Discord.Net.WebSockets
[JsonProperty(PropertyName = "heartbeat_interval")]
public int HeartbeatInterval;
}
+ public sealed class Resumed
+ {
+ [JsonProperty(PropertyName = "heartbeat_interval")]
+ public int HeartbeatInterval;
+ }
public sealed class Redirect
{
diff --git a/src/Discord.Net/Net/WebSockets/VoiceWebSocket.cs b/src/Discord.Net/Net/WebSockets/VoiceWebSocket.cs
index 56bbcd83f..51b3c8679 100644
--- a/src/Discord.Net/Net/WebSockets/VoiceWebSocket.cs
+++ b/src/Discord.Net/Net/WebSockets/VoiceWebSocket.cs
@@ -53,13 +53,12 @@ namespace Discord.Net.WebSockets
_targetAudioBufferLength = client.Config.VoiceBufferLength / 20; //20 ms frames
}
- public async Task Login(string host, string serverId, string userId, string sessionId, string token, CancellationToken cancelToken)
+ public async Task Login(string serverId, string userId, string sessionId, string token, CancellationToken cancelToken)
{
if (_serverId == serverId && _userId == userId && _sessionId == sessionId && _token == token)
{
//Adjust the host and tell the system to reconnect
- _host = host;
- await DisconnectInternal(new Exception("Server transfer occurred."));
+ await DisconnectInternal(new Exception("Server transfer occurred."), isUnexpected: false);
return;
}
@@ -68,18 +67,19 @@ namespace Discord.Net.WebSockets
_sessionId = sessionId;
_token = token;
- await Connect(host, cancelToken);
+ await Connect();
}
- public async Task Reconnect(CancellationToken cancelToken)
+ public async Task Reconnect()
{
try
{
- await Task.Delay(_client.Config.ReconnectDelay, cancelToken).ConfigureAwait(false);
+ var cancelToken = ParentCancelToken;
+ await Task.Delay(_client.Config.ReconnectDelay, cancelToken).ConfigureAwait(false);
while (!cancelToken.IsCancellationRequested)
{
try
{
- await Connect(_host, cancelToken).ConfigureAwait(false);
+ await Connect().ConfigureAwait(false);
break;
}
catch (OperationCanceledException) { throw; }
@@ -295,7 +295,7 @@ namespace Discord.Net.WebSockets
var payload = (msg.Payload as JToken).ToObject();
_heartbeatInterval = payload.HeartbeatInterval;
_ssrc = payload.SSRC;
- _endpoint = new IPEndPoint((await Dns.GetHostAddressesAsync(_host.Replace("wss://", "")).ConfigureAwait(false)).FirstOrDefault(), payload.Port);
+ _endpoint = new IPEndPoint((await Dns.GetHostAddressesAsync(Host.Replace("wss://", "")).ConfigureAwait(false)).FirstOrDefault(), payload.Port);
//_mode = payload.Modes.LastOrDefault();
_isEncrypted = !payload.Modes.Contains("plain");
_udp.Connect(_endpoint);
diff --git a/src/Discord.Net/Net/WebSockets/WebSocket.cs b/src/Discord.Net/Net/WebSockets/WebSocket.cs
index 59acefdf5..6fc5e1731 100644
--- a/src/Discord.Net/Net/WebSockets/WebSocket.cs
+++ b/src/Discord.Net/Net/WebSockets/WebSocket.cs
@@ -38,7 +38,8 @@ namespace Discord.Net.WebSockets
protected readonly DiscordClient _client;
protected readonly LogMessageSeverity _logLevel;
- protected string _host;
+ public string Host { get; set; }
+
protected int _loginTimeout, _heartbeatInterval;
private DateTime _lastHeartbeat;
private Task _runTask;
@@ -49,6 +50,7 @@ namespace Discord.Net.WebSockets
protected ExceptionDispatchInfo _disconnectReason;
private bool _wasDisconnectUnexpected;
+ public CancellationToken ParentCancelToken { get; set; }
public CancellationToken CancelToken => _cancelToken;
private CancellationTokenSource _cancelTokenSource;
protected CancellationToken _cancelToken;
@@ -69,22 +71,24 @@ namespace Discord.Net.WebSockets
};
}
- protected virtual async Task Connect(string host, CancellationToken cancelToken)
+ protected virtual async Task Connect()
{
if (_state != (int)WebSocketState.Disconnected)
throw new InvalidOperationException("Client is already connected or connecting to the server.");
- try
+ try
{
await Disconnect().ConfigureAwait(false);
_state = (int)WebSocketState.Connecting;
_cancelTokenSource = new CancellationTokenSource();
- _cancelToken = CancellationTokenSource.CreateLinkedTokenSource(_cancelTokenSource.Token, cancelToken).Token;
+ if (ParentCancelToken != null)
+ _cancelToken = CancellationTokenSource.CreateLinkedTokenSource(_cancelTokenSource.Token, ParentCancelToken).Token;
+ else
+ _cancelToken = _cancelTokenSource.Token;
- await _engine.Connect(host, _cancelToken).ConfigureAwait(false);
- _host = host;
+ await _engine.Connect(Host, _cancelToken).ConfigureAwait(false);
_lastHeartbeat = DateTime.UtcNow;
_runTask = RunTasks();
diff --git a/src/Discord.Net/TimeoutException.cs b/src/Discord.Net/TimeoutException.cs
new file mode 100644
index 000000000..5c35374c7
--- /dev/null
+++ b/src/Discord.Net/TimeoutException.cs
@@ -0,0 +1,16 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Discord
+{
+ public sealed class TimeoutException : Exception
+ {
+ internal TimeoutException()
+ : base("An operation has timed out.")
+ {
+ }
+ }
+}