diff --git a/Runtime/Client/LootLockerEndPoints.cs b/Runtime/Client/LootLockerEndPoints.cs index 68a039164..db62e1bd3 100644 --- a/Runtime/Client/LootLockerEndPoints.cs +++ b/Runtime/Client/LootLockerEndPoints.cs @@ -299,6 +299,7 @@ public class LootLockerEndPoints [Header("Metadata")] public static EndPointClass listMetadata = new EndPointClass("metadata/source/{0}/id/{1}", LootLockerHTTPMethod.GET); public static EndPointClass getMultisourceMetadata = new EndPointClass("metadata/multisource", LootLockerHTTPMethod.POST); + public static EndPointClass metadataOperations = new EndPointClass("metadata", LootLockerHTTPMethod.POST); // Notifications [Header("Notifications")] diff --git a/Runtime/Game/LootLockerSDKManager.cs b/Runtime/Game/LootLockerSDKManager.cs index 35e664b91..a8692b7b6 100644 --- a/Runtime/Game/LootLockerSDKManager.cs +++ b/Runtime/Game/LootLockerSDKManager.cs @@ -101,6 +101,7 @@ public static bool CheckInitialized(bool skipSessionCheck = false) LootLockerConfig.current.token = ""; LootLockerConfig.current.refreshToken = ""; LootLockerConfig.current.deviceID = ""; + LootLockerConfig.current.playerULID = null; if (!Init()) { return false; @@ -210,6 +211,7 @@ public static void StartPlaystationNetworkSession(string psnOnlineId, Action /// The source type for which to request metadata /// The specific source id for which to request metadata - /// Delegate for handling the server response + /// Delegate for handling the server response /// Base64 values will be set to content_type "application/x-redacted" and the content will be an empty String. Use this to avoid accidentally fetching large data files. public static void ListMetadata(LootLockerMetadataSources Source, string SourceID, Action onComplete, bool IgnoreFiles = false) { @@ -5815,7 +5822,7 @@ public static void ListMetadata(LootLockerMetadataSources Source, string SourceI /// The specific source id for which to request metadata /// Used together with PerPage to apply pagination to this request. Page designates which "page" of items to fetch /// Used together with Page to apply pagination to this request.PerPage designates how many items are considered a "page" - /// Delegate for handling the server response + /// Delegate for handling the server response /// Base64 values will be set to content_type "application/x-redacted" and the content will be an empty String. Use this to avoid accidentally fetching large data files. public static void ListMetadata(LootLockerMetadataSources Source, string SourceID, int Page, int PerPage, Action onComplete, bool IgnoreFiles = false) { @@ -5828,7 +5835,7 @@ public static void ListMetadata(LootLockerMetadataSources Source, string SourceI /// The source type for which to request metadata /// The specific source id for which to request metadata /// The tags that the requested metadata should have, only metadata matching *all of* the given tags will be returned - /// Delegate for handling the server response + /// Delegate for handling the server response /// Base64 values will be set to content_type "application/x-redacted" and the content will be an empty String. Use this to avoid accidentally fetching large data files. public static void ListMetadataWithTags(LootLockerMetadataSources Source, string SourceID, string[] Tags, Action onComplete, bool IgnoreFiles = false) { @@ -5843,7 +5850,7 @@ public static void ListMetadataWithTags(LootLockerMetadataSources Source, string /// The tags that the requested metadata should have, only metadata matching *all of* the given tags will be returned /// Used together with PerPage to apply pagination to this request.Page designates which "page" of items to fetch /// Used together with Page to apply pagination to this request.PerPage designates how many items are considered a "page" - /// Delegate for handling the server response + /// Delegate for handling the server response /// Base64 values will be set to content_type "application/x-redacted" and the content will be an empty String. Use this to avoid accidentally fetching large data files. public static void ListMetadataWithTags(LootLockerMetadataSources Source, string SourceID, string[] Tags, int Page, int PerPage, Action onComplete, bool IgnoreFiles = false) { @@ -5860,9 +5867,9 @@ public static void ListMetadataWithTags(LootLockerMetadataSources Source, string /// Get Metadata for the specified source with the given key /// /// The source type for which to request metadata - /// The specific source id for which to request metadata + /// The specific source id for which to request metadata, note that if the source is self then this too should be set to "self" /// The key of the metadata to fetch, use this to fetch metadata for a specific key for the specified source. - /// Delegate for handling the server response + /// Delegate for handling the server response /// Optional: Base64 values will be set to content_type "application/x-redacted" and the content will be an empty String. Use this to avoid accidentally fetching large data files. public static void GetMetadata(LootLockerMetadataSources Source, string SourceID, string Key, Action onComplete, bool IgnoreFiles=false) { @@ -5884,9 +5891,9 @@ public static void GetMetadata(LootLockerMetadataSources Source, string SourceID /// List the requested page of Metadata for the specified source that has all of the provided tags and paginate according to the supplied pagination settings /// /// The combination of sources to get keys for, and the keys to get for those sources - /// Delegate for handling the server response + /// Delegate for handling the server response /// Optional: Base64 values will be set to content_type "application/x-redacted" and the content will be an empty String. Use this to avoid accidentally fetching large data files. - public static void GetMultisourceMetadata(LootLockerMetadataSourceAndKeys[] SourcesAndKeysToGet, Action onComplete, bool ignoreFiles = false) + public static void GetMultisourceMetadata(LootLockerMetadataSourceAndKeys[] SourcesAndKeysToGet, Action onComplete, bool IgnoreFiles = false) { if (!CheckInitialized()) { @@ -5894,7 +5901,26 @@ public static void GetMultisourceMetadata(LootLockerMetadataSourceAndKeys[] Sour return; } - LootLockerAPIManager.GetMultisourceMetadata(SourcesAndKeysToGet, ignoreFiles, onComplete); + LootLockerAPIManager.GetMultisourceMetadata(SourcesAndKeysToGet, IgnoreFiles, onComplete); + } + + /// + /// Perform the specified metadata operations for the specified source + /// Note that a subset of the specified operations can fail without the full request failing. Make sure to check the errors array in the response. + /// + /// The source type that the source id refers to + /// The specific source id for which to set metadata, note that if the source is self then this too should be set to "self" + /// List of operations to perform for the given source + /// Delegate for handling the server response + public static void PerformMetadataOperations(LootLockerMetadataSources Source, string SourceID, List OperationsToPerform, Action onComplete) + { + if (!CheckInitialized()) + { + onComplete?.Invoke(LootLockerResponseFactory.SDKNotInitializedError()); + return; + } + + LootLockerAPIManager.PerformMetadataOperations(Source, SourceID, OperationsToPerform, onComplete); } #endregion @@ -5903,7 +5929,7 @@ public static void GetMultisourceMetadata(LootLockerMetadataSourceAndKeys[] Sour /// /// List notifications without filters and with default pagination settings /// - /// Delegate for handling the server response + /// Delegate for handling the server response public static void ListNotificationsWithDefaultParameters(Action onComplete) { if (!CheckInitialized()) @@ -5942,7 +5968,7 @@ public static void ListNotificationsWithDefaultParameters(Action(Optional) Used together with Page to apply pagination to this request. PerPage designates how many notifications are considered a "page". Set to 0 to not use this filter. /// (Optional) Used together with PerPage to apply pagination to this request. Page designates which "page" of items to fetch. Set to 0 to not use this filter. /// - /// Delegate for handling the server response + /// Delegate for handling the server response public static void ListNotifications(bool ShowRead, LootLockerNotificationPriority? WithPriority, string OfType, string WithSource, int PerPage, int Page, Action onComplete) { if (!CheckInitialized()) @@ -5982,7 +6008,7 @@ public static void ListNotifications(bool ShowRead, LootLockerNotificationPriori /// /// Warning: This will mark ALL unread notifications as read, so if you have listed notifications but due to filters and/or pagination not pulled all of them you may have unviewed unread notifications /// - /// Delegate for handling the server response + /// Delegate for handling the server response public static void MarkAllNotificationsAsRead(Action onComplete) { if (!CheckInitialized()) @@ -5998,7 +6024,7 @@ public static void MarkAllNotificationsAsRead(Action /// List of ids of notifications to mark as read - /// Delegate for handling the server response + /// Delegate for handling the server response public static void MarkNotificationsAsRead(string[] NotificationIds, Action onComplete) { if (!CheckInitialized()) diff --git a/Runtime/Game/Requests/EntitlementRequests.cs.meta b/Runtime/Game/Requests/EntitlementRequests.cs.meta index 0e7c5d6bb..0c2b8ffa6 100644 --- a/Runtime/Game/Requests/EntitlementRequests.cs.meta +++ b/Runtime/Game/Requests/EntitlementRequests.cs.meta @@ -1,2 +1,11 @@ fileFormatVersion: 2 -guid: 930b5026c070b2d4fb183ec255103c0d \ No newline at end of file +guid: 930b5026c070b2d4fb183ec255103c0d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Game/Requests/LootLockerSessionRequest.cs b/Runtime/Game/Requests/LootLockerSessionRequest.cs index ab3084801..8a2081572 100644 --- a/Runtime/Game/Requests/LootLockerSessionRequest.cs +++ b/Runtime/Game/Requests/LootLockerSessionRequest.cs @@ -343,6 +343,7 @@ public static void Session(LootLockerSessionRequest data, Action(serverResponse); LootLockerConfig.current.token = response.session_token; LootLockerConfig.current.deviceID = data?.player_identifier; + LootLockerConfig.current.playerULID = response.player_ulid; onComplete?.Invoke(response); }, false); } @@ -381,6 +382,7 @@ public static void GuestSession(LootLockerSessionRequest data, Action(serverResponse); LootLockerConfig.current.token = response.session_token; LootLockerConfig.current.deviceID = (data as LootLockerSessionRequest)?.player_identifier; + LootLockerConfig.current.playerULID = response.player_ulid; onComplete?.Invoke(response); }, false); } @@ -439,6 +441,7 @@ public static void NintendoSwitchSession(LootLockerNintendoSwitchSessionRequest var response = LootLockerResponse.Deserialize(serverResponse); LootLockerConfig.current.token = response.session_token; LootLockerConfig.current.deviceID = ""; + LootLockerConfig.current.playerULID = response.player_ulid; onComplete?.Invoke(response); }, false); } @@ -474,6 +477,7 @@ private static void EpicSession(string json, Action(serverResponse); LootLockerConfig.current.token = response.session_token; LootLockerConfig.current.deviceID = ""; + LootLockerConfig.current.playerULID = response.player_ulid; onComplete?.Invoke(response); }, false); } @@ -531,6 +536,7 @@ private static void AppleSession(string json, Action(serverResponse); LootLockerConfig.current.token = response.session_token; LootLockerConfig.current.deviceID = response.player_identifier; + LootLockerConfig.current.playerULID = response.player_ulid; LootLockerConfig.current.refreshToken = response.refresh_token; onComplete?.Invoke(response); }, false); diff --git a/Runtime/Game/Requests/MetadataRequests.cs b/Runtime/Game/Requests/MetadataRequests.cs index 81e2eac88..ca2e270c6 100644 --- a/Runtime/Game/Requests/MetadataRequests.cs +++ b/Runtime/Game/Requests/MetadataRequests.cs @@ -1,11 +1,10 @@ using System; using LootLocker.LootLockerEnums; using LootLocker.Requests; +using System.Collections.Generic; #if LOOTLOCKER_USE_NEWTONSOFTJSON using Newtonsoft.Json.Linq; -#else -using System.Collections.Generic; #endif //================================================== @@ -23,6 +22,8 @@ public enum LootLockerMetadataSources catalog_item = 2, progression = 3, currency = 4, + player = 5, + self = 6 }; /// @@ -36,6 +37,16 @@ public enum LootLockerMetadataTypes Json = 3, Base64 = 4, }; + + /// + /// Possible metadata actions + /// + public enum LootLockerMetadataActions + { + create = 0, + update = 1, + delete = 2 + }; } namespace LootLocker.Requests @@ -78,7 +89,11 @@ public class LootLockerMetadataEntry /// List of tags applied to this metadata entry /// public string[] tags { get; set; } - + /// + /// The access level set for this metadata entry. Valid values are game_api.read, game_api.write and player.read (only applicable for player metadata and means that the metadata entry is readable for players except the owner), though no values are required. + /// Note that different sources can allow or disallow a subset of these values. + /// + public string[] access { get; set; } /// /// Get the value as a String. Returns true if value could be parsed in which case output contains the value in string format, returns false if parsing failed. /// @@ -229,6 +244,66 @@ public class LootLockerMetadataSourceAndEntries public LootLockerMetadataEntry[] entries { get; set; } } + public class LootLockerMetadataOperationErrorKeyTypePair + { + /// + /// The metadata key that the operation error refers to + /// + public string key { get; set; } + /// + /// The type of value that the set operation was for + /// + public LootLockerMetadataTypes type { get; set; } + } + + /// + /// + public class LootLockerMetadataOperationError + { + /// + /// The type of action that this metadata operation was + /// + public LootLockerMetadataActions action { get; set; } + /// + /// The error message describing why this metadata set operation failed + /// + public string error { get; set; } + /// + /// The key and type of value that the operation was for + /// + public LootLockerMetadataOperationErrorKeyTypePair entry { get; set; } + } + + /// + /// + public class LootLockerMetadataOperation : LootLockerMetadataEntry + { + /// + /// The type of action to perform for this metadata operation + /// + public LootLockerMetadataActions action { get; set; } + } + + /// + /// + public class LootLockerInternalMetadataOperationWithStringEnums : LootLockerMetadataOperation + { + public LootLockerInternalMetadataOperationWithStringEnums(LootLockerMetadataOperation other) + { + this.key = other.key; + this.type = other.type.ToString().ToLower(); + this.value = other.value; + this.tags = other.tags; + this.access = other.access; + this.action = other.action.ToString().ToLower(); + } + + public new string type { get; set; } + public new string action { get; set; } + + + } + //================================================== // Request Definitions //================================================== @@ -243,6 +318,28 @@ public class LootLockerGetMultisourceMetadataRequest public LootLockerMetadataSourceAndKeys[] sources { get; set; } } + /// + /// + public class LootLockerInternalMetadataOperationRequest + { + /// + /// Whether or not this operation is for metadata on the current player + /// + public bool self { get; set; } + /// + /// The type of source that the source id refers to + /// + public string source { get; set; } + /// + /// The id of the specific source that the set operation was taken on, note that if source is set to self then this should also be set to "self" + /// + public string source_id { get; set; } + /// + /// List of operations to perform for the given source + /// + public LootLockerInternalMetadataOperationWithStringEnums[] entries { get; set; } + } + //================================================== // Response Definitions //================================================== @@ -279,6 +376,24 @@ public class LootLockerGetMultisourceMetadataResponse : LootLockerResponse /// public LootLockerMetadataSourceAndEntries[] Metadata { get; set; } }; + + /// + /// + public class LootLockerMetadataOperationsResponse : LootLockerResponse + { + /// + /// The type of source that the source id refers to + /// + public LootLockerMetadataSources source { get; set; } + /// + /// The id of the specific source that the set operation was taken on, note that if source is set to self then this will also be set to "self" + /// + public string source_id { get; set; } + /// + /// A list of errors (if any) that occurred when executing the provided metadata actions + /// + public LootLockerMetadataOperationError[] errors { get; set; } + } } //================================================== @@ -291,6 +406,10 @@ public partial class LootLockerAPIManager { public static void ListMetadata(LootLockerMetadataSources Source, string SourceID, int Page, int PerPage, string Key, string[] Tags, bool ignoreFiles, Action onComplete) { + if (Source == LootLockerMetadataSources.self) + { + SourceID = "self"; + } string formattedEndpoint = string.Format(LootLockerEndPoints.listMetadata.endPoint, Source.ToString(), SourceID); string queryParams = ""; @@ -314,9 +433,15 @@ public static void ListMetadata(LootLockerMetadataSources Source, string SourceI LootLockerServerRequest.CallAPI(formattedEndpoint, LootLockerEndPoints.listMetadata.httpMethod, onComplete: (serverResponse) => { - LootLockerResponse.Deserialize(onComplete, serverResponse); + var parsedResponse = LootLockerResponse.Deserialize(serverResponse); + if(parsedResponse.entries == null) + { + parsedResponse.entries = new LootLockerMetadataEntry[] {}; + } + onComplete?.Invoke(parsedResponse); }); } + public static void GetMultisourceMetadata(LootLockerMetadataSourceAndKeys[] SourcesAndKeysToGet, bool ignoreFiles, Action onComplete) { if (SourcesAndKeysToGet == null) @@ -335,6 +460,14 @@ public static void GetMultisourceMetadata(LootLockerMetadataSourceAndKeys[] Sour endpoint += queryParams; } + foreach (LootLockerMetadataSourceAndKeys sourcePair in SourcesAndKeysToGet) + { + if(sourcePair.source == LootLockerMetadataSources.self) + { + sourcePair.id = "self"; + } + } + LootLockerGetMultisourceMetadataRequest request = new LootLockerGetMultisourceMetadataRequest { sources = SourcesAndKeysToGet }; string json = LootLockerJson.SerializeObject(request); @@ -344,5 +477,37 @@ public static void GetMultisourceMetadata(LootLockerMetadataSourceAndKeys[] Sour LootLockerResponse.Deserialize(onComplete, serverResponse); }); } + + public static void PerformMetadataOperations(LootLockerMetadataSources Source, string SourceID, List OperationsToPerform, Action onComplete) + { + if (string.IsNullOrEmpty(SourceID) || OperationsToPerform.Count == 0) + { + onComplete?.Invoke(LootLockerResponseFactory.InputUnserializableError()); + return; + } + + List entries = new List(); + foreach(var op in OperationsToPerform) + { + entries.Add(new LootLockerInternalMetadataOperationWithStringEnums(op)); + } + + LootLockerInternalMetadataOperationRequest request = new LootLockerInternalMetadataOperationRequest + { + self = Source == LootLockerMetadataSources.self, + source = Source == LootLockerMetadataSources.self ? null : Source.ToString().ToLower(), + source_id = Source == LootLockerMetadataSources.self ? null : SourceID, + entries = entries.ToArray() + }; + + string json = LootLockerJson.SerializeObject(request); + + LootLockerServerRequest.CallAPI(LootLockerEndPoints.metadataOperations.endPoint, LootLockerEndPoints.metadataOperations.httpMethod, json, onComplete: + (serverResponse) => + { + LootLockerResponse.Deserialize(onComplete, serverResponse); + }); + } } } + \ No newline at end of file diff --git a/Runtime/Game/Requests/NotificationRequests.cs.meta b/Runtime/Game/Requests/NotificationRequests.cs.meta index 7d51a9259..4aa460be5 100644 --- a/Runtime/Game/Requests/NotificationRequests.cs.meta +++ b/Runtime/Game/Requests/NotificationRequests.cs.meta @@ -1,2 +1,11 @@ fileFormatVersion: 2 -guid: a11c5f1533dceae4d8799c6dae914b18 \ No newline at end of file +guid: a11c5f1533dceae4d8799c6dae914b18 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Game/Requests/RemoteSessionRequest.cs b/Runtime/Game/Requests/RemoteSessionRequest.cs index 52df6ae7e..a63db417c 100644 --- a/Runtime/Game/Requests/RemoteSessionRequest.cs +++ b/Runtime/Game/Requests/RemoteSessionRequest.cs @@ -422,6 +422,7 @@ private void _RefreshRemoteSession(LootLockerRefreshRemoteSessionRequest data, A var response = LootLockerResponse.Deserialize(serverResponse); LootLockerConfig.current.token = response.session_token; LootLockerConfig.current.deviceID = response.player_identifier; + LootLockerConfig.current.playerULID = response.player_ulid; LootLockerConfig.current.refreshToken = response.refresh_token; onComplete?.Invoke(response); }, false); @@ -465,6 +466,7 @@ private void StartRemoteSession(string leaseCode, string nonce, Action