From 0edb333babe3a7331a801cde3647c38dfedf7ce4 Mon Sep 17 00:00:00 2001 From: "PUBNUB\\jakub.grzesiowski" Date: Fri, 21 Nov 2025 15:32:26 +0100 Subject: [PATCH 1/6] Implement the "pending" status for Memberships created from Invites --- .../PubNubChatApi.Tests/ChannelTests.cs | 29 ++++++++++++++++++ .../PubNubChatApi.Tests/MembershipTests.cs | 8 +++-- .../PubnubChatApi/Entities/Channel.cs | 30 ++++++++++++++++++- .../PubnubChatApi/Entities/Chat.cs | 9 ++++-- .../Entities/Data/ChatMembershipData.cs | 4 +-- .../Runtime/PubnubChatApi/Entities/Channel.cs | 30 ++++++++++++++++++- .../Runtime/PubnubChatApi/Entities/Chat.cs | 9 ++++-- .../Entities/Data/ChatMembershipData.cs | 4 +-- 8 files changed, 109 insertions(+), 14 deletions(-) diff --git a/c-sharp-chat/PubnubChatApi/PubNubChatApi.Tests/ChannelTests.cs b/c-sharp-chat/PubnubChatApi/PubNubChatApi.Tests/ChannelTests.cs index 62e40c4..14d977e 100644 --- a/c-sharp-chat/PubnubChatApi/PubNubChatApi.Tests/ChannelTests.cs +++ b/c-sharp-chat/PubnubChatApi/PubNubChatApi.Tests/ChannelTests.cs @@ -169,6 +169,35 @@ public async Task TestGetMemberships() Assert.That(memberships.Memberships.Count, Is.GreaterThanOrEqualTo(1)); } + [Test] + public async Task TestGetInvitees() + { + var channel = TestUtils.AssertOperation(await chat.CreatePublicConversation()); + await channel.Invite(user); + await Task.Delay(3500); + var invitees = TestUtils.AssertOperation(await channel.GetInvitees()); + Assert.True(invitees.Memberships.Any(x => x.UserId == user.Id && x.ChannelId == channel.Id && x.MembershipData.Status == "pending")); + + //Cleanup + await channel.Delete(); + } + + [Test] + public async Task TestInviteAndJoin() + { + var channel = TestUtils.AssertOperation(await chat.CreatePublicConversation()); + await channel.Invite(user); + await Task.Delay(3500); + var invitees = TestUtils.AssertOperation(await channel.GetInvitees()); + Assert.True(invitees.Memberships.Any(x => x.UserId == user.Id && x.ChannelId == channel.Id && x.MembershipData.Status == "pending")); + await channel.Join(); + await Task.Delay(3500); + invitees = TestUtils.AssertOperation(await channel.GetInvitees()); + Assert.False(invitees.Memberships.Any()); + var members = TestUtils.AssertOperation(await channel.GetMemberships()); + Assert.True(members.Memberships.Any(x => x.UserId == user.Id && x.ChannelId == channel.Id && x.MembershipData.Status != "pending")); + } + [Test] public async Task TestStartTyping() { diff --git a/c-sharp-chat/PubnubChatApi/PubNubChatApi.Tests/MembershipTests.cs b/c-sharp-chat/PubnubChatApi/PubNubChatApi.Tests/MembershipTests.cs index fbd5c3a..0796a0e 100644 --- a/c-sharp-chat/PubnubChatApi/PubNubChatApi.Tests/MembershipTests.cs +++ b/c-sharp-chat/PubnubChatApi/PubNubChatApi.Tests/MembershipTests.cs @@ -85,10 +85,13 @@ public async Task TestUpdateMemberships() [Test] public async Task TestInvite() { - var testChannel = TestUtils.AssertOperation(await chat.CreateGroupConversation([user], "test_invite_group_channel")).CreatedChannel; + var testChannel = TestUtils.AssertOperation(await chat.CreateGroupConversation([user], Guid.NewGuid().ToString())).CreatedChannel; var testUser = await chat.GetOrCreateUser("test_invite_user"); var returnedMembership = TestUtils.AssertOperation(await testChannel.Invite(testUser)); - Assert.True(returnedMembership.ChannelId == testChannel.Id && returnedMembership.UserId == testUser.Id); + Assert.True(returnedMembership.ChannelId == testChannel.Id && returnedMembership.UserId == testUser.Id && returnedMembership.MembershipData.Status == "pending"); + + //Cleanup + await testChannel.Delete(); } [Test] @@ -106,6 +109,7 @@ public async Task TestInviteMultiple() returnedMemberships.Count == 2 && returnedMemberships.Any(x => x.UserId == secondUser.Id && x.ChannelId == testChannel.Id) && returnedMemberships.Any(x => x.UserId == thirdUser.Id && x.ChannelId == testChannel.Id)); + Assert.True(returnedMemberships.All(x => x.MembershipData.Status == "pending")); } [Test] diff --git a/c-sharp-chat/PubnubChatApi/PubnubChatApi/Entities/Channel.cs b/c-sharp-chat/PubnubChatApi/PubnubChatApi/Entities/Channel.cs index 3673150..9f9b321 100644 --- a/c-sharp-chat/PubnubChatApi/PubnubChatApi/Entities/Channel.cs +++ b/c-sharp-chat/PubnubChatApi/PubnubChatApi/Entities/Channel.cs @@ -1148,7 +1148,7 @@ public async Task>> WhoIsPresent() /// The sort parameter. /// The maximum amount of the memberships received. /// The page object for pagination. - /// A ChatOperationResult containing the list of the Membership objects. + /// A ChatOperationResult containing a wrapper object with the list of the Membership objects. /// /// /// var channel = //... @@ -1166,6 +1166,34 @@ public async Task> GetMemberships(st return await chat.GetChannelMemberships(Id, filter, sort, limit, page).ConfigureAwait(false); } + /// + /// Gets the list of the Membership objects which have a Status of "pending". + /// + /// Gets the list of the Membership objects that represent the users that are invited + /// to the channel. + /// + /// + /// The sort parameter. + /// The maximum amount of the memberships received. + /// The page object for pagination. + /// A ChatOperationResult containing a wrapper object with the list of the Membership objects. + /// + /// + /// var channel = //... + /// var result = await channel.GetInvitees(limit: 10); + /// var invites = result.Result.Memberships; + /// foreach (var invited in invites) { + /// Console.WriteLine($"Invited user: {invited.UserId}"); + /// } + /// + /// + /// + public async Task> GetInvitees(string sort = "", int limit = 0, + PNPageObject page = null) + { + return await chat.GetChannelMemberships(Id, "status == \"pending\"", sort, limit, page).ConfigureAwait(false); + } + /// /// Asynchronously gets the Message object for the given timetoken sent from this Channel. /// diff --git a/c-sharp-chat/PubnubChatApi/PubnubChatApi/Entities/Chat.cs b/c-sharp-chat/PubnubChatApi/PubnubChatApi/Entities/Chat.cs index e32c3a3..70e1341 100644 --- a/c-sharp-chat/PubnubChatApi/PubnubChatApi/Entities/Chat.cs +++ b/c-sharp-chat/PubnubChatApi/PubnubChatApi/Entities/Chat.cs @@ -357,10 +357,10 @@ public async Task> InviteToChannel(string channe new() { Channel = channelId, + Status = "pending" //TODO: these too here? //TODO: again, should ChatMembershipData from Create(...)Channel also be passed here? /*Custom = , - Status = , Type = */ } }).ExecuteAsync().ConfigureAwait(false); @@ -380,7 +380,10 @@ public async Task> InviteToChannel(string channe var inviteEventPayload = $"{{\"channelType\": \"{channel.Result.Type}\", \"channelId\": {channelId}}}"; await EmitEvent(PubnubChatEventType.Invite, userId, inviteEventPayload).ConfigureAwait(false); - var newMembership = new Membership(this, userId, channelId, new ChatMembershipData()); + var newMembership = new Membership(this, userId, channelId, new ChatMembershipData() + { + Status = "pending" + }); await newMembership.SetLastReadMessageTimeToken(ChatUtils.TimeTokenNow()).ConfigureAwait(false); result.Result = newMembership; @@ -413,7 +416,7 @@ public async Task>> InviteMultipleToChannel PNChannelMemberField.UUID_STATUS }) //TODO: again, should ChatMembershipData from Create(...)Channel also be passed here? - .Uuids(users.Select(x => new PNChannelMember() { Custom = x.CustomData, Uuid = x.Id }).ToList()) + .Uuids(users.Select(x => new PNChannelMember() { Custom = x.CustomData, Uuid = x.Id, Status = "pending"}).ToList()) .ExecuteAsync().ConfigureAwait(false); if (result.RegisterOperation(inviteResponse)) diff --git a/c-sharp-chat/PubnubChatApi/PubnubChatApi/Entities/Data/ChatMembershipData.cs b/c-sharp-chat/PubnubChatApi/PubnubChatApi/Entities/Data/ChatMembershipData.cs index 4f2b629..b655bfc 100644 --- a/c-sharp-chat/PubnubChatApi/PubnubChatApi/Entities/Data/ChatMembershipData.cs +++ b/c-sharp-chat/PubnubChatApi/PubnubChatApi/Entities/Data/ChatMembershipData.cs @@ -15,8 +15,8 @@ namespace PubnubChatApi public class ChatMembershipData { public Dictionary CustomData { get; set; } = new(); - public string Status { get; set; } - public string Type { get; set; } + public string Status { get; set; } = string.Empty; + public string Type { get; set; } = string.Empty; public static implicit operator ChatMembershipData(PNChannelMembersItemResult membersItem) { diff --git a/unity-chat/PubnubChatUnity/Assets/PubnubChat/Runtime/PubnubChatApi/Entities/Channel.cs b/unity-chat/PubnubChatUnity/Assets/PubnubChat/Runtime/PubnubChatApi/Entities/Channel.cs index 3673150..9f9b321 100644 --- a/unity-chat/PubnubChatUnity/Assets/PubnubChat/Runtime/PubnubChatApi/Entities/Channel.cs +++ b/unity-chat/PubnubChatUnity/Assets/PubnubChat/Runtime/PubnubChatApi/Entities/Channel.cs @@ -1148,7 +1148,7 @@ public async Task>> WhoIsPresent() /// The sort parameter. /// The maximum amount of the memberships received. /// The page object for pagination. - /// A ChatOperationResult containing the list of the Membership objects. + /// A ChatOperationResult containing a wrapper object with the list of the Membership objects. /// /// /// var channel = //... @@ -1166,6 +1166,34 @@ public async Task> GetMemberships(st return await chat.GetChannelMemberships(Id, filter, sort, limit, page).ConfigureAwait(false); } + /// + /// Gets the list of the Membership objects which have a Status of "pending". + /// + /// Gets the list of the Membership objects that represent the users that are invited + /// to the channel. + /// + /// + /// The sort parameter. + /// The maximum amount of the memberships received. + /// The page object for pagination. + /// A ChatOperationResult containing a wrapper object with the list of the Membership objects. + /// + /// + /// var channel = //... + /// var result = await channel.GetInvitees(limit: 10); + /// var invites = result.Result.Memberships; + /// foreach (var invited in invites) { + /// Console.WriteLine($"Invited user: {invited.UserId}"); + /// } + /// + /// + /// + public async Task> GetInvitees(string sort = "", int limit = 0, + PNPageObject page = null) + { + return await chat.GetChannelMemberships(Id, "status == \"pending\"", sort, limit, page).ConfigureAwait(false); + } + /// /// Asynchronously gets the Message object for the given timetoken sent from this Channel. /// diff --git a/unity-chat/PubnubChatUnity/Assets/PubnubChat/Runtime/PubnubChatApi/Entities/Chat.cs b/unity-chat/PubnubChatUnity/Assets/PubnubChat/Runtime/PubnubChatApi/Entities/Chat.cs index e32c3a3..70e1341 100644 --- a/unity-chat/PubnubChatUnity/Assets/PubnubChat/Runtime/PubnubChatApi/Entities/Chat.cs +++ b/unity-chat/PubnubChatUnity/Assets/PubnubChat/Runtime/PubnubChatApi/Entities/Chat.cs @@ -357,10 +357,10 @@ public async Task> InviteToChannel(string channe new() { Channel = channelId, + Status = "pending" //TODO: these too here? //TODO: again, should ChatMembershipData from Create(...)Channel also be passed here? /*Custom = , - Status = , Type = */ } }).ExecuteAsync().ConfigureAwait(false); @@ -380,7 +380,10 @@ public async Task> InviteToChannel(string channe var inviteEventPayload = $"{{\"channelType\": \"{channel.Result.Type}\", \"channelId\": {channelId}}}"; await EmitEvent(PubnubChatEventType.Invite, userId, inviteEventPayload).ConfigureAwait(false); - var newMembership = new Membership(this, userId, channelId, new ChatMembershipData()); + var newMembership = new Membership(this, userId, channelId, new ChatMembershipData() + { + Status = "pending" + }); await newMembership.SetLastReadMessageTimeToken(ChatUtils.TimeTokenNow()).ConfigureAwait(false); result.Result = newMembership; @@ -413,7 +416,7 @@ public async Task>> InviteMultipleToChannel PNChannelMemberField.UUID_STATUS }) //TODO: again, should ChatMembershipData from Create(...)Channel also be passed here? - .Uuids(users.Select(x => new PNChannelMember() { Custom = x.CustomData, Uuid = x.Id }).ToList()) + .Uuids(users.Select(x => new PNChannelMember() { Custom = x.CustomData, Uuid = x.Id, Status = "pending"}).ToList()) .ExecuteAsync().ConfigureAwait(false); if (result.RegisterOperation(inviteResponse)) diff --git a/unity-chat/PubnubChatUnity/Assets/PubnubChat/Runtime/PubnubChatApi/Entities/Data/ChatMembershipData.cs b/unity-chat/PubnubChatUnity/Assets/PubnubChat/Runtime/PubnubChatApi/Entities/Data/ChatMembershipData.cs index 4f2b629..b655bfc 100644 --- a/unity-chat/PubnubChatUnity/Assets/PubnubChat/Runtime/PubnubChatApi/Entities/Data/ChatMembershipData.cs +++ b/unity-chat/PubnubChatUnity/Assets/PubnubChat/Runtime/PubnubChatApi/Entities/Data/ChatMembershipData.cs @@ -15,8 +15,8 @@ namespace PubnubChatApi public class ChatMembershipData { public Dictionary CustomData { get; set; } = new(); - public string Status { get; set; } - public string Type { get; set; } + public string Status { get; set; } = string.Empty; + public string Type { get; set; } = string.Empty; public static implicit operator ChatMembershipData(PNChannelMembersItemResult membersItem) { From 4c59190b5d186bfdba203d10d39630a8551c1b70 Mon Sep 17 00:00:00 2001 From: "PUBNUB\\jakub.grzesiowski" Date: Mon, 24 Nov 2025 14:07:09 +0100 Subject: [PATCH 2/6] add code snippet for GetInvitees() --- .../Assets/Snippets/ChannelSample.cs | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/unity-chat/PubnubChatUnity/Assets/Snippets/ChannelSample.cs b/unity-chat/PubnubChatUnity/Assets/Snippets/ChannelSample.cs index 54d2e62..ddf833f 100644 --- a/unity-chat/PubnubChatUnity/Assets/Snippets/ChannelSample.cs +++ b/unity-chat/PubnubChatUnity/Assets/Snippets/ChannelSample.cs @@ -48,4 +48,26 @@ public static async Task EventSubscriptionExample() } // snippet.end } + + public static async Task GetInviteesExample() + { + // snippet.get_invitees_example + // Get or create a channel + var channelResult = await chat.GetChannel("my_channel"); + if (channelResult.Error) + { + Debug.LogError($"Could not fetch channel! Error: {channelResult.Exception.Message}"); + } + var channel = channelResult.Result; + var getInvitees = await channel.GetInvitees(); + if (getInvitees.Error) + { + Debug.LogError($"Could not fetch invitees! Error: {getInvitees.Exception.Message}"); + } + foreach (var membership in getInvitees.Result.Memberships) + { + Debug.Log($"User {membership.UserId} has is invited to channel {membership.ChannelId}"); + } + // snippet.end + } } From fd51a871e1675abe678699d4d66a27927c5bd011 Mon Sep 17 00:00:00 2001 From: "PUBNUB\\jakub.grzesiowski" Date: Mon, 24 Nov 2025 14:18:10 +0100 Subject: [PATCH 3/6] Add filter argument to GetInvitees --- .../PubnubChatApi/PubnubChatApi/Entities/Channel.cs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/c-sharp-chat/PubnubChatApi/PubnubChatApi/Entities/Channel.cs b/c-sharp-chat/PubnubChatApi/PubnubChatApi/Entities/Channel.cs index 9f9b321..966370e 100644 --- a/c-sharp-chat/PubnubChatApi/PubnubChatApi/Entities/Channel.cs +++ b/c-sharp-chat/PubnubChatApi/PubnubChatApi/Entities/Channel.cs @@ -1173,6 +1173,7 @@ public async Task> GetMemberships(st /// to the channel. /// /// + /// The filter parameter. Note that it will always contain "status == \"pending\"" in the end request. /// The sort parameter. /// The maximum amount of the memberships received. /// The page object for pagination. @@ -1188,10 +1189,15 @@ public async Task> GetMemberships(st /// /// /// - public async Task> GetInvitees(string sort = "", int limit = 0, + public async Task> GetInvitees(string filter = "", string sort = "", int limit = 0, PNPageObject page = null) { - return await chat.GetChannelMemberships(Id, "status == \"pending\"", sort, limit, page).ConfigureAwait(false); + var finalFilter = "status == \"pending\""; + if (!string.IsNullOrEmpty(filter)) + { + finalFilter = $"{filter} && {finalFilter}"; + } + return await chat.GetChannelMemberships(Id, finalFilter, sort, limit, page).ConfigureAwait(false); } /// From 1c4ddb4022aa4c228ea19ddb89f1fa690d683884 Mon Sep 17 00:00:00 2001 From: "PUBNUB\\jakub.grzesiowski" Date: Mon, 24 Nov 2025 14:22:32 +0100 Subject: [PATCH 4/6] Also add filter to GetInvitees on Unity side --- .../Runtime/PubnubChatApi/Entities/Channel.cs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/unity-chat/PubnubChatUnity/Assets/PubnubChat/Runtime/PubnubChatApi/Entities/Channel.cs b/unity-chat/PubnubChatUnity/Assets/PubnubChat/Runtime/PubnubChatApi/Entities/Channel.cs index 9f9b321..966370e 100644 --- a/unity-chat/PubnubChatUnity/Assets/PubnubChat/Runtime/PubnubChatApi/Entities/Channel.cs +++ b/unity-chat/PubnubChatUnity/Assets/PubnubChat/Runtime/PubnubChatApi/Entities/Channel.cs @@ -1173,6 +1173,7 @@ public async Task> GetMemberships(st /// to the channel. /// /// + /// The filter parameter. Note that it will always contain "status == \"pending\"" in the end request. /// The sort parameter. /// The maximum amount of the memberships received. /// The page object for pagination. @@ -1188,10 +1189,15 @@ public async Task> GetMemberships(st /// /// /// - public async Task> GetInvitees(string sort = "", int limit = 0, + public async Task> GetInvitees(string filter = "", string sort = "", int limit = 0, PNPageObject page = null) { - return await chat.GetChannelMemberships(Id, "status == \"pending\"", sort, limit, page).ConfigureAwait(false); + var finalFilter = "status == \"pending\""; + if (!string.IsNullOrEmpty(filter)) + { + finalFilter = $"{filter} && {finalFilter}"; + } + return await chat.GetChannelMemberships(Id, finalFilter, sort, limit, page).ConfigureAwait(false); } /// From a5f1d82ac0fc691bbd39cc65c1895c835dac231d Mon Sep 17 00:00:00 2001 From: PubNub Release Bot <120067856+pubnub-release-bot@users.noreply.github.com> Date: Mon, 24 Nov 2025 13:30:54 +0000 Subject: [PATCH 5/6] PubNub SDK v1.2.0 release. --- .pubnub.yml | 11 ++++++++++- .../Assets/PubnubChat/Runtime/UnityChatPNSDKSource.cs | 2 +- .../PubnubChatUnity/Assets/PubnubChat/package.json | 2 +- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/.pubnub.yml b/.pubnub.yml index 481abec..6412069 100644 --- a/.pubnub.yml +++ b/.pubnub.yml @@ -1,6 +1,15 @@ --- -version: v1.1.1 +version: v1.2.0 changelog: + - date: 2025-11-24 + version: v1.2.0 + changes: + - type: feature + text: "Memberships generated from Invite methods now have Status = "pending" and can be filtered out in GetMemberships()." + - type: feature + text: "Added new overloads for update related methods and events so that they now use ChatEntityChangeType to convey the type of update." + - type: improvement + text: "Changed SetListening(...) and AddListenerTo(...) methods to align with other Chat SDKs. New methods are use the "Stream" and "StreamOn" naming convetions." - date: 2025-11-06 version: v1.1.1 changes: diff --git a/unity-chat/PubnubChatUnity/Assets/PubnubChat/Runtime/UnityChatPNSDKSource.cs b/unity-chat/PubnubChatUnity/Assets/PubnubChat/Runtime/UnityChatPNSDKSource.cs index a7e311d..7bf9e40 100644 --- a/unity-chat/PubnubChatUnity/Assets/PubnubChat/Runtime/UnityChatPNSDKSource.cs +++ b/unity-chat/PubnubChatUnity/Assets/PubnubChat/Runtime/UnityChatPNSDKSource.cs @@ -5,7 +5,7 @@ namespace PubnubChatApi { public class UnityChatPNSDKSource : IPNSDKSource { - private const string build = "1.1.1"; + private const string build = "1.2.0"; private string GetPlatformString() { diff --git a/unity-chat/PubnubChatUnity/Assets/PubnubChat/package.json b/unity-chat/PubnubChatUnity/Assets/PubnubChat/package.json index 01b6f96..8ea9ca6 100644 --- a/unity-chat/PubnubChatUnity/Assets/PubnubChat/package.json +++ b/unity-chat/PubnubChatUnity/Assets/PubnubChat/package.json @@ -1,6 +1,6 @@ { "name": "com.pubnub.pubnubchat", - "version": "1.1.1", + "version": "1.2.0", "displayName": "Pubnub Chat", "description": "PubNub Unity Chat SDK", "unity": "2022.3", From 79dbf3ec7d33cf9f069ddd2e244e999562835fe8 Mon Sep 17 00:00:00 2001 From: "PUBNUB\\jakub.grzesiowski" Date: Mon, 24 Nov 2025 15:38:23 +0100 Subject: [PATCH 6/6] fix pubnub.yml --- .pubnub.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pubnub.yml b/.pubnub.yml index 6412069..be0f2bc 100644 --- a/.pubnub.yml +++ b/.pubnub.yml @@ -5,11 +5,11 @@ changelog: version: v1.2.0 changes: - type: feature - text: "Memberships generated from Invite methods now have Status = "pending" and can be filtered out in GetMemberships()." + text: "Memberships generated from Invite methods now have Status pending and can be filtered out in GetMemberships()." - type: feature text: "Added new overloads for update related methods and events so that they now use ChatEntityChangeType to convey the type of update." - type: improvement - text: "Changed SetListening(...) and AddListenerTo(...) methods to align with other Chat SDKs. New methods are use the "Stream" and "StreamOn" naming convetions." + text: "Changed SetListening(...) and AddListenerTo(...) methods to align with other Chat SDKs. New methods are use the Stream and StreamOn naming convetions." - date: 2025-11-06 version: v1.1.1 changes: