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