Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
3f508f2
meta: Attach unitypackages from inside unity repo
kirre-bylund Jul 6, 2026
cddedbe
meta: Robustness changes to open-release-pr.yml
kirre-bylund Jul 6, 2026
93ce035
meta: Further robustness fixes to workflows
kirre-bylund Jul 9, 2026
955c934
fix: Allow "clear local player data" button when disabled extension
kirre-bylund Jul 16, 2026
e5ef68e
Add List Platform Keys endpoint
kirre-bylund Jul 17, 2026
0c4ad76
Fix .meta file and add doxygen group for PlatformKeys
kirre-bylund Jul 17, 2026
ea5468f
feat: Add support for WLL custom sign up fields
kirre-bylund Jul 9, 2026
03f5eb2
fix: Decouple custom_fields from login request, fix test assertions
kirre-bylund Jul 17, 2026
4b0081c
feat: align SDKs with "better files" backend changes
kirre-bylund Aug 26, 2026
97b3ba3
fixes after review
kirre-bylund Aug 27, 2026
5aada3f
ci: Add sign up fields for testing
kirre-bylund Aug 27, 2026
6a0e6cb
feat: add ConnectSteamAccount, ConnectXboxAccount, ConnectNintendo, C…
kirre-bylund Aug 21, 2026
e011ddf
feat: add auto_create_profile optional to session requests
kirre-bylund Aug 21, 2026
76f01b4
feat: add GetInt/SetInt to ILootLockerStateWriter and expose GetState…
kirre-bylund Aug 21, 2026
45a15e6
fix: use WaitForSecondsRealtime instead of WaitForSeconds in coroutines
kirre-bylund Aug 21, 2026
677d3e7
chore: reorganize project settings UI with foldouts, suppress unreach…
kirre-bylund Aug 21, 2026
c6c4b36
fix: Fixes after review
kirre-bylund Aug 28, 2026
9f63eb3
fix: Custom sign up field request should be json primitive
kirre-bylund Aug 28, 2026
de7ee7f
fix: file tests
kirre-bylund Sep 2, 2026
b7cea13
Bump version to 8.2.0
github-actions[bot] Sep 2, 2026
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
9 changes: 9 additions & 0 deletions .doxygen/groups.dox
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,15 @@
///
/// See the [LootLocker documentation](https://docs.lootlocker.com/commerce/wallets).

/// @defgroup PlatformKeys Platform Keys
/// @brief List platform keys redeemed by the authenticated player.
///
/// Platform keys are keys distributed through LootLocker's Campaign system
/// (e.g. Steam keys, Discord keys). This endpoint returns all keys redeemed
/// by the currently authenticated player, grouped by campaign.
///
/// See the [LootLocker documentation](https://docs.lootlocker.com/campaigns/overview).

/// @defgroup Catalog Catalog
/// @brief Browse item listings and prices in the LootLocker storefront catalog.
///
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/create-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ jobs:

PR_JSON=$(gh pr view "$PR_NUMBER" --json number,body,labels,title,state --jq '.')
PR_TITLE=$(echo "$PR_JSON" | jq -r '.title')
HAS_RC_LABEL=$(echo "$PR_JSON" | jq -r '[.labels[].name] | index("release candidate") != null')
HAS_RC_LABEL=$(echo "$PR_JSON" | jq -r '.labels[].name' | tr '[:upper:]' '[:lower:]' | grep -q 'release candidate' && echo "true" || echo "false")
PR_STATE=$(echo "$PR_JSON" | jq -r '.state')

echo "Found PR #${PR_NUMBER}: ${PR_TITLE} (state: ${PR_STATE})"
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/enforce-release-pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ jobs:
name: Validate release candidate PR
runs-on: ubuntu-latest
timeout-minutes: 3
if: contains(github.event.pull_request.labels.*.name, 'release candidate')
if: contains(github.event.pull_request.labels.*.name, 'Release Candidate') || contains(github.event.pull_request.labels.*.name, 'release candidate')

steps:
- name: Checkout
Expand Down
45 changes: 24 additions & 21 deletions .github/workflows/open-release-pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,19 +41,21 @@ jobs:
id: current
run: |
VERSION=$(jq -r '.version' package.json)
if [ -z "$VERSION" ] || [ "$VERSION" = "null" ]; then
echo "::error::Could not read version from package.json"
exit 1
fi
echo "VERSION=${VERSION}" >> "$GITHUB_OUTPUT"
echo "Current version: ${VERSION}"

- name: Get previous release tag and commit log
id: prev-tag
run: |
LATEST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "")
LATEST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "v0.0.0")
echo "TAG=${LATEST_TAG}" >> "$GITHUB_OUTPUT"
echo "Previous tag: ${LATEST_TAG:-none}"

# Collect commits since last tag (or all commits if no tag yet)
echo "Previous tag: ${LATEST_TAG}"
REV_RANGE=""
if [ -n "$LATEST_TAG" ]; then
if [ -n "$LATEST_TAG" ] && [ "$LATEST_TAG" != "v0.0.0" ]; then
REV_RANGE="${LATEST_TAG}..HEAD"
else
REV_RANGE="HEAD"
Expand Down Expand Up @@ -81,23 +83,24 @@ jobs:
if [ "$BUMP" = "auto" ] && [ "$ALREADY_CALCULATED" = "false" ]; then
echo "Auto-detecting version from OpenRouter..."
if [ -z "${{ secrets.OPENROUTER_API_KEY }}" ]; then
echo "::error::OPENROUTER_API_KEY secret not set. Use manual bump or set secret."
exit 1
fi
COMMITS=$(head -100 /tmp/commits.log)
printf 'Based on these commit messages since the last release (v%s), determine the correct semver version for the next release. Analyze breaking changes, new features, and fixes. Respond with only the version number (e.g., 9.0.0).\n\n%s\n' "$CURRENT" "$COMMITS" > /tmp/version-prompt.txt
RESPONSE=$(curl -s "https://openrouter.ai/api/v1/chat/completions" \
-H "Authorization: Bearer ${{ secrets.OPENROUTER_API_KEY }}" \
-H "Content-Type: application/json" \
-d "$(jq -n --rawfile prompt /tmp/version-prompt.txt '{model:"deepseek/deepseek-v4-pro",messages:[{role:"user",content:$prompt}],reasoning:{effort:"low"},max_tokens:2000,temperature:0}')")
NEW=$(echo "$RESPONSE" | jq -r '.choices[0].message.content // ""' | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1)
if [ -z "$NEW" ]; then
echo "::warning::AI returned no parseable version, falling back to patch bump."
PATCH=$(echo "$CURRENT" | cut -d. -f3)
NEW="$(echo "$CURRENT" | cut -d. -f1).$(echo "$CURRENT" | cut -d. -f2).$((PATCH + 1))"
echo "::warning::OPENROUTER_API_KEY not set. Falling back to patch bump."
BUMP="patch"
else
COMMITS=$(head -100 /tmp/commits.log)
printf 'The current version is v%s. Based on these commit messages since the last release, determine the correct semver version for the next release. Analyze breaking changes, new features, and fixes. Respond with only the version number (e.g., 9.0.0).\n\n%s\n' "$CURRENT" "$COMMITS" > /tmp/version-prompt.txt
RESPONSE=$(curl -s "https://openrouter.ai/api/v1/chat/completions" \
-H "Authorization: Bearer ${{ secrets.OPENROUTER_API_KEY }}" \
-H "Content-Type: application/json" \
-d "$(jq -n --rawfile prompt /tmp/version-prompt.txt '{model:"deepseek/deepseek-v4-pro",messages:[{role:"user",content:$prompt}],reasoning:{effort:"low"},max_tokens:2000,temperature:0}')")
NEW=$(echo "$RESPONSE" | jq -r '.choices[0].message.content // ""' | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1)
if [ -z "$NEW" ]; then
echo "::warning::AI returned no parseable version. Falling back to patch bump."
BUMP="patch"
else
echo "AI suggested version: ${NEW}"
ALREADY_CALCULATED=true
fi
fi
echo "AI suggested version: ${NEW}"
ALREADY_CALCULATED=true
fi

if [ "$ALREADY_CALCULATED" = "false" ]; then
Expand Down
3 changes: 2 additions & 1 deletion .github/workflows/package-sdk.yml
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,8 @@ jobs:
- name: Attach .unitypackage to release
if: github.event_name == 'release'
run: |
for f in unity-sdk-packager/LootLockerSDK*.unitypackage; do
cd unity-sdk
for f in ../unity-sdk-packager/LootLockerSDK*.unitypackage; do
echo "Uploading $f"
gh release upload --clobber "${{ github.ref_name }}" "$f"
done
Expand Down
13 changes: 13 additions & 0 deletions Runtime/Client/LootLockerEndPoints.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ public class LootLockerEndPoints
// White Label Login
[Header("White Label Login")]
public static EndPointClass whiteLabelSignUp = new EndPointClass("white-label-login/sign-up", LootLockerHTTPMethod.POST, LootLockerEnums.LootLockerCallerRole.Base);
public static EndPointClass whiteLabelSignUpFields = new EndPointClass("white-label-login/sign-up/fields", LootLockerHTTPMethod.GET, LootLockerEnums.LootLockerCallerRole.Base);
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);
Expand Down Expand Up @@ -73,6 +74,14 @@ public class LootLockerEndPoints
public static EndPointClass uploadPlayerFile = new EndPointClass("player/files", LootLockerHTTPMethod.UPLOAD_FILE);
public static EndPointClass updatePlayerFile = new EndPointClass("/player/files/{0}", LootLockerHTTPMethod.UPDATE_FILE);
public static EndPointClass deletePlayerFile = new EndPointClass("/player/files/{0}", LootLockerHTTPMethod.DELETE);
public static EndPointClass listPlayerFileRevisions = new EndPointClass("player/files/{0}/revisions", LootLockerHTTPMethod.GET);
public static EndPointClass getPlayerFileRevision = new EndPointClass("player/files/{0}/revisions/{1}", LootLockerHTTPMethod.GET);
public static EndPointClass promotePlayerFileRevision = new EndPointClass("player/files/{0}/revisions/{1}/current", LootLockerHTTPMethod.POST);
public static EndPointClass getPlayerFileByKey = new EndPointClass("player/files/key/{0}", LootLockerHTTPMethod.GET);
public static EndPointClass listPlayerFileRevisionsByKey = new EndPointClass("player/files/key/{0}/revisions", LootLockerHTTPMethod.GET);
public static EndPointClass getPlayerFileRevisionByKey = new EndPointClass("player/files/key/{0}/revisions/{1}", LootLockerHTTPMethod.GET);
public static EndPointClass promotePlayerFileRevisionByKey = new EndPointClass("player/files/key/{0}/revisions/{1}/current", LootLockerHTTPMethod.POST);
public static EndPointClass deletePlayerFileByKey = new EndPointClass("player/files/key/{0}", LootLockerHTTPMethod.DELETE);

// Player Progressions
[Header("Player Progressions")]
Expand Down Expand Up @@ -253,6 +262,10 @@ public class LootLockerEndPoints
public static EndPointClass getCurrencyDetails = new EndPointClass("currency/code/{0}", LootLockerHTTPMethod.GET);
public static EndPointClass getCurrencyDenominationsByCode = new EndPointClass("currency/code/{0}/denominations", LootLockerHTTPMethod.GET);

// Platform Keys
[Header("Platform Keys")]
public static EndPointClass listPlatformKeys = new EndPointClass("platform-keys/v1", LootLockerHTTPMethod.GET);

// Balances
[Header("Balances")]
public static EndPointClass listBalancesInWallet = new EndPointClass("balances/wallet/{0}", LootLockerHTTPMethod.GET);
Expand Down
2 changes: 1 addition & 1 deletion Runtime/Client/LootLockerLifecycleManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -572,7 +572,7 @@ private IEnumerator ServiceHealthMonitor()

while (_serviceHealthMonitoringEnabled && Application.isPlaying)
{
yield return new WaitForSeconds(healthCheckInterval);
yield return new WaitForSecondsRealtime(healthCheckInterval);

if (_state != LifecycleManagerState.Ready)
{
Expand Down
6 changes: 3 additions & 3 deletions Runtime/Client/LootLockerPresenceClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -478,7 +478,7 @@ private IEnumerator WaitForConnectionAndUpdateStatus(string status, Dictionary<s
int waitCount = 0;
while(!IsConnectedAndAuthenticated && waitCount < maxWaitTimes)
{
yield return new WaitForSeconds(0.1f);
yield return new WaitForSecondsRealtime(0.1f);
waitCount++;
}

Expand Down Expand Up @@ -1122,7 +1122,7 @@ private IEnumerator PingCoroutine()
while (IsConnectedAndAuthenticated)
{
SendPing();
yield return new WaitForSeconds(PING_INTERVAL);
yield return new WaitForSecondsRealtime(PING_INTERVAL);
}
}

Expand All @@ -1138,7 +1138,7 @@ private IEnumerator ScheduleReconnectCoroutine(float customDelay = -1f)
LootLockerLogger.Log($"Scheduling Presence reconnect attempt {reconnectAttempts}/{MAX_RECONNECT_ATTEMPTS} in {delayToUse} seconds", LootLockerLogger.LogLevel.Debug);
ChangeConnectionState(LootLockerPresenceConnectionState.Reconnecting);

yield return new WaitForSeconds(delayToUse);
yield return new WaitForSecondsRealtime(delayToUse);

if (shouldReconnect && connectionState == LootLockerPresenceConnectionState.Reconnecting)
{
Expand Down
2 changes: 1 addition & 1 deletion Runtime/Client/LootLockerPresenceManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1132,7 +1132,7 @@ private IEnumerator _AutoConnectExistingSessions()
ConnectPresence(state.ULID);

// Small delay between connections to avoid overwhelming the system
yield return new WaitForSeconds(0.1f);
yield return new WaitForSecondsRealtime(0.1f);
}
}
}
Expand Down
5 changes: 5 additions & 0 deletions Runtime/Client/LootLockerStateData.cs
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,11 @@ public void OverrideStateWriter(ILootLockerStateWriter newWriter)
}
}

public static ILootLockerStateWriter GetStateWriter()
{
return _stateWriter;
}

//==================================================
// Constants
//==================================================
Expand Down
45 changes: 45 additions & 0 deletions Runtime/Client/LootLockerStateWriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,19 @@ public interface ILootLockerStateWriter
/// <param name="key">The key to set the value for.</param>
/// <param name="value">The value to set.</param>
void SetString(string key, string value);
/// <summary>
/// Get an int from persistent storage. If the key does not exist then return the provided default value.
/// </summary>
/// <param name="key">The key to retrieve the value for.</param>
/// <param name="defaultValue">The value to return if the key does not exist.</param>
/// <returns>The value associated with the key, or the default value if the key does not exist.</returns>
int GetInt(string key, int defaultValue = 0);
/// <summary>
/// Set an integer in persistent storage.
/// </summary>
/// <param name="key">The key to set the value for.</param>
/// <param name="value">The value to set.</param>
void SetInt(string key, int value);

/// <summary>
/// Delete a key from persistent storage.
Expand Down Expand Up @@ -64,6 +77,17 @@ public string GetString(string key, string defaultValue = "")
return PlayerPrefs.GetString(key, defaultValue);
}

/// <summary>
/// Gets an int from PlayerPrefs.
/// </summary>
/// <param name="key">The key to retrieve the value for.</param>
/// <param name="defaultValue">The value to return if the key does not exist.</param>
/// <returns>The value associated with the key, or the default value if the key does not exist.</returns>
public int GetInt(string key, int defaultValue = 0)
{
return PlayerPrefs.GetInt(key, defaultValue);
}

/// <summary>
/// Checks if a key exists in PlayerPrefs.
/// </summary>
Expand All @@ -74,6 +98,17 @@ public bool HasKey(string key)
return PlayerPrefs.HasKey(key);
}

/// <summary>
/// Sets an int in PlayerPrefs and saves the changes.
/// </summary>
/// <param name="key">The key to set the value for.</param>
/// <param name="value">The value to set.</param>
public void SetInt(string key, int value)
{
PlayerPrefs.SetInt(key, value);
PlayerPrefs.Save();
}

/// <summary>
/// Sets a string in PlayerPrefs and saves the changes.
/// </summary>
Expand All @@ -98,6 +133,11 @@ public string GetString(string key, string defaultValue = "")
return defaultValue;
}

public int GetInt(string key, int defaultValue = 0)
{
return defaultValue;
}

public bool HasKey(string key)
{
return false;
Expand All @@ -107,5 +147,10 @@ public void SetString(string key, string value)
{
// Do nothing
}

public void SetInt(string key, int value)
{
// Do nothing
}
}
}
2 changes: 1 addition & 1 deletion Runtime/Editor/Editor UI/LootLockerAdminExtension.cs
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ public partial class LootLockerAdminExtension : EditorWindow
[MenuItem("Window/" + LootLockerConfig.PackageName + "/Tools/Clear Local Player Data", true, 101)]
public static bool ValidateClearLocalPlayerData()
{
return LootLockerConfig.current.enableEditorAdminExtension;
return true;
}

[MenuItem("Window/" + LootLockerConfig.PackageName + "/Tools/Clear Local Player Data", false, 101)]
Expand Down
Loading
Loading