using System.Net.Http.Json; using ClaudeDo.Worker.Online.Interfaces; namespace ClaudeDo.Worker.Online; public sealed class OnlineInboxApiClient : IOnlineInboxApi { private readonly HttpClient _http; private readonly IOnlineAuthProvider _auth; public OnlineInboxApiClient(HttpClient http, IOnlineAuthProvider auth) { _http = http; _auth = auth; } /// /// Validates that is HTTPS or a loopback address. /// Throws for non-HTTPS non-loopback URLs. /// public static void ValidateBaseUrl(string baseUrl) { if (string.IsNullOrWhiteSpace(baseUrl)) throw new InvalidOperationException("online_inbox.api_base_url is not configured."); var uri = new Uri(baseUrl, UriKind.Absolute); if (uri.Scheme != "https" && !uri.IsLoopback) throw new InvalidOperationException( $"online_inbox.api_base_url must be HTTPS or loopback. Got: {baseUrl}"); } public async Task PutListsAsync(IReadOnlyList lists, CancellationToken ct = default) { using var req = await BuildAsync(HttpMethod.Put, "/lists", lists, ct); using var resp = await _http.SendAsync(req, ct); await EnsureSuccessAsync(resp, ct); } public async Task> GetUnimportedTasksAsync(CancellationToken ct = default) { using var req = await BuildAsync(HttpMethod.Get, "/tasks?imported=false", null, ct); using var resp = await _http.SendAsync(req, ct); await EnsureSuccessAsync(resp, ct); var result = await resp.Content.ReadFromJsonAsync>(ct); return result ?? []; } public async Task MarkImportedAsync(string id, CancellationToken ct = default) { using var req = await BuildAsync(HttpMethod.Post, $"/tasks/{Uri.EscapeDataString(id)}/imported", null, ct); using var resp = await _http.SendAsync(req, ct); await EnsureSuccessAsync(resp, ct); } public async Task PutMirrorAsync(IReadOnlyList tasks, CancellationToken ct = default) { using var req = await BuildAsync(HttpMethod.Put, "/tasks/mirror", tasks, ct); using var resp = await _http.SendAsync(req, ct); await EnsureSuccessAsync(resp, ct); } private async Task BuildAsync( HttpMethod method, string path, object? body, CancellationToken ct) { var token = await _auth.GetAccessTokenAsync(ct); var req = new HttpRequestMessage(method, path); if (token is not null) req.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token); if (body is not null) req.Content = JsonContent.Create(body); return req; } private static async Task EnsureSuccessAsync(HttpResponseMessage resp, CancellationToken ct) { if (resp.IsSuccessStatusCode) return; var body = await resp.Content.ReadAsStringAsync(ct); throw new OnlineInboxException((int)resp.StatusCode, $"Online Inbox API error {(int)resp.StatusCode}: {body}"); } }