using System; using System.IO; using System.Text; using System.Threading.Tasks; using Best.HTTP; using IchniOnline.Online.Network.Models; using UnityEngine; namespace IchniOnline.Online.Network { /// /// BestHTTP-based API client singleton for IchniOnline backend communication. /// Pure HTTP layer — no business logic. /// public class IchniOnlineApiClient { private static IchniOnlineApiClient _instance; public static IchniOnlineApiClient Instance => _instance ??= new IchniOnlineApiClient(); public string BaseUrl { get; set; } = "http://localhost:53734"; public string JwtToken { get; set; } private IchniOnlineApiClient() { } public async Task> GetAsync(string endpoint) { string url = BuildUrl(endpoint); var request = new HTTPRequest(new Uri(url), HTTPMethods.Get); AddAuthHeader(request); try { var resp = await request.GetHTTPResponseAsync(); return ProcessResponse(resp); } catch (Exception ex) { return ApiResult.Fail(ResponseCode.InternalServerError, "Network error", ex.Message); } } public async Task> PostAsync(string endpoint, object body) { string url = BuildUrl(endpoint); var request = new HTTPRequest(new Uri(url), HTTPMethods.Post); request.SetHeader("Content-Type", "application/json"); AddAuthHeader(request); if (body != null) { string jsonBody = JsonUtility.ToJson(body); request.UploadSettings.UploadStream = new MemoryStream(Encoding.UTF8.GetBytes(jsonBody)); } try { var resp = await request.GetHTTPResponseAsync(); return ProcessResponse(resp); } catch (Exception ex) { return ApiResult.Fail(ResponseCode.InternalServerError, "Network error", ex.Message); } } private string BuildUrl(string endpoint) { if (string.IsNullOrEmpty(BaseUrl)) throw new InvalidOperationException("BaseUrl is not configured."); string baseUrl = BaseUrl.TrimEnd('/'); string path = endpoint.StartsWith("/") ? endpoint : $"/{endpoint}"; return baseUrl + path; } private void AddAuthHeader(HTTPRequest request) { if (!string.IsNullOrEmpty(JwtToken)) { request.SetHeader("Authorization", $"Bearer {JwtToken}"); } } private ApiResult ProcessResponse(HTTPResponse resp) { string json = resp.DataAsText; if (resp.StatusCode >= 200 && resp.StatusCode < 300) { if (string.IsNullOrEmpty(json)) { return ApiResult.Fail(ResponseCode.InternalServerError, "Empty response body"); } var response = JsonUtility.FromJson(json, typeof(GlobalResponse)) as GlobalResponse; if (response == null) { return ApiResult.Fail(ResponseCode.InternalServerError, "Failed to parse response JSON"); } if (response.Code == ResponseCode.Ok) { return ApiResult.Ok(response.Data); } return ApiResult.Fail(response.Code, response.Message); } // Non-2xx: try to parse server error body if (!string.IsNullOrEmpty(json)) { var errorResponse = JsonUtility.FromJson(json, typeof(GlobalResponseBase)) as GlobalResponseBase; if (errorResponse != null) { return ApiResult.Fail(errorResponse.Code, errorResponse.Message); } } return ApiResult.Fail(ResponseCode.InternalServerError, $"HTTP error {resp.StatusCode}"); } } }