using Dalamud.Plugin.Services; using MareSynchronos.API.SignalR; using MareSynchronos.Interop; using MareSynchronos.MareConfiguration; using MareSynchronos.Services.Mediator; using MareSynchronos.Services.ServerConfiguration; using MareSynchronos.WebAPI.SignalR.Utils; using MessagePack; using MessagePack.Resolvers; using Microsoft.AspNetCore.Http.Connections; using Microsoft.AspNetCore.SignalR.Client; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; namespace MareSynchronos.WebAPI.SignalR; public class HubFactory : MediatorSubscriberBase { private readonly ILoggerProvider _loggingProvider; private readonly ServerConfigurationManager _serverConfigurationManager; private readonly TokenProvider _tokenProvider; private HubConnection? _instance; private bool _isDisposed = false; public HubFactory(ILogger logger, MareMediator mediator, ServerConfigurationManager serverConfigurationManager, TokenProvider tokenProvider, ILoggerProvider pluginLog) : base(logger, mediator) { _serverConfigurationManager = serverConfigurationManager; _tokenProvider = tokenProvider; _loggingProvider = pluginLog; } public async Task DisposeHubAsync() { if (_instance == null || _isDisposed) return; Logger.LogDebug("Disposing current HubConnection"); _isDisposed = true; _instance.Closed -= HubOnClosed; _instance.Reconnecting -= HubOnReconnecting; _instance.Reconnected -= HubOnReconnected; await _instance.StopAsync().ConfigureAwait(false); await _instance.DisposeAsync().ConfigureAwait(false); _instance = null; Logger.LogDebug("Current HubConnection disposed"); } public HubConnection GetOrCreate(CancellationToken ct) { if (!_isDisposed && _instance != null) return _instance; return BuildHubConnection(ct); } private HubConnection BuildHubConnection(CancellationToken ct) { Logger.LogDebug("Building new HubConnection"); _instance = new HubConnectionBuilder() .WithUrl(_serverConfigurationManager.CurrentApiUrl + IMareHub.Path, options => { options.AccessTokenProvider = () => _tokenProvider.GetOrUpdateToken(ct); options.Transports = HttpTransportType.WebSockets | HttpTransportType.ServerSentEvents | HttpTransportType.LongPolling; }) .AddMessagePackProtocol(opt => { var resolver = CompositeResolver.Create(StandardResolverAllowPrivate.Instance, BuiltinResolver.Instance, AttributeFormatterResolver.Instance, // replace enum resolver DynamicEnumAsStringResolver.Instance, DynamicGenericResolver.Instance, DynamicUnionResolver.Instance, DynamicObjectResolver.Instance, PrimitiveObjectResolver.Instance, // final fallback(last priority) StandardResolver.Instance); opt.SerializerOptions = MessagePackSerializerOptions.Standard .WithCompression(MessagePackCompression.Lz4Block) .WithResolver(resolver); }) .WithAutomaticReconnect(new ForeverRetryPolicy(Mediator)) .ConfigureLogging(a => { a.ClearProviders().AddProvider(_loggingProvider); a.SetMinimumLevel(LogLevel.Information); }) .Build(); _instance.Closed += HubOnClosed; _instance.Reconnecting += HubOnReconnecting; _instance.Reconnected += HubOnReconnected; _isDisposed = false; return _instance; } private Task HubOnClosed(Exception? arg) { Mediator.Publish(new HubClosedMessage(arg)); return Task.CompletedTask; } private Task HubOnReconnected(string? arg) { Mediator.Publish(new HubReconnectedMessage(arg)); return Task.CompletedTask; } private Task HubOnReconnecting(Exception? arg) { Mediator.Publish(new HubReconnectingMessage(arg)); return Task.CompletedTask; } }