From d9a06a7ae5a8e1ead09dcb8b405127cf69941f04 Mon Sep 17 00:00:00 2001
From: tudor <7089284+tudddorrr@users.noreply.github.com>
Date: Tue, 6 Jan 2026 22:38:53 +0000
Subject: [PATCH 01/12] update readme with player relationships
---
README.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/README.md b/README.md
index 3ce7dac..a7f8357 100644
--- a/README.md
+++ b/README.md
@@ -31,6 +31,7 @@ You'll need to init the submodules after cloning: `git submodule update --init`.
- ๐ฃ๏ธ [Game feedback](https://trytalo.com/feedback): Collect and manage feedback from your players.
- ๐ก๏ธ [Continuity](https://trytalo.com/continuity): Keep your data in-sync even when your players are offline.
- ๐ [Player presence](https://trytalo.com/players#presence): See if players are online and set custom statuses.
+- ๐ค [Player relationships](https://trytalo.com/player-relationships): Easily add friends, followers and social systems to your game.
## Samples included with the package
From dd9cc36c649cb9735479fb858458389fdbbd6a9c Mon Sep 17 00:00:00 2001
From: tudor <7089284+tudddorrr@users.noreply.github.com>
Date: Tue, 6 Jan 2026 22:55:32 +0000
Subject: [PATCH 02/12] add friends list ui to readme
---
README.md | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/README.md b/README.md
index a7f8357..4df8c4a 100644
--- a/README.md
+++ b/README.md
@@ -40,7 +40,8 @@ You'll need to init the submodules after cloning: `git submodule update --init`.
- ๐ **Authentication**: a registration/login flow, showing how to create player accounts and authenticate them.
- ๐ฎ **Playground**: a text-based playground allowing you to test identifying, events, stats and leaderboards.
- ๐ฌ **Chat**: showing how to send messages between players in a chat room using channels.
-- ๐ค **Channel storage**: showing how to store data that can be accessed by other players using channels.
+- ๐ฆ **Channel storage**: showing how to store data that can be accessed by other players using channels.
+- ๐ค **Friends list**: a friends list UI with friend statuses, incoming/outgoing requests and player-to-player broadcasts.
## Links
From aef405d306b2676b115527608ba714a7996711ee Mon Sep 17 00:00:00 2001
From: tudor <7089284+tudddorrr@users.noreply.github.com>
Date: Fri, 20 Feb 2026 20:35:25 +0000
Subject: [PATCH 03/12] update sln
---
unity.slnx | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/unity.slnx b/unity.slnx
index 8e1c5af..033166a 100644
--- a/unity.slnx
+++ b/unity.slnx
@@ -1,7 +1,7 @@
๏ปฟ
-
-
+
+
From 75b8310f09dc3631dd99c880b7c1851a6545395d Mon Sep 17 00:00:00 2001
From: tudor <7089284+tudddorrr@users.noreply.github.com>
Date: Fri, 20 Feb 2026 20:36:14 +0000
Subject: [PATCH 04/12] support leading debounce in DebouncedAPI
---
.../Talo/Runtime/APIs/DebouncedAPI.cs | 74 +++++++++++++++++--
CLAUDE.md | 4 +-
2 files changed, 69 insertions(+), 9 deletions(-)
diff --git a/Assets/Talo Game Services/Talo/Runtime/APIs/DebouncedAPI.cs b/Assets/Talo Game Services/Talo/Runtime/APIs/DebouncedAPI.cs
index 40f960a..2bb5735 100644
--- a/Assets/Talo Game Services/Talo/Runtime/APIs/DebouncedAPI.cs
+++ b/Assets/Talo Game Services/Talo/Runtime/APIs/DebouncedAPI.cs
@@ -9,14 +9,22 @@ public abstract class DebouncedAPI : BaseAPI where TOperation : Enum
{
private class DebouncedOperation
{
- public float nextUpdateTime;
- public bool hasPending;
+ public float windowEndTime;
+ public bool windowOpen;
+ public bool hasTrailingCallQueued;
+ public bool isExecuting;
}
private readonly Dictionary operations = new();
protected DebouncedAPI(string service) : base(service) { }
+ private void OpenWindow(DebouncedOperation op)
+ {
+ op.windowOpen = true;
+ op.windowEndTime = Time.realtimeSinceStartup + Talo.Settings.debounceTimerSeconds;
+ }
+
protected void Debounce(TOperation operation)
{
if (!operations.ContainsKey(operation))
@@ -24,8 +32,29 @@ protected void Debounce(TOperation operation)
operations[operation] = new DebouncedOperation();
}
- operations[operation].nextUpdateTime = Time.realtimeSinceStartup + Talo.Settings.debounceTimerSeconds;
- operations[operation].hasPending = true;
+ var op = operations[operation];
+
+ if (!op.windowOpen && !op.isExecuting)
+ {
+ // leading call: fire immediately and open the debounce window
+ op.hasTrailingCallQueued = false;
+ op.isExecuting = true;
+ OpenWindow(op);
+
+ ExecuteDebouncedOperation(operation).ContinueWith((t) => {
+ op.isExecuting = false;
+ if (t.IsFaulted)
+ {
+ Debug.LogError(t.Exception);
+ }
+ }, TaskScheduler.FromCurrentSynchronizationContext());
+ }
+ else
+ {
+ // window open or request in-flight: queue a trailing call and extend the window
+ op.hasTrailingCallQueued = true;
+ OpenWindow(op);
+ }
}
public async Task ProcessPendingUpdates()
@@ -34,16 +63,45 @@ public async Task ProcessPendingUpdates()
foreach (var kvp in operations)
{
- if (kvp.Value.hasPending && Time.realtimeSinceStartup >= kvp.Value.nextUpdateTime)
+ var op = kvp.Value;
+ var windowClosed = Time.realtimeSinceStartup >= op.windowEndTime;
+ if (windowClosed)
{
- keysToProcess.Add(kvp.Key);
+ if (op.hasTrailingCallQueued)
+ {
+ if (!op.isExecuting)
+ {
+ // window closed with a trailing call pending: execute it
+ keysToProcess.Add(kvp.Key);
+ }
+ else
+ {
+ // leading call still in-flight: delay trailing until it completes
+ OpenWindow(op);
+ }
+ }
+ else if (op.windowOpen)
+ {
+ // window closed with no trailing call: reset for the next leading call
+ op.windowOpen = false;
+ }
}
}
foreach (var key in keysToProcess)
{
- operations[key].hasPending = false;
- await ExecuteDebouncedOperation(key);
+ var op = operations[key];
+ op.hasTrailingCallQueued = false;
+ op.isExecuting = true;
+ try
+ {
+ await ExecuteDebouncedOperation(key);
+ }
+ finally
+ {
+ op.isExecuting = false;
+ op.windowOpen = false;
+ }
}
}
diff --git a/CLAUDE.md b/CLAUDE.md
index 2e506f7..c88bd0b 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -49,6 +49,8 @@ Events are batched and flushed on application quit/pause/focus loss. On WebGL, e
### Debouncing
Player updates and save updates are debounced to prevent excessive API calls during rapid property changes. APIs that need debouncing inherit from `DebouncedAPI` (a generic base class) and define a `DebouncedOperation` enum for type-safe operation keys. The base class uses a dictionary to track multiple debounced operations independently.
+The debounce is **leading and trailing**: the first call fires immediately (leading), and if further calls arrive during the debounce window they are coalesced into a single trailing call executed after the window closes. The window is defined by `debounceTimerSeconds` (default: 1s) and resets on each subsequent call.
+
To add debouncing to an API:
1. Define a public `enum DebouncedOperation` with your debounced operations
2. Inherit from `DebouncedAPI`
@@ -56,7 +58,7 @@ To add debouncing to an API:
4. Implement `ExecuteDebouncedOperation(DebouncedOperation operation)` with a switch statement
5. The base class's `ProcessPendingUpdates()` is called by `TaloManager.Update()` every frame
-Example: `PlayersAPI` defines `enum DebouncedOperation { Update }` and inherits from `DebouncedAPI`. When `Player.SetProp()` is called, it calls `Debounce(DebouncedOperation.Update)`, which queues the update to be executed after `debounceTimerSeconds` (default: 1s). Multiple property changes within the debounce window result in a single API call.
+Example: `PlayersAPI` defines `enum DebouncedOperation { Update }` and inherits from `DebouncedAPI`. When `Player.SetProp()` is called, it calls `Debounce(DebouncedOperation.Update)`. The first call fires immediately; subsequent calls within the debounce window result in a single trailing API call at the end of the window.
## Common Development Commands
From cad1018167f8390089551e105150ece4b35e5de1 Mon Sep 17 00:00:00 2001
From: tudor <7089284+tudddorrr@users.noreply.github.com>
Date: Sat, 21 Feb 2026 00:11:49 +0000
Subject: [PATCH 05/12] add contributing.md
---
CONTRIBUTING.md | 53 +++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 53 insertions(+)
create mode 100644 CONTRIBUTING.md
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 0000000..f73989a
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,53 @@
+# Contributing
+
+## Setup
+
+1. Clone the repository with submodules:
+ ```bash
+ git clone --recurse-submodules
+ # or after cloning:
+ git submodule update --init
+ ```
+
+2. Open the project in **Unity 6000.0.59f2** (or later).
+
+## Project structure
+
+```
+Assets/Talo Game Services/Talo/
+โโโ Runtime/
+โ โโโ APIs/ # One file per API (extend BaseAPI or DebouncedAPI)
+โ โโโ Entities/ # Data models (Player, GameSave, LeaderboardEntry, etc.)
+โ โโโ Requests/ # Request payload classes
+โ โโโ Responses/ # Response payload classes
+โ โโโ SocketRequests/ # WebSocket message types (outbound)
+โ โโโ SocketResponses/ # WebSocket message types (inbound)
+โ โโโ Utils/ # Internal managers and helpers
+โ โโโ Vendor/ # Third-party dependencies (WebSocket client)
+โ โโโ Talo.cs # Main static entry point
+โ โโโ TaloManager.cs # MonoBehaviour lifecycle manager
+โ โโโ TaloSocket.cs # WebSocket connection handler
+โ โโโ TaloSettings.cs # Add new settings here
+โโโ Tests/ # NUnit test suite
+โโโ Samples/ # Demo scenes (Leaderboards, Saves, Auth, Chat, etc.)
+```
+
+## Code style
+
+Follow the patterns established in existing API and entity files:
+
+- All API classes inherit from `BaseAPI` (or `DebouncedAPI` when debouncing is needed)
+- Use `async`/`await` for all network operations
+- Request and response types live in their respective `Requests/` and `Responses/` folders
+
+## Testing your changes
+
+Tests use the **Unity Test Framework** (NUnit). Run them inside the editor via **Window > General > Test Runner**.
+
+Consider adding tests where relevant.
+
+## Submitting a PR
+
+- Keep PRs focused โ one feature or fix per PR
+- Target the `develop` branch
+- Use the Playground sample scene (`Samples/Playground`) to test your changes interactively.
From 2a71cd72e11694a5075bd77d7c39b8571abfd472 Mon Sep 17 00:00:00 2001
From: tudor <7089284+tudddorrr@users.noreply.github.com>
Date: Sun, 22 Feb 2026 01:23:55 +0000
Subject: [PATCH 06/12] fix unnecessary leaderboard entry position bumping
---
.../Utils/LeaderboardEntriesManager.cs | 21 +++++++++-----
.../LeaderboardEntriesManagerTests.cs | 29 +++++++++++++++++++
2 files changed, 42 insertions(+), 8 deletions(-)
diff --git a/Assets/Talo Game Services/Talo/Runtime/Utils/LeaderboardEntriesManager.cs b/Assets/Talo Game Services/Talo/Runtime/Utils/LeaderboardEntriesManager.cs
index a086cfa..5acadd8 100644
--- a/Assets/Talo Game Services/Talo/Runtime/Utils/LeaderboardEntriesManager.cs
+++ b/Assets/Talo Game Services/Talo/Runtime/Utils/LeaderboardEntriesManager.cs
@@ -5,7 +5,7 @@ namespace TaloGameServices
{
public class LeaderboardEntriesManager
{
- private Dictionary> _currentEntries = new Dictionary>();
+ private readonly Dictionary> _currentEntries = new();
public List GetEntries(string internalName)
{
@@ -16,7 +16,7 @@ public List GetEntries(string internalName)
return _currentEntries[internalName];
}
- public void UpsertEntry(string internalName, LeaderboardEntry entry, bool bumpPositions = false)
+ public void UpsertEntry(string internalName, LeaderboardEntry upsertEntry, bool bumpPositions = false)
{
if (!_currentEntries.ContainsKey(internalName))
{
@@ -26,18 +26,23 @@ public void UpsertEntry(string internalName, LeaderboardEntry entry, bool bumpPo
var entries = _currentEntries[internalName];
// ensure there isn't an existing entry
- entries.RemoveAll((e) => e.id == entry.id);
+ entries.RemoveAll((e) => e.id == upsertEntry.id);
- int insertPosition = FindInsertPosition(entries, entry);
- entries.Insert(insertPosition, entry);
+ int insertPosition = FindInsertPosition(entries, upsertEntry);
+ entries.Insert(insertPosition, upsertEntry);
if (bumpPositions)
{
- foreach (var e in entries)
+ // find any collisions and bump any subsequent entries down by 1
+ int collisionIndex = entries.FindIndex((e) => e.id != upsertEntry.id && e.position == upsertEntry.position);
+ if (collisionIndex != -1)
{
- if (e.id != entry.id && e.position >= entry.position)
+ for (int i = collisionIndex; i < entries.Count; i++)
{
- e.position += 1;
+ if (entries[i].id != upsertEntry.id)
+ {
+ entries[i].position += 1;
+ }
}
}
}
diff --git a/Assets/Talo Game Services/Talo/Tests/LeaderboardsAPI/LeaderboardEntriesManagerTests.cs b/Assets/Talo Game Services/Talo/Tests/LeaderboardsAPI/LeaderboardEntriesManagerTests.cs
index 4fff1e0..72faed1 100644
--- a/Assets/Talo Game Services/Talo/Tests/LeaderboardsAPI/LeaderboardEntriesManagerTests.cs
+++ b/Assets/Talo Game Services/Talo/Tests/LeaderboardsAPI/LeaderboardEntriesManagerTests.cs
@@ -191,6 +191,35 @@ public void UpsertEntry_BumpPositions_OnlyBumpsAffectedEntries()
Assert.AreEqual(3, entries[3].position); // bumped from 2 to 3
}
+ [Test]
+ public void UpsertEntry_BumpPositions_DoesNotBumpWhenPlayerImprovesButKeepsSamePosition()
+ {
+ var entry1 = new LeaderboardEntry { id = 1, score = 100f, position = 0, leaderboardSortMode = "desc" };
+ var entry2 = new LeaderboardEntry { id = 2, score = 80f, position = 1, leaderboardSortMode = "desc" };
+ var entry3 = new LeaderboardEntry { id = 3, score = 60f, position = 2, leaderboardSortMode = "desc" };
+
+ manager.UpsertEntry("test", entry1);
+ manager.UpsertEntry("test", entry2);
+ manager.UpsertEntry("test", entry3);
+
+ // entry1 improves their score but stays at position 0 - no one should be bumped
+ var updatedEntry1 = new LeaderboardEntry { id = 1, score = 110f, position = 0, leaderboardSortMode = "desc" };
+ manager.UpsertEntry("test", updatedEntry1, bumpPositions: true);
+
+ var entries = manager.GetEntries("test");
+
+ Assert.AreEqual(3, entries.Count);
+
+ Assert.AreEqual(1, entries[0].id);
+ Assert.AreEqual(0, entries[0].position); // still first
+
+ Assert.AreEqual(2, entries[1].id);
+ Assert.AreEqual(1, entries[1].position); // unchanged
+
+ Assert.AreEqual(3, entries[2].id);
+ Assert.AreEqual(2, entries[2].position); // unchanged
+ }
+
[Test]
public void UpsertEntry_NoBumpPositions_PreservesExistingPositions()
{
From d7003e0e9cf2b3ad83ea62c2b82cb74475087b2f Mon Sep 17 00:00:00 2001
From: tudor <7089284+tudddorrr@users.noreply.github.com>
Date: Sun, 22 Feb 2026 09:01:04 +0000
Subject: [PATCH 07/12] better collision comment
---
.../Talo/Runtime/Utils/LeaderboardEntriesManager.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Assets/Talo Game Services/Talo/Runtime/Utils/LeaderboardEntriesManager.cs b/Assets/Talo Game Services/Talo/Runtime/Utils/LeaderboardEntriesManager.cs
index 5acadd8..3373c4f 100644
--- a/Assets/Talo Game Services/Talo/Runtime/Utils/LeaderboardEntriesManager.cs
+++ b/Assets/Talo Game Services/Talo/Runtime/Utils/LeaderboardEntriesManager.cs
@@ -33,7 +33,7 @@ public void UpsertEntry(string internalName, LeaderboardEntry upsertEntry, bool
if (bumpPositions)
{
- // find any collisions and bump any subsequent entries down by 1
+ // if we find a collision, bump subsequent entries down by 1
int collisionIndex = entries.FindIndex((e) => e.id != upsertEntry.id && e.position == upsertEntry.position);
if (collisionIndex != -1)
{
From 18dd44801ca4d70ce5d7fd3cd82fdac23488c8c0 Mon Sep 17 00:00:00 2001
From: tudor <7089284+tudddorrr@users.noreply.github.com>
Date: Sun, 22 Feb 2026 09:31:52 +0000
Subject: [PATCH 08/12] upsertEntry -> entryToUpsert
---
.../Talo/Runtime/Utils/LeaderboardEntriesManager.cs | 12 ++++++------
1 file changed, 6 insertions(+), 6 deletions(-)
diff --git a/Assets/Talo Game Services/Talo/Runtime/Utils/LeaderboardEntriesManager.cs b/Assets/Talo Game Services/Talo/Runtime/Utils/LeaderboardEntriesManager.cs
index 3373c4f..ff5be5f 100644
--- a/Assets/Talo Game Services/Talo/Runtime/Utils/LeaderboardEntriesManager.cs
+++ b/Assets/Talo Game Services/Talo/Runtime/Utils/LeaderboardEntriesManager.cs
@@ -16,7 +16,7 @@ public List GetEntries(string internalName)
return _currentEntries[internalName];
}
- public void UpsertEntry(string internalName, LeaderboardEntry upsertEntry, bool bumpPositions = false)
+ public void UpsertEntry(string internalName, LeaderboardEntry entryToUpsert, bool bumpPositions = false)
{
if (!_currentEntries.ContainsKey(internalName))
{
@@ -26,20 +26,20 @@ public void UpsertEntry(string internalName, LeaderboardEntry upsertEntry, bool
var entries = _currentEntries[internalName];
// ensure there isn't an existing entry
- entries.RemoveAll((e) => e.id == upsertEntry.id);
+ entries.RemoveAll((e) => e.id == entryToUpsert.id);
- int insertPosition = FindInsertPosition(entries, upsertEntry);
- entries.Insert(insertPosition, upsertEntry);
+ int insertPosition = FindInsertPosition(entries, entryToUpsert);
+ entries.Insert(insertPosition, entryToUpsert);
if (bumpPositions)
{
// if we find a collision, bump subsequent entries down by 1
- int collisionIndex = entries.FindIndex((e) => e.id != upsertEntry.id && e.position == upsertEntry.position);
+ int collisionIndex = entries.FindIndex((e) => e.id != entryToUpsert.id && e.position == entryToUpsert.position);
if (collisionIndex != -1)
{
for (int i = collisionIndex; i < entries.Count; i++)
{
- if (entries[i].id != upsertEntry.id)
+ if (entries[i].id != entryToUpsert.id)
{
entries[i].position += 1;
}
From f573a7cf1fcbe161eb29f0be88ff2975b1501c66 Mon Sep 17 00:00:00 2001
From: tudor <7089284+tudddorrr@users.noreply.github.com>
Date: Tue, 24 Feb 2026 20:54:39 +0000
Subject: [PATCH 09/12] add api for changing a player with auth's identifier
---
.../Talo/Runtime/APIs/PlayerAuthAPI.cs | 15 ++++++++++++++-
.../Requests/PlayerAuthChangeIdentifierRequest.cs | 9 +++++++++
.../PlayerAuthChangeIdentifierRequest.cs.meta | 2 ++
.../PlayerAuthChangeIdentifierResponse.cs | 8 ++++++++
.../PlayerAuthChangeIdentifierResponse.cs.meta | 2 ++
.../Talo/Runtime/Utils/PlayerAuthException.cs | 3 ++-
.../Talo/Runtime/Utils/SessionManager.cs | 13 ++++++++++++-
.../Settings/Panel Settings.asset | 3 +++
8 files changed, 52 insertions(+), 3 deletions(-)
create mode 100644 Assets/Talo Game Services/Talo/Runtime/Requests/PlayerAuthChangeIdentifierRequest.cs
create mode 100644 Assets/Talo Game Services/Talo/Runtime/Requests/PlayerAuthChangeIdentifierRequest.cs.meta
create mode 100644 Assets/Talo Game Services/Talo/Runtime/Responses/PlayerAuthChangeIdentifierResponse.cs
create mode 100644 Assets/Talo Game Services/Talo/Runtime/Responses/PlayerAuthChangeIdentifierResponse.cs.meta
diff --git a/Assets/Talo Game Services/Talo/Runtime/APIs/PlayerAuthAPI.cs b/Assets/Talo Game Services/Talo/Runtime/APIs/PlayerAuthAPI.cs
index 0c342ca..1bc3b55 100644
--- a/Assets/Talo Game Services/Talo/Runtime/APIs/PlayerAuthAPI.cs
+++ b/Assets/Talo Game Services/Talo/Runtime/APIs/PlayerAuthAPI.cs
@@ -6,7 +6,7 @@ namespace TaloGameServices
{
public class PlayerAuthAPI : BaseAPI
{
- private SessionManager _sessionManager = new();
+ private readonly SessionManager _sessionManager = new();
public SessionManager SessionManager => _sessionManager;
@@ -114,6 +114,19 @@ public async Task ChangeEmail(string currentPassword, string newEmail)
await Call(uri, "POST", content);
}
+ public async Task ChangeIdentifier(string currentPassword, string newIdentifier)
+ {
+ var uri = new Uri($"{baseUrl}/change_identifier");
+ string content = JsonUtility.ToJson(new PlayerAuthChangeIdentifierRequest {
+ currentPassword = currentPassword,
+ newIdentifier = newIdentifier
+ });
+ var json = await Call(uri, "POST", content);
+
+ var res = JsonUtility.FromJson(json);
+ _sessionManager.HandleIdentifierUpdated(res);
+ }
+
public async Task ForgotPassword(string email)
{
var uri = new Uri($"{baseUrl}/forgot_password");
diff --git a/Assets/Talo Game Services/Talo/Runtime/Requests/PlayerAuthChangeIdentifierRequest.cs b/Assets/Talo Game Services/Talo/Runtime/Requests/PlayerAuthChangeIdentifierRequest.cs
new file mode 100644
index 0000000..84c8755
--- /dev/null
+++ b/Assets/Talo Game Services/Talo/Runtime/Requests/PlayerAuthChangeIdentifierRequest.cs
@@ -0,0 +1,9 @@
+namespace TaloGameServices
+{
+ [System.Serializable]
+ public class PlayerAuthChangeIdentifierRequest
+ {
+ public string currentPassword;
+ public string newIdentifier;
+ }
+}
diff --git a/Assets/Talo Game Services/Talo/Runtime/Requests/PlayerAuthChangeIdentifierRequest.cs.meta b/Assets/Talo Game Services/Talo/Runtime/Requests/PlayerAuthChangeIdentifierRequest.cs.meta
new file mode 100644
index 0000000..9f9ddad
--- /dev/null
+++ b/Assets/Talo Game Services/Talo/Runtime/Requests/PlayerAuthChangeIdentifierRequest.cs.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: 57cdb010475b8496f8a1f16fc88bb404
\ No newline at end of file
diff --git a/Assets/Talo Game Services/Talo/Runtime/Responses/PlayerAuthChangeIdentifierResponse.cs b/Assets/Talo Game Services/Talo/Runtime/Responses/PlayerAuthChangeIdentifierResponse.cs
new file mode 100644
index 0000000..be610d9
--- /dev/null
+++ b/Assets/Talo Game Services/Talo/Runtime/Responses/PlayerAuthChangeIdentifierResponse.cs
@@ -0,0 +1,8 @@
+๏ปฟnamespace TaloGameServices
+{
+ [System.Serializable]
+ public class PlayerAuthChangeIdentifierResponse
+ {
+ public PlayerAlias alias;
+ }
+}
diff --git a/Assets/Talo Game Services/Talo/Runtime/Responses/PlayerAuthChangeIdentifierResponse.cs.meta b/Assets/Talo Game Services/Talo/Runtime/Responses/PlayerAuthChangeIdentifierResponse.cs.meta
new file mode 100644
index 0000000..a265741
--- /dev/null
+++ b/Assets/Talo Game Services/Talo/Runtime/Responses/PlayerAuthChangeIdentifierResponse.cs.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: bfb958912de184a41ac04d92e78ea63b
\ No newline at end of file
diff --git a/Assets/Talo Game Services/Talo/Runtime/Utils/PlayerAuthException.cs b/Assets/Talo Game Services/Talo/Runtime/Utils/PlayerAuthException.cs
index 47e7f6d..9852771 100644
--- a/Assets/Talo Game Services/Talo/Runtime/Utils/PlayerAuthException.cs
+++ b/Assets/Talo Game Services/Talo/Runtime/Utils/PlayerAuthException.cs
@@ -14,7 +14,8 @@ public enum PlayerAuthErrorCode {
NEW_EMAIL_MATCHES_CURRENT_EMAIL,
PASSWORD_RESET_CODE_INVALID,
VERIFICATION_EMAIL_REQUIRED,
- INVALID_EMAIL
+ INVALID_EMAIL,
+ NEW_IDENTIFIER_MATCHES_CURRENT_IDENTIFIER
}
public class PlayerAuthException : Exception
diff --git a/Assets/Talo Game Services/Talo/Runtime/Utils/SessionManager.cs b/Assets/Talo Game Services/Talo/Runtime/Utils/SessionManager.cs
index 6c855d9..5a4145a 100644
--- a/Assets/Talo Game Services/Talo/Runtime/Utils/SessionManager.cs
+++ b/Assets/Talo Game Services/Talo/Runtime/Utils/SessionManager.cs
@@ -15,10 +15,15 @@ public void HandleSessionCreated(PlayerAuthSessionResponse res)
Talo.Socket.SetSocketToken(res.socketToken);
}
+ private void SetIdentifierPlayerPref()
+ {
+ PlayerPrefs.SetString("TaloSessionIdentifier", Talo.CurrentAlias.identifier);
+ }
+
private void SaveSession(string sessionToken)
{
PlayerPrefs.SetString("TaloSessionToken", sessionToken);
- PlayerPrefs.SetString("TaloSessionIdentifier", Talo.CurrentAlias.identifier);
+ SetIdentifierPlayerPref();
}
public async Task ClearSession()
@@ -42,5 +47,11 @@ public bool CheckForSession()
{
return !string.IsNullOrEmpty(GetSessionToken());
}
+
+ public void HandleIdentifierUpdated(PlayerAuthChangeIdentifierResponse res)
+ {
+ Talo.CurrentAlias = res.alias;
+ SetIdentifierPlayerPref();
+ }
}
}
diff --git a/Assets/Talo Game Services/Talo/Samples/AuthenticationDemo/Settings/Panel Settings.asset b/Assets/Talo Game Services/Talo/Samples/AuthenticationDemo/Settings/Panel Settings.asset
index f0f27ab..2298b2f 100644
--- a/Assets/Talo Game Services/Talo/Samples/AuthenticationDemo/Settings/Panel Settings.asset
+++ b/Assets/Talo Game Services/Talo/Samples/AuthenticationDemo/Settings/Panel Settings.asset
@@ -41,6 +41,9 @@ MonoBehaviour:
m_AtlasBlitShader: {fileID: 9101, guid: 0000000000000000f000000000000000, type: 0}
m_RuntimeShader: {fileID: 9100, guid: 0000000000000000f000000000000000, type: 0}
m_RuntimeWorldShader: {fileID: 9102, guid: 0000000000000000f000000000000000, type: 0}
+ m_SDFShader: {fileID: 19011, guid: 0000000000000000f000000000000000, type: 0}
+ m_BitmapShader: {fileID: 9001, guid: 0000000000000000f000000000000000, type: 0}
+ m_SpriteShader: {fileID: 19012, guid: 0000000000000000f000000000000000, type: 0}
m_ICUDataAsset: {fileID: 0}
forceGammaRendering: 0
textSettings: {fileID: 0}
From 5102dd4269819f881dd13af2befcf0fec76232f4 Mon Sep 17 00:00:00 2001
From: tudor <7089284+tudddorrr@users.noreply.github.com>
Date: Tue, 24 Feb 2026 21:41:01 +0000
Subject: [PATCH 10/12] write new identifier to offline alias cache
---
.../Talo/Runtime/APIs/PlayersAPI.cs | 13 +++----------
.../Talo/Runtime/Entities/PlayerAlias.cs | 15 ++++++++++++++-
.../Talo/Runtime/Utils/SessionManager.cs | 1 +
3 files changed, 18 insertions(+), 11 deletions(-)
diff --git a/Assets/Talo Game Services/Talo/Runtime/APIs/PlayersAPI.cs b/Assets/Talo Game Services/Talo/Runtime/APIs/PlayersAPI.cs
index d7dab0b..c84d74b 100644
--- a/Assets/Talo Game Services/Talo/Runtime/APIs/PlayersAPI.cs
+++ b/Assets/Talo Game Services/Talo/Runtime/APIs/PlayersAPI.cs
@@ -22,7 +22,7 @@ public enum DebouncedOperation
public event Action OnIdentificationFailed;
public event Action OnIdentityCleared;
- private readonly string offlineDataPath = Application.persistentDataPath + "/ta.bin";
+ public static readonly string offlineDataPath = Application.persistentDataPath + "/ta.bin";
public PlayersAPI() : base("v1/players")
{
@@ -83,7 +83,7 @@ public async Task Identify(string service, string identifier)
var res = JsonUtility.FromJson(json);
var alias = res.alias;
- WriteOfflineAlias(alias);
+ alias.WriteOfflineAlias();
return await HandleIdentifySuccess(alias, res.socketToken);
}
catch
@@ -133,7 +133,7 @@ public async Task Update()
var res = JsonUtility.FromJson(json);
Talo.CurrentPlayer = res.player;
- WriteOfflineAlias(Talo.CurrentAlias);
+ Talo.CurrentAlias.WriteOfflineAlias();
return Talo.CurrentPlayer;
}
@@ -191,13 +191,6 @@ private async Task IdentifyOffline(string service, string identifier)
}
}
- private void WriteOfflineAlias(PlayerAlias alias)
- {
- if (!Talo.Settings.cachePlayerOnIdentify) return;
- var content = JsonUtility.ToJson(alias);
- Talo.Crypto.WriteFileContent(offlineDataPath, content);
- }
-
private PlayerAlias GetOfflineAlias()
{
if (!Talo.Settings.cachePlayerOnIdentify || !File.Exists(offlineDataPath)) return null;
diff --git a/Assets/Talo Game Services/Talo/Runtime/Entities/PlayerAlias.cs b/Assets/Talo Game Services/Talo/Runtime/Entities/PlayerAlias.cs
index 41b5ee3..4a7e4a5 100644
--- a/Assets/Talo Game Services/Talo/Runtime/Entities/PlayerAlias.cs
+++ b/Assets/Talo Game Services/Talo/Runtime/Entities/PlayerAlias.cs
@@ -1,4 +1,6 @@
-๏ปฟnamespace TaloGameServices
+๏ปฟusing UnityEngine;
+
+namespace TaloGameServices
{
[System.Serializable]
public class PlayerAlias
@@ -12,5 +14,16 @@ public bool MatchesIdentifyRequest(string service, string identifier)
{
return this.service == service && this.identifier == identifier;
}
+
+ public void WriteOfflineAlias()
+ {
+ if (!Talo.Settings.cachePlayerOnIdentify)
+ {
+ return;
+ }
+
+ var content = JsonUtility.ToJson(this);
+ Talo.Crypto.WriteFileContent(PlayersAPI.offlineDataPath, content);
+ }
}
}
diff --git a/Assets/Talo Game Services/Talo/Runtime/Utils/SessionManager.cs b/Assets/Talo Game Services/Talo/Runtime/Utils/SessionManager.cs
index 5a4145a..6ac0fab 100644
--- a/Assets/Talo Game Services/Talo/Runtime/Utils/SessionManager.cs
+++ b/Assets/Talo Game Services/Talo/Runtime/Utils/SessionManager.cs
@@ -51,6 +51,7 @@ public bool CheckForSession()
public void HandleIdentifierUpdated(PlayerAuthChangeIdentifierResponse res)
{
Talo.CurrentAlias = res.alias;
+ Talo.CurrentAlias.WriteOfflineAlias();
SetIdentifierPlayerPref();
}
}
From 6aa3e0882a54358a019c3b782244089a311bd9bb Mon Sep 17 00:00:00 2001
From: tudor <7089284+tudddorrr@users.noreply.github.com>
Date: Tue, 24 Feb 2026 22:14:50 +0000
Subject: [PATCH 11/12] add playerId filter to GetEntriesOptions
---
.../Talo/Runtime/APIs/LeaderboardsAPI.cs | 7 +++++--
1 file changed, 5 insertions(+), 2 deletions(-)
diff --git a/Assets/Talo Game Services/Talo/Runtime/APIs/LeaderboardsAPI.cs b/Assets/Talo Game Services/Talo/Runtime/APIs/LeaderboardsAPI.cs
index bafd90d..48f07ad 100644
--- a/Assets/Talo Game Services/Talo/Runtime/APIs/LeaderboardsAPI.cs
+++ b/Assets/Talo Game Services/Talo/Runtime/APIs/LeaderboardsAPI.cs
@@ -10,6 +10,7 @@ public class GetEntriesOptions
{
public int page = 0;
public int aliasId = -1;
+ public string playerId = "";
public bool includeArchived = false;
public string propKey = "";
public string propValue = "";
@@ -20,6 +21,7 @@ public string ToQueryString()
{
var query = new Dictionary { ["page"] = page.ToString() };
if (aliasId != -1) query["aliasId"] = aliasId.ToString();
+ if (!string.IsNullOrEmpty(playerId)) query["playerId"] = playerId;
if (includeArchived) query["withDeleted"] = "1";
if (!string.IsNullOrEmpty(propKey)) query["propKey"] = propKey;
if (!string.IsNullOrEmpty(propValue)) query["propValue"] = propValue;
@@ -32,7 +34,7 @@ public string ToQueryString()
public class LeaderboardsAPI : BaseAPI
{
- private LeaderboardEntriesManager _entriesManager = new();
+ private readonly LeaderboardEntriesManager _entriesManager = new();
public LeaderboardsAPI() : base("v1/leaderboards") { }
@@ -65,6 +67,7 @@ public async Task GetEntries(string internalName, Ge
return res;
}
+ [Obsolete("Use GetEntries(string internalName, GetEntriesOptions options) with the aliasId or playerId option instead.")]
public async Task GetEntriesForCurrentPlayer(string internalName, GetEntriesOptions options = null)
{
Talo.IdentityCheck();
@@ -86,7 +89,7 @@ public async Task GetEntries(string internalName, in
});
}
- [Obsolete("Use GetEntriesForCurrentPlayer(string internalName, GetEntriesOptions options) instead.")]
+ [Obsolete("Use GetEntries(string internalName, GetEntriesOptions options) with the aliasId or playerId option instead.")]
public async Task GetEntriesForCurrentPlayer(string internalName, int page, bool includeArchived = false)
{
Talo.IdentityCheck();
From 74bf1bf4f20a9c02872771c36ded66161be10c36 Mon Sep 17 00:00:00 2001
From: tudor <7089284+tudddorrr@users.noreply.github.com>
Date: Tue, 24 Feb 2026 22:47:36 +0000
Subject: [PATCH 12/12] 0.52.0
---
Assets/Talo Game Services/Talo/Runtime/APIs/BaseAPI.cs | 2 +-
Assets/Talo Game Services/Talo/VERSION | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/Assets/Talo Game Services/Talo/Runtime/APIs/BaseAPI.cs b/Assets/Talo Game Services/Talo/Runtime/APIs/BaseAPI.cs
index 979f3cc..ac4c256 100644
--- a/Assets/Talo Game Services/Talo/Runtime/APIs/BaseAPI.cs
+++ b/Assets/Talo Game Services/Talo/Runtime/APIs/BaseAPI.cs
@@ -9,7 +9,7 @@ namespace TaloGameServices
public class BaseAPI
{
// automatically updated with a pre-commit hook
- private const string ClientVersion = "0.51.0";
+ private const string ClientVersion = "0.52.0";
protected string baseUrl;
diff --git a/Assets/Talo Game Services/Talo/VERSION b/Assets/Talo Game Services/Talo/VERSION
index c5d4cee..4f9b378 100644
--- a/Assets/Talo Game Services/Talo/VERSION
+++ b/Assets/Talo Game Services/Talo/VERSION
@@ -1 +1 @@
-0.51.0
+0.52.0