@@ -7,7 +7,7 @@ | |||||
<ProjectGuid>{1B5603B4-6F8F-4289-B945-7BAAE523D740}</ProjectGuid> | <ProjectGuid>{1B5603B4-6F8F-4289-B945-7BAAE523D740}</ProjectGuid> | ||||
<OutputType>Library</OutputType> | <OutputType>Library</OutputType> | ||||
<AppDesignerFolder>Properties</AppDesignerFolder> | <AppDesignerFolder>Properties</AppDesignerFolder> | ||||
<RootNamespace>Discord.Net</RootNamespace> | |||||
<RootNamespace>Discord</RootNamespace> | |||||
<AssemblyName>Discord.Net.Commands</AssemblyName> | <AssemblyName>Discord.Net.Commands</AssemblyName> | ||||
<FileAlignment>512</FileAlignment> | <FileAlignment>512</FileAlignment> | ||||
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion> | <TargetFrameworkVersion>v4.5</TargetFrameworkVersion> | ||||
@@ -7,7 +7,7 @@ | |||||
<ProjectGuid>{8D71A857-879A-4A10-859E-5FF824ED6688}</ProjectGuid> | <ProjectGuid>{8D71A857-879A-4A10-859E-5FF824ED6688}</ProjectGuid> | ||||
<OutputType>Library</OutputType> | <OutputType>Library</OutputType> | ||||
<AppDesignerFolder>Properties</AppDesignerFolder> | <AppDesignerFolder>Properties</AppDesignerFolder> | ||||
<RootNamespace>Discord.Net</RootNamespace> | |||||
<RootNamespace>Discord</RootNamespace> | |||||
<AssemblyName>Discord.Net</AssemblyName> | <AssemblyName>Discord.Net</AssemblyName> | ||||
<FileAlignment>512</FileAlignment> | <FileAlignment>512</FileAlignment> | ||||
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion> | <TargetFrameworkVersion>v4.5</TargetFrameworkVersion> | ||||
@@ -159,6 +159,9 @@ | |||||
<Compile Include="..\Discord.Net\Net\API\Endpoints.cs"> | <Compile Include="..\Discord.Net\Net\API\Endpoints.cs"> | ||||
<Link>Net\API\Endpoints.cs</Link> | <Link>Net\API\Endpoints.cs</Link> | ||||
</Compile> | </Compile> | ||||
<Compile Include="..\Discord.Net\Net\API\HttpException.cs"> | |||||
<Link>Net\API\HttpException.cs</Link> | |||||
</Compile> | |||||
<Compile Include="..\Discord.Net\Net\API\Requests.cs"> | <Compile Include="..\Discord.Net\Net\API\Requests.cs"> | ||||
<Link>Net\API\Requests.cs</Link> | <Link>Net\API\Requests.cs</Link> | ||||
</Compile> | </Compile> | ||||
@@ -177,9 +180,6 @@ | |||||
<Compile Include="..\Discord.Net\Net\API\RestClient.SharpRest.cs"> | <Compile Include="..\Discord.Net\Net\API\RestClient.SharpRest.cs"> | ||||
<Link>Net\API\RestClient.SharpRest.cs</Link> | <Link>Net\API\RestClient.SharpRest.cs</Link> | ||||
</Compile> | </Compile> | ||||
<Compile Include="..\Discord.Net\Net\HttpException.cs"> | |||||
<Link>Net\HttpException.cs</Link> | |||||
</Compile> | |||||
<Compile Include="..\Discord.Net\Net\WebSockets\Commands.cs"> | <Compile Include="..\Discord.Net\Net\WebSockets\Commands.cs"> | ||||
<Link>Net\WebSockets\Commands.cs</Link> | <Link>Net\WebSockets\Commands.cs</Link> | ||||
</Compile> | </Compile> | ||||
@@ -187,7 +187,7 @@ | |||||
<Link>Net\WebSockets\DataWebSocket.cs</Link> | <Link>Net\WebSockets\DataWebSocket.cs</Link> | ||||
</Compile> | </Compile> | ||||
<Compile Include="..\Discord.Net\Net\WebSockets\DataWebSockets.Events.cs"> | <Compile Include="..\Discord.Net\Net\WebSockets\DataWebSockets.Events.cs"> | ||||
<Link>Net\DataWebSockets.Events.cs</Link> | |||||
<Link>Net\WebSockets\DataWebSockets.Events.cs</Link> | |||||
</Compile> | </Compile> | ||||
<Compile Include="..\Discord.Net\Net\WebSockets\Events.cs"> | <Compile Include="..\Discord.Net\Net\WebSockets\Events.cs"> | ||||
<Link>Net\WebSockets\Events.cs</Link> | <Link>Net\WebSockets\Events.cs</Link> | ||||
@@ -216,6 +216,9 @@ | |||||
<Compile Include="..\Discord.Net\Net\WebSockets\WebSocketMessage.cs"> | <Compile Include="..\Discord.Net\Net\WebSockets\WebSocketMessage.cs"> | ||||
<Link>Net\WebSockets\WebSocketMessage.cs</Link> | <Link>Net\WebSockets\WebSocketMessage.cs</Link> | ||||
</Compile> | </Compile> | ||||
<Compile Include="..\Discord.Net\TimeoutException.cs"> | |||||
<Link>TimeoutException.cs</Link> | |||||
</Compile> | |||||
<Compile Include="Properties\AssemblyInfo.cs" /> | <Compile Include="Properties\AssemblyInfo.cs" /> | ||||
</ItemGroup> | </ItemGroup> | ||||
<ItemGroup /> | <ItemGroup /> | ||||
@@ -95,7 +95,7 @@ namespace Discord | |||||
_api = new DiscordAPIClient(_config.LogLevel); | _api = new DiscordAPIClient(_config.LogLevel); | ||||
_dataSocket = new DataWebSocket(this); | _dataSocket = new DataWebSocket(this); | ||||
_dataSocket.Connected += (s, e) => { if (_state == (int)DiscordClientState.Connecting) CompleteConnect(); }; | _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) | if (_config.EnableVoice) | ||||
{ | { | ||||
_voiceSocket = new VoiceWebSocket(this); | _voiceSocket = new VoiceWebSocket(this); | ||||
@@ -112,7 +112,7 @@ namespace Discord | |||||
} | } | ||||
RaiseVoiceDisconnected(e); | RaiseVoiceDisconnected(e); | ||||
if (e.WasUnexpected) | if (e.WasUnexpected) | ||||
await _voiceSocket.Reconnect(_cancelToken); | |||||
await _voiceSocket.Reconnect(); | |||||
}; | }; | ||||
_voiceSocket.IsSpeaking += (s, e) => | _voiceSocket.IsSpeaking += (s, e) => | ||||
{ | { | ||||
@@ -292,6 +292,8 @@ namespace Discord | |||||
} | } | ||||
} | } | ||||
break; | break; | ||||
case "RESUMED": | |||||
break; | |||||
//Servers | //Servers | ||||
case "GUILD_CREATE": | case "GUILD_CREATE": | ||||
@@ -358,7 +360,7 @@ namespace Discord | |||||
var user = _users.GetOrAdd(data.User.Id); | var user = _users.GetOrAdd(data.User.Id); | ||||
user.Update(data.User); | user.Update(data.User); | ||||
if (_config.TrackActivity) | if (_config.TrackActivity) | ||||
user.UpdateActivity(DateTime.UtcNow); | |||||
user.UpdateActivity(); | |||||
var member = _members.GetOrAdd(data.User.Id, data.GuildId); | var member = _members.GetOrAdd(data.User.Id, data.GuildId); | ||||
member.Update(data); | member.Update(data); | ||||
RaiseUserAdded(member); | RaiseUserAdded(member); | ||||
@@ -536,7 +538,7 @@ namespace Discord | |||||
if (user != null) | if (user != null) | ||||
{ | { | ||||
if (_config.TrackActivity) | if (_config.TrackActivity) | ||||
user.UpdateActivity(DateTime.UtcNow); | |||||
user.UpdateActivity(); | |||||
if (channel != null) | if (channel != null) | ||||
RaiseUserIsTyping(user, channel); | RaiseUserIsTyping(user, channel); | ||||
} | } | ||||
@@ -550,8 +552,8 @@ namespace Discord | |||||
var server = _servers[data.GuildId]; | var server = _servers[data.GuildId]; | ||||
if (_config.EnableVoice) | 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; | break; | ||||
@@ -582,11 +584,6 @@ namespace Discord | |||||
}; | }; | ||||
} | } | ||||
private void _dataSocket_Connected(object sender, EventArgs e) | |||||
{ | |||||
throw new NotImplementedException(); | |||||
} | |||||
//Connection | //Connection | ||||
/// <summary> Connects to the Discord server with the provided token. </summary> | /// <summary> Connects to the Discord server with the provided token. </summary> | ||||
public async Task Connect(string token) | public async Task Connect(string token) | ||||
@@ -594,46 +591,54 @@ namespace Discord | |||||
if (_state != (int)DiscordClientState.Disconnected) | if (_state != (int)DiscordClientState.Disconnected) | ||||
await Disconnect().ConfigureAwait(false); | 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); | |||||
} | |||||
/// <summary> Connects to the Discord server with the provided email and password. </summary> | /// <summary> Connects to the Discord server with the provided email and password. </summary> | ||||
/// <returns> Returns a token for future connections. </returns> | /// <returns> Returns a token for future connections. </returns> | ||||
public async Task<string> Connect(string email, string password) | public async Task<string> Connect(string email, string password) | ||||
{ | { | ||||
if (_state != (int)DiscordClientState.Disconnected) | if (_state != (int)DiscordClientState.Disconnected) | ||||
await Disconnect().ConfigureAwait(false); | 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<string> ConnectInternal(string token) | private async Task<string> ConnectInternal(string token) | ||||
{ | { | ||||
try | |||||
try | |||||
{ | { | ||||
_disconnectedEvent.Reset(); | _disconnectedEvent.Reset(); | ||||
_cancelTokenSource = new CancellationTokenSource(); | _cancelTokenSource = new CancellationTokenSource(); | ||||
_cancelToken = _cancelTokenSource.Token; | _cancelToken = _cancelTokenSource.Token; | ||||
_state = (int)DiscordClientState.Connecting; | |||||
_api.Token = token; | _api.Token = token; | ||||
_api.CancelToken = _cancelToken; | |||||
_token = token; | |||||
_state = (int)DiscordClientState.Connecting; | |||||
string url = (await _api.GetWebSocketEndpoint().ConfigureAwait(false)).Url; | string url = (await _api.GetWebSocketEndpoint().ConfigureAwait(false)).Url; | ||||
url = "wss://gateway-besaid.discord.gg/"; | |||||
if (_config.LogLevel >= LogMessageSeverity.Verbose) | if (_config.LogLevel >= LogMessageSeverity.Verbose) | ||||
RaiseOnLog(LogMessageSeverity.Verbose, LogMessageSource.Authentication, $"Websocket endpoint: {url}"); | 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(); | _runTask = RunTasks(); | ||||
@@ -641,8 +646,7 @@ namespace Discord | |||||
{ | { | ||||
//Cancel if either Disconnect is called, data socket errors or timeout is reached | //Cancel if either Disconnect is called, data socket errors or timeout is reached | ||||
var cancelToken = CancellationTokenSource.CreateLinkedTokenSource(_cancelToken, _dataSocket.CancelToken).Token; | 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) | catch (OperationCanceledException) | ||||
{ | { | ||||
@@ -656,6 +660,7 @@ namespace Discord | |||||
} | } | ||||
catch | catch | ||||
{ | { | ||||
await Disconnect().ConfigureAwait(false); | await Disconnect().ConfigureAwait(false); | ||||
throw; | throw; | ||||
} | } | ||||
@@ -13,5 +13,26 @@ namespace Discord.Helpers | |||||
CompletedTask = Task.Delay(0); | CompletedTask = Task.Delay(0); | ||||
#endif | #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<T> Timeout<T>(this Task<T> 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); | |||||
} | |||||
} | } | ||||
} | } |
@@ -1,4 +1,5 @@ | |||||
using System; | using System; | ||||
using System.Threading; | |||||
using System.Threading.Tasks; | using System.Threading.Tasks; | ||||
namespace Discord.Net.API | namespace Discord.Net.API | ||||
@@ -21,6 +22,12 @@ namespace Discord.Net.API | |||||
get { return _token; } | get { return _token; } | ||||
set { _token = value; _rest.SetToken(value); } | set { _token = value; _rest.SetToken(value); } | ||||
} | } | ||||
private CancellationToken _cancelToken; | |||||
public CancellationToken CancelToken | |||||
{ | |||||
get { return _cancelToken; } | |||||
set { _cancelToken = value; _rest.SetCancelToken(value); } | |||||
} | |||||
//Auth | //Auth | ||||
public Task<Responses.Gateway> GetWebSocketEndpoint() | public Task<Responses.Gateway> GetWebSocketEndpoint() | ||||
@@ -191,5 +198,15 @@ namespace Discord.Net.API | |||||
var request = new Requests.ChangeAvatar { Avatar = $"data:{type},/9j/{base64}", CurrentEmail = currentEmail, CurrentPassword = currentPassword }; | var request = new Requests.ChangeAvatar { Avatar = $"data:{type},/9j/{base64}", CurrentEmail = currentEmail, CurrentPassword = currentPassword }; | ||||
return _rest.Patch<Responses.ChangeProfile>(Endpoints.UserMe, request); | return _rest.Patch<Responses.ChangeProfile>(Endpoints.UserMe, request); | ||||
} | } | ||||
//Other | |||||
/*public Task<Responses.Status> GetUnresolvedIncidents() | |||||
{ | |||||
return _rest.Get<Responses.Status>(Endpoints.StatusUnresolvedMaintenance); | |||||
} | |||||
public Task<Responses.Status> GetActiveIncidents() | |||||
{ | |||||
return _rest.Get<Responses.Status>(Endpoints.StatusActiveMaintenance); | |||||
}*/ | |||||
} | } | ||||
} | } |
@@ -1,7 +1,8 @@ | |||||
namespace Discord.Net.API | namespace Discord.Net.API | ||||
{ | { | ||||
internal static class Endpoints | 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 BaseApi = "https://discordapp.com/api/"; | ||||
//public const string Track = "track"; | //public const string Track = "track"; | ||||
public const string Gateway = "gateway"; | public const string Gateway = "gateway"; | ||||
@@ -41,5 +42,8 @@ | |||||
public const string Voice = "voice"; | public const string Voice = "voice"; | ||||
public const string VoiceRegions = "voice/regions"; | public const string VoiceRegions = "voice/regions"; | ||||
public const string VoiceIce = "voice/ice"; | public const string VoiceIce = "voice/ice"; | ||||
} | |||||
public const string StatusActiveMaintenance = "scheduled-maintenances/active.json"; | |||||
public const string StatusUnresolvedMaintenance = "scheduled-maintenances/unresolved.json"; | |||||
} | |||||
} | } |
@@ -1,7 +1,7 @@ | |||||
using System; | using System; | ||||
using System.Net; | using System.Net; | ||||
namespace Discord.Net | |||||
namespace Discord.Net.API | |||||
{ | { | ||||
public class HttpException : Exception | public class HttpException : Exception | ||||
{ | { |
@@ -7,8 +7,7 @@ using System.Threading.Tasks; | |||||
namespace Discord.Net.WebSockets | namespace Discord.Net.WebSockets | ||||
{ | { | ||||
internal partial class DataWebSocket : WebSocket | internal partial class DataWebSocket : WebSocket | ||||
{ | |||||
private string _redirectServer; | |||||
{ | |||||
private int _lastSeq; | private int _lastSeq; | ||||
public string SessionId => _sessionId; | public string SessionId => _sessionId; | ||||
@@ -18,29 +17,25 @@ namespace Discord.Net.WebSockets | |||||
: base(client) | : 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(); | Commands.Login msg = new Commands.Login(); | ||||
msg.Payload.Token = token; | msg.Payload.Token = token; | ||||
msg.Payload.Properties["$device"] = "Discord.Net"; | msg.Payload.Properties["$device"] = "Discord.Net"; | ||||
QueueMessage(msg); | 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) | protected override async Task ProcessMessage(string json) | ||||
@@ -54,27 +49,31 @@ namespace Discord.Net.WebSockets | |||||
case 0: | case 0: | ||||
{ | { | ||||
JToken token = msg.Payload as JToken; | JToken token = msg.Payload as JToken; | ||||
if (msg.Type == "READY") | |||||
if (msg.Type == "READY") | |||||
{ | { | ||||
var payload = token.ToObject<Events.Ready>(); | var payload = token.ToObject<Events.Ready>(); | ||||
_sessionId = payload.SessionId; | _sessionId = payload.SessionId; | ||||
_heartbeatInterval = payload.HeartbeatInterval; | _heartbeatInterval = payload.HeartbeatInterval; | ||||
QueueMessage(new Commands.UpdateStatus()); | QueueMessage(new Commands.UpdateStatus()); | ||||
} | } | ||||
else if (msg.Type == "RESUMED") | |||||
{ | |||||
var payload = token.ToObject<Events.Resumed>(); | |||||
_heartbeatInterval = payload.HeartbeatInterval; | |||||
QueueMessage(new Commands.UpdateStatus()); | |||||
} | |||||
RaiseReceivedEvent(msg.Type, token); | RaiseReceivedEvent(msg.Type, token); | ||||
if (msg.Type == "READY") | |||||
if (msg.Type == "READY" || msg.Type == "RESUMED") | |||||
CompleteConnect(); | CompleteConnect(); | ||||
/*if (_logLevel >= LogMessageSeverity.Info) | |||||
RaiseOnLog(LogMessageSeverity.Info, "Got Event: " + msg.Type);*/ | |||||
} | } | ||||
break; | break; | ||||
case 7: //Redirect | case 7: //Redirect | ||||
{ | { | ||||
var payload = (msg.Payload as JToken).ToObject<Events.Redirect>(); | var payload = (msg.Payload as JToken).ToObject<Events.Redirect>(); | ||||
_host = payload.Url; | |||||
Host = payload.Url; | |||||
if (_logLevel >= LogMessageSeverity.Info) | if (_logLevel >= LogMessageSeverity.Info) | ||||
RaiseOnLog(LogMessageSeverity.Info, "Redirected to " + payload.Url); | RaiseOnLog(LogMessageSeverity.Info, "Redirected to " + payload.Url); | ||||
await DisconnectInternal(new Exception("Server is redirecting."), true); | |||||
await Redirect(payload.Url); | |||||
} | } | ||||
break; | break; | ||||
default: | default: | ||||
@@ -36,6 +36,11 @@ namespace Discord.Net.WebSockets | |||||
[JsonProperty(PropertyName = "heartbeat_interval")] | [JsonProperty(PropertyName = "heartbeat_interval")] | ||||
public int HeartbeatInterval; | public int HeartbeatInterval; | ||||
} | } | ||||
public sealed class Resumed | |||||
{ | |||||
[JsonProperty(PropertyName = "heartbeat_interval")] | |||||
public int HeartbeatInterval; | |||||
} | |||||
public sealed class Redirect | public sealed class Redirect | ||||
{ | { | ||||
@@ -53,13 +53,12 @@ namespace Discord.Net.WebSockets | |||||
_targetAudioBufferLength = client.Config.VoiceBufferLength / 20; //20 ms frames | _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) | if (_serverId == serverId && _userId == userId && _sessionId == sessionId && _token == token) | ||||
{ | { | ||||
//Adjust the host and tell the system to reconnect | //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; | return; | ||||
} | } | ||||
@@ -68,18 +67,19 @@ namespace Discord.Net.WebSockets | |||||
_sessionId = sessionId; | _sessionId = sessionId; | ||||
_token = token; | _token = token; | ||||
await Connect(host, cancelToken); | |||||
await Connect(); | |||||
} | } | ||||
public async Task Reconnect(CancellationToken cancelToken) | |||||
public async Task Reconnect() | |||||
{ | { | ||||
try | 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) | while (!cancelToken.IsCancellationRequested) | ||||
{ | { | ||||
try | try | ||||
{ | { | ||||
await Connect(_host, cancelToken).ConfigureAwait(false); | |||||
await Connect().ConfigureAwait(false); | |||||
break; | break; | ||||
} | } | ||||
catch (OperationCanceledException) { throw; } | catch (OperationCanceledException) { throw; } | ||||
@@ -295,7 +295,7 @@ namespace Discord.Net.WebSockets | |||||
var payload = (msg.Payload as JToken).ToObject<VoiceEvents.Ready>(); | var payload = (msg.Payload as JToken).ToObject<VoiceEvents.Ready>(); | ||||
_heartbeatInterval = payload.HeartbeatInterval; | _heartbeatInterval = payload.HeartbeatInterval; | ||||
_ssrc = payload.SSRC; | _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(); | //_mode = payload.Modes.LastOrDefault(); | ||||
_isEncrypted = !payload.Modes.Contains("plain"); | _isEncrypted = !payload.Modes.Contains("plain"); | ||||
_udp.Connect(_endpoint); | _udp.Connect(_endpoint); | ||||
@@ -38,7 +38,8 @@ namespace Discord.Net.WebSockets | |||||
protected readonly DiscordClient _client; | protected readonly DiscordClient _client; | ||||
protected readonly LogMessageSeverity _logLevel; | protected readonly LogMessageSeverity _logLevel; | ||||
protected string _host; | |||||
public string Host { get; set; } | |||||
protected int _loginTimeout, _heartbeatInterval; | protected int _loginTimeout, _heartbeatInterval; | ||||
private DateTime _lastHeartbeat; | private DateTime _lastHeartbeat; | ||||
private Task _runTask; | private Task _runTask; | ||||
@@ -49,6 +50,7 @@ namespace Discord.Net.WebSockets | |||||
protected ExceptionDispatchInfo _disconnectReason; | protected ExceptionDispatchInfo _disconnectReason; | ||||
private bool _wasDisconnectUnexpected; | private bool _wasDisconnectUnexpected; | ||||
public CancellationToken ParentCancelToken { get; set; } | |||||
public CancellationToken CancelToken => _cancelToken; | public CancellationToken CancelToken => _cancelToken; | ||||
private CancellationTokenSource _cancelTokenSource; | private CancellationTokenSource _cancelTokenSource; | ||||
protected CancellationToken _cancelToken; | 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) | if (_state != (int)WebSocketState.Disconnected) | ||||
throw new InvalidOperationException("Client is already connected or connecting to the server."); | throw new InvalidOperationException("Client is already connected or connecting to the server."); | ||||
try | |||||
try | |||||
{ | { | ||||
await Disconnect().ConfigureAwait(false); | await Disconnect().ConfigureAwait(false); | ||||
_state = (int)WebSocketState.Connecting; | _state = (int)WebSocketState.Connecting; | ||||
_cancelTokenSource = new CancellationTokenSource(); | _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; | _lastHeartbeat = DateTime.UtcNow; | ||||
_runTask = RunTasks(); | _runTask = RunTasks(); | ||||
@@ -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.") | |||||
{ | |||||
} | |||||
} | |||||
} |