From 8ba0268f956f019438969746d925f0bb3b410764 Mon Sep 17 00:00:00 2001 From: Erik Bylund Date: Tue, 4 Nov 2025 15:46:53 +0100 Subject: [PATCH 01/13] fix: Only refresh sessions on authorized game requests --- Runtime/Client/LootLockerHTTPClient.cs | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/Runtime/Client/LootLockerHTTPClient.cs b/Runtime/Client/LootLockerHTTPClient.cs index 5157db3f..a6b07194 100644 --- a/Runtime/Client/LootLockerHTTPClient.cs +++ b/Runtime/Client/LootLockerHTTPClient.cs @@ -461,7 +461,7 @@ private HTTPExecutionQueueProcessingResult ProcessOngoingRequest(LootLockerHTTPE if (ShouldRetryRequest(executionItem.WebRequest.responseCode, executionItem.RequestData.TimesRetried) && !(executionItem.WebRequest.responseCode == 401 && !IsAuthorizedRequest(executionItem))) { - if (ShouldRefreshSession(executionItem.WebRequest.responseCode, playerData == null ? LL_AuthPlatforms.None : playerData.CurrentPlatform.Platform) && (CanRefreshUsingRefreshToken(executionItem.RequestData) || CanStartNewSessionUsingCachedAuthData(executionItem.RequestData.ForPlayerWithUlid))) + if (ShouldRefreshSession(executionItem, playerData == null ? LL_AuthPlatforms.None : playerData.CurrentPlatform.Platform) && (CanRefreshUsingRefreshToken(executionItem.RequestData) || CanStartNewSessionUsingCachedAuthData(executionItem.RequestData.ForPlayerWithUlid))) { return HTTPExecutionQueueProcessingResult.NeedsSessionRefresh; } @@ -731,14 +731,24 @@ private static bool ShouldRetryRequest(long statusCode, int timesRetried) return (statusCode == 401 || statusCode == 403 || statusCode == 502 || statusCode == 500 || statusCode == 503) && timesRetried < configuration.MaxRetries; } - private static bool ShouldRefreshSession(long statusCode, LL_AuthPlatforms platform) + private static bool ShouldRefreshSession(LootLockerHTTPExecutionQueueItem request, LL_AuthPlatforms platform) { - return (statusCode == 401 || statusCode == 403) && LootLockerConfig.current.allowTokenRefresh && !new List{ LL_AuthPlatforms.Steam, LL_AuthPlatforms.NintendoSwitch, LL_AuthPlatforms.None }.Contains(platform); + return IsAuthorizedGameRequest(request) && (request.WebRequest?.responseCode == 401 || request.WebRequest?.responseCode == 403) && LootLockerConfig.current.allowTokenRefresh && !new List{ LL_AuthPlatforms.Steam, LL_AuthPlatforms.NintendoSwitch, LL_AuthPlatforms.None }.Contains(platform); } private static bool IsAuthorizedRequest(LootLockerHTTPExecutionQueueItem request) { - return !string.IsNullOrEmpty(request.WebRequest?.GetRequestHeader("x-session-token")) || !string.IsNullOrEmpty(request.WebRequest?.GetRequestHeader("x-auth-token")); + return IsAuthorizedGameRequest(request) || IsAuthorizedAdminRequest(request); + } + + private static bool IsAuthorizedGameRequest(LootLockerHTTPExecutionQueueItem request) + { + return !string.IsNullOrEmpty(request.WebRequest?.GetRequestHeader("x-session-token")); + } + + private static bool IsAuthorizedAdminRequest(LootLockerHTTPExecutionQueueItem request) + { + return !string.IsNullOrEmpty(request.WebRequest?.GetRequestHeader("x-auth-token")); } private static bool CanRefreshUsingRefreshToken(LootLockerHTTPRequestData cachedRequest) From 469acae815ee7f55dfe6e3a8392e92039133904c Mon Sep 17 00:00:00 2001 From: Erik Bylund Date: Tue, 4 Nov 2025 10:49:38 +0100 Subject: [PATCH 02/13] fix: Add support for asset variations and rental options to catalogs --- Runtime/Game/Requests/CatalogRequests.cs | 187 ++++++++++++++++++++++- 1 file changed, 185 insertions(+), 2 deletions(-) diff --git a/Runtime/Game/Requests/CatalogRequests.cs b/Runtime/Game/Requests/CatalogRequests.cs index af1f5870..a0471d65 100644 --- a/Runtime/Game/Requests/CatalogRequests.cs +++ b/Runtime/Game/Requests/CatalogRequests.cs @@ -167,7 +167,65 @@ public override bool Equals(object obj) { return obj.GetHashCode() == GetHashCode(); } + } + + /// + /// Class to help getting asset item details including variation and rental option IDs + /// + public class LootLockerAssetItemDetailsKey + { + /// + /// The id of a catalog listing + /// + public string catalog_listing_id { get; set; } + /// + /// The id of the item + /// + public string item_id { get; set; } + /// + /// The id of the specific variation of this asset that this refers to + /// + public string variation_id { get; set; } + /// + /// The id of the specific rental option of this asset that this refers to + /// + public string rental_option_id { get; set; } + + public override int GetHashCode() + { + return catalog_listing_id.GetHashCode() + item_id.GetHashCode() + + (variation_id?.GetHashCode() ?? 0) + (rental_option_id?.GetHashCode() ?? 0); + } + public override bool Equals(object obj) + { + if (obj is LootLockerAssetItemDetailsKey other) + { + return catalog_listing_id == other.catalog_listing_id && + item_id == other.item_id && + variation_id == other.variation_id && + rental_option_id == other.rental_option_id; + } + return false; + } + + public LootLockerAssetItemDetailsKey() + { + } + + public LootLockerAssetItemDetailsKey(string catalogListingId, string itemId, string variationId, string rentalOptionId) + { + catalog_listing_id = catalogListingId; + item_id = itemId; + variation_id = variationId; + rental_option_id = rentalOptionId; + } + + public LootLockerAssetItemDetailsKey(LootLockerItemDetailsKey key) + { + catalog_listing_id = key.catalog_listing_id; + item_id = key.item_id; + } } /// @@ -257,6 +315,19 @@ public LootLockerItemDetailsKey GetItemDetailsKey() { return new LootLockerItemDetailsKey { catalog_listing_id = catalog_listing_id, item_id = id }; } + /// + /// Function to help identify asset details including variation and rental options + /// + /// The identifier for looking up asset details + public LootLockerAssetItemDetailsKey GetAssetItemDetailsKey() + { + return new LootLockerAssetItemDetailsKey { + catalog_listing_id = catalog_listing_id, + item_id = id, + variation_id = variation_id, + rental_option_id = rental_option_id + }; + } } /// @@ -480,9 +551,16 @@ public class LootLockerListCatalogPricesResponse : LootLockerResponse /// /// Lookup map for details about entities of entity type assets + /// If the asset in question has variations or rental options, those will be in the optional_asset_detail_variants structure instead /// public Dictionary asset_details { get; set; } + /// + /// This is a list of potentially matching asset details for this catalog entry, in case there are multiple variations / rental options + /// Asset Variations and Rental Options are deprecated features, this is added for backward compatibility only + /// + public Dictionary optional_asset_detail_variants { get; set; } + /// /// Lookup map for details about entities of entity type progression_points /// @@ -529,6 +607,15 @@ public void AppendCatalogItems(LootLockerListCatalogPricesResponse catalogPrices asset_details.Add(assetDetail.Key, assetDetail.Value); } + // Also append asset detail variants if they exist + if (catalogPrices.optional_asset_detail_variants != null) + { + foreach (var assetDetailVariant in catalogPrices.optional_asset_detail_variants) + { + optional_asset_detail_variants.Add(assetDetailVariant.Key, assetDetailVariant.Value); + } + } + foreach (var progressionPointDetail in catalogPrices.progression_points_details) { progression_points_details.Add(progressionPointDetail.Key, progressionPointDetail.Value); @@ -586,9 +673,22 @@ public LootLockerListCatalogPricesResponse(LootLockerResponse serverResponse) if (parsedResponse.assets_details != null && parsedResponse.assets_details.Length > 0) { asset_details = new Dictionary(); + optional_asset_detail_variants = new Dictionary(); + foreach (var detail in parsedResponse.assets_details) { - asset_details[detail.GetItemDetailsKey()] = detail; + if (detail == null) + continue; + if (!string.IsNullOrEmpty(detail.variation_id) || !string.IsNullOrEmpty(detail.rental_option_id)) + { + // Populate for backward compatibility + optional_asset_detail_variants[detail.GetAssetItemDetailsKey()] = detail; + continue; + } + else + { + asset_details[detail.GetItemDetailsKey()] = detail; + } } } @@ -641,6 +741,12 @@ public class LootLockerInlinedCatalogEntry : LootLockerCatalogEntry /// public LootLockerAssetDetails asset_details { get; set; } + /// + /// This is a list of potentially matching asset details for this catalog entry, in case there are multiple variations / rental options + /// Asset Variations and Rental Options are deprecated features, this is added for backward compatibility only + /// + public List optional_asset_detail_variants { get; set; } + /// /// Progression point details inlined for this catalog entry, will be null if the entity_kind is not progression_points /// @@ -679,6 +785,17 @@ public LootLockerInlinedCatalogEntry(LootLockerCatalogEntry entry, LootLockerLis { asset_details = catalogListing.asset_details[entry.GetItemDetailsKey()]; } + else + { + optional_asset_detail_variants = new List(); + foreach (var optionalAssetDetailVariant in catalogListing.optional_asset_detail_variants) + { + if (entry.GetItemDetailsKey() == optionalAssetDetailVariant.Value.GetItemDetailsKey()) + { + optional_asset_detail_variants.Add(optionalAssetDetailVariant.Value); + } + } + } break; case LootLockerCatalogEntryEntityKind.currency: if (catalogListing.currency_details.ContainsKey(entry.GetItemDetailsKey())) @@ -712,6 +829,7 @@ public LootLockerInlinedCatalogEntry(LootLockerCatalogEntry entry, LootLockerLis inlinedGroupDetails.id = catalogLevelGroup.id; inlinedGroupDetails.associations = catalogLevelGroup.associations; + Dictionary processedOptionalAssetDetails = new Dictionary(); foreach (var association in catalogLevelGroup.associations) { switch (association.kind) @@ -721,6 +839,18 @@ public LootLockerInlinedCatalogEntry(LootLockerCatalogEntry entry, LootLockerLis { inlinedGroupDetails.assetDetails.Add(catalogListing.asset_details[association.GetItemDetailsKey()]); } + else + { + foreach (var optionalAssetDetailVariant in catalogListing.optional_asset_detail_variants) + { + if(processedOptionalAssetDetails.ContainsKey(optionalAssetDetailVariant.Key)) + continue; + if (association.GetItemDetailsKey() == optionalAssetDetailVariant.Value.GetItemDetailsKey()) + { + inlinedGroupDetails.assetDetails.Add(optionalAssetDetailVariant.Value); + } + } + } break; case LootLockerCatalogEntryEntityKind.progression_points: if (catalogListing.progression_points_details.ContainsKey(association.GetItemDetailsKey())) @@ -789,9 +919,16 @@ public class LootLockerListCatalogPricesV2Response : LootLockerResponse /// /// Lookup map for details about entities of entity type assets + /// If the asset in question has variations or rental options, those will be in the optional_asset_detail_variants structure instead /// public Dictionary asset_details { get; set; } + /// + /// This is a list of potentially matching asset details for this catalog entry, in case there are multiple variations / rental options + /// Asset Variations and Rental Options are deprecated features, this is added for backward compatibility only + /// + public Dictionary optional_asset_detail_variants { get; set; } + /// /// Lookup map for details about entities of entity type progression_points /// @@ -844,6 +981,11 @@ public void AppendCatalogItems(LootLockerListCatalogPricesV2Response catalogPric asset_details.Add(assetDetail.Key, assetDetail.Value); } + foreach (var assetDetailVariant in catalogPrices.optional_asset_detail_variants) + { + optional_asset_detail_variants.Add(assetDetailVariant.Key, assetDetailVariant.Value); + } + foreach (var progressionPointDetail in catalogPrices.progression_points_details) { progression_points_details.Add(progressionPointDetail.Key, progressionPointDetail.Value); @@ -901,9 +1043,20 @@ public LootLockerListCatalogPricesV2Response(LootLockerResponse serverResponse) if (parsedResponse.assets_details != null && parsedResponse.assets_details.Length > 0) { asset_details = new Dictionary(); + optional_asset_detail_variants = new Dictionary(); foreach (var detail in parsedResponse.assets_details) { - asset_details[detail.GetItemDetailsKey()] = detail; + if (detail == null) + continue; + if (!string.IsNullOrEmpty(detail.variation_id) || !string.IsNullOrEmpty(detail.rental_option_id)) + { + // Populate for backward compatibility + optional_asset_detail_variants[detail.GetAssetItemDetailsKey()] = detail; + } + else + { + asset_details[detail.GetItemDetailsKey()] = detail; + } } } @@ -956,6 +1109,12 @@ public class LootLockerInlinedCatalogEntry : LootLockerCatalogEntry /// public LootLockerAssetDetails asset_details { get; set; } + /// + /// This is a list of potentially matching asset details for this catalog entry, in case there are multiple variations / rental options + /// Asset Variations and Rental Options are deprecated features, this is added for backward compatibility only + /// + public List optional_asset_detail_variants { get; set; } + /// /// Progression point details inlined for this catalog entry, will be null if the entity_kind is not progression_points /// @@ -994,6 +1153,17 @@ public LootLockerInlinedCatalogEntry(LootLockerCatalogEntry entry, LootLockerLis { asset_details = catalogListing.asset_details[entry.GetItemDetailsKey()]; } + else + { + optional_asset_detail_variants = new List(); + foreach (var optionalAssetDetailVariant in catalogListing.optional_asset_detail_variants) + { + if (entry.GetItemDetailsKey() == optionalAssetDetailVariant.Value.GetItemDetailsKey()) + { + optional_asset_detail_variants.Add(optionalAssetDetailVariant.Value); + } + } + } break; case LootLockerCatalogEntryEntityKind.currency: if (catalogListing.currency_details.ContainsKey(entry.GetItemDetailsKey())) @@ -1027,6 +1197,7 @@ public LootLockerInlinedCatalogEntry(LootLockerCatalogEntry entry, LootLockerLis inlinedGroupDetails.id = catalogLevelGroup.id; inlinedGroupDetails.associations = catalogLevelGroup.associations; + Dictionary processedOptionalAssetDetails = new Dictionary(); foreach (var association in catalogLevelGroup.associations) { switch (association.kind) @@ -1036,6 +1207,18 @@ public LootLockerInlinedCatalogEntry(LootLockerCatalogEntry entry, LootLockerLis { inlinedGroupDetails.assetDetails.Add(catalogListing.asset_details[association.GetItemDetailsKey()]); } + else + { + foreach (var optionalAssetDetailVariant in catalogListing.optional_asset_detail_variants) + { + if(processedOptionalAssetDetails.ContainsKey(optionalAssetDetailVariant.Key)) + continue; + if (association.GetItemDetailsKey() == optionalAssetDetailVariant.Value.GetItemDetailsKey()) + { + inlinedGroupDetails.assetDetails.Add(optionalAssetDetailVariant.Value); + } + } + } break; case LootLockerCatalogEntryEntityKind.progression_points: if (catalogListing.progression_points_details.ContainsKey(association.GetItemDetailsKey())) From 942e7f6756b25a20d68d171624c7b84e5c61ee7b Mon Sep 17 00:00:00 2001 From: Erik Bylund Date: Tue, 4 Nov 2025 12:02:08 +0100 Subject: [PATCH 03/13] Fixes after review --- Runtime/Game/Requests/CatalogRequests.cs | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/Runtime/Game/Requests/CatalogRequests.cs b/Runtime/Game/Requests/CatalogRequests.cs index a0471d65..cb1a6b63 100644 --- a/Runtime/Game/Requests/CatalogRequests.cs +++ b/Runtime/Game/Requests/CatalogRequests.cs @@ -610,6 +610,10 @@ public void AppendCatalogItems(LootLockerListCatalogPricesResponse catalogPrices // Also append asset detail variants if they exist if (catalogPrices.optional_asset_detail_variants != null) { + if (optional_asset_detail_variants == null) + { + optional_asset_detail_variants = new Dictionary(); + } foreach (var assetDetailVariant in catalogPrices.optional_asset_detail_variants) { optional_asset_detail_variants.Add(assetDetailVariant.Key, assetDetailVariant.Value); @@ -848,6 +852,7 @@ public LootLockerInlinedCatalogEntry(LootLockerCatalogEntry entry, LootLockerLis if (association.GetItemDetailsKey() == optionalAssetDetailVariant.Value.GetItemDetailsKey()) { inlinedGroupDetails.assetDetails.Add(optionalAssetDetailVariant.Value); + processedOptionalAssetDetails[optionalAssetDetailVariant.Key] = true; } } } @@ -981,9 +986,17 @@ public void AppendCatalogItems(LootLockerListCatalogPricesV2Response catalogPric asset_details.Add(assetDetail.Key, assetDetail.Value); } - foreach (var assetDetailVariant in catalogPrices.optional_asset_detail_variants) + // Also append asset detail variants if they exist + if (catalogPrices.optional_asset_detail_variants != null) { - optional_asset_detail_variants.Add(assetDetailVariant.Key, assetDetailVariant.Value); + if (optional_asset_detail_variants == null) + { + optional_asset_detail_variants = new Dictionary(); + } + foreach (var assetDetailVariant in catalogPrices.optional_asset_detail_variants) + { + optional_asset_detail_variants.Add(assetDetailVariant.Key, assetDetailVariant.Value); + } } foreach (var progressionPointDetail in catalogPrices.progression_points_details) @@ -1216,6 +1229,7 @@ public LootLockerInlinedCatalogEntry(LootLockerCatalogEntry entry, LootLockerLis if (association.GetItemDetailsKey() == optionalAssetDetailVariant.Value.GetItemDetailsKey()) { inlinedGroupDetails.assetDetails.Add(optionalAssetDetailVariant.Value); + processedOptionalAssetDetails[optionalAssetDetailVariant.Key] = true; } } } From c2fe9d66db4cc70bcddfeb0a75fffe7ce6be95aa Mon Sep 17 00:00:00 2001 From: Erik Bylund Date: Wed, 5 Nov 2025 11:16:49 +0100 Subject: [PATCH 04/13] feat: Add support for epic games, playstation and stripe to catalog listings --- Runtime/Game/Requests/CatalogRequests.cs | 42 ++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/Runtime/Game/Requests/CatalogRequests.cs b/Runtime/Game/Requests/CatalogRequests.cs index cb1a6b63..09cd6cdb 100644 --- a/Runtime/Game/Requests/CatalogRequests.cs +++ b/Runtime/Game/Requests/CatalogRequests.cs @@ -126,6 +126,36 @@ public class LootLockerCatalogSteamStoreListing public LootLockerCatalogSteamStoreListingPrice[] prices { get; set; } } + /// + /// + public class LootLockerCatalogStripeStoreListing + { + /// + /// The currency to use for the purchase + /// + public string currency { get; set; } + /// + /// The amount to charge in the smallest unit of the currency (e.g. cents for USD) + /// + public int amount { get; set; } + } + + public class LootLockerCatalogEpicGamesStoreListing + { + /// + /// The Epic Games audience item id associated with this listing + /// + public string audience_item_id { get; set; } + } + + public class LootLockerCatalogPlaystationStoreListing + { + /// + /// The Playstation entitlement label associated with this listing + /// + public string entitlement_label { get; set; } + } + /// /// public class LootLockerCatalogEntryListings @@ -142,6 +172,18 @@ public class LootLockerCatalogEntryListings /// The listing information (if configured) for Steam Store /// public LootLockerCatalogSteamStoreListing steam_store { get; set; } + /// + /// The listing information (if configured) for Stripe Store + /// + public LootLockerCatalogStripeStoreListing stripe_store { get; set; } + /// + /// The listing information (if configured) for Epic Games Store + /// + public LootLockerCatalogEpicGamesStoreListing epic_games_store { get; set; } + /// + /// The listing information (if configured) for Playstation Store + /// + public LootLockerCatalogPlaystationStoreListing playstation_store { get; set; } } /// From eebe04d58797bb13dc1282af7d7ae4aa32f1498a Mon Sep 17 00:00:00 2001 From: Erik Bylund Date: Wed, 5 Nov 2025 13:22:40 +0100 Subject: [PATCH 05/13] feat: Add support for Simple Inventory Listing --- Runtime/Client/LootLockerEndPoints.cs | 1 + Runtime/Game/LootLockerSDKManager.cs | 37 ++++++++++++++++++ Runtime/Game/Requests/PlayerRequest.cs | 52 ++++++++++++++++++++++++++ 3 files changed, 90 insertions(+) diff --git a/Runtime/Client/LootLockerEndPoints.cs b/Runtime/Client/LootLockerEndPoints.cs index 08b23bd1..0610a8e3 100644 --- a/Runtime/Client/LootLockerEndPoints.cs +++ b/Runtime/Client/LootLockerEndPoints.cs @@ -53,6 +53,7 @@ public class LootLockerEndPoints public static EndPointClass getInfoFromSession = new EndPointClass("player/hazy-hammock/v1/info", LootLockerHTTPMethod.GET); public static EndPointClass listPlayerInfo = new EndPointClass("player/hazy-hammock/v1/info", LootLockerHTTPMethod.POST); public static EndPointClass getInventory = new EndPointClass("v1/player/inventory/list", LootLockerHTTPMethod.GET); + public static EndPointClass listSimplifiedInventory = new EndPointClass("player/inventories/v1", LootLockerHTTPMethod.POST); public static EndPointClass getCurrencyBalance = new EndPointClass("v1/player/balance", LootLockerHTTPMethod.GET); public static EndPointClass playerAssetNotifications = new EndPointClass("v1/player/notification/assets", LootLockerHTTPMethod.GET); public static EndPointClass playerAssetDeactivationNotification = new EndPointClass("v1/player/notification/deactivations", LootLockerHTTPMethod.GET); diff --git a/Runtime/Game/LootLockerSDKManager.cs b/Runtime/Game/LootLockerSDKManager.cs index 3ac2a649..94cc1bd5 100644 --- a/Runtime/Game/LootLockerSDKManager.cs +++ b/Runtime/Game/LootLockerSDKManager.cs @@ -2831,6 +2831,43 @@ public static void GetInventory(int count, Action o } + /// + /// List player inventory with default parameters (no filters, first page, default page size). + /// + /// onComplete Action for handling the response + /// Optional : Execute the request for the specified player. If not supplied, the default player will be used. + public static void ListPlayerInventoryWithDefaultParameters(Action onComplete, string forPlayerWithUlid = null) + { + ListPlayerInventory(new LootLockerListSimplifiedInventoryRequest(), 0, 0, 0, onComplete, forPlayerWithUlid); + } + + /// + /// List player inventory with minimal response data. Due to looking up less data, this endpoint is significantly faster than GetInventory. + /// + /// Request object containing any filters to apply to the inventory listing. + /// Optional : Filter inventory by character ID. + /// Optional : Number of items per page. + /// Optional : Page number to retrieve. + public static void ListPlayerInventory(LootLockerListSimplifiedInventoryRequest request, int characterId, int perPage, int page, Action onComplete, string forPlayerWithUlid = null) + { + if (!CheckInitialized(false, forPlayerWithUlid)) + { + onComplete?.Invoke(LootLockerResponseFactory.SDKNotInitializedError(forPlayerWithUlid)); + return; + } + var endPoint = LootLockerEndPoints.listSimplifiedInventory; + + var queryParams = new LootLocker.Utilities.HTTP.QueryParamaterBuilder(); + if (characterId > 0) + queryParams.Add("character_id", characterId); + if (perPage > 0) + queryParams.Add("per_page", perPage); + if (page > 0) + queryParams.Add("page", page); + + LootLockerServerRequest.CallAPI(forPlayerWithUlid, endPoint.endPoint + queryParams.Build(), endPoint.httpMethod, LootLockerJson.SerializeObject(request), onComplete: (serverResponse) => { LootLockerResponse.Deserialize(onComplete, serverResponse); }); + } + /// /// Get the amount of credits/currency that the player has. /// diff --git a/Runtime/Game/Requests/PlayerRequest.cs b/Runtime/Game/Requests/PlayerRequest.cs index d23f54d4..7850776d 100644 --- a/Runtime/Game/Requests/PlayerRequest.cs +++ b/Runtime/Game/Requests/PlayerRequest.cs @@ -75,6 +75,29 @@ public class LootLockerInventory public float balance { get; set; } } + /// + /// A simplified view of an inventory item + /// + public class LootLockerSimpleInventoryItem + { + /// + /// The asset id of the inventory item + /// + public int asset_id { get; set; } + /// + /// The instance id of the inventory item + /// + public int instance_id { get; set; } + /// + /// The acquisition source of the inventory item + /// + public string acquisition_source { get; set; } + /// + /// The acquisition date of the inventory item + /// + public DateTime? acquisition_date { get; set; } + } + public class LootLockerRewardObject { public int instance_id { get; set; } @@ -153,6 +176,22 @@ public class LootLockerListPlayerInfoRequest public string[] player_public_uid { get; set; } } + /// + /// Request to list a player's simplified inventory with the given filters + /// + public class LootLockerListSimplifiedInventoryRequest + { + /// + /// A list of asset ids to filter the inventory items by + /// + public int[] asset_ids { get; set; } + /// + /// A list of context ids to filter the inventory items by + /// + public int[] context_ids { get; set; } + + } + //================================================== // Response Definitions //================================================== @@ -202,6 +241,19 @@ public class LootLockerInventoryResponse : LootLockerResponse { public LootLockerInventory[] inventory { get; set; } } + + /// + /// The response class for simplified inventory requests + /// + [Serializable] + public class LootLockerSimpleInventoryResponse : LootLockerResponse + { + /// + /// List of simplified inventory items according to the requested filters + /// + public LootLockerSimpleInventoryItem[] items { get; set; } + public LootLockerExtendedPagination pagination { get; set; } + } public class LootLockerPlayerFilesResponse : LootLockerResponse { From 8d48792d9180df9f2923e9a6e215267e710dfafa Mon Sep 17 00:00:00 2001 From: Erik Bylund Date: Wed, 5 Nov 2025 13:27:40 +0100 Subject: [PATCH 06/13] fix: Fixes after review of simple inventory --- Runtime/Game/LootLockerSDKManager.cs | 8 +++----- Runtime/Game/Requests/PlayerRequest.cs | 4 +++- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Runtime/Game/LootLockerSDKManager.cs b/Runtime/Game/LootLockerSDKManager.cs index 94cc1bd5..d3bb4bfc 100644 --- a/Runtime/Game/LootLockerSDKManager.cs +++ b/Runtime/Game/LootLockerSDKManager.cs @@ -2838,7 +2838,7 @@ public static void GetInventory(int count, Action o /// Optional : Execute the request for the specified player. If not supplied, the default player will be used. public static void ListPlayerInventoryWithDefaultParameters(Action onComplete, string forPlayerWithUlid = null) { - ListPlayerInventory(new LootLockerListSimplifiedInventoryRequest(), 0, 0, 0, onComplete, forPlayerWithUlid); + ListPlayerInventory(new LootLockerListSimplifiedInventoryRequest(), 0, 100, 1, onComplete, forPlayerWithUlid); } /// @@ -2860,10 +2860,8 @@ public static void ListPlayerInventory(LootLockerListSimplifiedInventoryRequest var queryParams = new LootLocker.Utilities.HTTP.QueryParamaterBuilder(); if (characterId > 0) queryParams.Add("character_id", characterId); - if (perPage > 0) - queryParams.Add("per_page", perPage); - if (page > 0) - queryParams.Add("page", page); + queryParams.Add("per_page", perPage > 0 ? perPage : 100); + queryParams.Add("page", page > 0 ? page : 1); LootLockerServerRequest.CallAPI(forPlayerWithUlid, endPoint.endPoint + queryParams.Build(), endPoint.httpMethod, LootLockerJson.SerializeObject(request), onComplete: (serverResponse) => { LootLockerResponse.Deserialize(onComplete, serverResponse); }); } diff --git a/Runtime/Game/Requests/PlayerRequest.cs b/Runtime/Game/Requests/PlayerRequest.cs index 7850776d..f15337df 100644 --- a/Runtime/Game/Requests/PlayerRequest.cs +++ b/Runtime/Game/Requests/PlayerRequest.cs @@ -189,7 +189,6 @@ public class LootLockerListSimplifiedInventoryRequest /// A list of context ids to filter the inventory items by /// public int[] context_ids { get; set; } - } //================================================== @@ -252,6 +251,9 @@ public class LootLockerSimpleInventoryResponse : LootLockerResponse /// List of simplified inventory items according to the requested filters /// public LootLockerSimpleInventoryItem[] items { get; set; } + /// + /// Pagination information for the response + /// public LootLockerExtendedPagination pagination { get; set; } } From 99ed31046f00484dfadee9c6b402164800884a12 Mon Sep 17 00:00:00 2001 From: Erik Bylund Date: Wed, 5 Nov 2025 14:34:07 +0100 Subject: [PATCH 07/13] fix: Split simple inventories into character and player --- Runtime/Game/LootLockerSDKManager.cs | 36 ++++++++++++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) diff --git a/Runtime/Game/LootLockerSDKManager.cs b/Runtime/Game/LootLockerSDKManager.cs index d3bb4bfc..c937a108 100644 --- a/Runtime/Game/LootLockerSDKManager.cs +++ b/Runtime/Game/LootLockerSDKManager.cs @@ -2838,17 +2838,49 @@ public static void GetInventory(int count, Action o /// Optional : Execute the request for the specified player. If not supplied, the default player will be used. public static void ListPlayerInventoryWithDefaultParameters(Action onComplete, string forPlayerWithUlid = null) { - ListPlayerInventory(new LootLockerListSimplifiedInventoryRequest(), 0, 100, 1, onComplete, forPlayerWithUlid); + ListPlayerInventory(new LootLockerListSimplifiedInventoryRequest(), 100, 1, onComplete, forPlayerWithUlid); } /// /// List player inventory with minimal response data. Due to looking up less data, this endpoint is significantly faster than GetInventory. /// /// Request object containing any filters to apply to the inventory listing. + /// Optional : Number of items per page. + /// Optional : Page number to retrieve. + public static void ListPlayerInventory(LootLockerListSimplifiedInventoryRequest request, int perPage, int page, Action onComplete, string forPlayerWithUlid = null) + { + if (!CheckInitialized(false, forPlayerWithUlid)) + { + onComplete?.Invoke(LootLockerResponseFactory.SDKNotInitializedError(forPlayerWithUlid)); + return; + } + var endPoint = LootLockerEndPoints.listSimplifiedInventory; + + var queryParams = new LootLocker.Utilities.HTTP.QueryParamaterBuilder(); + queryParams.Add("per_page", perPage > 0 ? perPage : 100); + queryParams.Add("page", page > 0 ? page : 1); + + LootLockerServerRequest.CallAPI(forPlayerWithUlid, endPoint.endPoint + queryParams.Build(), endPoint.httpMethod, LootLockerJson.SerializeObject(request), onComplete: (serverResponse) => { LootLockerResponse.Deserialize(onComplete, serverResponse); }); + } + + /// + /// List character inventory with default parameters (no filters, first page, default page size). + /// + /// onComplete Action for handling the response + /// Optional : Execute the request for the specified player. If not supplied, the default player will be used. + public static void ListCharacterInventoryWithDefaultParameters(Action onComplete, string forPlayerWithUlid = null) + { + ListCharacterInventory(new LootLockerListSimplifiedInventoryRequest(), 0, 100, 1, onComplete, forPlayerWithUlid); + } + + /// + /// List character inventory with minimal response data. Due to looking up less data, this endpoint is significantly faster than GetInventory. + /// + /// Request object containing any filters to apply to the inventory listing. /// Optional : Filter inventory by character ID. /// Optional : Number of items per page. /// Optional : Page number to retrieve. - public static void ListPlayerInventory(LootLockerListSimplifiedInventoryRequest request, int characterId, int perPage, int page, Action onComplete, string forPlayerWithUlid = null) + public static void ListCharacterInventory(LootLockerListSimplifiedInventoryRequest request, int characterId, int perPage, int page, Action onComplete, string forPlayerWithUlid = null) { if (!CheckInitialized(false, forPlayerWithUlid)) { From db4df90620a4f3a5d3ac2f36eef46f586c95f160 Mon Sep 17 00:00:00 2001 From: Erik Bylund Date: Thu, 6 Nov 2025 10:14:59 +0100 Subject: [PATCH 08/13] fix: Stop accessing x-session-token without checking it's existance --- Runtime/Client/LootLockerHTTPClient.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Runtime/Client/LootLockerHTTPClient.cs b/Runtime/Client/LootLockerHTTPClient.cs index a6b07194..445ff18b 100644 --- a/Runtime/Client/LootLockerHTTPClient.cs +++ b/Runtime/Client/LootLockerHTTPClient.cs @@ -697,7 +697,7 @@ private void HandleSessionRefreshResult(LootLockerResponse newSessionResponse, s return; } var playerData = LootLockerStateData.GetStateForPlayerOrDefaultStateOrEmpty(executionItem.RequestData.ForPlayerWithUlid); - string tokenBeforeRefresh = executionItem.RequestData.ExtraHeaders["x-session-token"]; + string tokenBeforeRefresh = executionItem.RequestData.ExtraHeaders.GetValueOrDefault("x-session-token", ""); string tokenAfterRefresh = playerData?.SessionToken; if (string.IsNullOrEmpty(tokenAfterRefresh) || tokenBeforeRefresh.Equals(playerData.SessionToken)) { From 3ca7d5acb06b6d4a219213f087e486d0484943f4 Mon Sep 17 00:00:00 2001 From: Erik Bylund Date: Tue, 11 Nov 2025 09:10:41 +0100 Subject: [PATCH 09/13] Add caller role to account verification 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 0610a8e3..c1f61ee0 100644 --- a/Runtime/Client/LootLockerEndPoints.cs +++ b/Runtime/Client/LootLockerEndPoints.cs @@ -45,7 +45,7 @@ public class LootLockerEndPoints public static EndPointClass whiteLabelLogin = new EndPointClass("white-label-login/login", LootLockerHTTPMethod.POST, LootLockerEnums.LootLockerCallerRole.Base); public static EndPointClass whiteLabelVerifySession = new EndPointClass("white-label-login/verify-session", LootLockerHTTPMethod.POST, LootLockerEnums.LootLockerCallerRole.Base); public static EndPointClass whiteLabelRequestPasswordReset = new EndPointClass("white-label-login/request-reset-password", LootLockerHTTPMethod.POST, LootLockerEnums.LootLockerCallerRole.Base); - public static EndPointClass whiteLabelRequestAccountVerification = new EndPointClass("white-label-login/request-verification", LootLockerHTTPMethod.POST); + public static EndPointClass whiteLabelRequestAccountVerification = new EndPointClass("white-label-login/request-verification", LootLockerHTTPMethod.POST, LootLockerEnums.LootLockerCallerRole.Base); public static EndPointClass whiteLabelLoginSessionRequest = new EndPointClass("v2/session/white-label", LootLockerHTTPMethod.POST, LootLockerEnums.LootLockerCallerRole.Base); // Player From 264c96b3d0e472ed6afd6873eaa7ff431de435fb Mon Sep 17 00:00:00 2001 From: Erik Bylund Date: Wed, 5 Nov 2025 14:51:08 +0100 Subject: [PATCH 10/13] feat: Add ability to pretty print json --- Runtime/Client/LootLockerJson.cs | 28 ++++++++++++++++++++++ Runtime/Client/LootLockerResponse.cs | 6 ++--- Runtime/Game/Resources/LootLockerConfig.cs | 1 + 3 files changed, 32 insertions(+), 3 deletions(-) diff --git a/Runtime/Client/LootLockerJson.cs b/Runtime/Client/LootLockerJson.cs index 24ffb776..7a40ec45 100644 --- a/Runtime/Client/LootLockerJson.cs +++ b/Runtime/Client/LootLockerJson.cs @@ -85,6 +85,21 @@ public static bool TryDeserializeObject(string json, JsonSerializerSettings o return false; } } + + public static string PrettifyJsonString(string json) + { + try + { + var parsedJson = DeserializeObject(json); + var tempSettings = LootLockerJsonSettings.Default; + tempSettings.Formatting = Formatting.Indented; + return SerializeObject(parsedJson, tempSettings); + } + catch (Exception) + { + return json; + } + } #else //LOOTLOCKER_USE_NEWTONSOFTJSON public static string SerializeObject(object obj) { @@ -142,6 +157,19 @@ public static bool TryDeserializeObject(string json, JsonOptions options, out return false; } } + + public static string PrettifyJsonString(string json) + { + try + { + var parsedJson = DeserializeObject(json); + return Json.SerializeFormatted(parsedJson, LootLockerJsonSettings.Default); + } + catch (Exception) + { + return json; + } + } #endif //LOOTLOCKER_USE_NEWTONSOFTJSON } } diff --git a/Runtime/Client/LootLockerResponse.cs b/Runtime/Client/LootLockerResponse.cs index b954d77d..6f2ce0f6 100644 --- a/Runtime/Client/LootLockerResponse.cs +++ b/Runtime/Client/LootLockerResponse.cs @@ -102,7 +102,7 @@ public class LootLockerResponseFactory return new T() { success = true, - text = responseBody, + text = LootLockerConfig.current.prettifyJson ? LootLockerJson.PrettifyJsonString(responseBody) : responseBody, statusCode = statusCode, errorData = null, requestContext = new LootLockerRequestContext(forPlayerWithUlid, requestTime) @@ -117,7 +117,7 @@ public class LootLockerResponseFactory return new T() { success = false, - text = responseBody, + text = LootLockerConfig.current.prettifyJson ? LootLockerJson.PrettifyJsonString(responseBody) : responseBody, statusCode = statusCode, errorData = null, requestContext = new LootLockerRequestContext(forPlayerWithUlid, requestTime) @@ -132,7 +132,7 @@ public class LootLockerResponseFactory return new T() { success = failureResponse.success, - text = failureResponse.text, + text = LootLockerConfig.current.prettifyJson ? LootLockerJson.PrettifyJsonString(failureResponse.text) : failureResponse.text, statusCode = failureResponse.statusCode, errorData = failureResponse.errorData, requestContext = failureResponse.requestContext diff --git a/Runtime/Game/Resources/LootLockerConfig.cs b/Runtime/Game/Resources/LootLockerConfig.cs index 6bf4f041..fd39c4d4 100644 --- a/Runtime/Game/Resources/LootLockerConfig.cs +++ b/Runtime/Game/Resources/LootLockerConfig.cs @@ -274,6 +274,7 @@ public static bool IsTargetingProductionEnvironment() [HideInInspector] public string baseUrl = UrlProtocol + GetUrlCore(); [HideInInspector] public float clientSideRequestTimeOut = 180f; public LootLockerLogger.LogLevel logLevel = LootLockerLogger.LogLevel.Info; + public bool prettifyJson = true; public bool logErrorsAsWarnings = false; public bool logInBuilds = false; public bool allowTokenRefresh = true; From 6dab7bd5da65bd690236cd48c94b4b5266081486 Mon Sep 17 00:00:00 2001 From: Erik Bylund Date: Thu, 6 Nov 2025 12:24:53 +0100 Subject: [PATCH 11/13] feat: Re-add obfuscation and make nice pretty print --- Runtime/Client/LootLockerJson.cs | 13 +++-- Runtime/Client/LootLockerResponse.cs | 6 +-- .../Editor/LogViewer/LootLockerLogViewerUI.cs | 54 ++++++++++++++++--- Runtime/Editor/ProjectSettings.cs | 9 ++++ Runtime/Game/LootLockerLogger.cs | 17 +++++- Runtime/Game/Resources/LootLockerConfig.cs | 9 +++- 6 files changed, 91 insertions(+), 17 deletions(-) diff --git a/Runtime/Client/LootLockerJson.cs b/Runtime/Client/LootLockerJson.cs index 7a40ec45..69458c38 100644 --- a/Runtime/Client/LootLockerJson.cs +++ b/Runtime/Client/LootLockerJson.cs @@ -23,6 +23,7 @@ public static class LootLockerJsonSettings }; #else public static readonly JsonOptions Default = new JsonOptions((JsonSerializationOptions.Default | JsonSerializationOptions.EnumAsText) & ~JsonSerializationOptions.SkipGetOnly); + public static readonly JsonOptions Indented = new JsonOptions((JsonSerializationOptions.Default | JsonSerializationOptions.EnumAsText) & ~JsonSerializationOptions.SkipGetOnly); #endif } @@ -91,8 +92,12 @@ public static string PrettifyJsonString(string json) try { var parsedJson = DeserializeObject(json); - var tempSettings = LootLockerJsonSettings.Default; - tempSettings.Formatting = Formatting.Indented; + var tempSettings = new JsonSerializerSettings + { + ContractResolver = LootLockerJsonSettings.Default.ContractResolver, + Converters = LootLockerJsonSettings.Default.Converters, + Formatting = Formatting.Indented + }; return SerializeObject(parsedJson, tempSettings); } catch (Exception) @@ -163,7 +168,9 @@ public static string PrettifyJsonString(string json) try { var parsedJson = DeserializeObject(json); - return Json.SerializeFormatted(parsedJson, LootLockerJsonSettings.Default); + var indentedOptions = LootLockerJsonSettings.Default.Clone(); + indentedOptions.FormattingTab = " "; + return Json.SerializeFormatted(parsedJson, indentedOptions); } catch (Exception) { diff --git a/Runtime/Client/LootLockerResponse.cs b/Runtime/Client/LootLockerResponse.cs index 6f2ce0f6..b954d77d 100644 --- a/Runtime/Client/LootLockerResponse.cs +++ b/Runtime/Client/LootLockerResponse.cs @@ -102,7 +102,7 @@ public class LootLockerResponseFactory return new T() { success = true, - text = LootLockerConfig.current.prettifyJson ? LootLockerJson.PrettifyJsonString(responseBody) : responseBody, + text = responseBody, statusCode = statusCode, errorData = null, requestContext = new LootLockerRequestContext(forPlayerWithUlid, requestTime) @@ -117,7 +117,7 @@ public class LootLockerResponseFactory return new T() { success = false, - text = LootLockerConfig.current.prettifyJson ? LootLockerJson.PrettifyJsonString(responseBody) : responseBody, + text = responseBody, statusCode = statusCode, errorData = null, requestContext = new LootLockerRequestContext(forPlayerWithUlid, requestTime) @@ -132,7 +132,7 @@ public class LootLockerResponseFactory return new T() { success = failureResponse.success, - text = LootLockerConfig.current.prettifyJson ? LootLockerJson.PrettifyJsonString(failureResponse.text) : failureResponse.text, + text = failureResponse.text, statusCode = failureResponse.statusCode, errorData = failureResponse.errorData, requestContext = failureResponse.requestContext diff --git a/Runtime/Editor/LogViewer/LootLockerLogViewerUI.cs b/Runtime/Editor/LogViewer/LootLockerLogViewerUI.cs index 1ce1cf4a..6e48f260 100644 --- a/Runtime/Editor/LogViewer/LootLockerLogViewerUI.cs +++ b/Runtime/Editor/LogViewer/LootLockerLogViewerUI.cs @@ -216,7 +216,11 @@ private void ExportLogs() if (!string.IsNullOrEmpty(http.RequestBody)) { sb.AppendLine("Request Body:"); - sb.AppendLine(http.RequestBody); + sb.AppendLine( + LootLockerConfig.current.obfuscateLogs ? + LootLockerObfuscator.ObfuscateJsonStringForLogging(http.RequestBody) : + http.RequestBody + ); } sb.AppendLine("Response Headers:"); foreach (var h in http.ResponseHeaders ?? new Dictionary()) @@ -224,7 +228,11 @@ private void ExportLogs() if (!string.IsNullOrEmpty(http.Response?.text)) { sb.AppendLine("Response Body:"); - sb.AppendLine(http.Response.text); + sb.AppendLine( + LootLockerConfig.current.obfuscateLogs ? + LootLockerObfuscator.ObfuscateJsonStringForLogging(http.Response.text) : + http.Response.text + ); } writer.Write(sb.ToString()); } @@ -401,25 +409,55 @@ private void AddHttpLogEntryToUI(LootLockerLogger.LootLockerHttpLogEntry entry) var reqHeaders = new TextField { value = $"Request Headers: {FormatHeaders(entry.RequestHeaders)}", isReadOnly = true }; reqHeaders.AddToClassList("log-message-field"); details.Add(reqHeaders); + if (!string.IsNullOrEmpty(entry.RequestBody)) { - var reqBody = new TextField { value = $"Request Body: {entry.RequestBody}", isReadOnly = true }; - reqBody.AddToClassList("log-message-field"); - details.Add(reqBody); + var requestBodyFoldout = CreateJsonFoldout("Request Body", entry.RequestBody); + details.Add(requestBodyFoldout); } + var respHeaders = new TextField { value = $"Response Headers: {FormatHeaders(entry.ResponseHeaders)}", isReadOnly = true }; respHeaders.AddToClassList("log-message-field"); details.Add(respHeaders); + if (!string.IsNullOrEmpty(entry.Response?.text)) { - var respBody = new TextField { value = $"Response Body: {entry.Response.text}", isReadOnly = true }; - respBody.AddToClassList("log-message-field"); - details.Add(respBody); + var responseBodyFoldout = CreateJsonFoldout("Response Body", entry.Response.text); + details.Add(responseBodyFoldout); } + foldout.Add(details); logContainer.Add(foldout); } + private Foldout CreateJsonFoldout(string title, string jsonContent) + { + // Initially show minified JSON (obfuscated if configured) + var obfuscatedJson = LootLockerConfig.current.obfuscateLogs + ? LootLockerObfuscator.ObfuscateJsonStringForLogging(jsonContent) + : jsonContent; + var prettifiedJson = LootLockerJson.PrettifyJsonString(obfuscatedJson); + var collapsedTitle = $"{title}: {obfuscatedJson}"; + + var foldout = new Foldout { text = collapsedTitle, value = false }; + + // Create a container for the JSON content + var jsonContainer = new VisualElement(); + + var jsonField = new TextField { value = prettifiedJson, isReadOnly = true, multiline = true }; + jsonField.AddToClassList("log-message-field"); + jsonField.style.whiteSpace = WhiteSpace.PreWrap; + jsonContainer.Add(jsonField); + + foldout.RegisterValueChangedCallback(evt => + { + foldout.text = evt.newValue ? title : collapsedTitle; + }); + + foldout.Add(jsonContainer); + return foldout; + } + private string FormatHeaders(Dictionary headers) { if (headers == null) return ""; diff --git a/Runtime/Editor/ProjectSettings.cs b/Runtime/Editor/ProjectSettings.cs index d34ef016..161f52bc 100644 --- a/Runtime/Editor/ProjectSettings.cs +++ b/Runtime/Editor/ProjectSettings.cs @@ -159,6 +159,15 @@ private void DrawGameSettings() } EditorGUILayout.Space(); + EditorGUI.BeginChangeCheck(); + EditorGUILayout.PropertyField(m_CustomSettings.FindProperty("prettifyJson"), new GUIContent("Log JSON Formatted")); + + if (EditorGUI.EndChangeCheck()) + { + gameSettings.prettifyJson = m_CustomSettings.FindProperty("prettifyJson").boolValue; + } + EditorGUILayout.Space(); + EditorGUI.BeginChangeCheck(); EditorGUILayout.PropertyField(m_CustomSettings.FindProperty("allowTokenRefresh")); diff --git a/Runtime/Game/LootLockerLogger.cs b/Runtime/Game/LootLockerLogger.cs index cd7b4907..0cbdf6de 100644 --- a/Runtime/Game/LootLockerLogger.cs +++ b/Runtime/Game/LootLockerLogger.cs @@ -245,7 +245,7 @@ public static void LogHttpRequestResponse(LootLockerHttpLogEntry entry) if (!string.IsNullOrEmpty(entry.RequestBody)) { sb.AppendLine("Request Body:"); - sb.AppendLine(entry.RequestBody); + sb.AppendLine(ObfuscateAndPrettifyJsonIfConfigured(entry.RequestBody)); } sb.AppendLine("Response Headers:"); foreach (var h in entry.ResponseHeaders ?? new Dictionary()) @@ -253,7 +253,7 @@ public static void LogHttpRequestResponse(LootLockerHttpLogEntry entry) if (!string.IsNullOrEmpty(entry.Response?.text)) { sb.AppendLine("Response Body:"); - sb.AppendLine(entry.Response.text); + sb.AppendLine(ObfuscateAndPrettifyJsonIfConfigured(entry.Response.text)); } LogLevel level = entry.Response?.success ?? false ? LogLevel.Verbose : LogLevel.Error; @@ -302,6 +302,19 @@ private void ReplayHttpLogRecord(ILootLockerHttpLogListener listener) } } } + + private static string ObfuscateAndPrettifyJsonIfConfigured(string json) + { + if (LootLockerConfig.current.obfuscateLogs) + { + json = LootLockerObfuscator.ObfuscateJsonStringForLogging(json); + } + if (LootLockerConfig.current.prettifyJson) + { + json = LootLockerJson.PrettifyJsonString(json); + } + return json; + } } public interface LootLockerLogListener diff --git a/Runtime/Game/Resources/LootLockerConfig.cs b/Runtime/Game/Resources/LootLockerConfig.cs index fd39c4d4..9bd8d684 100644 --- a/Runtime/Game/Resources/LootLockerConfig.cs +++ b/Runtime/Game/Resources/LootLockerConfig.cs @@ -173,13 +173,14 @@ static void ListInstalledPackagesRequestProgress() } #endif - public static bool CreateNewSettings(string apiKey, string gameVersion, string domainKey, LootLockerLogger.LogLevel logLevel = LootLockerLogger.LogLevel.Info, bool logInBuilds = false, bool errorsAsWarnings = false, bool allowTokenRefresh = false) + public static bool CreateNewSettings(string apiKey, string gameVersion, string domainKey, LootLockerLogger.LogLevel logLevel = LootLockerLogger.LogLevel.Info, bool logInBuilds = false, bool errorsAsWarnings = false, bool allowTokenRefresh = false, bool prettifyJson = false) { _current = Get(); _current.apiKey = apiKey; _current.game_version = gameVersion; _current.logLevel = logLevel; + _current.prettifyJson = prettifyJson; _current.logInBuilds = logInBuilds; _current.logErrorsAsWarnings = errorsAsWarnings; _current.allowTokenRefresh = allowTokenRefresh; @@ -199,6 +200,10 @@ public static bool ClearSettings() _current.apiKey = null; _current.game_version = null; _current.logLevel = LootLockerLogger.LogLevel.Info; + _current.prettifyJson = false; + _current.logInBuilds = false; + _current.logErrorsAsWarnings = false; + _current.obfuscateLogs = true; _current.allowTokenRefresh = true; _current.domainKey = null; #if UNITY_EDITOR @@ -274,7 +279,9 @@ public static bool IsTargetingProductionEnvironment() [HideInInspector] public string baseUrl = UrlProtocol + GetUrlCore(); [HideInInspector] public float clientSideRequestTimeOut = 180f; public LootLockerLogger.LogLevel logLevel = LootLockerLogger.LogLevel.Info; + // Write JSON in a pretty and indented format when logging public bool prettifyJson = true; + [HideInInspector] public bool obfuscateLogs = true; public bool logErrorsAsWarnings = false; public bool logInBuilds = false; public bool allowTokenRefresh = true; From b0569a66700f66f574e4aaa5807adc778b3d2611 Mon Sep 17 00:00:00 2001 From: Erik Bylund Date: Wed, 12 Nov 2025 08:14:03 +0100 Subject: [PATCH 12/13] Bump Version to v6.5.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 560b3a1a..f2f48d63 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "com.lootlocker.lootlockersdk", - "version": "6.4.0", + "version": "6.5.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", From 28e746ab3ee9860e9727099c305f28e786328e04 Mon Sep 17 00:00:00 2001 From: Erik Bylund Date: Fri, 14 Nov 2025 10:13:46 +0100 Subject: [PATCH 13/13] fix: Compilation issues for < Unity Version --- Runtime/Client/LootLockerHTTPClient.cs | 2 +- Runtime/Editor/LogViewer/LootLockerLogViewerUI.cs | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/Runtime/Client/LootLockerHTTPClient.cs b/Runtime/Client/LootLockerHTTPClient.cs index 445ff18b..d7ebd77f 100644 --- a/Runtime/Client/LootLockerHTTPClient.cs +++ b/Runtime/Client/LootLockerHTTPClient.cs @@ -697,7 +697,7 @@ private void HandleSessionRefreshResult(LootLockerResponse newSessionResponse, s return; } var playerData = LootLockerStateData.GetStateForPlayerOrDefaultStateOrEmpty(executionItem.RequestData.ForPlayerWithUlid); - string tokenBeforeRefresh = executionItem.RequestData.ExtraHeaders.GetValueOrDefault("x-session-token", ""); + string tokenBeforeRefresh = executionItem.RequestData.ExtraHeaders.TryGetValue("x-session-token", out var existingToken) ? existingToken : ""; string tokenAfterRefresh = playerData?.SessionToken; if (string.IsNullOrEmpty(tokenAfterRefresh) || tokenBeforeRefresh.Equals(playerData.SessionToken)) { diff --git a/Runtime/Editor/LogViewer/LootLockerLogViewerUI.cs b/Runtime/Editor/LogViewer/LootLockerLogViewerUI.cs index 6e48f260..506de51d 100644 --- a/Runtime/Editor/LogViewer/LootLockerLogViewerUI.cs +++ b/Runtime/Editor/LogViewer/LootLockerLogViewerUI.cs @@ -446,7 +446,11 @@ private Foldout CreateJsonFoldout(string title, string jsonContent) var jsonField = new TextField { value = prettifiedJson, isReadOnly = true, multiline = true }; jsonField.AddToClassList("log-message-field"); - jsonField.style.whiteSpace = WhiteSpace.PreWrap; + #if UNITY_6000_0_OR_NEWER + jsonField.style.whiteSpace = WhiteSpace.PreWrap; + #else + jsonField.style.whiteSpace = WhiteSpace.Normal; + #endif jsonContainer.Add(jsonField); foldout.RegisterValueChangedCallback(evt =>