using System.Net; using System.Net.Http.Headers; using System.Net.Http.Json; using System.Text.Json; namespace EpsonPrintService; public sealed class PuduRobotClient { private readonly HttpClient _http; private readonly string _baseUrl; private readonly string _username; private readonly string _password; private string? _token; public PuduRobotClient(string baseUrl, string username, string password, HttpClient? http = null) { _baseUrl = baseUrl.TrimEnd('/'); _username = username; _password = password; _http = http ?? new HttpClient { Timeout = TimeSpan.FromSeconds(30) }; } public async Task SendToTableAsync(string robotSn, string targetPoint, CancellationToken ct = default) { await EnsureTokenAsync(ct); try { await PostAddCommandAsync(robotSn, targetPoint, ct); } catch (HttpRequestException ex) when (ex.StatusCode == HttpStatusCode.Unauthorized) { // Token expired — refresh once and retry. _token = null; await EnsureTokenAsync(ct); await PostAddCommandAsync(robotSn, targetPoint, ct); } } private async Task EnsureTokenAsync(CancellationToken ct) { if (_token is not null) return; using var resp = await _http.PostAsJsonAsync( $"{_baseUrl}/api/Auth/login", new { username = _username, password = _password }, ct); resp.EnsureSuccessStatusCode(); var bodyText = await resp.Content.ReadAsStringAsync(ct); var token = ExtractToken(bodyText); if (string.IsNullOrEmpty(token)) throw new InvalidOperationException($"Pudu login: no token returned. Body: {bodyText}"); _token = token; } private async Task PostAddCommandAsync(string robotSn, string targetPoint, CancellationToken ct) { using var req = new HttpRequestMessage(HttpMethod.Post, $"{_baseUrl}/api/Robots/add-command") { Content = JsonContent.Create(new { Sn = robotSn, TargetPoint = targetPoint }) }; req.Headers.Authorization = new AuthenticationHeaderValue("Bearer", _token); using var resp = await _http.SendAsync(req, ct); resp.EnsureSuccessStatusCode(); } // Recursive search so we match the token regardless of wrapping (e.g. {"data":{"token":"..."}}). // Mirrors the behavior of `sed -n 's/.*"token":"\([^"]*\)".*/\1/p'` in send_robot.sh. private static string? ExtractToken(string json) { try { using var doc = JsonDocument.Parse(json); return FindTokenProperty(doc.RootElement); } catch (JsonException) { return null; } } private static string? FindTokenProperty(JsonElement element) { switch (element.ValueKind) { case JsonValueKind.Object: foreach (var prop in element.EnumerateObject()) { if ((prop.Name.Equals("token", StringComparison.OrdinalIgnoreCase) || prop.Name.Equals("accessToken", StringComparison.OrdinalIgnoreCase)) && prop.Value.ValueKind == JsonValueKind.String) { return prop.Value.GetString(); } } foreach (var prop in element.EnumerateObject()) { var nested = FindTokenProperty(prop.Value); if (nested is not null) return nested; } return null; case JsonValueKind.Array: foreach (var item in element.EnumerateArray()) { var nested = FindTokenProperty(item); if (nested is not null) return nested; } return null; default: return null; } } }