mirror of
https://github.com/barelyprofessional/KfChatDotNet.git
synced 2026-04-30 03:22:04 -04:00
3xpl websocket client in case anyone wanted one. Don't bother using it though, their websocket service is a piece of shit that's totally broken which I only found out after wasting a day on it.
This commit is contained in:
@@ -10,6 +10,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "KickWsClient", "KickWsClien
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "KfChatDotNetKickBot", "KfChatDotNetKickBot\KfChatDotNetKickBot.csproj", "{4734E0A4-150E-4915-B905-928BB4BE3FF6}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ThreeXplWsClient", "ThreeXplWsClient\ThreeXplWsClient.csproj", "{3D72D70A-48AD-4EE8-89DC-C78153EEA879}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ThreeXplCliClient", "ThreeXplCliClient\ThreeXplCliClient.csproj", "{D098E281-5535-4A07-9514-57AF78704B0C}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
@@ -36,5 +40,13 @@ Global
|
||||
{4734E0A4-150E-4915-B905-928BB4BE3FF6}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{4734E0A4-150E-4915-B905-928BB4BE3FF6}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{4734E0A4-150E-4915-B905-928BB4BE3FF6}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{3D72D70A-48AD-4EE8-89DC-C78153EEA879}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{3D72D70A-48AD-4EE8-89DC-C78153EEA879}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{3D72D70A-48AD-4EE8-89DC-C78153EEA879}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{3D72D70A-48AD-4EE8-89DC-C78153EEA879}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{D098E281-5535-4A07-9514-57AF78704B0C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{D098E281-5535-4A07-9514-57AF78704B0C}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{D098E281-5535-4A07-9514-57AF78704B0C}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{D098E281-5535-4A07-9514-57AF78704B0C}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
|
||||
15
ThreeXplCliClient/NLog.config
Normal file
15
ThreeXplCliClient/NLog.config
Normal file
@@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.nlog-project.org/schemas/NLog.xsd NLog.xsd"
|
||||
autoReload="true"
|
||||
throwExceptions="false"
|
||||
internalLogLevel="Off" internalLogFile="~/nlog-internal.log">
|
||||
<targets>
|
||||
<target xsi:type="Console" name="console"/>
|
||||
</targets>
|
||||
|
||||
<rules>
|
||||
<logger name="*" minlevel="Debug" writeTo="console" />
|
||||
</rules>
|
||||
</nlog>
|
||||
3483
ThreeXplCliClient/NLog.xsd
Normal file
3483
ThreeXplCliClient/NLog.xsd
Normal file
File diff suppressed because it is too large
Load Diff
18
ThreeXplCliClient/Program.cs
Normal file
18
ThreeXplCliClient/Program.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using Spectre.Console;
|
||||
using ThreeXplWsClient.Events;
|
||||
|
||||
namespace ThreeXplCliClient
|
||||
{
|
||||
public class Program
|
||||
{
|
||||
static async Task Main(string[] args)
|
||||
{
|
||||
Console.OutputEncoding = Encoding.UTF8;
|
||||
AnsiConsole.MarkupLine("[green]3xpl test client started[/]");
|
||||
var cliClient = new ThreeXplClient();
|
||||
await cliClient.Start();
|
||||
}
|
||||
}
|
||||
}
|
||||
30
ThreeXplCliClient/ThreeXplCliClient.csproj
Normal file
30
ThreeXplCliClient/ThreeXplCliClient.csproj
Normal file
@@ -0,0 +1,30 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="NLog" Version="5.3.2" />
|
||||
<PackageReference Include="Spectre.Console" Version="0.49.1" />
|
||||
<PackageReference Include="System.Text.Json" Version="8.0.3" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\ThreeXplWsClient\ThreeXplWsClient.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Update="NLog.config">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<None Remove="NLog.config" />
|
||||
<Content Include="NLog.config">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
38
ThreeXplCliClient/ThreeXplClient.cs
Normal file
38
ThreeXplCliClient/ThreeXplClient.cs
Normal file
@@ -0,0 +1,38 @@
|
||||
using System.Text.Json;
|
||||
using Spectre.Console;
|
||||
using ThreeXplWsClient.Events;
|
||||
|
||||
namespace ThreeXplCliClient;
|
||||
|
||||
public class ThreeXplClient
|
||||
{
|
||||
private List<string> _addresses =
|
||||
[
|
||||
"MC8TiBEsnQVjxbvLtTsUXjTBZTQaR8fe8X",
|
||||
"ltc1qks2m7hvmhs3c20zrfvptv9pvk82p8g70sgw5mk"
|
||||
];
|
||||
public async Task Start()
|
||||
{
|
||||
var client = new ThreeXplWsClient.ThreeXplWsClient();
|
||||
client.OnThreeXplPush += OnThreeXplEvent;
|
||||
await client.StartWsClient();
|
||||
while (true)
|
||||
{
|
||||
var prompt = AnsiConsole.Ask<string>("Channel: ");
|
||||
client.SendSubscribeRequest(prompt);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnThreeXplEvent(object sender, ThreeXplPushModel e, int connectionId)
|
||||
{
|
||||
AnsiConsole.MarkupLine("[blue]Received event from 3xpl[/]");
|
||||
foreach (var txn in e.Pub.Data.Data)
|
||||
{
|
||||
if (txn.Address == null) return;
|
||||
if (_addresses.Contains(txn.Address))
|
||||
{
|
||||
AnsiConsole.MarkupLine($"[green]Saw txn I'm interested in: {txn.Address}, effect {txn.Effect}, currency {txn.Currency}[/]");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
22
ThreeXplWsClient/Events/EventHandlers.cs
Normal file
22
ThreeXplWsClient/Events/EventHandlers.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
using Websocket.Client;
|
||||
|
||||
namespace ThreeXplWsClient.Events;
|
||||
|
||||
public class EventHandlers
|
||||
{
|
||||
public delegate void OnThreeXplPing(object sender, int connectionId);
|
||||
|
||||
public delegate void OnThreeXplPush(object sender, ThreeXplPushModel e, int connectionId);
|
||||
|
||||
public delegate void OnWsDisconnectionEventHandler(object sender, DisconnectionInfo e, int connectionId);
|
||||
|
||||
public delegate void OnWsReconnectEventHandler(object sender, ReconnectionInfo e, int connectionId);
|
||||
|
||||
public delegate void OnWsMessageReceivedEventHandler(object sender, ResponseMessage e, int connectionId);
|
||||
|
||||
public delegate void OnThreeXplConnect(object sender, ThreeXplConnectDataModel e, int connectionId);
|
||||
|
||||
public delegate void OnThreeXplError(object sender, ThreeXplErrorModel e, int connectionId);
|
||||
|
||||
public delegate void OnThreeXplSubscribe(object sender, ThreeXplSubscribeModel e, int connectionId);
|
||||
}
|
||||
110
ThreeXplWsClient/Events/EventModels.cs
Normal file
110
ThreeXplWsClient/Events/EventModels.cs
Normal file
@@ -0,0 +1,110 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace ThreeXplWsClient.Events;
|
||||
|
||||
public class BaseThreeXplPacketModel
|
||||
{
|
||||
[JsonPropertyName("connect")]
|
||||
public ThreeXplConnectDataModel? Connect { get; set; }
|
||||
[JsonPropertyName("id")]
|
||||
public int? Id { get; set; }
|
||||
[JsonPropertyName("error")]
|
||||
public ThreeXplErrorModel? Error { get; set; }
|
||||
[JsonPropertyName("subscribe")]
|
||||
public ThreeXplSubscribeModel? Subscribe { get; set; }
|
||||
[JsonPropertyName("push")]
|
||||
public ThreeXplPushModel? Push { get; set; }
|
||||
|
||||
}
|
||||
|
||||
public class ThreeXplDataModel
|
||||
{
|
||||
[JsonPropertyName("blockchain")]
|
||||
public string? Blockchain { get; set; }
|
||||
[JsonPropertyName("module")]
|
||||
public string? Module { get; set; }
|
||||
[JsonPropertyName("block")]
|
||||
public int? Block { get; set; }
|
||||
[JsonPropertyName("transaction")]
|
||||
public string? Transaction { get; set; }
|
||||
[JsonPropertyName("sort_key")]
|
||||
public int? SortKey { get; set; }
|
||||
[JsonPropertyName("time")]
|
||||
public DateTimeOffset? Time { get; set; }
|
||||
[JsonPropertyName("currency")]
|
||||
public string? Currency { get; set; }
|
||||
[JsonPropertyName("effect")]
|
||||
public string? Effect { get; set; }
|
||||
[JsonPropertyName("failed")]
|
||||
public bool? Failed { get; set; }
|
||||
[JsonPropertyName("extra")]
|
||||
public object? Extra { get; set; }
|
||||
[JsonPropertyName("extra_indexed")]
|
||||
public object? ExtraIndexed { get; set; }
|
||||
[JsonPropertyName("address")]
|
||||
public string? Address { get; set; }
|
||||
}
|
||||
|
||||
public class ThreeXplContextModel
|
||||
{
|
||||
// "time":"0.21778600 1718465848"
|
||||
[JsonPropertyName("time")]
|
||||
public string? Time { get; set; }
|
||||
}
|
||||
|
||||
public class ThreeXplConnectDataModel
|
||||
{
|
||||
[JsonPropertyName("client")]
|
||||
public string? Client { get; set; }
|
||||
[JsonPropertyName("version")]
|
||||
public string? Version { get; set; }
|
||||
[JsonPropertyName("ping")]
|
||||
public int? Ping { get; set; }
|
||||
[JsonPropertyName("pong")]
|
||||
public bool? Pong { get; set; }
|
||||
}
|
||||
|
||||
public class ThreeXplErrorModel
|
||||
{
|
||||
[JsonPropertyName("code")]
|
||||
public int? Code { get; set; }
|
||||
[JsonPropertyName("Message")]
|
||||
public string? Message { get; set; }
|
||||
[JsonPropertyName("temporary")]
|
||||
public bool? Temporary { get; set; }
|
||||
}
|
||||
|
||||
public class ThreeXplSubscribeModel
|
||||
{
|
||||
[JsonPropertyName("recoverable")]
|
||||
public bool? Recoverable { get; set; }
|
||||
[JsonPropertyName("epoch")]
|
||||
public string? Epoch { get; set; }
|
||||
[JsonPropertyName("positioned")]
|
||||
public bool? Positioned { get; set; }
|
||||
}
|
||||
|
||||
public class ThreeXplPushModel
|
||||
{
|
||||
[JsonPropertyName("channel")]
|
||||
public required string Channel { get; set; }
|
||||
[JsonPropertyName("pub")]
|
||||
public required ThreeXplPushPubModel Pub { get; set; }
|
||||
[JsonPropertyName("offset")]
|
||||
public int? Offset { get; set; }
|
||||
|
||||
}
|
||||
|
||||
public class ThreeXplPushPubModel
|
||||
{
|
||||
[JsonPropertyName("data")]
|
||||
public required ThreeXplPushDataModel Data { get; set; }
|
||||
}
|
||||
|
||||
public class ThreeXplPushDataModel
|
||||
{
|
||||
[JsonPropertyName("data")]
|
||||
public required List<ThreeXplDataModel> Data { get; set; }
|
||||
[JsonPropertyName("context")]
|
||||
public required ThreeXplContextModel Context { get; set; }
|
||||
}
|
||||
17
ThreeXplWsClient/Models/JwtResponseModels.cs
Normal file
17
ThreeXplWsClient/Models/JwtResponseModels.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace ThreeXplWsClient.Models;
|
||||
|
||||
public class GetWebsocketTokenModel
|
||||
{
|
||||
[JsonPropertyName("data")]
|
||||
public required string Data { get; set; }
|
||||
[JsonPropertyName("context")]
|
||||
public GetWebSocketTokenContextModel? Context { get; set; }
|
||||
}
|
||||
|
||||
public class GetWebSocketTokenContextModel
|
||||
{
|
||||
[JsonPropertyName("code")]
|
||||
public int? Code { get; set; }
|
||||
}
|
||||
31
ThreeXplWsClient/Models/RequestModels.cs
Normal file
31
ThreeXplWsClient/Models/RequestModels.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace ThreeXplWsClient.Models;
|
||||
|
||||
public class ConnectRequestModel
|
||||
{
|
||||
[JsonPropertyName("connect")]
|
||||
public required ConnectRequestTokenModel Connect { get; set; }
|
||||
[JsonPropertyName("id")]
|
||||
public int Id { get; set; } = 1;
|
||||
}
|
||||
|
||||
public class ConnectRequestTokenModel
|
||||
{
|
||||
[JsonPropertyName("token")]
|
||||
public required string Token { get; set; }
|
||||
}
|
||||
|
||||
public class SubscribeRequestModel
|
||||
{
|
||||
[JsonPropertyName("subscribe")]
|
||||
public required SubscribeRequestChannelModel Subscribe { get; set; }
|
||||
[JsonPropertyName("id")]
|
||||
public int Id { get; set; } = 2;
|
||||
}
|
||||
|
||||
public class SubscribeRequestChannelModel
|
||||
{
|
||||
[JsonPropertyName("channel")]
|
||||
public required string Channel { get; set; }
|
||||
}
|
||||
235
ThreeXplWsClient/ThreeXplWsClient.cs
Normal file
235
ThreeXplWsClient/ThreeXplWsClient.cs
Normal file
@@ -0,0 +1,235 @@
|
||||
using System.Net;
|
||||
using System.Net.Http.Json;
|
||||
using System.Net.WebSockets;
|
||||
using System.Text.Json;
|
||||
using NLog;
|
||||
using ThreeXplWsClient.Events;
|
||||
using ThreeXplWsClient.Models;
|
||||
using Websocket.Client;
|
||||
|
||||
namespace ThreeXplWsClient;
|
||||
|
||||
public class ThreeXplWsClient
|
||||
{
|
||||
public event EventHandlers.OnWsMessageReceivedEventHandler OnWsMessageReceived;
|
||||
public event EventHandlers.OnWsDisconnectionEventHandler OnWsDisconnection;
|
||||
public event EventHandlers.OnWsReconnectEventHandler OnWsReconnect;
|
||||
public event EventHandlers.OnThreeXplPing OnThreeXplPing;
|
||||
public event EventHandlers.OnThreeXplPush OnThreeXplPush;
|
||||
public event EventHandlers.OnThreeXplConnect OnThreeXplConnect;
|
||||
public event EventHandlers.OnThreeXplError OnThreeXplError;
|
||||
public event EventHandlers.OnThreeXplSubscribe OnThreeXplSubscribe;
|
||||
|
||||
private WebsocketClient _wsClient;
|
||||
private readonly Logger _logger = LogManager.GetCurrentClassLogger();
|
||||
private string? _wsJwt;
|
||||
private DateTime _wsJwtLastRetrieved = DateTime.Now;
|
||||
private int _wsJwtValidityPeriodSeconds;
|
||||
private string? _proxy;
|
||||
private int _reconnectTimeout;
|
||||
private Uri _wsUri;
|
||||
// Basically they have a limit of 10 subscriptions per connection and I have more than 10 addresses to monitor, so
|
||||
// I give each connection an ID number as that way I know what addresses need to be resubscribed in the event of a
|
||||
// connection drop. This ID is included with every event fired and set when the class is constructed.
|
||||
private int _connectionId;
|
||||
|
||||
/// <summary>
|
||||
/// Client for the 3xpl WebSocket API
|
||||
/// </summary>
|
||||
/// <param name="threeXplWsUri">URI for the websocket API, published at https://3xpl.com/data/websocket-api</param>
|
||||
/// <param name="proxy">Web proxy to use for the WebSocket connection</param>
|
||||
/// <param name="reconnectTimeout">Reconnect timeout, defaults to 30 seconds as 3xpl tells us to expect a ping every 25 seconds</param>
|
||||
/// <param name="jwtValidityPeriodSeconds">How long the JWT is valid for. Set to int.MaxValue if you've manually provided a non-expiring token</param>
|
||||
/// <param name="jwtApiToken">Manually provide a JWT if you have access to create your own</param>
|
||||
/// <param name="connectionId">ID that can be used to differentiate multiple 3xpl connections</param>
|
||||
public ThreeXplWsClient(string threeXplWsUri = "wss://stream.3xpl.net", string? proxy = null,
|
||||
int reconnectTimeout = 30, int jwtValidityPeriodSeconds = 600, string? jwtApiToken = null, int connectionId = 0)
|
||||
{
|
||||
_wsUri = new Uri(threeXplWsUri);
|
||||
_proxy = proxy;
|
||||
_reconnectTimeout = reconnectTimeout;
|
||||
_wsJwtValidityPeriodSeconds = jwtValidityPeriodSeconds;
|
||||
_wsJwt = jwtApiToken;
|
||||
_connectionId = connectionId;
|
||||
}
|
||||
|
||||
private async Task RefreshApiToken()
|
||||
{
|
||||
_logger.Debug("Refreshing the API token");
|
||||
if (_wsJwtValidityPeriodSeconds == int.MaxValue)
|
||||
{
|
||||
_logger.Debug($"Token is non expiring as it is set to {int.MaxValue}");
|
||||
return;
|
||||
}
|
||||
if (_wsJwt != null && _wsJwtLastRetrieved.AddSeconds(_wsJwtValidityPeriodSeconds) >= DateTime.Now)
|
||||
{
|
||||
_logger.Debug(
|
||||
$"Token has not yet expired. Its expiration date is {_wsJwtLastRetrieved.AddSeconds(_wsJwtValidityPeriodSeconds):yyyy-MM-dd HH:mm:ss}");
|
||||
return;
|
||||
}
|
||||
|
||||
var handler = new HttpClientHandler { AutomaticDecompression = DecompressionMethods.All };
|
||||
if (_proxy != null)
|
||||
{
|
||||
handler.Proxy = new WebProxy(_proxy);
|
||||
handler.UseProxy = true;
|
||||
}
|
||||
|
||||
using var client = new HttpClient(handler);
|
||||
var token = await client.GetFromJsonAsync<GetWebsocketTokenModel>("https://3xpl.com/get-websockets-token");
|
||||
if (token == null)
|
||||
{
|
||||
_logger.Error("Caught a null when retrieving a WebSocket JWT from 3xpl");
|
||||
throw new InvalidOperationException("Caught a null when retrieving a WebSocket JWT from 3xp");
|
||||
}
|
||||
|
||||
_wsJwt = token.Data;
|
||||
_wsJwtLastRetrieved = DateTime.Now;
|
||||
}
|
||||
|
||||
public async Task StartWsClient()
|
||||
{
|
||||
_logger.Debug("StartWsClient() called, creating client");
|
||||
await CreateWsClient();
|
||||
}
|
||||
|
||||
private async Task CreateWsClient()
|
||||
{
|
||||
var factory = new Func<ClientWebSocket>(() =>
|
||||
{
|
||||
var clientWs = new ClientWebSocket();
|
||||
if (_proxy == null) return clientWs;
|
||||
clientWs.Options.Proxy = new WebProxy(_proxy);
|
||||
return clientWs;
|
||||
});
|
||||
var client = new WebsocketClient(_wsUri, factory)
|
||||
{
|
||||
ReconnectTimeout = TimeSpan.FromSeconds(_reconnectTimeout)
|
||||
};
|
||||
_wsClient = client;
|
||||
|
||||
client.ReconnectionHappened.Subscribe(WsReconnection);
|
||||
client.MessageReceived.Subscribe(WsMessageReceived);
|
||||
client.DisconnectionHappened.Subscribe(WsDisconnection);
|
||||
|
||||
_logger.Debug("Websocket client has been built, about to start");
|
||||
await client.Start();
|
||||
_logger.Debug("Websocket client started!");
|
||||
}
|
||||
|
||||
public bool IsConnected()
|
||||
{
|
||||
return _wsClient is { IsRunning: true };
|
||||
}
|
||||
|
||||
private void WsDisconnection(DisconnectionInfo disconnectionInfo)
|
||||
{
|
||||
_logger.Error($"Client disconnected from the chat (or never successfully connected). Type is {disconnectionInfo.Type}");
|
||||
_logger.Error(disconnectionInfo.Exception);
|
||||
OnWsDisconnection?.Invoke(this, disconnectionInfo, _connectionId);
|
||||
}
|
||||
|
||||
private void SendConnectRequest()
|
||||
{
|
||||
if (_wsJwt == null)
|
||||
{
|
||||
_logger.Error("JWT was null.");
|
||||
throw new InvalidOperationException("JWT was null");
|
||||
}
|
||||
|
||||
var data = new ConnectRequestModel { Connect = new ConnectRequestTokenModel { Token = _wsJwt } };
|
||||
var payload = JsonSerializer.Serialize(data);
|
||||
_logger.Debug("Sending the following payload to 3xpl");
|
||||
_logger.Debug(payload);
|
||||
_wsClient.Send(payload);
|
||||
}
|
||||
|
||||
private void WsReconnection(ReconnectionInfo reconnectionInfo)
|
||||
{
|
||||
_logger.Error($"Websocket connection dropped and reconnected. Reconnection type is {reconnectionInfo.Type}");
|
||||
_logger.Info("Refreshing JWT");
|
||||
RefreshApiToken().Wait();
|
||||
_logger.Info("Sending connect request");
|
||||
SendConnectRequest();
|
||||
OnWsReconnect?.Invoke(this, reconnectionInfo, _connectionId);
|
||||
}
|
||||
|
||||
public void SendSubscribeRequest(string channel)
|
||||
{
|
||||
var data = new SubscribeRequestModel { Subscribe = new SubscribeRequestChannelModel { Channel = channel }};
|
||||
var payload = JsonSerializer.Serialize(data);
|
||||
_logger.Debug("Sending the following subscription payload to 3xpl");
|
||||
_logger.Debug(payload);
|
||||
_wsClient.Send(payload);
|
||||
}
|
||||
|
||||
private void WsMessageReceived(ResponseMessage message)
|
||||
{
|
||||
OnWsMessageReceived?.Invoke(this, message, _connectionId);
|
||||
_logger.Debug("Received JSON from 3xpl");
|
||||
_logger.Debug(message.Text);
|
||||
|
||||
if (message.Text == null)
|
||||
{
|
||||
_logger.Info("Websocket message was null, ignoring packet");
|
||||
return;
|
||||
}
|
||||
|
||||
if (message.Text == "{}")
|
||||
{
|
||||
_logger.Debug("Received ping from 3xpl. Sending back a pong and invoking event");
|
||||
_wsClient.Send("{}");
|
||||
OnThreeXplPing?.Invoke(this, _connectionId);
|
||||
return;
|
||||
}
|
||||
|
||||
BaseThreeXplPacketModel threeXplPacket;
|
||||
try
|
||||
{
|
||||
threeXplPacket = JsonSerializer.Deserialize<BaseThreeXplPacketModel>(message.Text) ??
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.Error("Failed to parse 3xpl payload. Exception follows:");
|
||||
_logger.Error(e);
|
||||
_logger.Error("--- Message from 3xpl follows ---");
|
||||
_logger.Error(message.Text);
|
||||
_logger.Error("--- /end of message ---");
|
||||
return;
|
||||
}
|
||||
|
||||
if (threeXplPacket.Connect != null)
|
||||
{
|
||||
_logger.Debug("Received connect packet from 3xpl, invoking event");
|
||||
OnThreeXplConnect?.Invoke(this, threeXplPacket.Connect, _connectionId);
|
||||
return;
|
||||
}
|
||||
|
||||
if (threeXplPacket.Push != null)
|
||||
{
|
||||
_logger.Debug("Received data event from 3xpl");
|
||||
OnThreeXplPush?.Invoke(this, threeXplPacket.Push, _connectionId);
|
||||
return;
|
||||
}
|
||||
|
||||
if (threeXplPacket.Error != null)
|
||||
{
|
||||
_logger.Debug("Received error packet from 3xpl");
|
||||
OnThreeXplError?.Invoke(this, threeXplPacket.Error, _connectionId);
|
||||
return;
|
||||
}
|
||||
|
||||
if (threeXplPacket.Subscribe != null)
|
||||
{
|
||||
_logger.Debug("Received subscribe packet from 3xpl");
|
||||
OnThreeXplSubscribe?.Invoke(this, threeXplPacket.Subscribe, _connectionId);
|
||||
return;
|
||||
}
|
||||
|
||||
_logger.Error("Failed to handle 3xpl packet");
|
||||
_logger.Error("--- Message from 3xpl follows ---");
|
||||
_logger.Error(message.Text);
|
||||
_logger.Error("--- /end of message ---");
|
||||
}
|
||||
}
|
||||
15
ThreeXplWsClient/ThreeXplWsClient.csproj
Normal file
15
ThreeXplWsClient/ThreeXplWsClient.csproj
Normal file
@@ -0,0 +1,15 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="NLog" Version="5.3.2" />
|
||||
<PackageReference Include="System.Text.Json" Version="8.0.3" />
|
||||
<PackageReference Include="Websocket.Client" Version="5.1.1" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
Reference in New Issue
Block a user