using System; using System.Collections.Generic; using System.Text; using System.Threading.Tasks; using Newtonsoft.Json; using Newtonsoft.Json.Serialization; using UnityEngine; using UnityEngine.Networking; using Vanguard.Api.Contracts; using Vanguard.App; using Vanguard.Auth; namespace Vanguard.Api { /// /// HTTP/JSON клиент мета-API. UnityWebRequest работает только на главном /// потоке, поэтому все вызовы должны идти из Unity-кода; continuation после /// await тоже возвращается на главный поток. /// public sealed class ApiClient { private static readonly JsonSerializerSettings JsonSettings = new JsonSerializerSettings { ContractResolver = new CamelCasePropertyNamesContractResolver(), NullValueHandling = NullValueHandling.Ignore, DateParseHandling = DateParseHandling.DateTimeOffset, }; private readonly AppConfig _config; private readonly TokenStore _tokens; public ApiClient(AppConfig config, TokenStore tokens) { _config = config; _tokens = tokens; } public string BaseUrl => _config.ServerBaseUrl; public async Task GetAsync(string path, bool authenticated = true) { var body = await SendAsync("GET", path, null, authenticated); if (string.IsNullOrEmpty(body)) return default; return JsonConvert.DeserializeObject(body, JsonSettings); } public async Task PostAsync(string path, object body, bool authenticated = true) { var response = await SendAsync("POST", path, body, authenticated); if (string.IsNullOrEmpty(response)) return default; return JsonConvert.DeserializeObject(response, JsonSettings); } public Task PostAsync(string path, object body, bool authenticated = true) => SendAsync("POST", path, body, authenticated); public async Task PutAsync(string path, object body, bool authenticated = true) { var response = await SendAsync("PUT", path, body, authenticated); return string.IsNullOrEmpty(response) ? default : JsonConvert.DeserializeObject(response, JsonSettings); } public Task DeleteAsync(string path, bool authenticated = true) => SendAsync("DELETE", path, null, authenticated); private async Task SendAsync( string method, string path, object body, bool authenticated) { var url = _config.ServerBaseUrl + path; using (var request = new UnityWebRequest(url, method)) { request.downloadHandler = new DownloadHandlerBuffer(); request.timeout = Mathf.CeilToInt(_config.RequestTimeoutSeconds); request.SetRequestHeader("Accept", "application/json"); if (body != null) { var json = JsonConvert.SerializeObject(body, JsonSettings); request.uploadHandler = new UploadHandlerRaw(Encoding.UTF8.GetBytes(json)); request.SetRequestHeader("Content-Type", "application/json"); } if (authenticated && !string.IsNullOrEmpty(_tokens.AccessToken)) request.SetRequestHeader("Authorization", "Bearer " + _tokens.AccessToken); await SendRequestAsync(request); var text = request.downloadHandler != null ? request.downloadHandler.text : string.Empty; if (request.responseCode == 0) { var reason = string.IsNullOrEmpty(request.error) ? "Нет связи с сервером." : request.error; throw new ApiException(0, "network", reason); } if (request.responseCode >= 400) throw ParseError(request.responseCode, text); return text; } } private static Task SendRequestAsync(UnityWebRequest request) { var completion = new TaskCompletionSource(); var operation = request.SendWebRequest(); operation.completed += _ => completion.TrySetResult(true); return completion.Task; } private static ApiException ParseError(long statusCode, string body) { ApiErrorDto error = null; try { if (!string.IsNullOrWhiteSpace(body)) error = JsonConvert.DeserializeObject(body, JsonSettings); } catch (JsonException) { // Ниже отдадим общий текст: тело не JSON. } if (error == null) { return new ApiException( statusCode, "http." + statusCode, "Сервер вернул ошибку " + statusCode + "."); } var violations = new List(); if (error.Violations != null) { foreach (var violation in error.Violations) violations.Add(new ApiViolation(violation.Code, violation.Message)); } var message = string.IsNullOrWhiteSpace(error.Message) ? "Сервер вернул ошибку " + statusCode + "." : error.Message; return new ApiException(statusCode, error.Error, message, violations); } } }