From 0299a5ae02921fe48e5f29be5145f247a2a0847c Mon Sep 17 00:00:00 2001 From: Erik Bylund Date: Tue, 21 Oct 2025 09:28:34 +0200 Subject: [PATCH 01/10] fix: Stop writing query params to static endpoints --- Runtime/Game/Requests/PlayerRequest.cs | 4 ++-- Runtime/Game/Requests/ReportRequets.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Runtime/Game/Requests/PlayerRequest.cs b/Runtime/Game/Requests/PlayerRequest.cs index ea6ce3621..d23f54d4b 100644 --- a/Runtime/Game/Requests/PlayerRequest.cs +++ b/Runtime/Game/Requests/PlayerRequest.cs @@ -270,7 +270,7 @@ public static void LookupPlayerNames(string forPlayerWithUlid, string idType, st queryParams.Add(idType, identifier); } - LootLockerServerRequest.CallAPI(forPlayerWithUlid, endPoint.endPoint += queryParams.Build(), endPoint.httpMethod, null, onComplete: (serverResponse) => { LootLockerResponse.Deserialize(onComplete, serverResponse); }); + LootLockerServerRequest.CallAPI(forPlayerWithUlid, endPoint.endPoint + queryParams.Build(), endPoint.httpMethod, null, onComplete: (serverResponse) => { LootLockerResponse.Deserialize(onComplete, serverResponse); }); } public static void LookupPlayer1stPartyPlatformIDs(string forPlayerWithUlid, LookupPlayer1stPartyPlatformIDsRequest lookupPlayer1stPartyPlatformIDsRequest, Action onComplete) @@ -289,7 +289,7 @@ public static void LookupPlayer1stPartyPlatformIDs(string forPlayerWithUlid, Loo queryParams.Add("player_public_uid", playerPublicUID); } - LootLockerServerRequest.CallAPI(forPlayerWithUlid, endPoint.endPoint += queryParams.Build(), endPoint.httpMethod, null, onComplete: (serverResponse) => { LootLockerResponse.Deserialize(onComplete, serverResponse); }); + LootLockerServerRequest.CallAPI(forPlayerWithUlid, endPoint.endPoint + queryParams.Build(), endPoint.httpMethod, null, onComplete: (serverResponse) => { LootLockerResponse.Deserialize(onComplete, serverResponse); }); } } } diff --git a/Runtime/Game/Requests/ReportRequets.cs b/Runtime/Game/Requests/ReportRequets.cs index e14a9bfae..54ea3e8ad 100644 --- a/Runtime/Game/Requests/ReportRequets.cs +++ b/Runtime/Game/Requests/ReportRequets.cs @@ -115,7 +115,7 @@ public static void GetRemovedUGCForPlayer(string forPlayerWithUlid, GetRemovedUG queryParams.Add("since", input.Since); } - LootLockerServerRequest.CallAPI(forPlayerWithUlid, endPoint.endPoint += queryParams.Build(), endPoint.httpMethod, null, (serverResponse) => { LootLockerResponse.Deserialize(onComplete, serverResponse); }); + LootLockerServerRequest.CallAPI(forPlayerWithUlid, endPoint.endPoint + queryParams.Build(), endPoint.httpMethod, null, (serverResponse) => { LootLockerResponse.Deserialize(onComplete, serverResponse); }); } public static void CreatePlayerReport(string forPlayerWithUlid, ReportsCreatePlayerRequest data, Action onComplete) From 457de358ae1704b4af6248d151ffc9f2b419eebb Mon Sep 17 00:00:00 2001 From: JohannesLoot <97440747+JohannesLoot@users.noreply.github.com> Date: Thu, 23 Oct 2025 16:06:50 +0200 Subject: [PATCH 02/10] Fix placeholder format in listCatalogItemsByKey endpoint --- Runtime/Client/LootLockerEndPoints.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Runtime/Client/LootLockerEndPoints.cs b/Runtime/Client/LootLockerEndPoints.cs index 1dcea118c..08b23bd18 100644 --- a/Runtime/Client/LootLockerEndPoints.cs +++ b/Runtime/Client/LootLockerEndPoints.cs @@ -274,7 +274,7 @@ public class LootLockerEndPoints [Header("Catalogs")] public static EndPointClass listCatalogs = new EndPointClass("catalogs", LootLockerHTTPMethod.GET); public static EndPointClass deprecatedListCatalogItemsByKey = new EndPointClass("catalog/key/{0}/prices", LootLockerHTTPMethod.GET); - public static EndPointClass listCatalogItemsByKey = new EndPointClass("catalogs/inspired-ibex/v1/catalog/key/{key}/list", LootLockerHTTPMethod.GET); + public static EndPointClass listCatalogItemsByKey = new EndPointClass("catalogs/inspired-ibex/v1/catalog/key/{0}/list", LootLockerHTTPMethod.GET); // Misc [Header("Misc")] From cc60e98638668eb59cf7973ad48717f738dbb28e Mon Sep 17 00:00:00 2001 From: Erik Bylund Date: Fri, 17 Oct 2025 13:16:09 +0200 Subject: [PATCH 03/10] fix: Set the first ulid being used each play session as the default for requests --- Runtime/Client/LootLockerStateData.cs | 9 +++- .../PlayMode/MultiUserTests.cs | 51 ++++++++++++++++++- 2 files changed, 58 insertions(+), 2 deletions(-) diff --git a/Runtime/Client/LootLockerStateData.cs b/Runtime/Client/LootLockerStateData.cs index 6548b7895..e81168150 100644 --- a/Runtime/Client/LootLockerStateData.cs +++ b/Runtime/Client/LootLockerStateData.cs @@ -195,6 +195,9 @@ public static LootLockerPlayerData GetStateForPlayerOrDefaultStateOrEmpty(string } string playerULIDToGetDataFor = string.IsNullOrEmpty(playerULID) ? ActiveMetaData.DefaultPlayer : playerULID; + // Make this player the default for requests if there is no default yet or if the current default is not currently active + bool shouldBeMadeDefault = ActivePlayerData.Count == 0 && !playerULIDToGetDataFor.Equals(ActiveMetaData.DefaultPlayer, StringComparison.OrdinalIgnoreCase); + if (ActivePlayerData.TryGetValue(playerULIDToGetDataFor, out var data)) { return data; @@ -204,6 +207,10 @@ public static LootLockerPlayerData GetStateForPlayerOrDefaultStateOrEmpty(string { if (ActivePlayerData.TryGetValue(playerULIDToGetDataFor, out var data2)) { + if (shouldBeMadeDefault) + { + SetDefaultPlayerULID(data2.ULID); + } return data2; } } @@ -264,7 +271,7 @@ public static bool SetPlayerData(LootLockerPlayerData updatedPlayerData) { ActiveMetaData.WhiteLabelEmailToPlayerUlidMap[updatedPlayerData.WhiteLabelEmail] = updatedPlayerData.ULID; } - if (string.IsNullOrEmpty(ActiveMetaData.DefaultPlayer)) + if (string.IsNullOrEmpty(ActiveMetaData.DefaultPlayer) || !ActivePlayerData.ContainsKey(ActiveMetaData.DefaultPlayer)) { SetDefaultPlayerULID(updatedPlayerData.ULID); } diff --git a/Tests/LootLockerTests/PlayMode/MultiUserTests.cs b/Tests/LootLockerTests/PlayMode/MultiUserTests.cs index b8a9b424f..fe2517281 100644 --- a/Tests/LootLockerTests/PlayMode/MultiUserTests.cs +++ b/Tests/LootLockerTests/PlayMode/MultiUserTests.cs @@ -806,7 +806,7 @@ public IEnumerator MultiUser_SetPlayerDataWhenPlayerCachesExistButNoPlayersAreAc // When bool loginCompleted = false; - LootLockerSDKManager.StartGuestSession(response => + LootLockerSDKManager.StartGuestSession("completely-novel-identifier", response => { ulids.Add(response.player_ulid); loginCompleted = true; @@ -826,6 +826,55 @@ public IEnumerator MultiUser_SetPlayerDataWhenPlayerCachesExistButNoPlayersAreAc yield return null; } + + [UnityTest, Category("LootLocker"), Category("LootLockerCI"), Category("LootLockerCIFast")] + public IEnumerator MultiUser_GetPlayerDataWhenPlayerCachesExistButNoPlayersAreActive_GetsPlayerAndSetsDefault() + { + // Setup Succeeded + Assert.IsFalse(SetupFailed, "Setup did not succeed"); + + // Given + List ulids = new List(); + int guestUsersToCreate = 3; + for (int i = 0; i < guestUsersToCreate; i++) + { + bool guestLoginCompleted = false; + LootLockerSDKManager.StartGuestSession(response => + { + ulids.Add(response.player_ulid); + guestLoginCompleted = true; + }); + yield return new WaitUntil(() => guestLoginCompleted); + } + + foreach (var ulid in ulids) + { + LootLockerStateData.SetPlayerULIDToInactive(ulid); + } + + // When + bool pingCompleted = false; + string pingUlid = null; + LootLockerSDKManager.Ping(response => + { + pingUlid = response.requestContext.player_ulid; + pingCompleted = true; + }, ulids[ulids.Count - 1]); + yield return new WaitUntil(() => pingCompleted); + + // Then + int postPingActivePlayerCount = LootLockerStateData.GetActivePlayerULIDs().Count; + var postPingDefaultPlayerPlayerData = LootLockerStateData.GetStateForPlayerOrDefaultStateOrEmpty(null); + var postPingDefaultPlayerUlid = LootLockerStateData.GetDefaultPlayerULID(); + + Assert.AreEqual(1, postPingActivePlayerCount); + Assert.IsNotNull(postPingDefaultPlayerUlid); + Assert.IsNotNull(postPingDefaultPlayerPlayerData); + Assert.AreEqual(ulids[ulids.Count - 1], postPingDefaultPlayerUlid); + Assert.AreEqual(ulids[ulids.Count - 1], postPingDefaultPlayerPlayerData.ULID); + + yield return null; + } [UnityTest, Category("LootLocker"), Category("LootLockerCI"), Category("LootLockerCIFast")] public IEnumerator MultiUser_GetPlayerUlidFromWLEmailWhenPlayerIsCached_ReturnsCorrectULID() From 781d939ad57b22536923d0c3600154d7e4c0efb2 Mon Sep 17 00:00:00 2001 From: Erik Bylund Date: Fri, 24 Oct 2025 07:55:16 +0200 Subject: [PATCH 04/10] fix: Clarify that PSN and Xbox session refreshing does not work --- Runtime/Client/LootLockerHTTPClient.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Runtime/Client/LootLockerHTTPClient.cs b/Runtime/Client/LootLockerHTTPClient.cs index d00519ab9..96a9ff8d9 100644 --- a/Runtime/Client/LootLockerHTTPClient.cs +++ b/Runtime/Client/LootLockerHTTPClient.cs @@ -647,8 +647,6 @@ private IEnumerator RefreshSession(string refreshForPlayerUlid, string forExecut }, refreshForPlayerUlid); } break; - case LL_AuthPlatforms.PlayStationNetwork: - case LL_AuthPlatforms.XboxOne: case LL_AuthPlatforms.AmazonLuna: { LootLockerSDKManager.StartAmazonLunaSession(playerData.Identifier, (response) => @@ -658,6 +656,8 @@ private IEnumerator RefreshSession(string refreshForPlayerUlid, string forExecut }, playerData.SessionOptionals); } break; + case LL_AuthPlatforms.PlayStationNetwork: + case LL_AuthPlatforms.XboxOne: case LL_AuthPlatforms.NintendoSwitch: case LL_AuthPlatforms.Steam: { From 3e881352efa06a5bb04a94e068b4a1ef03ff1b7f Mon Sep 17 00:00:00 2001 From: Erik Bylund Date: Fri, 24 Oct 2025 08:58:27 +0200 Subject: [PATCH 05/10] feat: Add player lookups for epic and google play games --- Runtime/Game/LootLockerSDKManager.cs | 34 ++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/Runtime/Game/LootLockerSDKManager.cs b/Runtime/Game/LootLockerSDKManager.cs index 634e5e958..d3243be9e 100644 --- a/Runtime/Game/LootLockerSDKManager.cs +++ b/Runtime/Game/LootLockerSDKManager.cs @@ -3242,6 +3242,40 @@ public static void LookupPlayerNamesByXboxIds(string[] xboxIds, Action + /// Get player names and important ids of a set of players from their last active platform by Epic Games ID's + /// + /// A list of multiple player Epic Games ID's + /// onComplete Action for handling the response of type PlayerNameLookupResponse + /// Optional : Execute the request for the specified player. If not supplied, the default player will be used. + public static void LookupPlayerNamesByEpicGamesIds(string[] epicGamesIds, Action onComplete, string forPlayerWithUlid = null) + { + if (!CheckInitialized(false, forPlayerWithUlid)) + { + onComplete?.Invoke(LootLockerResponseFactory.SDKNotInitializedError(forPlayerWithUlid)); + return; + } + + LootLockerAPIManager.LookupPlayerNames(forPlayerWithUlid, "epic_games_id", epicGamesIds, onComplete); + } + + /// + /// Get player names and important ids of a set of players from their last active platform by Google Play Games ID's + /// + /// A list of multiple player Google Play Games ID's + /// onComplete Action for handling the response of type PlayerNameLookupResponse + /// Optional : Execute the request for the specified player. If not supplied, the default player will be used. + public static void LookupPlayerNamesByGooglePlayGamesIds(string[] googlePlayGamesIds, Action onComplete, string forPlayerWithUlid = null) + { + if (!CheckInitialized(false, forPlayerWithUlid)) + { + onComplete?.Invoke(LootLockerResponseFactory.SDKNotInitializedError(forPlayerWithUlid)); + return; + } + + LootLockerAPIManager.LookupPlayerNames(forPlayerWithUlid, "google_play_games_id", googlePlayGamesIds, onComplete); + } + /// /// Mark the logged in player for deletion. After 30 days the player will be deleted from the system. /// From 0e5b9559e84636ced8a51ed3539ff3dd122a814b Mon Sep 17 00:00:00 2001 From: Erik Bylund Date: Fri, 24 Oct 2025 08:34:28 +0200 Subject: [PATCH 06/10] fix: Handle exceptions in callbacks gracefully --- Runtime/Client/LootLockerHTTPClient.cs | 4 +++- Runtime/Client/LootLockerHttpRequestData.cs | 9 ++++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/Runtime/Client/LootLockerHTTPClient.cs b/Runtime/Client/LootLockerHTTPClient.cs index 96a9ff8d9..8ef603188 100644 --- a/Runtime/Client/LootLockerHTTPClient.cs +++ b/Runtime/Client/LootLockerHTTPClient.cs @@ -566,8 +566,10 @@ private void CallListenersAndMarkDone(LootLockerHTTPExecutionQueueItem execution executionItem.Done = true; response.requestContext = new LootLockerRequestContext(executionItem.RequestData.ForPlayerWithUlid, executionItem.RequestData.RequestStartTime); executionItem.Response = response; + if(!CompletedRequestIDs.Contains(executionItem.RequestData.RequestId)) { + CompletedRequestIDs.Add(executionItem.RequestData.RequestId); + } executionItem.RequestData.CallListenersWithResult(response); - CompletedRequestIDs.Add(executionItem.RequestData.RequestId); } private IEnumerator RefreshSession(string refreshForPlayerUlid, string forExecutionItemId, Action onSessionRefreshedCallback) diff --git a/Runtime/Client/LootLockerHttpRequestData.cs b/Runtime/Client/LootLockerHttpRequestData.cs index 51d4976b2..4bc2cb99a 100644 --- a/Runtime/Client/LootLockerHttpRequestData.cs +++ b/Runtime/Client/LootLockerHttpRequestData.cs @@ -87,7 +87,14 @@ public void CallListenersWithResult(LootLockerResponse response) { foreach(var listener in Listeners) { - listener?.Invoke(response); + try + { + listener?.Invoke(response); + } + catch (Exception e) + { + LootLockerLogger.Log($"Exception thrown in HTTP request listener for request id {RequestId}. Exception was: {e}.", LootLockerLogger.LogLevel.Error); + } } HaveListenersBeenInvoked = true; } From db61d0a56cf63041663d8ae078be991f45b26e23 Mon Sep 17 00:00:00 2001 From: Erik Bylund Date: Fri, 24 Oct 2025 08:50:58 +0200 Subject: [PATCH 07/10] fix: Handle FormatExceptions when adding path parameters --- Runtime/Client/EndPointClass.cs | 36 +++++++++++++++++++++++++++++---- 1 file changed, 32 insertions(+), 4 deletions(-) diff --git a/Runtime/Client/EndPointClass.cs b/Runtime/Client/EndPointClass.cs index 0aab85fc5..9f86ae7fd 100644 --- a/Runtime/Client/EndPointClass.cs +++ b/Runtime/Client/EndPointClass.cs @@ -27,22 +27,50 @@ public EndPointClass(string endPoint, LootLockerHTTPMethod httpMethod, LootLocke public string WithPathParameter(object arg0) { - return string.Format(endPoint, WebUtility.UrlEncode(arg0.ToString())); + try { + return string.Format(endPoint, WebUtility.UrlEncode(arg0.ToString())); + } + catch (FormatException e) + { + LootLockerLogger.Log($"Error formatting endpoint \"{endPoint}\" with path parameter \"{WebUtility.UrlEncode(arg0.ToString())}\": {e}", LootLockerLogger.LogLevel.Error); + return endPoint; + } } public string WithPathParameters(object arg0, object arg1) { - return string.Format(endPoint, WebUtility.UrlEncode(arg0.ToString()), WebUtility.UrlEncode(arg1.ToString())); + try { + return string.Format(endPoint, WebUtility.UrlEncode(arg0.ToString()), WebUtility.UrlEncode(arg1.ToString())); + } + catch (FormatException e) + { + LootLockerLogger.Log($"Error formatting endpoint \"{endPoint}\" with path parameters \"{WebUtility.UrlEncode(arg0.ToString())}\", \"{WebUtility.UrlEncode(arg1.ToString())}\": {e}", LootLockerLogger.LogLevel.Error); + return endPoint; + } } public string WithPathParameters(object arg0, object arg1, object arg2) { - return string.Format(endPoint, WebUtility.UrlEncode(arg0.ToString()), WebUtility.UrlEncode(arg1.ToString()), WebUtility.UrlEncode(arg2.ToString())); + try { + return string.Format(endPoint, WebUtility.UrlEncode(arg0.ToString()), WebUtility.UrlEncode(arg1.ToString()), WebUtility.UrlEncode(arg2.ToString())); + } + catch (FormatException e) + { + LootLockerLogger.Log($"Error formatting endpoint \"{endPoint}\" with path parameters \"{WebUtility.UrlEncode(arg0.ToString())}\", \"{WebUtility.UrlEncode(arg1.ToString())}\", \"{WebUtility.UrlEncode(arg2.ToString())}\": {e}", LootLockerLogger.LogLevel.Error); + return endPoint; + } } public string WithPathParameters(object arg0, object arg1, object arg2, object arg3) { - return string.Format(endPoint, WebUtility.UrlEncode(arg0.ToString()), WebUtility.UrlEncode(arg1.ToString()), WebUtility.UrlEncode(arg2.ToString()), WebUtility.UrlEncode(arg3.ToString())); + try { + return string.Format(endPoint, WebUtility.UrlEncode(arg0.ToString()), WebUtility.UrlEncode(arg1.ToString()), WebUtility.UrlEncode(arg2.ToString()), WebUtility.UrlEncode(arg3.ToString())); + } + catch (FormatException e) + { + LootLockerLogger.Log($"Error formatting endpoint \"{endPoint}\" with path parameters \"{WebUtility.UrlEncode(arg0.ToString())}\", \"{WebUtility.UrlEncode(arg1.ToString())}\", \"{WebUtility.UrlEncode(arg2.ToString())}\", \"{WebUtility.UrlEncode(arg3.ToString())}\": {e}", LootLockerLogger.LogLevel.Error); + return endPoint; + } } } } From 457bb2fc38998a9583fec5b290a28302172ee96c Mon Sep 17 00:00:00 2001 From: Erik Bylund Date: Fri, 24 Oct 2025 08:51:19 +0200 Subject: [PATCH 08/10] fix: Handle null keys in Query Parameter Building --- Runtime/Client/EndPointClass.cs | 16 ++++++++-------- Runtime/Client/LootLockerHTTPClient.cs | 3 ++- .../Game/Utilities/LootLockerHttpUtilities.cs | 2 +- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/Runtime/Client/EndPointClass.cs b/Runtime/Client/EndPointClass.cs index 9f86ae7fd..f266daf04 100644 --- a/Runtime/Client/EndPointClass.cs +++ b/Runtime/Client/EndPointClass.cs @@ -28,11 +28,11 @@ public EndPointClass(string endPoint, LootLockerHTTPMethod httpMethod, LootLocke public string WithPathParameter(object arg0) { try { - return string.Format(endPoint, WebUtility.UrlEncode(arg0.ToString())); + return string.Format(endPoint, WebUtility.UrlEncode(arg0?.ToString() ?? "null")); } catch (FormatException e) { - LootLockerLogger.Log($"Error formatting endpoint \"{endPoint}\" with path parameter \"{WebUtility.UrlEncode(arg0.ToString())}\": {e}", LootLockerLogger.LogLevel.Error); + LootLockerLogger.Log($"Error formatting endpoint \"{endPoint}\" with path parameter \"{WebUtility.UrlEncode(arg0?.ToString() ?? "null")}\": {e}", LootLockerLogger.LogLevel.Error); return endPoint; } } @@ -40,11 +40,11 @@ public string WithPathParameter(object arg0) public string WithPathParameters(object arg0, object arg1) { try { - return string.Format(endPoint, WebUtility.UrlEncode(arg0.ToString()), WebUtility.UrlEncode(arg1.ToString())); + return string.Format(endPoint, WebUtility.UrlEncode(arg0?.ToString() ?? "null"), WebUtility.UrlEncode(arg1?.ToString() ?? "null")); } catch (FormatException e) { - LootLockerLogger.Log($"Error formatting endpoint \"{endPoint}\" with path parameters \"{WebUtility.UrlEncode(arg0.ToString())}\", \"{WebUtility.UrlEncode(arg1.ToString())}\": {e}", LootLockerLogger.LogLevel.Error); + LootLockerLogger.Log($"Error formatting endpoint \"{endPoint}\" with path parameters \"{WebUtility.UrlEncode(arg0?.ToString() ?? "null")}\", \"{WebUtility.UrlEncode(arg1?.ToString() ?? "null")}\": {e}", LootLockerLogger.LogLevel.Error); return endPoint; } } @@ -52,11 +52,11 @@ public string WithPathParameters(object arg0, object arg1) public string WithPathParameters(object arg0, object arg1, object arg2) { try { - return string.Format(endPoint, WebUtility.UrlEncode(arg0.ToString()), WebUtility.UrlEncode(arg1.ToString()), WebUtility.UrlEncode(arg2.ToString())); + return string.Format(endPoint, WebUtility.UrlEncode(arg0?.ToString() ?? "null"), WebUtility.UrlEncode(arg1?.ToString() ?? "null"), WebUtility.UrlEncode(arg2?.ToString() ?? "null")); } catch (FormatException e) { - LootLockerLogger.Log($"Error formatting endpoint \"{endPoint}\" with path parameters \"{WebUtility.UrlEncode(arg0.ToString())}\", \"{WebUtility.UrlEncode(arg1.ToString())}\", \"{WebUtility.UrlEncode(arg2.ToString())}\": {e}", LootLockerLogger.LogLevel.Error); + LootLockerLogger.Log($"Error formatting endpoint \"{endPoint}\" with path parameters \"{WebUtility.UrlEncode(arg0?.ToString() ?? "null")}\", \"{WebUtility.UrlEncode(arg1?.ToString() ?? "null")}\", \"{WebUtility.UrlEncode(arg2?.ToString() ?? "null")}\": {e}", LootLockerLogger.LogLevel.Error); return endPoint; } } @@ -64,11 +64,11 @@ public string WithPathParameters(object arg0, object arg1, object arg2) public string WithPathParameters(object arg0, object arg1, object arg2, object arg3) { try { - return string.Format(endPoint, WebUtility.UrlEncode(arg0.ToString()), WebUtility.UrlEncode(arg1.ToString()), WebUtility.UrlEncode(arg2.ToString()), WebUtility.UrlEncode(arg3.ToString())); + return string.Format(endPoint, WebUtility.UrlEncode(arg0?.ToString() ?? "null"), WebUtility.UrlEncode(arg1?.ToString() ?? "null"), WebUtility.UrlEncode(arg2?.ToString() ?? "null"), WebUtility.UrlEncode(arg3?.ToString() ?? "null")); } catch (FormatException e) { - LootLockerLogger.Log($"Error formatting endpoint \"{endPoint}\" with path parameters \"{WebUtility.UrlEncode(arg0.ToString())}\", \"{WebUtility.UrlEncode(arg1.ToString())}\", \"{WebUtility.UrlEncode(arg2.ToString())}\", \"{WebUtility.UrlEncode(arg3.ToString())}\": {e}", LootLockerLogger.LogLevel.Error); + LootLockerLogger.Log($"Error formatting endpoint \"{endPoint}\" with path parameters \"{WebUtility.UrlEncode(arg0?.ToString() ?? "null")}\", \"{WebUtility.UrlEncode(arg1?.ToString() ?? "null")}\", \"{WebUtility.UrlEncode(arg2?.ToString() ?? "null")}\", \"{WebUtility.UrlEncode(arg3?.ToString() ?? "null")}\": {e}", LootLockerLogger.LogLevel.Error); return endPoint; } } diff --git a/Runtime/Client/LootLockerHTTPClient.cs b/Runtime/Client/LootLockerHTTPClient.cs index 8ef603188..5157db3f8 100644 --- a/Runtime/Client/LootLockerHTTPClient.cs +++ b/Runtime/Client/LootLockerHTTPClient.cs @@ -566,7 +566,8 @@ private void CallListenersAndMarkDone(LootLockerHTTPExecutionQueueItem execution executionItem.Done = true; response.requestContext = new LootLockerRequestContext(executionItem.RequestData.ForPlayerWithUlid, executionItem.RequestData.RequestStartTime); executionItem.Response = response; - if(!CompletedRequestIDs.Contains(executionItem.RequestData.RequestId)) { + if (!CompletedRequestIDs.Contains(executionItem.RequestData.RequestId)) + { CompletedRequestIDs.Add(executionItem.RequestData.RequestId); } executionItem.RequestData.CallListenersWithResult(response); diff --git a/Runtime/Game/Utilities/LootLockerHttpUtilities.cs b/Runtime/Game/Utilities/LootLockerHttpUtilities.cs index 5881d8a60..1f2a9c97a 100644 --- a/Runtime/Game/Utilities/LootLockerHttpUtilities.cs +++ b/Runtime/Game/Utilities/LootLockerHttpUtilities.cs @@ -59,7 +59,7 @@ public string Build() foreach (KeyValuePair pair in _queryParams) { - if (string.IsNullOrEmpty(pair.Value)) + if (string.IsNullOrEmpty(pair.Key) || string.IsNullOrEmpty(pair.Value)) continue; if (query.Length > 1) From ee340cd6ca6b0cb2d1638318b09ed9a64711b35b Mon Sep 17 00:00:00 2001 From: Erik Bylund Date: Fri, 24 Oct 2025 15:28:07 +0200 Subject: [PATCH 09/10] fix: Fix Path Parameters in hero endpoints --- Runtime/Game/LootLockerSDKManager.cs | 18 ++++------ Runtime/Game/Requests/HeroRequest.cs | 50 ++++++++-------------------- 2 files changed, 21 insertions(+), 47 deletions(-) diff --git a/Runtime/Game/LootLockerSDKManager.cs b/Runtime/Game/LootLockerSDKManager.cs index d3243be9e..3ac2a6499 100644 --- a/Runtime/Game/LootLockerSDKManager.cs +++ b/Runtime/Game/LootLockerSDKManager.cs @@ -4059,9 +4059,10 @@ public static void GetHeroInventory(int heroID, Action /// List the loadout of the specified hero that the current player owns /// + /// HeroID Id of the hero /// onComplete Action for handling the response of type LootLockerHeroLoadoutResponse /// Optional : Execute the request for the specified player. If not supplied, the default player will be used. - public static void GetHeroLoadout(Action onComplete, string forPlayerWithUlid = null) + public static void GetHeroLoadout(int HeroID, Action onComplete, string forPlayerWithUlid = null) { if (!CheckInitialized(false, forPlayerWithUlid)) { @@ -4069,7 +4070,7 @@ public static void GetHeroLoadout(Action onComple return; } - LootLockerAPIManager.GetHeroLoadout(forPlayerWithUlid, onComplete); + LootLockerAPIManager.GetHeroLoadout(forPlayerWithUlid, HeroID, onComplete); } /// @@ -4110,7 +4111,7 @@ public static void AddAssetToHeroLoadout(int heroID, int assetInstanceID, Action data.hero_id = heroID; - LootLockerAPIManager.AddAssetToHeroLoadout(forPlayerWithUlid, data, onComplete); + LootLockerAPIManager.AddAssetToHeroLoadout(forPlayerWithUlid, heroID, data, onComplete); } /// @@ -4136,7 +4137,7 @@ public static void AddAssetVariationToHeroLoadout(int heroID, int assetID, int a data.asset_id = assetID; data.asset_variation_id = assetInstanceID; - LootLockerAPIManager.AddAssetVariationToHeroLoadout(forPlayerWithUlid, data, onComplete); + LootLockerAPIManager.AddAssetVariationToHeroLoadout(forPlayerWithUlid, heroID, data, onComplete); } /// @@ -4147,7 +4148,7 @@ public static void AddAssetVariationToHeroLoadout(int heroID, int assetID, int a /// Id of the hero /// onComplete Action for handling the response of type LootLockerHeroLoadoutResponse /// Optional : Execute the request for the specified player. If not supplied, the default player will be used. - public static void RemoveAssetFromHeroLoadout(string assetID, string heroID, Action onComplete, string forPlayerWithUlid = null) + public static void RemoveAssetFromHeroLoadout(int assetID, int heroID, Action onComplete, string forPlayerWithUlid = null) { if (!CheckInitialized(false, forPlayerWithUlid)) { @@ -4155,12 +4156,7 @@ public static void RemoveAssetFromHeroLoadout(string assetID, string heroID, Act return; } - LootLockerGetRequest lootLockerGetRequest = new LootLockerGetRequest(); - - lootLockerGetRequest.getRequests.Add(assetID); - lootLockerGetRequest.getRequests.Add(heroID); - - LootLockerAPIManager.RemoveAssetFromHeroLoadout(forPlayerWithUlid, lootLockerGetRequest, onComplete); + LootLockerAPIManager.RemoveAssetFromHeroLoadout(forPlayerWithUlid, heroID, assetID, onComplete); } #endregion diff --git a/Runtime/Game/Requests/HeroRequest.cs b/Runtime/Game/Requests/HeroRequest.cs index 7199fbb40..cdd9c3cf9 100644 --- a/Runtime/Game/Requests/HeroRequest.cs +++ b/Runtime/Game/Requests/HeroRequest.cs @@ -116,10 +116,7 @@ public static void ListPlayerHeroes(string forPlayerWithUlid, Action onComplete) { EndPointClass endPoint = LootLockerEndPoints.listOtherPlayersHeroesBySteamID64; - - string getVariable = endPoint.endPoint; - - LootLockerServerRequest.CallAPI(forPlayerWithUlid, getVariable, endPoint.httpMethod, SteamID64.ToString(), (serverResponse) => { LootLockerResponse.Deserialize(onComplete, serverResponse); }); + LootLockerServerRequest.CallAPI(forPlayerWithUlid, endPoint.WithPathParameter(SteamID64.ToString()), endPoint.httpMethod, SteamID64.ToString(), (serverResponse) => { LootLockerResponse.Deserialize(onComplete, serverResponse); }); } public static void CreateHero(LootLockerCreateHeroRequest data, @@ -154,19 +151,14 @@ public static void CreateHeroWithVariation(string forPlayerWithUlid, LootLockerC public static void GetHero(string forPlayerWithUlid, int HeroID, Action onComplete) { EndPointClass endPoint = LootLockerEndPoints.getHero; - - string getVariable = endPoint.endPoint; - - LootLockerServerRequest.CallAPI(forPlayerWithUlid, getVariable, endPoint.httpMethod, HeroID.ToString(), (serverResponse) => { LootLockerResponse.Deserialize(onComplete, serverResponse); }); + LootLockerServerRequest.CallAPI(forPlayerWithUlid, endPoint.WithPathParameter(HeroID.ToString()), endPoint.httpMethod, HeroID.ToString(), (serverResponse) => { LootLockerResponse.Deserialize(onComplete, serverResponse); }); } public static void GetOtherPlayersDefaultHeroBySteamID64(string forPlayerWithUlid, int steamID64, Action onComplete) { EndPointClass endPoint = LootLockerEndPoints.getOtherPlayersDefaultHeroBySteamID64; - string getVariable = endPoint.endPoint; - - LootLockerServerRequest.CallAPI(forPlayerWithUlid, getVariable, endPoint.httpMethod, steamID64.ToString(), (serverResponse) => { LootLockerResponse.Deserialize(onComplete, serverResponse); }); + LootLockerServerRequest.CallAPI(forPlayerWithUlid, endPoint.WithPathParameter(steamID64.ToString()), endPoint.httpMethod, steamID64.ToString(), (serverResponse) => { LootLockerResponse.Deserialize(onComplete, serverResponse); }); } public static void UpdateHero(string forPlayerWithUlid, LootLockerGetRequest lootLockerGetRequest, LootLockerUpdateHeroRequest data, Action onComplete) @@ -189,39 +181,31 @@ public static void DeleteHero(string forPlayerWithUlid, int HeroID, Action { LootLockerResponse.Deserialize(onComplete, serverResponse); }); + LootLockerServerRequest.CallAPI(forPlayerWithUlid, endPoint.WithPathParameter(HeroID.ToString()), endPoint.httpMethod, HeroID.ToString(), (serverResponse) => { LootLockerResponse.Deserialize(onComplete, serverResponse); }); } public static void GetHeroInventory(string forPlayerWithUlid, int HeroID, Action onComplete) { EndPointClass endPoint = LootLockerEndPoints.getHeroInventory; - string getVariable = endPoint.endPoint; - - LootLockerServerRequest.CallAPI(forPlayerWithUlid, getVariable, endPoint.httpMethod, HeroID.ToString(), (serverResponse) => { LootLockerResponse.Deserialize(onComplete, serverResponse); }); + LootLockerServerRequest.CallAPI(forPlayerWithUlid, endPoint.WithPathParameter(HeroID.ToString()), endPoint.httpMethod, HeroID.ToString(), (serverResponse) => { LootLockerResponse.Deserialize(onComplete, serverResponse); }); } - public static void GetHeroLoadout(string forPlayerWithUlid, Action onComplete) + public static void GetHeroLoadout(string forPlayerWithUlid, int HeroID, Action onComplete) { EndPointClass endPoint = LootLockerEndPoints.getHeroLoadout; - string getVariable = endPoint.endPoint; - - LootLockerServerRequest.CallAPI(forPlayerWithUlid, getVariable, endPoint.httpMethod, null, (serverResponse) => { LootLockerResponse.Deserialize(onComplete, serverResponse); }); + LootLockerServerRequest.CallAPI(forPlayerWithUlid, endPoint.WithPathParameter(HeroID.ToString()), endPoint.httpMethod, null, (serverResponse) => { LootLockerResponse.Deserialize(onComplete, serverResponse); }); } public static void GetOtherPlayersHeroLoadout(string forPlayerWithUlid, int HeroID, Action onComplete) { EndPointClass endPoint = LootLockerEndPoints.getOtherPlayersHeroLoadout; - string getVariable = endPoint.endPoint; - - LootLockerServerRequest.CallAPI(forPlayerWithUlid, getVariable, endPoint.httpMethod, HeroID.ToString(), (serverResponse) => { LootLockerResponse.Deserialize(onComplete, serverResponse); }); + LootLockerServerRequest.CallAPI(forPlayerWithUlid, endPoint.WithPathParameter(HeroID.ToString()), endPoint.httpMethod, HeroID.ToString(), (serverResponse) => { LootLockerResponse.Deserialize(onComplete, serverResponse); }); } - public static void AddAssetToHeroLoadout(string forPlayerWithUlid, LootLockerAddAssetToHeroLoadoutRequest data, Action onComplete) + public static void AddAssetToHeroLoadout(string forPlayerWithUlid, int HeroID, LootLockerAddAssetToHeroLoadoutRequest data, Action onComplete) { EndPointClass endPoint = LootLockerEndPoints.addAssetToHeroLoadout; @@ -232,12 +216,10 @@ public static void AddAssetToHeroLoadout(string forPlayerWithUlid, LootLockerAdd } string json = LootLockerJson.SerializeObject(data); - string getVariable = endPoint.endPoint; - - LootLockerServerRequest.CallAPI(forPlayerWithUlid, getVariable, endPoint.httpMethod, json, (serverResponse) => { LootLockerResponse.Deserialize(onComplete, serverResponse); }); + LootLockerServerRequest.CallAPI(forPlayerWithUlid, endPoint.WithPathParameter(HeroID.ToString()), endPoint.httpMethod, json, (serverResponse) => { LootLockerResponse.Deserialize(onComplete, serverResponse); }); } - public static void AddAssetVariationToHeroLoadout(string forPlayerWithUlid, LootLockerAddAssetVariationToHeroLoadoutRequest data, Action onComplete) + public static void AddAssetVariationToHeroLoadout(string forPlayerWithUlid, int HeroID, LootLockerAddAssetVariationToHeroLoadoutRequest data, Action onComplete) { EndPointClass endPoint = LootLockerEndPoints.addAssetVariationToHeroLoadout; @@ -248,18 +230,14 @@ public static void AddAssetVariationToHeroLoadout(string forPlayerWithUlid, Loot } string json = LootLockerJson.SerializeObject(data); - string getVariable = endPoint.endPoint; - - LootLockerServerRequest.CallAPI(forPlayerWithUlid, getVariable, endPoint.httpMethod, json, (serverResponse) => { LootLockerResponse.Deserialize(onComplete, serverResponse); }); + LootLockerServerRequest.CallAPI(forPlayerWithUlid, endPoint.WithPathParameter(HeroID.ToString()), endPoint.httpMethod, json, (serverResponse) => { LootLockerResponse.Deserialize(onComplete, serverResponse); }); } - public static void RemoveAssetFromHeroLoadout(string forPlayerWithUlid, LootLockerGetRequest lootLockerGetRequest, Action onComplete) + public static void RemoveAssetFromHeroLoadout(string forPlayerWithUlid, int HeroID, int assetID, Action onComplete) { EndPointClass endPoint = LootLockerEndPoints.removeAssetFromHeroLoadout; - string getVariable = endPoint.WithPathParameters(lootLockerGetRequest.getRequests[0], lootLockerGetRequest.getRequests[1]); - - LootLockerServerRequest.CallAPI(forPlayerWithUlid, getVariable, endPoint.httpMethod, null, (serverResponse) => { LootLockerResponse.Deserialize(onComplete, serverResponse); }); ; + LootLockerServerRequest.CallAPI(forPlayerWithUlid, endPoint.WithPathParameters(assetID.ToString(), HeroID.ToString()), endPoint.httpMethod, null, (serverResponse) => { LootLockerResponse.Deserialize(onComplete, serverResponse); }); } From 8ad866bf0693f554d311e9ae4a601e411b3ecc5c Mon Sep 17 00:00:00 2001 From: Erik Bylund Date: Fri, 31 Oct 2025 10:01:24 +0100 Subject: [PATCH 10/10] Bump version to 6.4.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 38b3888f8..560b3a1a1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "com.lootlocker.lootlockersdk", - "version": "6.3.0", + "version": "6.4.0", "displayName": "LootLocker", "description": "LootLocker is a game backend-as-a-service with plug and play tools to upgrade your game and give your players the best experience possible. Designed for teams of all shapes and sizes, on mobile, PC and console. From solo developers, indie teams, AAA studios, and publishers. Built with cross-platform in mind.\n\n▪ Manage your game\nSave time and upgrade your game with leaderboards, progression, and more. Completely off-the-shelf features, built to work with any game and platform.\n\n▪ Manage your content\nTake charge of your game's content on all platforms, in one place. Sort, edit and manage everything, from cosmetics to currencies, UGC to DLC. Without breaking a sweat.\n\n▪ Manage your players\nStore your players' data together in one place. Access their profile and friends list cross-platform. Manage reports, messages, refunds and gifts to keep them hooked.\n", "unity": "2019.2",