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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Assets/Talo Game Services/Talo/Runtime/APIs/BaseAPI.cs
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ protected async Task<string> 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
{
Expand Down
37 changes: 31 additions & 6 deletions Assets/Talo Game Services/Talo/Runtime/APIs/ChannelsAPI.cs
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ public class ChannelsAPI : BaseAPI
public event Action<Channel, PlayerAlias> OnOwnershipTransferred;
public event Action<Channel> OnChannelDeleted;
public event Action<Channel, string[]> OnChannelUpdated;
public event Action<RejectedProp[]> OnChannelPropsRejected;
public event Action<Channel, ChannelStoragePropError[]> OnChannelStoragePropsFailedToSet;
public event Action<Channel, ChannelStorageProp[], ChannelStorageProp[]> OnChannelStoragePropsUpdated;

Expand Down Expand Up @@ -180,10 +181,22 @@ private async Task<Channel> SendCreateChannelRequest(CreateChannelOptions option
@private = options.isPrivate,
temporaryMembership = options.temporaryMembership
});
var json = await Call(uri, "POST", content);

var res = JsonUtility.FromJson<ChannelResponse>(json);
return res.channel;
try
{
var json = await Call(uri, "POST", content);

var res = JsonUtility.FromJson<ChannelResponse>(json);
return res.channel;
}
catch (RequestException ex)
{
if (ex.IsBadRequest())
{
RejectedProp.TryEmit(ex.responseBody, OnChannelPropsRejected);
}
throw;
}
}

public async Task<Channel> Create(CreateChannelOptions options)
Expand Down Expand Up @@ -254,10 +267,22 @@ public async Task<Channel> 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<ChannelResponse>(json);
return res.channel;
try
{
var json = await Call(uri, "PUT", content);

var res = JsonUtility.FromJson<ChannelResponse>(json);
return res.channel;
}
catch (RequestException ex)
{
if (ex.IsBadRequest())
{
RejectedProp.TryEmit(ex.responseBody, OnChannelPropsRejected);
}
throw;
}
}

public async Task Delete(int channelId)
Expand Down
15 changes: 14 additions & 1 deletion Assets/Talo Game Services/Talo/Runtime/APIs/FeedbackAPI.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ namespace TaloGameServices
{
public class FeedbackAPI : BaseAPI
{
public event Action<RejectedProp[]> OnPropsRejected;

public FeedbackAPI() : base("v1/game-feedback") { }

public async Task<FeedbackCategory[]> GetCategories()
Expand All @@ -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;
}
}
}
}
24 changes: 19 additions & 5 deletions Assets/Talo Game Services/Talo/Runtime/APIs/LeaderboardsAPI.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ public class LeaderboardsAPI : BaseAPI
{
private readonly LeaderboardEntriesManager _entriesManager = new();

public event Action<RejectedProp[]> OnPropsRejected;

public LeaderboardsAPI() : base("v1/leaderboards") { }

public List<LeaderboardEntry> GetCachedEntries(string internalName, GetCachedEntriesOptions options = null)
Expand Down Expand Up @@ -126,12 +128,24 @@ public async Task<LeaderboardEntriesResponse> 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<LeaderboardEntryResponse>(json);
_entriesManager.UpsertEntry(internalName, res.entry, true);
try
{
var json = await Call(uri, "POST", Prop.SanitiseJson(content));

var res = JsonUtility.FromJson<LeaderboardEntryResponse>(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;
}
}
}
}
}
6 changes: 6 additions & 0 deletions Assets/Talo Game Services/Talo/Runtime/APIs/PlayersAPI.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ public enum DebouncedOperation
public event Action OnIdentificationStarted;
public event Action OnIdentificationFailed;
public event Action OnIdentityCleared;
public event Action<RejectedProp[]> OnPropsRejected;

public static readonly string offlineDataPath = Application.persistentDataPath + "/ta.bin";

Expand Down Expand Up @@ -165,6 +166,11 @@ public async Task<Player> Update()
Talo.CurrentPlayer = res.player;
Talo.CurrentAlias.WriteOfflineAlias();

if (res.rejectedProps != null && res.rejectedProps.Length > 0)
{
OnPropsRejected?.Invoke(res.rejectedProps);
}

return Talo.CurrentPlayer;
}

Expand Down
53 changes: 53 additions & 0 deletions Assets/Talo Game Services/Talo/Runtime/Entities/RejectedProp.cs
Original file line number Diff line number Diff line change
@@ -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<RejectedPropWrapper>(json);
return wrapper?.rejectedProps ?? Array.Empty<RejectedProp>();
}

public static void TryEmit(string json, Action<RejectedProp[]> onRejected)
{
var rejectedProps = FromJson(json);
if (rejectedProps.Length > 0)
{
onRejected?.Invoke(rejectedProps);
}
}
}

[Serializable]
public class RejectedPropWrapper
{
public RejectedProp[] rejectedProps;
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ public class ChannelStoragePropError
{
public string key;
public string error;
public string message;
}

[Serializable]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@
public class PlayersUpdateResponse
{
public Player player;
public RejectedProp[] rejectedProps;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
13 changes: 13 additions & 0 deletions Assets/Talo Game Services/Talo/Runtime/Utils/RequestException.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ namespace TaloGameServices
public class RequestException : Exception
{
public long responseCode;
public string responseBody;

public RequestException()
{
Expand All @@ -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);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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})");
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,22 @@
using UnityEngine;
using System.Threading.Tasks;
using System;

namespace TaloGameServices.Sample.Playground
{
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();
Expand All @@ -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}");
}
}
}
Loading