Skip to content

Commit

Permalink
[Release] Version 0.5.0
Browse files Browse the repository at this point in the history
** Breaking Release because of Async Events switch **
  • Loading branch information
Syzuna authored Apr 11, 2024
2 parents b1cae3f + cf9adcd commit d8d96b5
Show file tree
Hide file tree
Showing 31 changed files with 624 additions and 202 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/check-buildstatus.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,11 @@ jobs:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Setup .NET
uses: actions/setup-dotnet@v3
with:
dotnet-version: 7.0.x
dotnet-version: 8.0.x
- name: Restore dependencies
run: dotnet restore
- name: Build TwitchLib.EventSub.Websockets
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/preview-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,13 @@ jobs:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v3
- uses: benjlevesque/short-sha@v2.1
- uses: actions/checkout@v4
- uses: benjlevesque/short-sha@v2.2
id: short-sha
- name: Setup .NET
uses: actions/setup-dotnet@v3
with:
dotnet-version: 7.0.x
dotnet-version: 8.0.x
- name: Restore dependencies
run: dotnet restore
- name: Build TwitchLib.EventSub.Websockets
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,11 @@ jobs:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Setup .NET
uses: actions/setup-dotnet@v3
with:
dotnet-version: 7.0.x
dotnet-version: 8.0.x
- name: Restore dependencies
run: dotnet restore
- name: Build TwitchLib.EventSub.Websockets
Expand Down
7 changes: 2 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,12 @@ EventSub via Websockets is still in open beta.
You can use it in production but Twitch may introduce breaking changes without prior notice.
The same goes for this implementation until it reaches Version 1.0.0

## Prerequisites
- Currently we only support setup via Dependency Injection

## Resources
If you need help on how to setup Dependency Injection in your Console or WPF Application you can have a look at these guides:
- Console: https://dfederm.com/building-a-console-app-with-.net-generic-host/
- WPF: https://laurentkempe.com/2019/09/03/WPF-and-dotnet-Generic-Host-with-dotnet-Core-3-0/

You can also find a console app example for .NET 6 and for .NET Framework 4.8 in the repo.
You can also find a console app example for .NET 8 and for .NET Framework 4.8 in the repo.

## Installation

Expand Down Expand Up @@ -124,4 +121,4 @@ namespace TwitchLib.EventSub.Websockets.Test

Alternatively you can also just clone the examples:
- .NET Framework 4.8 https://github.com/TwitchLib/TwitchLib.EventSub.Websockets/tree/main/TwitchLib.EventSub.Websockets.Example.NetStandard
- .NET 6 -> https://github.com/TwitchLib/TwitchLib.EventSub.Websockets/tree/main/TwitchLib.EventSub.Websockets.Example
- .NET 8 -> https://github.com/TwitchLib/TwitchLib.EventSub.Websockets/tree/main/TwitchLib.EventSub.Websockets.Example
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ private static IHostBuilder CreateHostBuilder(string[] args) =>
services.AddLogging();
services.AddTwitchLibEventSubWebsockets();

services.AddHostedService<WebsocketHostedService>();
services.AddHostedService<WebsocketHostedServiceWithoutDI>();
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Hosting" Version="7.0.0" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.0" />
</ItemGroup>

<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,12 @@ public WebsocketHostedService(IConfiguration configuration ,ILogger<WebsocketHos
_eventSubWebsocketClient.ChannelFollow += OnChannelFollow;
}

private void OnErrorOccurred(object sender, ErrorOccuredArgs e)
private async Task OnErrorOccurred(object sender, ErrorOccuredArgs e)
{
_logger.LogError($"Websocket {_eventSubWebsocketClient.SessionId} - Error occurred!");
}

private void OnChannelFollow(object sender, ChannelFollowArgs e)
private async Task OnChannelFollow(object sender, ChannelFollowArgs e)
{
var eventData = e.Notification.Payload.Event;
_logger.LogInformation($"{eventData.UserName} followed {eventData.BroadcasterUserName} at {eventData.FollowedAt}");
Expand All @@ -50,7 +50,7 @@ public async Task StopAsync(CancellationToken cancellationToken)
await _eventSubWebsocketClient.DisconnectAsync();
}

private void OnWebsocketConnected(object sender, WebsocketConnectedArgs e)
private async Task OnWebsocketConnected(object sender, WebsocketConnectedArgs e)
{
_logger.LogInformation($"Websocket {_eventSubWebsocketClient.SessionId} connected!");

Expand All @@ -60,7 +60,7 @@ private void OnWebsocketConnected(object sender, WebsocketConnectedArgs e)
}
}

private async void OnWebsocketDisconnected(object sender, EventArgs e)
private async Task OnWebsocketDisconnected(object sender, EventArgs e)
{
_logger.LogError($"Websocket {_eventSubWebsocketClient.SessionId} disconnected!");

Expand All @@ -72,7 +72,7 @@ private async void OnWebsocketDisconnected(object sender, EventArgs e)
}
}

private void OnWebsocketReconnected(object sender, EventArgs e)
private async Task OnWebsocketReconnected(object sender, EventArgs e)
{
_logger.LogWarning($"Websocket {_eventSubWebsocketClient.SessionId} reconnected");
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using TwitchLib.EventSub.Websockets.Core.EventArgs;
using TwitchLib.EventSub.Websockets.Core.EventArgs.Channel;

namespace TwitchLib.EventSub.Websockets.Example.NetStandard
{
public class WebsocketHostedServiceWithoutDI : IHostedService
{
private readonly ILogger<WebsocketHostedService> _logger;
private readonly EventSubWebsocketClient _eventSubWebsocketClient;

public WebsocketHostedServiceWithoutDI(ILogger<WebsocketHostedService> logger, ILoggerFactory loggerFactory)
{
_logger = logger;
_eventSubWebsocketClient = new EventSubWebsocketClient(loggerFactory);

_eventSubWebsocketClient.WebsocketConnected += OnWebsocketConnected;
_eventSubWebsocketClient.WebsocketDisconnected += OnWebsocketDisconnected;
_eventSubWebsocketClient.WebsocketReconnected += OnWebsocketReconnected;
_eventSubWebsocketClient.ErrorOccurred += OnErrorOccurred;

_eventSubWebsocketClient.ChannelFollow += OnChannelFollow;
}

public async Task StartAsync(CancellationToken cancellationToken)
{
await _eventSubWebsocketClient.ConnectAsync();
}

public async Task StopAsync(CancellationToken cancellationToken)
{
await _eventSubWebsocketClient.DisconnectAsync();
}

private async Task OnErrorOccurred(object sender, ErrorOccuredArgs e)
{
_logger.LogError($"Websocket {_eventSubWebsocketClient.SessionId} - Error occurred!");
}

private async Task OnChannelFollow(object sender, ChannelFollowArgs e)
{
var eventData = e.Notification.Payload.Event;
_logger.LogInformation($"{eventData.UserName} followed {eventData.BroadcasterUserName} at {eventData.FollowedAt}");
}

private async Task OnWebsocketConnected(object sender, WebsocketConnectedArgs e)
{
_logger.LogInformation($"Websocket {_eventSubWebsocketClient.SessionId} connected!");

if (!e.IsRequestedReconnect)
{
// subscribe to topics
}
}

private async Task OnWebsocketDisconnected(object sender, EventArgs e)
{
_logger.LogError($"Websocket {_eventSubWebsocketClient.SessionId} disconnected!");

// Don't do this in production. You should implement a better reconnect strategy
while (!await _eventSubWebsocketClient.ReconnectAsync())
{
_logger.LogError("Websocket reconnect failed!");
await Task.Delay(1000);
}
}

private async Task OnWebsocketReconnected(object sender, EventArgs e)
{
_logger.LogWarning($"Websocket {_eventSubWebsocketClient.SessionId} reconnected");
}
}
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk.Worker">

<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<Nullable>disable</Nullable>
<ImplicitUsings>disable</ImplicitUsings>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Hosting" Version="7.0.0" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.0" />
</ItemGroup>

<ItemGroup>
Expand Down
10 changes: 5 additions & 5 deletions TwitchLib.EventSub.Websockets.Example/WebsocketHostedService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,12 @@ public WebsocketHostedService(ILogger<WebsocketHostedService> logger, EventSubWe
_eventSubWebsocketClient.ChannelFollow += OnChannelFollow;
}

private void OnErrorOccurred(object sender, ErrorOccuredArgs e)
private async Task OnErrorOccurred(object sender, ErrorOccuredArgs e)
{
_logger.LogError($"Websocket {_eventSubWebsocketClient.SessionId} - Error occurred!");
}

private void OnChannelFollow(object sender, ChannelFollowArgs e)
private async Task OnChannelFollow(object sender, ChannelFollowArgs e)
{
var eventData = e.Notification.Payload.Event;
_logger.LogInformation($"{eventData.UserName} followed {eventData.BroadcasterUserName} at {eventData.FollowedAt}");
Expand All @@ -47,7 +47,7 @@ public async Task StopAsync(CancellationToken cancellationToken)
await _eventSubWebsocketClient.DisconnectAsync();
}

private void OnWebsocketConnected(object sender, WebsocketConnectedArgs e)
private async Task OnWebsocketConnected(object sender, WebsocketConnectedArgs e)
{
_logger.LogInformation($"Websocket {_eventSubWebsocketClient.SessionId} connected!");

Expand All @@ -57,7 +57,7 @@ private void OnWebsocketConnected(object sender, WebsocketConnectedArgs e)
}
}

private async void OnWebsocketDisconnected(object sender, EventArgs e)
private async Task OnWebsocketDisconnected(object sender, EventArgs e)
{
_logger.LogError($"Websocket {_eventSubWebsocketClient.SessionId} disconnected!");

Expand All @@ -69,7 +69,7 @@ private async void OnWebsocketDisconnected(object sender, EventArgs e)
}
}

private void OnWebsocketReconnected(object sender, EventArgs e)
private async Task OnWebsocketReconnected(object sender, EventArgs e)
{
_logger.LogWarning($"Websocket {_eventSubWebsocketClient.SessionId} reconnected");
}
Expand Down
34 changes: 26 additions & 8 deletions TwitchLib.EventSub.Websockets/Client/WebsocketClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using TwitchLib.EventSub.Core;
using TwitchLib.EventSub.Websockets.Core.EventArgs;
using TwitchLib.EventSub.Websockets.Extensions;

#if NET6_0_OR_GREATER
using System.Buffers;
Expand All @@ -23,12 +25,12 @@ public class WebsocketClient : IDisposable
/// </summary>
public bool IsConnected => _webSocket.State == WebSocketState.Open;
/// <summary>
/// Determines if the Client is has encountered an unrecoverable issue based on WebsocketState
/// Determines if the Client has encountered an unrecoverable issue based on WebsocketState
/// </summary>
public bool IsFaulted => _webSocket.CloseStatus != WebSocketCloseStatus.Empty && _webSocket.CloseStatus != WebSocketCloseStatus.NormalClosure;

internal event EventHandler<DataReceivedArgs> OnDataReceived;
internal event EventHandler<ErrorOccuredArgs> OnErrorOccurred;
internal event AsyncEventHandler<DataReceivedArgs> OnDataReceived;
internal event AsyncEventHandler<ErrorOccuredArgs> OnErrorOccurred;

private readonly ClientWebSocket _webSocket;
private readonly ILogger<WebsocketClient> _logger;
Expand All @@ -52,7 +54,7 @@ public async Task<bool> ConnectAsync(Uri url)
{
try
{
if (_webSocket.State == WebSocketState.Open || _webSocket.State == WebSocketState.Connecting)
if (_webSocket.State is WebSocketState.Open or WebSocketState.Connecting)
return true;

await _webSocket.ConnectAsync(url, CancellationToken.None);
Expand All @@ -78,7 +80,7 @@ public async Task<bool> DisconnectAsync()
{
try
{
if (_webSocket.State == WebSocketState.Open || _webSocket.State == WebSocketState.Connecting)
if (_webSocket.State is WebSocketState.Open or WebSocketState.Connecting)
await _webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None);

return true;
Expand All @@ -99,9 +101,10 @@ public async Task<bool> DisconnectAsync()
private async Task ProcessDataAsync()
{
const int minimumBufferSize = 256;
var storeSize = 4096;
var decoder = Encoding.UTF8.GetDecoder();

var store = MemoryPool<byte>.Shared.Rent().Memory;
var store = MemoryPool<byte>.Shared.Rent(storeSize).Memory;
var buffer = MemoryPool<byte>.Shared.Rent(minimumBufferSize).Memory;

var payloadSize = 0;
Expand All @@ -114,6 +117,19 @@ private async Task ProcessDataAsync()
do
{
receiveResult = await _webSocket.ReceiveAsync(buffer, CancellationToken.None);

if (payloadSize + receiveResult.Count >= storeSize)
{
storeSize +=
#if NET8_0_OR_GREATER
int.Max(4096, receiveResult.Count);
#else
Math.Max(4096, receiveResult.Count);
#endif
var newStore = MemoryPool<byte>.Shared.Rent(storeSize).Memory;
store.CopyTo(newStore);
store = newStore;
}

buffer.CopyTo(store[payloadSize..]);

Expand All @@ -139,7 +155,7 @@ private async Task ProcessDataAsync()
case WebSocketMessageType.Binary:
break;
case WebSocketMessageType.Close:
_logger?.LogCritical($"{(WebSocketCloseStatus)_webSocket.CloseStatus!} - {_webSocket.CloseStatusDescription!}");
_logger?.LogWebsocketClosed((WebSocketCloseStatus)_webSocket.CloseStatus!, _webSocket.CloseStatusDescription!);
break;
default:
throw new ArgumentOutOfRangeException();
Expand Down Expand Up @@ -179,7 +195,9 @@ private async Task ProcessDataAsync()
if (buffer.Array == null)
continue;

#pragma warning disable CA1849
memory.Write(buffer.Array, buffer.Offset, receiveResult.Count);
#pragma warning restore CA1849
payloadSize += receiveResult.Count;
} while (!receiveResult.EndOfMessage);

Expand All @@ -204,7 +222,7 @@ private async Task ProcessDataAsync()
break;
case WebSocketMessageType.Close:
if (_webSocket.CloseStatus != null)
_logger?.LogCritical($"{(WebSocketCloseStatus) _webSocket.CloseStatus} - {_webSocket.CloseStatusDescription}");
_logger?.LogWebsocketClosed((WebSocketCloseStatus)_webSocket.CloseStatus!, _webSocket.CloseStatusDescription!);
break;
default:
throw new ArgumentOutOfRangeException();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
using TwitchLib.EventSub.Core.SubscriptionTypes.Channel;
using TwitchLib.EventSub.Websockets.Core.Models;
namespace TwitchLib.EventSub.Websockets.Core.EventArgs.Channel
{
public class ChannelAdBreakBeginArgs : TwitchLibEventSubEventArgs<EventSubNotification<ChannelAdBreakBegin>>
{
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using TwitchLib.EventSub.Core.SubscriptionTypes.Channel;
using TwitchLib.EventSub.Websockets.Core.Models;

namespace TwitchLib.EventSub.Websockets.Core.EventArgs.Channel
{
public class ChannelChatMessageArgs : TwitchLibEventSubEventArgs<EventSubNotification<ChannelChatMessage>>
{
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
using TwitchLib.EventSub.Core.SubscriptionTypes.Channel;
using TwitchLib.EventSub.Websockets.Core.Models;

namespace TwitchLib.EventSub.Websockets.Core.EventArgs.Channel
{
public class ChannelGuestStarGuestUpdateArgs : TwitchLibEventSubEventArgs<EventSubNotification<ChannelGuestStarGuestUpdate>>
{ }
}
Loading

0 comments on commit d8d96b5

Please sign in to comment.