add oauth2 to client
This commit is contained in:
2
MareAPI
2
MareAPI
Submodule MareAPI updated: 4e939e8cd8...040add0608
@@ -3,7 +3,6 @@ using MareSynchronos.MareConfiguration;
|
|||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using NReco.Logging.File;
|
|
||||||
|
|
||||||
namespace MareSynchronos.Interop;
|
namespace MareSynchronos.Interop;
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ public class ServerConfig : IMareConfiguration
|
|||||||
|
|
||||||
public List<ServerStorage> ServerStorage { get; set; } = new()
|
public List<ServerStorage> ServerStorage { get; set; } = new()
|
||||||
{
|
{
|
||||||
{ new ServerStorage() { ServerName = ApiController.MainServer, ServerUri = ApiController.MainServiceUri } },
|
{ new ServerStorage() { ServerName = ApiController.MainServer, ServerUri = ApiController.MainServiceUri, UseOAuth2 = true } },
|
||||||
};
|
};
|
||||||
|
|
||||||
public bool SendCensusData { get; set; } = false;
|
public bool SendCensusData { get; set; } = false;
|
||||||
|
|||||||
@@ -6,4 +6,5 @@ public record Authentication
|
|||||||
public string CharacterName { get; set; } = string.Empty;
|
public string CharacterName { get; set; } = string.Empty;
|
||||||
public uint WorldId { get; set; } = 0;
|
public uint WorldId { get; set; } = 0;
|
||||||
public int SecretKeyIdx { get; set; } = -1;
|
public int SecretKeyIdx { get; set; } = -1;
|
||||||
|
public string? UID { get; set; }
|
||||||
}
|
}
|
||||||
@@ -8,4 +8,6 @@ public class ServerStorage
|
|||||||
public Dictionary<int, SecretKey> SecretKeys { get; set; } = [];
|
public Dictionary<int, SecretKey> SecretKeys { get; set; } = [];
|
||||||
public string ServerName { get; set; } = string.Empty;
|
public string ServerName { get; set; } = string.Empty;
|
||||||
public string ServerUri { get; set; } = string.Empty;
|
public string ServerUri { get; set; } = string.Empty;
|
||||||
|
public bool UseOAuth2 { get; set; } = false;
|
||||||
|
public string? OAuthToken { get; set; } = null;
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
using MareSynchronos.FileCache;
|
using MareSynchronos.FileCache;
|
||||||
using MareSynchronos.MareConfiguration;
|
using MareSynchronos.MareConfiguration;
|
||||||
using MareSynchronos.MareConfiguration.Models;
|
|
||||||
using MareSynchronos.PlayerData.Pairs;
|
using MareSynchronos.PlayerData.Pairs;
|
||||||
using MareSynchronos.PlayerData.Services;
|
using MareSynchronos.PlayerData.Services;
|
||||||
using MareSynchronos.Services;
|
using MareSynchronos.Services;
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
using MareSynchronos.API.Dto.User;
|
using MareSynchronos.FileCache;
|
||||||
using MareSynchronos.FileCache;
|
|
||||||
using MareSynchronos.Interop.Ipc;
|
using MareSynchronos.Interop.Ipc;
|
||||||
using MareSynchronos.PlayerData.Handlers;
|
using MareSynchronos.PlayerData.Handlers;
|
||||||
using MareSynchronos.PlayerData.Pairs;
|
using MareSynchronos.PlayerData.Pairs;
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ public sealed class CommandManagerService : IDisposable
|
|||||||
{
|
{
|
||||||
_serverConfigurationManager.CurrentServer.FullPause = fullPause;
|
_serverConfigurationManager.CurrentServer.FullPause = fullPause;
|
||||||
_serverConfigurationManager.Save();
|
_serverConfigurationManager.Save();
|
||||||
_ = _apiController.CreateConnections();
|
_ = _apiController.CreateConnectionsAsync();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (string.Equals(splitArgs[0], "gpose", StringComparison.OrdinalIgnoreCase))
|
else if (string.Equals(splitArgs[0], "gpose", StringComparison.OrdinalIgnoreCase))
|
||||||
|
|||||||
@@ -1,9 +1,15 @@
|
|||||||
using MareSynchronos.MareConfiguration;
|
using Dalamud.Utility;
|
||||||
|
using MareSynchronos.API.Routes;
|
||||||
|
using MareSynchronos.MareConfiguration;
|
||||||
using MareSynchronos.MareConfiguration.Models;
|
using MareSynchronos.MareConfiguration.Models;
|
||||||
using MareSynchronos.Services.Mediator;
|
using MareSynchronos.Services.Mediator;
|
||||||
using MareSynchronos.WebAPI;
|
using MareSynchronos.WebAPI;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
using System.IdentityModel.Tokens.Jwt;
|
||||||
|
using System.Net.Http.Json;
|
||||||
|
using System.Security.Cryptography;
|
||||||
|
using System.Text.Json;
|
||||||
|
|
||||||
namespace MareSynchronos.Services.ServerConfiguration;
|
namespace MareSynchronos.Services.ServerConfiguration;
|
||||||
|
|
||||||
@@ -76,6 +82,45 @@ public class ServerConfigurationManager
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public (string OAuthToken, string UID)? GetOAuth2(out bool hasMulti, int serverIdx = -1)
|
||||||
|
{
|
||||||
|
ServerStorage? currentServer;
|
||||||
|
currentServer = serverIdx == -1 ? CurrentServer : GetServerByIndex(serverIdx);
|
||||||
|
if (currentServer == null)
|
||||||
|
{
|
||||||
|
currentServer = new();
|
||||||
|
Save();
|
||||||
|
}
|
||||||
|
hasMulti = false;
|
||||||
|
|
||||||
|
var charaName = _dalamudUtil.GetPlayerNameAsync().GetAwaiter().GetResult();
|
||||||
|
var worldId = _dalamudUtil.GetHomeWorldIdAsync().GetAwaiter().GetResult();
|
||||||
|
|
||||||
|
var auth = currentServer.Authentications.FindAll(f => string.Equals(f.CharacterName, charaName) && f.WorldId == worldId);
|
||||||
|
if (auth.Count >= 2)
|
||||||
|
{
|
||||||
|
_logger.LogTrace("GetSecretKey accessed, returning null because multiple ({count}) identical characters.", auth.Count);
|
||||||
|
hasMulti = true;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (auth.Count == 0)
|
||||||
|
{
|
||||||
|
_logger.LogTrace("GetSecretKey accessed, returning null because no set up characters for {chara} on {world}", charaName, worldId);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(auth.Single().UID) && !string.IsNullOrEmpty(currentServer.OAuthToken))
|
||||||
|
{
|
||||||
|
_logger.LogTrace("GetSecretKey accessed, returning {key} ({keyValue}) for {chara} on {world}", auth.Single().UID, string.Join("", currentServer.OAuthToken.Take(10)), charaName, worldId);
|
||||||
|
return (currentServer.OAuthToken, auth.Single().UID!);
|
||||||
|
}
|
||||||
|
|
||||||
|
_logger.LogTrace("GetSecretKey accessed, returning null because no UID found for {chara} on {world} or OAuthToken is not configured.", charaName, worldId);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
public string? GetSecretKey(out bool hasMulti, int serverIdx = -1)
|
public string? GetSecretKey(out bool hasMulti, int serverIdx = -1)
|
||||||
{
|
{
|
||||||
ServerStorage? currentServer;
|
ServerStorage? currentServer;
|
||||||
@@ -145,6 +190,14 @@ public class ServerConfigurationManager
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public string GetDiscordUserFromToken(ServerStorage server)
|
||||||
|
{
|
||||||
|
JwtSecurityTokenHandler handler = new JwtSecurityTokenHandler();
|
||||||
|
if (server.OAuthToken == null) return string.Empty;
|
||||||
|
var token = handler.ReadJwtToken(server.OAuthToken);
|
||||||
|
return token.Claims.First(f => string.Equals(f.Type, "discord_user", StringComparison.Ordinal)).Value!;
|
||||||
|
}
|
||||||
|
|
||||||
public string[] GetServerNames()
|
public string[] GetServerNames()
|
||||||
{
|
{
|
||||||
return _configService.Current.ServerStorage.Select(v => v.ServerName).ToArray();
|
return _configService.Current.ServerStorage.Select(v => v.ServerName).ToArray();
|
||||||
@@ -152,7 +205,7 @@ public class ServerConfigurationManager
|
|||||||
|
|
||||||
public bool HasValidConfig()
|
public bool HasValidConfig()
|
||||||
{
|
{
|
||||||
return CurrentServer != null;
|
return CurrentServer != null && CurrentServer.Authentications.Count > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Save()
|
public void Save()
|
||||||
@@ -181,7 +234,7 @@ public class ServerConfigurationManager
|
|||||||
{
|
{
|
||||||
CharacterName = _dalamudUtil.GetPlayerNameAsync().GetAwaiter().GetResult(),
|
CharacterName = _dalamudUtil.GetPlayerNameAsync().GetAwaiter().GetResult(),
|
||||||
WorldId = _dalamudUtil.GetHomeWorldIdAsync().GetAwaiter().GetResult(),
|
WorldId = _dalamudUtil.GetHomeWorldIdAsync().GetAwaiter().GetResult(),
|
||||||
SecretKeyIdx = server.SecretKeys.Last().Key,
|
SecretKeyIdx = !server.UseOAuth2 ? server.SecretKeys.Last().Key : -1,
|
||||||
});
|
});
|
||||||
Save();
|
Save();
|
||||||
}
|
}
|
||||||
@@ -391,7 +444,7 @@ public class ServerConfigurationManager
|
|||||||
{
|
{
|
||||||
if (_configService.Current.ServerStorage.Count == 0 || !string.Equals(_configService.Current.ServerStorage[0].ServerUri, ApiController.MainServiceUri, StringComparison.OrdinalIgnoreCase))
|
if (_configService.Current.ServerStorage.Count == 0 || !string.Equals(_configService.Current.ServerStorage[0].ServerUri, ApiController.MainServiceUri, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
_configService.Current.ServerStorage.Insert(0, new ServerStorage() { ServerUri = ApiController.MainServiceUri, ServerName = ApiController.MainServer });
|
_configService.Current.ServerStorage.Insert(0, new ServerStorage() { ServerUri = ApiController.MainServiceUri, ServerName = ApiController.MainServer, UseOAuth2 = true });
|
||||||
}
|
}
|
||||||
Save();
|
Save();
|
||||||
}
|
}
|
||||||
@@ -411,4 +464,67 @@ public class ServerConfigurationManager
|
|||||||
_serverTagConfig.Current.ServerTagStorage[CurrentApiUrl] = new();
|
_serverTagConfig.Current.ServerTagStorage[CurrentApiUrl] = new();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<Dictionary<string, string>> GetUIDsWithDiscordToken(string serverUri, string token)
|
||||||
|
{
|
||||||
|
using HttpClient client = new HttpClient();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var baseUri = serverUri.Replace("wss://", "https://").Replace("ws://", "http://");
|
||||||
|
var oauthCheckUri = MareAuth.GetUIDs(new Uri(baseUri));
|
||||||
|
client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);
|
||||||
|
var response = await client.GetAsync(oauthCheckUri).ConfigureAwait(false);
|
||||||
|
var responseStream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
|
||||||
|
return await JsonSerializer.DeserializeAsync<Dictionary<string, string>>(responseStream).ConfigureAwait(false) ?? [];
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogWarning(ex, "Failure getting UIDs");
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Uri?> CheckDiscordOAuth(string serverUri)
|
||||||
|
{
|
||||||
|
using HttpClient client = new HttpClient();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var baseUri = serverUri.Replace("wss://", "https://").Replace("ws://", "http://");
|
||||||
|
var oauthCheckUri = MareAuth.GetDiscordOAuthEndpoint(new Uri(baseUri));
|
||||||
|
var response = await client.GetFromJsonAsync<Uri?>(oauthCheckUri).ConfigureAwait(false);
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogWarning(ex, "Failure checking for Discord Auth");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<string?> GetDiscordOAuthToken(Uri discordAuthUri, string serverUri, CancellationToken token)
|
||||||
|
{
|
||||||
|
var sessionId = BitConverter.ToString(RandomNumberGenerator.GetBytes(64)).Replace("-", "").ToLower();
|
||||||
|
Util.OpenLink(discordAuthUri.ToString() + "?sessionId=" + sessionId);
|
||||||
|
|
||||||
|
string? discordToken = null;
|
||||||
|
using HttpClient client = new HttpClient();
|
||||||
|
client.Timeout = TimeSpan.FromSeconds(60);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var baseUri = serverUri.Replace("wss://", "https://").Replace("ws://", "http://");
|
||||||
|
var oauthCheckUri = MareAuth.GetDiscordOAuthToken(new Uri(baseUri), sessionId);
|
||||||
|
var response = await client.GetAsync(oauthCheckUri, token).ConfigureAwait(false);
|
||||||
|
discordToken = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogWarning(ex, "Failure getting Discord Token");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (discordToken == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
return discordToken;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -260,7 +260,7 @@ public class CompactUi : WindowMediatorSubscriberBase
|
|||||||
|
|
||||||
_serverManager.Save();
|
_serverManager.Save();
|
||||||
|
|
||||||
_ = _apiController.CreateConnections();
|
_ = _apiController.CreateConnectionsAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
_uiSharedService.DrawCombo("Secret Key##addCharacterSecretKey", keys, (f) => f.Value.FriendlyName, (f) => _secretKeyIdx = f.Key);
|
_uiSharedService.DrawCombo("Secret Key##addCharacterSecretKey", keys, (f) => f.Value.FriendlyName, (f) => _secretKeyIdx = f.Key);
|
||||||
@@ -345,7 +345,7 @@ public class CompactUi : WindowMediatorSubscriberBase
|
|||||||
{
|
{
|
||||||
_serverManager.CurrentServer.FullPause = !_serverManager.CurrentServer.FullPause;
|
_serverManager.CurrentServer.FullPause = !_serverManager.CurrentServer.FullPause;
|
||||||
_serverManager.Save();
|
_serverManager.Save();
|
||||||
_ = _apiController.CreateConnections();
|
_ = _apiController.CreateConnectionsAsync();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -446,8 +446,18 @@ public class CompactUi : WindowMediatorSubscriberBase
|
|||||||
{
|
{
|
||||||
DrawAddCharacter();
|
DrawAddCharacter();
|
||||||
}
|
}
|
||||||
|
if (_apiController.ServerState is ServerState.OAuthLoginTokenStale)
|
||||||
|
{
|
||||||
|
DrawRenewOAuth2();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawRenewOAuth2()
|
||||||
|
{
|
||||||
|
ImGuiHelpers.ScaledDummy(10f);
|
||||||
|
// add some text and a button to restart discord authentication
|
||||||
|
}
|
||||||
|
|
||||||
private IEnumerable<IDrawFolder> GetDrawFolders()
|
private IEnumerable<IDrawFolder> GetDrawFolders()
|
||||||
{
|
{
|
||||||
@@ -599,6 +609,8 @@ public class CompactUi : WindowMediatorSubscriberBase
|
|||||||
ServerState.Connected => string.Empty,
|
ServerState.Connected => string.Empty,
|
||||||
ServerState.NoSecretKey => "You have no secret key set for this current character. Use the button below or open the settings and set a secret key for the current character. You can reuse the same secret key for multiple characters.",
|
ServerState.NoSecretKey => "You have no secret key set for this current character. Use the button below or open the settings and set a secret key for the current character. You can reuse the same secret key for multiple characters.",
|
||||||
ServerState.MultiChara => "Your Character Configuration has multiple characters configured with same name and world. You will not be able to connect until you fix this issue. Remove the duplicates from the configuration in Settings -> Service Settings -> Character Management and reconnect manually after.",
|
ServerState.MultiChara => "Your Character Configuration has multiple characters configured with same name and world. You will not be able to connect until you fix this issue. Remove the duplicates from the configuration in Settings -> Service Settings -> Character Management and reconnect manually after.",
|
||||||
|
ServerState.OAuthMisconfigured => "OAuth2 is enabled but not fully configured, verify in the Settings -> Service Settings that you have OAuth2 connected and functioning and a UID assigned to your current character.",
|
||||||
|
ServerState.OAuthLoginTokenStale => "Your OAuth2 login token is stale and cannot be used to renew. Go to the Settings -> Service Settings and unlink then relink your OAuth2 configuration.",
|
||||||
_ => string.Empty
|
_ => string.Empty
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -618,6 +630,8 @@ public class CompactUi : WindowMediatorSubscriberBase
|
|||||||
ServerState.RateLimited => ImGuiColors.DalamudYellow,
|
ServerState.RateLimited => ImGuiColors.DalamudYellow,
|
||||||
ServerState.NoSecretKey => ImGuiColors.DalamudYellow,
|
ServerState.NoSecretKey => ImGuiColors.DalamudYellow,
|
||||||
ServerState.MultiChara => ImGuiColors.DalamudYellow,
|
ServerState.MultiChara => ImGuiColors.DalamudYellow,
|
||||||
|
ServerState.OAuthMisconfigured => ImGuiColors.DalamudRed,
|
||||||
|
ServerState.OAuthLoginTokenStale => ImGuiColors.DalamudRed,
|
||||||
_ => ImGuiColors.DalamudRed
|
_ => ImGuiColors.DalamudRed
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -636,6 +650,8 @@ public class CompactUi : WindowMediatorSubscriberBase
|
|||||||
ServerState.RateLimited => "Rate Limited",
|
ServerState.RateLimited => "Rate Limited",
|
||||||
ServerState.NoSecretKey => "No Secret Key",
|
ServerState.NoSecretKey => "No Secret Key",
|
||||||
ServerState.MultiChara => "Duplicate Characters",
|
ServerState.MultiChara => "Duplicate Characters",
|
||||||
|
ServerState.OAuthMisconfigured => "Misconfigured OAuth2",
|
||||||
|
ServerState.OAuthLoginTokenStale => "Stale OAuth2",
|
||||||
ServerState.Connected => _apiController.DisplayName,
|
ServerState.Connected => _apiController.DisplayName,
|
||||||
_ => string.Empty
|
_ => string.Empty
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -93,7 +93,7 @@ public class DrawUserPair
|
|||||||
|
|
||||||
if (_uiSharedService.IconTextButton(FontAwesomeIcon.PlayCircle, "Cycle pause state", _menuWidth, true))
|
if (_uiSharedService.IconTextButton(FontAwesomeIcon.PlayCircle, "Cycle pause state", _menuWidth, true))
|
||||||
{
|
{
|
||||||
_ = _apiController.CyclePause(_pair.UserData);
|
_ = _apiController.CyclePauseAsync(_pair.UserData);
|
||||||
ImGui.CloseCurrentPopup();
|
ImGui.CloseCurrentPopup();
|
||||||
}
|
}
|
||||||
ImGui.Separator();
|
ImGui.Separator();
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using Dalamud.Interface.Colors;
|
using Dalamud.Interface.Colors;
|
||||||
using Dalamud.Interface.Utility;
|
using Dalamud.Interface.Utility;
|
||||||
|
using Dalamud.Interface.Utility.Raii;
|
||||||
using Dalamud.Utility;
|
using Dalamud.Utility;
|
||||||
using ImGuiNET;
|
using ImGuiNET;
|
||||||
using MareSynchronos.FileCache;
|
using MareSynchronos.FileCache;
|
||||||
@@ -30,6 +31,7 @@ public partial class IntroUi : WindowMediatorSubscriberBase
|
|||||||
private string _timeoutLabel = string.Empty;
|
private string _timeoutLabel = string.Empty;
|
||||||
private Task? _timeoutTask;
|
private Task? _timeoutTask;
|
||||||
private string[]? _tosParagraphs;
|
private string[]? _tosParagraphs;
|
||||||
|
private bool _useLegacyLogin = false;
|
||||||
|
|
||||||
public IntroUi(ILogger<IntroUi> logger, UiSharedService uiShared, MareConfigService configService,
|
public IntroUi(ILogger<IntroUi> logger, UiSharedService uiShared, MareConfigService configService,
|
||||||
CacheMonitor fileCacheManager, ServerConfigurationManager serverConfigurationManager, MareMediator mareMediator,
|
CacheMonitor fileCacheManager, ServerConfigurationManager serverConfigurationManager, MareMediator mareMediator,
|
||||||
@@ -60,6 +62,8 @@ public partial class IntroUi : WindowMediatorSubscriberBase
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private int _prevIdx = -1;
|
||||||
|
|
||||||
protected override void DrawInternal()
|
protected override void DrawInternal()
|
||||||
{
|
{
|
||||||
if (_uiShared.IsInGpose) return;
|
if (_uiShared.IsInGpose) return;
|
||||||
@@ -216,8 +220,23 @@ public partial class IntroUi : WindowMediatorSubscriberBase
|
|||||||
|
|
||||||
UiSharedService.TextWrapped("Once you have received a secret key you can connect to the service using the tools provided below.");
|
UiSharedService.TextWrapped("Once you have received a secret key you can connect to the service using the tools provided below.");
|
||||||
|
|
||||||
_ = _uiShared.DrawServiceSelection(selectOnChange: true);
|
var serverIdx = _uiShared.DrawServiceSelection(selectOnChange: true, showConnect: false);
|
||||||
|
if (serverIdx != _prevIdx)
|
||||||
|
{
|
||||||
|
_uiShared.RestOAuthTasksState();
|
||||||
|
_prevIdx = serverIdx;
|
||||||
|
}
|
||||||
|
var selectedServer = _serverConfigurationManager.GetServerByIndex(serverIdx);
|
||||||
|
_useLegacyLogin = !selectedServer.UseOAuth2;
|
||||||
|
|
||||||
|
if (ImGui.Checkbox("Use Legacy Login with Secret Key", ref _useLegacyLogin))
|
||||||
|
{
|
||||||
|
_serverConfigurationManager.GetServerByIndex(serverIdx).UseOAuth2 = !_useLegacyLogin;
|
||||||
|
_serverConfigurationManager.Save();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_useLegacyLogin)
|
||||||
|
{
|
||||||
var text = "Enter Secret Key";
|
var text = "Enter Secret Key";
|
||||||
var buttonText = "Save";
|
var buttonText = "Save";
|
||||||
var buttonWidth = _secretKey.Length != 64 ? 0 : ImGuiHelpers.GetButtonSize(buttonText).X + ImGui.GetStyle().ItemSpacing.X;
|
var buttonWidth = _secretKey.Length != 64 ? 0 : ImGuiHelpers.GetButtonSize(buttonText).X + ImGui.GetStyle().ItemSpacing.X;
|
||||||
@@ -259,7 +278,53 @@ public partial class IntroUi : WindowMediatorSubscriberBase
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
_secretKey = string.Empty;
|
_secretKey = string.Empty;
|
||||||
_ = Task.Run(() => _uiShared.ApiController.CreateConnections());
|
_ = Task.Run(() => _uiShared.ApiController.CreateConnectionsAsync());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (selectedServer.OAuthToken == null)
|
||||||
|
{
|
||||||
|
UiSharedService.TextWrapped("Press the button below to verify the server has OAuth2 capabilities. Afterwards, authenticate using Discord in the Browser window.");
|
||||||
|
_uiShared.DrawOAuth(selectedServer);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
UiSharedService.ColorTextWrapped($"OAuth2 is enabled, linked to: Discord User {_serverConfigurationManager.GetDiscordUserFromToken(selectedServer)}", ImGuiColors.HealerGreen);
|
||||||
|
UiSharedService.TextWrapped("Now press the update UIDs button to get a list of all of your UIDs on the server.");
|
||||||
|
_uiShared.DrawUpdateOAuthUIDsButton(selectedServer);
|
||||||
|
var playerName = _dalamudUtilService.GetPlayerName();
|
||||||
|
var playerWorld = _dalamudUtilService.GetHomeWorldId();
|
||||||
|
UiSharedService.TextWrapped($"Once pressed, select the UID you want to use for your current character {_dalamudUtilService.GetPlayerName()}. If no UIDs are visible, make sure you are connected to the correct Discord account. " +
|
||||||
|
$"If that is not the case, use the unlink button below (hold CTRL to unlink).");
|
||||||
|
_uiShared.DrawUnlinkOAuthButton(selectedServer);
|
||||||
|
|
||||||
|
var auth = selectedServer.Authentications.Find(a => string.Equals(a.CharacterName, playerName, StringComparison.Ordinal) && a.WorldId == playerWorld);
|
||||||
|
if (auth == null)
|
||||||
|
{
|
||||||
|
selectedServer.Authentications.Add(new Authentication()
|
||||||
|
{
|
||||||
|
CharacterName = playerName,
|
||||||
|
WorldId = playerWorld
|
||||||
|
});
|
||||||
|
_serverConfigurationManager.Save();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (auth != null)
|
||||||
|
{
|
||||||
|
_uiShared.DrawUIDComboForAuthentication(0, auth, selectedServer.ServerUri);
|
||||||
|
|
||||||
|
using (ImRaii.Disabled(string.IsNullOrEmpty(auth.UID)))
|
||||||
|
{
|
||||||
|
if (_uiShared.IconTextButton(Dalamud.Interface.FontAwesomeIcon.Link, "Connect to Service"))
|
||||||
|
{
|
||||||
|
_ = Task.Run(() => _uiShared.ApiController.CreateConnectionsAsync());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (string.IsNullOrEmpty(auth.UID))
|
||||||
|
UiSharedService.AttachToolTip("Select a UID to be able to connect to the service");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,36 +31,37 @@ namespace MareSynchronos.UI;
|
|||||||
public class SettingsUi : WindowMediatorSubscriberBase
|
public class SettingsUi : WindowMediatorSubscriberBase
|
||||||
{
|
{
|
||||||
private readonly ApiController _apiController;
|
private readonly ApiController _apiController;
|
||||||
private readonly IpcManager _ipcManager;
|
|
||||||
private readonly CacheMonitor _cacheMonitor;
|
private readonly CacheMonitor _cacheMonitor;
|
||||||
private readonly DalamudUtilService _dalamudUtilService;
|
|
||||||
private readonly MareConfigService _configService;
|
private readonly MareConfigService _configService;
|
||||||
private readonly ConcurrentDictionary<GameObjectHandler, Dictionary<string, FileDownloadStatus>> _currentDownloads = new();
|
private readonly ConcurrentDictionary<GameObjectHandler, Dictionary<string, FileDownloadStatus>> _currentDownloads = new();
|
||||||
|
private readonly DalamudUtilService _dalamudUtilService;
|
||||||
|
private readonly FileCacheManager _fileCacheManager;
|
||||||
private readonly FileCompactor _fileCompactor;
|
private readonly FileCompactor _fileCompactor;
|
||||||
private readonly FileUploadManager _fileTransferManager;
|
private readonly FileUploadManager _fileTransferManager;
|
||||||
private readonly FileTransferOrchestrator _fileTransferOrchestrator;
|
private readonly FileTransferOrchestrator _fileTransferOrchestrator;
|
||||||
private readonly FileCacheManager _fileCacheManager;
|
private readonly IpcManager _ipcManager;
|
||||||
private readonly MareCharaFileManager _mareCharaFileManager;
|
private readonly MareCharaFileManager _mareCharaFileManager;
|
||||||
private readonly PairManager _pairManager;
|
private readonly PairManager _pairManager;
|
||||||
private readonly PerformanceCollectorService _performanceCollector;
|
private readonly PerformanceCollectorService _performanceCollector;
|
||||||
private readonly ServerConfigurationManager _serverConfigurationManager;
|
|
||||||
private readonly PlayerPerformanceConfigService _playerPerformanceConfigService;
|
private readonly PlayerPerformanceConfigService _playerPerformanceConfigService;
|
||||||
|
private readonly ServerConfigurationManager _serverConfigurationManager;
|
||||||
private readonly UiSharedService _uiShared;
|
private readonly UiSharedService _uiShared;
|
||||||
|
private readonly IProgress<(int, int, FileCacheEntity)> _validationProgress;
|
||||||
|
private (int, int, FileCacheEntity) _currentProgress;
|
||||||
private bool _deleteAccountPopupModalShown = false;
|
private bool _deleteAccountPopupModalShown = false;
|
||||||
private bool _deleteFilesPopupModalShown = false;
|
private bool _deleteFilesPopupModalShown = false;
|
||||||
private string _exportDescription = string.Empty;
|
private string _exportDescription = string.Empty;
|
||||||
|
private Task? _exportTask;
|
||||||
private string _lastTab = string.Empty;
|
private string _lastTab = string.Empty;
|
||||||
private bool? _notesSuccessfullyApplied = null;
|
private bool? _notesSuccessfullyApplied = null;
|
||||||
private bool _overwriteExistingLabels = false;
|
private bool _overwriteExistingLabels = false;
|
||||||
private bool _readClearCache = false;
|
private bool _readClearCache = false;
|
||||||
private bool _readExport = false;
|
private bool _readExport = false;
|
||||||
private bool _wasOpen = false;
|
private int _selectedEntry = -1;
|
||||||
private readonly IProgress<(int, int, FileCacheEntity)> _validationProgress;
|
private string _uidToAddForIgnore = string.Empty;
|
||||||
private Task<List<FileCacheEntity>>? _validationTask;
|
|
||||||
private CancellationTokenSource? _validationCts;
|
private CancellationTokenSource? _validationCts;
|
||||||
private (int, int, FileCacheEntity) _currentProgress;
|
private Task<List<FileCacheEntity>>? _validationTask;
|
||||||
private Task? _exportTask;
|
private bool _wasOpen = false;
|
||||||
|
|
||||||
public SettingsUi(ILogger<SettingsUi> logger,
|
public SettingsUi(ILogger<SettingsUi> logger,
|
||||||
UiSharedService uiShared, MareConfigService configService,
|
UiSharedService uiShared, MareConfigService configService,
|
||||||
MareCharaFileManager mareCharaFileManager, PairManager pairManager,
|
MareCharaFileManager mareCharaFileManager, PairManager pairManager,
|
||||||
@@ -111,11 +112,9 @@ public class SettingsUi : WindowMediatorSubscriberBase
|
|||||||
public CharacterData? LastCreatedCharacterData { private get; set; }
|
public CharacterData? LastCreatedCharacterData { private get; set; }
|
||||||
private ApiController ApiController => _uiShared.ApiController;
|
private ApiController ApiController => _uiShared.ApiController;
|
||||||
|
|
||||||
protected override void DrawInternal()
|
public override void OnOpen()
|
||||||
{
|
{
|
||||||
_ = _uiShared.DrawOtherPluginState();
|
_uiShared.RestOAuthTasksState();
|
||||||
|
|
||||||
DrawSettingsContent();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void OnClose()
|
public override void OnClose()
|
||||||
@@ -126,6 +125,43 @@ public class SettingsUi : WindowMediatorSubscriberBase
|
|||||||
base.OnClose();
|
base.OnClose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void DrawInternal()
|
||||||
|
{
|
||||||
|
_ = _uiShared.DrawOtherPluginState();
|
||||||
|
|
||||||
|
DrawSettingsContent();
|
||||||
|
}
|
||||||
|
private static bool InputDtrColors(string label, ref DtrEntry.Colors colors)
|
||||||
|
{
|
||||||
|
using var id = ImRaii.PushId(label);
|
||||||
|
var innerSpacing = ImGui.GetStyle().ItemInnerSpacing.X;
|
||||||
|
var foregroundColor = ConvertColor(colors.Foreground);
|
||||||
|
var glowColor = ConvertColor(colors.Glow);
|
||||||
|
|
||||||
|
var ret = ImGui.ColorEdit3("###foreground", ref foregroundColor, ImGuiColorEditFlags.NoInputs | ImGuiColorEditFlags.NoLabel | ImGuiColorEditFlags.Uint8);
|
||||||
|
if (ImGui.IsItemHovered())
|
||||||
|
ImGui.SetTooltip("Foreground Color - Set to pure black (#000000) to use the default color");
|
||||||
|
|
||||||
|
ImGui.SameLine(0.0f, innerSpacing);
|
||||||
|
ret |= ImGui.ColorEdit3("###glow", ref glowColor, ImGuiColorEditFlags.NoInputs | ImGuiColorEditFlags.NoLabel | ImGuiColorEditFlags.Uint8);
|
||||||
|
if (ImGui.IsItemHovered())
|
||||||
|
ImGui.SetTooltip("Glow Color - Set to pure black (#000000) to use the default color");
|
||||||
|
|
||||||
|
ImGui.SameLine(0.0f, innerSpacing);
|
||||||
|
ImGui.TextUnformatted(label);
|
||||||
|
|
||||||
|
if (ret)
|
||||||
|
colors = new(ConvertBackColor(foregroundColor), ConvertBackColor(glowColor));
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
static Vector3 ConvertColor(uint color)
|
||||||
|
=> unchecked(new((byte)color / 255.0f, (byte)(color >> 8) / 255.0f, (byte)(color >> 16) / 255.0f));
|
||||||
|
|
||||||
|
static uint ConvertBackColor(Vector3 color)
|
||||||
|
=> byte.CreateSaturating(color.X * 255.0f) | ((uint)byte.CreateSaturating(color.Y * 255.0f) << 8) | ((uint)byte.CreateSaturating(color.Z * 255.0f) << 16);
|
||||||
|
}
|
||||||
|
|
||||||
private void DrawBlockedTransfers()
|
private void DrawBlockedTransfers()
|
||||||
{
|
{
|
||||||
_lastTab = "BlockedTransfers";
|
_lastTab = "BlockedTransfers";
|
||||||
@@ -975,410 +1011,6 @@ public class SettingsUi : WindowMediatorSubscriberBase
|
|||||||
_uiShared.DrawHelpText("Enabling this will only show online notifications (type: Info) for pairs where you have set an individual note.");
|
_uiShared.DrawHelpText("Enabling this will only show online notifications (type: Info) for pairs where you have set an individual note.");
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool InputDtrColors(string label, ref DtrEntry.Colors colors)
|
|
||||||
{
|
|
||||||
using var id = ImRaii.PushId(label);
|
|
||||||
var innerSpacing = ImGui.GetStyle().ItemInnerSpacing.X;
|
|
||||||
var foregroundColor = ConvertColor(colors.Foreground);
|
|
||||||
var glowColor = ConvertColor(colors.Glow);
|
|
||||||
|
|
||||||
var ret = ImGui.ColorEdit3("###foreground", ref foregroundColor, ImGuiColorEditFlags.NoInputs | ImGuiColorEditFlags.NoLabel | ImGuiColorEditFlags.Uint8);
|
|
||||||
if (ImGui.IsItemHovered())
|
|
||||||
ImGui.SetTooltip("Foreground Color - Set to pure black (#000000) to use the default color");
|
|
||||||
|
|
||||||
ImGui.SameLine(0.0f, innerSpacing);
|
|
||||||
ret |= ImGui.ColorEdit3("###glow", ref glowColor, ImGuiColorEditFlags.NoInputs | ImGuiColorEditFlags.NoLabel | ImGuiColorEditFlags.Uint8);
|
|
||||||
if (ImGui.IsItemHovered())
|
|
||||||
ImGui.SetTooltip("Glow Color - Set to pure black (#000000) to use the default color");
|
|
||||||
|
|
||||||
ImGui.SameLine(0.0f, innerSpacing);
|
|
||||||
ImGui.TextUnformatted(label);
|
|
||||||
|
|
||||||
if (ret)
|
|
||||||
colors = new(ConvertBackColor(foregroundColor), ConvertBackColor(glowColor));
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
|
|
||||||
static Vector3 ConvertColor(uint color)
|
|
||||||
=> unchecked(new((byte)color / 255.0f, (byte)(color >> 8) / 255.0f, (byte)(color >> 16) / 255.0f));
|
|
||||||
|
|
||||||
static uint ConvertBackColor(Vector3 color)
|
|
||||||
=> byte.CreateSaturating(color.X * 255.0f) | ((uint)byte.CreateSaturating(color.Y * 255.0f) << 8) | ((uint)byte.CreateSaturating(color.Z * 255.0f) << 16);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void DrawServerConfiguration()
|
|
||||||
{
|
|
||||||
_lastTab = "Service Settings";
|
|
||||||
if (ApiController.ServerAlive)
|
|
||||||
{
|
|
||||||
_uiShared.BigText("Service Actions");
|
|
||||||
ImGuiHelpers.ScaledDummy(new Vector2(5, 5));
|
|
||||||
if (ImGui.Button("Delete all my files"))
|
|
||||||
{
|
|
||||||
_deleteFilesPopupModalShown = true;
|
|
||||||
ImGui.OpenPopup("Delete all your files?");
|
|
||||||
}
|
|
||||||
|
|
||||||
_uiShared.DrawHelpText("Completely deletes all your uploaded files on the service.");
|
|
||||||
|
|
||||||
if (ImGui.BeginPopupModal("Delete all your files?", ref _deleteFilesPopupModalShown, UiSharedService.PopupWindowFlags))
|
|
||||||
{
|
|
||||||
UiSharedService.TextWrapped(
|
|
||||||
"All your own uploaded files on the service will be deleted.\nThis operation cannot be undone.");
|
|
||||||
ImGui.TextUnformatted("Are you sure you want to continue?");
|
|
||||||
ImGui.Separator();
|
|
||||||
ImGui.Spacing();
|
|
||||||
|
|
||||||
var buttonSize = (ImGui.GetWindowContentRegionMax().X - ImGui.GetWindowContentRegionMin().X -
|
|
||||||
ImGui.GetStyle().ItemSpacing.X) / 2;
|
|
||||||
|
|
||||||
if (ImGui.Button("Delete everything", new Vector2(buttonSize, 0)))
|
|
||||||
{
|
|
||||||
_ = Task.Run(_fileTransferManager.DeleteAllFiles);
|
|
||||||
_deleteFilesPopupModalShown = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui.SameLine();
|
|
||||||
|
|
||||||
if (ImGui.Button("Cancel##cancelDelete", new Vector2(buttonSize, 0)))
|
|
||||||
{
|
|
||||||
_deleteFilesPopupModalShown = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
UiSharedService.SetScaledWindowSize(325);
|
|
||||||
ImGui.EndPopup();
|
|
||||||
}
|
|
||||||
ImGui.SameLine();
|
|
||||||
if (ImGui.Button("Delete account"))
|
|
||||||
{
|
|
||||||
_deleteAccountPopupModalShown = true;
|
|
||||||
ImGui.OpenPopup("Delete your account?");
|
|
||||||
}
|
|
||||||
|
|
||||||
_uiShared.DrawHelpText("Completely deletes your account and all uploaded files to the service.");
|
|
||||||
|
|
||||||
if (ImGui.BeginPopupModal("Delete your account?", ref _deleteAccountPopupModalShown, UiSharedService.PopupWindowFlags))
|
|
||||||
{
|
|
||||||
UiSharedService.TextWrapped(
|
|
||||||
"Your account and all associated files and data on the service will be deleted.");
|
|
||||||
UiSharedService.TextWrapped("Your UID will be removed from all pairing lists.");
|
|
||||||
ImGui.TextUnformatted("Are you sure you want to continue?");
|
|
||||||
ImGui.Separator();
|
|
||||||
ImGui.Spacing();
|
|
||||||
|
|
||||||
var buttonSize = (ImGui.GetWindowContentRegionMax().X - ImGui.GetWindowContentRegionMin().X -
|
|
||||||
ImGui.GetStyle().ItemSpacing.X) / 2;
|
|
||||||
|
|
||||||
if (ImGui.Button("Delete account", new Vector2(buttonSize, 0)))
|
|
||||||
{
|
|
||||||
_ = Task.Run(ApiController.UserDelete);
|
|
||||||
_deleteAccountPopupModalShown = false;
|
|
||||||
Mediator.Publish(new SwitchToIntroUiMessage());
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui.SameLine();
|
|
||||||
|
|
||||||
if (ImGui.Button("Cancel##cancelDelete", new Vector2(buttonSize, 0)))
|
|
||||||
{
|
|
||||||
_deleteAccountPopupModalShown = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
UiSharedService.SetScaledWindowSize(325);
|
|
||||||
ImGui.EndPopup();
|
|
||||||
}
|
|
||||||
ImGui.Separator();
|
|
||||||
}
|
|
||||||
|
|
||||||
_uiShared.BigText("Service & Character Settings");
|
|
||||||
ImGuiHelpers.ScaledDummy(new Vector2(5, 5));
|
|
||||||
var sendCensus = _serverConfigurationManager.SendCensusData;
|
|
||||||
if (ImGui.Checkbox("Send Statistical Census Data", ref sendCensus))
|
|
||||||
{
|
|
||||||
_serverConfigurationManager.SendCensusData = sendCensus;
|
|
||||||
}
|
|
||||||
_uiShared.DrawHelpText("This will allow sending census data to the currently connected service." + UiSharedService.TooltipSeparator
|
|
||||||
+ "Census data contains:" + Environment.NewLine
|
|
||||||
+ "- Current World" + Environment.NewLine
|
|
||||||
+ "- Current Gender" + Environment.NewLine
|
|
||||||
+ "- Current Race" + Environment.NewLine
|
|
||||||
+ "- Current Clan (this is not your Free Company, this is e.g. Keeper or Seeker for Miqo'te)" + UiSharedService.TooltipSeparator
|
|
||||||
+ "The census data is only saved temporarily and will be removed from the server on disconnect. It is stored temporarily associated with your UID while you are connected." + UiSharedService.TooltipSeparator
|
|
||||||
+ "If you do not wish to participate in the statistical census, untick this box and reconnect to the server.");
|
|
||||||
ImGuiHelpers.ScaledDummy(new Vector2(10, 10));
|
|
||||||
|
|
||||||
var idx = _uiShared.DrawServiceSelection();
|
|
||||||
|
|
||||||
ImGuiHelpers.ScaledDummy(new Vector2(10, 10));
|
|
||||||
|
|
||||||
var selectedServer = _serverConfigurationManager.GetServerByIndex(idx);
|
|
||||||
if (selectedServer == _serverConfigurationManager.CurrentServer)
|
|
||||||
{
|
|
||||||
UiSharedService.ColorTextWrapped("For any changes to be applied to the current service you need to reconnect to the service.", ImGuiColors.DalamudYellow);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ImGui.BeginTabBar("serverTabBar"))
|
|
||||||
{
|
|
||||||
if (ImGui.BeginTabItem("Character Management"))
|
|
||||||
{
|
|
||||||
if (selectedServer.SecretKeys.Any())
|
|
||||||
{
|
|
||||||
UiSharedService.ColorTextWrapped("Characters listed here will automatically connect to the selected Mare service with the settings as provided below." +
|
|
||||||
" Make sure to enter the character names correctly or use the 'Add current character' button at the bottom.", ImGuiColors.DalamudYellow);
|
|
||||||
int i = 0;
|
|
||||||
foreach (var item in selectedServer.Authentications.ToList())
|
|
||||||
{
|
|
||||||
using var charaId = ImRaii.PushId("selectedChara" + i);
|
|
||||||
|
|
||||||
var worldIdx = (ushort)item.WorldId;
|
|
||||||
var data = _uiShared.WorldData.OrderBy(u => u.Value, StringComparer.Ordinal).ToDictionary(k => k.Key, k => k.Value);
|
|
||||||
if (!data.TryGetValue(worldIdx, out string? worldPreview))
|
|
||||||
{
|
|
||||||
worldPreview = data.First().Value;
|
|
||||||
}
|
|
||||||
|
|
||||||
var secretKeyIdx = item.SecretKeyIdx;
|
|
||||||
var keys = selectedServer.SecretKeys;
|
|
||||||
if (!keys.TryGetValue(secretKeyIdx, out var secretKey))
|
|
||||||
{
|
|
||||||
secretKey = new();
|
|
||||||
}
|
|
||||||
var friendlyName = secretKey.FriendlyName;
|
|
||||||
|
|
||||||
bool thisIsYou = false;
|
|
||||||
if (string.Equals(_dalamudUtilService.GetPlayerName(), item.CharacterName, StringComparison.OrdinalIgnoreCase)
|
|
||||||
&& _dalamudUtilService.GetWorldId() == worldIdx)
|
|
||||||
{
|
|
||||||
thisIsYou = true;
|
|
||||||
}
|
|
||||||
if (ImGui.TreeNode($"chara", (thisIsYou ? "[CURRENT] " : "") + $"Character: {item.CharacterName}, World: {worldPreview}, Secret Key: {friendlyName}"))
|
|
||||||
{
|
|
||||||
var charaName = item.CharacterName;
|
|
||||||
if (ImGui.InputText("Character Name", ref charaName, 64))
|
|
||||||
{
|
|
||||||
item.CharacterName = charaName;
|
|
||||||
_serverConfigurationManager.Save();
|
|
||||||
}
|
|
||||||
|
|
||||||
_uiShared.DrawCombo("World##" + item.CharacterName + i, data, (w) => w.Value,
|
|
||||||
(w) =>
|
|
||||||
{
|
|
||||||
if (item.WorldId != w.Key)
|
|
||||||
{
|
|
||||||
item.WorldId = w.Key;
|
|
||||||
_serverConfigurationManager.Save();
|
|
||||||
}
|
|
||||||
}, EqualityComparer<KeyValuePair<ushort, string>>.Default.Equals(data.FirstOrDefault(f => f.Key == worldIdx), default) ? data.First() : data.First(f => f.Key == worldIdx));
|
|
||||||
|
|
||||||
_uiShared.DrawCombo("Secret Key##" + item.CharacterName + i, keys, (w) => w.Value.FriendlyName,
|
|
||||||
(w) =>
|
|
||||||
{
|
|
||||||
if (w.Key != item.SecretKeyIdx)
|
|
||||||
{
|
|
||||||
item.SecretKeyIdx = w.Key;
|
|
||||||
_serverConfigurationManager.Save();
|
|
||||||
}
|
|
||||||
}, EqualityComparer<KeyValuePair<int, SecretKey>>.Default.Equals(keys.FirstOrDefault(f => f.Key == item.SecretKeyIdx), default) ? keys.First() : keys.First(f => f.Key == item.SecretKeyIdx));
|
|
||||||
|
|
||||||
if (_uiShared.IconTextButton(FontAwesomeIcon.Trash, "Delete Character") && UiSharedService.CtrlPressed())
|
|
||||||
_serverConfigurationManager.RemoveCharacterFromServer(idx, item);
|
|
||||||
UiSharedService.AttachToolTip("Hold CTRL to delete this entry.");
|
|
||||||
|
|
||||||
ImGui.TreePop();
|
|
||||||
}
|
|
||||||
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui.Separator();
|
|
||||||
if (!selectedServer.Authentications.Exists(c => string.Equals(c.CharacterName, _uiShared.PlayerName, StringComparison.Ordinal)
|
|
||||||
&& c.WorldId == _uiShared.WorldId))
|
|
||||||
{
|
|
||||||
if (_uiShared.IconTextButton(FontAwesomeIcon.User, "Add current character"))
|
|
||||||
{
|
|
||||||
_serverConfigurationManager.AddCurrentCharacterToServer(idx);
|
|
||||||
}
|
|
||||||
ImGui.SameLine();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_uiShared.IconTextButton(FontAwesomeIcon.Plus, "Add new character"))
|
|
||||||
{
|
|
||||||
_serverConfigurationManager.AddEmptyCharacterToServer(idx);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
UiSharedService.ColorTextWrapped("You need to add a Secret Key first before adding Characters.", ImGuiColors.DalamudYellow);
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui.EndTabItem();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ImGui.BeginTabItem("Secret Key Management"))
|
|
||||||
{
|
|
||||||
foreach (var item in selectedServer.SecretKeys.ToList())
|
|
||||||
{
|
|
||||||
using var id = ImRaii.PushId("key" + item.Key);
|
|
||||||
var friendlyName = item.Value.FriendlyName;
|
|
||||||
if (ImGui.InputText("Secret Key Display Name", ref friendlyName, 255))
|
|
||||||
{
|
|
||||||
item.Value.FriendlyName = friendlyName;
|
|
||||||
_serverConfigurationManager.Save();
|
|
||||||
}
|
|
||||||
var key = item.Value.Key;
|
|
||||||
if (ImGui.InputText("Secret Key", ref key, 64))
|
|
||||||
{
|
|
||||||
item.Value.Key = key;
|
|
||||||
_serverConfigurationManager.Save();
|
|
||||||
}
|
|
||||||
if (!selectedServer.Authentications.Exists(p => p.SecretKeyIdx == item.Key))
|
|
||||||
{
|
|
||||||
if (_uiShared.IconTextButton(FontAwesomeIcon.Trash, "Delete Secret Key") && UiSharedService.CtrlPressed())
|
|
||||||
{
|
|
||||||
selectedServer.SecretKeys.Remove(item.Key);
|
|
||||||
_serverConfigurationManager.Save();
|
|
||||||
}
|
|
||||||
UiSharedService.AttachToolTip("Hold CTRL to delete this secret key entry");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
UiSharedService.ColorTextWrapped("This key is in use and cannot be deleted", ImGuiColors.DalamudYellow);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (item.Key != selectedServer.SecretKeys.Keys.LastOrDefault())
|
|
||||||
ImGui.Separator();
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui.Separator();
|
|
||||||
if (_uiShared.IconTextButton(FontAwesomeIcon.Plus, "Add new Secret Key"))
|
|
||||||
{
|
|
||||||
selectedServer.SecretKeys.Add(selectedServer.SecretKeys.Any() ? selectedServer.SecretKeys.Max(p => p.Key) + 1 : 0, new SecretKey()
|
|
||||||
{
|
|
||||||
FriendlyName = "New Secret Key",
|
|
||||||
});
|
|
||||||
_serverConfigurationManager.Save();
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui.EndTabItem();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ImGui.BeginTabItem("Service Settings"))
|
|
||||||
{
|
|
||||||
var serverName = selectedServer.ServerName;
|
|
||||||
var serverUri = selectedServer.ServerUri;
|
|
||||||
var isMain = string.Equals(serverName, ApiController.MainServer, StringComparison.OrdinalIgnoreCase);
|
|
||||||
var flags = isMain ? ImGuiInputTextFlags.ReadOnly : ImGuiInputTextFlags.None;
|
|
||||||
|
|
||||||
if (ImGui.InputText("Service URI", ref serverUri, 255, flags))
|
|
||||||
{
|
|
||||||
selectedServer.ServerUri = serverUri;
|
|
||||||
}
|
|
||||||
if (isMain)
|
|
||||||
{
|
|
||||||
_uiShared.DrawHelpText("You cannot edit the URI of the main service.");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ImGui.InputText("Service Name", ref serverName, 255, flags))
|
|
||||||
{
|
|
||||||
selectedServer.ServerName = serverName;
|
|
||||||
_serverConfigurationManager.Save();
|
|
||||||
}
|
|
||||||
if (isMain)
|
|
||||||
{
|
|
||||||
_uiShared.DrawHelpText("You cannot edit the name of the main service.");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isMain && selectedServer != _serverConfigurationManager.CurrentServer)
|
|
||||||
{
|
|
||||||
if (_uiShared.IconTextButton(FontAwesomeIcon.Trash, "Delete Service") && UiSharedService.CtrlPressed())
|
|
||||||
{
|
|
||||||
_serverConfigurationManager.DeleteServer(selectedServer);
|
|
||||||
}
|
|
||||||
_uiShared.DrawHelpText("Hold CTRL to delete this service");
|
|
||||||
}
|
|
||||||
ImGui.EndTabItem();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ImGui.BeginTabItem("Permission Settings"))
|
|
||||||
{
|
|
||||||
_uiShared.BigText("Default Permission Settings");
|
|
||||||
if (selectedServer == _serverConfigurationManager.CurrentServer && _apiController.IsConnected)
|
|
||||||
{
|
|
||||||
UiSharedService.TextWrapped("Note: The default permissions settings here are not applied retroactively to existing pairs or joined Syncshells.");
|
|
||||||
UiSharedService.TextWrapped("Note: The default permissions settings here are sent and stored on the connected service.");
|
|
||||||
ImGuiHelpers.ScaledDummy(5f);
|
|
||||||
var perms = _apiController.DefaultPermissions!;
|
|
||||||
bool individualIsSticky = perms.IndividualIsSticky;
|
|
||||||
bool disableIndividualSounds = perms.DisableIndividualSounds;
|
|
||||||
bool disableIndividualAnimations = perms.DisableIndividualAnimations;
|
|
||||||
bool disableIndividualVFX = perms.DisableIndividualVFX;
|
|
||||||
if (ImGui.Checkbox("Individually set permissions become preferred permissions", ref individualIsSticky))
|
|
||||||
{
|
|
||||||
perms.IndividualIsSticky = individualIsSticky;
|
|
||||||
_ = _apiController.UserUpdateDefaultPermissions(perms);
|
|
||||||
}
|
|
||||||
_uiShared.DrawHelpText("The preferred attribute means that the permissions to that user will never change through any of your permission changes to Syncshells " +
|
|
||||||
"(i.e. if you have paused one specific user in a Syncshell and they become preferred permissions, then pause and unpause the same Syncshell, the user will remain paused - " +
|
|
||||||
"if a user does not have preferred permissions, it will follow the permissions of the Syncshell and be unpaused)." + Environment.NewLine + Environment.NewLine +
|
|
||||||
"This setting means:" + Environment.NewLine +
|
|
||||||
" - All new individual pairs get their permissions defaulted to preferred permissions." + Environment.NewLine +
|
|
||||||
" - All individually set permissions for any pair will also automatically become preferred permissions. This includes pairs in Syncshells." + Environment.NewLine + Environment.NewLine +
|
|
||||||
"It is possible to remove or set the preferred permission state for any pair at any time." + Environment.NewLine + Environment.NewLine +
|
|
||||||
"If unsure, leave this setting off.");
|
|
||||||
ImGuiHelpers.ScaledDummy(3f);
|
|
||||||
|
|
||||||
if (ImGui.Checkbox("Disable individual pair sounds", ref disableIndividualSounds))
|
|
||||||
{
|
|
||||||
perms.DisableIndividualSounds = disableIndividualSounds;
|
|
||||||
_ = _apiController.UserUpdateDefaultPermissions(perms);
|
|
||||||
}
|
|
||||||
_uiShared.DrawHelpText("This setting will disable sound sync for all new individual pairs.");
|
|
||||||
if (ImGui.Checkbox("Disable individual pair animations", ref disableIndividualAnimations))
|
|
||||||
{
|
|
||||||
perms.DisableIndividualAnimations = disableIndividualAnimations;
|
|
||||||
_ = _apiController.UserUpdateDefaultPermissions(perms);
|
|
||||||
}
|
|
||||||
_uiShared.DrawHelpText("This setting will disable animation sync for all new individual pairs.");
|
|
||||||
if (ImGui.Checkbox("Disable individual pair VFX", ref disableIndividualVFX))
|
|
||||||
{
|
|
||||||
perms.DisableIndividualVFX = disableIndividualVFX;
|
|
||||||
_ = _apiController.UserUpdateDefaultPermissions(perms);
|
|
||||||
}
|
|
||||||
_uiShared.DrawHelpText("This setting will disable VFX sync for all new individual pairs.");
|
|
||||||
ImGuiHelpers.ScaledDummy(5f);
|
|
||||||
bool disableGroundSounds = perms.DisableGroupSounds;
|
|
||||||
bool disableGroupAnimations = perms.DisableGroupAnimations;
|
|
||||||
bool disableGroupVFX = perms.DisableGroupVFX;
|
|
||||||
if (ImGui.Checkbox("Disable Syncshell pair sounds", ref disableGroundSounds))
|
|
||||||
{
|
|
||||||
perms.DisableGroupSounds = disableGroundSounds;
|
|
||||||
_ = _apiController.UserUpdateDefaultPermissions(perms);
|
|
||||||
}
|
|
||||||
_uiShared.DrawHelpText("This setting will disable sound sync for all non-sticky pairs in newly joined syncshells.");
|
|
||||||
if (ImGui.Checkbox("Disable Syncshell pair animations", ref disableGroupAnimations))
|
|
||||||
{
|
|
||||||
perms.DisableGroupAnimations = disableGroupAnimations;
|
|
||||||
_ = _apiController.UserUpdateDefaultPermissions(perms);
|
|
||||||
}
|
|
||||||
_uiShared.DrawHelpText("This setting will disable animation sync for all non-sticky pairs in newly joined syncshells.");
|
|
||||||
if (ImGui.Checkbox("Disable Syncshell pair VFX", ref disableGroupVFX))
|
|
||||||
{
|
|
||||||
perms.DisableGroupVFX = disableGroupVFX;
|
|
||||||
_ = _apiController.UserUpdateDefaultPermissions(perms);
|
|
||||||
}
|
|
||||||
_uiShared.DrawHelpText("This setting will disable VFX sync for all non-sticky pairs in newly joined syncshells.");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
UiSharedService.ColorTextWrapped("Default Permission Settings unavailable for this service. " +
|
|
||||||
"You need to connect to this service to change the default permissions since they are stored on the service.", ImGuiColors.DalamudYellow);
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui.EndTabItem();
|
|
||||||
}
|
|
||||||
ImGui.EndTabBar();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void DrawPerformance()
|
private void DrawPerformance()
|
||||||
{
|
{
|
||||||
_uiShared.BigText("Performance Settings");
|
_uiShared.BigText("Performance Settings");
|
||||||
@@ -1527,8 +1159,421 @@ public class SettingsUi : WindowMediatorSubscriberBase
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private string _uidToAddForIgnore = string.Empty;
|
private void DrawServerConfiguration()
|
||||||
private int _selectedEntry = -1;
|
{
|
||||||
|
_lastTab = "Service Settings";
|
||||||
|
if (ApiController.ServerAlive)
|
||||||
|
{
|
||||||
|
_uiShared.BigText("Service Actions");
|
||||||
|
ImGuiHelpers.ScaledDummy(new Vector2(5, 5));
|
||||||
|
if (ImGui.Button("Delete all my files"))
|
||||||
|
{
|
||||||
|
_deleteFilesPopupModalShown = true;
|
||||||
|
ImGui.OpenPopup("Delete all your files?");
|
||||||
|
}
|
||||||
|
|
||||||
|
_uiShared.DrawHelpText("Completely deletes all your uploaded files on the service.");
|
||||||
|
|
||||||
|
if (ImGui.BeginPopupModal("Delete all your files?", ref _deleteFilesPopupModalShown, UiSharedService.PopupWindowFlags))
|
||||||
|
{
|
||||||
|
UiSharedService.TextWrapped(
|
||||||
|
"All your own uploaded files on the service will be deleted.\nThis operation cannot be undone.");
|
||||||
|
ImGui.TextUnformatted("Are you sure you want to continue?");
|
||||||
|
ImGui.Separator();
|
||||||
|
ImGui.Spacing();
|
||||||
|
|
||||||
|
var buttonSize = (ImGui.GetWindowContentRegionMax().X - ImGui.GetWindowContentRegionMin().X -
|
||||||
|
ImGui.GetStyle().ItemSpacing.X) / 2;
|
||||||
|
|
||||||
|
if (ImGui.Button("Delete everything", new Vector2(buttonSize, 0)))
|
||||||
|
{
|
||||||
|
_ = Task.Run(_fileTransferManager.DeleteAllFiles);
|
||||||
|
_deleteFilesPopupModalShown = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.SameLine();
|
||||||
|
|
||||||
|
if (ImGui.Button("Cancel##cancelDelete", new Vector2(buttonSize, 0)))
|
||||||
|
{
|
||||||
|
_deleteFilesPopupModalShown = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
UiSharedService.SetScaledWindowSize(325);
|
||||||
|
ImGui.EndPopup();
|
||||||
|
}
|
||||||
|
ImGui.SameLine();
|
||||||
|
if (ImGui.Button("Delete account"))
|
||||||
|
{
|
||||||
|
_deleteAccountPopupModalShown = true;
|
||||||
|
ImGui.OpenPopup("Delete your account?");
|
||||||
|
}
|
||||||
|
|
||||||
|
_uiShared.DrawHelpText("Completely deletes your account and all uploaded files to the service.");
|
||||||
|
|
||||||
|
if (ImGui.BeginPopupModal("Delete your account?", ref _deleteAccountPopupModalShown, UiSharedService.PopupWindowFlags))
|
||||||
|
{
|
||||||
|
UiSharedService.TextWrapped(
|
||||||
|
"Your account and all associated files and data on the service will be deleted.");
|
||||||
|
UiSharedService.TextWrapped("Your UID will be removed from all pairing lists.");
|
||||||
|
ImGui.TextUnformatted("Are you sure you want to continue?");
|
||||||
|
ImGui.Separator();
|
||||||
|
ImGui.Spacing();
|
||||||
|
|
||||||
|
var buttonSize = (ImGui.GetWindowContentRegionMax().X - ImGui.GetWindowContentRegionMin().X -
|
||||||
|
ImGui.GetStyle().ItemSpacing.X) / 2;
|
||||||
|
|
||||||
|
if (ImGui.Button("Delete account", new Vector2(buttonSize, 0)))
|
||||||
|
{
|
||||||
|
_ = Task.Run(ApiController.UserDelete);
|
||||||
|
_deleteAccountPopupModalShown = false;
|
||||||
|
Mediator.Publish(new SwitchToIntroUiMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.SameLine();
|
||||||
|
|
||||||
|
if (ImGui.Button("Cancel##cancelDelete", new Vector2(buttonSize, 0)))
|
||||||
|
{
|
||||||
|
_deleteAccountPopupModalShown = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
UiSharedService.SetScaledWindowSize(325);
|
||||||
|
ImGui.EndPopup();
|
||||||
|
}
|
||||||
|
ImGui.Separator();
|
||||||
|
}
|
||||||
|
|
||||||
|
_uiShared.BigText("Service & Character Settings");
|
||||||
|
ImGuiHelpers.ScaledDummy(new Vector2(5, 5));
|
||||||
|
var sendCensus = _serverConfigurationManager.SendCensusData;
|
||||||
|
if (ImGui.Checkbox("Send Statistical Census Data", ref sendCensus))
|
||||||
|
{
|
||||||
|
_serverConfigurationManager.SendCensusData = sendCensus;
|
||||||
|
}
|
||||||
|
_uiShared.DrawHelpText("This will allow sending census data to the currently connected service." + UiSharedService.TooltipSeparator
|
||||||
|
+ "Census data contains:" + Environment.NewLine
|
||||||
|
+ "- Current World" + Environment.NewLine
|
||||||
|
+ "- Current Gender" + Environment.NewLine
|
||||||
|
+ "- Current Race" + Environment.NewLine
|
||||||
|
+ "- Current Clan (this is not your Free Company, this is e.g. Keeper or Seeker for Miqo'te)" + UiSharedService.TooltipSeparator
|
||||||
|
+ "The census data is only saved temporarily and will be removed from the server on disconnect. It is stored temporarily associated with your UID while you are connected." + UiSharedService.TooltipSeparator
|
||||||
|
+ "If you do not wish to participate in the statistical census, untick this box and reconnect to the server.");
|
||||||
|
ImGuiHelpers.ScaledDummy(new Vector2(10, 10));
|
||||||
|
|
||||||
|
var idx = _uiShared.DrawServiceSelection();
|
||||||
|
if (_lastSelectedServerIndex != idx)
|
||||||
|
{
|
||||||
|
_uiShared.RestOAuthTasksState();
|
||||||
|
_lastSelectedServerIndex = idx;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGuiHelpers.ScaledDummy(new Vector2(10, 10));
|
||||||
|
|
||||||
|
var selectedServer = _serverConfigurationManager.GetServerByIndex(idx);
|
||||||
|
if (selectedServer == _serverConfigurationManager.CurrentServer)
|
||||||
|
{
|
||||||
|
UiSharedService.ColorTextWrapped("For any changes to be applied to the current service you need to reconnect to the service.", ImGuiColors.DalamudYellow);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool useOauth = selectedServer.UseOAuth2;
|
||||||
|
|
||||||
|
if (ImGui.BeginTabBar("serverTabBar"))
|
||||||
|
{
|
||||||
|
if (ImGui.BeginTabItem("Character Management"))
|
||||||
|
{
|
||||||
|
if (selectedServer.SecretKeys.Any() || useOauth)
|
||||||
|
{
|
||||||
|
UiSharedService.ColorTextWrapped("Characters listed here will automatically connect to the selected Mare service with the settings as provided below." +
|
||||||
|
" Make sure to enter the character names correctly or use the 'Add current character' button at the bottom.", ImGuiColors.DalamudYellow);
|
||||||
|
int i = 0;
|
||||||
|
_uiShared.DrawUpdateOAuthUIDsButton(selectedServer);
|
||||||
|
foreach (var item in selectedServer.Authentications.ToList())
|
||||||
|
{
|
||||||
|
using var charaId = ImRaii.PushId("selectedChara" + i);
|
||||||
|
|
||||||
|
var worldIdx = (ushort)item.WorldId;
|
||||||
|
var data = _uiShared.WorldData.OrderBy(u => u.Value, StringComparer.Ordinal).ToDictionary(k => k.Key, k => k.Value);
|
||||||
|
if (!data.TryGetValue(worldIdx, out string? worldPreview))
|
||||||
|
{
|
||||||
|
worldPreview = data.First().Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
var friendlyName = string.Empty;
|
||||||
|
string friendlyNameTranslation = string.Empty;
|
||||||
|
Dictionary<int, SecretKey> keys = [];
|
||||||
|
|
||||||
|
if (!useOauth)
|
||||||
|
{
|
||||||
|
var secretKeyIdx = item.SecretKeyIdx;
|
||||||
|
keys = selectedServer.SecretKeys;
|
||||||
|
if (!keys.TryGetValue(secretKeyIdx, out var secretKey))
|
||||||
|
{
|
||||||
|
secretKey = new();
|
||||||
|
}
|
||||||
|
|
||||||
|
friendlyName = secretKey.FriendlyName;
|
||||||
|
friendlyNameTranslation = "Secret Key";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
friendlyName = item.UID;
|
||||||
|
friendlyNameTranslation = "UID";
|
||||||
|
}
|
||||||
|
|
||||||
|
bool thisIsYou = false;
|
||||||
|
if (string.Equals(_dalamudUtilService.GetPlayerName(), item.CharacterName, StringComparison.OrdinalIgnoreCase)
|
||||||
|
&& _dalamudUtilService.GetWorldId() == worldIdx)
|
||||||
|
{
|
||||||
|
thisIsYou = true;
|
||||||
|
}
|
||||||
|
if (ImGui.TreeNode($"chara", (thisIsYou ? "[CURRENT] " : "") + $"Character: {item.CharacterName}, World: {worldPreview}, {friendlyNameTranslation}: {friendlyName}"))
|
||||||
|
{
|
||||||
|
var charaName = item.CharacterName;
|
||||||
|
if (ImGui.InputText("Character Name", ref charaName, 64))
|
||||||
|
{
|
||||||
|
item.CharacterName = charaName;
|
||||||
|
_serverConfigurationManager.Save();
|
||||||
|
}
|
||||||
|
|
||||||
|
_uiShared.DrawCombo("World##" + item.CharacterName + i, data, (w) => w.Value,
|
||||||
|
(w) =>
|
||||||
|
{
|
||||||
|
if (item.WorldId != w.Key)
|
||||||
|
{
|
||||||
|
item.WorldId = w.Key;
|
||||||
|
_serverConfigurationManager.Save();
|
||||||
|
}
|
||||||
|
}, EqualityComparer<KeyValuePair<ushort, string>>.Default.Equals(data.FirstOrDefault(f => f.Key == worldIdx), default) ? data.First() : data.First(f => f.Key == worldIdx));
|
||||||
|
|
||||||
|
if (!useOauth)
|
||||||
|
{
|
||||||
|
_uiShared.DrawCombo("Secret Key##" + item.CharacterName + i, keys, (w) => w.Value.FriendlyName,
|
||||||
|
(w) =>
|
||||||
|
{
|
||||||
|
if (w.Key != item.SecretKeyIdx)
|
||||||
|
{
|
||||||
|
item.SecretKeyIdx = w.Key;
|
||||||
|
_serverConfigurationManager.Save();
|
||||||
|
}
|
||||||
|
}, EqualityComparer<KeyValuePair<int, SecretKey>>.Default.Equals(keys.FirstOrDefault(f => f.Key == item.SecretKeyIdx), default) ? keys.First() : keys.First(f => f.Key == item.SecretKeyIdx));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_uiShared.DrawUIDComboForAuthentication(i, item, selectedServer.ServerUri);
|
||||||
|
}
|
||||||
|
if (_uiShared.IconTextButton(FontAwesomeIcon.Trash, "Delete Character") && UiSharedService.CtrlPressed())
|
||||||
|
_serverConfigurationManager.RemoveCharacterFromServer(idx, item);
|
||||||
|
UiSharedService.AttachToolTip("Hold CTRL to delete this entry.");
|
||||||
|
|
||||||
|
ImGui.TreePop();
|
||||||
|
}
|
||||||
|
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.Separator();
|
||||||
|
if (!selectedServer.Authentications.Exists(c => string.Equals(c.CharacterName, _uiShared.PlayerName, StringComparison.Ordinal)
|
||||||
|
&& c.WorldId == _uiShared.WorldId))
|
||||||
|
{
|
||||||
|
if (_uiShared.IconTextButton(FontAwesomeIcon.User, "Add current character"))
|
||||||
|
{
|
||||||
|
_serverConfigurationManager.AddCurrentCharacterToServer(idx);
|
||||||
|
}
|
||||||
|
ImGui.SameLine();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_uiShared.IconTextButton(FontAwesomeIcon.Plus, "Add new character"))
|
||||||
|
{
|
||||||
|
_serverConfigurationManager.AddEmptyCharacterToServer(idx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
UiSharedService.ColorTextWrapped("You need to add a Secret Key first before adding Characters.", ImGuiColors.DalamudYellow);
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.EndTabItem();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!useOauth && ImGui.BeginTabItem("Secret Key Management"))
|
||||||
|
{
|
||||||
|
foreach (var item in selectedServer.SecretKeys.ToList())
|
||||||
|
{
|
||||||
|
using var id = ImRaii.PushId("key" + item.Key);
|
||||||
|
var friendlyName = item.Value.FriendlyName;
|
||||||
|
if (ImGui.InputText("Secret Key Display Name", ref friendlyName, 255))
|
||||||
|
{
|
||||||
|
item.Value.FriendlyName = friendlyName;
|
||||||
|
_serverConfigurationManager.Save();
|
||||||
|
}
|
||||||
|
var key = item.Value.Key;
|
||||||
|
if (ImGui.InputText("Secret Key", ref key, 64))
|
||||||
|
{
|
||||||
|
item.Value.Key = key;
|
||||||
|
_serverConfigurationManager.Save();
|
||||||
|
}
|
||||||
|
if (!selectedServer.Authentications.Exists(p => p.SecretKeyIdx == item.Key))
|
||||||
|
{
|
||||||
|
if (_uiShared.IconTextButton(FontAwesomeIcon.Trash, "Delete Secret Key") && UiSharedService.CtrlPressed())
|
||||||
|
{
|
||||||
|
selectedServer.SecretKeys.Remove(item.Key);
|
||||||
|
_serverConfigurationManager.Save();
|
||||||
|
}
|
||||||
|
UiSharedService.AttachToolTip("Hold CTRL to delete this secret key entry");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
UiSharedService.ColorTextWrapped("This key is in use and cannot be deleted", ImGuiColors.DalamudYellow);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item.Key != selectedServer.SecretKeys.Keys.LastOrDefault())
|
||||||
|
ImGui.Separator();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.Separator();
|
||||||
|
if (_uiShared.IconTextButton(FontAwesomeIcon.Plus, "Add new Secret Key"))
|
||||||
|
{
|
||||||
|
selectedServer.SecretKeys.Add(selectedServer.SecretKeys.Any() ? selectedServer.SecretKeys.Max(p => p.Key) + 1 : 0, new SecretKey()
|
||||||
|
{
|
||||||
|
FriendlyName = "New Secret Key",
|
||||||
|
});
|
||||||
|
_serverConfigurationManager.Save();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.EndTabItem();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ImGui.BeginTabItem("Service Settings"))
|
||||||
|
{
|
||||||
|
var serverName = selectedServer.ServerName;
|
||||||
|
var serverUri = selectedServer.ServerUri;
|
||||||
|
var isMain = string.Equals(serverName, ApiController.MainServer, StringComparison.OrdinalIgnoreCase);
|
||||||
|
var flags = isMain ? ImGuiInputTextFlags.ReadOnly : ImGuiInputTextFlags.None;
|
||||||
|
|
||||||
|
if (ImGui.InputText("Service URI", ref serverUri, 255, flags))
|
||||||
|
{
|
||||||
|
selectedServer.ServerUri = serverUri;
|
||||||
|
}
|
||||||
|
if (isMain)
|
||||||
|
{
|
||||||
|
_uiShared.DrawHelpText("You cannot edit the URI of the main service.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ImGui.InputText("Service Name", ref serverName, 255, flags))
|
||||||
|
{
|
||||||
|
selectedServer.ServerName = serverName;
|
||||||
|
_serverConfigurationManager.Save();
|
||||||
|
}
|
||||||
|
if (isMain)
|
||||||
|
{
|
||||||
|
_uiShared.DrawHelpText("You cannot edit the name of the main service.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ImGui.Checkbox("Use Discord OAuth2 Authentication", ref useOauth))
|
||||||
|
{
|
||||||
|
selectedServer.UseOAuth2 = useOauth;
|
||||||
|
_serverConfigurationManager.Save();
|
||||||
|
}
|
||||||
|
_uiShared.DrawHelpText("Use Discord OAuth2 Authentication to identify with this server instead of secret keys");
|
||||||
|
if (useOauth)
|
||||||
|
{
|
||||||
|
_uiShared.DrawOAuth(selectedServer);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isMain && selectedServer != _serverConfigurationManager.CurrentServer)
|
||||||
|
{
|
||||||
|
ImGui.Separator();
|
||||||
|
if (_uiShared.IconTextButton(FontAwesomeIcon.Trash, "Delete Service") && UiSharedService.CtrlPressed())
|
||||||
|
{
|
||||||
|
_serverConfigurationManager.DeleteServer(selectedServer);
|
||||||
|
}
|
||||||
|
_uiShared.DrawHelpText("Hold CTRL to delete this service");
|
||||||
|
}
|
||||||
|
ImGui.EndTabItem();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ImGui.BeginTabItem("Permission Settings"))
|
||||||
|
{
|
||||||
|
_uiShared.BigText("Default Permission Settings");
|
||||||
|
if (selectedServer == _serverConfigurationManager.CurrentServer && _apiController.IsConnected)
|
||||||
|
{
|
||||||
|
UiSharedService.TextWrapped("Note: The default permissions settings here are not applied retroactively to existing pairs or joined Syncshells.");
|
||||||
|
UiSharedService.TextWrapped("Note: The default permissions settings here are sent and stored on the connected service.");
|
||||||
|
ImGuiHelpers.ScaledDummy(5f);
|
||||||
|
var perms = _apiController.DefaultPermissions!;
|
||||||
|
bool individualIsSticky = perms.IndividualIsSticky;
|
||||||
|
bool disableIndividualSounds = perms.DisableIndividualSounds;
|
||||||
|
bool disableIndividualAnimations = perms.DisableIndividualAnimations;
|
||||||
|
bool disableIndividualVFX = perms.DisableIndividualVFX;
|
||||||
|
if (ImGui.Checkbox("Individually set permissions become preferred permissions", ref individualIsSticky))
|
||||||
|
{
|
||||||
|
perms.IndividualIsSticky = individualIsSticky;
|
||||||
|
_ = _apiController.UserUpdateDefaultPermissions(perms);
|
||||||
|
}
|
||||||
|
_uiShared.DrawHelpText("The preferred attribute means that the permissions to that user will never change through any of your permission changes to Syncshells " +
|
||||||
|
"(i.e. if you have paused one specific user in a Syncshell and they become preferred permissions, then pause and unpause the same Syncshell, the user will remain paused - " +
|
||||||
|
"if a user does not have preferred permissions, it will follow the permissions of the Syncshell and be unpaused)." + Environment.NewLine + Environment.NewLine +
|
||||||
|
"This setting means:" + Environment.NewLine +
|
||||||
|
" - All new individual pairs get their permissions defaulted to preferred permissions." + Environment.NewLine +
|
||||||
|
" - All individually set permissions for any pair will also automatically become preferred permissions. This includes pairs in Syncshells." + Environment.NewLine + Environment.NewLine +
|
||||||
|
"It is possible to remove or set the preferred permission state for any pair at any time." + Environment.NewLine + Environment.NewLine +
|
||||||
|
"If unsure, leave this setting off.");
|
||||||
|
ImGuiHelpers.ScaledDummy(3f);
|
||||||
|
|
||||||
|
if (ImGui.Checkbox("Disable individual pair sounds", ref disableIndividualSounds))
|
||||||
|
{
|
||||||
|
perms.DisableIndividualSounds = disableIndividualSounds;
|
||||||
|
_ = _apiController.UserUpdateDefaultPermissions(perms);
|
||||||
|
}
|
||||||
|
_uiShared.DrawHelpText("This setting will disable sound sync for all new individual pairs.");
|
||||||
|
if (ImGui.Checkbox("Disable individual pair animations", ref disableIndividualAnimations))
|
||||||
|
{
|
||||||
|
perms.DisableIndividualAnimations = disableIndividualAnimations;
|
||||||
|
_ = _apiController.UserUpdateDefaultPermissions(perms);
|
||||||
|
}
|
||||||
|
_uiShared.DrawHelpText("This setting will disable animation sync for all new individual pairs.");
|
||||||
|
if (ImGui.Checkbox("Disable individual pair VFX", ref disableIndividualVFX))
|
||||||
|
{
|
||||||
|
perms.DisableIndividualVFX = disableIndividualVFX;
|
||||||
|
_ = _apiController.UserUpdateDefaultPermissions(perms);
|
||||||
|
}
|
||||||
|
_uiShared.DrawHelpText("This setting will disable VFX sync for all new individual pairs.");
|
||||||
|
ImGuiHelpers.ScaledDummy(5f);
|
||||||
|
bool disableGroundSounds = perms.DisableGroupSounds;
|
||||||
|
bool disableGroupAnimations = perms.DisableGroupAnimations;
|
||||||
|
bool disableGroupVFX = perms.DisableGroupVFX;
|
||||||
|
if (ImGui.Checkbox("Disable Syncshell pair sounds", ref disableGroundSounds))
|
||||||
|
{
|
||||||
|
perms.DisableGroupSounds = disableGroundSounds;
|
||||||
|
_ = _apiController.UserUpdateDefaultPermissions(perms);
|
||||||
|
}
|
||||||
|
_uiShared.DrawHelpText("This setting will disable sound sync for all non-sticky pairs in newly joined syncshells.");
|
||||||
|
if (ImGui.Checkbox("Disable Syncshell pair animations", ref disableGroupAnimations))
|
||||||
|
{
|
||||||
|
perms.DisableGroupAnimations = disableGroupAnimations;
|
||||||
|
_ = _apiController.UserUpdateDefaultPermissions(perms);
|
||||||
|
}
|
||||||
|
_uiShared.DrawHelpText("This setting will disable animation sync for all non-sticky pairs in newly joined syncshells.");
|
||||||
|
if (ImGui.Checkbox("Disable Syncshell pair VFX", ref disableGroupVFX))
|
||||||
|
{
|
||||||
|
perms.DisableGroupVFX = disableGroupVFX;
|
||||||
|
_ = _apiController.UserUpdateDefaultPermissions(perms);
|
||||||
|
}
|
||||||
|
_uiShared.DrawHelpText("This setting will disable VFX sync for all non-sticky pairs in newly joined syncshells.");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
UiSharedService.ColorTextWrapped("Default Permission Settings unavailable for this service. " +
|
||||||
|
"You need to connect to this service to change the default permissions since they are stored on the service.", ImGuiColors.DalamudYellow);
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.EndTabItem();
|
||||||
|
}
|
||||||
|
ImGui.EndTabBar();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private int _lastSelectedServerIndex = -1;
|
||||||
|
|
||||||
|
|
||||||
private void DrawSettingsContent()
|
private void DrawSettingsContent()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ using MareSynchronos.PlayerData.Pairs;
|
|||||||
using MareSynchronos.Services;
|
using MareSynchronos.Services;
|
||||||
using MareSynchronos.Services.Mediator;
|
using MareSynchronos.Services.Mediator;
|
||||||
using MareSynchronos.Services.ServerConfiguration;
|
using MareSynchronos.Services.ServerConfiguration;
|
||||||
|
using MareSynchronos.Utils;
|
||||||
using MareSynchronos.WebAPI;
|
using MareSynchronos.WebAPI;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
@@ -713,7 +714,7 @@ public partial class UiSharedService : DisposableMediatorSubscriberBase
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int DrawServiceSelection(bool selectOnChange = false)
|
public int DrawServiceSelection(bool selectOnChange = false, bool showConnect = true)
|
||||||
{
|
{
|
||||||
string[] comboEntries = _serverConfigurationManager.GetServerNames();
|
string[] comboEntries = _serverConfigurationManager.GetServerNames();
|
||||||
|
|
||||||
@@ -753,13 +754,16 @@ public partial class UiSharedService : DisposableMediatorSubscriberBase
|
|||||||
ImGui.EndCombo();
|
ImGui.EndCombo();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (showConnect)
|
||||||
|
{
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
var text = "Connect";
|
var text = "Connect";
|
||||||
if (_serverSelectionIndex == _serverConfigurationManager.CurrentServerIndex) text = "Reconnect";
|
if (_serverSelectionIndex == _serverConfigurationManager.CurrentServerIndex) text = "Reconnect";
|
||||||
if (IconTextButton(FontAwesomeIcon.Link, text))
|
if (IconTextButton(FontAwesomeIcon.Link, text))
|
||||||
{
|
{
|
||||||
_serverConfigurationManager.SelectServer(_serverSelectionIndex);
|
_serverConfigurationManager.SelectServer(_serverSelectionIndex);
|
||||||
_ = _apiController.CreateConnections();
|
_ = _apiController.CreateConnectionsAsync();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ImGui.TreeNode("Add Custom Service"))
|
if (ImGui.TreeNode("Add Custom Service"))
|
||||||
@@ -776,6 +780,7 @@ public partial class UiSharedService : DisposableMediatorSubscriberBase
|
|||||||
{
|
{
|
||||||
ServerName = _customServerName,
|
ServerName = _customServerName,
|
||||||
ServerUri = _customServerUri,
|
ServerUri = _customServerUri,
|
||||||
|
UseOAuth2 = true
|
||||||
});
|
});
|
||||||
_customServerName = string.Empty;
|
_customServerName = string.Empty;
|
||||||
_customServerUri = string.Empty;
|
_customServerUri = string.Empty;
|
||||||
@@ -855,4 +860,177 @@ public partial class UiSharedService : DisposableMediatorSubscriberBase
|
|||||||
UidFont.Dispose();
|
UidFont.Dispose();
|
||||||
GameFont.Dispose();
|
GameFont.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Task<Uri?>? _discordOAuthCheck;
|
||||||
|
private CancellationTokenSource _discordOAuthGetCts = new();
|
||||||
|
private Task<string?>? _discordOAuthGetCode;
|
||||||
|
private Task<Dictionary<string, string>>? _discordOAuthUIDs;
|
||||||
|
|
||||||
|
public void DrawUpdateOAuthUIDsButton(ServerStorage selectedServer)
|
||||||
|
{
|
||||||
|
using (ImRaii.Disabled(selectedServer.OAuthToken == null))
|
||||||
|
{
|
||||||
|
if ((_discordOAuthUIDs == null || _discordOAuthUIDs.IsCompleted)
|
||||||
|
&& IconTextButton(FontAwesomeIcon.ArrowsSpin, "Update UIDs from Service")
|
||||||
|
&& selectedServer.OAuthToken != null)
|
||||||
|
{
|
||||||
|
_discordOAuthUIDs = _serverConfigurationManager.GetUIDsWithDiscordToken(selectedServer.ServerUri, selectedServer.OAuthToken);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void DrawUIDComboForAuthentication(int indexOffset, Authentication item, string serverUri)
|
||||||
|
{
|
||||||
|
using (ImRaii.Disabled(_discordOAuthUIDs == null))
|
||||||
|
{
|
||||||
|
DrawCombo("UID##" + item.CharacterName + serverUri + indexOffset, _discordOAuthUIDs?.Result ?? new(StringComparer.Ordinal) { { item.UID ?? string.Empty, string.Empty } },
|
||||||
|
(v) =>
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(v.Value))
|
||||||
|
{
|
||||||
|
return $"{v.Key} ({v.Value})";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(v.Key))
|
||||||
|
return "No UID set";
|
||||||
|
|
||||||
|
return $"{v.Key}";
|
||||||
|
},
|
||||||
|
(v) =>
|
||||||
|
{
|
||||||
|
if (!string.Equals(v.Key, item.UID, StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
item.UID = v.Key;
|
||||||
|
_serverConfigurationManager.Save();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_discordOAuthUIDs?.Result?.FirstOrDefault(f => string.Equals(f.Key, item.UID, StringComparison.Ordinal)) ?? default);
|
||||||
|
}
|
||||||
|
if (_discordOAuthUIDs == null)
|
||||||
|
{
|
||||||
|
AttachToolTip("Use the button above to update your UIDs from the service before you can assign UIDs to characters.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void DrawOAuth(ServerStorage selectedServer)
|
||||||
|
{
|
||||||
|
var oauthToken = selectedServer.OAuthToken;
|
||||||
|
using var _ = ImRaii.PushIndent(10f);
|
||||||
|
if (oauthToken == null)
|
||||||
|
{
|
||||||
|
if (_discordOAuthCheck == null)
|
||||||
|
{
|
||||||
|
if (IconTextButton(FontAwesomeIcon.QuestionCircle, "Check if Server supports Discord OAuth2"))
|
||||||
|
{
|
||||||
|
_discordOAuthCheck = _serverConfigurationManager.CheckDiscordOAuth(selectedServer.ServerUri);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (!_discordOAuthCheck.IsCompleted)
|
||||||
|
{
|
||||||
|
ColorTextWrapped($"Checking OAuth2 compatibility with {selectedServer.ServerUri}", ImGuiColors.DalamudYellow);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (_discordOAuthCheck.Result != null)
|
||||||
|
{
|
||||||
|
ColorTextWrapped("Server is compatible with Discord OAuth2", ImGuiColors.HealerGreen);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ColorTextWrapped("Server is not compatible with Discord OAuth2", ImGuiColors.DalamudRed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_discordOAuthCheck != null && _discordOAuthCheck.IsCompleted)
|
||||||
|
{
|
||||||
|
if (IconTextButton(FontAwesomeIcon.ArrowRight, "Authenticate with Server"))
|
||||||
|
{
|
||||||
|
_discordOAuthGetCode = _serverConfigurationManager.GetDiscordOAuthToken(_discordOAuthCheck.Result!, selectedServer.ServerUri, _discordOAuthGetCts.Token);
|
||||||
|
}
|
||||||
|
else if (_discordOAuthGetCode != null && !_discordOAuthGetCode.IsCompleted)
|
||||||
|
{
|
||||||
|
TextWrapped("A browser window has been opened, follow it to authenticate. Click the button below if you accidentally closed the window and need to restart the authentication.");
|
||||||
|
if (IconTextButton(FontAwesomeIcon.Ban, "Cancel Authentication"))
|
||||||
|
{
|
||||||
|
_discordOAuthGetCts = _discordOAuthGetCts.CancelRecreate();
|
||||||
|
_discordOAuthGetCode = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (_discordOAuthGetCode != null && _discordOAuthGetCode.IsCompleted)
|
||||||
|
{
|
||||||
|
TextWrapped("Discord OAuth is completed, status: ");
|
||||||
|
ImGui.SameLine();
|
||||||
|
if (_discordOAuthGetCode.Result != null)
|
||||||
|
{
|
||||||
|
selectedServer.OAuthToken = _discordOAuthGetCode.Result;
|
||||||
|
_discordOAuthGetCode = null;
|
||||||
|
_serverConfigurationManager.Save();
|
||||||
|
ColorTextWrapped("Success", ImGuiColors.HealerGreen);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ColorTextWrapped("Failed, please check /xllog for more information", ImGuiColors.DalamudRed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (oauthToken != null)
|
||||||
|
{
|
||||||
|
ColorTextWrapped($"OAuth2 is enabled, linked to: Discord User {_serverConfigurationManager.GetDiscordUserFromToken(selectedServer)}", ImGuiColors.HealerGreen);
|
||||||
|
if ((_discordOAuthUIDs == null || _discordOAuthUIDs.IsCompleted)
|
||||||
|
&& IconTextButton(FontAwesomeIcon.Question, "Check Discord Connection"))
|
||||||
|
{
|
||||||
|
_discordOAuthUIDs = _serverConfigurationManager.GetUIDsWithDiscordToken(selectedServer.ServerUri, oauthToken);
|
||||||
|
}
|
||||||
|
else if (_discordOAuthUIDs != null)
|
||||||
|
{
|
||||||
|
if (!_discordOAuthUIDs.IsCompleted)
|
||||||
|
{
|
||||||
|
ColorTextWrapped("Checking UIDs on Server", ImGuiColors.DalamudYellow);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var foundUids = _discordOAuthUIDs.Result?.Count ?? 0;
|
||||||
|
var primaryUid = _discordOAuthUIDs.Result?.FirstOrDefault() ?? new KeyValuePair<string, string>(string.Empty, string.Empty);
|
||||||
|
var vanity = string.IsNullOrEmpty(primaryUid.Value) ? "-" : primaryUid.Value;
|
||||||
|
if (foundUids > 0)
|
||||||
|
{
|
||||||
|
ColorTextWrapped($"Found {foundUids} associated UIDs on the server, Primary UID: {primaryUid.Key} (Vanity UID: {vanity})",
|
||||||
|
ImGuiColors.HealerGreen);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ColorTextWrapped($"Found no UIDs associated to this linked OAuth2 account", ImGuiColors.DalamudRed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DrawUnlinkOAuthButton(selectedServer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void DrawUnlinkOAuthButton(ServerStorage selectedServer)
|
||||||
|
{
|
||||||
|
using (ImRaii.Disabled(!CtrlPressed()))
|
||||||
|
{
|
||||||
|
if (IconTextButton(FontAwesomeIcon.Trash, "Unlink OAuth2 Connection") && UiSharedService.CtrlPressed())
|
||||||
|
{
|
||||||
|
selectedServer.OAuthToken = null;
|
||||||
|
_serverConfigurationManager.Save();
|
||||||
|
RestOAuthTasksState();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DrawHelpText("Hold CTRL to unlink the current OAuth2 connection.");
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void RestOAuthTasksState()
|
||||||
|
{
|
||||||
|
_discordOAuthCheck = null;
|
||||||
|
_discordOAuthGetCts = _discordOAuthGetCts.CancelRecreate();
|
||||||
|
_discordOAuthGetCode = null;
|
||||||
|
_discordOAuthUIDs = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -39,7 +39,7 @@ public partial class ApiController
|
|||||||
{
|
{
|
||||||
CheckConnection();
|
CheckConnection();
|
||||||
await _mareHub!.SendAsync(nameof(UserDelete)).ConfigureAwait(false);
|
await _mareHub!.SendAsync(nameof(UserDelete)).ConfigureAwait(false);
|
||||||
await CreateConnections().ConfigureAwait(false);
|
await CreateConnectionsAsync().ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<List<OnlineUserIdentDto>> UserGetOnlinePairs(CensusDataDto? censusDataDto)
|
public async Task<List<OnlineUserIdentDto>> UserGetOnlinePairs(CensusDataDto? censusDataDto)
|
||||||
|
|||||||
@@ -55,11 +55,11 @@ public sealed partial class ApiController : DisposableMediatorSubscriberBase, IM
|
|||||||
Mediator.Subscribe<DalamudLoginMessage>(this, (_) => DalamudUtilOnLogIn());
|
Mediator.Subscribe<DalamudLoginMessage>(this, (_) => DalamudUtilOnLogIn());
|
||||||
Mediator.Subscribe<DalamudLogoutMessage>(this, (_) => DalamudUtilOnLogOut());
|
Mediator.Subscribe<DalamudLogoutMessage>(this, (_) => DalamudUtilOnLogOut());
|
||||||
Mediator.Subscribe<HubClosedMessage>(this, (msg) => MareHubOnClosed(msg.Exception));
|
Mediator.Subscribe<HubClosedMessage>(this, (msg) => MareHubOnClosed(msg.Exception));
|
||||||
Mediator.Subscribe<HubReconnectedMessage>(this, (msg) => _ = MareHubOnReconnected());
|
Mediator.Subscribe<HubReconnectedMessage>(this, (msg) => _ = MareHubOnReconnectedAsync());
|
||||||
Mediator.Subscribe<HubReconnectingMessage>(this, (msg) => MareHubOnReconnecting(msg.Exception));
|
Mediator.Subscribe<HubReconnectingMessage>(this, (msg) => MareHubOnReconnecting(msg.Exception));
|
||||||
Mediator.Subscribe<CyclePauseMessage>(this, (msg) => _ = CyclePause(msg.UserData));
|
Mediator.Subscribe<CyclePauseMessage>(this, (msg) => _ = CyclePauseAsync(msg.UserData));
|
||||||
Mediator.Subscribe<CensusUpdateMessage>(this, (msg) => _lastCensus = msg);
|
Mediator.Subscribe<CensusUpdateMessage>(this, (msg) => _lastCensus = msg);
|
||||||
Mediator.Subscribe<PauseMessage>(this, (msg) => _ = Pause(msg.UserData));
|
Mediator.Subscribe<PauseMessage>(this, (msg) => _ = PauseAsync(msg.UserData));
|
||||||
|
|
||||||
ServerState = ServerState.Offline;
|
ServerState = ServerState.Offline;
|
||||||
|
|
||||||
@@ -105,7 +105,7 @@ public sealed partial class ApiController : DisposableMediatorSubscriberBase, IM
|
|||||||
return await _mareHub!.InvokeAsync<bool>(nameof(CheckClientHealth)).ConfigureAwait(false);
|
return await _mareHub!.InvokeAsync<bool>(nameof(CheckClientHealth)).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task CreateConnections()
|
public async Task CreateConnectionsAsync()
|
||||||
{
|
{
|
||||||
if (!_serverManager.ShownCensusPopup)
|
if (!_serverManager.ShownCensusPopup)
|
||||||
{
|
{
|
||||||
@@ -122,11 +122,13 @@ public sealed partial class ApiController : DisposableMediatorSubscriberBase, IM
|
|||||||
{
|
{
|
||||||
Logger.LogInformation("Not recreating Connection, paused");
|
Logger.LogInformation("Not recreating Connection, paused");
|
||||||
_connectionDto = null;
|
_connectionDto = null;
|
||||||
await StopConnection(ServerState.Disconnected).ConfigureAwait(false);
|
await StopConnectionAsync(ServerState.Disconnected).ConfigureAwait(false);
|
||||||
_connectionCancellationTokenSource?.Cancel();
|
_connectionCancellationTokenSource?.Cancel();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!_serverManager.CurrentServer.UseOAuth2)
|
||||||
|
{
|
||||||
var secretKey = _serverManager.GetSecretKey(out bool multi);
|
var secretKey = _serverManager.GetSecretKey(out bool multi);
|
||||||
if (multi)
|
if (multi)
|
||||||
{
|
{
|
||||||
@@ -134,7 +136,7 @@ public sealed partial class ApiController : DisposableMediatorSubscriberBase, IM
|
|||||||
_connectionDto = null;
|
_connectionDto = null;
|
||||||
Mediator.Publish(new NotificationMessage("Multiple Identical Characters detected", "Your Service configuration has multiple characters with the same name and world set up. Delete the duplicates in the character management to be able to connect to Mare.",
|
Mediator.Publish(new NotificationMessage("Multiple Identical Characters detected", "Your Service configuration has multiple characters with the same name and world set up. Delete the duplicates in the character management to be able to connect to Mare.",
|
||||||
NotificationType.Error));
|
NotificationType.Error));
|
||||||
await StopConnection(ServerState.MultiChara).ConfigureAwait(false);
|
await StopConnectionAsync(ServerState.MultiChara).ConfigureAwait(false);
|
||||||
_connectionCancellationTokenSource?.Cancel();
|
_connectionCancellationTokenSource?.Cancel();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -143,12 +145,45 @@ public sealed partial class ApiController : DisposableMediatorSubscriberBase, IM
|
|||||||
{
|
{
|
||||||
Logger.LogWarning("No secret key set for current character");
|
Logger.LogWarning("No secret key set for current character");
|
||||||
_connectionDto = null;
|
_connectionDto = null;
|
||||||
await StopConnection(ServerState.NoSecretKey).ConfigureAwait(false);
|
await StopConnectionAsync(ServerState.NoSecretKey).ConfigureAwait(false);
|
||||||
|
_connectionCancellationTokenSource?.Cancel();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var oauth2 = _serverManager.GetOAuth2(out bool multi);
|
||||||
|
if (multi)
|
||||||
|
{
|
||||||
|
Logger.LogWarning("Multiple secret keys for current character");
|
||||||
|
_connectionDto = null;
|
||||||
|
Mediator.Publish(new NotificationMessage("Multiple Identical Characters detected", "Your Service configuration has multiple characters with the same name and world set up. Delete the duplicates in the character management to be able to connect to Mare.",
|
||||||
|
NotificationType.Error));
|
||||||
|
await StopConnectionAsync(ServerState.MultiChara).ConfigureAwait(false);
|
||||||
_connectionCancellationTokenSource?.Cancel();
|
_connectionCancellationTokenSource?.Cancel();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await StopConnection(ServerState.Disconnected).ConfigureAwait(false);
|
if (!oauth2.HasValue)
|
||||||
|
{
|
||||||
|
Logger.LogWarning("No UID/OAuth set for current character");
|
||||||
|
_connectionDto = null;
|
||||||
|
await StopConnectionAsync(ServerState.OAuthMisconfigured).ConfigureAwait(false);
|
||||||
|
_connectionCancellationTokenSource?.Cancel();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!await _tokenProvider.TryUpdateOAuth2LoginTokenAsync().ConfigureAwait(false))
|
||||||
|
{
|
||||||
|
Logger.LogWarning("OAuth2 login token could not be updated");
|
||||||
|
_connectionDto = null;
|
||||||
|
await StopConnectionAsync(ServerState.OAuthLoginTokenStale).ConfigureAwait(false);
|
||||||
|
_connectionCancellationTokenSource?.Cancel();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await StopConnectionAsync(ServerState.Disconnected).ConfigureAwait(false);
|
||||||
|
|
||||||
Logger.LogInformation("Recreating Connection");
|
Logger.LogInformation("Recreating Connection");
|
||||||
Mediator.Publish(new EventMessage(new Services.Events.Event(nameof(ApiController), Services.Events.EventSeverity.Informational,
|
Mediator.Publish(new EventMessage(new Services.Events.Event(nameof(ApiController), Services.Events.EventSeverity.Informational,
|
||||||
@@ -162,7 +197,7 @@ public sealed partial class ApiController : DisposableMediatorSubscriberBase, IM
|
|||||||
{
|
{
|
||||||
AuthFailureMessage = string.Empty;
|
AuthFailureMessage = string.Empty;
|
||||||
|
|
||||||
await StopConnection(ServerState.Disconnected).ConfigureAwait(false);
|
await StopConnectionAsync(ServerState.Disconnected).ConfigureAwait(false);
|
||||||
ServerState = ServerState.Connecting;
|
ServerState = ServerState.Connecting;
|
||||||
|
|
||||||
try
|
try
|
||||||
@@ -208,7 +243,7 @@ public sealed partial class ApiController : DisposableMediatorSubscriberBase, IM
|
|||||||
$"This client version is incompatible and will not be able to connect. Please update your Mare Synchronos client.",
|
$"This client version is incompatible and will not be able to connect. Please update your Mare Synchronos client.",
|
||||||
NotificationType.Error));
|
NotificationType.Error));
|
||||||
}
|
}
|
||||||
await StopConnection(ServerState.VersionMisMatch).ConfigureAwait(false);
|
await StopConnectionAsync(ServerState.VersionMisMatch).ConfigureAwait(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -232,8 +267,8 @@ public sealed partial class ApiController : DisposableMediatorSubscriberBase, IM
|
|||||||
NotificationType.Error, TimeSpan.FromSeconds(15)));
|
NotificationType.Error, TimeSpan.FromSeconds(15)));
|
||||||
}
|
}
|
||||||
|
|
||||||
await LoadIninitialPairs().ConfigureAwait(false);
|
await LoadIninitialPairsAsync().ConfigureAwait(false);
|
||||||
await LoadOnlinePairs().ConfigureAwait(false);
|
await LoadOnlinePairsAsync().ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
catch (OperationCanceledException)
|
catch (OperationCanceledException)
|
||||||
{
|
{
|
||||||
@@ -246,7 +281,7 @@ public sealed partial class ApiController : DisposableMediatorSubscriberBase, IM
|
|||||||
|
|
||||||
if (ex.StatusCode == System.Net.HttpStatusCode.Unauthorized)
|
if (ex.StatusCode == System.Net.HttpStatusCode.Unauthorized)
|
||||||
{
|
{
|
||||||
await StopConnection(ServerState.Unauthorized).ConfigureAwait(false);
|
await StopConnectionAsync(ServerState.Unauthorized).ConfigureAwait(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -257,7 +292,7 @@ public sealed partial class ApiController : DisposableMediatorSubscriberBase, IM
|
|||||||
catch (InvalidOperationException ex)
|
catch (InvalidOperationException ex)
|
||||||
{
|
{
|
||||||
Logger.LogWarning(ex, "InvalidOperationException on connection");
|
Logger.LogWarning(ex, "InvalidOperationException on connection");
|
||||||
await StopConnection(ServerState.Disconnected).ConfigureAwait(false);
|
await StopConnectionAsync(ServerState.Disconnected).ConfigureAwait(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -270,7 +305,7 @@ public sealed partial class ApiController : DisposableMediatorSubscriberBase, IM
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task CyclePause(UserData userData)
|
public Task CyclePauseAsync(UserData userData)
|
||||||
{
|
{
|
||||||
CancellationTokenSource cts = new();
|
CancellationTokenSource cts = new();
|
||||||
cts.CancelAfter(TimeSpan.FromSeconds(5));
|
cts.CancelAfter(TimeSpan.FromSeconds(5));
|
||||||
@@ -293,7 +328,7 @@ public sealed partial class ApiController : DisposableMediatorSubscriberBase, IM
|
|||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task Pause(UserData userData)
|
public async Task PauseAsync(UserData userData)
|
||||||
{
|
{
|
||||||
var pair = _pairManager.GetOnlineUserPairs().Single(p => p.UserPair != null && p.UserData == userData);
|
var pair = _pairManager.GetOnlineUserPairs().Single(p => p.UserPair != null && p.UserData == userData);
|
||||||
var perm = pair.UserPair!.OwnPermissions;
|
var perm = pair.UserPair!.OwnPermissions;
|
||||||
@@ -301,9 +336,9 @@ public sealed partial class ApiController : DisposableMediatorSubscriberBase, IM
|
|||||||
await UserSetPairPermissions(new UserPermissionsDto(userData, perm)).ConfigureAwait(false);
|
await UserSetPairPermissions(new UserPermissionsDto(userData, perm)).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<ConnectionDto> GetConnectionDto() => GetConnectionDto(true);
|
public Task<ConnectionDto> GetConnectionDto() => GetConnectionDtoAsync(true);
|
||||||
|
|
||||||
public async Task<ConnectionDto> GetConnectionDto(bool publishConnected = true)
|
public async Task<ConnectionDto> GetConnectionDtoAsync(bool publishConnected)
|
||||||
{
|
{
|
||||||
var dto = await _mareHub!.InvokeAsync<ConnectionDto>(nameof(GetConnectionDto)).ConfigureAwait(false);
|
var dto = await _mareHub!.InvokeAsync<ConnectionDto>(nameof(GetConnectionDto)).ConfigureAwait(false);
|
||||||
if (publishConnected) Mediator.Publish(new ConnectedMessage(dto));
|
if (publishConnected) Mediator.Publish(new ConnectedMessage(dto));
|
||||||
@@ -315,18 +350,18 @@ public sealed partial class ApiController : DisposableMediatorSubscriberBase, IM
|
|||||||
base.Dispose(disposing);
|
base.Dispose(disposing);
|
||||||
|
|
||||||
_healthCheckTokenSource?.Cancel();
|
_healthCheckTokenSource?.Cancel();
|
||||||
_ = Task.Run(async () => await StopConnection(ServerState.Disconnected).ConfigureAwait(false));
|
_ = Task.Run(async () => await StopConnectionAsync(ServerState.Disconnected).ConfigureAwait(false));
|
||||||
_connectionCancellationTokenSource?.Cancel();
|
_connectionCancellationTokenSource?.Cancel();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task ClientHealthCheck(CancellationToken ct)
|
private async Task ClientHealthCheckAsync(CancellationToken ct)
|
||||||
{
|
{
|
||||||
while (!ct.IsCancellationRequested && _mareHub != null)
|
while (!ct.IsCancellationRequested && _mareHub != null)
|
||||||
{
|
{
|
||||||
await Task.Delay(TimeSpan.FromSeconds(30), ct).ConfigureAwait(false);
|
await Task.Delay(TimeSpan.FromSeconds(30), ct).ConfigureAwait(false);
|
||||||
Logger.LogDebug("Checking Client Health State");
|
Logger.LogDebug("Checking Client Health State");
|
||||||
|
|
||||||
bool requireReconnect = await RefreshToken(ct).ConfigureAwait(false);
|
bool requireReconnect = await RefreshTokenAsync(ct).ConfigureAwait(false);
|
||||||
|
|
||||||
if (requireReconnect) break;
|
if (requireReconnect) break;
|
||||||
|
|
||||||
@@ -336,12 +371,12 @@ public sealed partial class ApiController : DisposableMediatorSubscriberBase, IM
|
|||||||
|
|
||||||
private void DalamudUtilOnLogIn()
|
private void DalamudUtilOnLogIn()
|
||||||
{
|
{
|
||||||
_ = Task.Run(() => CreateConnections());
|
_ = Task.Run(() => CreateConnectionsAsync());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DalamudUtilOnLogOut()
|
private void DalamudUtilOnLogOut()
|
||||||
{
|
{
|
||||||
_ = Task.Run(async () => await StopConnection(ServerState.Disconnected).ConfigureAwait(false));
|
_ = Task.Run(async () => await StopConnectionAsync(ServerState.Disconnected).ConfigureAwait(false));
|
||||||
ServerState = ServerState.Offline;
|
ServerState = ServerState.Offline;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -378,12 +413,12 @@ public sealed partial class ApiController : DisposableMediatorSubscriberBase, IM
|
|||||||
_healthCheckTokenSource?.Cancel();
|
_healthCheckTokenSource?.Cancel();
|
||||||
_healthCheckTokenSource?.Dispose();
|
_healthCheckTokenSource?.Dispose();
|
||||||
_healthCheckTokenSource = new CancellationTokenSource();
|
_healthCheckTokenSource = new CancellationTokenSource();
|
||||||
_ = ClientHealthCheck(_healthCheckTokenSource.Token);
|
_ = ClientHealthCheckAsync(_healthCheckTokenSource.Token);
|
||||||
|
|
||||||
_initialized = true;
|
_initialized = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task LoadIninitialPairs()
|
private async Task LoadIninitialPairsAsync()
|
||||||
{
|
{
|
||||||
foreach (var entry in await GroupsGetAll().ConfigureAwait(false))
|
foreach (var entry in await GroupsGetAll().ConfigureAwait(false))
|
||||||
{
|
{
|
||||||
@@ -398,7 +433,7 @@ public sealed partial class ApiController : DisposableMediatorSubscriberBase, IM
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task LoadOnlinePairs()
|
private async Task LoadOnlinePairsAsync()
|
||||||
{
|
{
|
||||||
CensusDataDto? dto = null;
|
CensusDataDto? dto = null;
|
||||||
if (_serverManager.SendCensusData && _lastCensus != null)
|
if (_serverManager.SendCensusData && _lastCensus != null)
|
||||||
@@ -430,27 +465,27 @@ public sealed partial class ApiController : DisposableMediatorSubscriberBase, IM
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task MareHubOnReconnected()
|
private async Task MareHubOnReconnectedAsync()
|
||||||
{
|
{
|
||||||
ServerState = ServerState.Reconnecting;
|
ServerState = ServerState.Reconnecting;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
InitializeApiHooks();
|
InitializeApiHooks();
|
||||||
_connectionDto = await GetConnectionDto(publishConnected: false).ConfigureAwait(false);
|
_connectionDto = await GetConnectionDtoAsync(publishConnected: false).ConfigureAwait(false);
|
||||||
if (_connectionDto.ServerVersion != IMareHub.ApiVersion)
|
if (_connectionDto.ServerVersion != IMareHub.ApiVersion)
|
||||||
{
|
{
|
||||||
await StopConnection(ServerState.VersionMisMatch).ConfigureAwait(false);
|
await StopConnectionAsync(ServerState.VersionMisMatch).ConfigureAwait(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
ServerState = ServerState.Connected;
|
ServerState = ServerState.Connected;
|
||||||
await LoadIninitialPairs().ConfigureAwait(false);
|
await LoadIninitialPairsAsync().ConfigureAwait(false);
|
||||||
await LoadOnlinePairs().ConfigureAwait(false);
|
await LoadOnlinePairsAsync().ConfigureAwait(false);
|
||||||
Mediator.Publish(new ConnectedMessage(_connectionDto));
|
Mediator.Publish(new ConnectedMessage(_connectionDto));
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Logger.LogCritical(ex, "Failure to obtain data after reconnection");
|
Logger.LogCritical(ex, "Failure to obtain data after reconnection");
|
||||||
await StopConnection(ServerState.Disconnected).ConfigureAwait(false);
|
await StopConnectionAsync(ServerState.Disconnected).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -465,7 +500,7 @@ public sealed partial class ApiController : DisposableMediatorSubscriberBase, IM
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<bool> RefreshToken(CancellationToken ct)
|
private async Task<bool> RefreshTokenAsync(CancellationToken ct)
|
||||||
{
|
{
|
||||||
Logger.LogDebug("Checking token");
|
Logger.LogDebug("Checking token");
|
||||||
|
|
||||||
@@ -478,28 +513,28 @@ public sealed partial class ApiController : DisposableMediatorSubscriberBase, IM
|
|||||||
Logger.LogDebug("Reconnecting due to updated token");
|
Logger.LogDebug("Reconnecting due to updated token");
|
||||||
|
|
||||||
_doNotNotifyOnNextInfo = true;
|
_doNotNotifyOnNextInfo = true;
|
||||||
await CreateConnections().ConfigureAwait(false);
|
await CreateConnectionsAsync().ConfigureAwait(false);
|
||||||
requireReconnect = true;
|
requireReconnect = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (MareAuthFailureException ex)
|
catch (MareAuthFailureException ex)
|
||||||
{
|
{
|
||||||
AuthFailureMessage = ex.Reason;
|
AuthFailureMessage = ex.Reason;
|
||||||
await StopConnection(ServerState.Unauthorized).ConfigureAwait(false);
|
await StopConnectionAsync(ServerState.Unauthorized).ConfigureAwait(false);
|
||||||
requireReconnect = true;
|
requireReconnect = true;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Logger.LogWarning(ex, "Could not refresh token, forcing reconnect");
|
Logger.LogWarning(ex, "Could not refresh token, forcing reconnect");
|
||||||
_doNotNotifyOnNextInfo = true;
|
_doNotNotifyOnNextInfo = true;
|
||||||
await CreateConnections().ConfigureAwait(false);
|
await CreateConnectionsAsync().ConfigureAwait(false);
|
||||||
requireReconnect = true;
|
requireReconnect = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return requireReconnect;
|
return requireReconnect;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task StopConnection(ServerState state)
|
private async Task StopConnectionAsync(ServerState state)
|
||||||
{
|
{
|
||||||
ServerState = ServerState.Disconnecting;
|
ServerState = ServerState.Disconnecting;
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
namespace MareSynchronos.WebAPI.SignalR;
|
namespace MareSynchronos.WebAPI.SignalR;
|
||||||
|
|
||||||
public record JwtIdentifier(string ApiUrl, string CharaHash, string SecretKey)
|
public record JwtIdentifier(string ApiUrl, string CharaHash, string UID, string SecretKeyOrOAuth)
|
||||||
{
|
{
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
{
|
{
|
||||||
return "{JwtIdentifier; Url: " + ApiUrl + ", Chara: " + CharaHash + ", HasSecretKey: " + !string.IsNullOrEmpty(SecretKey) + "}";
|
return "{JwtIdentifier; Url: " + ApiUrl + ", Chara: " + CharaHash + ", UID: " + UID + ", HasSecretKeyOrOAuth: " + !string.IsNullOrEmpty(SecretKeyOrOAuth) + "}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -51,7 +51,7 @@ public sealed class TokenProvider : IDisposable, IMediatorSubscriber
|
|||||||
_httpClient.Dispose();
|
_httpClient.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<string> GetNewToken(bool isRenewal, JwtIdentifier identifier, CancellationToken token)
|
public async Task<string> GetNewToken(bool isRenewal, JwtIdentifier identifier, CancellationToken ct)
|
||||||
{
|
{
|
||||||
Uri tokenUri;
|
Uri tokenUri;
|
||||||
string response = string.Empty;
|
string response = string.Empty;
|
||||||
@@ -63,16 +63,34 @@ public sealed class TokenProvider : IDisposable, IMediatorSubscriber
|
|||||||
{
|
{
|
||||||
_logger.LogDebug("GetNewToken: Requesting");
|
_logger.LogDebug("GetNewToken: Requesting");
|
||||||
|
|
||||||
|
if (!_serverManager.CurrentServer.UseOAuth2)
|
||||||
|
{
|
||||||
tokenUri = MareAuth.AuthFullPath(new Uri(_serverManager.CurrentApiUrl
|
tokenUri = MareAuth.AuthFullPath(new Uri(_serverManager.CurrentApiUrl
|
||||||
.Replace("wss://", "https://", StringComparison.OrdinalIgnoreCase)
|
.Replace("wss://", "https://", StringComparison.OrdinalIgnoreCase)
|
||||||
.Replace("ws://", "http://", StringComparison.OrdinalIgnoreCase)));
|
.Replace("ws://", "http://", StringComparison.OrdinalIgnoreCase)));
|
||||||
var secretKey = _serverManager.GetSecretKey(out _)!;
|
var secretKey = _serverManager.GetSecretKey(out _)!;
|
||||||
var auth = secretKey.GetHash256();
|
var auth = secretKey.GetHash256();
|
||||||
result = await _httpClient.PostAsync(tokenUri, new FormUrlEncodedContent(new[]
|
_logger.LogInformation("Sending SecretKey Request to server with auth {auth}", string.Join("", identifier.SecretKeyOrOAuth.Take(10)));
|
||||||
{
|
result = await _httpClient.PostAsync(tokenUri, new FormUrlEncodedContent(
|
||||||
|
[
|
||||||
new KeyValuePair<string, string>("auth", auth),
|
new KeyValuePair<string, string>("auth", auth),
|
||||||
new KeyValuePair<string, string>("charaIdent", await _dalamudUtil.GetPlayerNameHashedAsync().ConfigureAwait(false)),
|
new KeyValuePair<string, string>("charaIdent", await _dalamudUtil.GetPlayerNameHashedAsync().ConfigureAwait(false)),
|
||||||
}), token).ConfigureAwait(false);
|
]), ct).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
tokenUri = MareAuth.AuthWithOauthFullPath(new Uri(_serverManager.CurrentApiUrl
|
||||||
|
.Replace("wss://", "https://", StringComparison.OrdinalIgnoreCase)
|
||||||
|
.Replace("ws://", "http://", StringComparison.OrdinalIgnoreCase)));
|
||||||
|
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, tokenUri.ToString());
|
||||||
|
request.Content = new FormUrlEncodedContent([
|
||||||
|
new KeyValuePair<string, string>("uid", identifier.UID),
|
||||||
|
new KeyValuePair<string, string>("charaIdent", identifier.CharaHash)
|
||||||
|
]);
|
||||||
|
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", identifier.SecretKeyOrOAuth);
|
||||||
|
_logger.LogInformation("Sending OAuth Request to server with auth {auth}", string.Join("", identifier.SecretKeyOrOAuth.Take(10)));
|
||||||
|
result = await _httpClient.SendAsync(request, ct).ConfigureAwait(false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -83,7 +101,7 @@ public sealed class TokenProvider : IDisposable, IMediatorSubscriber
|
|||||||
.Replace("ws://", "http://", StringComparison.OrdinalIgnoreCase)));
|
.Replace("ws://", "http://", StringComparison.OrdinalIgnoreCase)));
|
||||||
HttpRequestMessage request = new(HttpMethod.Get, tokenUri.ToString());
|
HttpRequestMessage request = new(HttpMethod.Get, tokenUri.ToString());
|
||||||
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", _tokenCache[identifier]);
|
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", _tokenCache[identifier]);
|
||||||
result = await _httpClient.SendAsync(request, token).ConfigureAwait(false);
|
result = await _httpClient.SendAsync(request, ct).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
response = await result.Content.ReadAsStringAsync().ConfigureAwait(false);
|
response = await result.Content.ReadAsStringAsync().ConfigureAwait(false);
|
||||||
@@ -102,7 +120,7 @@ public sealed class TokenProvider : IDisposable, IMediatorSubscriber
|
|||||||
Mediator.Publish(new NotificationMessage("Error refreshing token", "Your authentication token could not be renewed. Try reconnecting to Mare manually.",
|
Mediator.Publish(new NotificationMessage("Error refreshing token", "Your authentication token could not be renewed. Try reconnecting to Mare manually.",
|
||||||
NotificationType.Error));
|
NotificationType.Error));
|
||||||
else
|
else
|
||||||
Mediator.Publish(new NotificationMessage("Error generating token", "Your authentication token could not be generated. Check Mares main UI to see the error message.",
|
Mediator.Publish(new NotificationMessage("Error generating token", "Your authentication token could not be generated. Check Mares Main UI to see the error message.",
|
||||||
NotificationType.Error));
|
NotificationType.Error));
|
||||||
Mediator.Publish(new DisconnectedMessage());
|
Mediator.Publish(new DisconnectedMessage());
|
||||||
throw new MareAuthFailureException(response);
|
throw new MareAuthFailureException(response);
|
||||||
@@ -144,9 +162,20 @@ public sealed class TokenProvider : IDisposable, IMediatorSubscriber
|
|||||||
return _lastJwtIdentifier;
|
return _lastJwtIdentifier;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (_serverManager.CurrentServer.UseOAuth2)
|
||||||
|
{
|
||||||
|
var oauthInfo = _serverManager.GetOAuth2(out _)!;
|
||||||
jwtIdentifier = new(_serverManager.CurrentApiUrl,
|
jwtIdentifier = new(_serverManager.CurrentApiUrl,
|
||||||
playerIdentifier,
|
playerIdentifier,
|
||||||
|
oauthInfo.Value.UID, oauthInfo.Value.OAuthToken);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
jwtIdentifier = new(_serverManager.CurrentApiUrl,
|
||||||
|
playerIdentifier,
|
||||||
|
string.Empty,
|
||||||
_serverManager.GetSecretKey(out _)!);
|
_serverManager.GetSecretKey(out _)!);
|
||||||
|
}
|
||||||
_lastJwtIdentifier = jwtIdentifier;
|
_lastJwtIdentifier = jwtIdentifier;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -205,4 +234,39 @@ public sealed class TokenProvider : IDisposable, IMediatorSubscriber
|
|||||||
_logger.LogTrace("GetOrUpdate: Getting new token");
|
_logger.LogTrace("GetOrUpdate: Getting new token");
|
||||||
return await GetNewToken(renewal, jwtIdentifier, ct).ConfigureAwait(false);
|
return await GetNewToken(renewal, jwtIdentifier, ct).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<bool> TryUpdateOAuth2LoginTokenAsync()
|
||||||
|
{
|
||||||
|
var oauth2 = _serverManager.GetOAuth2(out _);
|
||||||
|
if (oauth2 == null) return false;
|
||||||
|
|
||||||
|
var handler = new JwtSecurityTokenHandler();
|
||||||
|
var jwt = handler.ReadJwtToken(oauth2.Value.OAuthToken);
|
||||||
|
if (jwt.ValidTo == DateTime.MinValue || jwt.ValidTo.Subtract(TimeSpan.FromDays(7)) > DateTime.Now)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if (jwt.ValidTo < DateTime.UtcNow)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
|
||||||
|
var tokenUri = MareAuth.RenewOAuthTokenFullPath(new Uri(_serverManager.CurrentApiUrl
|
||||||
|
.Replace("wss://", "https://", StringComparison.OrdinalIgnoreCase)
|
||||||
|
.Replace("ws://", "http://", StringComparison.OrdinalIgnoreCase)));
|
||||||
|
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, tokenUri.ToString());
|
||||||
|
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", oauth2.Value.OAuthToken);
|
||||||
|
_logger.LogInformation("Sending Request to server with auth {auth}", string.Join("", oauth2.Value.OAuthToken.Take(10)));
|
||||||
|
var result = await _httpClient.SendAsync(request).ConfigureAwait(false);
|
||||||
|
|
||||||
|
if (!result.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
_logger.LogWarning("Could not renew OAuth2 Login token, error code {error}", result.StatusCode);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var newToken = await result.Content.ReadAsStringAsync().ConfigureAwait(false);
|
||||||
|
_serverManager.CurrentServer.OAuthToken = newToken;
|
||||||
|
_serverManager.Save();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -13,4 +13,6 @@ public enum ServerState
|
|||||||
RateLimited,
|
RateLimited,
|
||||||
NoSecretKey,
|
NoSecretKey,
|
||||||
MultiChara,
|
MultiChara,
|
||||||
|
OAuthMisconfigured,
|
||||||
|
OAuthLoginTokenStale
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user