Update 0.1.6 - Deploy AutoDetect, last debug and optimization

This commit is contained in:
2025-09-13 13:41:00 +02:00
parent b79a51748f
commit 04a8ee3186
10 changed files with 297 additions and 45 deletions

View File

@@ -3,7 +3,7 @@
<PropertyGroup>
<AssemblyName>UmbraSync</AssemblyName>
<RootNamespace>UmbraSync</RootNamespace>
<Version>0.1.4.1</Version>
<Version>0.1.6.2</Version>
</PropertyGroup>
<ItemGroup>

View File

@@ -61,4 +61,23 @@ public class AutoDetectRequestService
}
return ok;
}
public async Task<bool> SendAcceptNotifyAsync(string targetUid, CancellationToken ct = default)
{
var endpoint = _configProvider.AcceptEndpoint;
if (string.IsNullOrEmpty(endpoint))
{
_logger.LogDebug("No accept endpoint configured");
return false;
}
string? displayName = null;
try
{
var me = await _dalamud.RunOnFrameworkThread(() => _dalamud.GetPlayerCharacter()).ConfigureAwait(false);
displayName = me?.Name.TextValue;
}
catch { }
_logger.LogInformation("Nearby: sending accept notify via {endpoint}", endpoint);
return await _client.SendAcceptAsync(endpoint!, targetUid, displayName, ct).ConfigureAwait(false);
}
}

View File

@@ -29,6 +29,7 @@ public class DiscoveryConfigProvider
public bool HasConfig => _config != null;
public bool NearbyEnabled => _config?.NearbyDiscovery?.Enabled ?? false;
public byte[]? Salt => _config?.NearbyDiscovery?.SaltBytes;
public string? SaltB64 => _config?.NearbyDiscovery?.SaltB64;
public DateTimeOffset? SaltExpiresAt => _config?.NearbyDiscovery?.SaltExpiresAt;
public int RefreshSec => _config?.NearbyDiscovery?.RefreshSec ?? 300;
public int MinQueryIntervalMs => _config?.NearbyDiscovery?.Policies?.MinQueryIntervalMs ?? 2000;
@@ -36,6 +37,7 @@ public class DiscoveryConfigProvider
public string? PublishEndpoint => _config?.NearbyDiscovery?.Endpoints?.Publish;
public string? QueryEndpoint => _config?.NearbyDiscovery?.Endpoints?.Query;
public string? RequestEndpoint => _config?.NearbyDiscovery?.Endpoints?.Request;
public string? AcceptEndpoint => _config?.NearbyDiscovery?.Endpoints?.Accept;
public bool TryLoadFromStapled()
{
@@ -50,7 +52,7 @@ public class DiscoveryConfigProvider
root.NearbyDiscovery?.Hydrate();
_config = root;
_lastLoad = DateTimeOffset.UtcNow;
_logger.LogInformation("Loaded Nearby well-known (stapled), enabled={enabled}, expires={exp}", NearbyEnabled, _config?.NearbyDiscovery?.SaltExpiresAt);
_logger.LogDebug("Loaded Nearby well-known (stapled), enabled={enabled}, expires={exp}", NearbyEnabled, _config?.NearbyDiscovery?.SaltExpiresAt);
return true;
}
catch (Exception ex)
@@ -158,6 +160,7 @@ public class DiscoveryConfigProvider
[JsonPropertyName("publish")] public string? Publish { get; set; }
[JsonPropertyName("query")] public string? Query { get; set; }
[JsonPropertyName("request")] public string? Request { get; set; }
[JsonPropertyName("accept")] public string? Accept { get; set; }
}
private sealed class Policies

View File

@@ -28,6 +28,10 @@ public class NearbyDiscoveryService : IHostedService, IMediatorSubscriber
private bool _loggedConfigReady;
private string? _lastSnapshotSig;
private volatile bool _isConnected;
private bool _notifiedDisabled;
private bool _notifiedEnabled;
private bool _disableSent;
private bool _lastAutoDetectState;
public NearbyDiscoveryService(ILogger<NearbyDiscoveryService> logger, MareMediator mediator,
MareConfigService config, DiscoveryConfigProvider configProvider, DalamudUtilService dalamudUtilService,
@@ -50,6 +54,7 @@ public class NearbyDiscoveryService : IHostedService, IMediatorSubscriber
_mediator.Subscribe<ConnectedMessage>(this, _ => { _isConnected = true; _configProvider.TryLoadFromStapled(); });
_mediator.Subscribe<DisconnectedMessage>(this, _ => { _isConnected = false; _lastPublishedSignature = null; });
_ = Task.Run(() => Loop(_loopCts.Token));
_lastAutoDetectState = _config.Current.EnableAutoDetectDiscovery;
return Task.CompletedTask;
}
@@ -62,20 +67,112 @@ public class NearbyDiscoveryService : IHostedService, IMediatorSubscriber
private async Task Loop(CancellationToken ct)
{
// best effort config load
_configProvider.TryLoadFromStapled();
while (!ct.IsCancellationRequested)
{
try
{
bool currentState = _config.Current.EnableAutoDetectDiscovery;
if (currentState != _lastAutoDetectState)
{
_lastAutoDetectState = currentState;
if (currentState)
{
// Force immediate publish on toggle ON
try
{
// Ensure well-known is present
if (!_configProvider.HasConfig || _configProvider.IsExpired())
{
if (!_configProvider.TryLoadFromStapled())
{
await _configProvider.TryFetchFromServerAsync(ct).ConfigureAwait(false);
}
}
var ep = _configProvider.PublishEndpoint;
var saltBytes = _configProvider.Salt;
if (!string.IsNullOrEmpty(ep) && saltBytes is { Length: > 0 })
{
var saltHex = Convert.ToHexString(saltBytes);
string? displayName = null;
ushort meWorld = 0;
try
{
var me = await _dalamud.RunOnFrameworkThread(() => _dalamud.GetPlayerCharacter()).ConfigureAwait(false);
if (me != null)
{
displayName = me.Name.TextValue;
if (me is Dalamud.Game.ClientState.Objects.SubKinds.IPlayerCharacter mePc)
meWorld = (ushort)mePc.HomeWorld.RowId;
}
}
catch { }
if (!string.IsNullOrEmpty(displayName))
{
var selfHash = (saltHex + displayName + meWorld.ToString()).GetHash256();
_lastPublishedSignature = null; // ensure future loop doesn't skip
var okNow = await _api.PublishAsync(ep!, new[] { selfHash }, displayName, ct, _config.Current.AllowAutoDetectPairRequests).ConfigureAwait(false);
_logger.LogInformation("Nearby immediate publish on toggle ON: {result}", okNow ? "success" : "failed");
}
}
}
catch (Exception ex)
{
_logger.LogDebug(ex, "Nearby immediate publish on toggle ON failed");
}
if (!_notifiedEnabled)
{
_mediator.Publish(new NotificationMessage("Nearby Detection", "AutoDetect enabled : you are now visible.", default));
_notifiedEnabled = true;
_notifiedDisabled = false;
_disableSent = false;
}
}
else
{
var ep = _configProvider.PublishEndpoint;
if (!string.IsNullOrEmpty(ep) && !_disableSent)
{
var disableUrl = ep.Replace("/publish", "/disable");
try { await _api.DisableAsync(disableUrl, ct).ConfigureAwait(false); _disableSent = true; } catch { }
}
if (!_notifiedDisabled)
{
_mediator.Publish(new NotificationMessage("Nearby Detection", "AutoDetect disabled : you are not visible.", default));
_notifiedDisabled = true;
_notifiedEnabled = false;
}
}
}
if (!_config.Current.EnableAutoDetectDiscovery || !_dalamud.IsLoggedIn || !_isConnected)
{
if (!_config.Current.EnableAutoDetectDiscovery && !string.IsNullOrEmpty(_configProvider.PublishEndpoint))
{
var disableUrl = _configProvider.PublishEndpoint.Replace("/publish", "/disable");
try
{
if (!_disableSent)
{
await _api.DisableAsync(disableUrl, ct).ConfigureAwait(false);
_disableSent = true;
}
}
catch { }
if (!_notifiedDisabled)
{
_mediator.Publish(new NotificationMessage("Nearby Detection", "AutoDetect disabled : you are not visible.", default));
_notifiedDisabled = true;
_notifiedEnabled = false;
}
}
await Task.Delay(1000, ct).ConfigureAwait(false);
continue;
}
// Ensure we have a valid Nearby config: try stapled, then HTTP fallback
if (!_configProvider.HasConfig || _configProvider.IsExpired())
{
if (!_configProvider.TryLoadFromStapled())
@@ -114,7 +211,6 @@ public class NearbyDiscoveryService : IHostedService, IMediatorSubscriber
hashes.Add(h);
}
// Debug snapshot once per change
try
{
var snapSig = string.Join(',', hashes.OrderBy(s => s, StringComparer.Ordinal)).GetHash256();
@@ -134,7 +230,6 @@ public class NearbyDiscoveryService : IHostedService, IMediatorSubscriber
}
catch { }
// Publish OUR presence (own hash) if endpoint is available (deduplicated)
if (!string.IsNullOrEmpty(_configProvider.PublishEndpoint))
{
string? displayName = null;
@@ -161,9 +256,19 @@ public class NearbyDiscoveryService : IHostedService, IMediatorSubscriber
{
_lastPublishedSignature = sig;
var shortSelf = selfHash!.Length > 8 ? selfHash[..8] : selfHash;
_logger.LogInformation("Nearby publish: self presence updated (hash={hash})", shortSelf);
var ok = await _api.PublishAsync(_configProvider.PublishEndpoint!, new[] { selfHash! }, displayName, ct).ConfigureAwait(false);
_logger.LogDebug("Nearby publish: self presence updated (hash={hash})", shortSelf);
var ok = await _api.PublishAsync(_configProvider.PublishEndpoint!, new[] { selfHash! }, displayName, ct, _config.Current.AllowAutoDetectPairRequests).ConfigureAwait(false);
_logger.LogInformation("Nearby publish result: {result}", ok ? "success" : "failed");
if (ok)
{
if (!_notifiedEnabled)
{
_mediator.Publish(new NotificationMessage("Nearby Detection", "AutoDetect enabled : you are now visible.", default));
_notifiedEnabled = true;
_notifiedDisabled = false;
_disableSent = false; // allow future /disable when turning off again
}
}
}
else
{
@@ -193,7 +298,7 @@ public class NearbyDiscoveryService : IHostedService, IMediatorSubscriber
if (hashToIndex.TryGetValue(m.Hash, out var idx))
{
var e = entries[idx];
entries[idx] = new NearbyEntry(e.Name, e.WorldId, e.Distance, true, m.Token);
entries[idx] = new NearbyEntry(e.Name, e.WorldId, e.Distance, true, m.Token, m.DisplayName, m.Uid);
}
}
}
@@ -204,13 +309,18 @@ public class NearbyDiscoveryService : IHostedService, IMediatorSubscriber
if (matchCount != _lastMatchCount)
{
_lastMatchCount = matchCount;
_logger.LogInformation("Nearby: {count} Umbra users nearby", matchCount);
_logger.LogDebug("Nearby: {count} Umbra users nearby", matchCount);
}
}
}
catch (Exception ex)
{
_logger.LogDebug(ex, "Nearby query failed; falling back to local list");
if (ex.Message.Contains("DISCOVERY_SALT_EXPIRED", StringComparison.OrdinalIgnoreCase))
{
_logger.LogInformation("Nearby: salt expired, refetching well-known");
try { await _configProvider.TryFetchFromServerAsync(ct).ConfigureAwait(false); } catch { }
}
}
}
else
@@ -218,12 +328,13 @@ public class NearbyDiscoveryService : IHostedService, IMediatorSubscriber
if (!_loggedLocalOnly)
{
_loggedLocalOnly = true;
_logger.LogInformation("Nearby: well-known not available or disabled; running in local-only mode");
_logger.LogDebug("Nearby: well-known not available or disabled; running in local-only mode");
}
}
_mediator.Publish(new DiscoveryListUpdated(entries));
var delayMs = Math.Max(1000, _configProvider.MinQueryIntervalMs);
if (entries.Count == 0) delayMs = Math.Max(delayMs, 5000);
await Task.Delay(delayMs, ct).ConfigureAwait(false);
}
catch (OperationCanceledException) { }
@@ -259,7 +370,7 @@ public class NearbyDiscoveryService : IHostedService, IMediatorSubscriber
if (obj is Dalamud.Game.ClientState.Objects.SubKinds.IPlayerCharacter pc)
worldId = (ushort)pc.HomeWorld.RowId;
list.Add(new NearbyEntry(name, worldId, dist, false, null));
list.Add(new NearbyEntry(name, worldId, dist, false, null, null, null));
}
}
catch

View File

@@ -11,14 +11,17 @@ public sealed class NearbyPendingService : IMediatorSubscriber
private readonly ILogger<NearbyPendingService> _logger;
private readonly MareMediator _mediator;
private readonly ApiController _api;
private readonly AutoDetectRequestService _requestService;
private readonly ConcurrentDictionary<string, string> _pending = new(StringComparer.Ordinal);
private static readonly Regex ReqRegex = new(@"^Nearby Request: (.+) \[(?<uid>[A-Z0-9]+)\]$", RegexOptions.Compiled);
private static readonly Regex AcceptRegex = new(@"^Nearby Accept: (.+) \[(?<uid>[A-Z0-9]+)\]$", RegexOptions.Compiled);
public NearbyPendingService(ILogger<NearbyPendingService> logger, MareMediator mediator, ApiController api)
public NearbyPendingService(ILogger<NearbyPendingService> logger, MareMediator mediator, ApiController api, AutoDetectRequestService requestService)
{
_logger = logger;
_mediator = mediator;
_api = api;
_requestService = requestService;
_mediator.Subscribe<NotificationMessage>(this, OnNotification);
}
@@ -30,6 +33,19 @@ public sealed class NearbyPendingService : IMediatorSubscriber
{
// Watch info messages for Nearby request pattern
if (msg.Type != MareSynchronos.MareConfiguration.Models.NotificationType.Info) return;
var ma = AcceptRegex.Match(msg.Message);
if (ma.Success)
{
var uidA = ma.Groups["uid"].Value;
if (!string.IsNullOrEmpty(uidA))
{
_ = _api.UserAddPair(new MareSynchronos.API.Dto.User.UserDto(new MareSynchronos.API.Data.UserData(uidA)));
_pending.TryRemove(uidA, out _);
_logger.LogInformation("NearbyPending: auto-accepted pairing with {uid}", uidA);
}
return;
}
var m = ReqRegex.Match(msg.Message);
if (!m.Success) return;
var uid = m.Groups["uid"].Value;
@@ -59,6 +75,7 @@ public sealed class NearbyPendingService : IMediatorSubscriber
{
await _api.UserAddPair(new MareSynchronos.API.Dto.User.UserDto(new MareSynchronos.API.Data.UserData(uid))).ConfigureAwait(false);
_pending.TryRemove(uid, out _);
_ = _requestService.SendAcceptNotifyAsync(uid);
return true;
}
catch (Exception ex)

View File

@@ -108,7 +108,7 @@ public record GPoseLobbyReceiveCharaData(CharaDataDownloadDto CharaDataDownloadD
public record GPoseLobbyReceivePoseData(UserData UserData, PoseData PoseData) : MessageBase;
public record GPoseLobbyReceiveWorldData(UserData UserData, WorldData WorldData) : MessageBase;
public record NearbyEntry(string Name, ushort WorldId, float Distance, bool IsMatch, string? Token);
public record NearbyEntry(string Name, ushort WorldId, float Distance, bool IsMatch, string? Token, string? DisplayName, string? Uid);
public record DiscoveryListUpdated(List<NearbyEntry> Entries) : MessageBase;
public record PluginChangeMessage(string InternalName, Version Version, bool IsLoaded) : KeyedMessage(InternalName);

View File

@@ -6,9 +6,12 @@ using Dalamud.Interface.Utility.Raii;
using Dalamud.Plugin.Services;
using MareSynchronos.MareConfiguration;
using MareSynchronos.Services;
using MareSynchronos.PlayerData.Pairs;
using MareSynchronos.Services.Mediator;
using Microsoft.Extensions.Logging;
using System.Numerics;
using System.Globalization;
using System.Text;
namespace MareSynchronos.UI;
@@ -18,18 +21,20 @@ public class AutoDetectUi : WindowMediatorSubscriberBase
private readonly DalamudUtilService _dalamud;
private readonly IObjectTable _objectTable;
private readonly Services.AutoDetect.AutoDetectRequestService _requestService;
private readonly PairManager _pairManager;
private List<Services.Mediator.NearbyEntry> _entries = new();
public AutoDetectUi(ILogger<AutoDetectUi> logger, MareMediator mediator,
MareConfigService configService, DalamudUtilService dalamudUtilService, IObjectTable objectTable,
Services.AutoDetect.AutoDetectRequestService requestService,
Services.AutoDetect.AutoDetectRequestService requestService, PairManager pairManager,
PerformanceCollectorService performanceCollectorService)
: base(logger, mediator, "Umbra Nearby", performanceCollectorService)
: base(logger, mediator, "AutoDetect", performanceCollectorService)
{
_configService = configService;
_dalamud = dalamudUtilService;
_objectTable = objectTable;
_requestService = requestService;
_pairManager = pairManager;
Flags |= ImGuiWindowFlags.NoScrollbar;
SizeConstraints = new WindowSizeConstraints()
@@ -78,7 +83,7 @@ public class AutoDetectUi : WindowMediatorSubscriberBase
ImGui.TableSetupColumn("Action");
ImGui.TableHeadersRow();
var data = _entries.Count > 0 ? _entries : BuildLocalSnapshot(maxDist);
var data = _entries.Count > 0 ? _entries.Where(e => e.IsMatch).ToList() : new List<Services.Mediator.NearbyEntry>();
foreach (var e in data)
{
ImGui.TableNextColumn();
@@ -88,20 +93,25 @@ public class AutoDetectUi : WindowMediatorSubscriberBase
ImGui.TableNextColumn();
ImGui.TextUnformatted(float.IsNaN(e.Distance) ? "-" : $"{e.Distance:0.0} m");
ImGui.TableNextColumn();
ImGui.TextUnformatted(e.IsMatch ? "On Umbra" : "Unknown");
bool alreadyPaired = IsAlreadyPairedByUidOrAlias(e);
string status = alreadyPaired ? "Paired" : (string.IsNullOrEmpty(e.Token) ? "Requests disabled" : "On Umbra");
ImGui.TextUnformatted(status);
ImGui.TableNextColumn();
bool allowRequests = _configService.Current.AllowAutoDetectPairRequests;
using (ImRaii.Disabled(!allowRequests || !e.IsMatch || string.IsNullOrEmpty(e.Token)))
using (ImRaii.Disabled(alreadyPaired || string.IsNullOrEmpty(e.Token)))
{
if (ImGui.Button($"Send request##{e.Name}"))
if (alreadyPaired)
{
ImGui.Button($"Already sync##{e.Name}");
}
else if (string.IsNullOrEmpty(e.Token))
{
ImGui.Button($"Requests disabled##{e.Name}");
}
else if (ImGui.Button($"Send request##{e.Name}"))
{
_ = _requestService.SendRequestAsync(e.Token!);
}
}
if (!allowRequests)
{
UiSharedService.AttachToolTip("Enable 'Allow pair requests' in Settings to send a request.");
}
}
ImGui.EndTable();
@@ -140,8 +150,52 @@ public class AutoDetectUi : WindowMediatorSubscriberBase
string name = obj.Name.ToString();
ushort worldId = 0;
if (obj is IPlayerCharacter pc) worldId = (ushort)pc.HomeWorld.RowId;
list.Add(new Services.Mediator.NearbyEntry(name, worldId, dist, false, null));
list.Add(new Services.Mediator.NearbyEntry(name, worldId, dist, false, null, null, null));
}
return list;
}
private bool IsAlreadyPairedByUidOrAlias(Services.Mediator.NearbyEntry e)
{
try
{
// 1) Match by UID when available (authoritative)
if (!string.IsNullOrEmpty(e.Uid))
{
foreach (var p in _pairManager.DirectPairs)
{
if (string.Equals(p.UserData.UID, e.Uid, StringComparison.Ordinal))
return true;
}
}
// 2) Fallback on alias/name (legacy compatibility)
var key = NormalizeKey(e.DisplayName ?? e.Name);
if (string.IsNullOrEmpty(key)) return false;
foreach (var p in _pairManager.DirectPairs)
{
if (NormalizeKey(p.UserData.AliasOrUID) == key) return true;
if (!string.IsNullOrEmpty(p.UserData.Alias) && NormalizeKey(p.UserData.Alias!) == key) return true;
}
}
catch
{
// ignore
}
return false;
}
private static string NormalizeKey(string? input)
{
if (string.IsNullOrWhiteSpace(input)) return string.Empty;
var formD = input.Normalize(NormalizationForm.FormD);
var sb = new StringBuilder(formD.Length);
foreach (var ch in formD)
{
var cat = CharUnicodeInfo.GetUnicodeCategory(ch);
if (cat != UnicodeCategory.NonSpacingMark)
sb.Append(char.ToLowerInvariant(ch));
}
return sb.ToString();
}
}

View File

@@ -384,11 +384,8 @@ public class CompactUi : WindowMediatorSubscriberBase
_uiSharedService.IconText(icon);
if (ImGui.IsItemClicked(ImGuiMouseButton.Left)) _nearbyOpen = !_nearbyOpen;
ImGui.SameLine();
var nearbyCount = _nearbyEntries?.Count ?? 0;
var onUmbra = _nearbyEntries?.Count(e => e.IsMatch) ?? 0;
ImGui.TextUnformatted(onUmbra > 0
? $"Nearby ({nearbyCount} — {onUmbra} on Umbra)"
: $"Nearby ({nearbyCount} Players)");
ImGui.TextUnformatted($"Nearby ({onUmbra})");
if (ImGui.IsItemClicked(ImGuiMouseButton.Left)) _nearbyOpen = !_nearbyOpen;
// Header action button to open Nearby window
@@ -404,7 +401,7 @@ public class CompactUi : WindowMediatorSubscriberBase
if (_nearbyOpen)
{
ImGui.Indent();
if (nearbyCount == 0)
if (onUmbra == 0)
{
UiSharedService.ColorTextWrapped("No nearby players detected.", ImGuiColors.DalamudGrey3);
}

View File

@@ -235,13 +235,6 @@ public class SettingsUi : WindowMediatorSubscriberBase
_configService.Current.AutoDetectMaxDistanceMeters = maxMeters;
_configService.Save();
}
int muteMin = _configService.Current.AutoDetectMuteMinutes;
ImGui.SetNextItemWidth(200 * ImGuiHelpers.GlobalScale);
if (ImGui.SliderInt("Default mute duration (minutes)", ref muteMin, 1, 120))
{
_configService.Current.AutoDetectMuteMinutes = muteMin;
_configService.Save();
}
ImGui.Unindent();
}

View File

@@ -3,6 +3,7 @@ using System.Text;
using System.Text.Json;
using Microsoft.Extensions.Logging;
using MareSynchronos.WebAPI.SignalR;
using MareSynchronos.Services.AutoDetect;
namespace MareSynchronos.WebAPI.AutoDetect;
@@ -10,13 +11,16 @@ public class DiscoveryApiClient
{
private readonly ILogger<DiscoveryApiClient> _logger;
private readonly TokenProvider _tokenProvider;
private readonly DiscoveryConfigProvider _configProvider;
private readonly HttpClient _httpClient = new();
private static readonly JsonSerializerOptions JsonOpt = new() { PropertyNameCaseInsensitive = true };
// private readonly ISaltProvider _saltProvider; // For future use if needed
public DiscoveryApiClient(ILogger<DiscoveryApiClient> logger, TokenProvider tokenProvider)
public DiscoveryApiClient(ILogger<DiscoveryApiClient> logger, TokenProvider tokenProvider, DiscoveryConfigProvider configProvider)
{
_logger = logger;
_tokenProvider = tokenProvider;
_configProvider = configProvider;
_httpClient.Timeout = TimeSpan.FromSeconds(30);
}
@@ -28,7 +32,11 @@ public class DiscoveryApiClient
if (string.IsNullOrEmpty(token)) return [];
using var req = new HttpRequestMessage(HttpMethod.Post, endpoint);
req.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
var body = JsonSerializer.Serialize(new { hashes = hashes.Distinct(StringComparer.Ordinal).ToArray() });
var body = JsonSerializer.Serialize(new
{
hashes = hashes.Distinct(StringComparer.Ordinal).ToArray(),
salt = _configProvider.SaltB64
});
req.Content = new StringContent(body, Encoding.UTF8, "application/json");
var resp = await _httpClient.SendAsync(req, ct).ConfigureAwait(false);
resp.EnsureSuccessStatusCode();
@@ -70,7 +78,7 @@ public class DiscoveryApiClient
}
}
public async Task<bool> PublishAsync(string endpoint, IEnumerable<string> hashes, string? displayName, CancellationToken ct)
public async Task<bool> PublishAsync(string endpoint, IEnumerable<string> hashes, string? displayName, CancellationToken ct, bool allowRequests = true)
{
try
{
@@ -78,7 +86,13 @@ public class DiscoveryApiClient
if (string.IsNullOrEmpty(jwt)) return false;
using var req = new HttpRequestMessage(HttpMethod.Post, endpoint);
req.Headers.Authorization = new AuthenticationHeaderValue("Bearer", jwt);
var bodyObj = new { hashes = hashes.Distinct(StringComparer.Ordinal).ToArray(), displayName };
var bodyObj = new
{
hashes = hashes.Distinct(StringComparer.Ordinal).ToArray(),
displayName,
salt = _configProvider.SaltB64,
allowRequests
};
var body = JsonSerializer.Serialize(bodyObj);
req.Content = new StringContent(body, Encoding.UTF8, "application/json");
var resp = await _httpClient.SendAsync(req, ct).ConfigureAwait(false);
@@ -90,11 +104,55 @@ public class DiscoveryApiClient
return false;
}
}
public async Task<bool> SendAcceptAsync(string endpoint, string targetUid, string? displayName, CancellationToken ct)
{
try
{
var jwt = await _tokenProvider.GetOrUpdateToken(ct).ConfigureAwait(false);
if (string.IsNullOrEmpty(jwt)) return false;
using var req = new HttpRequestMessage(HttpMethod.Post, endpoint);
req.Headers.Authorization = new AuthenticationHeaderValue("Bearer", jwt);
var bodyObj = new { targetUid, displayName };
var body = JsonSerializer.Serialize(bodyObj);
req.Content = new StringContent(body, Encoding.UTF8, "application/json");
var resp = await _httpClient.SendAsync(req, ct).ConfigureAwait(false);
return resp.IsSuccessStatusCode;
}
catch (Exception ex)
{
_logger.LogWarning(ex, "Discovery accept notify failed");
return false;
}
}
public async Task DisableAsync(string endpoint, CancellationToken ct)
{
try
{
var jwt = await _tokenProvider.GetOrUpdateToken(ct).ConfigureAwait(false);
if (string.IsNullOrEmpty(jwt)) return;
using var req = new HttpRequestMessage(HttpMethod.Post, endpoint);
req.Headers.Authorization = new AuthenticationHeaderValue("Bearer", jwt);
// no body required
var resp = await _httpClient.SendAsync(req, ct).ConfigureAwait(false);
if (!resp.IsSuccessStatusCode)
{
string txt = string.Empty;
try { txt = await resp.Content.ReadAsStringAsync(ct).ConfigureAwait(false); } catch { }
_logger.LogWarning("Discovery disable failed: {code} {reason} {body}", (int)resp.StatusCode, resp.ReasonPhrase, txt);
}
}
catch (Exception ex)
{
_logger.LogWarning(ex, "Discovery disable failed");
}
}
}
public sealed class ServerMatch
{
public string Hash { get; set; } = string.Empty;
public string Token { get; set; } = string.Empty;
public string? Token { get; set; }
public string? Uid { get; set; }
public string? DisplayName { get; set; }
}