using System.IO; using System.IO.Pipes; using System.Runtime.Versioning; using System.Text.Json; using System.Threading; using System.Threading.Tasks; using WebhookServer.Core.Ipc; using WebhookServer.Core.Models; namespace WebhookServer.Gui.Services; /// /// Thin client around the admin named pipe. Each call connects, sends one request, /// reads one response, and disconnects — keeps lifecycle simple at the cost of /// connect-per-call overhead. The service single-instance pipe queues requests so /// concurrent calls from the GUI serialize automatically. /// [SupportedOSPlatform("windows")] public sealed class AdminPipeClient { public TimeSpan ConnectTimeout { get; init; } = TimeSpan.FromSeconds(5); public async Task InvokeAsync(string op, object? data = null, CancellationToken ct = default) { var request = new AdminRequest { Op = op, Data = data is null ? null : JsonSerializer.SerializeToDocument(data, AdminProtocol.JsonOptions).RootElement.Clone(), }; await using var pipe = new NamedPipeClientStream( ".", PipeSecurityFactory.PipeName, PipeDirection.InOut, PipeOptions.Asynchronous); await pipe.ConnectAsync((int)ConnectTimeout.TotalMilliseconds, ct).ConfigureAwait(false); await PipeFraming.WriteAsync(pipe, request, ct).ConfigureAwait(false); using var reader = PipeFraming.CreateReader(pipe); var response = await PipeFraming.ReadAsync(reader, ct).ConfigureAwait(false); return response ?? AdminResponse.Failure("empty response from service"); } public async Task InvokeAsync(string op, object? data = null, CancellationToken ct = default) where T : class { var resp = await InvokeAsync(op, data, ct).ConfigureAwait(false); if (!resp.Ok || resp.Data is null) return null; return resp.Data.Value.Deserialize(AdminProtocol.JsonOptions); } public Task PingAsync(CancellationToken ct = default) => InvokeAsync(AdminOps.Ping, null, ct); public Task GetStatusAsync(CancellationToken ct = default) => InvokeAsync(AdminOps.GetStatus, null, ct); public Task GetConfigAsync(CancellationToken ct = default) => InvokeAsync(AdminOps.GetConfig, null, ct); public Task CreateEndpointAsync(EndpointConfig endpoint, CancellationToken ct = default) => InvokeAsync(AdminOps.CreateEndpoint, endpoint, ct); public Task UpdateEndpointAsync(EndpointConfig endpoint, CancellationToken ct = default) => InvokeAsync(AdminOps.UpdateEndpoint, endpoint, ct); public Task DeleteEndpointAsync(Guid id, CancellationToken ct = default) => InvokeAsync(AdminOps.DeleteEndpoint, new DeleteEndpointArgs { Id = id }, ct); public Task SetEndpointEnabledAsync(Guid id, bool enabled, CancellationToken ct = default) => InvokeAsync(enabled ? AdminOps.EnableEndpoint : AdminOps.DisableEndpoint, new EndpointToggle { Id = id }, ct); public Task BindHttpsAsync(HttpsBinding? binding, CancellationToken ct = default) => InvokeAsync(AdminOps.BindHttps, binding, ct); public Task RestartListenerAsync(CancellationToken ct = default) => InvokeAsync(AdminOps.RestartListener, null, ct); public async Task> TailLogsAsync(int lines, CancellationToken ct = default) { var resp = await InvokeAsync(AdminOps.TailLogs, new TailLogsArgs { LinesToBacklog = lines, Follow = false }, ct).ConfigureAwait(false); if (!resp.Ok || resp.Data is null) return new List(); var lst = resp.Data.Value.GetProperty("lines").Deserialize>(AdminProtocol.JsonOptions); return lst ?? new List(); } public async Task> ListBackupsAsync(CancellationToken ct = default) { var resp = await InvokeAsync(AdminOps.ListBackups, null, ct).ConfigureAwait(false); if (!resp.Ok || resp.Data is null) return new List(); var lst = resp.Data.Value.GetProperty("backups").Deserialize>(AdminProtocol.JsonOptions); return lst ?? new List(); } public Task RestoreBackupAsync(string fileName, CancellationToken ct = default) => InvokeAsync(AdminOps.RestoreBackup, new RestoreBackupArgs { FileName = fileName }, ct); public Task ImportConfigAsync(ServerConfig config, CancellationToken ct = default) => InvokeAsync(AdminOps.ImportConfig, config, ct); public Task CreateCheckpointAsync(string? description, CancellationToken ct = default) => InvokeAsync(AdminOps.CreateCheckpoint, new CreateCheckpointArgs { Description = description }, ct); }