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