diff --git a/Runtime/Client/LootLockerBaseServerAPI.cs b/Runtime/Client/LootLockerBaseServerAPI.cs index b32e84dc..7b5b8b52 100644 --- a/Runtime/Client/LootLockerBaseServerAPI.cs +++ b/Runtime/Client/LootLockerBaseServerAPI.cs @@ -60,6 +60,16 @@ public void SwitchURL(LootLocker.LootLockerEnums.LootLockerCallerRole mainCaller callerRole = mainCallerRole; } + private bool WebRequestSucceeded(UnityWebRequest webRequest) + { + return ! +#if UNITY_2020_1_OR_NEWER + (webRequest.result == UnityWebRequest.Result.ProtocolError || webRequest.result == UnityWebRequest.Result.ConnectionError || !string.IsNullOrEmpty(webRequest.error)); +#else + (webRequest.isHttpError || webRequest.isNetworkError || !string.IsNullOrEmpty(webRequest.error)); +#endif + } + public void SendRequest(LootLockerServerRequest request, System.Action OnServerResponse = null) { StartCoroutine(coroutine()); @@ -97,7 +107,7 @@ IEnumerator coroutine() if (!webRequest.isDone && timedOut) { LootLockerLogger.GetForLogLevel(LootLockerLogger.LogLevel.Warning)("Exceeded maxTimeOut waiting for a response from " + request.httpMethod + " " + url); - OnServerResponse?.Invoke(new LootLockerResponse() { hasError = true, statusCode = 408, text = "{\"error\": \"" + request.endpoint + " Timed out.\"}", Error = request.endpoint + " Timed out." }); + OnServerResponse?.Invoke(LootLockerResponseFactory.Error(request.endpoint + " Timed out.", 408)); yield break; } @@ -112,55 +122,17 @@ IEnumerator coroutine() LootLockerLogger.GetForLogLevel(LootLockerLogger.LogLevel.Error)(ObfuscateJsonStringForLogging(webRequest.downloadHandler.text)); } - LootLockerResponse response = new LootLockerResponse(); - response.statusCode = (int)webRequest.responseCode; -#if UNITY_2020_1_OR_NEWER - if (webRequest.result == UnityWebRequest.Result.ProtocolError || webRequest.result == UnityWebRequest.Result.ConnectionError || !string.IsNullOrEmpty(webRequest.error)) -#else - if (webRequest.isHttpError || webRequest.isNetworkError || !string.IsNullOrEmpty(webRequest.error)) -#endif - + if(WebRequestSucceeded(webRequest)) { - switch (webRequest.responseCode) - { - case 200: - response.Error = ""; - break; - case 400: - response.Error = "Bad Request -- Your request has an error"; - break; - case 402: - response.Error = "Payment Required -- Payment failed. Insufficient funds, etc."; - break; - case 401: - response.Error = "Unauthorized -- Your session_token is invalid"; - break; - case 403: - response.Error = "Forbidden -- You do not have access"; - break; - case 404: - response.Error = "Not Found"; - break; - case 405: - response.Error = "Method Not Allowed"; - break; - case 406: - response.Error = "Not Acceptable -- Purchasing is disabled"; - break; - case 409: - response.Error = "Conflict -- Your state is most likely not aligned with the servers."; - break; - case 429: - response.Error = "Too Many Requests -- You're being limited for sending too many requests too quickly."; - break; - case 500: - response.Error = "Internal Server Error -- We had a problem with our server. Try again later."; - break; - case 503: - response.Error = "Service Unavailable -- We're either offline for maintenance, or an error that should be solvable by calling again later was triggered."; - break; - } - + LootLockerResponse response = new LootLockerResponse(); + response.statusCode = (int)webRequest.responseCode; + response.success = true; + response.text = webRequest.downloadHandler.text; + response.errorData = null; + OnServerResponse?.Invoke(response); + } + else + { if ((webRequest.responseCode == 401 || webRequest.responseCode == 403) && LootLockerConfig.current.allowTokenRefresh && CurrentPlatform.Get() != Platforms.Steam && tries < maxRetry) { tries++; @@ -170,23 +142,75 @@ IEnumerator coroutine() else { tries = 0; - response.Error += " " + ObfuscateJsonStringForLogging(webRequest.downloadHandler.text); + LootLockerResponse response = new LootLockerResponse(); response.statusCode = (int)webRequest.responseCode; response.success = false; - response.hasError = true; response.text = webRequest.downloadHandler.text; - LootLockerLogger.GetForLogLevel(LootLockerLogger.LogLevel.Error)(response.Error); + + LootLockerErrorData errorData = LootLockerJson.DeserializeObject(webRequest.downloadHandler.text); + // Check if the error uses the "new" error style (https://docs.lootlocker.com/reference/error-codes) + if (errorData != null && !string.IsNullOrEmpty(errorData.code)) + { + response.errorData = errorData; + } + else + { + errorData = new LootLockerErrorData(); + switch (webRequest.responseCode) + { + case 400: + errorData.message = "Bad Request -- Your request has an error."; + errorData.code = "bad_request"; + break; + case 401: + errorData.message = "Unauthorized -- Your session_token is invalid."; + errorData.code = "unauthorized"; + break; + case 402: + errorData.message = "Payment Required -- Payment failed. Insufficient funds, etc."; + errorData.code = "payment_required"; + break; + case 403: + errorData.message = "Forbidden -- You do not have access to this resource."; + errorData.code = "forbidden"; + break; + case 404: + errorData.message = "Not Found -- The requested resource could not be found."; + errorData.code = "not_found"; + break; + case 405: + errorData.message = "Method Not Allowed -- The selected http method is invalid for this resource."; + errorData.code = "method_not_allowed"; + break; + case 406: + errorData.message = "Not Acceptable -- Purchasing is disabled."; + errorData.code = "not_acceptable"; + break; + case 409: + errorData.message = "Conflict -- Your state is most likely not aligned with the servers."; + errorData.code = "conflict"; + break; + case 429: + errorData.message = "Too Many Requests -- You're being limited for sending too many requests too quickly."; + errorData.code = "too_many_requests"; + break; + case 500: + errorData.message = "Internal Server Error -- We had a problem with our server. Try again later."; + errorData.code = "internal_server_error"; + break; + case 503: + errorData.message = "Service Unavailable -- We're either offline for maintenance, or an error that should be solvable by calling again later was triggered."; + errorData.code = "service_unavailable"; + break; + default: + errorData.message = "Unknown error."; + break; + } + errorData.message += " " + ObfuscateJsonStringForLogging(webRequest.downloadHandler.text); + } + LootLockerLogger.GetForLogLevel(LootLockerLogger.LogLevel.Error)(errorData.message + (!string.IsNullOrEmpty(errorData.doc_url) ? " -- " + errorData.doc_url : "")); OnServerResponse?.Invoke(response); } - - } - else - { - response.success = true; - response.hasError = false; - response.statusCode = (int)webRequest.responseCode; - response.text = webRequest.downloadHandler.text; - OnServerResponse?.Invoke(response); } } @@ -278,7 +302,7 @@ UnityWebRequest CreateWebRequest(string url, LootLockerServerRequest request) // Set the content type - NO QUOTES around the boundary string contentType = String.Concat("multipart/form-data; boundary=--", Encoding.UTF8.GetString(boundary)); - // Make my request object and add the raw body. Set anything else you need here + // Make my request object and add the raw text. Set anything else you need here webRequest = new UnityWebRequest(); webRequest.SetRequestHeader("Content-Type", "multipart/form-data; boundary=--"); webRequest.uri = new Uri(url); diff --git a/Runtime/Client/LootLockerServerRequest.cs b/Runtime/Client/LootLockerServerRequest.cs index 1a161e18..8bc08f46 100644 --- a/Runtime/Client/LootLockerServerRequest.cs +++ b/Runtime/Client/LootLockerServerRequest.cs @@ -90,35 +90,75 @@ public enum LootLockerHTTPMethod UPDATE_FILE = 9 } + public class LootLockerErrorData + { + /// + /// A descriptive code identifying the error. + /// + public string code { get; set; } + + /// + /// A link to further documentation on the error. + /// + public string doc_url { get; set; } + + /// + /// A unique identifier of the request to use in contact with support. + /// + public string request_id { get; set; } + + /// + /// A unique identifier for tracing the request through LootLocker systems, use this in contact with support. + /// + public string trace_id { get; set; } + + /// + /// If the request was not a success this property will hold any error messages + /// + public string message { get; set; } + } + /// /// All ServerAPI.SendRequest responses will invoke the callback using an instance of this class for easier handling in client code. /// public class LootLockerResponse { /// - /// TRUE if http error OR server returns an error status + /// HTTP Status Code /// - public bool hasError { get; set; } + public int statusCode { get; set; } /// - /// HTTP Status Code + /// Whether this request was a success /// - public int statusCode { get; set; } + public bool success { get; set; } /// - /// Raw text response from the server - /// If hasError = true, this will contain the error message. + /// Raw text/http body from the server response /// public string text { get; set; } - public bool success { get; set; } + /// + /// If this request was not a success, this structure holds all the information needed to identify the problem + /// + public LootLockerErrorData errorData { get; set; } + /// + /// TRUE if http error OR server returns an error status + /// + [Obsolete("This property is deprecated, use success instead")] + public bool hasError { get { return !success; } } - public string Error { get; set; } + /// + /// If the request was not a success this property will hold any error messages + /// + [Obsolete("This property is deprecated, replaced by the errorData.message property")] + public string Error { get { return errorData?.message; } } /// /// A texture downloaded in the webrequest, if applicable, otherwise this will be null. /// + [Obsolete("This property is deprecated")] public Texture2D texture { get; set; } /// @@ -150,18 +190,18 @@ public static T Deserialize(LootLockerResponse serverResponse, { if (serverResponse == null) { - return new T() { success = false, Error = "Unknown error, please check your internet connection." }; + return LootLockerResponseFactory.Error("Unknown error, please check your internet connection."); } - else if (!string.IsNullOrEmpty(serverResponse.Error)) + else if (serverResponse.errorData != null && !string.IsNullOrEmpty(serverResponse.errorData.code)) { - return new T() { success = false, Error = serverResponse.Error, statusCode = serverResponse.statusCode }; + return new T() { success = false, errorData = serverResponse.errorData, statusCode = serverResponse.statusCode }; } var response = LootLockerJson.DeserializeObject(serverResponse.text, options ?? LootLockerJsonSettings.Default) ?? new T(); response.text = serverResponse.text; response.success = serverResponse.success; - response.Error = serverResponse.Error; + response.errorData = serverResponse.errorData; response.statusCode = serverResponse.statusCode; return response; @@ -182,14 +222,14 @@ public class LootLockerResponseFactory /// /// Construct an error response to send to the client. /// - public static T Error(string errorMessage) where T : LootLockerResponse, new() + public static T Error(string errorMessage, int statusCode = 0) where T : LootLockerResponse, new() { return new T() { success = false, - hasError = true, - Error = errorMessage, - text = errorMessage + text = errorMessage, + statusCode = statusCode, + errorData = new LootLockerErrorData() { message = errorMessage } }; } diff --git a/Runtime/Game/LootLockerGameServerAPI.cs b/Runtime/Game/LootLockerGameServerAPI.cs index ba411f8d..fcca7443 100644 --- a/Runtime/Game/LootLockerGameServerAPI.cs +++ b/Runtime/Game/LootLockerGameServerAPI.cs @@ -1,9 +1,6 @@ using System; -using System.Collections; using System.Collections.Generic; -using LootLocker; using LootLocker.Requests; -using UnityEngine; namespace LootLocker { @@ -80,26 +77,14 @@ protected override void RefreshTokenAndCompleteCall(LootLockerServerRequest cach } } LootLockerLogger.GetForLogLevel(LootLockerLogger.LogLevel.Warning)($"Token has expired, please refresh it"); - LootLockerResponse res = new LootLockerResponse - { - statusCode = 401, - Error = "Token Expired", - hasError = true - }; - OnServerResponse?.Invoke(res); + OnServerResponse?.Invoke(LootLockerResponseFactory.Error("Token Expired", 401)); return; } case Platforms.NintendoSwitch: case Platforms.Steam: { LootLockerLogger.GetForLogLevel(LootLockerLogger.LogLevel.Warning)($"Token has expired and token refresh is not supported for {CurrentPlatform.GetFriendlyString()}"); - LootLockerResponse res = new LootLockerResponse - { - statusCode = 401, - Error = "Token Expired", - hasError = true - }; - OnServerResponse?.Invoke(res); + OnServerResponse?.Invoke(LootLockerResponseFactory.Error("Token Expired", 401)); return; } case Platforms.PlayStationNetwork: @@ -117,13 +102,7 @@ protected override void RefreshTokenAndCompleteCall(LootLockerServerRequest cach default: { LootLockerLogger.GetForLogLevel(LootLockerLogger.LogLevel.Error)($"Platform {CurrentPlatform.GetFriendlyString()} not supported"); - LootLockerResponse res = new LootLockerResponse - { - statusCode = 401, - Error = $"Platform {CurrentPlatform.GetFriendlyString()} not supported", - hasError = true - }; - OnServerResponse?.Invoke(res); + OnServerResponse?.Invoke(LootLockerResponseFactory.Error($"Platform {CurrentPlatform.GetFriendlyString()} not supported", 401)); return; } } @@ -144,21 +123,14 @@ void CompleteCall(LootLockerServerRequest newcacheServerRequest, Action("Token Expired", 401)); } } else { LootLockerLogger.GetForLogLevel(LootLockerLogger.LogLevel.Info)("Session refresh failed"); LootLockerResponse res = new LootLockerResponse(); - res.statusCode = 401; - res.Error = "Token Expired"; - res.hasError = true; - newOnServerResponse?.Invoke(res); + newOnServerResponse?.Invoke(LootLockerResponseFactory.Error("Token Expired", 401)); } } } diff --git a/Runtime/Game/LootLockerSDKManager.cs b/Runtime/Game/LootLockerSDKManager.cs index 4529da84..7c3ea153 100644 --- a/Runtime/Game/LootLockerSDKManager.cs +++ b/Runtime/Game/LootLockerSDKManager.cs @@ -823,7 +823,7 @@ public static void EndSession(Action onComplete) { if (!CheckInitialized(true) || !CheckActiveSession()) { - onComplete?.Invoke(new LootLockerSessionResponse() { success = true, hasError = false, text = "No active session" }); + onComplete?.Invoke(new LootLockerSessionResponse() { success = true, text = "No active session" }); return; } diff --git a/Runtime/Game/Requests/AssetRequest.cs b/Runtime/Game/Requests/AssetRequest.cs index 1c14ccbf..f35ebecb 100644 --- a/Runtime/Game/Requests/AssetRequest.cs +++ b/Runtime/Game/Requests/AssetRequest.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using LootLocker.Requests; using System.Linq; +using UnityEngine.UI; namespace LootLocker.LootLockerEnums { @@ -76,14 +77,6 @@ public class LootLockerAssetResponse : LootLockerResponse public class LootLockerSingleAssetResponse : LootLockerResponse { public LootLockerCommonAsset asset { get; set; } - - public void SetResponseInfo(LootLockerResponse response) - { - this.hasError = response.hasError; - this.statusCode = response.statusCode; - this.success = response.success; - this.Error = response.Error; - } } public class LootLockerRental_Options @@ -326,21 +319,20 @@ public static void GetAssetById(LootLockerGetRequest data, Action { - - LootLockerAssetResponse realResponse = LootLockerResponse.Deserialize(serverResponse); - LootLockerSingleAssetResponse newResponse = new LootLockerSingleAssetResponse(); - - string serializedAsset = LootLockerJson.SerializeObject(realResponse.assets[0]); - - newResponse.asset = LootLockerJson.DeserializeObject(serializedAsset); + LootLockerServerRequest.CallAPI(getVariable, endPoint.httpMethod, null, onComplete : (Action)((serverResponse) => { - string singleAssetResponse = LootLockerJson.SerializeObject(newResponse); - newResponse.text = singleAssetResponse; - newResponse.SetResponseInfo(serverResponse); + LootLockerAssetResponse assetListResponse = LootLockerResponse.Deserialize(serverResponse); + LootLockerSingleAssetResponse singleAssetResponse = new LootLockerSingleAssetResponse(); + singleAssetResponse.success = assetListResponse.success; + singleAssetResponse.statusCode = assetListResponse.statusCode; + singleAssetResponse.errorData = assetListResponse.errorData; + singleAssetResponse.EventId = assetListResponse.EventId; + singleAssetResponse.asset = assetListResponse.assets != null && assetListResponse.assets.Count() > 0 ? assetListResponse.assets[0] : null; + string serializedSingleAssetResponse = LootLockerJson.SerializeObject(singleAssetResponse); + singleAssetResponse.text = serializedSingleAssetResponse; - LootLockerResponse.Deserialize(onComplete, newResponse); - }); + onComplete?.Invoke(singleAssetResponse); + })); } public void ResetAssetCalls() diff --git a/Runtime/Game/Requests/CharacterRequest.cs b/Runtime/Game/Requests/CharacterRequest.cs index e5ea370b..e2cc1099 100644 --- a/Runtime/Game/Requests/CharacterRequest.cs +++ b/Runtime/Game/Requests/CharacterRequest.cs @@ -101,6 +101,7 @@ public class EquipAssetToCharacterLoadoutResponse : LootLockerResponse { public LootLockerCharacter character { get; set; } public LootLockerLoadouts[] loadout { get; set; } + public string error { get; set; } } //[Serializable] diff --git a/Runtime/Game/Requests/CollectableRequest.cs b/Runtime/Game/Requests/CollectableRequest.cs index 797bb97f..66cc820b 100644 --- a/Runtime/Game/Requests/CollectableRequest.cs +++ b/Runtime/Game/Requests/CollectableRequest.cs @@ -99,16 +99,8 @@ public static void GetCollectables(Action onC LootLockerServerRequest.CallAPI(endPoint.endPoint, endPoint.httpMethod, "", (serverResponse) => { - LootLockerGetCollectablesResponse response = new LootLockerGetCollectablesResponse(); - if (string.IsNullOrEmpty(serverResponse.Error)) - response = LootLockerJson.DeserializeObject(serverResponse.text); - - response.text = serverResponse.text; - response.success = serverResponse.success; - response.Error = serverResponse.Error; - response.statusCode = serverResponse.statusCode; - onComplete?.Invoke(response); - }, true); + onComplete?.Invoke(LootLockerResponse.Deserialize(serverResponse)); + }); } [Obsolete("This function is deprecated and will be removed soon. Please use the function GetCollectables() instead")] @@ -118,15 +110,7 @@ public static void GettingCollectables(Action { - LootLockerGettingCollectablesResponse response = new LootLockerGettingCollectablesResponse(); - if (string.IsNullOrEmpty(serverResponse.Error)) - response = LootLockerJson.DeserializeObject(serverResponse.text); - - response.text = serverResponse.text; - response.success = serverResponse.success; - response.Error = serverResponse.Error; - response.statusCode = serverResponse.statusCode; - onComplete?.Invoke(response); + onComplete?.Invoke(LootLockerResponse.Deserialize(serverResponse)); }, true); } @@ -145,7 +129,7 @@ public static void CollectItem(LootLockerCollectingAnItemRequest data, Action { LootLockerCollectItemResponse response = new LootLockerCollectItemResponse(); - if (string.IsNullOrEmpty(serverResponse.Error)) + if (serverResponse.success) { response = LootLockerJson.DeserializeObject(serverResponse.text); string[] collectableStrings = data.slug.Split('.'); @@ -161,7 +145,7 @@ public static void CollectItem(LootLockerCollectingAnItemRequest data, Action { LootLockerCollectingAnItemResponse response = new LootLockerCollectingAnItemResponse(); - if (string.IsNullOrEmpty(serverResponse.Error)) + if (serverResponse.success) { response = LootLockerJson.DeserializeObject(serverResponse.text); string[] collectableStrings = data.slug.Split('.'); @@ -199,7 +183,7 @@ public static void CollectingAnItem(LootLockerCollectingAnItemRequest data, Acti response.text = serverResponse.text; response.success = serverResponse.success; - response.Error = serverResponse.Error; + response.errorData = serverResponse.errorData; response.statusCode = serverResponse.statusCode; onComplete?.Invoke(response); }, true); diff --git a/Runtime/Game/Requests/CrashesRequest.cs b/Runtime/Game/Requests/CrashesRequest.cs index 7fc4ed38..0bcf1acb 100644 --- a/Runtime/Game/Requests/CrashesRequest.cs +++ b/Runtime/Game/Requests/CrashesRequest.cs @@ -37,22 +37,7 @@ public static void SubmittingACrashLog(LootLockerSubmittingACrashLogRequest data } LootLockerServerRequest.UploadFile(requestEndPoint.endPoint, requestEndPoint.httpMethod, System.IO.File.ReadAllBytes(data.logFilePath), - data.logFileName, "application/zip", formData, onComplete: (serverResponse) => - { - LootLockerResponse response = new LootLockerResponse(); - if (string.IsNullOrEmpty(serverResponse.Error)) - { - LootLockerLogger.GetForLogLevel(LootLockerLogger.LogLevel.Verbose)(serverResponse.text); - response = LootLockerJson.DeserializeObject(serverResponse.text); - onComplete?.Invoke(response); - } - else - { - response.success = serverResponse.success; - response.Error = serverResponse.Error; response.statusCode = serverResponse.statusCode; - onComplete?.Invoke(response); - } - }, useAuthToken: false); + data.logFileName, "application/zip", formData, onComplete: onComplete, useAuthToken: false); } } } diff --git a/Runtime/Game/Requests/WhiteLabelRequest.cs b/Runtime/Game/Requests/WhiteLabelRequest.cs index 404f7c22..22e23136 100755 --- a/Runtime/Game/Requests/WhiteLabelRequest.cs +++ b/Runtime/Game/Requests/WhiteLabelRequest.cs @@ -61,11 +61,9 @@ public static LootLockerWhiteLabelLoginAndStartSessionResponse MakeWhiteLabelLog { statusCode = sessionResponse?.statusCode ?? loginResponse.statusCode, success = sessionResponse?.success ?? loginResponse.success, - Error = sessionResponse?.Error ?? loginResponse?.Error, + errorData = sessionResponse?.errorData ?? loginResponse?.errorData, EventId = sessionResponse?.EventId ?? loginResponse?.EventId, - hasError = sessionResponse?.hasError ?? loginResponse.hasError, text = sessionResponse?.text ?? loginResponse?.text, - texture = sessionResponse?.texture ?? loginResponse?.texture, LoginResponse = loginResponse, SessionResponse = sessionResponse }; @@ -144,18 +142,7 @@ public static void WhiteLabelRequestAccountVerification(string email, Action - { - LootLockerResponse response = new LootLockerResponse - { - text = serverResponse.text, - success = serverResponse.success, - Error = serverResponse.Error, - statusCode = serverResponse.statusCode - }; - - onComplete?.Invoke(response); - }); + LootLockerServerRequest.CallDomainAuthAPI(endPoint.endPoint, endPoint.httpMethod, json, onComplete); } } }