From 0ac349c5e6f5e1a62f1e60b28d9d3e7cfce7da0e Mon Sep 17 00:00:00 2001 From: Usef Farahmand <50228817+UsefFarahmand@users.noreply.github.com> Date: Mon, 5 Aug 2024 13:53:38 +0330 Subject: [PATCH] Add package --- Runtime.meta | 8 + Runtime/Abstraction.meta | 8 + Runtime/Abstraction/INetworkLoadingHandler.cs | 8 + .../INetworkLoadingHandler.cs.meta | 11 + Runtime/Assets.meta | 8 + Runtime/Assets/APIConfig.cs | 33 +++ Runtime/Assets/APIConfig.cs.meta | 11 + Runtime/Assets/BaseURLConfig.cs | 10 + Runtime/Assets/BaseURLConfig.cs.meta | 11 + Runtime/Assets/HTTPRequestParamsCard.cs | 36 +++ Runtime/Assets/HTTPRequestParamsCard.cs.meta | 11 + Runtime/GenericRequestSender.cs | 91 +++++++ Runtime/GenericRequestSender.cs.meta | 11 + Runtime/RequestSender.cs | 89 +++++++ Runtime/RequestSender.cs.meta | 11 + Runtime/RequestSenderBase.cs | 169 +++++++++++++ Runtime/RequestSenderBase.cs.meta | 11 + Runtime/SharedTypes.meta | 8 + Runtime/SharedTypes/APIConfigData.cs | 32 +++ Runtime/SharedTypes/APIConfigData.cs.meta | 11 + Runtime/SharedTypes/HTTPRequestMethod.cs | 4 + Runtime/SharedTypes/HTTPRequestMethod.cs.meta | 11 + Runtime/SharedTypes/HTTPResponseCodes.cs | 13 + Runtime/SharedTypes/HTTPResponseCodes.cs.meta | 11 + Runtime/SharedTypes/NetworkResponse.cs | 29 +++ Runtime/SharedTypes/NetworkResponse.cs.meta | 11 + Runtime/SharedTypes/RequestFeedbackConfig.cs | 23 ++ .../SharedTypes/RequestFeedbackConfig.cs.meta | 11 + Runtime/SharedTypes/RequestSendConfig.cs | 23 ++ Runtime/SharedTypes/RequestSendConfig.cs.meta | 11 + Runtime/Tools.meta | 8 + Runtime/Tools/JWTTokenResolver.cs | 48 ++++ Runtime/Tools/JWTTokenResolver.cs.meta | 11 + Runtime/Tools/NetworkLoadingHandlerCreator.cs | 26 ++ .../NetworkLoadingHandlerCreator.cs.meta | 11 + Runtime/Tools/RequestLogger.cs | 55 ++++ Runtime/Tools/RequestLogger.cs.meta | 11 + Runtime/Tools/UrlUtility.cs | 19 ++ Runtime/Tools/UrlUtility.cs.meta | 11 + Runtime/UAPIModule.asmdef | 16 ++ Runtime/UAPIModule.asmdef.meta | 7 + Tests.meta | 8 + Tests/APIConfigTests.cs | 46 ++++ Tests/APIConfigTests.cs.meta | 11 + Tests/APITest.cs | 40 +++ Tests/APITest.cs.meta | 11 + Tests/JwtTokenResolverTests.cs | 105 ++++++++ Tests/JwtTokenResolverTests.cs.meta | 11 + Tests/Scenes.meta | 8 + Tests/Scenes/APIManagerRuntimeTest.unity | 239 ++++++++++++++++++ Tests/Scenes/APIManagerRuntimeTest.unity.meta | 7 + Tests/ScriptableObjects.meta | 8 + Tests/ScriptableObjects/APIConfig.asset | 21 ++ Tests/ScriptableObjects/APIConfig.asset.meta | 8 + Tests/ScriptableObjects/BaseURLConfig.asset | 15 ++ .../BaseURLConfig.asset.meta | 8 + .../ScriptableObjects/HttpRequestParams.asset | 15 ++ .../HttpRequestParams.asset.meta | 8 + Tests/UAPIModule.Test.asmdef | 20 ++ Tests/UAPIModule.Test.asmdef.meta | 7 + package.json | 14 + package.json.meta | 7 + 62 files changed, 1575 insertions(+) create mode 100644 Runtime.meta create mode 100644 Runtime/Abstraction.meta create mode 100644 Runtime/Abstraction/INetworkLoadingHandler.cs create mode 100644 Runtime/Abstraction/INetworkLoadingHandler.cs.meta create mode 100644 Runtime/Assets.meta create mode 100644 Runtime/Assets/APIConfig.cs create mode 100644 Runtime/Assets/APIConfig.cs.meta create mode 100644 Runtime/Assets/BaseURLConfig.cs create mode 100644 Runtime/Assets/BaseURLConfig.cs.meta create mode 100644 Runtime/Assets/HTTPRequestParamsCard.cs create mode 100644 Runtime/Assets/HTTPRequestParamsCard.cs.meta create mode 100644 Runtime/GenericRequestSender.cs create mode 100644 Runtime/GenericRequestSender.cs.meta create mode 100644 Runtime/RequestSender.cs create mode 100644 Runtime/RequestSender.cs.meta create mode 100644 Runtime/RequestSenderBase.cs create mode 100644 Runtime/RequestSenderBase.cs.meta create mode 100644 Runtime/SharedTypes.meta create mode 100644 Runtime/SharedTypes/APIConfigData.cs create mode 100644 Runtime/SharedTypes/APIConfigData.cs.meta create mode 100644 Runtime/SharedTypes/HTTPRequestMethod.cs create mode 100644 Runtime/SharedTypes/HTTPRequestMethod.cs.meta create mode 100644 Runtime/SharedTypes/HTTPResponseCodes.cs create mode 100644 Runtime/SharedTypes/HTTPResponseCodes.cs.meta create mode 100644 Runtime/SharedTypes/NetworkResponse.cs create mode 100644 Runtime/SharedTypes/NetworkResponse.cs.meta create mode 100644 Runtime/SharedTypes/RequestFeedbackConfig.cs create mode 100644 Runtime/SharedTypes/RequestFeedbackConfig.cs.meta create mode 100644 Runtime/SharedTypes/RequestSendConfig.cs create mode 100644 Runtime/SharedTypes/RequestSendConfig.cs.meta create mode 100644 Runtime/Tools.meta create mode 100644 Runtime/Tools/JWTTokenResolver.cs create mode 100644 Runtime/Tools/JWTTokenResolver.cs.meta create mode 100644 Runtime/Tools/NetworkLoadingHandlerCreator.cs create mode 100644 Runtime/Tools/NetworkLoadingHandlerCreator.cs.meta create mode 100644 Runtime/Tools/RequestLogger.cs create mode 100644 Runtime/Tools/RequestLogger.cs.meta create mode 100644 Runtime/Tools/UrlUtility.cs create mode 100644 Runtime/Tools/UrlUtility.cs.meta create mode 100644 Runtime/UAPIModule.asmdef create mode 100644 Runtime/UAPIModule.asmdef.meta create mode 100644 Tests.meta create mode 100644 Tests/APIConfigTests.cs create mode 100644 Tests/APIConfigTests.cs.meta create mode 100644 Tests/APITest.cs create mode 100644 Tests/APITest.cs.meta create mode 100644 Tests/JwtTokenResolverTests.cs create mode 100644 Tests/JwtTokenResolverTests.cs.meta create mode 100644 Tests/Scenes.meta create mode 100644 Tests/Scenes/APIManagerRuntimeTest.unity create mode 100644 Tests/Scenes/APIManagerRuntimeTest.unity.meta create mode 100644 Tests/ScriptableObjects.meta create mode 100644 Tests/ScriptableObjects/APIConfig.asset create mode 100644 Tests/ScriptableObjects/APIConfig.asset.meta create mode 100644 Tests/ScriptableObjects/BaseURLConfig.asset create mode 100644 Tests/ScriptableObjects/BaseURLConfig.asset.meta create mode 100644 Tests/ScriptableObjects/HttpRequestParams.asset create mode 100644 Tests/ScriptableObjects/HttpRequestParams.asset.meta create mode 100644 Tests/UAPIModule.Test.asmdef create mode 100644 Tests/UAPIModule.Test.asmdef.meta create mode 100644 package.json create mode 100644 package.json.meta diff --git a/Runtime.meta b/Runtime.meta new file mode 100644 index 0000000..425f8bb --- /dev/null +++ b/Runtime.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 3d065f34299027b4a8220511032f0aca +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Abstraction.meta b/Runtime/Abstraction.meta new file mode 100644 index 0000000..995c128 --- /dev/null +++ b/Runtime/Abstraction.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 398d1ee2ca5b6b94f9903d736cc22fa3 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Abstraction/INetworkLoadingHandler.cs b/Runtime/Abstraction/INetworkLoadingHandler.cs new file mode 100644 index 0000000..9470105 --- /dev/null +++ b/Runtime/Abstraction/INetworkLoadingHandler.cs @@ -0,0 +1,8 @@ +namespace UAPIModule.Abstraction +{ + public interface INetworkLoadingHandler + { + void ShowLoading(); + void HideLoading(); + } +} diff --git a/Runtime/Abstraction/INetworkLoadingHandler.cs.meta b/Runtime/Abstraction/INetworkLoadingHandler.cs.meta new file mode 100644 index 0000000..ca23234 --- /dev/null +++ b/Runtime/Abstraction/INetworkLoadingHandler.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a05f2862f94a3d742886269908d10b63 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Assets.meta b/Runtime/Assets.meta new file mode 100644 index 0000000..89f2216 --- /dev/null +++ b/Runtime/Assets.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: b19acd382be241e46b3821d0ab085568 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Assets/APIConfig.cs b/Runtime/Assets/APIConfig.cs new file mode 100644 index 0000000..97150e0 --- /dev/null +++ b/Runtime/Assets/APIConfig.cs @@ -0,0 +1,33 @@ +using UAPIModule.SharedTypes; +using UnityEngine; + +namespace UAPIModule.Assets +{ + [CreateAssetMenu(fileName = nameof(APIConfig), menuName = "UAPIModule/" + nameof(APIConfig), order = 1)] + public class APIConfig : ScriptableObject + { + [field: SerializeField, Tooltip("The base URL configuration for the API request.")] + public BaseURLConfig BaseURLConfig { get; private set; } + + [field: SerializeField, Tooltip("The endpoint of the API request.")] + public string Endpoint { get; private set; } + + [field: SerializeField, Tooltip("The HTTP method type (GET, POST, PUT, etc.) for the API request.")] + public HTTPRequestMethod MethodType { get; private set; } + + [field: SerializeField, Tooltip("The headers for the API request.")] + public HttpRequestParams Headers { get; private set; } + + [field: SerializeField, Tooltip("Indicates whether the API request needs an authorization header.")] + public bool NeedsAuthHeader { get; private set; } + + [field: SerializeField, Tooltip("The timeout duration for the API request in milliseconds.")] + public int Timeout { get; private set; } = 10000; + + [field: SerializeField, Tooltip("Indicates whether to use the 'Bearer' prefix in the authorization header.")] + public bool UseBearerPrefix { get; private set; } = true; + + public APIConfigData CreateConfigData() => + new(BaseURLConfig, Endpoint, MethodType, Headers, NeedsAuthHeader, Timeout, UseBearerPrefix); + } +} diff --git a/Runtime/Assets/APIConfig.cs.meta b/Runtime/Assets/APIConfig.cs.meta new file mode 100644 index 0000000..1a0e7aa --- /dev/null +++ b/Runtime/Assets/APIConfig.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c693ecc3fda8a3b4cb3777531ab9efcf +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Assets/BaseURLConfig.cs b/Runtime/Assets/BaseURLConfig.cs new file mode 100644 index 0000000..d40ae8f --- /dev/null +++ b/Runtime/Assets/BaseURLConfig.cs @@ -0,0 +1,10 @@ +using UnityEngine; + +namespace UAPIModule.Assets +{ + [CreateAssetMenu(fileName = nameof(BaseURLConfig), menuName = "UAPIModule/" + nameof(BaseURLConfig))] + public class BaseURLConfig : ScriptableObject + { + [field: SerializeField] public string BaseURL { get; private set; } + } +} diff --git a/Runtime/Assets/BaseURLConfig.cs.meta b/Runtime/Assets/BaseURLConfig.cs.meta new file mode 100644 index 0000000..d3adb76 --- /dev/null +++ b/Runtime/Assets/BaseURLConfig.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 535809360fc0cff48b7bba30719d144e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Assets/HTTPRequestParamsCard.cs b/Runtime/Assets/HTTPRequestParamsCard.cs new file mode 100644 index 0000000..87fba0b --- /dev/null +++ b/Runtime/Assets/HTTPRequestParamsCard.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; +using UnityEngine; + +namespace UAPIModule.Assets +{ + [CreateAssetMenu(fileName = nameof(HttpRequestParams), menuName = "UAPIModule/" + nameof(HttpRequestParams))] + public class HttpRequestParams : ScriptableObject + { + [field: SerializeField] public List Parameters { private set; get; } + + public bool HasEmptyParams() => + Parameters.Exists(keyValueItems => + string.IsNullOrEmpty(keyValueItems.key) || string.IsNullOrEmpty(keyValueItems.value)); + + public void AddParam(string key, string value) + { + if (Parameters.Exists(keyValueItems => keyValueItems.key == key)) return; + Parameters.Add(new KeyValueItem() { key = key, value = value }); + } + + [ContextMenu("Add Default Header Params")] + public void AddDefaultHeaderParams() + { + AddParam("Content-Type", "application/json"); + AddParam("Accept", "application/json"); + } + + [Serializable] + public class KeyValueItem + { + public string key; + public string value; + } + } +} diff --git a/Runtime/Assets/HTTPRequestParamsCard.cs.meta b/Runtime/Assets/HTTPRequestParamsCard.cs.meta new file mode 100644 index 0000000..8156104 --- /dev/null +++ b/Runtime/Assets/HTTPRequestParamsCard.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 2cb80fa1b1977a4429bcfc138da62659 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/GenericRequestSender.cs b/Runtime/GenericRequestSender.cs new file mode 100644 index 0000000..913d136 --- /dev/null +++ b/Runtime/GenericRequestSender.cs @@ -0,0 +1,91 @@ +using Cysharp.Threading.Tasks; +using Newtonsoft.Json; +using System; +using System.Net.Http; +using System.Threading; +using UAPIModule.Abstraction; +using UAPIModule.Assets; +using UAPIModule.SharedTypes; + +namespace UAPIModule +{ + public abstract class RequestSender : RequestSenderBase where T : class + { + protected RequestSender(INetworkLoadingHandler loadingHandler) : base(loadingHandler) + { + } + + public async UniTask> SendRequest(APIConfig config, RequestFeedbackConfig feedbackConfig, RequestSendConfig sendConfig) + { + return await SendRequest(config.CreateConfigData(), feedbackConfig, sendConfig); + } + + public async UniTask> SendRequest(APIConfigData config, RequestFeedbackConfig feedbackConfig, RequestSendConfig sendConfig) + { + if (httpClient == null) + { + throw new InvalidOperationException("HttpClient is not initialized."); + } + + var cancellationTokenSource = new CancellationTokenSource(config.Timeout); + + try + { + var response = await SendRequestInternal(config, feedbackConfig, sendConfig, cancellationTokenSource.Token); + string responseBody = await response.Content.ReadAsStringAsync(); + + var networkResponse = new NetworkResponse + { + isSuccessful = response.IsSuccessStatusCode, + statusCode = (long)response.StatusCode, + errorMessage = response.IsSuccessStatusCode ? null : response.ReasonPhrase, + data = response.IsSuccessStatusCode ? JsonConvert.DeserializeObject(responseBody) : null + }; + + if (!networkResponse.isSuccessful) + { + networkResponse.errorMessage = responseBody; + } + + requestLogger.LogResponse(networkResponse, config.BaseURLConfig.BaseURL + config.Endpoint); + + return networkResponse; + } + catch (TimeoutException e) + { + return HandleError((long)HTTPResponseCodes.REQUEST_TIMEOUT_408, e.Message, config.BaseURLConfig.BaseURL + config.Endpoint); + } + catch (HttpRequestException e) + { + return HandleError((long)HTTPResponseCodes.SERVER_ERROR_500, GetErrorMessage(e), config.BaseURLConfig.BaseURL + config.Endpoint); + } + catch (Exception e) + { + return HandleError((long)HTTPResponseCodes.SERVER_ERROR_500, e.Message, config.BaseURLConfig.BaseURL + config.Endpoint); + } + } + + private NetworkResponse HandleError(long statusCode, string errorMessage, string url) + { + var errorResponse = new NetworkResponse + { + isSuccessful = false, + statusCode = statusCode, + errorMessage = errorMessage + }; + + requestLogger.LogResponse(errorResponse, url); + return errorResponse; + } + + private string GetErrorMessage(HttpRequestException e) + { + string errorResponseBody = e.Message; + if (e.Data.Contains("ResponseBody")) + { + errorResponseBody = e.Data["ResponseBody"].ToString(); + } + return errorResponseBody; + } + } +} diff --git a/Runtime/GenericRequestSender.cs.meta b/Runtime/GenericRequestSender.cs.meta new file mode 100644 index 0000000..a0ffd31 --- /dev/null +++ b/Runtime/GenericRequestSender.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 5d28a013eb5191f4b85855957fb8ba08 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/RequestSender.cs b/Runtime/RequestSender.cs new file mode 100644 index 0000000..306e7ec --- /dev/null +++ b/Runtime/RequestSender.cs @@ -0,0 +1,89 @@ +using Cysharp.Threading.Tasks; +using System; +using System.Net.Http; +using System.Threading; +using UAPIModule.Abstraction; +using UAPIModule.Assets; +using UAPIModule.SharedTypes; + +namespace UAPIModule +{ + public abstract class RequestSender : RequestSenderBase + { + protected RequestSender(INetworkLoadingHandler loadingHandler) : base(loadingHandler) + { + } + + public async UniTask SendRequest(APIConfig config, RequestFeedbackConfig feedbackConfig, RequestSendConfig sendConfig) + { + return await SendRequest(config.CreateConfigData(), feedbackConfig, sendConfig); + } + + public async UniTask SendRequest(APIConfigData config, RequestFeedbackConfig feedbackConfig, RequestSendConfig sendConfig) + { + if (httpClient == null) + { + throw new InvalidOperationException("HttpClient is not initialized."); + } + + var cancellationTokenSource = new CancellationTokenSource(config.Timeout); + + try + { + var response = await SendRequestInternal(config, feedbackConfig, sendConfig, cancellationTokenSource.Token); + string responseBody = await response.Content.ReadAsStringAsync(); + + var networkResponse = new NetworkResponse + { + isSuccessful = response.IsSuccessStatusCode, + statusCode = (long)response.StatusCode, + errorMessage = response.IsSuccessStatusCode ? null : response.ReasonPhrase, + }; + + if (!networkResponse.isSuccessful) + { + networkResponse.errorMessage = responseBody; + } + + requestLogger.LogResponse(networkResponse, config.BaseURLConfig.BaseURL + config.Endpoint); + + return networkResponse; + } + catch (TimeoutException e) + { + return HandleError((long)HTTPResponseCodes.REQUEST_TIMEOUT_408, e.Message, config.BaseURLConfig.BaseURL + config.Endpoint); + } + catch (HttpRequestException e) + { + return HandleError((long)HTTPResponseCodes.SERVER_ERROR_500, GetErrorMessage(e), config.BaseURLConfig.BaseURL + config.Endpoint); + } + catch (Exception e) + { + return HandleError((long)HTTPResponseCodes.SERVER_ERROR_500, e.Message, config.BaseURLConfig.BaseURL + config.Endpoint); + } + } + + private NetworkResponse HandleError(long statusCode, string errorMessage, string url) + { + var errorResponse = new NetworkResponse + { + isSuccessful = false, + statusCode = statusCode, + errorMessage = errorMessage + }; + + requestLogger.LogResponse(errorResponse, url); + return errorResponse; + } + + private string GetErrorMessage(HttpRequestException e) + { + string errorResponseBody = e.Message; + if (e.Data.Contains("ResponseBody")) + { + errorResponseBody = e.Data["ResponseBody"].ToString(); + } + return errorResponseBody; + } + } +} diff --git a/Runtime/RequestSender.cs.meta b/Runtime/RequestSender.cs.meta new file mode 100644 index 0000000..d8c85af --- /dev/null +++ b/Runtime/RequestSender.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 499f3c858e4b1214ba025f405491fedd +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/RequestSenderBase.cs b/Runtime/RequestSenderBase.cs new file mode 100644 index 0000000..4867deb --- /dev/null +++ b/Runtime/RequestSenderBase.cs @@ -0,0 +1,169 @@ +using Cysharp.Threading.Tasks; +using Newtonsoft.Json; +using System; +using System.Linq; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using UAPIModule.Abstraction; +using UAPIModule.SharedTypes; +using UAPIModule.Tools; +using UnityEngine; + +namespace UAPIModule +{ + + public abstract class RequestSenderBase + { + protected static readonly HttpClient httpClient; + internal readonly RequestLogger requestLogger = new(); + protected readonly INetworkLoadingHandler loadingHandler; + + static RequestSenderBase() + { + httpClient = new HttpClient(); + } + + protected RequestSenderBase(INetworkLoadingHandler loadingHandler) + { + this.loadingHandler = loadingHandler ?? throw new ArgumentNullException(nameof(loadingHandler)); + } + + protected async UniTask SendRequestInternal(APIConfigData config, RequestFeedbackConfig feedbackConfig, RequestSendConfig sendConfig, CancellationToken cancellationToken) + { + string url = config.BaseURLConfig != null ? config.BaseURLConfig.BaseURL + config.Endpoint : config.Endpoint; + if (sendConfig.HasPathSuffix) + { + url = UrlUtility.Join(url, sendConfig.PathSuffix); + } + + HttpRequestMessage requestMessage = new HttpRequestMessage(new HttpMethod(config.MethodType.ToString()), url); + AddHeaders(requestMessage, config, sendConfig); + + if ((config.MethodType == HTTPRequestMethod.POST || config.MethodType == HTTPRequestMethod.PUT) && sendConfig.HasBody()) + { + SetRequestBody(requestMessage, config, sendConfig); + } + + requestLogger.LogRequest(url); + + // Show loading if needed + if (feedbackConfig.ShowLoading) + { + loadingHandler?.ShowLoading(); + } + + try + { + return await httpClient.SendAsync(requestMessage, cancellationToken); + } + catch (TaskCanceledException) + { + if (cancellationToken.IsCancellationRequested) + { + throw new TimeoutException("Request timed out"); + } + throw; + } + finally + { + // Hide loading if needed + if (feedbackConfig.ShowLoading) + { + loadingHandler?.HideLoading(); + } + } + } + + protected void AddHeaders(HttpRequestMessage requestMessage, APIConfigData config, RequestSendConfig sendConfig) + { + if (config.Headers != null) + { + foreach (var header in config.Headers.Parameters) + { + if (!header.key.Equals("Content-Type", StringComparison.OrdinalIgnoreCase)) + { + requestMessage.Headers.Add(header.key, header.value); + } + } + } + + if (sendConfig.RequestHeaders != null) + { + foreach (var header in sendConfig.RequestHeaders) + { + requestMessage.Headers.Add(header.Key, header.Value); + } + } + + if (config.NeedsAuthHeader) + { + string authToken = sendConfig.BearerToken ?? JwtTokenResolver.AccessToken; + if (string.IsNullOrEmpty(authToken)) + { + Debug.LogError("Auth token is null or empty"); + throw new InvalidOperationException("Auth token is null or empty"); + } + var authHeaderValue = config.UseBearerPrefix ? $"Bearer {authToken}" : authToken; + requestMessage.Headers.Add(JwtTokenResolver.AUTHORIZATION_HEADER_KEY, authHeaderValue); + } + } + + protected void SetRequestBody(HttpRequestMessage requestMessage, APIConfigData config, RequestSendConfig sendConfig) + { + if (!string.IsNullOrEmpty(sendConfig.RequestBodyString)) + { + requestMessage.Content = new StringContent(sendConfig.RequestBodyString); + } + else + { + requestMessage.Content = new StringContent(JsonConvert.SerializeObject(sendConfig.RequestBody)); + } + + var contentTypeHeader = config.Headers.Parameters.FirstOrDefault(h => h.key.Equals("Content-Type", StringComparison.OrdinalIgnoreCase)); + if (contentTypeHeader != null) + { + requestMessage.Content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue(contentTypeHeader.value); + } + } + + protected async UniTask RefreshAccessToken() + { + try + { + string refreshToken = JwtTokenResolver.RefreshToken; + if (string.IsNullOrEmpty(refreshToken)) + { + Debug.LogError("Refresh token is null or empty"); + return false; + } + + var refreshRequest = new HttpRequestMessage(HttpMethod.Post, "YOUR_REFRESH_TOKEN_URL"); + var refreshHeader = JwtTokenResolver.RefreshTokenHeader; + refreshRequest.Headers.Add(refreshHeader.Key, refreshHeader.Value); + + var response = await httpClient.SendAsync(refreshRequest); + if (response.IsSuccessStatusCode) + { + var responseBody = await response.Content.ReadAsStringAsync(); + var tokenResponse = JsonUtility.FromJson(responseBody); + JwtTokenResolver.SetAccessToken(tokenResponse.AccessToken); + JwtTokenResolver.SetRefreshToken(tokenResponse.RefreshToken); + return true; + } + } + catch (Exception ex) + { + Debug.LogError($"Error refreshing token: {ex.Message}"); + } + + return false; + } + + private class TokenResponse + { + public string AccessToken { get; set; } + public string RefreshToken { get; set; } + } + } +} diff --git a/Runtime/RequestSenderBase.cs.meta b/Runtime/RequestSenderBase.cs.meta new file mode 100644 index 0000000..6193da6 --- /dev/null +++ b/Runtime/RequestSenderBase.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 7acb009e4324d0845a0ae154e21dbf8e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/SharedTypes.meta b/Runtime/SharedTypes.meta new file mode 100644 index 0000000..9de7489 --- /dev/null +++ b/Runtime/SharedTypes.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 10e228284fd55ab4da7c64fbc562851b +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/SharedTypes/APIConfigData.cs b/Runtime/SharedTypes/APIConfigData.cs new file mode 100644 index 0000000..19953e0 --- /dev/null +++ b/Runtime/SharedTypes/APIConfigData.cs @@ -0,0 +1,32 @@ +using UAPIModule.Assets; + +namespace UAPIModule.SharedTypes +{ + public struct APIConfigData + { + public BaseURLConfig BaseURLConfig { get; private set; } + public string Endpoint { get; private set; } + public HTTPRequestMethod MethodType { get; private set; } + public HttpRequestParams Headers { get; private set; } + public bool NeedsAuthHeader { get; private set; } + public int Timeout { get; private set; } + public bool UseBearerPrefix { get; private set; } + + public APIConfigData(BaseURLConfig baseURLConfig, + string endpoint, + HTTPRequestMethod methodType, + HttpRequestParams headers, + bool needsAuthHeader, + int timeout, + bool useBearerPrefix) + { + BaseURLConfig = baseURLConfig; + Endpoint = endpoint; + MethodType = methodType; + Headers = headers; + NeedsAuthHeader = needsAuthHeader; + Timeout = timeout; + UseBearerPrefix = useBearerPrefix; + } + } +} diff --git a/Runtime/SharedTypes/APIConfigData.cs.meta b/Runtime/SharedTypes/APIConfigData.cs.meta new file mode 100644 index 0000000..a2458a9 --- /dev/null +++ b/Runtime/SharedTypes/APIConfigData.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: afaca8dcf0500544580d0c0be6a2f95a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/SharedTypes/HTTPRequestMethod.cs b/Runtime/SharedTypes/HTTPRequestMethod.cs new file mode 100644 index 0000000..a071f94 --- /dev/null +++ b/Runtime/SharedTypes/HTTPRequestMethod.cs @@ -0,0 +1,4 @@ +namespace UAPIModule.SharedTypes +{ + public enum HTTPRequestMethod { GET, POST, PUT, HEAD, CREATE, DELETE } +} diff --git a/Runtime/SharedTypes/HTTPRequestMethod.cs.meta b/Runtime/SharedTypes/HTTPRequestMethod.cs.meta new file mode 100644 index 0000000..9c1bf9a --- /dev/null +++ b/Runtime/SharedTypes/HTTPRequestMethod.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 68350334d9933af469b4391b2a833574 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/SharedTypes/HTTPResponseCodes.cs b/Runtime/SharedTypes/HTTPResponseCodes.cs new file mode 100644 index 0000000..f83047a --- /dev/null +++ b/Runtime/SharedTypes/HTTPResponseCodes.cs @@ -0,0 +1,13 @@ +namespace UAPIModule.SharedTypes +{ + public enum HTTPResponseCodes + { + OK_200 = 200, + AUTHORIZED_201 = 201, + NOT_FOUND_404 = 404, + SERVER_ERROR_500 = 500, + UNAUTHORIZED_401 = 401, + FORBIDEN_403 = 403, + REQUEST_TIMEOUT_408 = 408 + } +} diff --git a/Runtime/SharedTypes/HTTPResponseCodes.cs.meta b/Runtime/SharedTypes/HTTPResponseCodes.cs.meta new file mode 100644 index 0000000..71700f5 --- /dev/null +++ b/Runtime/SharedTypes/HTTPResponseCodes.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 70905353a6209d64994a0d3d76090a33 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/SharedTypes/NetworkResponse.cs b/Runtime/SharedTypes/NetworkResponse.cs new file mode 100644 index 0000000..6062b05 --- /dev/null +++ b/Runtime/SharedTypes/NetworkResponse.cs @@ -0,0 +1,29 @@ +using System; + +[Serializable] +public class NetworkResponse : NetworkResponse +{ + public T data; + + public override string ToString() + { + return base.ToString() + + $"Data: {data?.ToString() ?? "null"}\n"; + } +} + +[Serializable] +public class NetworkResponse +{ + public bool isSuccessful; + public long statusCode; + public string errorMessage; + + public override string ToString() + { + return $"Network Response:\n" + + $"Is Successful: {isSuccessful}\n" + + $"Status Code: {statusCode}\n" + + $"Error Message: {errorMessage}\n"; + } +} diff --git a/Runtime/SharedTypes/NetworkResponse.cs.meta b/Runtime/SharedTypes/NetworkResponse.cs.meta new file mode 100644 index 0000000..a43422a --- /dev/null +++ b/Runtime/SharedTypes/NetworkResponse.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b3d69e32b07a356489cac98ae71d043e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/SharedTypes/RequestFeedbackConfig.cs b/Runtime/SharedTypes/RequestFeedbackConfig.cs new file mode 100644 index 0000000..8778b4a --- /dev/null +++ b/Runtime/SharedTypes/RequestFeedbackConfig.cs @@ -0,0 +1,23 @@ +namespace UAPIModule.SharedTypes +{ + public class RequestFeedbackConfig + { + public bool ShowRetryDialog { get; set; } = true; + public bool IsForce { get; set; } = false; + public bool ShowLoading { get; set; } = true; + + public static RequestFeedbackConfig NoFeedback => new() + { + IsForce = false, + ShowLoading = false, + ShowRetryDialog = false + }; + + public static RequestFeedbackConfig InitializationFeedback => new() + { + IsForce = true, + ShowLoading = false, + ShowRetryDialog = true + }; + } +} diff --git a/Runtime/SharedTypes/RequestFeedbackConfig.cs.meta b/Runtime/SharedTypes/RequestFeedbackConfig.cs.meta new file mode 100644 index 0000000..b5e3db4 --- /dev/null +++ b/Runtime/SharedTypes/RequestFeedbackConfig.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f6e3e59672b8a88458ea3cf27156098c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/SharedTypes/RequestSendConfig.cs b/Runtime/SharedTypes/RequestSendConfig.cs new file mode 100644 index 0000000..08d7997 --- /dev/null +++ b/Runtime/SharedTypes/RequestSendConfig.cs @@ -0,0 +1,23 @@ +using Newtonsoft.Json; +using System.Collections.Generic; +using System.Linq; + +namespace UAPIModule +{ + public class RequestSendConfig + { + [JsonProperty] public Dictionary RequestBody { get; set; } = null; + public string RequestBodyString { get; set; } = null; + public List> RequestHeaders { get; set; } = null; + public string PathSuffix { get; set; } = null; + internal string BearerToken { get; set; } = null; + + internal bool HasBody() + { + return !string.IsNullOrEmpty(RequestBodyString) || + (RequestBody != null && RequestBody.Any()); + } + + internal bool HasPathSuffix => !string.IsNullOrEmpty(PathSuffix); + } +} diff --git a/Runtime/SharedTypes/RequestSendConfig.cs.meta b/Runtime/SharedTypes/RequestSendConfig.cs.meta new file mode 100644 index 0000000..4efcd50 --- /dev/null +++ b/Runtime/SharedTypes/RequestSendConfig.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 3f53f6647f876d04c953e29a133c20d2 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Tools.meta b/Runtime/Tools.meta new file mode 100644 index 0000000..f0702d6 --- /dev/null +++ b/Runtime/Tools.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: c3bd03cba05b6824ca159694faebf804 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Tools/JWTTokenResolver.cs b/Runtime/Tools/JWTTokenResolver.cs new file mode 100644 index 0000000..d26a236 --- /dev/null +++ b/Runtime/Tools/JWTTokenResolver.cs @@ -0,0 +1,48 @@ +using System; +using System.Collections.Generic; +using UnityEngine; + +namespace UAPIModule.Tools +{ + public static class JwtTokenResolver + { + public const string AUTHORIZATION_HEADER_KEY = "Authorization"; + private const string ACCESS_TOKEN_KEY = "JWT_ACCESS_TOKEN"; + private const string REFRESH_TOKEN_KEY = "JWT_REFRESH_TOKEN"; + private static bool useBearerPrefix = true; + + public static Action TokenExpiredStrategy { private set; get; } + + public static void SetTokenExpiredStrategy(Action strategy) => + TokenExpiredStrategy = strategy; + + public static string AccessToken => + PlayerPrefs.GetString(ACCESS_TOKEN_KEY, ""); + public static string RefreshToken => + PlayerPrefs.GetString(REFRESH_TOKEN_KEY, ""); + + public static KeyValuePair AccessTokenHeader => + new KeyValuePair(AUTHORIZATION_HEADER_KEY, (useBearerPrefix ? "Bearer " : "") + AccessToken); + + public static KeyValuePair RefreshTokenHeader => + new KeyValuePair(AUTHORIZATION_HEADER_KEY, RefreshToken); + + public static void SetAccessToken(string token) => + PlayerPrefs.SetString(ACCESS_TOKEN_KEY, token); + + public static void SetRefreshToken(string token) => + PlayerPrefs.SetString(REFRESH_TOKEN_KEY, token); + + public static void RemoveTokens() + { + PlayerPrefs.DeleteKey(ACCESS_TOKEN_KEY); + PlayerPrefs.DeleteKey(REFRESH_TOKEN_KEY); + } + + public static void RemoveAccessToken() => + PlayerPrefs.DeleteKey(ACCESS_TOKEN_KEY); + + public static void SetUseBearerPrefix(bool usePrefix) => + useBearerPrefix = usePrefix; + } +} diff --git a/Runtime/Tools/JWTTokenResolver.cs.meta b/Runtime/Tools/JWTTokenResolver.cs.meta new file mode 100644 index 0000000..140fe7d --- /dev/null +++ b/Runtime/Tools/JWTTokenResolver.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c3f7b951df1db85478c3b4c9f1022cfc +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Tools/NetworkLoadingHandlerCreator.cs b/Runtime/Tools/NetworkLoadingHandlerCreator.cs new file mode 100644 index 0000000..14bff0a --- /dev/null +++ b/Runtime/Tools/NetworkLoadingHandlerCreator.cs @@ -0,0 +1,26 @@ +using UAPIModule.Abstraction; +using UnityEngine; + +namespace UAPIModule.Tools +{ + internal class SimpleLoadingHandler : MonoBehaviour, INetworkLoadingHandler + { + public void ShowLoading() + { + Debug.Log("Loading started..."); + // Implement loading UI show logic + } + + public void HideLoading() + { + Debug.Log("Loading ended."); + // Implement loading UI hide logic + } + } + + public static class NetworkLoadingHandlerCreator + { + public static INetworkLoadingHandler CreateAndGet() => + new GameObject("SimpleLoadingHandler").AddComponent(); + } +} diff --git a/Runtime/Tools/NetworkLoadingHandlerCreator.cs.meta b/Runtime/Tools/NetworkLoadingHandlerCreator.cs.meta new file mode 100644 index 0000000..8e24126 --- /dev/null +++ b/Runtime/Tools/NetworkLoadingHandlerCreator.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b71b9896896410248bd44906c1686979 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Tools/RequestLogger.cs b/Runtime/Tools/RequestLogger.cs new file mode 100644 index 0000000..a097cb5 --- /dev/null +++ b/Runtime/Tools/RequestLogger.cs @@ -0,0 +1,55 @@ +using System; +using UAPIModule.SharedTypes; +using UnityEngine; + +namespace UAPIModule.Tools +{ + internal class RequestLogger + { + public void LogRequest(string url) + { + Debug.Log($"[HTTPRequest] SendRoutine ({DateTime.Now:T}) -> Send -> Status: Requested -> {url}"); + } + + public void LogResponse(NetworkResponse response, string url) where T : class + { + if (response.isSuccessful) + LogResponseSuccess(response.data); + else + LogResponseError(response); + + void LogResponseSuccess(T data) + { + Debug.Log($"[HTTPRequest] SendRoutine ({DateTime.Now:T}) -> Resp - Status: Success -> {url} Data -> {JsonUtility.ToJson(data)}"); + } + + void LogResponseError(NetworkResponse response) + { + Debug.LogError($"[HTTPRequest] SendRoutine ({DateTime.Now:T}) -> Resp - Status: Error -> {url} Response Code -> {response.statusCode} Error -> {response.errorMessage}"); + } + } + + public void LogResponse(NetworkResponse response, string url) + { + if (response.isSuccessful) + LogResponseSuccess(); + else + LogResponseError(response); + + void LogResponseSuccess() + { + Debug.Log($"[HTTPRequest] SendRoutine ({DateTime.Now:T}) -> Resp - Status: Success -> {url}"); + } + + void LogResponseError(NetworkResponse response) + { + Debug.LogError($"[HTTPRequest] SendRoutine ({DateTime.Now:T}) -> Resp - Status: Error -> {url} Response Code -> {response.statusCode} Error -> {response.errorMessage}"); + } + } + + public void LogCustomMessage(string url, string message) + { + Debug.Log($"[HTTPRequest] SendRoutine ({DateTime.Now:T}) -> Resp - Status: {message} -> {url}"); + } + } +} diff --git a/Runtime/Tools/RequestLogger.cs.meta b/Runtime/Tools/RequestLogger.cs.meta new file mode 100644 index 0000000..4a97eb5 --- /dev/null +++ b/Runtime/Tools/RequestLogger.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b3462f324b7a6da4f889788b1f7ec200 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Tools/UrlUtility.cs b/Runtime/Tools/UrlUtility.cs new file mode 100644 index 0000000..141cdec --- /dev/null +++ b/Runtime/Tools/UrlUtility.cs @@ -0,0 +1,19 @@ +namespace UAPIModule.Tools +{ + public static class UrlUtility + { + /// + /// Joins the urls by handling the existence or absence of the slash in the urls. + /// + public static string Join(string url, string additionalUrl) + { + if (url[url.Length - 1] == '/') + url = url.Remove(url.Length - 1); + + if (additionalUrl[0] == '/') + additionalUrl = additionalUrl.Substring(1); + + return $"{url}/{additionalUrl}"; + } + } +} diff --git a/Runtime/Tools/UrlUtility.cs.meta b/Runtime/Tools/UrlUtility.cs.meta new file mode 100644 index 0000000..98c48b3 --- /dev/null +++ b/Runtime/Tools/UrlUtility.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 5457fe30c8ce90747b93566e9555c8c9 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/UAPIModule.asmdef b/Runtime/UAPIModule.asmdef new file mode 100644 index 0000000..40003d8 --- /dev/null +++ b/Runtime/UAPIModule.asmdef @@ -0,0 +1,16 @@ +{ + "name": "UAPIModule", + "rootNamespace": "UAPIModule", + "references": [ + "GUID:f51ebe6a0ceec4240a699833d6309b23" + ], + "includePlatforms": [], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": false, + "precompiledReferences": [], + "autoReferenced": true, + "defineConstraints": [], + "versionDefines": [], + "noEngineReferences": false +} \ No newline at end of file diff --git a/Runtime/UAPIModule.asmdef.meta b/Runtime/UAPIModule.asmdef.meta new file mode 100644 index 0000000..3966dbf --- /dev/null +++ b/Runtime/UAPIModule.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: fb338a082c8bccd4c8a33c34b627ce0f +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Tests.meta b/Tests.meta new file mode 100644 index 0000000..ddd618a --- /dev/null +++ b/Tests.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: e46ad438e7e52444db7f096859a658cc +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Tests/APIConfigTests.cs b/Tests/APIConfigTests.cs new file mode 100644 index 0000000..61d5f34 --- /dev/null +++ b/Tests/APIConfigTests.cs @@ -0,0 +1,46 @@ +using NUnit.Framework; +using System.Collections.Generic; +using UAPIModule.Assets; +using UAPIModule.SharedTypes; +using UnityEngine; + +namespace UAPIModule.Tests +{ + public class APIConfigTests + { + [Test] + public void CreateConfigData_ValidValues_ReturnsCorrectConfigData() + { + // Arrange + var baseURLConfig = ScriptableObject.CreateInstance(); + baseURLConfig.GetType().GetProperty("BaseURL").SetValue(baseURLConfig, "https://example.com"); + + var headers = ScriptableObject.CreateInstance(); + headers.GetType().GetProperty("Parameters").SetValue(headers, new List + { + new() { key = "Content-Type", value = "application/json" } + }); + + var apiConfig = ScriptableObject.CreateInstance(); + apiConfig.GetType().GetProperty("BaseURLConfig").SetValue(apiConfig, baseURLConfig); + apiConfig.GetType().GetProperty("Endpoint").SetValue(apiConfig, "/test"); + apiConfig.GetType().GetProperty("MethodType").SetValue(apiConfig, HTTPRequestMethod.GET); + apiConfig.GetType().GetProperty("Headers").SetValue(apiConfig, headers); + apiConfig.GetType().GetProperty("NeedsAuthHeader").SetValue(apiConfig, true); + apiConfig.GetType().GetProperty("Timeout").SetValue(apiConfig, 15000); + apiConfig.GetType().GetProperty("UseBearerPrefix").SetValue(apiConfig, false); + + // Act + var configData = apiConfig.CreateConfigData(); + + // Assert + Assert.AreEqual(baseURLConfig, configData.BaseURLConfig); + Assert.AreEqual("/test", configData.Endpoint); + Assert.AreEqual(HTTPRequestMethod.GET, configData.MethodType); + Assert.AreEqual(headers, configData.Headers); + Assert.AreEqual(true, configData.NeedsAuthHeader); + Assert.AreEqual(15000, configData.Timeout); + Assert.AreEqual(false, configData.UseBearerPrefix); + } + } +} diff --git a/Tests/APIConfigTests.cs.meta b/Tests/APIConfigTests.cs.meta new file mode 100644 index 0000000..1b47142 --- /dev/null +++ b/Tests/APIConfigTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 621b42963643ca44fab4e209aec365c5 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Tests/APITest.cs b/Tests/APITest.cs new file mode 100644 index 0000000..0ac8dbe --- /dev/null +++ b/Tests/APITest.cs @@ -0,0 +1,40 @@ +using UAPIModule.Abstraction; +using UAPIModule.Assets; +using UAPIModule.SharedTypes; +using UAPIModule.Tools; +using UnityEngine; + +namespace UAPIModule.Tests +{ + public class APIManager : RequestSender + { + public APIManager(INetworkLoadingHandler loadingHandler) : base(loadingHandler) { } + } + + public class APITest : MonoBehaviour + { + public APIConfig apiConfig; + private INetworkLoadingHandler loadingHandler; + + private void Awake() + { + loadingHandler = NetworkLoadingHandlerCreator.CreateAndGet(); + } + + private async void Start() + { + APIManager apiManager = new APIManager(loadingHandler); + RequestFeedbackConfig feedbackConfig = RequestFeedbackConfig.InitializationFeedback; + + NetworkResponse response = await apiManager.SendRequest(apiConfig, feedbackConfig, null); + if (response.isSuccessful) + { + Debug.Log(response.data); + } + else + { + Debug.LogError($"Request failed: {response.errorMessage}"); + } + } + } +} diff --git a/Tests/APITest.cs.meta b/Tests/APITest.cs.meta new file mode 100644 index 0000000..d1bd53c --- /dev/null +++ b/Tests/APITest.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 52303b4024588964f9d0c65788c61c16 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Tests/JwtTokenResolverTests.cs b/Tests/JwtTokenResolverTests.cs new file mode 100644 index 0000000..1b5514a --- /dev/null +++ b/Tests/JwtTokenResolverTests.cs @@ -0,0 +1,105 @@ +using NUnit.Framework; +using UAPIModule.Tools; +using UnityEngine; + +namespace UAPIModule.Tests +{ + public class JwtTokenResolverTests + { + [SetUp] + public void SetUp() + { + PlayerPrefs.DeleteAll(); + JwtTokenResolver.SetUseBearerPrefix(true); // Reset to default state + } + + [Test] + public void AccessToken_SetAndGet_ReturnsCorrectToken() + { + // Arrange + var token = "testAccessToken"; + + // Act + JwtTokenResolver.SetAccessToken(token); + var retrievedToken = JwtTokenResolver.AccessToken; + + // Assert + Assert.AreEqual(token, retrievedToken); + } + + [Test] + public void RefreshToken_SetAndGet_ReturnsCorrectToken() + { + // Arrange + var token = "testRefreshToken"; + + // Act + JwtTokenResolver.SetRefreshToken(token); + var retrievedToken = JwtTokenResolver.RefreshToken; + + // Assert + Assert.AreEqual(token, retrievedToken); + } + + [Test] + public void AccessTokenHeader_UseBearerPrefix_ReturnsBearerToken() + { + // Arrange + var token = "testAccessToken"; + JwtTokenResolver.SetAccessToken(token); + + // Act + var authHeader = JwtTokenResolver.AccessTokenHeader; + + // Assert + Assert.AreEqual(JwtTokenResolver.AUTHORIZATION_HEADER_KEY, authHeader.Key); + Assert.AreEqual("Bearer " + token, authHeader.Value); + } + + [Test] + public void AccessTokenHeader_DoNotUseBearerPrefix_ReturnsTokenWithoutBearer() + { + // Arrange + var token = "testAccessToken"; + JwtTokenResolver.SetAccessToken(token); + JwtTokenResolver.SetUseBearerPrefix(false); + + // Act + var authHeader = JwtTokenResolver.AccessTokenHeader; + + // Assert + Assert.AreEqual(JwtTokenResolver.AUTHORIZATION_HEADER_KEY, authHeader.Key); + Assert.AreEqual(token, authHeader.Value); + } + + [Test] + public void RemoveTokens_RemovesBothAccessTokenAndRefreshToken() + { + // Arrange + JwtTokenResolver.SetAccessToken("accessToken"); + JwtTokenResolver.SetRefreshToken("refreshToken"); + + // Act + JwtTokenResolver.RemoveTokens(); + + // Assert + Assert.IsEmpty(JwtTokenResolver.AccessToken); + Assert.IsEmpty(JwtTokenResolver.RefreshToken); + } + + [Test] + public void RemoveAccessToken_RemovesOnlyAccessToken() + { + // Arrange + JwtTokenResolver.SetAccessToken("accessToken"); + JwtTokenResolver.SetRefreshToken("refreshToken"); + + // Act + JwtTokenResolver.RemoveAccessToken(); + + // Assert + Assert.IsEmpty(JwtTokenResolver.AccessToken); + Assert.AreEqual("refreshToken", JwtTokenResolver.RefreshToken); + } + } +} diff --git a/Tests/JwtTokenResolverTests.cs.meta b/Tests/JwtTokenResolverTests.cs.meta new file mode 100644 index 0000000..1260725 --- /dev/null +++ b/Tests/JwtTokenResolverTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: dc5a21e507528934890e60fd33327677 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Tests/Scenes.meta b/Tests/Scenes.meta new file mode 100644 index 0000000..2410cea --- /dev/null +++ b/Tests/Scenes.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 9f3de381066b5ce4b93b97f88cc96819 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Tests/Scenes/APIManagerRuntimeTest.unity b/Tests/Scenes/APIManagerRuntimeTest.unity new file mode 100644 index 0000000..c9fa653 --- /dev/null +++ b/Tests/Scenes/APIManagerRuntimeTest.unity @@ -0,0 +1,239 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!29 &1 +OcclusionCullingSettings: + m_ObjectHideFlags: 0 + serializedVersion: 2 + m_OcclusionBakeSettings: + smallestOccluder: 5 + smallestHole: 0.25 + backfaceThreshold: 100 + m_SceneGUID: 00000000000000000000000000000000 + m_OcclusionCullingData: {fileID: 0} +--- !u!104 &2 +RenderSettings: + m_ObjectHideFlags: 0 + serializedVersion: 9 + m_Fog: 0 + m_FogColor: {r: 0.5, g: 0.5, b: 0.5, a: 1} + m_FogMode: 3 + m_FogDensity: 0.01 + m_LinearFogStart: 0 + m_LinearFogEnd: 300 + m_AmbientSkyColor: {r: 0.212, g: 0.227, b: 0.259, a: 1} + m_AmbientEquatorColor: {r: 0.114, g: 0.125, b: 0.133, a: 1} + m_AmbientGroundColor: {r: 0.047, g: 0.043, b: 0.035, a: 1} + m_AmbientIntensity: 1 + m_AmbientMode: 0 + m_SubtractiveShadowColor: {r: 0.42, g: 0.478, b: 0.627, a: 1} + m_SkyboxMaterial: {fileID: 0} + m_HaloStrength: 0.5 + m_FlareStrength: 1 + m_FlareFadeSpeed: 3 + m_HaloTexture: {fileID: 0} + m_SpotCookie: {fileID: 10001, guid: 0000000000000000e000000000000000, type: 0} + m_DefaultReflectionMode: 0 + m_DefaultReflectionResolution: 128 + m_ReflectionBounces: 1 + m_ReflectionIntensity: 1 + m_CustomReflection: {fileID: 0} + m_Sun: {fileID: 0} + m_IndirectSpecularColor: {r: 262.05307, g: 324.67154, b: 429.72485, a: 1} + m_UseRadianceAmbientProbe: 0 +--- !u!157 &3 +LightmapSettings: + m_ObjectHideFlags: 0 + serializedVersion: 12 + m_GIWorkflowMode: 1 + m_GISettings: + serializedVersion: 2 + m_BounceScale: 1 + m_IndirectOutputScale: 1 + m_AlbedoBoost: 1 + m_EnvironmentLightingMode: 0 + m_EnableBakedLightmaps: 1 + m_EnableRealtimeLightmaps: 0 + m_LightmapEditorSettings: + serializedVersion: 12 + m_Resolution: 2 + m_BakeResolution: 40 + m_AtlasSize: 1024 + m_AO: 0 + m_AOMaxDistance: 1 + m_CompAOExponent: 1 + m_CompAOExponentDirect: 0 + m_ExtractAmbientOcclusion: 0 + m_Padding: 2 + m_LightmapParameters: {fileID: 0} + m_LightmapsBakeMode: 1 + m_TextureCompression: 1 + m_FinalGather: 0 + m_FinalGatherFiltering: 1 + m_FinalGatherRayCount: 256 + m_ReflectionCompression: 2 + m_MixedBakeMode: 2 + m_BakeBackend: 1 + m_PVRSampling: 1 + m_PVRDirectSampleCount: 32 + m_PVRSampleCount: 512 + m_PVRBounces: 2 + m_PVREnvironmentSampleCount: 256 + m_PVREnvironmentReferencePointCount: 2048 + m_PVRFilteringMode: 1 + m_PVRDenoiserTypeDirect: 1 + m_PVRDenoiserTypeIndirect: 1 + m_PVRDenoiserTypeAO: 1 + m_PVRFilterTypeDirect: 0 + m_PVRFilterTypeIndirect: 0 + m_PVRFilterTypeAO: 0 + m_PVREnvironmentMIS: 1 + m_PVRCulling: 1 + m_PVRFilteringGaussRadiusDirect: 1 + m_PVRFilteringGaussRadiusIndirect: 5 + m_PVRFilteringGaussRadiusAO: 2 + m_PVRFilteringAtrousPositionSigmaDirect: 0.5 + m_PVRFilteringAtrousPositionSigmaIndirect: 2 + m_PVRFilteringAtrousPositionSigmaAO: 1 + m_ExportTrainingData: 0 + m_TrainingDataDestination: TrainingData + m_LightProbeSampleCountMultiplier: 4 + m_LightingDataAsset: {fileID: 0} + m_LightingSettings: {fileID: 1048866777} +--- !u!196 &4 +NavMeshSettings: + serializedVersion: 2 + m_ObjectHideFlags: 0 + m_BuildSettings: + serializedVersion: 3 + agentTypeID: 0 + agentRadius: 0.5 + agentHeight: 2 + agentSlope: 45 + agentClimb: 0.4 + ledgeDropHeight: 0 + maxJumpAcrossDistance: 0 + minRegionArea: 2 + manualCellSize: 0 + cellSize: 0.16666667 + manualTileSize: 0 + tileSize: 256 + buildHeightMesh: 0 + maxJobWorkers: 0 + preserveTilesOutsideBounds: 0 + debug: + m_Flags: 0 + m_NavMeshData: {fileID: 0} +--- !u!1 &250969199 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 250969201} + - component: {fileID: 250969200} + m_Layer: 0 + m_Name: APITest + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!114 &250969200 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 250969199} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 52303b4024588964f9d0c65788c61c16, type: 3} + m_Name: + m_EditorClassIdentifier: + apiConfig: {fileID: 11400000, guid: 639ddf4cf27bde040869ccf516c6c416, type: 2} +--- !u!4 &250969201 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 250969199} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 1745.6381, y: 212.62993, z: -0.274212} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!850595691 &1048866777 +LightingSettings: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: + serializedVersion: 6 + m_GIWorkflowMode: 1 + m_EnableBakedLightmaps: 1 + m_EnableRealtimeLightmaps: 0 + m_RealtimeEnvironmentLighting: 1 + m_BounceScale: 1 + m_AlbedoBoost: 1 + m_IndirectOutputScale: 1 + m_UsingShadowmask: 1 + m_BakeBackend: 1 + m_LightmapMaxSize: 1024 + m_BakeResolution: 40 + m_Padding: 2 + m_LightmapCompression: 3 + m_AO: 0 + m_AOMaxDistance: 1 + m_CompAOExponent: 1 + m_CompAOExponentDirect: 0 + m_ExtractAO: 0 + m_MixedBakeMode: 2 + m_LightmapsBakeMode: 1 + m_FilterMode: 1 + m_LightmapParameters: {fileID: 15204, guid: 0000000000000000f000000000000000, type: 0} + m_ExportTrainingData: 0 + m_TrainingDataDestination: TrainingData + m_RealtimeResolution: 2 + m_ForceWhiteAlbedo: 0 + m_ForceUpdates: 0 + m_FinalGather: 0 + m_FinalGatherRayCount: 256 + m_FinalGatherFiltering: 1 + m_PVRCulling: 1 + m_PVRSampling: 1 + m_PVRDirectSampleCount: 32 + m_PVRSampleCount: 512 + m_PVREnvironmentSampleCount: 256 + m_PVREnvironmentReferencePointCount: 2048 + m_LightProbeSampleCountMultiplier: 4 + m_PVRBounces: 2 + m_PVRMinBounces: 2 + m_PVREnvironmentImportanceSampling: 1 + m_PVRFilteringMode: 1 + m_PVRDenoiserTypeDirect: 1 + m_PVRDenoiserTypeIndirect: 1 + m_PVRDenoiserTypeAO: 1 + m_PVRFilterTypeDirect: 0 + m_PVRFilterTypeIndirect: 0 + m_PVRFilterTypeAO: 0 + m_PVRFilteringGaussRadiusDirect: 1 + m_PVRFilteringGaussRadiusIndirect: 5 + m_PVRFilteringGaussRadiusAO: 2 + m_PVRFilteringAtrousPositionSigmaDirect: 0.5 + m_PVRFilteringAtrousPositionSigmaIndirect: 2 + m_PVRFilteringAtrousPositionSigmaAO: 1 + m_PVRTiledBaking: 0 + m_NumRaysToShootPerTexel: -1 + m_RespectSceneVisibilityWhenBakingGI: 0 +--- !u!1660057539 &9223372036854775807 +SceneRoots: + m_ObjectHideFlags: 0 + m_Roots: + - {fileID: 250969201} diff --git a/Tests/Scenes/APIManagerRuntimeTest.unity.meta b/Tests/Scenes/APIManagerRuntimeTest.unity.meta new file mode 100644 index 0000000..14e63e7 --- /dev/null +++ b/Tests/Scenes/APIManagerRuntimeTest.unity.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: f401899035602bd48a67a11ca4cd72ff +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Tests/ScriptableObjects.meta b/Tests/ScriptableObjects.meta new file mode 100644 index 0000000..5ecd222 --- /dev/null +++ b/Tests/ScriptableObjects.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 41ada6b3bb31bcf459f9ec9c1b3cbe80 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Tests/ScriptableObjects/APIConfig.asset b/Tests/ScriptableObjects/APIConfig.asset new file mode 100644 index 0000000..301507f --- /dev/null +++ b/Tests/ScriptableObjects/APIConfig.asset @@ -0,0 +1,21 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &11400000 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: c693ecc3fda8a3b4cb3777531ab9efcf, type: 3} + m_Name: APIConfig + m_EditorClassIdentifier: + k__BackingField: {fileID: 11400000, guid: 731bd769c24ad4a47b0410904e855393, type: 2} + k__BackingField: todos/1 + k__BackingField: 0 + k__BackingField: {fileID: 11400000, guid: 0f12871756c5861498a58930e56a46ab, type: 2} + k__BackingField: 0 + k__BackingField: 10000 + k__BackingField: 1 diff --git a/Tests/ScriptableObjects/APIConfig.asset.meta b/Tests/ScriptableObjects/APIConfig.asset.meta new file mode 100644 index 0000000..bc20058 --- /dev/null +++ b/Tests/ScriptableObjects/APIConfig.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 639ddf4cf27bde040869ccf516c6c416 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Tests/ScriptableObjects/BaseURLConfig.asset b/Tests/ScriptableObjects/BaseURLConfig.asset new file mode 100644 index 0000000..96820c1 --- /dev/null +++ b/Tests/ScriptableObjects/BaseURLConfig.asset @@ -0,0 +1,15 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &11400000 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 535809360fc0cff48b7bba30719d144e, type: 3} + m_Name: BaseURLConfig + m_EditorClassIdentifier: + k__BackingField: https://jsonplaceholder.typicode.com/todos/ diff --git a/Tests/ScriptableObjects/BaseURLConfig.asset.meta b/Tests/ScriptableObjects/BaseURLConfig.asset.meta new file mode 100644 index 0000000..9f86818 --- /dev/null +++ b/Tests/ScriptableObjects/BaseURLConfig.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 731bd769c24ad4a47b0410904e855393 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Tests/ScriptableObjects/HttpRequestParams.asset b/Tests/ScriptableObjects/HttpRequestParams.asset new file mode 100644 index 0000000..e583d22 --- /dev/null +++ b/Tests/ScriptableObjects/HttpRequestParams.asset @@ -0,0 +1,15 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &11400000 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 2cb80fa1b1977a4429bcfc138da62659, type: 3} + m_Name: HttpRequestParams + m_EditorClassIdentifier: + k__BackingField: [] diff --git a/Tests/ScriptableObjects/HttpRequestParams.asset.meta b/Tests/ScriptableObjects/HttpRequestParams.asset.meta new file mode 100644 index 0000000..6b51a32 --- /dev/null +++ b/Tests/ScriptableObjects/HttpRequestParams.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 0f12871756c5861498a58930e56a46ab +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Tests/UAPIModule.Test.asmdef b/Tests/UAPIModule.Test.asmdef new file mode 100644 index 0000000..f7c1482 --- /dev/null +++ b/Tests/UAPIModule.Test.asmdef @@ -0,0 +1,20 @@ +{ + "name": "UAPIModule.Tests", + "rootNamespace": "UAPIModule.Tests", + "references": [ + "GUID:f51ebe6a0ceec4240a699833d6309b23", + "GUID:fb338a082c8bccd4c8a33c34b627ce0f", + "GUID:27619889b8ba8c24980f49ee34dbb44a" + ], + "includePlatforms": [], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": true, + "precompiledReferences": [ + "nunit.framework.dll" + ], + "autoReferenced": true, + "defineConstraints": [], + "versionDefines": [], + "noEngineReferences": false +} \ No newline at end of file diff --git a/Tests/UAPIModule.Test.asmdef.meta b/Tests/UAPIModule.Test.asmdef.meta new file mode 100644 index 0000000..87a9988 --- /dev/null +++ b/Tests/UAPIModule.Test.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: a1459d8558898864b93784abaa716992 +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/package.json b/package.json new file mode 100644 index 0000000..4a83333 --- /dev/null +++ b/package.json @@ -0,0 +1,14 @@ +{ + "name": "com.useffarahmand.uapimodule", + "version": "1.0.0", + "displayName": "UAPIModule", + "description": "UAPIModule", + "unity": "2022.3", + "unityRelease": "0f1", + "author": { + "name": "Usef Farahmand" + }, + "dependencies": { + "com.unity.nuget.newtonsoft-json": "3.2.1" + } +} diff --git a/package.json.meta b/package.json.meta new file mode 100644 index 0000000..ae09e90 --- /dev/null +++ b/package.json.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 47c92046aa27ad447839f26f86c194c1 +PackageManifestImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: