142 lines
7.5 KiB
C#
142 lines
7.5 KiB
C#
using System.Net.Http.Json;
|
|
using System.Text;
|
|
using System.Text.Json;
|
|
using FreeCode.Core.Enums;
|
|
using FreeCode.Core.Models;
|
|
|
|
namespace FreeCode.Bridge;
|
|
|
|
public sealed class BridgeApiClient
|
|
{
|
|
private readonly HttpClient _httpClient;
|
|
|
|
public BridgeApiClient(HttpClient? httpClient = null)
|
|
{
|
|
_httpClient = httpClient ?? new HttpClient();
|
|
_httpClient.Timeout = TimeSpan.FromSeconds(30);
|
|
}
|
|
|
|
public async Task<BridgeStatusInfo> GetStatusAsync(BridgeConfig config, CancellationToken ct = default)
|
|
{
|
|
using var response = await _httpClient.GetAsync(Root(config, "bridge/status"), ct).ConfigureAwait(false);
|
|
response.EnsureSuccessStatusCode();
|
|
return await ReadJsonAsync<BridgeStatusInfo>(response, ct).ConfigureAwait(false) ?? new BridgeStatusInfo(BridgeStatus.Idle);
|
|
}
|
|
|
|
public async Task PostEventAsync(BridgeConfig config, object payload, CancellationToken ct = default)
|
|
{
|
|
using var response = await SendJsonAsync(HttpMethod.Post, Root(config, "bridge/events"), payload, ct).ConfigureAwait(false);
|
|
response.EnsureSuccessStatusCode();
|
|
}
|
|
|
|
public async Task SendSessionUpdateAsync(BridgeConfig config, string sessionId, object payload, CancellationToken ct = default)
|
|
{
|
|
using var response = await SendJsonAsync(HttpMethod.Post, Root(config, $"bridge/sessions/{Uri.EscapeDataString(sessionId)}/update"), payload, ct).ConfigureAwait(false);
|
|
response.EnsureSuccessStatusCode();
|
|
}
|
|
|
|
public async Task<object?> RequestPermissionAsync(BridgeConfig config, string sessionId, object payload, CancellationToken ct = default)
|
|
{
|
|
using var response = await SendJsonAsync(HttpMethod.Post, Root(config, $"bridge/sessions/{Uri.EscapeDataString(sessionId)}/permissions/request"), payload, ct).ConfigureAwait(false);
|
|
response.EnsureSuccessStatusCode();
|
|
return await ReadJsonAsync<object>(response, ct).ConfigureAwait(false);
|
|
}
|
|
|
|
public async Task<BridgeStatusInfo> RegisterBridgeEnvironmentAsync(BridgeConfig config, BridgeEnvironment environment, CancellationToken ct = default)
|
|
{
|
|
using var response = await SendJsonAsync(HttpMethod.Post, Root(config, "bridge/environments"), environment, ct).ConfigureAwait(false);
|
|
response.EnsureSuccessStatusCode();
|
|
return await ReadJsonAsync<BridgeStatusInfo>(response, ct).ConfigureAwait(false) ?? new BridgeStatusInfo(BridgeStatus.Registered);
|
|
}
|
|
|
|
public async Task<WorkItem?> PollForWorkAsync(BridgeConfig config, string environmentId, CancellationToken ct = default)
|
|
{
|
|
using var response = await _httpClient.GetAsync(Root(config, $"bridge/environments/{Uri.EscapeDataString(environmentId)}/work"), ct).ConfigureAwait(false);
|
|
if (response.StatusCode == System.Net.HttpStatusCode.NoContent)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
response.EnsureSuccessStatusCode();
|
|
return await ReadJsonAsync<WorkItem>(response, ct).ConfigureAwait(false);
|
|
}
|
|
|
|
public async Task AcknowledgeWorkAsync(BridgeConfig config, string workId, string sessionToken, CancellationToken ct = default)
|
|
{
|
|
using var response = await SendJsonAsync(HttpMethod.Post, Root(config, $"bridge/work/{Uri.EscapeDataString(workId)}/ack"), new Dictionary<string, object?>
|
|
{
|
|
["sessionToken"] = sessionToken
|
|
}, ct).ConfigureAwait(false);
|
|
response.EnsureSuccessStatusCode();
|
|
}
|
|
|
|
public async Task<SessionHandle> SpawnSessionAsync(BridgeConfig config, SessionSpawnOptions options, CancellationToken ct = default)
|
|
{
|
|
using var response = await SendJsonAsync(HttpMethod.Post, Root(config, "bridge/sessions"), options, ct).ConfigureAwait(false);
|
|
response.EnsureSuccessStatusCode();
|
|
return await ReadJsonAsync<SessionHandle>(response, ct).ConfigureAwait(false) ?? new SessionHandle(options.Environment.Id, string.Empty);
|
|
}
|
|
|
|
public async Task SendPermissionResponseAsync(BridgeConfig config, string sessionId, PermissionResponse responseBody, CancellationToken ct = default)
|
|
{
|
|
using var response = await SendJsonAsync(HttpMethod.Post, Root(config, $"bridge/sessions/{Uri.EscapeDataString(sessionId)}/permissions"), responseBody, ct).ConfigureAwait(false);
|
|
response.EnsureSuccessStatusCode();
|
|
}
|
|
|
|
public async Task HeartbeatAsync(BridgeConfig config, string workId, string sessionToken, CancellationToken ct = default)
|
|
{
|
|
using var response = await SendJsonAsync(HttpMethod.Post, Root(config, $"bridge/work/{Uri.EscapeDataString(workId)}/heartbeat"), new Dictionary<string, object?>
|
|
{
|
|
["sessionToken"] = sessionToken
|
|
}, ct).ConfigureAwait(false);
|
|
response.EnsureSuccessStatusCode();
|
|
}
|
|
|
|
public async Task StopWorkAsync(BridgeConfig config, string workId, CancellationToken ct = default)
|
|
{
|
|
using var response = await _httpClient.DeleteAsync(Root(config, $"bridge/work/{Uri.EscapeDataString(workId)}"), ct).ConfigureAwait(false);
|
|
response.EnsureSuccessStatusCode();
|
|
}
|
|
|
|
public async Task DeregisterEnvironmentAsync(BridgeConfig config, string environmentId, CancellationToken ct = default)
|
|
{
|
|
using var response = await _httpClient.DeleteAsync(Root(config, $"bridge/environments/{Uri.EscapeDataString(environmentId)}"), ct).ConfigureAwait(false);
|
|
response.EnsureSuccessStatusCode();
|
|
}
|
|
|
|
private async Task<HttpResponseMessage> SendJsonAsync(HttpMethod method, Uri uri, object payload, CancellationToken ct)
|
|
{
|
|
var json = payload switch
|
|
{
|
|
JsonElement element => JsonSerializer.Serialize(element, SourceGenerationContext.Default.JsonElement),
|
|
Dictionary<string, object?> dictionary => JsonSerializer.Serialize(dictionary),
|
|
BridgeEnvironment environment => JsonSerializer.Serialize(environment, SourceGenerationContext.Default.BridgeEnvironment),
|
|
SessionSpawnOptions options => JsonSerializer.Serialize(options, SourceGenerationContext.Default.SessionSpawnOptions),
|
|
PermissionResponse permissionResponse => JsonSerializer.Serialize(permissionResponse, SourceGenerationContext.Default.PermissionResponse),
|
|
RemoteConnectedEvent remoteConnectedEvent => JsonSerializer.Serialize(remoteConnectedEvent, SourceGenerationContext.Default.RemoteConnectedEvent),
|
|
RemoteDisconnectedEvent remoteDisconnectedEvent => JsonSerializer.Serialize(remoteDisconnectedEvent, SourceGenerationContext.Default.RemoteDisconnectedEvent),
|
|
RemoteMessageEvent remoteMessageEvent => JsonSerializer.Serialize(remoteMessageEvent, SourceGenerationContext.Default.RemoteMessageEvent),
|
|
_ => throw new NotSupportedException($"Unsupported bridge JSON payload type: {payload.GetType().FullName}")
|
|
};
|
|
|
|
var request = new HttpRequestMessage(method, uri)
|
|
{
|
|
Content = new StringContent(json, Encoding.UTF8, "application/json")
|
|
};
|
|
|
|
return await _httpClient.SendAsync(request, ct).ConfigureAwait(false);
|
|
}
|
|
|
|
private static Uri Root(BridgeConfig config, string relativePath)
|
|
{
|
|
var baseUri = new Uri(config.BaseUrl.TrimEnd('/') + "/", UriKind.Absolute);
|
|
return new Uri(baseUri, relativePath);
|
|
}
|
|
|
|
private static async Task<T?> ReadJsonAsync<T>(HttpResponseMessage response, CancellationToken ct)
|
|
{
|
|
await using var stream = await response.Content.ReadAsStreamAsync(ct).ConfigureAwait(false);
|
|
return (T?)await response.Content.ReadFromJsonAsync(typeof(T), SourceGenerationContext.Default, ct).ConfigureAwait(false);
|
|
}
|
|
}
|