diff --git a/.pubnub.yml b/.pubnub.yml index 0bbd214a..0aea4750 100644 --- a/.pubnub.yml +++ b/.pubnub.yml @@ -1,6 +1,11 @@ --- -version: v9.5.0 +version: v10.0.0 changelog: + - date: 2026-10-06 + version: v10.0.0 + changes: + - type: feature + text: "Added Data Sync feature support." - date: 2026-07-21 version: v9.5.0 changes: @@ -759,6 +764,41 @@ features: - OBJECTS-REMOVE-CHANNEL-MEMBERS-V2 - OBJECTS-MANAGE-MEMBERSHIPS-V2 - OBJECTS-MANAGE-CHANNEL-MEMBERS-V2 + data-sync: + - DATASYNC-USER-CREATE + - DATASYNC-USER-GET + - DATASYNC-USER-GET-ALL + - DATASYNC-USER-UPDATE + - DATASYNC-USER-PATCH + - DATASYNC-USER-DELETE + - DATASYNC-CHANNEL-CREATE + - DATASYNC-CHANNEL-GET + - DATASYNC-CHANNEL-GET-ALL + - DATASYNC-CHANNEL-UPDATE + - DATASYNC-CHANNEL-PATCH + - DATASYNC-CHANNEL-DELETE + - DATASYNC-MEMBERSHIP-CREATE + - DATASYNC-MEMBERSHIP-GET + - DATASYNC-MEMBERSHIP-GET-ALL + - DATASYNC-MEMBERSHIP-UPDATE + - DATASYNC-MEMBERSHIP-PATCH + - DATASYNC-MEMBERSHIP-DELETE + - DATASYNC-ENTITY-CREATE + - DATASYNC-ENTITY-GET + - DATASYNC-ENTITY-GET-ALL + - DATASYNC-ENTITY-UPDATE + - DATASYNC-ENTITY-PATCH + - DATASYNC-ENTITY-DELETE + - DATASYNC-RELATIONSHIP-CREATE + - DATASYNC-RELATIONSHIP-GET + - DATASYNC-RELATIONSHIP-GET-ALL + - DATASYNC-RELATIONSHIP-UPDATE + - DATASYNC-RELATIONSHIP-PATCH + - DATASYNC-RELATIONSHIP-DELETE + - DATASYNC-FILTERING + - DATASYNC-SORTING + - DATASYNC-EVENTS-LISTENER + - DATASYNC-ACCESS-MANAGER files: - FILES-SEND-FILE - FILES-LIST-FILES @@ -817,7 +857,7 @@ sdks: distribution-type: package distribution-repository: git release package-name: PubNub.unitypackage - location: https://github.com/pubnub/unity/releases/download/v9.5.0/PubNub.unitypackage + location: https://github.com/pubnub/unity/releases/download/v10.0.0/PubNub.unitypackage requires: - name: "UnityEditor" @@ -949,7 +989,7 @@ sdks: distribution-type: package distribution-repository: git release package-name: PubNub.unitypackage - location: https://github.com/pubnub/unity/releases/download/v9.5.0/PubNub.unitypackage + location: https://github.com/pubnub/unity/releases/download/v10.0.0/PubNub.unitypackage requires: - name: "UnityEditor" diff --git a/PubNubUnity/Assets/PubNub/Runtime/Adapters/SubscribeCallbackListener.cs b/PubNubUnity/Assets/PubNub/Runtime/Adapters/SubscribeCallbackListener.cs index 1b4251a4..ddadd7c9 100644 --- a/PubNubUnity/Assets/PubNub/Runtime/Adapters/SubscribeCallbackListener.cs +++ b/PubNubUnity/Assets/PubNub/Runtime/Adapters/SubscribeCallbackListener.cs @@ -14,6 +14,7 @@ public class SubscribeCallbackListener { public event Action onMessageAction; public event Action onFile; public event Action onStatus; + public event Action onDataSync; protected SubscribeCallbackExt listener; @@ -22,6 +23,7 @@ public SubscribeCallbackListener( Action presenceCallback, Action> signalCallback, Action objectEventCallback, + Action dataSyncEventCallback, Action messageActionCallback, Action fileCallback, Action statusCallback) : this() { @@ -30,6 +32,7 @@ public SubscribeCallbackListener( this.onSignal += signalCallback; this.onObject += objectEventCallback; this.onMessageAction += messageActionCallback; + this.onDataSync += dataSyncEventCallback; this.onFile += fileCallback; this.onStatus += statusCallback; } @@ -65,6 +68,12 @@ public SubscribeCallbackListener() { #endif onObject.Dispatch(pnObj, objectEventObj); }, + (pnObj, dataSyncEventObj) => { + #if PN_DEBUG + Debug.Log(dataSyncEventObj.Channel); + #endif + onDataSync.Dispatch(pnObj, dataSyncEventObj); + }, // Message actions (pnObj, msgActionEvent) => { #if PN_DEBUG diff --git a/PubNubUnity/Assets/PubNub/Runtime/Plugins/PubnubApiUnity.dll b/PubNubUnity/Assets/PubNub/Runtime/Plugins/PubnubApiUnity.dll index c4b64fbb..9add1971 100644 Binary files a/PubNubUnity/Assets/PubNub/Runtime/Plugins/PubnubApiUnity.dll and b/PubNubUnity/Assets/PubNub/Runtime/Plugins/PubnubApiUnity.dll differ diff --git a/PubNubUnity/Assets/PubNub/Runtime/Plugins/PubnubApiUnity.xml b/PubNubUnity/Assets/PubNub/Runtime/Plugins/PubnubApiUnity.xml index 136f1116..4a45b52b 100644 --- a/PubNubUnity/Assets/PubNub/Runtime/Plugins/PubnubApiUnity.xml +++ b/PubNubUnity/Assets/PubNub/Runtime/Plugins/PubnubApiUnity.xml @@ -4,6 +4,13 @@ PubnubApiUnity + + + Assigns Data Sync projections to resources/patterns. The projection name + per resource is encoded into the token's meta "pn-projections" section. + Entity-level Data Sync permissions are set through / . + + Convert the UTC/GMT DateTime to Unix Nano Seconds format @@ -164,6 +171,833 @@ Hexadecimal CBOR string Thrown when unsupported type is passed + + + Channel identifier. Optional — if not provided, the server generates one. + Must be 1–255 characters if provided. + + + + + Entity class name. Optional — defaults to "Channel" on the server if not + provided. Must be "Channel" or a subclass of "Channel". + + + + + Schema version of the entity class. Required. Must be >= 1. + + + + + Class hierarchy level ("Global" or "SubKey") used to disambiguate classes + with the same name defined at different levels. Optional. + + + + + Channel status (e.g., "active", "archived"). 1–100 characters. + + + + + User-defined custom properties. Supports arbitrarily nested objects. + + + + + Channel identifier. Required. + + + + + ETag for optimistic concurrency control. If provided, the server rejects + the delete when the current resource version does not match (HTTP 412). + + + + + Channel identifier. Required. + + + + + Entity class name to filter by. Optional — if not provided the server returns + channels of every class descending from "Channel". + + + + + Schema version of the entity class. Optional — if not provided the server + returns channels matching the latest version. + + + + + Class hierarchy level ("Global" or "SubKey") used to disambiguate classes + with the same name defined at different levels. Optional. + + + + + Pagination cursor returned from a previous request. + + + + + Maximum number of items to return per page. + Min 1, max 100, default 20. + + + + + Filter expression using AppContext Query Language, evaluated against strongly + consistent storage. Supports a limited number of conditions. + Example: "status == 'active'". + + + + + Filter expression using AppContext Query Language, evaluated against eventually + consistent storage. Supports logical operators and nested conditions. + + + + + Comma-separated list of fields to sort by, each optionally suffixed with + :desc (ascending by default). Example: "createdAt:desc,id". + + + + + Channel identifier. Required. + + + + + Schema version of the entity class. Required. Must be >= 1. + + + + + Channel status (e.g., "active", "archived"). 1–100 characters. + + + + + User-defined custom properties. Supports arbitrarily nested objects. + Replaces the entire payload — omitted fields are removed. + + + + + ETag for optimistic concurrency control. If provided, the server rejects + the update when the current resource version does not match (HTTP 412). + + + + + Channel identifier. Required. + + + + + List of JSON Patch operations (RFC 6902) to apply. + + + + + ETag for optimistic concurrency control. If provided, the server rejects + the patch when the current resource version does not match (HTTP 412). + + + + + Entrypoint for PubNub Data Sync operations + + + + + Parameters for creating a new entity via DataSync. + + + + + Entity identifier. Optional — if not provided, the server generates one. + Must be 1–255 characters if provided. + + + + + Entity class identifier (e.g., "vehicle", "order", "sensor"). + Required. Immutable after creation. + + + + + Schema version of the entity class. Required. Must be >= 1. + + + + + Class hierarchy level ("Global" or "SubKey") used to disambiguate classes + with the same name defined at different levels. Optional. + + + + + Entity status (e.g., "active", "inactive"). 1–100 characters. + + + + + User-defined custom properties. Supports arbitrarily nested objects. + + + + + Parameters for deleting an entity by ID via DataSync. + + + + + Entity identifier. Required. + + + + + ETag for optimistic concurrency control. If provided, the server rejects + the delete when the current resource version does not match (HTTP 412). + + + + + Parameters for listing entities via DataSync. + + + + + Entity class name to filter by. Required. + + + + + Schema version of the entity class. Optional — if not provided the server + returns entities matching the latest version. + + + + + Class hierarchy level ("Global" or "SubKey") used to disambiguate classes + with the same name defined at different levels. Optional. + + + + + Pagination cursor returned from a previous request. + + + + + Maximum number of items to return per page. + Min 1, max 100, default 20. + + + + + Filter expression using AppContext Query Language, evaluated against strongly + consistent storage. Supports a limited number of conditions. + Example: "status == 'active'". + + + + + Filter expression using AppContext Query Language, evaluated against eventually + consistent storage. Supports logical operators and nested conditions. + + + + + Comma-separated list of fields to sort by, each optionally suffixed with + :desc (ascending by default). Example: "createdAt:desc,id". + + + + + Result returned by GetEntities (list) containing an array of entities + plus cursor-based pagination metadata. + + + + + Cursor-based pagination metadata returned in list responses. + DataSync pagination is forward-only. + + + + + Parameters for retrieving an entity by ID via DataSync. + + + + + Entity identifier. Required. + + + + + Result returned by a single-entity operation (Create, Get, Update, Patch). + The server response envelope is deserialized into this type. + + + + + Parameters for updating an entity with complete resource replacement (PUT) via DataSync. + + + + + Entity identifier. Required. + + + + + Schema version of the entity class. Required. Must be >= 1. + Note: entityClass is immutable and cannot be changed after creation. + + + + + Entity status (e.g., "active", "inactive"). 1–100 characters. + + + + + User-defined custom properties. Supports arbitrarily nested objects. + Replaces the entire payload — omitted fields are removed. + + + + + ETag for optimistic concurrency control. If provided, the server rejects + the update when the current resource version does not match (HTTP 412). + + + + + Entity identifier. Required. + + + + + List of JSON Patch operations (RFC 6902) to apply. Required; must contain at least one operation. + + + + + ETag for optimistic concurrency control. If provided, the server rejects + the patch when the current resource version does not match (HTTP 412). + + + + + A single JSON Patch operation as defined by RFC 6902. + + + + + The operation to perform: "add", "remove", "replace", "move", "copy", or "test". + + + + + JSON Pointer (RFC 6901) to the target location. + + + + + The value to apply. Required for "add", "replace", and "test" operations. + Can be any JSON-serializable value including null. + + + + + Source location (JSON Pointer). Required for "move" and "copy" operations. + + + + + Membership identifier. Optional — if not provided, the server generates one. + Must be 1–255 characters if provided. + + + + + Channel ID. Required. Immutable after creation. + Maps to entityAId on the wire. + + + + + User ID. Required. Immutable after creation. + Maps to entityBId on the wire. + + + + + Version of the membership relationship class. Required. Must be >= 1. + + + + + Membership status (e.g., "active", "banned"). 1–100 characters. + + + + + User-defined custom properties. Supports arbitrarily nested objects. + + + + + Membership identifier. Required. + + + + + ETag for optimistic concurrency control. If provided, the server rejects + the delete when the current resource version does not match (HTTP 412). + + + + + Membership identifier. Required. + + + + + Filter memberships by user ID. Optional. + + + + + Filter memberships by channel ID. Optional. + + + + + Version of the relationship class. Optional — if not provided the server + returns memberships matching the latest version. + + + + + Pagination cursor returned from a previous request. + + + + + Maximum number of items to return per page. + Min 1, max 100, default 20. + + + + + Filter expression using AppContext Query Language, evaluated against strongly + consistent storage. Supports a limited number of conditions. + Example: "status == 'active'". + + + + + Filter expression using AppContext Query Language, evaluated against eventually + consistent storage. Supports logical operators and nested conditions. + + + + + Comma-separated list of fields to sort by, each optionally suffixed with + :desc (ascending by default). Example: "createdAt:desc,id". + + + + + Membership identifier. Required. + + + + + Version of the membership relationship class. Required. Must be >= 1. + + + + + Membership status (e.g., "active", "banned"). 1–100 characters. + + + + + User-defined custom properties. Supports arbitrarily nested objects. + Replaces the entire payload — omitted fields are removed. + + + + + ETag for optimistic concurrency control. If provided, the server rejects + the update when the current resource version does not match (HTTP 412). + + + + + Membership identifier. Required. + + + + + List of JSON Patch operations (RFC 6902) to apply. + + + + + ETag for optimistic concurrency control. If provided, the server rejects + the patch when the current resource version does not match (HTTP 412). + + + + + Parameters for creating a new relationship via DataSync. + + + + + Relationship identifier. Optional — if not provided, the server generates one. + Must be 1–255 characters if provided. + + + + + First entity ID (the "A" side of the relationship). Required. Immutable after creation. + Must be 1–255 characters. + + + + + Second entity ID (the "B" side of the relationship). Required. Immutable after creation. + Must be 1–255 characters. + + + + + Relationship class identifier (e.g., "ProductOwner", "FriendOf"). + Required. Immutable after creation. + + + + + Version of the relationship class. Required. Must be >= 1. + + + + + Relationship status (e.g., "active", "inactive"). 1–100 characters. + + + + + User-defined custom properties. Supports arbitrarily nested objects. + + + + + Parameters for deleting a relationship by ID via DataSync. + + + + + Relationship identifier. Required. + + + + + ETag for optimistic concurrency control. If provided, the server rejects + the delete when the current resource version does not match (HTTP 412). + + + + + Parameters for retrieving a relationship by ID via DataSync. + + + + + Relationship identifier. Required. + + + + + Parameters for listing relationships via DataSync. + + + + + Relationship class name to filter by. Required. + + + + + Filter relationships by the first entity (A-side) ID. Optional. + At least one of EntityAId or EntityBId should be provided for meaningful results. + + + + + Filter relationships by the second entity (B-side) ID. Optional. + At least one of EntityAId or EntityBId should be provided for meaningful results. + + + + + Version of the relationship class. Optional — if not provided the server + uses the latest version. Must be >= 1 when provided. + + + + + Pagination cursor returned from a previous request. + + + + + Maximum number of items to return per page. + Min 1, max 100, default 20. + + + + + Filter expression using AppContext Query Language, evaluated against strongly + consistent storage. Supports a limited number of conditions. + Example: "status == 'active'". + + + + + Filter expression using AppContext Query Language, evaluated against eventually + consistent storage. Supports logical operators and nested conditions. + + + + + Comma-separated list of fields to sort by, each optionally suffixed with + :desc (ascending by default). Example: "createdAt:desc,id". + + + + + Result returned by GetRelationships (list) containing an array of relationships + plus cursor-based pagination metadata. + + + + + Result returned by a single-relationship operation (Create, Get, Update, Patch). + The server response envelope is deserialized into this type. + + + + + Parameters for updating a relationship with complete resource replacement (PUT) via DataSync. + + + + + Relationship identifier. Required. + + + + + Version of the relationship class. Required. Must be >= 1. + Note: entityAId, entityBId, and relationshipClass are immutable + and cannot be changed after creation. + + + + + Relationship status (e.g., "active", "inactive"). 1–100 characters. + + + + + User-defined custom properties. Supports arbitrarily nested objects. + Replaces the entire payload — omitted fields are removed. + + + + + ETag for optimistic concurrency control. If provided, the server rejects + the update when the current resource version does not match (HTTP 412). + + + + + Parameters for partially updating a relationship via JSON Patch (RFC 6902). + + + + + Relationship identifier. Required. + + + + + List of JSON Patch operations (RFC 6902) to apply. Required; must contain at least one operation. + + + + + ETag for optimistic concurrency control. If provided, the server rejects + the patch when the current resource version does not match (HTTP 412). + + + + + User identifier. Optional — if not provided, the server generates one. + Must be 1–255 characters if provided. + + + + + Entity class name. Optional — defaults to "User" on the server if not + provided. Must be "User" or a subclass of "User". + + + + + Schema version of the entity class. Required. Must be >= 1. + + + + + Class hierarchy level ("Global" or "SubKey") used to disambiguate classes + with the same name defined at different levels. Optional. + + + + + User status (e.g., "active", "inactive"). 1–100 characters. + + + + + User-defined custom properties. Supports arbitrarily nested objects. + + + + + User identifier. Required. + + + + + ETag for optimistic concurrency control. If provided, the server rejects + the delete when the current resource version does not match (HTTP 412). + + + + + User identifier. Required. + + + + + Entity class name to filter by. Optional — if not provided the server returns + users of every class descending from "User". + + + + + Schema version of the entity class. Optional — if not provided the server + returns users matching the latest version. + + + + + Class hierarchy level ("Global" or "SubKey") used to disambiguate classes + with the same name defined at different levels. Optional. + + + + + Pagination cursor returned from a previous request. + + + + + Maximum number of items to return per page. + Min 1, max 100, default 20. + + + + + Filter expression using AppContext Query Language, evaluated against strongly + consistent storage. Supports a limited number of conditions. + Example: "status == 'active'". + + + + + Filter expression using AppContext Query Language, evaluated against eventually + consistent storage. Supports logical operators and nested conditions. + + + + + Comma-separated list of fields to sort by, each optionally suffixed with + :desc (ascending by default). Example: "createdAt:desc,id". + + + + + User identifier. Required. + + + + + Schema version of the entity class. Required. Must be >= 1. + + + + + User status (e.g., "active", "inactive"). 1–100 characters. + + + + + User-defined custom properties. Supports arbitrarily nested objects. + Replaces the entire payload — omitted fields are removed. + + + + + ETag for optimistic concurrency control. If provided, the server rejects + the update when the current resource version does not match (HTTP 412). + + + + + User identifier. Required. + + + + + List of JSON Patch operations (RFC 6902) to apply. + + + + + ETag for optimistic concurrency control. If provided, the server rejects + the patch when the current resource version does not match (HTTP 412). + + Safely invokes a listener callback with exception handling to prevent listener exceptions from affecting SDK operations. @@ -285,6 +1119,79 @@ Create a state-invocation pair + + + Per-resource projection assignment for Data Sync. + The value is the single projection name the principal is "looking through" + for that resource (use "__default__" for the base projection). + These buckets assign projections only; the matching permissions for users and channels + come from the shared Uuids/Users and Channels/Spaces mappings, not from PNDataSyncTokenScopes. + + + + + Entity id -> projection name. Key e.g. "order-456" (resources) or "order-*" (patterns). + + + + + User id -> projection name. Key e.g. "alice" (resources) or "user-*" (patterns). + + + + + Channel id -> projection name. Key e.g. "general" (resources) or "channel-*" (patterns). + + + + + Relationship id -> projection name. Key e.g. "user.A:channel.X". + + + + + Membership id -> projection name. + + + + + Data Sync projection permissions for grant token requests. + Encoded into the "pn-projections" key within the token's meta section. + Exact (res) match takes priority over pattern (pat) match. + + + + + Projection assignments for specific Data Sync resources (by exact id). + + + + + Projection assignments matching Data Sync resources by regex pattern. + + + + + Optional DataSync permission scopes. Serialized to the REST keys + datasync:entities / datasync:relationships / datasync:memberships + inside the grant token resources / patterns sections. + + + + + Concrete or pattern entity ids, e.g. "order-456" (resources) or "order-*" (patterns). + + + + + Composite relationship ids, e.g. "user.A:channel.X". + + + + + Composite membership ids, e.g. "user-123:channel-X". + + Extension methods for PNPushType enum to get url value @@ -384,6 +1291,16 @@ Join. Applies to Objects v2 + + + Data Sync projection assignments decoded from the token's meta "pn-projections" section. + + + + + Optional DataSync permission scopes (entities, relationships, memberships). + + messages diff --git a/PubNubUnity/Assets/PubNub/Runtime/Util/UnityPNSDKSource.cs b/PubNubUnity/Assets/PubNub/Runtime/Util/UnityPNSDKSource.cs index 301bd8dd..772c1962 100644 --- a/PubNubUnity/Assets/PubNub/Runtime/Util/UnityPNSDKSource.cs +++ b/PubNubUnity/Assets/PubNub/Runtime/Util/UnityPNSDKSource.cs @@ -5,7 +5,7 @@ namespace PubnubApi.Unity { public class UnityPNSDKSource : IPNSDKSource { - private const string build = "9.5.0"; + private const string build = "10.0.0"; public string Build => build; public string GetPNSDK() { diff --git a/PubNubUnity/Assets/PubNub/package.json b/PubNubUnity/Assets/PubNub/package.json index d431c034..a6aea410 100644 --- a/PubNubUnity/Assets/PubNub/package.json +++ b/PubNubUnity/Assets/PubNub/package.json @@ -1,6 +1,6 @@ { "name": "com.pubnub.sdk", - "version": "9.5.0", + "version": "10.0.0", "displayName": "PubNub SDK", "description": "PubNub Real-time Cloud-Hosted Push API and Push Notification Client Frameworks", "unity": "2018.2", diff --git a/PubNubUnity/Assets/Snippets/AccessManager/AccessManagerSample.cs b/PubNubUnity/Assets/Snippets/AccessManager/AccessManagerSample.cs index 2fa2d13c..07c4cc8c 100644 --- a/PubNubUnity/Assets/Snippets/AccessManager/AccessManagerSample.cs +++ b/PubNubUnity/Assets/Snippets/AccessManager/AccessManagerSample.cs @@ -378,4 +378,92 @@ static async Task ParseTokenBasicUsage() // snippet.end */ } -} \ No newline at end of file + + static async Task GrantTokenDataSync() + { + // snippet.grant_token_datasync + try + { + PNResult grantResponse = await pubnub.GrantToken() + .TTL(15) + .AuthorizedUuid("user-alice") + .Resources(new PNTokenResources + { + DataSync = new PNDataSyncTokenScopes + { + Entities = new Dictionary + { + { "product-sneaker-42", new PNTokenAuthValues { Get = true } }, + }, + }, + }) + .Patterns(new PNTokenPatterns + { + DataSync = new PNDataSyncTokenScopes + { + Entities = new Dictionary + { + { "product-.*", new PNTokenAuthValues { Get = true } }, + }, + }, + }) + .ExecuteAsync(); + + Debug.Log(grantResponse.Result.Token); + } + catch (Exception ex) + { + Debug.Log($"Request can't be executed due to error: {ex.Message}"); + } + // snippet.end + } + + static async Task GrantTokenDataSyncProjection() + { + // snippet.grant_token_datasync_projection + try + { + PNResult grantResponse = await pubnub.GrantToken() + .TTL(15) + .AuthorizedUuid("user-alice") + .Resources(new PNTokenResources + { + DataSync = new PNDataSyncTokenScopes + { + Entities = new Dictionary + { + { "product-sneaker-42", new PNTokenAuthValues { Get = true } }, + }, + }, + }) + .Patterns(new PNTokenPatterns + { + DataSync = new PNDataSyncTokenScopes + { + Entities = new Dictionary + { + { "product-.*", new PNTokenAuthValues { Get = true } }, + }, + }, + }) + .DataSyncProjections(new PNDataSyncProjections + { + Resources = new PNDataSyncProjectionScope + { + Entities = new Dictionary + { + { "product-sneaker-42", "public" }, + }, + }, + }) + .ExecuteAsync(); + + Debug.Log(grantResponse.Result.Token); + } + catch (Exception ex) + { + Debug.Log($"Request can't be executed due to error: {ex.Message}"); + } + // snippet.end + } +} diff --git a/PubNubUnity/Assets/Snippets/DataSync.meta b/PubNubUnity/Assets/Snippets/DataSync.meta new file mode 100644 index 00000000..a1c35d9b --- /dev/null +++ b/PubNubUnity/Assets/Snippets/DataSync.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 22e3f047afee4aff804b5cc119197c5c +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/PubNubUnity/Assets/Snippets/DataSync/DataSyncSample.cs b/PubNubUnity/Assets/Snippets/DataSync/DataSyncSample.cs new file mode 100644 index 00000000..c928af6b --- /dev/null +++ b/PubNubUnity/Assets/Snippets/DataSync/DataSyncSample.cs @@ -0,0 +1,1476 @@ +// snippet.using +using PubnubApi; +using PubnubApi.EndPoint; +using PubnubApi.Unity; + +// snippet.end +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using UnityEngine; + +public class DataSyncSample +{ + private static Pubnub pubnub; + + static void Init() + { + // snippet.pubnub_init + // Configuration + PNConfiguration pnConfiguration = new PNConfiguration(new UserId("myUniqueUserId")) + { + SubscribeKey = "demo", + PublishKey = "demo", + Secure = true + }; + + // Initialize PubNub + Pubnub pubnub = PubnubUnityUtils.NewUnityPubnub(pnConfiguration); + + // If you're using Unity Editor setup you can get the Pubnub instance from PNManagerBehaviour + // For more details, see https://www.pubnub.com/docs/sdks/unity#configure-pubnub + /* + [SerializeField] private PNManagerBehaviour pubnubManager; + Pubnub pubnub = pubnubManager.pubnub; + */ + + // snippet.end + } + + // Users + + public static async Task CreateUserBasicUsage() + { + // snippet.create_user_basic_usage + try + { + PNResult response = await pubnub.DataSync.CreateUser(new CreateUserParameters + { + Id = "user-alice", + EntityClassVersion = 1, + Payload = new Dictionary + { + { "name", "Alice" }, + { "type", "shopper" }, + }, + }); + + if (!response.Status.Error) + { + Debug.Log(response.Result.Id); + } + else + { + Debug.Log($"Request can't be executed due to error: {response.Status.ErrorData.Information}"); + } + } + catch (Exception ex) + { + Debug.Log($"Request can't be executed due to error: {ex.Message}"); + } + // snippet.end + } + + public static async Task GetUserBasicUsage() + { + // snippet.get_user_basic_usage + try + { + PNResult response = await pubnub.DataSync.GetUser(new GetUserParameters + { + Id = "user-alice", + }); + + if (!response.Status.Error) + { + Debug.Log(response.Result.Id); + } + else + { + Debug.Log($"Request can't be executed due to error: {response.Status.ErrorData.Information}"); + } + } + catch (Exception ex) + { + Debug.Log($"Request can't be executed due to error: {ex.Message}"); + } + // snippet.end + } + + public static async Task GetUsersBasicUsage() + { + // snippet.get_users_basic_usage + try + { + PNResult response = await pubnub.DataSync.GetUsers(new GetUsersParameters + { + Limit = 20, + Sort = "createdAt", + }); + + if (!response.Status.Error) + { + Debug.Log(response.Result.Data.Count); + } + else + { + Debug.Log($"Request can't be executed due to error: {response.Status.ErrorData.Information}"); + } + } + catch (Exception ex) + { + Debug.Log($"Request can't be executed due to error: {ex.Message}"); + } + // snippet.end + } + + public static async Task SetUserBasicUsage() + { + // snippet.set_user_basic_usage + try + { + PNResult response = await pubnub.DataSync.SetUser(new SetUserParameters + { + Id = "user-alice", + EntityClassVersion = 1, + Payload = new Dictionary + { + { "name", "Alice B." }, + { "type", "shopper" }, + }, + IfMatch = "AbQdEfGhIjKlMn", + }); + + if (!response.Status.Error) + { + Debug.Log(response.Result.Id); + } + else + { + Debug.Log($"Request can't be executed due to error: {response.Status.ErrorData.Information}"); + } + } + catch (Exception ex) + { + Debug.Log($"Request can't be executed due to error: {ex.Message}"); + } + // snippet.end + } + + public static async Task UpdateUserBasicUsage() + { + // snippet.update_user_basic_usage + try + { + PNResult response = await pubnub.DataSync.UpdateUser(new UpdateUserParameters + { + Id = "user-alice", + Operations = new List + { + new JsonPatchOperation { Op = JsonPatchOperationType.Replace, Path = "/payload/name", Value = "Alice B." }, + }, + }); + + if (!response.Status.Error) + { + Debug.Log(response.Result.Id); + } + else + { + Debug.Log($"Request can't be executed due to error: {response.Status.ErrorData.Information}"); + } + } + catch (Exception ex) + { + Debug.Log($"Request can't be executed due to error: {ex.Message}"); + } + // snippet.end + } + + public static async Task RemoveUserBasicUsage() + { + // snippet.remove_user_basic_usage + try + { + PNResult response = await pubnub.DataSync.DeleteUser(new DeleteUserParameters + { + Id = "user-alice", + }); + + Debug.Log(response.Status.StatusCode); + } + catch (Exception ex) + { + Debug.Log($"Request can't be executed due to error: {ex.Message}"); + } + // snippet.end + } + + // Channels + + public static async Task CreateChannelBasicUsage() + { + // snippet.create_channel_basic_usage + try + { + PNResult response = await pubnub.DataSync.CreateChannel(new CreateChannelParameters + { + Id = "channel-summer-sale", + EntityClassVersion = 1, + Payload = new Dictionary + { + { "name", "Summer Sale" }, + { "type", "promotion" }, + }, + }); + + if (!response.Status.Error) + { + Debug.Log(response.Result.Id); + } + else + { + Debug.Log($"Request can't be executed due to error: {response.Status.ErrorData.Information}"); + } + } + catch (Exception ex) + { + Debug.Log($"Request can't be executed due to error: {ex.Message}"); + } + // snippet.end + } + + public static async Task GetChannelBasicUsage() + { + // snippet.get_channel_basic_usage + try + { + PNResult response = await pubnub.DataSync.GetChannel(new GetChannelParameters + { + Id = "channel-summer-sale", + }); + + if (!response.Status.Error) + { + Debug.Log(response.Result.Id); + } + else + { + Debug.Log($"Request can't be executed due to error: {response.Status.ErrorData.Information}"); + } + } + catch (Exception ex) + { + Debug.Log($"Request can't be executed due to error: {ex.Message}"); + } + // snippet.end + } + + public static async Task GetChannelsBasicUsage() + { + // snippet.get_channels_basic_usage + try + { + PNResult response = await pubnub.DataSync.GetChannels(new GetChannelsParameters + { + Limit = 20, + }); + + if (!response.Status.Error) + { + Debug.Log(response.Result.Data.Count); + } + else + { + Debug.Log($"Request can't be executed due to error: {response.Status.ErrorData.Information}"); + } + } + catch (Exception ex) + { + Debug.Log($"Request can't be executed due to error: {ex.Message}"); + } + // snippet.end + } + + public static async Task SetChannelBasicUsage() + { + // snippet.set_channel_basic_usage + try + { + PNResult response = await pubnub.DataSync.SetChannel(new SetChannelParameters + { + Id = "channel-summer-sale", + EntityClassVersion = 1, + Payload = new Dictionary + { + { "name", "Summer Sale 2026" }, + { "type", "promotion" }, + }, + }); + + if (!response.Status.Error) + { + Debug.Log(response.Result.Id); + } + else + { + Debug.Log($"Request can't be executed due to error: {response.Status.ErrorData.Information}"); + } + } + catch (Exception ex) + { + Debug.Log($"Request can't be executed due to error: {ex.Message}"); + } + // snippet.end + } + + public static async Task UpdateChannelBasicUsage() + { + // snippet.update_channel_basic_usage + try + { + PNResult response = await pubnub.DataSync.UpdateChannel(new UpdateChannelParameters + { + Id = "channel-summer-sale", + Operations = new List + { + new JsonPatchOperation { Op = JsonPatchOperationType.Replace, Path = "/payload/name", Value = "Summer Sale 2026" }, + }, + }); + + if (!response.Status.Error) + { + Debug.Log(response.Result.Id); + } + else + { + Debug.Log($"Request can't be executed due to error: {response.Status.ErrorData.Information}"); + } + } + catch (Exception ex) + { + Debug.Log($"Request can't be executed due to error: {ex.Message}"); + } + // snippet.end + } + + public static async Task RemoveChannelBasicUsage() + { + // snippet.remove_channel_basic_usage + try + { + PNResult response = await pubnub.DataSync.DeleteChannel(new DeleteChannelParameters + { + Id = "channel-summer-sale", + }); + + Debug.Log(response.Status.StatusCode); + } + catch (Exception ex) + { + Debug.Log($"Request can't be executed due to error: {ex.Message}"); + } + // snippet.end + } + + // Memberships + + public static async Task CreateMembershipBasicUsage() + { + // snippet.create_membership_basic_usage + try + { + PNResult response = await pubnub.DataSync.CreateMembership(new CreateMembershipParameters + { + ChannelId = "channel-summer-sale", + UserId = "user-alice", + MembershipClassVersion = 1, + Payload = new Dictionary + { + { "role", "viewer" }, + }, + }); + + if (!response.Status.Error) + { + Debug.Log(response.Result.Id); + } + else + { + Debug.Log($"Request can't be executed due to error: {response.Status.ErrorData.Information}"); + } + } + catch (Exception ex) + { + Debug.Log($"Request can't be executed due to error: {ex.Message}"); + } + // snippet.end + } + + public static async Task GetMembershipBasicUsage() + { + // snippet.get_membership_basic_usage + try + { + PNResult response = await pubnub.DataSync.GetMembership(new GetMembershipParameters + { + Id = "membership-alice-summer-sale", + }); + + if (!response.Status.Error) + { + Debug.Log(response.Result.Id); + } + else + { + Debug.Log($"Request can't be executed due to error: {response.Status.ErrorData.Information}"); + } + } + catch (Exception ex) + { + Debug.Log($"Request can't be executed due to error: {ex.Message}"); + } + // snippet.end + } + + public static async Task GetMembershipsBasicUsage() + { + // snippet.get_memberships_basic_usage + try + { + PNResult response = await pubnub.DataSync.GetMemberships(new GetMembershipsParameters + { + UserId = "user-alice", + Limit = 20, + }); + + if (!response.Status.Error) + { + Debug.Log(response.Result.Data.Count); + } + else + { + Debug.Log($"Request can't be executed due to error: {response.Status.ErrorData.Information}"); + } + } + catch (Exception ex) + { + Debug.Log($"Request can't be executed due to error: {ex.Message}"); + } + // snippet.end + } + + public static async Task SetMembershipBasicUsage() + { + // snippet.set_membership_basic_usage + try + { + PNResult response = await pubnub.DataSync.SetMembership(new UpdateMembershipParameters + { + Id = "membership-alice-summer-sale", + RelationshipClassVersion = 1, + Payload = new Dictionary + { + { "role", "moderator" }, + }, + }); + + if (!response.Status.Error) + { + Debug.Log(response.Result.Id); + } + else + { + Debug.Log($"Request can't be executed due to error: {response.Status.ErrorData.Information}"); + } + } + catch (Exception ex) + { + Debug.Log($"Request can't be executed due to error: {ex.Message}"); + } + // snippet.end + } + + public static async Task UpdateMembershipBasicUsage() + { + // snippet.update_membership_basic_usage + try + { + PNResult response = await pubnub.DataSync.UpdateMembership(new PatchMembershipParameters + { + Id = "membership-alice-summer-sale", + Operations = new List + { + new JsonPatchOperation { Op = JsonPatchOperationType.Replace, Path = "/payload/role", Value = "moderator" }, + }, + }); + + if (!response.Status.Error) + { + Debug.Log(response.Result.Id); + } + else + { + Debug.Log($"Request can't be executed due to error: {response.Status.ErrorData.Information}"); + } + } + catch (Exception ex) + { + Debug.Log($"Request can't be executed due to error: {ex.Message}"); + } + // snippet.end + } + + public static async Task RemoveMembershipBasicUsage() + { + // snippet.remove_membership_basic_usage + try + { + PNResult response = await pubnub.DataSync.DeleteMembership(new DeleteMembershipParameters + { + Id = "membership-alice-summer-sale", + }); + + Debug.Log(response.Status.StatusCode); + } + catch (Exception ex) + { + Debug.Log($"Request can't be executed due to error: {ex.Message}"); + } + // snippet.end + } + + // Entities + + public static async Task CreateEntityBasicUsage() + { + // snippet.create_entity_basic_usage + try + { + PNResult response = await pubnub.DataSync.CreateEntity(new CreateEntityParameters + { + Id = "product-sneaker-42", + EntityClass = "product", + EntityClassVersion = 1, + Payload = new Dictionary + { + { "name", "Retro Sneaker" }, + { "price", 89.99 }, + { "stock", 12 }, + }, + }); + + if (!response.Status.Error) + { + Debug.Log(response.Result.Id); + } + else + { + Debug.Log($"Request can't be executed due to error: {response.Status.ErrorData.Information}"); + } + } + catch (Exception ex) + { + Debug.Log($"Request can't be executed due to error: {ex.Message}"); + } + // snippet.end + } + + public static async Task GetEntityBasicUsage() + { + // snippet.get_entity_basic_usage + try + { + PNResult response = await pubnub.DataSync.GetEntity(new GetEntityParameters + { + Id = "product-sneaker-42", + }); + + if (!response.Status.Error) + { + Debug.Log(response.Result.Id); + } + else + { + Debug.Log($"Request can't be executed due to error: {response.Status.ErrorData.Information}"); + } + } + catch (Exception ex) + { + Debug.Log($"Request can't be executed due to error: {ex.Message}"); + } + // snippet.end + } + + public static async Task GetEntitiesBasicUsage() + { + // snippet.get_entities_basic_usage + try + { + PNResult response = await pubnub.DataSync.GetEntities(new GetEntitiesParameters + { + EntityClass = "product", + Sort = "price:desc", + Limit = 20, + }); + + if (!response.Status.Error) + { + Debug.Log(response.Result.Data.Count); + } + else + { + Debug.Log($"Request can't be executed due to error: {response.Status.ErrorData.Information}"); + } + } + catch (Exception ex) + { + Debug.Log($"Request can't be executed due to error: {ex.Message}"); + } + // snippet.end + } + + public static async Task SetEntityBasicUsage() + { + // snippet.set_entity_basic_usage + try + { + PNResult response = await pubnub.DataSync.SetEntity(new SetEntityParameters + { + Id = "product-sneaker-42", + EntityClassVersion = 1, + Payload = new Dictionary + { + { "name", "Retro Sneaker" }, + { "price", 79.99 }, + { "stock", 8 }, + }, + }); + + if (!response.Status.Error) + { + Debug.Log(response.Result.Id); + } + else + { + Debug.Log($"Request can't be executed due to error: {response.Status.ErrorData.Information}"); + } + } + catch (Exception ex) + { + Debug.Log($"Request can't be executed due to error: {ex.Message}"); + } + // snippet.end + } + + public static async Task UpdateEntityBasicUsage() + { + // snippet.update_entity_basic_usage + try + { + PNResult response = await pubnub.DataSync.UpdateEntity(new UpdateEntityParameters + { + Id = "product-sneaker-42", + Operations = new List + { + new JsonPatchOperation { Op = JsonPatchOperationType.Replace, Path = "/payload/price", Value = 79.99 }, + }, + }); + + if (!response.Status.Error) + { + Debug.Log(response.Result.Id); + } + else + { + Debug.Log($"Request can't be executed due to error: {response.Status.ErrorData.Information}"); + } + } + catch (Exception ex) + { + Debug.Log($"Request can't be executed due to error: {ex.Message}"); + } + // snippet.end + } + + public static async Task RemoveEntityBasicUsage() + { + // snippet.remove_entity_basic_usage + try + { + PNResult response = await pubnub.DataSync.DeleteEntity(new DeleteEntityParameters + { + Id = "product-sneaker-42", + }); + + Debug.Log(response.Status.StatusCode); + } + catch (Exception ex) + { + Debug.Log($"Request can't be executed due to error: {ex.Message}"); + } + // snippet.end + } + + // Relationships + + public static async Task CreateRelationshipBasicUsage() + { + // snippet.create_relationship_basic_usage + try + { + PNResult response = await pubnub.DataSync.CreateRelationship(new CreateRelationshipParameters + { + EntityAId = "seller-bob", + EntityBId = "product-sneaker-42", + RelationshipClass = "ProductOwner", + RelationshipClassVersion = 1, + Payload = new Dictionary + { + { "since", "2026-07-13" }, + }, + }); + + if (!response.Status.Error) + { + Debug.Log(response.Result.Id); + } + else + { + Debug.Log($"Request can't be executed due to error: {response.Status.ErrorData.Information}"); + } + } + catch (Exception ex) + { + Debug.Log($"Request can't be executed due to error: {ex.Message}"); + } + // snippet.end + } + + public static async Task GetRelationshipBasicUsage() + { + // snippet.get_relationship_basic_usage + try + { + PNResult response = await pubnub.DataSync.GetRelationship(new GetRelationshipParameters + { + Id = "rel-bob-owns-sneaker-42", + }); + + if (!response.Status.Error) + { + Debug.Log(response.Result.Id); + } + else + { + Debug.Log($"Request can't be executed due to error: {response.Status.ErrorData.Information}"); + } + } + catch (Exception ex) + { + Debug.Log($"Request can't be executed due to error: {ex.Message}"); + } + // snippet.end + } + + public static async Task GetRelationshipsBasicUsage() + { + // snippet.get_relationships_basic_usage + try + { + PNResult response = await pubnub.DataSync.GetRelationships(new GetRelationshipsParameters + { + RelationshipClass = "ProductOwner", + EntityAId = "seller-bob", + Limit = 20, + }); + + if (!response.Status.Error) + { + Debug.Log(response.Result.Data.Count); + } + else + { + Debug.Log($"Request can't be executed due to error: {response.Status.ErrorData.Information}"); + } + } + catch (Exception ex) + { + Debug.Log($"Request can't be executed due to error: {ex.Message}"); + } + // snippet.end + } + + public static async Task SetRelationshipBasicUsage() + { + // snippet.set_relationship_basic_usage + try + { + PNResult response = await pubnub.DataSync.SetRelationship(new SetRelationshipParameters + { + Id = "rel-bob-owns-sneaker-42", + RelationshipClassVersion = 1, + Payload = new Dictionary + { + { "since", "2026-07-13" }, + { "tier", "gold" }, + }, + }); + + if (!response.Status.Error) + { + Debug.Log(response.Result.Id); + } + else + { + Debug.Log($"Request can't be executed due to error: {response.Status.ErrorData.Information}"); + } + } + catch (Exception ex) + { + Debug.Log($"Request can't be executed due to error: {ex.Message}"); + } + // snippet.end + } + + public static async Task UpdateRelationshipBasicUsage() + { + // snippet.update_relationship_basic_usage + try + { + PNResult response = await pubnub.DataSync.UpdateRelationship(new UpdateRelationshipParameters + { + Id = "rel-bob-owns-sneaker-42", + Operations = new List + { + new JsonPatchOperation { Op = JsonPatchOperationType.Replace, Path = "/payload/tier", Value = "platinum" }, + }, + }); + + if (!response.Status.Error) + { + Debug.Log(response.Result.Id); + } + else + { + Debug.Log($"Request can't be executed due to error: {response.Status.ErrorData.Information}"); + } + } + catch (Exception ex) + { + Debug.Log($"Request can't be executed due to error: {ex.Message}"); + } + // snippet.end + } + + public static async Task RemoveRelationshipBasicUsage() + { + // snippet.remove_relationship_basic_usage + try + { + PNResult response = await pubnub.DataSync.DeleteRelationship(new DeleteRelationshipParameters + { + Id = "rel-bob-owns-sneaker-42", + }); + + Debug.Log(response.Status.StatusCode); + } + catch (Exception ex) + { + Debug.Log($"Request can't be executed due to error: {ex.Message}"); + } + // snippet.end + } + + // Users - other examples + + public static async Task GetUsersFilterFast() + { + // snippet.get_users_filter_fast + try + { + PNResult response = await pubnub.DataSync.GetUsers(new GetUsersParameters + { + FilterFast = "type == \"shopper\"", + }); + + if (!response.Status.Error) + { + Debug.Log(response.Result.Data.Count); + } + else + { + Debug.Log($"Request can't be executed due to error: {response.Status.ErrorData.Information}"); + } + } + catch (Exception ex) + { + Debug.Log($"Request can't be executed due to error: {ex.Message}"); + } + // snippet.end + } + + public static async Task GetUsersFilter() + { + // snippet.get_users_filter + try + { + PNResult response = await pubnub.DataSync.GetUsers(new GetUsersParameters + { + Filter = "name LIKE \"*Alice*\"", + }); + + if (!response.Status.Error) + { + Debug.Log(response.Result.Data.Count); + } + else + { + Debug.Log($"Request can't be executed due to error: {response.Status.ErrorData.Information}"); + } + } + catch (Exception ex) + { + Debug.Log($"Request can't be executed due to error: {ex.Message}"); + } + // snippet.end + } + + public static async Task GetUsersPagination() + { + // snippet.get_users_pagination + try + { + string cursor = null; + bool hasNext = true; + int page = 0; + + while (hasNext) + { + PNResult response = await pubnub.DataSync.GetUsers(new GetUsersParameters + { + FilterFast = "type == \"shopper\"", + Limit = 20, + Cursor = cursor, + }); + + if (!response.Status.Error) + { + Debug.Log($"Page {++page}: {response.Result.Data.Count}"); + cursor = response.Result.Meta.NextCursor; + hasNext = response.Result.Meta.HasNext; + } + else + { + Debug.Log($"Request can't be executed due to error: {response.Status.ErrorData.Information}"); + hasNext = false; + } + } + } + catch (Exception ex) + { + Debug.Log($"Request can't be executed due to error: {ex.Message}"); + } + // snippet.end + } + + // Channels - other examples + + public static async Task GetChannelsFilterFast() + { + // snippet.get_channels_filter_fast + try + { + PNResult response = await pubnub.DataSync.GetChannels(new GetChannelsParameters + { + FilterFast = "type == \"promotion\"", + }); + + if (!response.Status.Error) + { + Debug.Log(response.Result.Data.Count); + } + else + { + Debug.Log($"Request can't be executed due to error: {response.Status.ErrorData.Information}"); + } + } + catch (Exception ex) + { + Debug.Log($"Request can't be executed due to error: {ex.Message}"); + } + // snippet.end + } + + public static async Task GetChannelsFilter() + { + // snippet.get_channels_filter + try + { + PNResult response = await pubnub.DataSync.GetChannels(new GetChannelsParameters + { + Filter = "name LIKE \"*Sale*\"", + }); + + if (!response.Status.Error) + { + Debug.Log(response.Result.Data.Count); + } + else + { + Debug.Log($"Request can't be executed due to error: {response.Status.ErrorData.Information}"); + } + } + catch (Exception ex) + { + Debug.Log($"Request can't be executed due to error: {ex.Message}"); + } + // snippet.end + } + + public static async Task GetChannelsPagination() + { + // snippet.get_channels_pagination + try + { + string cursor = null; + bool hasNext = true; + int page = 0; + + while (hasNext) + { + PNResult response = await pubnub.DataSync.GetChannels(new GetChannelsParameters + { + FilterFast = "type == \"promotion\"", + Limit = 20, + Cursor = cursor, + }); + + if (!response.Status.Error) + { + Debug.Log($"Page {++page}: {response.Result.Data.Count}"); + cursor = response.Result.Meta.NextCursor; + hasNext = response.Result.Meta.HasNext; + } + else + { + Debug.Log($"Request can't be executed due to error: {response.Status.ErrorData.Information}"); + hasNext = false; + } + } + } + catch (Exception ex) + { + Debug.Log($"Request can't be executed due to error: {ex.Message}"); + } + // snippet.end + } + + // Memberships - other examples + + public static async Task GetMembershipsByChannelId() + { + // snippet.get_memberships_by_channel_id + try + { + PNResult response = await pubnub.DataSync.GetMemberships(new GetMembershipsParameters + { + ChannelId = "channel-summer-sale", + Limit = 20, + }); + + if (!response.Status.Error) + { + Debug.Log(response.Result.Data.Count); + } + else + { + Debug.Log($"Request can't be executed due to error: {response.Status.ErrorData.Information}"); + } + } + catch (Exception ex) + { + Debug.Log($"Request can't be executed due to error: {ex.Message}"); + } + // snippet.end + } + + public static async Task GetMembershipsFilterFast() + { + // snippet.get_memberships_filter_fast + try + { + PNResult response = await pubnub.DataSync.GetMemberships(new GetMembershipsParameters + { + UserId = "user-alice", + FilterFast = "role == \"viewer\"", + }); + + if (!response.Status.Error) + { + Debug.Log(response.Result.Data.Count); + } + else + { + Debug.Log($"Request can't be executed due to error: {response.Status.ErrorData.Information}"); + } + } + catch (Exception ex) + { + Debug.Log($"Request can't be executed due to error: {ex.Message}"); + } + // snippet.end + } + + public static async Task GetMembershipsFilter() + { + // snippet.get_memberships_filter + try + { + PNResult response = await pubnub.DataSync.GetMemberships(new GetMembershipsParameters + { + ChannelId = "channel-summer-sale", + Filter = "role LIKE \"*mod*\"", + }); + + if (!response.Status.Error) + { + Debug.Log(response.Result.Data.Count); + } + else + { + Debug.Log($"Request can't be executed due to error: {response.Status.ErrorData.Information}"); + } + } + catch (Exception ex) + { + Debug.Log($"Request can't be executed due to error: {ex.Message}"); + } + // snippet.end + } + + public static async Task GetMembershipsPagination() + { + // snippet.get_memberships_pagination + try + { + string cursor = null; + bool hasNext = true; + int page = 0; + + while (hasNext) + { + PNResult response = await pubnub.DataSync.GetMemberships(new GetMembershipsParameters + { + UserId = "user-alice", + Limit = 20, + Cursor = cursor, + }); + + if (!response.Status.Error) + { + Debug.Log($"Page {++page}: {response.Result.Data.Count}"); + cursor = response.Result.Meta.NextCursor; + hasNext = response.Result.Meta.HasNext; + } + else + { + Debug.Log($"Request can't be executed due to error: {response.Status.ErrorData.Information}"); + hasNext = false; + } + } + } + catch (Exception ex) + { + Debug.Log($"Request can't be executed due to error: {ex.Message}"); + } + // snippet.end + } + + // Entities - other examples + + public static async Task GetEntitiesFilterFast() + { + // snippet.get_entities_filter_fast + try + { + PNResult response = await pubnub.DataSync.GetEntities(new GetEntitiesParameters + { + EntityClass = "product", + FilterFast = "price < 100 && stock > 0", + }); + + if (!response.Status.Error) + { + Debug.Log(response.Result.Data.Count); + } + else + { + Debug.Log($"Request can't be executed due to error: {response.Status.ErrorData.Information}"); + } + } + catch (Exception ex) + { + Debug.Log($"Request can't be executed due to error: {ex.Message}"); + } + // snippet.end + } + + public static async Task GetEntitiesFilter() + { + // snippet.get_entities_filter + try + { + PNResult response = await pubnub.DataSync.GetEntities(new GetEntitiesParameters + { + EntityClass = "product", + Filter = "name LIKE \"*sneaker*\" && !(status == \"discontinued\")", + }); + + if (!response.Status.Error) + { + Debug.Log(response.Result.Data.Count); + } + else + { + Debug.Log($"Request can't be executed due to error: {response.Status.ErrorData.Information}"); + } + } + catch (Exception ex) + { + Debug.Log($"Request can't be executed due to error: {ex.Message}"); + } + // snippet.end + } + + public static async Task GetEntitiesPagination() + { + // snippet.get_entities_pagination + try + { + string cursor = null; + bool hasNext = true; + int page = 0; + + while (hasNext) + { + PNResult response = await pubnub.DataSync.GetEntities(new GetEntitiesParameters + { + EntityClass = "product", + FilterFast = "price < 100", + Limit = 20, + Cursor = cursor, + }); + + if (!response.Status.Error) + { + Debug.Log($"Page {++page}: {response.Result.Data.Count}"); + cursor = response.Result.Meta.NextCursor; + hasNext = response.Result.Meta.HasNext; + } + else + { + Debug.Log($"Request can't be executed due to error: {response.Status.ErrorData.Information}"); + hasNext = false; + } + } + } + catch (Exception ex) + { + Debug.Log($"Request can't be executed due to error: {ex.Message}"); + } + // snippet.end + } + + public static async Task UpdateEntityMultipleOperations() + { + // snippet.update_entity_multiple_operations + try + { + PNResult response = await pubnub.DataSync.UpdateEntity(new UpdateEntityParameters + { + Id = "product-sneaker-42", + Operations = new List + { + new JsonPatchOperation { Op = JsonPatchOperationType.Test, Path = "/payload/stock", Value = 8 }, + new JsonPatchOperation { Op = JsonPatchOperationType.Replace, Path = "/payload/price", Value = 74.99 }, + new JsonPatchOperation { Op = JsonPatchOperationType.Add, Path = "/payload/tags/-", Value = "clearance" }, + new JsonPatchOperation { Op = JsonPatchOperationType.Remove, Path = "/payload/legacy/field" }, + new JsonPatchOperation { Op = JsonPatchOperationType.Move, Path = "/payload/displayName", From = "/payload/legacyName" }, + new JsonPatchOperation { Op = JsonPatchOperationType.Copy, Path = "/payload/previousName", From = "/payload/displayName" }, + }, + IfMatch = "StUvWxYzAbCdEf", + }); + + if (!response.Status.Error) + { + Debug.Log(response.Result.Id); + } + else + { + Debug.Log($"Request can't be executed due to error: {response.Status.ErrorData.Information}"); + } + } + catch (Exception ex) + { + Debug.Log($"Request can't be executed due to error: {ex.Message}"); + } + // snippet.end + } + + // Relationships - other examples + + public static async Task GetRelationshipsByEntityBId() + { + // snippet.get_relationships_by_entity_b_id + try + { + PNResult response = await pubnub.DataSync.GetRelationships(new GetRelationshipsParameters + { + RelationshipClass = "ProductOwner", + EntityBId = "product-sneaker-42", + Limit = 20, + }); + + if (!response.Status.Error) + { + Debug.Log(response.Result.Data.Count); + } + else + { + Debug.Log($"Request can't be executed due to error: {response.Status.ErrorData.Information}"); + } + } + catch (Exception ex) + { + Debug.Log($"Request can't be executed due to error: {ex.Message}"); + } + // snippet.end + } + + public static async Task GetRelationshipsFilterFast() + { + // snippet.get_relationships_filter_fast + try + { + PNResult response = await pubnub.DataSync.GetRelationships(new GetRelationshipsParameters + { + RelationshipClass = "ProductOwner", + FilterFast = "tier == \"gold\"", + }); + + if (!response.Status.Error) + { + Debug.Log(response.Result.Data.Count); + } + else + { + Debug.Log($"Request can't be executed due to error: {response.Status.ErrorData.Information}"); + } + } + catch (Exception ex) + { + Debug.Log($"Request can't be executed due to error: {ex.Message}"); + } + // snippet.end + } + + public static async Task GetRelationshipsFilter() + { + // snippet.get_relationships_filter + try + { + PNResult response = await pubnub.DataSync.GetRelationships(new GetRelationshipsParameters + { + RelationshipClass = "ProductOwner", + Filter = "!(tier == \"platinum\")", + }); + + if (!response.Status.Error) + { + Debug.Log(response.Result.Data.Count); + } + else + { + Debug.Log($"Request can't be executed due to error: {response.Status.ErrorData.Information}"); + } + } + catch (Exception ex) + { + Debug.Log($"Request can't be executed due to error: {ex.Message}"); + } + // snippet.end + } + + public static async Task GetRelationshipsPagination() + { + // snippet.get_relationships_pagination + try + { + string cursor = null; + bool hasNext = true; + int page = 0; + + while (hasNext) + { + PNResult response = await pubnub.DataSync.GetRelationships(new GetRelationshipsParameters + { + RelationshipClass = "ProductOwner", + EntityAId = "seller-bob", + Limit = 20, + Cursor = cursor, + }); + + if (!response.Status.Error) + { + Debug.Log($"Page {++page}: {response.Result.Data.Count}"); + cursor = response.Result.Meta.NextCursor; + hasNext = response.Result.Meta.HasNext; + } + else + { + Debug.Log($"Request can't be executed due to error: {response.Status.ErrorData.Information}"); + hasNext = false; + } + } + } + catch (Exception ex) + { + Debug.Log($"Request can't be executed due to error: {ex.Message}"); + } + // snippet.end + } + + // Real-time updates + + public static async Task DataSyncEventListener() + { + // snippet.data_sync_event_listener + SubscribeCallbackListener listener = new SubscribeCallbackListener(); + listener.onDataSync += (pn, dataSyncEvent) => + { + string changedId = dataSyncEvent.EntityData?.Id + ?? dataSyncEvent.RelationshipData?.Id + ?? dataSyncEvent.Id; + Debug.Log($"{dataSyncEvent.Event} {changedId}"); + }; + + Subscription subscription = pubnub.Channel("product-sneaker-42").Subscription(); + subscription.AddListener(listener); + subscription.Subscribe(); + // snippet.end + } + + // Projection channels + + public static async Task SubscribeToProjectionChannels() + { + // snippet.subscribe_to_projection_channels + // Base projection: the __default__ view of the payload. + Subscription baseSubscription = pubnub.Channel("product-sneaker-42").Subscription(); + + // admin projection: the admin view of the payload. + Subscription adminSubscription = pubnub.Channel("__admin__product-sneaker-42").Subscription(); + // snippet.end + } +} diff --git a/PubNubUnity/Assets/Snippets/DataSync/DataSyncSample.cs.meta b/PubNubUnity/Assets/Snippets/DataSync/DataSyncSample.cs.meta new file mode 100644 index 00000000..ec27d831 --- /dev/null +++ b/PubNubUnity/Assets/Snippets/DataSync/DataSyncSample.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 098f7dc84cec476da61c7d84e5afb17a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/PubNubUnity/Assets/Snippets/Publish/PublishSubscribeSample.cs b/PubNubUnity/Assets/Snippets/Publish/PublishSubscribeSample.cs index 329ab04a..6b5c9393 100644 --- a/PubNubUnity/Assets/Snippets/Publish/PublishSubscribeSample.cs +++ b/PubNubUnity/Assets/Snippets/Publish/PublishSubscribeSample.cs @@ -603,6 +603,7 @@ public static void AddListenerBasicUsage() delegate(Pubnub pn, PNPresenceEventResult e) { Debug.Log("Presence event"); }, delegate(Pubnub pn, PNSignalResult e) { Debug.Log("Signal event"); }, delegate(Pubnub pn, PNObjectEventResult e) { Debug.Log("Object event"); }, + delegate(Pubnub pn, PNDataSyncEventResult e) { Debug.Log("Data Sync event"); }, delegate(Pubnub pn, PNMessageActionEventResult e) { Debug.Log("Message Action event"); }, delegate(Pubnub pn, PNFileEventResult e) { Debug.Log("File event"); }, delegate(Pubnub pn, PNStatus e) { Debug.Log("Status event"); } @@ -703,6 +704,33 @@ public static void AddListenerMethod1() Debug.Log(pubnub.JsonPluggableLibrary.SerializeToJsonString(objectEventObj)); }, + delegate(Pubnub pnObj, PNDataSyncEventResult dataSyncEventObj) + { + var channelName = dataSyncEventObj.Channel; // Channel + var evnt = dataSyncEventObj.Event; // Event + var type = dataSyncEventObj.Type; // Event type + if (type == "user") + { + /* got user related event. */ + } + else if (type == "channel") + { + /* got channel related event. */ + } + else if (type == "membership") + { + /* got membership related event. */ + } + else if (type == "entity") + { + /* got generic entity related event. */ + } + else if (type == "relationship") + { + /* got generic relationship related event. */ + } + Debug.Log(pubnub.JsonPluggableLibrary.SerializeToJsonString(dataSyncEventObj)); + }, delegate(Pubnub pnObj, PNMessageActionEventResult msgActionEvent) { //handle message action @@ -960,4 +988,120 @@ public static void Entities() { userMetadata.Subscription().Subscribe(); // snippet.end } -} \ No newline at end of file + + public static void AddDataSyncListener() + { + // snippet.add_datasync_listener + Subscription subscription = pubnub.Channel("product-sneaker-42").Subscription(); + + SubscribeCallbackListener listener = new SubscribeCallbackListener(); + listener.onDataSync += (pn, dataSyncEvent) => { /* handle event */ }; + listener.onStatus += (pn, status) => { /* handle status */ }; + + subscription.AddListener(listener); + // snippet.end + } + + public static void HandleDataSyncEvent() + { + // snippet.handle_datasync_event + // Subscribe to the object's id channel and attach the listener + const string objectId = "product-sneaker-42"; + Subscription subscription = pubnub.Channel(objectId).Subscription(); + + SubscribeCallbackListener listener = new SubscribeCallbackListener(); + listener.onDataSync += (pn, dataSyncEvent) => + { + // This channel can also receive relationship events and events about + // connected entities. Only handle events for the observed product. + string changedId = dataSyncEvent.Event == "delete" + ? dataSyncEvent.Id + : dataSyncEvent.EntityData?.Id; + + if (dataSyncEvent.Type != "entity" || changedId != objectId) + { + return; + } + + switch (dataSyncEvent.Event) + { + case "create": + Debug.Log("Product created: " + changedId); + break; + case "update": + Dictionary payload = dataSyncEvent.EntityData?.Payload; + if (payload != null && payload.TryGetValue("price", out object price)) + { + Debug.Log("New price: " + price); + } + break; + case "delete": + Debug.Log("Product deleted: " + changedId); + break; + } + }; + + subscription.AddListener(listener); + subscription.Subscribe(); + // snippet.end + } + + public static void DataSyncLocalCopySync() + { + // snippet.datasync_local_copy_sync + PNDataSyncEntityResult localCopy = null; + + async void RefreshLocalCopy() + { + PNResult response = await pubnub.DataSync.GetEntity(new GetEntityParameters + { + Id = "product-sneaker-42", + }); + + if (!response.Status.Error) + { + localCopy = response.Result; + } + } + + void ApplyEvent(PNDataSyncEventResult dataSyncEvent) + { + const string objectId = "product-sneaker-42"; + + // This channel can also receive relationship events and events about + // connected entities. Only apply events for the fetched entity. + // `Product` is a class of your own, so its events report Type "entity". + // A built-in user, channel, or membership reports "user", "channel", or "membership". + if (dataSyncEvent.Type != "entity") + { + return; + } + + if (dataSyncEvent.Event == "delete") + { + if (dataSyncEvent.Id != objectId) + { + return; + } + + localCopy = null; + return; + } + + PNDataSyncEntityResult changedEntity = dataSyncEvent.EntityData; + if (changedEntity == null || changedEntity.Id != objectId) + { + return; + } + + // Skip events that are older than what you already have. + if (localCopy != null && string.CompareOrdinal(changedEntity.UpdatedAt, localCopy.UpdatedAt) <= 0) + { + return; + } + + localCopy = changedEntity; + } + // snippet.end + } +} diff --git a/VERSION b/VERSION index d223b451..a13e7b9c 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -9.5.0 +10.0.0