using Microsoft.AspNetCore.SignalR.Client; using Microsoft.Extensions.Configuration; using Polly; using Refit; using Sledgemapper.Shared.Entities; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Net; using System.Net.Http; using System.Net.Http.Headers; using System.Threading; using System.Threading.Tasks; namespace Sledgemapper { public class CommunicationManager { public IMapApi Api { get; private set; } public HubConnection Connection { get; private set; } public readonly Session SessionData; private readonly ChannelsQueue Queue = new(); 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(SessionData.SessionName)) { Connection?.SendAsync("UpdatePosition", SessionData.SessionName, SessionData.SessionId, SessionData.Players.First(p => p.UserId == int.Parse(_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); }); } private Task GetToken() { return Task.FromResult(_authenticateResponse.Token); } public async Task Register(RegisterModel registerModel) { var result = await Api.Register(registerModel).ConfigureAwait(false); return result; } public async Task Login(AuthenticateModel authenticateModel) { _authenticateResponse = await Api.Authenticate(authenticateModel).ConfigureAwait(false); return _authenticateResponse; } 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); } 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, SessionData.SessionName).ConfigureAwait(false))); break; case Overlay overlay: Queue.Enqueue(async () => await Execute(async () => await Api.NewOverlay(overlay, SessionData.SessionName).ConfigureAwait(false))); break; case Wall wall: Queue.Enqueue(async () => await Execute(async () => await Api.NewWall(wall, SessionData.SessionName).ConfigureAwait(false))); break; case Note note: Queue.Enqueue(async () => await Execute(async () => await Api.NewNote(note, SessionData.SessionName).ConfigureAwait(false))); break; case Room room: Queue.Enqueue(async () => await Execute(async () => await Api.NewRoom(room, SessionData.SessionName).ConfigureAwait(false))); break; case Line line: Queue.Enqueue(async () => await Execute(async () => await Api.NewLine(line, SessionData.SessionName).ConfigureAwait(false))); break; } break; case TileAction.Delete: switch (entity) { case Tile tile: Queue.Enqueue(async () => await Execute(async () => await Api.DeleteTile(tile, SessionData.SessionName).ConfigureAwait(false))); break; case Overlay overlay: Queue.Enqueue(async () => await Execute(async () => await Api.DeleteOverlay(overlay, SessionData.SessionName).ConfigureAwait(false))); break; case Wall wall: Queue.Enqueue(async () => await Execute(async () => await Api.DeleteWall(wall, SessionData.SessionName).ConfigureAwait(false))); break; case Note note: Queue.Enqueue(async () => await Execute(async () => await Api.DeleteNote(note, SessionData.SessionName).ConfigureAwait(false))); break; } break; } } internal async Task Ping(Tile location) { if (Connection!=null && Connection.State == HubConnectionState.Connected) { await Connection.InvokeAsync("Ping",SessionData.SessionName, location); } } } class AuthenticatedHttpClientHandler : HttpClientHandler { private readonly Func> getToken; public AuthenticatedHttpClientHandler(Func> getToken) { if (getToken == null) throw new ArgumentNullException(nameof(getToken)); this.getToken = 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); } } }