using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; using RmmAgent.Models; using RmmAgent.Security; using System.Net.Http.Headers; using System.Net.Http.Json; namespace RmmAgent.Services; /// /// HttpClient avec Bearer token (agent token persistant DPAPI). /// Toutes requĂȘtes en HTTPS sortant standard (port 443). /// PAS de presigned S3 - upload screenshot direct au PHP via multipart. /// public class ApiClient { private readonly ILogger _logger; private readonly IConfiguration _config; private readonly ConfigStore _configStore; private HttpClient? _http; public ApiClient(ILogger logger, IConfiguration config, ConfigStore configStore) { _logger = logger; _config = config; _configStore = configStore; } public string ServerUrl => _config["Rmm:ServerUrl"]?.TrimEnd('/') ?? "https://localhost"; public string? AgentToken => _configStore.GetAgentToken(); public void Initialize() { var token = AgentToken; if (string.IsNullOrEmpty(token)) throw new InvalidOperationException("No agent token available"); var handler = new HttpClientHandler { AutomaticDecompression = System.Net.DecompressionMethods.All, }; _http = new HttpClient(handler) { BaseAddress = new Uri(ServerUrl), Timeout = TimeSpan.FromSeconds(30), }; _http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token); _logger.LogInformation("API client initialized. Server={Server}", ServerUrl); } public async Task SendHeartbeatAsync(HeartbeatRequest req, CancellationToken ct) { if (_http is null) throw new InvalidOperationException("Client not initialized"); var resp = await _http.PostAsJsonAsync("/agent/heartbeat", req, ct); resp.EnsureSuccessStatusCode(); return await resp.Content.ReadFromJsonAsync(cancellationToken: ct); } public async Task PushInventoryAsync(InventoryRequest req, CancellationToken ct) { if (_http is null) throw new InvalidOperationException("Client not initialized"); var resp = await _http.PostAsJsonAsync("/agent/inventory", req, ct); resp.EnsureSuccessStatusCode(); } /// /// Upload screenshot multipart direct au PHP (champ "file" + width/height). /// public async Task UploadScreenshotAsync(byte[] jpegBytes, int width, int height, CancellationToken ct) { if (_http is null) throw new InvalidOperationException("Client not initialized"); using var form = new MultipartFormDataContent(); var fileContent = new ByteArrayContent(jpegBytes); fileContent.Headers.ContentType = new MediaTypeHeaderValue("image/jpeg"); form.Add(fileContent, "file", "screenshot.jpg"); form.Add(new StringContent(width.ToString()), "width"); form.Add(new StringContent(height.ToString()), "height"); var resp = await _http.PostAsync("/agent/screenshot", form, ct); resp.EnsureSuccessStatusCode(); } public async Task ReportCommandResultAsync(string commandId, bool success, string? errorMessage, CancellationToken ct) { if (_http is null) return; try { await _http.PostAsJsonAsync("/agent/command-result", new CommandResultRequest(commandId, success, errorMessage), ct); } catch (Exception ex) { _logger.LogWarning(ex, "Failed to report command result"); } } }