feat: validate single-client broadcast operations
Build web service image / build (push) Successful in 1m9s

This commit is contained in:
2026-09-19 14:33:26 +08:00
parent e321d38fa3
commit c7c0ab273f
62 changed files with 2861 additions and 12651 deletions
@@ -22,6 +22,7 @@ public sealed class RemoteControlClient : IDisposable
private readonly Uri? _baseAddress;
private readonly X509Certificate2? _clientCertificate;
private readonly X509Certificate2? _serverCaCertificate;
private string? _connectionId;
private bool _authenticated;
public RemoteControlClient(RemoteAgentOptions options, HttpClient? httpClient = null)
@@ -57,7 +58,9 @@ public sealed class RemoteControlClient : IDisposable
AuthState = RemoteAuthState.Authenticating;
try
{
var response = await SendAsync<RemoteNodeRegistrationResponse>(HttpMethod.Post, "/v1/nodes/register", registration, false, cancellationToken);
var connectionId = registration.ConnectionId ?? NewConnectionId();
var response = await SendAsync<RemoteNodeRegistrationResponse>(HttpMethod.Post, "/v1/nodes/register", registration with { ConnectionId = connectionId }, false, cancellationToken);
_connectionId = response.ConnectionId ?? connectionId;
_authenticated = response.Authenticated;
AuthState = response.Authenticated ? RemoteAuthState.Authenticated : RemoteAuthState.AuthenticationFailed;
LastSuccessAt = DateTimeOffset.UtcNow;
@@ -75,7 +78,7 @@ public sealed class RemoteControlClient : IDisposable
public Task<RemoteHeartbeatResponse> HeartbeatAsync(RemoteHeartbeat heartbeat, CancellationToken cancellationToken = default) =>
SendAuthenticatedAsync<RemoteHeartbeatResponse>(HttpMethod.Post,
$"/v1/nodes/{Escape(heartbeat.NodeId)}/heartbeat", heartbeat, cancellationToken);
$"/v1/nodes/{Escape(heartbeat.NodeId)}/heartbeat", heartbeat with { ConnectionId = heartbeat.ConnectionId ?? _connectionId }, cancellationToken);
public async Task<IReadOnlyList<RemoteTaskEnvelope>> PollTasksAsync(
string accountId,
@@ -205,7 +208,7 @@ public sealed class RemoteControlClient : IDisposable
if (!response.IsSuccessStatusCode)
{
var error = TryDeserializeError(responseBytes, correlationId);
if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized)
if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized || error.Code == "StaleConnection")
{
_authenticated = false;
AuthState = RemoteAuthState.AuthenticationFailed;
@@ -292,6 +295,7 @@ public sealed class RemoteControlClient : IDisposable
}
private static string Escape(string value) => Uri.EscapeDataString(value);
private static string NewConnectionId() => Guid.NewGuid().ToString("N");
private static string NewCorrelationId() => Guid.NewGuid().ToString("N");
public void Dispose()