using Microsoft.AspNetCore.SignalR.Client; using Shogi.UI.Identity; namespace Shogi.UI.Shared; public class GameHubNode : IAsyncDisposable { private readonly HubConnection hubConnection; public GameHubNode(IConfiguration configuration) { var baseUrl = configuration["ShogiApiUrl"]; if (string.IsNullOrWhiteSpace(baseUrl)) { throw new InvalidOperationException("ShogiApiUrl configuration is missing."); } this.hubConnection = new HubConnectionBuilder() .WithUrl(new Uri(new Uri(baseUrl, UriKind.Absolute), "gamehub"), options => { options.HttpMessageHandlerFactory = handler => new CookieCredentialsMessageHandler { InnerHandler = handler }; options.SkipNegotiation = true; options.Transports = Microsoft.AspNetCore.Http.Connections.HttpTransportType.WebSockets; }) .Build(); } public bool IsConnected => this.hubConnection.State == HubConnectionState.Connected; public async Task BeginListen() { if (!this.IsConnected) { await this.hubConnection.StartAsync(); } } public async Task Subscribe(string sessionId) { await this.hubConnection.SendAsync("Subscribe", sessionId); } public async Task Unsubscribe(string sessionId) { await this.hubConnection.SendAsync("Unsubscribe", sessionId); } public IDisposable OnSessionJoined(Func func) { return this.hubConnection.On("SessionJoined", func); } public IDisposable OnPieceMoved(Func func) { return this.hubConnection.On("PieceMoved", func); } public ValueTask DisposeAsync() { GC.SuppressFinalize(this); return this.hubConnection.DisposeAsync(); } }