using System; using System.Collections.Generic; using System.Linq; using System.Net; using System.Net.Http; using System.Net.Http.Headers; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.SignalR.Client; using Polly; using Refit; using Sledgemapper.Shared.Entities; namespace Sledgemapper { public class CommunicationManager { private readonly ChannelsQueue _queue = new(); public readonly Session SessionData; private AuthenticateResponse _authenticateResponse; public CommunicationManager(Session sessionData) { #if DEBUG var baseAddress = "http://localhost:5000"; #else var baseAddress = "http://hub.michelescandura.com:5000"; #endif SessionData = sessionData; Connection = new HubConnectionBuilder() .WithAutomaticReconnect() .WithUrl($"{baseAddress}/SledgemapperHub", options => { options.AccessTokenProvider = () => Task.FromResult(_authenticateResponse.Token); }) .Build(); Api = RestService.For( new HttpClient(new AuthenticatedHttpClientHandler(GetToken)) { BaseAddress = new Uri(baseAddress) } ); Connection.On("UpdateMap", map => { SessionData.Map = map.Map; SessionData.Walls = map.Walls; SessionData.Overlays = map.Overlays; }); Connection.On("PlayerUpdate", player => { var p = SessionData.Players.FirstOrDefault(m => m.UserId == player.UserId); if (p != null) p.Position = player.Position; else SessionData.Players.Add(player); }); Connection.On("DeleteTile", tile => { SessionData.Map.Remove(tile.ToString(), out var _); }); Connection.On("DeleteWall", tile => { SessionData.Walls.Remove(tile.ToString(), out var _); }); Connection.On("DeleteNote", tile => { SessionData.Notes.Remove(tile.ToString(), out var _); }); Connection.On("DeleteOverlay", tile => { SessionData.Overlays.Remove(tile.ToString(), out var _); }); Connection.On("NewTile", tile => { SessionData.Map.Remove(tile.ToString(), out var _); SessionData.Map.TryAdd(tile.ToString(), tile); }); Connection.On("NewRoom", room => { SessionData.Rooms.Remove(room.ToString(), out var _); SessionData.Rooms.TryAdd(room.ToString(), room); }); Connection.On("NewLine", line => { SessionData.Lines.Remove(line.ToString(), out var _); SessionData.Lines.TryAdd(line.ToString(), line); }); Connection.On("RefreshPlayers", () => { if (!string.IsNullOrWhiteSpace(State.Instance.MapName)) Connection?.SendAsync("UpdatePosition", State.Instance.MapName, State.Instance.MapId, SessionData.Players.First(p => p.UserId == new Guid(_authenticateResponse.Id))); }); Connection.On("RemovePlayer", player => { var p = SessionData.Players.FirstOrDefault(m => m.UserId == player.UserId); if (p != null) SessionData.Players.Remove(p); }); Connection.On("NewWall", tile => { SessionData.Walls.Remove(tile.ToString(), out var _); SessionData.Walls.TryAdd(tile.ToString(), tile); }); Connection.On("NewOverlay", tile => { SessionData.Overlays.Remove(tile.ToString(), out var _); SessionData.Overlays.TryAdd(tile.ToString(), tile); }); Connection.On("NewNote", note => { //SessionData.Notes.Remove(note.ToString(), out var _); SessionData.Notes.AddOrUpdate(note.ToString(), note, (key, oldnote) => note); }); Connection.On("NewPlayer", player => { var p = SessionData.Players.FirstOrDefault(m => m.UserId == player.UserId); if (p is null) { SessionData.Players.Add(player); } else { p.Color = player.Color; p.Position = player.Position; } }); Connection.On("Ping", ping => { SessionData.Pings.TryAdd(Guid.NewGuid(), ping); }); } public IMapApi Api { get; } public HubConnection Connection { get; } public void Enqueue(BaseMapEntity entity, TileAction action) { switch (action) { case TileAction.Add: switch (entity) { case Tile tile: _queue.Enqueue(async () => await Execute(async () => await Api.NewTile(tile, State.Instance.CampaignId, State.Instance.MapId) .ConfigureAwait(false))); break; case Overlay overlay: _queue.Enqueue(async () => await Execute(async () => await Api.NewOverlay(overlay, State.Instance.CampaignId, State.Instance.MapId) .ConfigureAwait(false))); break; case Wall wall: _queue.Enqueue(async () => await Execute(async () => await Api.NewWall(wall, State.Instance.CampaignId, State.Instance.MapId) .ConfigureAwait(false))); break; case Note note: _queue.Enqueue(async () => await Execute(async () => await Api.NewNote(note, State.Instance.CampaignId, State.Instance.MapId) .ConfigureAwait(false))); break; case Room room: _queue.Enqueue(async () => await Execute(async () => await Api.NewRoom(room, State.Instance.CampaignId, State.Instance.MapId) .ConfigureAwait(false))); break; case Line line: _queue.Enqueue(async () => await Execute(async () => await Api.NewLine(line, State.Instance.CampaignId, State.Instance.MapId) .ConfigureAwait(false))); break; } break; case TileAction.Delete: switch (entity) { case Tile tile: _queue.Enqueue(async () => await Execute(async () => await Api.DeleteTile(tile, State.Instance.CampaignId, State.Instance.MapId) .ConfigureAwait(false))); break; case Overlay overlay: _queue.Enqueue(async () => await Execute(async () => await Api.DeleteOverlay(overlay, State.Instance.CampaignId, State.Instance.MapId) .ConfigureAwait(false))); break; case Wall wall: _queue.Enqueue(async () => await Execute(async () => await Api.DeleteWall(wall, State.Instance.CampaignId, State.Instance.MapId) .ConfigureAwait(false))); break; case Note note: _queue.Enqueue(async () => await Execute(async () => await Api.DeleteNote(note, State.Instance.CampaignId, State.Instance.MapId) .ConfigureAwait(false))); break; } break; } } private async Task Execute(Func call) { await Policy .Handle(ex => ex.StatusCode == HttpStatusCode.RequestTimeout) .RetryForeverAsync() //.RetryAsync(Polly.RetrySyntax., async (exception, retryCount) => await Task.Delay(500)) .ExecuteAsync(async () => await call().ConfigureAwait(false)) .ConfigureAwait(false); } private Task GetToken() { return Task.FromResult(_authenticateResponse.Token); } public async Task Login(AuthenticateModel authenticateModel) { _authenticateResponse = await Api.Authenticate(authenticateModel).ConfigureAwait(false); return _authenticateResponse; } internal async Task Ping(Tile location) { if (Connection is { State: HubConnectionState.Connected }) { await Connection.InvokeAsync("Ping", SessionData.SessionName, location); } } public async Task Register(RegisterModel registerModel) { var result = await Api.Register(registerModel).ConfigureAwait(false); return result; } } internal class AuthenticatedHttpClientHandler : HttpClientHandler { private readonly Func> _getToken; public AuthenticatedHttpClientHandler(Func> getToken) { _getToken = getToken ?? throw new ArgumentNullException(nameof(getToken)); //if (myConfigurationService.VerifySslCertificate == false) //{ ServerCertificateCustomValidationCallback = (message, certificate, chain, sslPolicyErrors) => true; //} } protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { // See if the request has an authorize header var auth = request.Headers.Authorization; if (auth != null) { var token = await _getToken().ConfigureAwait(false); request.Headers.Authorization = new AuthenticationHeaderValue(auth.Scheme, token); } return await base.SendAsync(request, cancellationToken).ConfigureAwait(false); } } }