using System; using System.Collections; using System.Collections.Generic; using System.Linq; using System.Net; using System.Net.Http; using System.Text; using System.Text.Json; using System.Threading.Tasks; using Microsoft.AspNetCore.SignalR.Client; using Polly; using Polly.Retry; using Refit; using Sentry; using SharpFontInternal; using Sledgemapper.Messages; using Sledgemapper.Shared.Entities; using TinyMessenger; using Session = Sledgemapper.Shared.Entities.Session; namespace Sledgemapper { public class CommunicationManager { public TinyMessengerHub Messenger { get; } public IMapApi Api { get; } public HubConnection Connection { get; } private readonly ChannelsQueue _queue; private readonly AsyncRetryPolicy _retryPolicy; public readonly Session SessionData; private AuthenticateResponse _authenticateResponse; public CommunicationManager(Session sessionData, TinyMessengerHub messenger) { Messenger = messenger; _queue = new ChannelsQueue(Messenger); #if DEBUG var baseAddress = "http://localhost:5000"; //baseAddress = "http://hub.michelescandura.com:5001"; #else var baseAddress = "http://hub.michelescandura.com:5001"; #endif CheckLogin(); _retryPolicy = Policy .Handle(ex => ex.StatusCode == HttpStatusCode.RequestTimeout) .Or() .RetryAsync(3); //.RetryAsync(Polly.RetrySyntax., async (exception, retryCount) => await Task.Delay(500)) 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 not 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 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) .Or() .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> GetCampaigns() { try { return await _retryPolicy.ExecuteAsync(Api.GetCampaigns); } catch (Exception e) { Sentry.SentrySdk.CaptureException(e); Messenger.Publish(new ErrorMessage(this, "Error loading campaigns")); } return null; } public async Task> GetMaps(Guid campaignId) { try { return await _retryPolicy.ExecuteAsync(() => Api.GetMaps(campaignId)); } catch (Exception e) { Sentry.SentrySdk.CaptureException(e); Messenger.Publish(new ErrorMessage(this, "Error loading maps")); } return null; } public async Task> GetPlayers(Guid campaignId) { try { return await _retryPolicy.ExecuteAsync(() => Api.GetPlayers(campaignId)); } catch (Exception e) { Sentry.SentrySdk.CaptureException(e); Messenger.Publish(new ErrorMessage(this, "Error loading players")); } return null; } public async Task Login(AuthenticateModel authenticateModel) { _authenticateResponse = await Policy .Handle(ex => ex.StatusCode == HttpStatusCode.RequestTimeout) .Or() .RetryAsync(3) //.RetryAsync(Polly.RetrySyntax., async (exception, retryCount) => await Task.Delay(500)) .ExecuteAsync(async () => await Api.Authenticate(authenticateModel)) .ConfigureAwait(false); //_authenticateResponse = await Api.Authenticate(authenticateModel).ConfigureAwait(false); var data = Encoding.UTF8.GetBytes(JsonSerializer.Serialize(_authenticateResponse)); Program.Helper.SaveUnencryptedTokenCache(JsonSerializer.SerializeToUtf8Bytes(_authenticateResponse)); await Connection.StopAsync(); State.Instance.CampaignId = Guid.Empty; State.Instance.CampaignName = string.Empty; State.Instance.SessionId = Guid.Empty; State.Instance.MapId = Guid.Empty; State.Instance.MapName = string.Empty; SessionData.SessionId = Guid.Empty; SessionData.SessionName = string.Empty; SessionData.Players.Clear(); return _authenticateResponse; } internal async Task Ping(Tile location) { if (Connection is { State: HubConnectionState.Connected }) await Connection.InvokeAsync("Ping", SessionData.SessionId, location); } public async Task Register(RegisterModel registerModel) { var result = await Api.Register(registerModel).ConfigureAwait(false); return result; } public async Task ResetSignalrConnection() { try { await Connection.StopAsync(); } catch (Exception ex) { SentrySdk.CaptureException(ex); } await Connection.StartAsync(); } public void CheckLogin() { var data = Program.Helper.LoadUnencryptedTokenCache(); if (data != null && data.Any()) { _authenticateResponse = JsonSerializer.Deserialize(Encoding.UTF8.GetString(data)); Messenger.Publish(new LoginSuccesfulMessage(this) { UserName = _authenticateResponse.Username, Initials = _authenticateResponse.Initials }); } } } }