Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion Runtime/Client/LootLockerEndPoints.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,14 +45,15 @@ 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
[Header("Player")]
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);
Expand Down
20 changes: 15 additions & 5 deletions Runtime/Client/LootLockerHTTPClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down Expand Up @@ -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.TryGetValue("x-session-token", out var existingToken) ? existingToken : "";
string tokenAfterRefresh = playerData?.SessionToken;
if (string.IsNullOrEmpty(tokenAfterRefresh) || tokenBeforeRefresh.Equals(playerData.SessionToken))
{
Expand Down Expand Up @@ -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>{ 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>{ 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)
Expand Down
35 changes: 35 additions & 0 deletions Runtime/Client/LootLockerJson.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down Expand Up @@ -85,6 +86,25 @@ public static bool TryDeserializeObject<T>(string json, JsonSerializerSettings o
return false;
}
}

public static string PrettifyJsonString(string json)
{
try
{
var parsedJson = DeserializeObject<object>(json);
var tempSettings = new JsonSerializerSettings
{
ContractResolver = LootLockerJsonSettings.Default.ContractResolver,
Converters = LootLockerJsonSettings.Default.Converters,
Formatting = Formatting.Indented
};
return SerializeObject(parsedJson, tempSettings);
}
catch (Exception)
{
return json;
}
}
#else //LOOTLOCKER_USE_NEWTONSOFTJSON
public static string SerializeObject(object obj)
{
Expand Down Expand Up @@ -142,6 +162,21 @@ public static bool TryDeserializeObject<T>(string json, JsonOptions options, out
return false;
}
}

public static string PrettifyJsonString(string json)
{
try
{
var parsedJson = DeserializeObject<object>(json);
var indentedOptions = LootLockerJsonSettings.Default.Clone();
indentedOptions.FormattingTab = " ";
return Json.SerializeFormatted(parsedJson, indentedOptions);
}
catch (Exception)
{
return json;
}
}
#endif //LOOTLOCKER_USE_NEWTONSOFTJSON
}
}
58 changes: 50 additions & 8 deletions Runtime/Editor/LogViewer/LootLockerLogViewerUI.cs
Original file line number Diff line number Diff line change
Expand Up @@ -216,15 +216,23 @@ 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<string, string>())
sb.AppendLine($" {h.Key}: {h.Value}");
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());
}
Expand Down Expand Up @@ -401,25 +409,59 @@ 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");
#if UNITY_6000_0_OR_NEWER
jsonField.style.whiteSpace = WhiteSpace.PreWrap;
#else
jsonField.style.whiteSpace = WhiteSpace.Normal;
#endif
jsonContainer.Add(jsonField);

foldout.RegisterValueChangedCallback(evt =>
{
foldout.text = evt.newValue ? title : collapsedTitle;
});

foldout.Add(jsonContainer);
return foldout;
}

private string FormatHeaders(Dictionary<string, string> headers)
{
if (headers == null) return "";
Expand Down
9 changes: 9 additions & 0 deletions Runtime/Editor/ProjectSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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"));

Expand Down
17 changes: 15 additions & 2 deletions Runtime/Game/LootLockerLogger.cs
Original file line number Diff line number Diff line change
Expand Up @@ -245,15 +245,15 @@ 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<string, string>())
sb.AppendLine($" {h.Key}: {h.Value}");
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;
Expand Down Expand Up @@ -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
Expand Down
67 changes: 67 additions & 0 deletions Runtime/Game/LootLockerSDKManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2831,6 +2831,73 @@ public static void GetInventory(int count, Action<LootLockerInventoryResponse> o

}

/// <summary>
/// List player inventory with default parameters (no filters, first page, default page size).
/// </summary>
/// <param name="onComplete">onComplete Action for handling the response</param>
/// <param name="forPlayerWithUlid">Optional : Execute the request for the specified player. If not supplied, the default player will be used.</param>
public static void ListPlayerInventoryWithDefaultParameters(Action<LootLockerSimpleInventoryResponse> onComplete, string forPlayerWithUlid = null)
{
ListPlayerInventory(new LootLockerListSimplifiedInventoryRequest(), 100, 1, onComplete, forPlayerWithUlid);
}

/// <summary>
/// List player inventory with minimal response data. Due to looking up less data, this endpoint is significantly faster than GetInventory.
/// </summary>
/// <param name="request">Request object containing any filters to apply to the inventory listing.</param>
/// <param name="perPage">Optional : Number of items per page.</param>
/// <param name="page">Optional : Page number to retrieve.</param>
public static void ListPlayerInventory(LootLockerListSimplifiedInventoryRequest request, int perPage, int page, Action<LootLockerSimpleInventoryResponse> onComplete, string forPlayerWithUlid = null)
{
if (!CheckInitialized(false, forPlayerWithUlid))
{
onComplete?.Invoke(LootLockerResponseFactory.SDKNotInitializedError<LootLockerSimpleInventoryResponse>(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); });
}

/// <summary>
/// List character inventory with default parameters (no filters, first page, default page size).
/// </summary>
/// <param name="onComplete">onComplete Action for handling the response</param>
/// <param name="forPlayerWithUlid">Optional : Execute the request for the specified player. If not supplied, the default player will be used.</param>
public static void ListCharacterInventoryWithDefaultParameters(Action<LootLockerSimpleInventoryResponse> onComplete, string forPlayerWithUlid = null)
{
ListCharacterInventory(new LootLockerListSimplifiedInventoryRequest(), 0, 100, 1, onComplete, forPlayerWithUlid);
}

/// <summary>
/// List character inventory with minimal response data. Due to looking up less data, this endpoint is significantly faster than GetInventory.
/// </summary>
/// <param name="request">Request object containing any filters to apply to the inventory listing.</param>
/// <param name="characterId">Optional : Filter inventory by character ID.</param>
/// <param name="perPage">Optional : Number of items per page.</param>
/// <param name="page">Optional : Page number to retrieve.</param>
public static void ListCharacterInventory(LootLockerListSimplifiedInventoryRequest request, int characterId, int perPage, int page, Action<LootLockerSimpleInventoryResponse> onComplete, string forPlayerWithUlid = null)
{
if (!CheckInitialized(false, forPlayerWithUlid))
{
onComplete?.Invoke(LootLockerResponseFactory.SDKNotInitializedError<LootLockerSimpleInventoryResponse>(forPlayerWithUlid));
return;
}
var endPoint = LootLockerEndPoints.listSimplifiedInventory;

var queryParams = new LootLocker.Utilities.HTTP.QueryParamaterBuilder();
if (characterId > 0)
queryParams.Add("character_id", characterId);
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); });
}

/// <summary>
/// Get the amount of credits/currency that the player has.
/// </summary>
Expand Down
Loading