From a0ac6d33f5ff352f7c0919c1544af1c5a5a9705d Mon Sep 17 00:00:00 2001 From: tudor <7089284+tudddorrr@users.noreply.github.com> Date: Sat, 23 May 2026 22:48:10 +0100 Subject: [PATCH] prop rejection events and error codes --- .../Talo/Runtime/APIs/BaseAPI.cs | 2 +- .../Talo/Runtime/APIs/ChannelsAPI.cs | 37 ++++++++++--- .../Talo/Runtime/APIs/FeedbackAPI.cs | 15 +++++- .../Talo/Runtime/APIs/LeaderboardsAPI.cs | 24 +++++++-- .../Talo/Runtime/APIs/PlayersAPI.cs | 6 +++ .../Talo/Runtime/Entities/RejectedProp.cs | 53 +++++++++++++++++++ .../Runtime/Entities/RejectedProp.cs.meta | 2 + .../ChannelStoragePropsSetResponse.cs | 1 + .../Responses/PlayersUpdateResponse.cs | 1 + .../Talo/Runtime/Utils/PlayerAuthException.cs | 4 +- .../Talo/Runtime/Utils/RequestException.cs | 13 +++++ .../Scripts/ChannelStorageDemoUIController.cs | 2 +- .../Playground/Scripts/Players/SetProp.cs | 18 ++++++- 13 files changed, 162 insertions(+), 16 deletions(-) create mode 100644 Assets/Talo Game Services/Talo/Runtime/Entities/RejectedProp.cs create mode 100644 Assets/Talo Game Services/Talo/Runtime/Entities/RejectedProp.cs.meta diff --git a/Assets/Talo Game Services/Talo/Runtime/APIs/BaseAPI.cs b/Assets/Talo Game Services/Talo/Runtime/APIs/BaseAPI.cs index 53d7f304..0cd26965 100644 --- a/Assets/Talo Game Services/Talo/Runtime/APIs/BaseAPI.cs +++ b/Assets/Talo Game Services/Talo/Runtime/APIs/BaseAPI.cs @@ -130,7 +130,7 @@ protected async Task Call( if (string.IsNullOrEmpty(errorCode)) { - throw new RequestException(www.responseCode, new Exception(message)); + throw new RequestException(www.responseCode, new Exception(message), www.downloadHandler.text); } else { diff --git a/Assets/Talo Game Services/Talo/Runtime/APIs/ChannelsAPI.cs b/Assets/Talo Game Services/Talo/Runtime/APIs/ChannelsAPI.cs index 1f0e22c9..a6391583 100644 --- a/Assets/Talo Game Services/Talo/Runtime/APIs/ChannelsAPI.cs +++ b/Assets/Talo Game Services/Talo/Runtime/APIs/ChannelsAPI.cs @@ -86,6 +86,7 @@ public class ChannelsAPI : BaseAPI public event Action OnOwnershipTransferred; public event Action OnChannelDeleted; public event Action OnChannelUpdated; + public event Action OnChannelPropsRejected; public event Action OnChannelStoragePropsFailedToSet; public event Action OnChannelStoragePropsUpdated; @@ -180,10 +181,22 @@ private async Task SendCreateChannelRequest(CreateChannelOptions option @private = options.isPrivate, temporaryMembership = options.temporaryMembership }); - var json = await Call(uri, "POST", content); - var res = JsonUtility.FromJson(json); - return res.channel; + try + { + var json = await Call(uri, "POST", content); + + var res = JsonUtility.FromJson(json); + return res.channel; + } + catch (RequestException ex) + { + if (ex.IsBadRequest()) + { + RejectedProp.TryEmit(ex.responseBody, OnChannelPropsRejected); + } + throw; + } } public async Task Create(CreateChannelOptions options) @@ -254,10 +267,22 @@ public async Task Update(int channelId, string name = "", int newOwnerA { content = JsonUtility.ToJson(new ChannelsUpdateOwnerRequest { name = name, newOwnerAliasId = newOwnerAliasId, props = props }); } - var json = await Call(uri, "PUT", content); - var res = JsonUtility.FromJson(json); - return res.channel; + try + { + var json = await Call(uri, "PUT", content); + + var res = JsonUtility.FromJson(json); + return res.channel; + } + catch (RequestException ex) + { + if (ex.IsBadRequest()) + { + RejectedProp.TryEmit(ex.responseBody, OnChannelPropsRejected); + } + throw; + } } public async Task Delete(int channelId) diff --git a/Assets/Talo Game Services/Talo/Runtime/APIs/FeedbackAPI.cs b/Assets/Talo Game Services/Talo/Runtime/APIs/FeedbackAPI.cs index b0dd7510..e1e2b4df 100644 --- a/Assets/Talo Game Services/Talo/Runtime/APIs/FeedbackAPI.cs +++ b/Assets/Talo Game Services/Talo/Runtime/APIs/FeedbackAPI.cs @@ -7,6 +7,8 @@ namespace TaloGameServices { public class FeedbackAPI : BaseAPI { + public event Action OnPropsRejected; + public FeedbackAPI() : base("v1/game-feedback") { } public async Task GetCategories() @@ -26,7 +28,18 @@ public async Task Send(string categoryInternalName, string comment, params (stri var propsArray = props.Select((propTuples) => new Prop(propTuples)).ToArray(); var content = JsonUtility.ToJson(new FeedbackPostRequest { comment = comment, props = propsArray }); - await Call(uri, "POST", content); + try + { + await Call(uri, "POST", content); + } + catch (RequestException ex) + { + if (ex.IsBadRequest()) + { + RejectedProp.TryEmit(ex.responseBody, OnPropsRejected); + } + throw; + } } } } diff --git a/Assets/Talo Game Services/Talo/Runtime/APIs/LeaderboardsAPI.cs b/Assets/Talo Game Services/Talo/Runtime/APIs/LeaderboardsAPI.cs index abd9b0e3..9420c4c1 100644 --- a/Assets/Talo Game Services/Talo/Runtime/APIs/LeaderboardsAPI.cs +++ b/Assets/Talo Game Services/Talo/Runtime/APIs/LeaderboardsAPI.cs @@ -45,6 +45,8 @@ public class LeaderboardsAPI : BaseAPI { private readonly LeaderboardEntriesManager _entriesManager = new(); + public event Action OnPropsRejected; + public LeaderboardsAPI() : base("v1/leaderboards") { } public List GetCachedEntries(string internalName, GetCachedEntriesOptions options = null) @@ -126,12 +128,24 @@ public async Task GetEntriesForCurrentPlayer(string var uri = new Uri($"{baseUrl}/{internalName}/entries"); var content = JsonUtility.ToJson(new LeaderboardsPostRequest { score = score, props = props }); - var json = await Call(uri, "POST", Prop.SanitiseJson(content)); - var res = JsonUtility.FromJson(json); - _entriesManager.UpsertEntry(internalName, res.entry, true); + try + { + var json = await Call(uri, "POST", Prop.SanitiseJson(content)); + + var res = JsonUtility.FromJson(json); + _entriesManager.UpsertEntry(internalName, res.entry, true); - return (res.entry, res.updated); + return (res.entry, res.updated); + } + catch (RequestException ex) + { + if (ex.IsBadRequest()) + { + RejectedProp.TryEmit(ex.responseBody, OnPropsRejected); + } + throw; + } } } -} +} \ No newline at end of file diff --git a/Assets/Talo Game Services/Talo/Runtime/APIs/PlayersAPI.cs b/Assets/Talo Game Services/Talo/Runtime/APIs/PlayersAPI.cs index 92952891..513230bd 100644 --- a/Assets/Talo Game Services/Talo/Runtime/APIs/PlayersAPI.cs +++ b/Assets/Talo Game Services/Talo/Runtime/APIs/PlayersAPI.cs @@ -21,6 +21,7 @@ public enum DebouncedOperation public event Action OnIdentificationStarted; public event Action OnIdentificationFailed; public event Action OnIdentityCleared; + public event Action OnPropsRejected; public static readonly string offlineDataPath = Application.persistentDataPath + "/ta.bin"; @@ -165,6 +166,11 @@ public async Task Update() Talo.CurrentPlayer = res.player; Talo.CurrentAlias.WriteOfflineAlias(); + if (res.rejectedProps != null && res.rejectedProps.Length > 0) + { + OnPropsRejected?.Invoke(res.rejectedProps); + } + return Talo.CurrentPlayer; } diff --git a/Assets/Talo Game Services/Talo/Runtime/Entities/RejectedProp.cs b/Assets/Talo Game Services/Talo/Runtime/Entities/RejectedProp.cs new file mode 100644 index 00000000..5f338fc1 --- /dev/null +++ b/Assets/Talo Game Services/Talo/Runtime/Entities/RejectedProp.cs @@ -0,0 +1,53 @@ +using System; +using UnityEngine; + +namespace TaloGameServices +{ + public enum PropRejectionReason + { + UNKNOWN_ERROR, + PROP_KEY_TOO_LONG, + PROP_VALUE_TOO_LONG, + PROP_ARRAY_TOO_LONG, + PROP_CONTAINS_PROFANITY, + PROP_KEY_RESERVED + } + + [Serializable] + public class RejectedProp + { + public string key; + public string error; + public string message; + + public PropRejectionReason GetReason() + { + if (Enum.TryParse(error, out PropRejectionReason reason)) + { + return reason; + } + return PropRejectionReason.UNKNOWN_ERROR; + } + + public static RejectedProp[] FromJson(string json) + { + var wrapper = JsonUtility.FromJson(json); + return wrapper?.rejectedProps ?? Array.Empty(); + } + + public static void TryEmit(string json, Action onRejected) + { + var rejectedProps = FromJson(json); + if (rejectedProps.Length > 0) + { + onRejected?.Invoke(rejectedProps); + } + } + } + + [Serializable] + public class RejectedPropWrapper + { + public RejectedProp[] rejectedProps; + } +} diff --git a/Assets/Talo Game Services/Talo/Runtime/Entities/RejectedProp.cs.meta b/Assets/Talo Game Services/Talo/Runtime/Entities/RejectedProp.cs.meta new file mode 100644 index 00000000..cebaf265 --- /dev/null +++ b/Assets/Talo Game Services/Talo/Runtime/Entities/RejectedProp.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 115a78a090dea474eacab67f7bc1acd3 \ No newline at end of file diff --git a/Assets/Talo Game Services/Talo/Runtime/Responses/ChannelStoragePropsSetResponse.cs b/Assets/Talo Game Services/Talo/Runtime/Responses/ChannelStoragePropsSetResponse.cs index aaf5f39d..25eaa6d2 100644 --- a/Assets/Talo Game Services/Talo/Runtime/Responses/ChannelStoragePropsSetResponse.cs +++ b/Assets/Talo Game Services/Talo/Runtime/Responses/ChannelStoragePropsSetResponse.cs @@ -7,6 +7,7 @@ public class ChannelStoragePropError { public string key; public string error; + public string message; } [Serializable] diff --git a/Assets/Talo Game Services/Talo/Runtime/Responses/PlayersUpdateResponse.cs b/Assets/Talo Game Services/Talo/Runtime/Responses/PlayersUpdateResponse.cs index db5ac5c9..8fe2e6b8 100644 --- a/Assets/Talo Game Services/Talo/Runtime/Responses/PlayersUpdateResponse.cs +++ b/Assets/Talo Game Services/Talo/Runtime/Responses/PlayersUpdateResponse.cs @@ -4,5 +4,6 @@ public class PlayersUpdateResponse { public Player player; + public RejectedProp[] rejectedProps; } } diff --git a/Assets/Talo Game Services/Talo/Runtime/Utils/PlayerAuthException.cs b/Assets/Talo Game Services/Talo/Runtime/Utils/PlayerAuthException.cs index 565b7b2d..e2f94628 100644 --- a/Assets/Talo Game Services/Talo/Runtime/Utils/PlayerAuthException.cs +++ b/Assets/Talo Game Services/Talo/Runtime/Utils/PlayerAuthException.cs @@ -16,7 +16,9 @@ public enum PlayerAuthErrorCode { VERIFICATION_EMAIL_REQUIRED, INVALID_EMAIL, NEW_IDENTIFIER_MATCHES_CURRENT_IDENTIFIER, - INVALID_MIGRATION_TARGET + INVALID_MIGRATION_TARGET, + EMAIL_TAKEN, + IDENTIFIER_PROFANITY } public class PlayerAuthException : Exception diff --git a/Assets/Talo Game Services/Talo/Runtime/Utils/RequestException.cs b/Assets/Talo Game Services/Talo/Runtime/Utils/RequestException.cs index 522c87b7..afc24c0f 100644 --- a/Assets/Talo Game Services/Talo/Runtime/Utils/RequestException.cs +++ b/Assets/Talo Game Services/Talo/Runtime/Utils/RequestException.cs @@ -5,6 +5,7 @@ namespace TaloGameServices public class RequestException : Exception { public long responseCode; + public string responseBody; public RequestException() { @@ -21,5 +22,17 @@ public RequestException(long responseCode, Exception inner) { this.responseCode = responseCode; } + + public RequestException(long responseCode, Exception inner, string responseBody) + : base($"{responseCode}: {inner.Message}", inner) + { + this.responseCode = responseCode; + this.responseBody = responseBody; + } + + public bool IsBadRequest() + { + return responseCode == 400 && !string.IsNullOrEmpty(responseBody); + } } } diff --git a/Assets/Talo Game Services/Talo/Samples/ChannelStorageDemo/Scripts/ChannelStorageDemoUIController.cs b/Assets/Talo Game Services/Talo/Samples/ChannelStorageDemo/Scripts/ChannelStorageDemoUIController.cs index 5343e1d2..abe713d9 100644 --- a/Assets/Talo Game Services/Talo/Samples/ChannelStorageDemo/Scripts/ChannelStorageDemoUIController.cs +++ b/Assets/Talo Game Services/Talo/Samples/ChannelStorageDemo/Scripts/ChannelStorageDemoUIController.cs @@ -143,7 +143,7 @@ private void OnChannelStoragePropsFailedToSet(Channel channel, ChannelStoragePro { foreach (var prop in errors) { - Debug.Log($"{prop.key}: {prop.error}"); + Debug.Log($"{prop.key}: {prop.message} ({prop.error})"); } } diff --git a/Assets/Talo Game Services/Talo/Samples/Playground/Scripts/Players/SetProp.cs b/Assets/Talo Game Services/Talo/Samples/Playground/Scripts/Players/SetProp.cs index ea9445a9..032341bf 100644 --- a/Assets/Talo Game Services/Talo/Samples/Playground/Scripts/Players/SetProp.cs +++ b/Assets/Talo Game Services/Talo/Samples/Playground/Scripts/Players/SetProp.cs @@ -1,5 +1,5 @@ using UnityEngine; -using System.Threading.Tasks; +using System; namespace TaloGameServices.Sample.Playground { @@ -7,6 +7,16 @@ public class SetProp : MonoBehaviour { public string key, value; + private void OnEnable() + { + Talo.Players.OnPropsRejected += OnPropsRejected; + } + + private void OnDisable() + { + Talo.Players.OnPropsRejected -= OnPropsRejected; + } + public void OnButtonClick() { UpdateProp(); @@ -31,5 +41,11 @@ private void UpdateProp() throw; } } + + private void OnPropsRejected(RejectedProp[] rejectedProps) + { + var reasons = string.Join(", ", Array.ConvertAll(rejectedProps, (rp) => $"[{rp.key}] {rp.message}")); + ResponseMessage.SetText($"Rejected props: {reasons}"); + } } }