diff --git a/.pubnub.yml b/.pubnub.yml index 481abec..be0f2bc 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/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..966370e 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,40 @@ 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 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. + /// 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 filter = "", string sort = "", int limit = 0, + PNPageObject page = null) + { + var finalFilter = "status == \"pending\""; + if (!string.IsNullOrEmpty(filter)) + { + finalFilter = $"{filter} && {finalFilter}"; + } + return await chat.GetChannelMemberships(Id, finalFilter, 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..966370e 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,40 @@ 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 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. + /// 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 filter = "", string sort = "", int limit = 0, + PNPageObject page = null) + { + var finalFilter = "status == \"pending\""; + if (!string.IsNullOrEmpty(filter)) + { + finalFilter = $"{filter} && {finalFilter}"; + } + return await chat.GetChannelMemberships(Id, finalFilter, 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) { 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", 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 + } }