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");
}
}
}