Update 0.1.6 - Fix UI settings & Delay Detection
This commit is contained in:
@@ -3,7 +3,7 @@
|
|||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<AssemblyName>UmbraSync</AssemblyName>
|
<AssemblyName>UmbraSync</AssemblyName>
|
||||||
<RootNamespace>UmbraSync</RootNamespace>
|
<RootNamespace>UmbraSync</RootNamespace>
|
||||||
<Version>0.1.6.2</Version>
|
<Version>0.1.6.3</Version>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@@ -32,6 +32,8 @@ public class NearbyDiscoveryService : IHostedService, IMediatorSubscriber
|
|||||||
private bool _notifiedEnabled;
|
private bool _notifiedEnabled;
|
||||||
private bool _disableSent;
|
private bool _disableSent;
|
||||||
private bool _lastAutoDetectState;
|
private bool _lastAutoDetectState;
|
||||||
|
private DateTime _lastHeartbeat = DateTime.MinValue;
|
||||||
|
private static readonly TimeSpan HeartbeatInterval = TimeSpan.FromSeconds(75);
|
||||||
|
|
||||||
public NearbyDiscoveryService(ILogger<NearbyDiscoveryService> logger, MareMediator mediator,
|
public NearbyDiscoveryService(ILogger<NearbyDiscoveryService> logger, MareMediator mediator,
|
||||||
MareConfigService config, DiscoveryConfigProvider configProvider, DalamudUtilService dalamudUtilService,
|
MareConfigService config, DiscoveryConfigProvider configProvider, DalamudUtilService dalamudUtilService,
|
||||||
@@ -53,10 +55,75 @@ public class NearbyDiscoveryService : IHostedService, IMediatorSubscriber
|
|||||||
_loopCts = new CancellationTokenSource();
|
_loopCts = new CancellationTokenSource();
|
||||||
_mediator.Subscribe<ConnectedMessage>(this, _ => { _isConnected = true; _configProvider.TryLoadFromStapled(); });
|
_mediator.Subscribe<ConnectedMessage>(this, _ => { _isConnected = true; _configProvider.TryLoadFromStapled(); });
|
||||||
_mediator.Subscribe<DisconnectedMessage>(this, _ => { _isConnected = false; _lastPublishedSignature = null; });
|
_mediator.Subscribe<DisconnectedMessage>(this, _ => { _isConnected = false; _lastPublishedSignature = null; });
|
||||||
|
_mediator.Subscribe<AllowPairRequestsToggled>(this, OnAllowPairRequestsToggled);
|
||||||
_ = Task.Run(() => Loop(_loopCts.Token));
|
_ = Task.Run(() => Loop(_loopCts.Token));
|
||||||
_lastAutoDetectState = _config.Current.EnableAutoDetectDiscovery;
|
_lastAutoDetectState = _config.Current.EnableAutoDetectDiscovery;
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
private async void OnAllowPairRequestsToggled(AllowPairRequestsToggled msg)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (!_config.Current.EnableAutoDetectDiscovery) return;
|
||||||
|
// Force a publish now so the server immediately reflects the new allow/deny state
|
||||||
|
_lastPublishedSignature = null; // ensure next loop won't skip
|
||||||
|
await PublishSelfOnceAsync(CancellationToken.None).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogDebug(ex, "OnAllowPairRequestsToggled failed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task PublishSelfOnceAsync(CancellationToken ct)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (!_config.Current.EnableAutoDetectDiscovery || !_dalamud.IsLoggedIn || !_isConnected) return;
|
||||||
|
|
||||||
|
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 not { Length: > 0 }) return;
|
||||||
|
|
||||||
|
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)) return;
|
||||||
|
|
||||||
|
var selfHash = (saltHex + displayName + meWorld.ToString()).GetHash256();
|
||||||
|
var ok = await _api.PublishAsync(ep!, new[] { selfHash }, displayName, ct, _config.Current.AllowAutoDetectPairRequests).ConfigureAwait(false);
|
||||||
|
_logger.LogInformation("Nearby publish (manual/immediate): {result}", ok ? "success" : "failed");
|
||||||
|
if (ok)
|
||||||
|
{
|
||||||
|
_lastPublishedSignature = selfHash;
|
||||||
|
_lastHeartbeat = DateTime.UtcNow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogDebug(ex, "Immediate publish failed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public Task StopAsync(CancellationToken cancellationToken)
|
public Task StopAsync(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
@@ -259,6 +326,7 @@ public class NearbyDiscoveryService : IHostedService, IMediatorSubscriber
|
|||||||
_logger.LogDebug("Nearby publish: self presence updated (hash={hash})", shortSelf);
|
_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);
|
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");
|
_logger.LogInformation("Nearby publish result: {result}", ok ? "success" : "failed");
|
||||||
|
if (ok) _lastHeartbeat = DateTime.UtcNow;
|
||||||
if (ok)
|
if (ok)
|
||||||
{
|
{
|
||||||
if (!_notifiedEnabled)
|
if (!_notifiedEnabled)
|
||||||
@@ -272,7 +340,17 @@ public class NearbyDiscoveryService : IHostedService, IMediatorSubscriber
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_logger.LogDebug("Nearby publish skipped (no changes)");
|
// No changes; perform heartbeat publish if interval elapsed
|
||||||
|
if (DateTime.UtcNow - _lastHeartbeat >= HeartbeatInterval)
|
||||||
|
{
|
||||||
|
var okHb = await _api.PublishAsync(_configProvider.PublishEndpoint!, new[] { selfHash! }, displayName, ct, _config.Current.AllowAutoDetectPairRequests).ConfigureAwait(false);
|
||||||
|
_logger.LogDebug("Nearby heartbeat publish: {result}", okHb ? "success" : "failed");
|
||||||
|
if (okHb) _lastHeartbeat = DateTime.UtcNow;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_logger.LogDebug("Nearby publish skipped (no changes)");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// else: no self character available; skip publish silently
|
// else: no self character available; skip publish silently
|
||||||
|
|||||||
@@ -110,6 +110,8 @@ public record GPoseLobbyReceiveWorldData(UserData UserData, WorldData WorldData)
|
|||||||
|
|
||||||
public record NearbyEntry(string Name, ushort WorldId, float Distance, bool IsMatch, string? Token, string? DisplayName, string? Uid);
|
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 DiscoveryListUpdated(List<NearbyEntry> Entries) : MessageBase;
|
||||||
|
public record NearbyDetectionToggled(bool Enabled) : MessageBase;
|
||||||
|
public record AllowPairRequestsToggled(bool Enabled) : MessageBase;
|
||||||
|
|
||||||
public record PluginChangeMessage(string InternalName, Version Version, bool IsLoaded) : KeyedMessage(InternalName);
|
public record PluginChangeMessage(string InternalName, Version Version, bool IsLoaded) : KeyedMessage(InternalName);
|
||||||
#pragma warning restore S2094
|
#pragma warning restore S2094
|
||||||
|
|||||||
@@ -218,14 +218,42 @@ public class SettingsUi : WindowMediatorSubscriberBase
|
|||||||
{
|
{
|
||||||
_configService.Current.EnableAutoDetectDiscovery = enableDiscovery;
|
_configService.Current.EnableAutoDetectDiscovery = enableDiscovery;
|
||||||
_configService.Save();
|
_configService.Save();
|
||||||
|
|
||||||
|
// notify services of toggle
|
||||||
|
Mediator.Publish(new NearbyDetectionToggled(enableDiscovery));
|
||||||
|
|
||||||
|
// if Nearby is turned OFF, force Allow Pair Requests OFF as well
|
||||||
|
if (!enableDiscovery && _configService.Current.AllowAutoDetectPairRequests)
|
||||||
|
{
|
||||||
|
_configService.Current.AllowAutoDetectPairRequests = false;
|
||||||
|
_configService.Save();
|
||||||
|
Mediator.Publish(new AllowPairRequestsToggled(false));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
bool allowRequests = _configService.Current.AllowAutoDetectPairRequests;
|
|
||||||
if (ImGui.Checkbox("Allow pair requests", ref allowRequests))
|
// Allow Pair Requests is disabled when Nearby is OFF
|
||||||
|
using (ImRaii.Disabled(!enableDiscovery))
|
||||||
{
|
{
|
||||||
_configService.Current.AllowAutoDetectPairRequests = allowRequests;
|
bool allowRequests = _configService.Current.AllowAutoDetectPairRequests;
|
||||||
_configService.Save();
|
if (ImGui.Checkbox("Allow pair requests", ref allowRequests))
|
||||||
|
{
|
||||||
|
_configService.Current.AllowAutoDetectPairRequests = allowRequests;
|
||||||
|
_configService.Save();
|
||||||
|
|
||||||
|
// notify services of toggle
|
||||||
|
Mediator.Publish(new AllowPairRequestsToggled(allowRequests));
|
||||||
|
|
||||||
|
// user-facing info toast
|
||||||
|
Mediator.Publish(new NotificationMessage(
|
||||||
|
"Nearby Detection",
|
||||||
|
allowRequests ? "Pair requests enabled: others can invite you." : "Pair requests disabled: others cannot invite you.",
|
||||||
|
NotificationType.Info,
|
||||||
|
default));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (enableDiscovery)
|
|
||||||
|
// Radius only available when both Nearby and Allow Pair Requests are ON
|
||||||
|
if (enableDiscovery && _configService.Current.AllowAutoDetectPairRequests)
|
||||||
{
|
{
|
||||||
ImGui.Indent();
|
ImGui.Indent();
|
||||||
int maxMeters = _configService.Current.AutoDetectMaxDistanceMeters;
|
int maxMeters = _configService.Current.AutoDetectMaxDistanceMeters;
|
||||||
|
|||||||
@@ -39,6 +39,21 @@ public class DiscoveryApiClient
|
|||||||
});
|
});
|
||||||
req.Content = new StringContent(body, Encoding.UTF8, "application/json");
|
req.Content = new StringContent(body, Encoding.UTF8, "application/json");
|
||||||
var resp = await _httpClient.SendAsync(req, ct).ConfigureAwait(false);
|
var resp = await _httpClient.SendAsync(req, ct).ConfigureAwait(false);
|
||||||
|
if (resp.StatusCode == System.Net.HttpStatusCode.Unauthorized)
|
||||||
|
{
|
||||||
|
// Retry once with a fresh token
|
||||||
|
var token2 = await _tokenProvider.GetOrUpdateToken(ct).ConfigureAwait(false);
|
||||||
|
if (string.IsNullOrEmpty(token2)) return [];
|
||||||
|
using var req2 = new HttpRequestMessage(HttpMethod.Post, endpoint);
|
||||||
|
req2.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token2);
|
||||||
|
var body2 = JsonSerializer.Serialize(new
|
||||||
|
{
|
||||||
|
hashes = hashes.Distinct(StringComparer.Ordinal).ToArray(),
|
||||||
|
salt = _configProvider.SaltB64
|
||||||
|
});
|
||||||
|
req2.Content = new StringContent(body2, Encoding.UTF8, "application/json");
|
||||||
|
resp = await _httpClient.SendAsync(req2, ct).ConfigureAwait(false);
|
||||||
|
}
|
||||||
resp.EnsureSuccessStatusCode();
|
resp.EnsureSuccessStatusCode();
|
||||||
var json = await resp.Content.ReadAsStringAsync(ct).ConfigureAwait(false);
|
var json = await resp.Content.ReadAsStringAsync(ct).ConfigureAwait(false);
|
||||||
var result = JsonSerializer.Deserialize<List<ServerMatch>>(json, JsonOpt) ?? [];
|
var result = JsonSerializer.Deserialize<List<ServerMatch>>(json, JsonOpt) ?? [];
|
||||||
@@ -62,6 +77,16 @@ public class DiscoveryApiClient
|
|||||||
var body = JsonSerializer.Serialize(new { token, displayName });
|
var body = JsonSerializer.Serialize(new { token, displayName });
|
||||||
req.Content = new StringContent(body, Encoding.UTF8, "application/json");
|
req.Content = new StringContent(body, Encoding.UTF8, "application/json");
|
||||||
var resp = await _httpClient.SendAsync(req, ct).ConfigureAwait(false);
|
var resp = await _httpClient.SendAsync(req, ct).ConfigureAwait(false);
|
||||||
|
if (resp.StatusCode == System.Net.HttpStatusCode.Unauthorized)
|
||||||
|
{
|
||||||
|
var jwt2 = await _tokenProvider.GetOrUpdateToken(ct).ConfigureAwait(false);
|
||||||
|
if (string.IsNullOrEmpty(jwt2)) return false;
|
||||||
|
using var req2 = new HttpRequestMessage(HttpMethod.Post, endpoint);
|
||||||
|
req2.Headers.Authorization = new AuthenticationHeaderValue("Bearer", jwt2);
|
||||||
|
var body2 = JsonSerializer.Serialize(new { token, displayName });
|
||||||
|
req2.Content = new StringContent(body2, Encoding.UTF8, "application/json");
|
||||||
|
resp = await _httpClient.SendAsync(req2, ct).ConfigureAwait(false);
|
||||||
|
}
|
||||||
if (!resp.IsSuccessStatusCode)
|
if (!resp.IsSuccessStatusCode)
|
||||||
{
|
{
|
||||||
string txt = string.Empty;
|
string txt = string.Empty;
|
||||||
@@ -96,6 +121,16 @@ public class DiscoveryApiClient
|
|||||||
var body = JsonSerializer.Serialize(bodyObj);
|
var body = JsonSerializer.Serialize(bodyObj);
|
||||||
req.Content = new StringContent(body, Encoding.UTF8, "application/json");
|
req.Content = new StringContent(body, Encoding.UTF8, "application/json");
|
||||||
var resp = await _httpClient.SendAsync(req, ct).ConfigureAwait(false);
|
var resp = await _httpClient.SendAsync(req, ct).ConfigureAwait(false);
|
||||||
|
if (resp.StatusCode == System.Net.HttpStatusCode.Unauthorized)
|
||||||
|
{
|
||||||
|
var jwt2 = await _tokenProvider.GetOrUpdateToken(ct).ConfigureAwait(false);
|
||||||
|
if (string.IsNullOrEmpty(jwt2)) return false;
|
||||||
|
using var req2 = new HttpRequestMessage(HttpMethod.Post, endpoint);
|
||||||
|
req2.Headers.Authorization = new AuthenticationHeaderValue("Bearer", jwt2);
|
||||||
|
var body2 = JsonSerializer.Serialize(bodyObj);
|
||||||
|
req2.Content = new StringContent(body2, Encoding.UTF8, "application/json");
|
||||||
|
resp = await _httpClient.SendAsync(req2, ct).ConfigureAwait(false);
|
||||||
|
}
|
||||||
return resp.IsSuccessStatusCode;
|
return resp.IsSuccessStatusCode;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -117,6 +152,16 @@ public class DiscoveryApiClient
|
|||||||
var body = JsonSerializer.Serialize(bodyObj);
|
var body = JsonSerializer.Serialize(bodyObj);
|
||||||
req.Content = new StringContent(body, Encoding.UTF8, "application/json");
|
req.Content = new StringContent(body, Encoding.UTF8, "application/json");
|
||||||
var resp = await _httpClient.SendAsync(req, ct).ConfigureAwait(false);
|
var resp = await _httpClient.SendAsync(req, ct).ConfigureAwait(false);
|
||||||
|
if (resp.StatusCode == System.Net.HttpStatusCode.Unauthorized)
|
||||||
|
{
|
||||||
|
var jwt2 = await _tokenProvider.GetOrUpdateToken(ct).ConfigureAwait(false);
|
||||||
|
if (string.IsNullOrEmpty(jwt2)) return false;
|
||||||
|
using var req2 = new HttpRequestMessage(HttpMethod.Post, endpoint);
|
||||||
|
req2.Headers.Authorization = new AuthenticationHeaderValue("Bearer", jwt2);
|
||||||
|
var body2 = JsonSerializer.Serialize(bodyObj);
|
||||||
|
req2.Content = new StringContent(body2, Encoding.UTF8, "application/json");
|
||||||
|
resp = await _httpClient.SendAsync(req2, ct).ConfigureAwait(false);
|
||||||
|
}
|
||||||
return resp.IsSuccessStatusCode;
|
return resp.IsSuccessStatusCode;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -135,6 +180,14 @@ public class DiscoveryApiClient
|
|||||||
req.Headers.Authorization = new AuthenticationHeaderValue("Bearer", jwt);
|
req.Headers.Authorization = new AuthenticationHeaderValue("Bearer", jwt);
|
||||||
// no body required
|
// no body required
|
||||||
var resp = await _httpClient.SendAsync(req, ct).ConfigureAwait(false);
|
var resp = await _httpClient.SendAsync(req, ct).ConfigureAwait(false);
|
||||||
|
if (resp.StatusCode == System.Net.HttpStatusCode.Unauthorized)
|
||||||
|
{
|
||||||
|
var jwt2 = await _tokenProvider.GetOrUpdateToken(ct).ConfigureAwait(false);
|
||||||
|
if (string.IsNullOrEmpty(jwt2)) return;
|
||||||
|
using var req2 = new HttpRequestMessage(HttpMethod.Post, endpoint);
|
||||||
|
req2.Headers.Authorization = new AuthenticationHeaderValue("Bearer", jwt2);
|
||||||
|
resp = await _httpClient.SendAsync(req2, ct).ConfigureAwait(false);
|
||||||
|
}
|
||||||
if (!resp.IsSuccessStatusCode)
|
if (!resp.IsSuccessStatusCode)
|
||||||
{
|
{
|
||||||
string txt = string.Empty;
|
string txt = string.Empty;
|
||||||
|
|||||||
Reference in New Issue
Block a user