From b79a51748fa4e5c3142fa0fe3812bfbef45ae6a1 Mon Sep 17 00:00:00 2001 From: SirConstance Date: Thu, 11 Sep 2025 22:37:29 +0200 Subject: [PATCH] Update 0.1.4 - AutoDetect WIP Debug & Fix UI & Optimization --- MareSynchronos/MareSynchronos.csproj | 2 +- MareSynchronos/Plugin.cs | 1 + .../AutoDetect/AutoDetectRequestService.cs | 31 +++++++- .../AutoDetect/DiscoveryConfigProvider.cs | 2 +- .../AutoDetect/NearbyDiscoveryService.cs | 61 +++++++++++++--- .../AutoDetect/NearbyPendingService.cs | 70 +++++++++++++++++++ MareSynchronos/UI/CompactUI.cs | 58 +++++++++++---- MareSynchronos/UI/SettingsUi.cs | 2 +- MareSynchronos/UmbraSync.json | 2 +- .../WebAPI/AutoDetect/DiscoveryApiClient.cs | 16 +++-- 10 files changed, 211 insertions(+), 34 deletions(-) create mode 100644 MareSynchronos/Services/AutoDetect/NearbyPendingService.cs diff --git a/MareSynchronos/MareSynchronos.csproj b/MareSynchronos/MareSynchronos.csproj index a3759a3..b0ff00a 100644 --- a/MareSynchronos/MareSynchronos.csproj +++ b/MareSynchronos/MareSynchronos.csproj @@ -3,7 +3,7 @@ UmbraSync UmbraSync - 0.1.2.0 + 0.1.4.1 diff --git a/MareSynchronos/Plugin.cs b/MareSynchronos/Plugin.cs index 12761b5..d357122 100644 --- a/MareSynchronos/Plugin.cs +++ b/MareSynchronos/Plugin.cs @@ -100,6 +100,7 @@ public sealed class Plugin : IDalamudPlugin collection.AddSingleton(); collection.AddSingleton(); collection.AddSingleton(); + collection.AddSingleton(); collection.AddSingleton(); collection.AddSingleton(); collection.AddSingleton(); diff --git a/MareSynchronos/Services/AutoDetect/AutoDetectRequestService.cs b/MareSynchronos/Services/AutoDetect/AutoDetectRequestService.cs index cac9879..0ee9e31 100644 --- a/MareSynchronos/Services/AutoDetect/AutoDetectRequestService.cs +++ b/MareSynchronos/Services/AutoDetect/AutoDetectRequestService.cs @@ -1,6 +1,9 @@ using Microsoft.Extensions.Logging; using MareSynchronos.WebAPI.AutoDetect; using MareSynchronos.MareConfiguration; +using MareSynchronos.Services; +using MareSynchronos.Services.Mediator; +using NotificationType = MareSynchronos.MareConfiguration.Models.NotificationType; namespace MareSynchronos.Services.AutoDetect; @@ -10,13 +13,17 @@ public class AutoDetectRequestService private readonly DiscoveryConfigProvider _configProvider; private readonly DiscoveryApiClient _client; private readonly MareConfigService _configService; + private readonly DalamudUtilService _dalamud; + private readonly MareMediator _mediator; - public AutoDetectRequestService(ILogger logger, DiscoveryConfigProvider configProvider, DiscoveryApiClient client, MareConfigService configService) + public AutoDetectRequestService(ILogger logger, DiscoveryConfigProvider configProvider, DiscoveryApiClient client, MareConfigService configService, MareMediator mediator, DalamudUtilService dalamudUtilService) { _logger = logger; _configProvider = configProvider; _client = client; _configService = configService; + _mediator = mediator; + _dalamud = dalamudUtilService; } public async Task SendRequestAsync(string token, CancellationToken ct = default) @@ -24,14 +31,34 @@ public class AutoDetectRequestService if (!_configService.Current.AllowAutoDetectPairRequests) { _logger.LogDebug("Nearby request blocked: AllowAutoDetectPairRequests is disabled"); + _mediator.Publish(new NotificationMessage("Nearby request blocked", "Enable 'Allow pair requests' in Settings to send requests.", NotificationType.Info)); return false; } var endpoint = _configProvider.RequestEndpoint; if (string.IsNullOrEmpty(endpoint)) { _logger.LogDebug("No request endpoint configured"); + _mediator.Publish(new NotificationMessage("Nearby request failed", "Server does not expose request endpoint.", NotificationType.Error)); return false; } - return await _client.SendRequestAsync(endpoint!, token, ct).ConfigureAwait(false); + string? displayName = null; + try + { + var me = await _dalamud.RunOnFrameworkThread(() => _dalamud.GetPlayerCharacter()).ConfigureAwait(false); + displayName = me?.Name.TextValue; + } + catch { } + + _logger.LogInformation("Nearby: sending pair request via {endpoint}", endpoint); + var ok = await _client.SendRequestAsync(endpoint!, token, displayName, ct).ConfigureAwait(false); + if (ok) + { + _mediator.Publish(new NotificationMessage("Nearby request sent", "The other user will receive a request notification.", NotificationType.Info)); + } + else + { + _mediator.Publish(new NotificationMessage("Nearby request failed", "The server rejected the request. Try again soon.", NotificationType.Warning)); + } + return ok; } } diff --git a/MareSynchronos/Services/AutoDetect/DiscoveryConfigProvider.cs b/MareSynchronos/Services/AutoDetect/DiscoveryConfigProvider.cs index 8e9fbd2..248aefc 100644 --- a/MareSynchronos/Services/AutoDetect/DiscoveryConfigProvider.cs +++ b/MareSynchronos/Services/AutoDetect/DiscoveryConfigProvider.cs @@ -50,7 +50,7 @@ public class DiscoveryConfigProvider root.NearbyDiscovery?.Hydrate(); _config = root; _lastLoad = DateTimeOffset.UtcNow; - _logger.LogDebug("Loaded Nearby well-known (stapled), enabled={enabled}", NearbyEnabled); + _logger.LogInformation("Loaded Nearby well-known (stapled), enabled={enabled}, expires={exp}", NearbyEnabled, _config?.NearbyDiscovery?.SaltExpiresAt); return true; } catch (Exception ex) diff --git a/MareSynchronos/Services/AutoDetect/NearbyDiscoveryService.cs b/MareSynchronos/Services/AutoDetect/NearbyDiscoveryService.cs index 1a35a1d..06a644b 100644 --- a/MareSynchronos/Services/AutoDetect/NearbyDiscoveryService.cs +++ b/MareSynchronos/Services/AutoDetect/NearbyDiscoveryService.cs @@ -25,6 +25,9 @@ public class NearbyDiscoveryService : IHostedService, IMediatorSubscriber private bool _loggedLocalOnly; private int _lastLocalCount = -1; private int _lastMatchCount = -1; + private bool _loggedConfigReady; + private string? _lastSnapshotSig; + private volatile bool _isConnected; public NearbyDiscoveryService(ILogger logger, MareMediator mediator, MareConfigService config, DiscoveryConfigProvider configProvider, DalamudUtilService dalamudUtilService, @@ -44,7 +47,8 @@ public class NearbyDiscoveryService : IHostedService, IMediatorSubscriber public Task StartAsync(CancellationToken cancellationToken) { _loopCts = new CancellationTokenSource(); - _mediator.Subscribe(this, _ => _configProvider.TryLoadFromStapled()); + _mediator.Subscribe(this, _ => { _isConnected = true; _configProvider.TryLoadFromStapled(); }); + _mediator.Subscribe(this, _ => { _isConnected = false; _lastPublishedSignature = null; }); _ = Task.Run(() => Loop(_loopCts.Token)); return Task.CompletedTask; } @@ -65,7 +69,7 @@ public class NearbyDiscoveryService : IHostedService, IMediatorSubscriber { try { - if (!_config.Current.EnableAutoDetectDiscovery || !_dalamud.IsLoggedIn) + if (!_config.Current.EnableAutoDetectDiscovery || !_dalamud.IsLoggedIn || !_isConnected) { await Task.Delay(1000, ct).ConfigureAwait(false); continue; @@ -79,6 +83,11 @@ public class NearbyDiscoveryService : IHostedService, IMediatorSubscriber await _configProvider.TryFetchFromServerAsync(ct).ConfigureAwait(false); } } + else if (!_loggedConfigReady && _configProvider.NearbyEnabled) + { + _loggedConfigReady = true; + _logger.LogInformation("Nearby: well-known loaded and enabled; refresh={refresh}s, expires={exp}", _configProvider.RefreshSec, _configProvider.SaltExpiresAt); + } var entries = await GetLocalNearbyAsync().ConfigureAwait(false); // Log when local count changes (including 0) to indicate activity @@ -95,7 +104,7 @@ public class NearbyDiscoveryService : IHostedService, IMediatorSubscriber try { var saltHex = Convert.ToHexString(_configProvider.Salt!); - // map hash->index for result matching and reuse for publish + // map hash->index for result matching Dictionary hashToIndex = new(StringComparer.Ordinal); List hashes = new(entries.Count); foreach (var (entry, idx) in entries.Select((e, i) => (e, i))) @@ -105,35 +114,63 @@ public class NearbyDiscoveryService : IHostedService, IMediatorSubscriber hashes.Add(h); } - // Publish local snapshot if endpoint is available (deduplicated) + // Debug snapshot once per change + try + { + var snapSig = string.Join(',', hashes.OrderBy(s => s, StringComparer.Ordinal)).GetHash256(); + if (!string.Equals(snapSig, _lastSnapshotSig, StringComparison.Ordinal)) + { + _lastSnapshotSig = snapSig; + var sample = entries.Take(5).Select(e => + { + var hh = (saltHex + e.Name + e.WorldId.ToString()).GetHash256(); + var shortH = hh.Length > 8 ? hh[..8] : hh; + return $"{e.Name}({e.WorldId})->{shortH}"; + }); + var saltShort = saltHex.Length > 8 ? saltHex[..8] : saltHex; + _logger.LogInformation("Nearby snapshot: {count} entries; salt={saltShort}…; samples=[{samples}]", + entries.Count, saltShort, string.Join(", ", sample)); + } + } + catch { } + + // Publish OUR presence (own hash) if endpoint is available (deduplicated) if (!string.IsNullOrEmpty(_configProvider.PublishEndpoint)) { string? displayName = null; + string? selfHash = null; try { var me = await _dalamud.RunOnFrameworkThread(() => _dalamud.GetPlayerCharacter()).ConfigureAwait(false); if (me != null) { displayName = me.Name.TextValue; + ushort meWorld = 0; + if (me is Dalamud.Game.ClientState.Objects.SubKinds.IPlayerCharacter mePc) + meWorld = (ushort)mePc.HomeWorld.RowId; + _logger.LogInformation("Nearby self ident: {name} ({world})", displayName, meWorld); + selfHash = (saltHex + displayName + meWorld.ToString()).GetHash256(); } } catch { /* ignore */ } - if (hashes.Count > 0) + if (!string.IsNullOrEmpty(selfHash)) { - var sig = string.Join(',', hashes.OrderBy(s => s, StringComparer.Ordinal)).GetHash256(); + var sig = selfHash!; if (!string.Equals(sig, _lastPublishedSignature, StringComparison.Ordinal)) { _lastPublishedSignature = sig; - _logger.LogDebug("Nearby publish: {count} hashes (updated)", hashes.Count); - _ = _api.PublishAsync(_configProvider.PublishEndpoint!, hashes, displayName, ct); + 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.LogInformation("Nearby publish result: {result}", ok ? "success" : "failed"); } else { _logger.LogDebug("Nearby publish skipped (no changes)"); } } - // else: no local entries; skip publish silently + // else: no self character available; skip publish silently } // Query for matches if endpoint is available @@ -160,6 +197,7 @@ public class NearbyDiscoveryService : IHostedService, IMediatorSubscriber } } } + _logger.LogInformation("Nearby: server returned {count} matches", allMatches.Count); // Log change in number of Umbra matches int matchCount = entries.Count(e => e.IsMatch); @@ -206,7 +244,8 @@ public class NearbyDiscoveryService : IHostedService, IMediatorSubscriber var localPos = local?.Position ?? Vector3.Zero; int maxDist = Math.Clamp(_config.Current.AutoDetectMaxDistanceMeters, 5, 100); - for (int i = 0; i < 200; i += 2) + int limit = Math.Min(200, _objectTable.Length); + for (int i = 0; i < limit; i++) { var obj = await _dalamud.RunOnFrameworkThread(() => _objectTable[i]).ConfigureAwait(false); if (obj == null || obj.ObjectKind != Dalamud.Game.ClientState.Objects.Enums.ObjectKind.Player) continue; @@ -215,7 +254,7 @@ public class NearbyDiscoveryService : IHostedService, IMediatorSubscriber float dist = local == null ? float.NaN : Vector3.Distance(localPos, obj.Position); if (!float.IsNaN(dist) && dist > maxDist) continue; - string name = obj.Name.ToString(); + string name = obj.Name.TextValue; ushort worldId = 0; if (obj is Dalamud.Game.ClientState.Objects.SubKinds.IPlayerCharacter pc) worldId = (ushort)pc.HomeWorld.RowId; diff --git a/MareSynchronos/Services/AutoDetect/NearbyPendingService.cs b/MareSynchronos/Services/AutoDetect/NearbyPendingService.cs new file mode 100644 index 0000000..ddc3855 --- /dev/null +++ b/MareSynchronos/Services/AutoDetect/NearbyPendingService.cs @@ -0,0 +1,70 @@ +using System.Collections.Concurrent; +using System.Text.RegularExpressions; +using MareSynchronos.Services.Mediator; +using MareSynchronos.WebAPI; +using Microsoft.Extensions.Logging; + +namespace MareSynchronos.Services.AutoDetect; + +public sealed class NearbyPendingService : IMediatorSubscriber +{ + private readonly ILogger _logger; + private readonly MareMediator _mediator; + private readonly ApiController _api; + private readonly ConcurrentDictionary _pending = new(StringComparer.Ordinal); + private static readonly Regex ReqRegex = new(@"^Nearby Request: (.+) \[(?[A-Z0-9]+)\]$", RegexOptions.Compiled); + + public NearbyPendingService(ILogger logger, MareMediator mediator, ApiController api) + { + _logger = logger; + _mediator = mediator; + _api = api; + _mediator.Subscribe(this, OnNotification); + } + + public MareMediator Mediator => _mediator; + + public IReadOnlyDictionary Pending => _pending; + + private void OnNotification(NotificationMessage msg) + { + // Watch info messages for Nearby request pattern + if (msg.Type != MareSynchronos.MareConfiguration.Models.NotificationType.Info) return; + var m = ReqRegex.Match(msg.Message); + if (!m.Success) return; + var uid = m.Groups["uid"].Value; + if (string.IsNullOrEmpty(uid)) return; + // Try to extract name as everything before space and '[' + var name = msg.Message; + try + { + var idx = msg.Message.IndexOf(':'); + if (idx >= 0) name = msg.Message[(idx + 1)..].Trim(); + var br = name.LastIndexOf('['); + if (br > 0) name = name[..br].Trim(); + } + catch { name = uid; } + _pending[uid] = name; + _logger.LogInformation("NearbyPending: received request from {uid} ({name})", uid, name); + } + + public void Remove(string uid) + { + _pending.TryRemove(uid, out _); + } + + public async Task AcceptAsync(string uid) + { + try + { + await _api.UserAddPair(new MareSynchronos.API.Dto.User.UserDto(new MareSynchronos.API.Data.UserData(uid))).ConfigureAwait(false); + _pending.TryRemove(uid, out _); + return true; + } + catch (Exception ex) + { + _logger.LogWarning(ex, "NearbyPending: accept failed for {uid}", uid); + return false; + } + } +} diff --git a/MareSynchronos/UI/CompactUI.cs b/MareSynchronos/UI/CompactUI.cs index fada6b0..69f724c 100644 --- a/MareSynchronos/UI/CompactUI.cs +++ b/MareSynchronos/UI/CompactUI.cs @@ -44,6 +44,7 @@ public class CompactUi : WindowMediatorSubscriberBase private readonly ServerConfigurationManager _serverManager; private readonly Stopwatch _timeout = new(); private readonly CharaDataManager _charaDataManager; + private readonly Services.AutoDetect.NearbyPendingService _nearbyPending; private readonly UidDisplayHandler _uidDisplayHandler; private readonly UiSharedService _uiSharedService; private bool _buttonState; @@ -62,6 +63,7 @@ public class CompactUi : WindowMediatorSubscriberBase public CompactUi(ILogger logger, UiSharedService uiShared, MareConfigService configService, ApiController apiController, PairManager pairManager, ChatService chatService, ServerConfigurationManager serverManager, MareMediator mediator, FileUploadManager fileTransferManager, UidDisplayHandler uidDisplayHandler, CharaDataManager charaDataManager, + Services.AutoDetect.NearbyPendingService nearbyPendingService, PerformanceCollectorService performanceCollectorService) : base(logger, mediator, "###UmbraSyncMainUI", performanceCollectorService) { @@ -73,6 +75,7 @@ public class CompactUi : WindowMediatorSubscriberBase _fileTransferManager = fileTransferManager; _uidDisplayHandler = uidDisplayHandler; _charaDataManager = charaDataManager; + _nearbyPending = nearbyPendingService; var tagHandler = new TagHandler(_serverManager); _groupPanel = new(this, uiShared, _pairManager, chatService, uidDisplayHandler, _configService, _serverManager, _charaDataManager); @@ -180,14 +183,6 @@ public class CompactUi : WindowMediatorSubscriberBase } ImGui.Separator(); using (ImRaii.PushId("transfers")) DrawTransfers(); - using (ImRaii.PushId("autosync")) - { - ImGui.SameLine(); - if (_uiSharedService.IconTextButton(FontAwesomeIcon.UserPlus, "Nearby", (WindowContentWidth - ImGui.GetStyle().ItemSpacing.X) / 2)) - { - Mediator.Publish(new UiToggleMessage(typeof(AutoDetectUi))); - } - } TransferPartHeight = ImGui.GetCursorPosY() - TransferPartHeight; using (ImRaii.PushId("group-user-popup")) _selectPairsForGroupUi.Draw(_pairManager.DirectPairs); using (ImRaii.PushId("grouping-popup")) _selectGroupForPairUi.Draw(); @@ -396,6 +391,16 @@ public class CompactUi : WindowMediatorSubscriberBase : $"Nearby ({nearbyCount} Players)"); if (ImGui.IsItemClicked(ImGuiMouseButton.Left)) _nearbyOpen = !_nearbyOpen; + // Header action button to open Nearby window + var btnWidth = _uiSharedService.GetIconTextButtonSize(FontAwesomeIcon.UserPlus, "Nearby"); + var headerRight = ImGui.GetWindowContentRegionMin().X + UiSharedService.GetWindowContentRegionWidth(); + ImGui.SameLine(); + ImGui.SetCursorPosX(headerRight - btnWidth); + if (_uiSharedService.IconTextButton(FontAwesomeIcon.UserPlus, "Nearby", btnWidth)) + { + Mediator.Publish(new UiToggleMessage(typeof(AutoDetectUi))); + } + if (_nearbyOpen) { ImGui.Indent(); @@ -407,6 +412,34 @@ public class CompactUi : WindowMediatorSubscriberBase { UiSharedService.ColorTextWrapped("Open Nearby for details.", ImGuiColors.DalamudGrey3); } + // Pending Nearby requests (Accept / Dismiss) + try + { + var inbox = _nearbyPending; + if (inbox != null && inbox.Pending.Count > 0) + { + ImGuiHelpers.ScaledDummy(6); + _uiSharedService.BigText("Requests"); + foreach (var kv in inbox.Pending) + { + ImGui.AlignTextToFramePadding(); + ImGui.TextUnformatted($"{kv.Value} [{kv.Key}]"); + ImGui.SameLine(); + if (_uiSharedService.IconButton(FontAwesomeIcon.Check)) + { + _ = inbox.AcceptAsync(kv.Key); + } + UiSharedService.AttachToolTip("Accept and add as pair"); + ImGui.SameLine(); + if (_uiSharedService.IconButton(FontAwesomeIcon.Times)) + { + inbox.Remove(kv.Key); + } + UiSharedService.AttachToolTip("Dismiss request"); + } + } + } + catch { } ImGui.Unindent(); ImGui.Separator(); } @@ -530,21 +563,20 @@ public class CompactUi : WindowMediatorSubscriberBase ImGui.TextUnformatted(downloadText); } - var bottomButtonWidth = (WindowContentWidth - ImGui.GetStyle().ItemSpacing.X) / 2; - + // Space for three equal-width buttons laid out precisely (avoid overlap/wrap) + var spacing = ImGui.GetStyle().ItemSpacing.X; + var bottomButtonWidth = (WindowContentWidth - spacing) / 2f; if (_uiSharedService.IconTextButton(FontAwesomeIcon.PersonCircleQuestion, "Character Analysis", bottomButtonWidth)) { Mediator.Publish(new UiToggleMessage(typeof(DataAnalysisUi))); } ImGui.SameLine(); - if (_uiSharedService.IconTextButton(FontAwesomeIcon.Running, "Character Data Hub", bottomButtonWidth)) { Mediator.Publish(new UiToggleMessage(typeof(CharaDataHubUi))); } - - ImGui.SameLine(); + ImGuiHelpers.ScaledDummy(2); } private void DrawUIDHeader() diff --git a/MareSynchronos/UI/SettingsUi.cs b/MareSynchronos/UI/SettingsUi.cs index 36fdf14..11d3787 100644 --- a/MareSynchronos/UI/SettingsUi.cs +++ b/MareSynchronos/UI/SettingsUi.cs @@ -212,7 +212,7 @@ public class SettingsUi : WindowMediatorSubscriberBase } ImGui.Separator(); - _uiShared.BigText("Nearby"); + _uiShared.BigText("AutoDetect"); bool enableDiscovery = _configService.Current.EnableAutoDetectDiscovery; if (ImGui.Checkbox("Enable Nearby detection (beta)", ref enableDiscovery)) { diff --git a/MareSynchronos/UmbraSync.json b/MareSynchronos/UmbraSync.json index 70ef498..da34e8d 100644 --- a/MareSynchronos/UmbraSync.json +++ b/MareSynchronos/UmbraSync.json @@ -8,7 +8,7 @@ "Tags": [ "customization" ], - "IconUrl": "https://repo.umbra-sync.net/logo.png", + "IconUrl": "https://repo.umbra-sync.net/images/logo.png", "RepoUrl": "https://repo.umbra-sync.net/plugin.json", "CanUnloadAsync": true } diff --git a/MareSynchronos/WebAPI/AutoDetect/DiscoveryApiClient.cs b/MareSynchronos/WebAPI/AutoDetect/DiscoveryApiClient.cs index 842a154..dd24560 100644 --- a/MareSynchronos/WebAPI/AutoDetect/DiscoveryApiClient.cs +++ b/MareSynchronos/WebAPI/AutoDetect/DiscoveryApiClient.cs @@ -11,6 +11,7 @@ public class DiscoveryApiClient private readonly ILogger _logger; private readonly TokenProvider _tokenProvider; private readonly HttpClient _httpClient = new(); + private static readonly JsonSerializerOptions JsonOpt = new() { PropertyNameCaseInsensitive = true }; public DiscoveryApiClient(ILogger logger, TokenProvider tokenProvider) { @@ -32,7 +33,7 @@ public class DiscoveryApiClient var resp = await _httpClient.SendAsync(req, ct).ConfigureAwait(false); resp.EnsureSuccessStatusCode(); var json = await resp.Content.ReadAsStringAsync(ct).ConfigureAwait(false); - var result = JsonSerializer.Deserialize>(json) ?? []; + var result = JsonSerializer.Deserialize>(json, JsonOpt) ?? []; return result; } catch (Exception ex) @@ -42,7 +43,7 @@ public class DiscoveryApiClient } } - public async Task SendRequestAsync(string endpoint, string token, CancellationToken ct) + public async Task SendRequestAsync(string endpoint, string token, string? displayName, CancellationToken ct) { try { @@ -50,10 +51,17 @@ 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 body = JsonSerializer.Serialize(new { token }); + var body = JsonSerializer.Serialize(new { token, displayName }); req.Content = new StringContent(body, Encoding.UTF8, "application/json"); var resp = await _httpClient.SendAsync(req, ct).ConfigureAwait(false); - return resp.IsSuccessStatusCode; + if (!resp.IsSuccessStatusCode) + { + string txt = string.Empty; + try { txt = await resp.Content.ReadAsStringAsync(ct).ConfigureAwait(false); } catch { } + _logger.LogWarning("Discovery request failed: {code} {reason} {body}", (int)resp.StatusCode, resp.ReasonPhrase, txt); + return false; + } + return true; } catch (Exception ex) {