diff --git a/Assets/Plugins/StreamChat/Core/IStreamChatClient.cs b/Assets/Plugins/StreamChat/Core/IStreamChatClient.cs
index d0fc323b..a578e238 100644
--- a/Assets/Plugins/StreamChat/Core/IStreamChatClient.cs
+++ b/Assets/Plugins/StreamChat/Core/IStreamChatClient.cs
@@ -57,6 +57,18 @@ public interface IStreamChatClient : IDisposable, IStreamChatClientEventsListene
/// Invite to a was rejected
///
event ChannelInviteHandler ChannelInviteRejected;
+
+ ///
+ /// Local user was added to a channel as a member. This event fires only for channels that are not tracked locally.
+ /// Use this event to get notified when the local user is added to a new channel. For tracked channels, use the event.
+ ///
+ event ChannelMemberAddedHandler AddedToChannelAsMember;
+
+ ///
+ /// Local user was removed from a channel as a member. This event fires only for channels that are not tracked locally.
+ /// Use this event to get notified when the local user was removed from a channel. For tracked channels use event
+ ///
+ event ChannelMemberRemovedHandler RemovedFromChannelAsMember;
///
/// Current connection state
@@ -64,7 +76,7 @@ public interface IStreamChatClient : IDisposable, IStreamChatClientEventsListene
ConnectionState ConnectionState { get; }
///
- /// Is client connected. Subscribe to to get notified when connection is established
+ /// Is client connected. Subscribe to event to get notified when connection is established
///
bool IsConnected { get; }
diff --git a/Assets/Plugins/StreamChat/Core/StatefulModels/IStreamChannel.cs b/Assets/Plugins/StreamChat/Core/StatefulModels/IStreamChannel.cs
index a3c1c7a2..eb155f0c 100644
--- a/Assets/Plugins/StreamChat/Core/StatefulModels/IStreamChannel.cs
+++ b/Assets/Plugins/StreamChat/Core/StatefulModels/IStreamChannel.cs
@@ -62,7 +62,7 @@ public interface IStreamChannel : IStreamStatefulModel
///
/// Event fired when a was added, updated, or removed.
///
- event StreamChannelMemberAnyChangeHandler MembersChanged;
+ event StreamChannelMemberAnyChangeHandler MembersChanged; //StreamTodo: typo, this should be MemberChanged
///
/// Event fired when visibility of this channel changed. Check to know if channel is hidden
diff --git a/Assets/Plugins/StreamChat/Core/StreamChatClient.cs b/Assets/Plugins/StreamChat/Core/StreamChatClient.cs
index ba8c90fd..60bf0ea6 100644
--- a/Assets/Plugins/StreamChat/Core/StreamChatClient.cs
+++ b/Assets/Plugins/StreamChat/Core/StreamChatClient.cs
@@ -49,7 +49,17 @@ namespace StreamChat.Core
//StreamTodo: Handle restoring state after lost connection
public delegate void ChannelInviteHandler(IStreamChannel channel, IStreamUser invitee);
-
+
+ ///
+ /// Member added to the channel handler
+ ///
+ public delegate void ChannelMemberAddedHandler(IStreamChannel channel, IStreamChannelMember member);
+
+ ///
+ /// Member removed from the channel handler
+ ///
+ public delegate void ChannelMemberRemovedHandler(IStreamChannel channel, IStreamChannelMember member);
+
public sealed class StreamChatClient : IStreamChatClient
{
public event ConnectionMadeHandler Connected;
@@ -65,6 +75,9 @@ public sealed class StreamChatClient : IStreamChatClient
public event ChannelInviteHandler ChannelInviteReceived;
public event ChannelInviteHandler ChannelInviteAccepted;
public event ChannelInviteHandler ChannelInviteRejected;
+
+ public event ChannelMemberAddedHandler AddedToChannelAsMember;
+ public event ChannelMemberRemovedHandler RemovedFromChannelAsMember;
public const int QueryUsersLimitMaxValue = 30;
public const int QueryUsersOffsetMaxValue = 1000;
@@ -197,33 +210,9 @@ public Task DisconnectUserAsync()
public bool IsLocalUser(IStreamUser user) => LocalUserData.User == user;
- public async Task GetOrCreateChannelWithIdAsync(ChannelType channelType, string channelId,
+ public Task GetOrCreateChannelWithIdAsync(ChannelType channelType, string channelId,
string name = null, IDictionary optionalCustomData = null)
- {
- StreamAsserts.AssertChannelTypeIsValid(channelType);
- StreamAsserts.AssertChannelIdLength(channelId);
-
- var requestBodyDto = new ChannelGetOrCreateRequestInternalDTO
- {
- Presence = true,
- State = true,
- Watch = true,
- Data = new ChannelRequestInternalDTO
- {
- Name = name,
- },
- };
-
- if (optionalCustomData != null && optionalCustomData.Any())
- {
- requestBodyDto.Data.AdditionalProperties = optionalCustomData?.ToDictionary(x => x.Key, x => x.Value);
- }
-
- var channelResponseDto = await InternalLowLevelClient.InternalChannelApi.GetOrCreateChannelAsync(
- channelType,
- channelId, requestBodyDto);
- return _cache.TryCreateOrUpdate(channelResponseDto);
- }
+ => InternalGetOrCreateChannelWithIdAsync(channelType, channelId, name, presence: true, state: true, watch: true, optionalCustomData);
public async Task GetOrCreateChannelWithMembersAsync(ChannelType channelType,
IEnumerable members, IDictionary optionalCustomData = null)
@@ -579,6 +568,36 @@ void IStreamChatClientEventsListener.Destroy()
void IStreamChatClientEventsListener.Update() => InternalLowLevelClient.Update(_timeService.DeltaTime);
internal StreamChatLowLevelClient InternalLowLevelClient { get; }
+
+ // We probably don't want to expose the presence, state, watch params to the public API
+ internal async Task InternalGetOrCreateChannelWithIdAsync(ChannelType channelType, string channelId,
+ string name = null, bool presence = true, bool state = true, bool watch = true,
+ IDictionary optionalCustomData = null)
+ {
+ StreamAsserts.AssertChannelTypeIsValid(channelType);
+ StreamAsserts.AssertChannelIdLength(channelId);
+
+ var requestBodyDto = new ChannelGetOrCreateRequestInternalDTO
+ {
+ Presence = presence,
+ State = state,
+ Watch = watch,
+ Data = new ChannelRequestInternalDTO
+ {
+ Name = name,
+ },
+ };
+
+ if (optionalCustomData != null && optionalCustomData.Any())
+ {
+ requestBodyDto.Data.AdditionalProperties = optionalCustomData?.ToDictionary(x => x.Key, x => x.Value);
+ }
+
+ var channelResponseDto = await InternalLowLevelClient.InternalChannelApi.GetOrCreateChannelAsync(
+ channelType,
+ channelId, requestBodyDto);
+ return _cache.TryCreateOrUpdate(channelResponseDto);
+ }
internal IStreamLocalUserData UpdateLocalUser(OwnUserInternalDTO ownUserInternalDto)
{
@@ -843,15 +862,23 @@ private void OnMarkReadNotification(NotificationMarkReadEventInternalDTO eventDt
_localUserData.InternalHandleMarkReadNotification(eventDto);
}
- private void OnAddedToChannelNotification(NotificationAddedToChannelEventInternalDTO obj)
+ private void OnAddedToChannelNotification(NotificationAddedToChannelEventInternalDTO eventDto)
{
- //StreamTodo: IMPLEMENT
+ var channel = _cache.TryCreateOrUpdate(eventDto.Channel);
+ var member = _cache.TryCreateOrUpdate(eventDto.Member);
+ _cache.TryCreateOrUpdate(eventDto.Member.User);
+
+ AddedToChannelAsMember?.Invoke(channel, member);
}
private void OnRemovedFromChannelNotification(
- NotificationRemovedFromChannelEventInternalDTO obj)
+ NotificationRemovedFromChannelEventInternalDTO eventDto)
{
-//StreamTodo: IMPLEMENT
+ var channel = _cache.TryCreateOrUpdate(eventDto.Channel);
+ var member = _cache.TryCreateOrUpdate(eventDto.Member);
+ _cache.TryCreateOrUpdate(eventDto.Member.User);
+
+ RemovedFromChannelAsMember?.Invoke(channel, member);
}
private void OnInvitedNotification(NotificationInvitedEventInternalDTO eventDto)
diff --git a/Assets/Plugins/StreamChat/Samples/EventsSamples.cs b/Assets/Plugins/StreamChat/Samples/EventsSamples.cs
new file mode 100644
index 00000000..7a76c645
--- /dev/null
+++ b/Assets/Plugins/StreamChat/Samples/EventsSamples.cs
@@ -0,0 +1,161 @@
+using System.Threading.Tasks;
+using StreamChat.Core;
+using StreamChat.Core.Models;
+using StreamChat.Core.StatefulModels;
+
+namespace StreamChat.Samples
+{
+ internal class EventsSamples
+ {
+ public async Task QueryChannelsEvents()
+ {
+ // Get a single channel
+ var channel = await Client.GetOrCreateChannelWithIdAsync(ChannelType.Messaging, "my-channel-id");
+
+ channel.MessageReceived += OnMessageReceived;
+ channel.MessageUpdated += OnMessageUpdated;
+ channel.MessageDeleted += OnMessageDeleted;
+
+ channel.ReactionAdded += OnReactionAdded;
+ channel.ReactionUpdated += OnReactionUpdated;
+ channel.ReactionRemoved += OnReactionRemoved;
+
+ channel.MemberAdded += OnMemberAdded;
+ channel.MemberRemoved += OnMemberRemoved;
+ channel.MemberUpdated += OnMemberUpdated;
+
+ channel.MembersChanged += OnMembersChanged;
+ channel.VisibilityChanged += OnVisibilityChanged;
+ channel.MuteChanged += OnMuteChanged;
+ channel.Truncated += OnTruncated;
+ channel.Updated += OnUpdated;
+ channel.WatcherAdded += OnWatcherAdded;
+ channel.WatcherRemoved += OnWatcherRemoved;
+ channel.UserStartedTyping += OnUserStartedTyping;
+ channel.UserStoppedTyping += OnUserStoppedTyping;
+ channel.TypingUsersChanged += OnTypingUsersChanged;
+ }
+
+ private void OnMessageReceived(IStreamChannel channel, IStreamMessage message)
+ {
+ }
+
+ private void OnMessageUpdated(IStreamChannel channel, IStreamMessage message)
+ {
+ }
+
+ private void OnMessageDeleted(IStreamChannel channel, IStreamMessage message, bool isharddelete)
+ {
+ }
+
+ private void OnReactionAdded(IStreamChannel channel, IStreamMessage message, StreamReaction reaction)
+ {
+ }
+
+ private void OnReactionUpdated(IStreamChannel channel, IStreamMessage message, StreamReaction reaction)
+ {
+ }
+
+ private void OnReactionRemoved(IStreamChannel channel, IStreamMessage message, StreamReaction reaction)
+ {
+ }
+
+ private void OnMemberAdded(IStreamChannel channel, IStreamChannelMember member)
+ {
+ }
+
+ private void OnMemberRemoved(IStreamChannel channel, IStreamChannelMember member)
+ {
+ }
+
+ private void OnMemberUpdated(IStreamChannel channel, IStreamChannelMember member)
+ {
+ }
+
+ private void OnMembersChanged(IStreamChannel channel, IStreamChannelMember member, OperationType operationType)
+ {
+ }
+
+ private void OnVisibilityChanged(IStreamChannel channel, bool isVisible)
+ {
+ }
+
+ private void OnMuteChanged(IStreamChannel channel, bool isMuted)
+ {
+ }
+
+ private void OnTruncated(IStreamChannel channel)
+ {
+ }
+
+ private void OnUpdated(IStreamChannel channel)
+ {
+ }
+
+ private void OnWatcherAdded(IStreamChannel channel, IStreamUser user)
+ {
+ }
+
+ private void OnWatcherRemoved(IStreamChannel channel, IStreamUser user)
+ {
+ }
+
+ private void OnUserStartedTyping(IStreamChannel channel, IStreamUser user)
+ {
+ }
+
+ private void OnUserStoppedTyping(IStreamChannel channel, IStreamUser user)
+ {
+ }
+
+ private void OnTypingUsersChanged(IStreamChannel channel)
+ {
+ }
+
+ public void ClientEvents()
+ {
+ Client.AddedToChannelAsMember += OnAddedToChannelAsMember;
+ Client.RemovedFromChannelAsMember += OnRemovedFromChannel;
+ }
+
+ private void OnAddedToChannelAsMember(IStreamChannel channel, IStreamChannelMember member)
+ {
+ // channel - new channel to which local user was just added
+ // member - object containing channel membership information
+ }
+
+ private void OnRemovedFromChannel(IStreamChannel channel, IStreamChannelMember member)
+ {
+ // channel - channel from which local user was removed
+ // member - object containing channel membership information
+ }
+
+ public void ConnectionEvents()
+ {
+ Client.Connected += OnConnected;
+ Client.Disconnected += OnDisconnected;
+ Client.ConnectionStateChanged += OnConnectionStateChanged;
+ }
+
+ private void OnConnectionStateChanged(ConnectionState previous, ConnectionState current)
+ {
+ }
+
+ private void OnDisconnected()
+ {
+ }
+
+ private void OnConnected(IStreamLocalUserData localUserData)
+ {
+ }
+
+ public void Unsubscribe()
+ {
+ Client.Connected -= OnConnected;
+ Client.Disconnected -= OnDisconnected;
+ Client.ConnectionStateChanged -= OnConnectionStateChanged;
+ }
+
+ private IStreamChatClient Client { get; } = StreamChatClient.CreateDefaultClient();
+ }
+}
\ No newline at end of file
diff --git a/Assets/Plugins/StreamChat/Samples/EventsSamples.cs.meta b/Assets/Plugins/StreamChat/Samples/EventsSamples.cs.meta
new file mode 100644
index 00000000..8bdab5b4
--- /dev/null
+++ b/Assets/Plugins/StreamChat/Samples/EventsSamples.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 26a2e9b9d0584abb85bb616f1be15616
+timeCreated: 1718025761
\ No newline at end of file
diff --git a/Assets/Plugins/StreamChat/Tests/StatefulClient/BaseStateIntegrationTests.cs b/Assets/Plugins/StreamChat/Tests/StatefulClient/BaseStateIntegrationTests.cs
index 9291c22b..4dd45c40 100644
--- a/Assets/Plugins/StreamChat/Tests/StatefulClient/BaseStateIntegrationTests.cs
+++ b/Assets/Plugins/StreamChat/Tests/StatefulClient/BaseStateIntegrationTests.cs
@@ -8,6 +8,7 @@
using NUnit.Framework;
using StreamChat.Core;
using StreamChat.Core.Exceptions;
+using StreamChat.Core.InternalDTO.Requests;
using StreamChat.Core.Requests;
using StreamChat.Core.StatefulModels;
using StreamChat.Libs.Auth;
@@ -48,11 +49,11 @@ protected static IEnumerable OtherAdminUsersCredentials
///
/// Create temp channel with random id that will be removed in [TearDown]
///
- protected async Task CreateUniqueTempChannelAsync(string name = null)
+ protected async Task CreateUniqueTempChannelAsync(string name = null, bool watch = true)
{
var channelId = "random-channel-11111-" + Guid.NewGuid();
- var channelState = await Client.GetOrCreateChannelWithIdAsync(ChannelType.Messaging, channelId, name);
+ var channelState = await Client.InternalGetOrCreateChannelWithIdAsync(ChannelType.Messaging, channelId, name, watch: watch);
_tempChannels.Add(channelState);
return channelState;
}
@@ -210,7 +211,7 @@ private static async Task ConnectAndExecuteAsync(Func test)
throw new AggregateException($"Failed all attempts. Last Exception: {exceptions.Last().Message} ", exceptions);
}
}
-
+
private async Task DeleteTempChannelsAsync()
{
if (_tempChannels.Count == 0)
@@ -218,20 +219,24 @@ private async Task DeleteTempChannelsAsync()
return;
}
- try
- {
- await Client.DeleteMultipleChannelsAsync(_tempChannels, isHardDelete: true);
- }
- catch (StreamApiException streamApiException)
+ for (int i = 0; i < 5; i++)
{
- if (streamApiException.Code == StreamApiException.RateLimitErrorHttpStatusCode)
+ try
{
- await Task.Delay(500);
+ await Client.DeleteMultipleChannelsAsync(_tempChannels, isHardDelete: true);
}
+ catch (StreamApiException streamApiException)
+ {
+ if (streamApiException.Code == StreamApiException.RateLimitErrorHttpStatusCode)
+ {
+ await Task.Delay(1000);
+ }
- Debug.Log($"Try {nameof(DeleteTempChannelsAsync)} again due to exception: " + streamApiException);
+ Debug.Log($"Attempt {i} failed. Try {nameof(DeleteTempChannelsAsync)} again due to exception: " + streamApiException);
+ continue;
+ }
- await Client.DeleteMultipleChannelsAsync(_tempChannels, isHardDelete: true);
+ break;
}
_tempChannels.Clear();
diff --git a/Assets/Plugins/StreamChat/Tests/StatefulClient/ChannelMembersTests.cs b/Assets/Plugins/StreamChat/Tests/StatefulClient/ChannelMembersTests.cs
index 3def887f..c29c145d 100644
--- a/Assets/Plugins/StreamChat/Tests/StatefulClient/ChannelMembersTests.cs
+++ b/Assets/Plugins/StreamChat/Tests/StatefulClient/ChannelMembersTests.cs
@@ -65,12 +65,15 @@ private async Task When_add_user_by_id_to_channel_expect_user_included_in_member
await WaitWhileTrueAsync(() => channel.Members.All(m => m.User != otherUser));
Assert.NotNull(channel.Members.FirstOrDefault(member => member.User == otherUser));
}
-
+
[UnityTest]
- public IEnumerator When_add_user_to_channel_with_hide_history_and_message_expect_user_as_members_and_message_sent()
- => ConnectAndExecute(When_add_user_to_channel_with_hide_history_and_message_expect_user_as_members_and_message_sent_Async);
+ public IEnumerator
+ When_add_user_to_channel_with_hide_history_and_message_expect_user_as_members_and_message_sent()
+ => ConnectAndExecute(
+ When_add_user_to_channel_with_hide_history_and_message_expect_user_as_members_and_message_sent_Async);
- private async Task When_add_user_to_channel_with_hide_history_and_message_expect_user_as_members_and_message_sent_Async()
+ private async Task
+ When_add_user_to_channel_with_hide_history_and_message_expect_user_as_members_and_message_sent_Async()
{
var channel = await CreateUniqueTempChannelAsync();
var otherUserId = OtherAdminUsersCredentials.First().UserId;
@@ -266,22 +269,22 @@ private async Task When_remove_members_expect_member_added_event_fired_Async()
var receivedEvent = false;
IStreamChannelMember eventMember = null;
IStreamChannel eventChannel = null;
- channel.MemberRemoved += (chanel, member) =>
+ channel.MemberRemoved += (channel2, member) =>
{
receivedEvent = true;
eventMember = member;
- eventChannel = chanel;
+ eventChannel = channel2;
};
var receivedEvent2 = false;
IStreamChannelMember eventMember2 = null;
IStreamChannel eventChannel2 = null;
OperationType? opType = default;
- channel.MembersChanged += (chanel, member, op) =>
+ channel.MembersChanged += (channel3, member, op) =>
{
receivedEvent2 = true;
eventMember2 = member;
- eventChannel2 = chanel;
+ eventChannel2 = channel3;
opType = op;
};
@@ -303,6 +306,71 @@ private async Task When_remove_members_expect_member_added_event_fired_Async()
Assert.AreEqual(user, eventMember2.User);
Assert.AreEqual(OperationType.Removed, opType.Value);
}
+
+ [UnityTest]
+ public IEnumerator When_user_added_to_not_watched_channel_expect_user_receive_added_to_channel_event()
+ => ConnectAndExecute(
+ When_user_added_to_not_watched_channel_expect_user_receive_added_to_channel_event_Async);
+
+ private async Task When_user_added_to_not_watched_channel_expect_user_receive_added_to_channel_event_Async()
+ {
+ var channel = await CreateUniqueTempChannelAsync(watch: false);
+
+ var receivedEvent = false;
+ IStreamChannelMember eventMember = null;
+ IStreamChannel eventChannel = null;
+ Client.AddedToChannelAsMember += (channel2, member) =>
+ {
+ receivedEvent = true;
+ eventMember = member;
+ eventChannel = channel2;
+ };
+
+ await channel.AddMembersAsync(hideHistory: default, optionalMessage: default, Client.LocalUserData.User);
+ await WaitWhileFalseAsync(() => receivedEvent);
+
+ Assert.IsTrue(receivedEvent);
+ Assert.IsNotNull(eventChannel);
+ Assert.IsNotNull(eventMember);
+ Assert.AreEqual(channel, eventChannel);
+ Assert.AreEqual(Client.LocalUserData.User, eventMember.User);
+ }
+
+ [UnityTest]
+ public IEnumerator When_user_removed_from_not_watched_channel_expect_user_removed_from_channel_event()
+ => ConnectAndExecute(
+ When_user_removed_from_not_watched_channel_expect_user_removed_from_channel_event_Async);
+
+ private async Task When_user_removed_from_not_watched_channel_expect_user_removed_from_channel_event_Async()
+ {
+ var channel = await CreateUniqueTempChannelAsync(watch: false);
+
+ var receivedAddedEvent = false;
+ var receivedRemovedEvent = false;
+ IStreamChannelMember eventMember = null;
+ IStreamChannel eventChannel = null;
+
+ Client.AddedToChannelAsMember += (channel2, member) => { receivedAddedEvent = true; };
+
+ await channel.AddMembersAsync(hideHistory: default, optionalMessage: default, Client.LocalUserData.User);
+ await WaitWhileFalseAsync(() => receivedAddedEvent);
+
+ Client.RemovedFromChannelAsMember += (channel3, member2) =>
+ {
+ receivedRemovedEvent = true;
+ eventMember = member2;
+ eventChannel = channel3;
+ };
+
+ await channel.RemoveMembersAsync(new IStreamUser[] { Client.LocalUserData.User });
+ await WaitWhileFalseAsync(() => receivedRemovedEvent);
+
+ Assert.IsTrue(receivedRemovedEvent);
+ Assert.IsNotNull(eventChannel);
+ Assert.IsNotNull(eventMember);
+ Assert.AreEqual(channel, eventChannel);
+ Assert.AreEqual(Client.LocalUserData.User, eventMember.User);
+ }
}
}
#endif
\ No newline at end of file