using System; using System.Collections.Generic; using System.Text; using System.Threading.Tasks; using UnityEngine; using UnityEngine.Networking; public sealed class BriskHttpClient { private readonly BriskContext _context; public BriskHttpClient(BriskContext context) { _context = context; } public Task> GetDataAsync(string path, Dictionary query = null, bool auth = false) { return SendForDictionaryAsync(UnityWebRequest.kHttpVerbGET, path, query, null, auth); } public Task GetRawDataAsync(string path, Dictionary query = null, bool auth = false) { return SendRawJsonAsync(UnityWebRequest.kHttpVerbGET, path, query, null, auth); } public Task> PostJsonAsync(string path, object body, bool auth = false) { return SendForDictionaryAsync(UnityWebRequest.kHttpVerbPOST, path, null, body, auth); } public Task PostJsonRawAsync(string path, object body, bool auth = false) { return SendRawJsonAsync(UnityWebRequest.kHttpVerbPOST, path, null, body, auth); } public Task PostJsonRawAsync(string path, object body, bool auth, Dictionary query) { return SendRawJsonAsync(UnityWebRequest.kHttpVerbPOST, path, query, body, auth); } public Task SendPutJsonRawAsync(string path, object body, bool auth = false) { return SendRawJsonAsync(UnityWebRequest.kHttpVerbPUT, path, null, body, auth); } public Task SendDeleteJsonRawAsync(string path, Dictionary query = null, bool auth = false) { return SendRawJsonAsync(UnityWebRequest.kHttpVerbDELETE, path, query, null, auth); } public async Task> PostMultipartAsync(string path, List formSections, bool auth = false) { using (var request = UnityWebRequest.Post(BuildUrl(path, null), formSections)) { if (auth) { AddAuthorizationHeader(request); } request.SetRequestHeader("Accept", "application/json"); await SendRequestAsync(request); return ParseEnvelope(request) as Dictionary ?? new Dictionary(); } } public async Task GetBytesAsync(string path, Dictionary query = null, bool auth = false) { using (var request = BuildRequest(UnityWebRequest.kHttpVerbGET, path, query, null, auth)) { await SendRequestAsync(request); EnsureSuccessOrThrow(request); return new BriskBinaryResponse { Bytes = request.downloadHandler != null ? request.downloadHandler.data : Array.Empty(), Headers = request.GetResponseHeaders() ?? new Dictionary() }; } } private async Task> SendForDictionaryAsync(string method, string path, Dictionary query, object body, bool auth) { var data = await SendRawJsonAsync(method, path, query, body, auth); return data as Dictionary ?? new Dictionary(); } private async Task SendRawJsonAsync(string method, string path, Dictionary query, object body, bool auth) { using (var request = BuildRequest(method, path, query, body, auth)) { await SendRequestAsync(request); return ParseEnvelope(request); } } private UnityWebRequest BuildRequest(string method, string path, Dictionary query, object body, bool auth) { var url = BuildUrl(path, query); var request = new UnityWebRequest(url, method) { downloadHandler = new DownloadHandlerBuffer() }; request.SetRequestHeader("Accept", "application/json"); if (body != null) { var json = BriskJson.Serialize(body); var bytes = Encoding.UTF8.GetBytes(json); request.uploadHandler = new UploadHandlerRaw(bytes); request.SetRequestHeader("Content-Type", "application/json"); } if (auth) { AddAuthorizationHeader(request); } return request; } private async Task SendRequestAsync(UnityWebRequest request) { try { var operation = request.SendWebRequest(); var taskSource = new TaskCompletionSource(); operation.completed += _ => taskSource.TrySetResult(true); await taskSource.Task; } catch (Exception exception) { throw new BriskNetworkException("Failed to send request.", exception); } if (request.result == UnityWebRequest.Result.ConnectionError || request.result == UnityWebRequest.Result.DataProcessingError) { throw new BriskNetworkException(request.error ?? "Network error."); } } private object ParseEnvelope(UnityWebRequest request) { var httpStatus = (int)request.responseCode; var responseText = request.downloadHandler != null ? request.downloadHandler.text : null; if (string.IsNullOrWhiteSpace(responseText)) { if (httpStatus >= 200 && httpStatus < 300) { return new Dictionary(); } throw new BriskHttpException(httpStatus, request.error ?? "HTTP error."); } var payload = BriskJson.Deserialize(responseText) as Dictionary; if (payload == null) { throw new BriskHttpException(httpStatus, "Invalid JSON response."); } var code = BriskValueReader.GetInt(payload, "code"); var message = BriskValueReader.GetString(payload, "message") ?? request.error ?? "Request failed."; if (code != 0 || httpStatus < 200 || httpStatus >= 300) { throw BriskErrorClassifier.Classify(httpStatus, code, message); } if (BriskValueReader.TryGetValue(payload, "data", out var dataValue)) { return dataValue; } return new Dictionary(); } private void EnsureSuccessOrThrow(UnityWebRequest request) { var httpStatus = (int)request.responseCode; if (httpStatus >= 200 && httpStatus < 300) { return; } throw CreateExceptionFromResponse(request); } private BriskException CreateExceptionFromResponse(UnityWebRequest request) { var httpStatus = (int)request.responseCode; var responseText = request.downloadHandler != null ? request.downloadHandler.text : null; if (!string.IsNullOrWhiteSpace(responseText)) { var payload = BriskJson.Deserialize(responseText) as Dictionary; if (payload != null) { var code = BriskValueReader.GetInt(payload, "code"); var message = BriskValueReader.GetString(payload, "message") ?? request.error ?? "Request failed."; return BriskErrorClassifier.Classify(httpStatus, code, message); } } return new BriskHttpException(httpStatus, request.error ?? "HTTP error."); } private void AddAuthorizationHeader(UnityWebRequest request) { var token = _context.Session.AccessToken; if (string.IsNullOrWhiteSpace(token)) { throw new BriskAuthExpiredException("Missing access token."); } request.SetRequestHeader("Authorization", "Bearer " + token); } private string BuildUrl(string path, Dictionary query) { var trimmedPath = path.StartsWith("/") ? path : "/" + path; var url = _context.Options.BaseUrl + trimmedPath; if (query == null || query.Count == 0) { return url; } var builder = new StringBuilder(url); var first = true; foreach (var pair in query) { if (string.IsNullOrWhiteSpace(pair.Value)) { continue; } if (first) { builder.Append('?'); } else { builder.Append('&'); } builder.Append(UnityWebRequest.EscapeURL(pair.Key)); builder.Append('='); builder.Append(UnityWebRequest.EscapeURL(pair.Value)); first = false; } return builder.ToString(); } }