using OpenIddict.Client; using RobotNet.MapShares.Models; using RobotNet.RobotShares.Models; using RobotNet.Script; using RobotNet.Script.Expressions; using RobotNet.ScriptManager.Clients; using RobotNet.ScriptManager.Helpers; using RobotNet.Shares; using Serialize.Linq.Serializers; using System.Linq.Expressions; using System.Net.Http.Headers; namespace RobotNet.ScriptManager.Models; public class ScriptRobotManager(OpenIddictClientService openIddictClient, string RobotManagerUrl, string[] RobotManagerScopes) : IRobotManager { private string? CachedToken; private DateTime TokenExpiry; private static readonly ExpressionSerializer expressionSerializer = new(new Serialize.Linq.Serializers.JsonSerializer()); public async Task GetRobotById(string robotId) { var robot = new RobotManagerHubClient(robotId, $"{RobotManagerUrl}/hubs/robot-manager", async () => { var result = await openIddictClient.AuthenticateWithClientCredentialsAsync(new() { Scopes = [.. RobotManagerScopes], }); if (result == null || result.AccessToken == null || result.AccessTokenExpirationDate == null) { return null; } else { return result.AccessToken; } }); await robot.StartAsync(); return robot; } public Task GetRobotState(string robotId) => GetRobotState(robotId, true); public async Task GetRobotState(string robotId, bool retry) { var accessToken = await RequestAccessToken(); if (string.IsNullOrEmpty(accessToken)) { throw new ArgumentException("Failed to get access token"); } using var client = new HttpClient() { BaseAddress = new Uri(RobotManagerUrl) }; using var request = new HttpRequestMessage(HttpMethod.Get, $"api/RobotManager/State/{robotId}"); request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken); using var response = await client.SendAsync(request); using var status = response.EnsureSuccessStatusCode(); if (status.StatusCode == System.Net.HttpStatusCode.Unauthorized) { if (retry) { // Retry with a new access token CachedToken = null; // Clear cached token to force a new request return await GetRobotState(robotId, false); } else { throw new UnauthorizedAccessException("Access token is invalid or expired."); } } else if (status.StatusCode != System.Net.HttpStatusCode.OK) { throw new HttpRequestException($"Failed to get robot state: {status.ReasonPhrase}"); } else { var state = await response.Content.ReadFromJsonAsync>(); if (state == null) { throw new InvalidOperationException("Failed to deserialize robot state response"); } else if (!state.IsSuccess) { throw new InvalidOperationException($"Robot Manager error: {state.Message}"); } else { if (state.Data == null) { throw new InvalidOperationException("Robot state data is null."); } return VDA5050ScriptHelper.ConvertToRobotState(state.Data); } } } public Task> SearchRobots(string map, string model) => SearchRobots(map, model, robot => true, true); public Task> SearchRobots(string map, string model, Expression> expr) => SearchRobots(map, model, expr, true); public async Task> SearchRobots(string map, string model, Expression> expr, bool retry) { var accessToken = await RequestAccessToken(); using var client = new HttpClient() { BaseAddress = new Uri(RobotManagerUrl) }; using var request = new HttpRequestMessage(HttpMethod.Post, $"api/RobotManager/Search"); request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken); request.Content = JsonContent.Create(new ElementExpressionModel { MapName = map, ModelName = model, Expression = expressionSerializer.SerializeText(expr), }); using var response = await client.SendAsync(request); using var status = response.EnsureSuccessStatusCode(); if (status.StatusCode == System.Net.HttpStatusCode.Unauthorized) { if (retry) { // Retry with a new access token CachedToken = null; // Clear cached token to force a new request return await SearchRobots(map, model, expr, false); } else { throw new UnauthorizedAccessException("Access token is invalid or expired."); } } else if (response.EnsureSuccessStatusCode().StatusCode != System.Net.HttpStatusCode.OK) { throw new HttpRequestException($"Failed to search robots: {response.ReasonPhrase}"); } else { var result = await response.Content.ReadFromJsonAsync>() ?? throw new Exception("Failed to convert result from Robot Manager"); if (result.IsSuccess) { return result.Data ?? []; } else { throw new Exception($"Robot Manager error: {result.Message}"); } } } public async Task RequestAccessToken() { try { if (!string.IsNullOrEmpty(CachedToken) && DateTime.UtcNow < TokenExpiry) { return CachedToken; } var result = await openIddictClient.AuthenticateWithClientCredentialsAsync(new() { Scopes = [.. RobotManagerScopes], }); if (result == null || result.AccessToken == null || result.AccessTokenExpirationDate == null) { return null; } else { TokenExpiry = result.AccessTokenExpirationDate.Value.UtcDateTime; CachedToken = result.AccessToken; return CachedToken; } } catch { return null; } } }