diff --git a/Assets/Plugins/StreamChat/Core/InternalDTO/Responses/SyncResponseInternalDTO.cs b/Assets/Plugins/StreamChat/Core/InternalDTO/Responses/SyncResponseInternalDTO.cs index 09d089f7..3775117a 100644 --- a/Assets/Plugins/StreamChat/Core/InternalDTO/Responses/SyncResponseInternalDTO.cs +++ b/Assets/Plugins/StreamChat/Core/InternalDTO/Responses/SyncResponseInternalDTO.cs @@ -23,7 +23,7 @@ internal partial class SyncResponseInternalDTO /// List of events /// [Newtonsoft.Json.JsonProperty("events", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] - public System.Collections.Generic.List Events { get; set; } = new System.Collections.Generic.List(); + public System.Collections.Generic.List Events { get; set; } = new System.Collections.Generic.List(); /// /// List of CIDs that user can't access diff --git a/Assets/Plugins/StreamChat/Core/LowLevelClient/API/ChannelApi.cs b/Assets/Plugins/StreamChat/Core/LowLevelClient/API/ChannelApi.cs index 00bc888b..37210cf5 100644 --- a/Assets/Plugins/StreamChat/Core/LowLevelClient/API/ChannelApi.cs +++ b/Assets/Plugins/StreamChat/Core/LowLevelClient/API/ChannelApi.cs @@ -1,6 +1,7 @@ using System; using System.Threading.Tasks; using StreamChat.Core.Helpers; +using StreamChat.Core.InternalDTO.Requests; using StreamChat.Core.InternalDTO.Responses; using StreamChat.Core.LowLevelClient.API.Internal; using StreamChat.Core.LowLevelClient.Models; @@ -141,6 +142,12 @@ public Task SendTypingStartEventAsync(string channelType, string channelId) public Task SendTypingStopEventAsync(string channelType, string channelId) => _internalChannelApi.SendTypingStopEventAsync(channelType, channelId); + public async Task SyncAsync(SyncRequest syncRequest) + { + var dto = await _internalChannelApi.SyncAsync(syncRequest.TrySaveToDto()); + return dto.ToDomain(); + } + private readonly IInternalChannelApi _internalChannelApi; } } \ No newline at end of file diff --git a/Assets/Plugins/StreamChat/Core/LowLevelClient/API/IChannelApi.cs b/Assets/Plugins/StreamChat/Core/LowLevelClient/API/IChannelApi.cs index 617ee621..e082b847 100644 --- a/Assets/Plugins/StreamChat/Core/LowLevelClient/API/IChannelApi.cs +++ b/Assets/Plugins/StreamChat/Core/LowLevelClient/API/IChannelApi.cs @@ -150,5 +150,8 @@ Task MarkReadAsync(string channelType, string channelId, Task SendTypingStartEventAsync(string channelType, string channelId); Task SendTypingStopEventAsync(string channelType, string channelId); + + //StreamTodo: perhaps we can skip this declaration and use the Internal one directly + Task SyncAsync(SyncRequest syncRequest); } } \ No newline at end of file diff --git a/Assets/Plugins/StreamChat/Core/LowLevelClient/API/Internal/IInternalChannelApi.cs b/Assets/Plugins/StreamChat/Core/LowLevelClient/API/Internal/IInternalChannelApi.cs index 9f46f6c4..0fd0cb78 100644 --- a/Assets/Plugins/StreamChat/Core/LowLevelClient/API/Internal/IInternalChannelApi.cs +++ b/Assets/Plugins/StreamChat/Core/LowLevelClient/API/Internal/IInternalChannelApi.cs @@ -52,5 +52,7 @@ Task MarkReadAsync(string channelType, string chann Task SendTypingStartEventAsync(string channelType, string channelId); Task SendTypingStopEventAsync(string channelType, string channelId); + + Task SyncAsync(SyncRequestInternalDTO syncRequest); } } \ No newline at end of file diff --git a/Assets/Plugins/StreamChat/Core/LowLevelClient/API/Internal/InternalChannelApi.cs b/Assets/Plugins/StreamChat/Core/LowLevelClient/API/Internal/InternalChannelApi.cs index 9c21dc62..5107082e 100644 --- a/Assets/Plugins/StreamChat/Core/LowLevelClient/API/Internal/InternalChannelApi.cs +++ b/Assets/Plugins/StreamChat/Core/LowLevelClient/API/Internal/InternalChannelApi.cs @@ -133,5 +133,8 @@ public Task SendTypingStopEventAsync(string channelType, string channelId) { Type = WSEventType.TypingStop }); + + public Task SyncAsync(SyncRequestInternalDTO syncRequest) + => Post($"/sync", syncRequest); } } \ No newline at end of file diff --git a/Assets/Plugins/StreamChat/Core/LowLevelClient/API/Internal/InternalMessageApi.cs b/Assets/Plugins/StreamChat/Core/LowLevelClient/API/Internal/InternalMessageApi.cs index e4515d55..2b49d812 100644 --- a/Assets/Plugins/StreamChat/Core/LowLevelClient/API/Internal/InternalMessageApi.cs +++ b/Assets/Plugins/StreamChat/Core/LowLevelClient/API/Internal/InternalMessageApi.cs @@ -1,5 +1,4 @@ -using System.Net.Http; -using System.Threading.Tasks; +using System.Threading.Tasks; using StreamChat.Core.InternalDTO.Requests; using StreamChat.Core.InternalDTO.Responses; using StreamChat.Core.Web; diff --git a/Assets/Plugins/StreamChat/Core/LowLevelClient/Events/EventBase.cs b/Assets/Plugins/StreamChat/Core/LowLevelClient/Events/EventBase.cs index f49fe50e..37fa506b 100644 --- a/Assets/Plugins/StreamChat/Core/LowLevelClient/Events/EventBase.cs +++ b/Assets/Plugins/StreamChat/Core/LowLevelClient/Events/EventBase.cs @@ -2,6 +2,8 @@ namespace StreamChat.Core.LowLevelClient.Events { public abstract class EventBase { + public System.DateTimeOffset CreatedAt { get; set; } + private System.Collections.Generic.Dictionary _additionalProperties = new System.Collections.Generic.Dictionary(); [Newtonsoft.Json.JsonExtensionData] diff --git a/Assets/Plugins/StreamChat/Core/LowLevelClient/Events/EventChannelDeleted.cs b/Assets/Plugins/StreamChat/Core/LowLevelClient/Events/EventChannelDeleted.cs index e200c7ad..1178ae2c 100644 --- a/Assets/Plugins/StreamChat/Core/LowLevelClient/Events/EventChannelDeleted.cs +++ b/Assets/Plugins/StreamChat/Core/LowLevelClient/Events/EventChannelDeleted.cs @@ -15,8 +15,6 @@ public sealed class EventChannelDeleted : EventBase, public string Cid { get; set; } - public System.DateTimeOffset? CreatedAt { get; set; } - public string Team { get; set; } public string Type { get; set; } diff --git a/Assets/Plugins/StreamChat/Core/LowLevelClient/Events/EventChannelHidden.cs b/Assets/Plugins/StreamChat/Core/LowLevelClient/Events/EventChannelHidden.cs index 9ef61d80..9cf7fb37 100644 --- a/Assets/Plugins/StreamChat/Core/LowLevelClient/Events/EventChannelHidden.cs +++ b/Assets/Plugins/StreamChat/Core/LowLevelClient/Events/EventChannelHidden.cs @@ -18,8 +18,6 @@ public sealed class EventChannelHidden : EventBase, public bool? ClearHistory { get; set; } - public System.DateTimeOffset? CreatedAt { get; set; } - public string Type { get; set; } public User User { get; set; } diff --git a/Assets/Plugins/StreamChat/Core/LowLevelClient/Events/EventChannelTruncated.cs b/Assets/Plugins/StreamChat/Core/LowLevelClient/Events/EventChannelTruncated.cs index 89910e52..fd5a8888 100644 --- a/Assets/Plugins/StreamChat/Core/LowLevelClient/Events/EventChannelTruncated.cs +++ b/Assets/Plugins/StreamChat/Core/LowLevelClient/Events/EventChannelTruncated.cs @@ -17,8 +17,6 @@ public sealed class EventChannelTruncated : EventBase, public string Cid { get; set; } - public System.DateTimeOffset? CreatedAt { get; set; } - public string Type { get; set; } EventChannelTruncated ILoadableFrom.LoadFromDto( diff --git a/Assets/Plugins/StreamChat/Core/LowLevelClient/Events/EventChannelUpdated.cs b/Assets/Plugins/StreamChat/Core/LowLevelClient/Events/EventChannelUpdated.cs index be4e50e7..ecae02ab 100644 --- a/Assets/Plugins/StreamChat/Core/LowLevelClient/Events/EventChannelUpdated.cs +++ b/Assets/Plugins/StreamChat/Core/LowLevelClient/Events/EventChannelUpdated.cs @@ -16,8 +16,6 @@ public sealed class EventChannelUpdated : EventBase, public string Cid { get; set; } - public System.DateTimeOffset? CreatedAt { get; set; } - public Message Message { get; set; } public string Team { get; set; } diff --git a/Assets/Plugins/StreamChat/Core/LowLevelClient/Events/EventChannelVisible.cs b/Assets/Plugins/StreamChat/Core/LowLevelClient/Events/EventChannelVisible.cs index c8548b30..79730db5 100644 --- a/Assets/Plugins/StreamChat/Core/LowLevelClient/Events/EventChannelVisible.cs +++ b/Assets/Plugins/StreamChat/Core/LowLevelClient/Events/EventChannelVisible.cs @@ -14,8 +14,6 @@ public sealed class EventChannelVisible : EventBase, public string Cid { get; set; } - public System.DateTimeOffset? CreatedAt { get; set; } - public string Type { get; set; } public User User { get; set; } diff --git a/Assets/Plugins/StreamChat/Core/LowLevelClient/Events/EventHealthCheck.cs b/Assets/Plugins/StreamChat/Core/LowLevelClient/Events/EventHealthCheck.cs index 09b1632d..fb320a8c 100644 --- a/Assets/Plugins/StreamChat/Core/LowLevelClient/Events/EventHealthCheck.cs +++ b/Assets/Plugins/StreamChat/Core/LowLevelClient/Events/EventHealthCheck.cs @@ -9,8 +9,6 @@ public partial class EventHealthCheck : EventBase, ILoadableFrom { - public System.DateTimeOffset? CreatedAt { get; set; } - public OwnUser Me { get; set; } public string Type { get; set; } diff --git a/Assets/Plugins/StreamChat/Core/LowLevelClient/Events/EventNotificationChannelTruncated.cs b/Assets/Plugins/StreamChat/Core/LowLevelClient/Events/EventNotificationChannelTruncated.cs index a66f3fa6..75869afb 100644 --- a/Assets/Plugins/StreamChat/Core/LowLevelClient/Events/EventNotificationChannelTruncated.cs +++ b/Assets/Plugins/StreamChat/Core/LowLevelClient/Events/EventNotificationChannelTruncated.cs @@ -15,8 +15,6 @@ public sealed class EventNotificationChannelTruncated : EventBase, public string Cid { get; set; } - public System.DateTimeOffset? CreatedAt { get; set; } - public string Type { get; set; } EventNotificationChannelTruncated diff --git a/Assets/Plugins/StreamChat/Core/LowLevelClient/Events/EventNotificationInviteAccepted.cs b/Assets/Plugins/StreamChat/Core/LowLevelClient/Events/EventNotificationInviteAccepted.cs index e8f73b19..ddc78120 100644 --- a/Assets/Plugins/StreamChat/Core/LowLevelClient/Events/EventNotificationInviteAccepted.cs +++ b/Assets/Plugins/StreamChat/Core/LowLevelClient/Events/EventNotificationInviteAccepted.cs @@ -16,8 +16,6 @@ public sealed class EventNotificationInviteAccepted : EventBase, public string Cid { get; set; } - public System.DateTimeOffset? CreatedAt { get; set; } - public ChannelMember Member { get; set; } public string Type { get; set; } diff --git a/Assets/Plugins/StreamChat/Core/LowLevelClient/Events/EventNotificationInviteRejected.cs b/Assets/Plugins/StreamChat/Core/LowLevelClient/Events/EventNotificationInviteRejected.cs index 79b8d031..4d2f3af5 100644 --- a/Assets/Plugins/StreamChat/Core/LowLevelClient/Events/EventNotificationInviteRejected.cs +++ b/Assets/Plugins/StreamChat/Core/LowLevelClient/Events/EventNotificationInviteRejected.cs @@ -16,8 +16,6 @@ public sealed class EventNotificationInviteRejected : EventBase, public string Cid { get; set; } - public System.DateTimeOffset? CreatedAt { get; set; } - public ChannelMember Member { get; set; } public string Type { get; set; } diff --git a/Assets/Plugins/StreamChat/Core/LowLevelClient/Events/EventNotificationInvited.cs b/Assets/Plugins/StreamChat/Core/LowLevelClient/Events/EventNotificationInvited.cs index 441444cd..591fe3ce 100644 --- a/Assets/Plugins/StreamChat/Core/LowLevelClient/Events/EventNotificationInvited.cs +++ b/Assets/Plugins/StreamChat/Core/LowLevelClient/Events/EventNotificationInvited.cs @@ -16,8 +16,6 @@ public sealed class EventNotificationInvited : EventBase, public string Cid { get; set; } - public System.DateTimeOffset? CreatedAt { get; set; } - public ChannelMember Member { get; set; } public string Type { get; set; } diff --git a/Assets/Plugins/StreamChat/Core/LowLevelClient/Events/EventNotificationMarkRead.cs b/Assets/Plugins/StreamChat/Core/LowLevelClient/Events/EventNotificationMarkRead.cs index 52ced2f9..a9d3b40e 100644 --- a/Assets/Plugins/StreamChat/Core/LowLevelClient/Events/EventNotificationMarkRead.cs +++ b/Assets/Plugins/StreamChat/Core/LowLevelClient/Events/EventNotificationMarkRead.cs @@ -20,8 +20,6 @@ public partial class EventNotificationMarkRead : EventBase, ILoadableFrom { - public System.DateTimeOffset? CreatedAt { get; set; } - public OwnUser Me { get; set; } public string Type { get; set; } diff --git a/Assets/Plugins/StreamChat/Core/LowLevelClient/Events/EventNotificationRemovedFromChannel.cs b/Assets/Plugins/StreamChat/Core/LowLevelClient/Events/EventNotificationRemovedFromChannel.cs index 7eeeaa46..965530e3 100644 --- a/Assets/Plugins/StreamChat/Core/LowLevelClient/Events/EventNotificationRemovedFromChannel.cs +++ b/Assets/Plugins/StreamChat/Core/LowLevelClient/Events/EventNotificationRemovedFromChannel.cs @@ -16,8 +16,6 @@ public sealed class EventNotificationRemovedFromChannel : EventBase, public string Cid { get; set; } - public System.DateTimeOffset? CreatedAt { get; set; } - public ChannelMember Member { get; set; } public string Type { get; set; } diff --git a/Assets/Plugins/StreamChat/Core/LowLevelClient/Events/EventReactionDeleted.cs b/Assets/Plugins/StreamChat/Core/LowLevelClient/Events/EventReactionDeleted.cs index dee36b20..5f19273d 100644 --- a/Assets/Plugins/StreamChat/Core/LowLevelClient/Events/EventReactionDeleted.cs +++ b/Assets/Plugins/StreamChat/Core/LowLevelClient/Events/EventReactionDeleted.cs @@ -13,8 +13,6 @@ public class EventReactionDeleted : EventBase, ILoadableFrom { - public System.DateTimeOffset? CreatedAt { get; set; } - public bool? DeleteConversationChannels { get; set; } public bool? HardDelete { get; set; } diff --git a/Assets/Plugins/StreamChat/Core/LowLevelClient/Events/EventUserFlagged.cs b/Assets/Plugins/StreamChat/Core/LowLevelClient/Events/EventUserFlagged.cs index d9b199d3..edef8aa7 100644 --- a/Assets/Plugins/StreamChat/Core/LowLevelClient/Events/EventUserFlagged.cs +++ b/Assets/Plugins/StreamChat/Core/LowLevelClient/Events/EventUserFlagged.cs @@ -7,8 +7,6 @@ namespace StreamChat.Core.LowLevelClient.Events { public partial class EventUserFlagged : EventBase, ILoadableFrom { - public System.DateTimeOffset? CreatedAt { get; set; } - public string TargetUser { get; set; } public System.Collections.Generic.List TargetUsers { get; set; } diff --git a/Assets/Plugins/StreamChat/Core/LowLevelClient/Events/EventUserMuted.cs b/Assets/Plugins/StreamChat/Core/LowLevelClient/Events/EventUserMuted.cs index 2cecac3b..717d82c7 100644 --- a/Assets/Plugins/StreamChat/Core/LowLevelClient/Events/EventUserMuted.cs +++ b/Assets/Plugins/StreamChat/Core/LowLevelClient/Events/EventUserMuted.cs @@ -7,8 +7,6 @@ namespace StreamChat.Core.LowLevelClient.Events { public partial class EventUserMuted : EventBase, ILoadableFrom { - public System.DateTimeOffset? CreatedAt { get; set; } - public string TargetUser { get; set; } public System.Collections.Generic.List TargetUsers { get; set; } diff --git a/Assets/Plugins/StreamChat/Core/LowLevelClient/Events/EventUserPresenceChanged.cs b/Assets/Plugins/StreamChat/Core/LowLevelClient/Events/EventUserPresenceChanged.cs index 678b2c70..99ce1659 100644 --- a/Assets/Plugins/StreamChat/Core/LowLevelClient/Events/EventUserPresenceChanged.cs +++ b/Assets/Plugins/StreamChat/Core/LowLevelClient/Events/EventUserPresenceChanged.cs @@ -8,8 +8,6 @@ namespace StreamChat.Core.LowLevelClient.Events public sealed class EventUserPresenceChanged : EventBase, ILoadableFrom { - public System.DateTimeOffset? CreatedAt { get; set; } - public string Type { get; set; } public User User { get; set; } diff --git a/Assets/Plugins/StreamChat/Core/LowLevelClient/Events/EventUserUnbanned.cs b/Assets/Plugins/StreamChat/Core/LowLevelClient/Events/EventUserUnbanned.cs index deb0b5d9..a1d8893e 100644 --- a/Assets/Plugins/StreamChat/Core/LowLevelClient/Events/EventUserUnbanned.cs +++ b/Assets/Plugins/StreamChat/Core/LowLevelClient/Events/EventUserUnbanned.cs @@ -14,8 +14,6 @@ public sealed class EventUserUnbanned : EventBase, public string Cid { get; set; } - public System.DateTimeOffset? CreatedAt { get; set; } - public bool? Shadow { get; set; } public string Team { get; set; } diff --git a/Assets/Plugins/StreamChat/Core/LowLevelClient/Events/EventUserUpdated.cs b/Assets/Plugins/StreamChat/Core/LowLevelClient/Events/EventUserUpdated.cs index 5f503477..0639651f 100644 --- a/Assets/Plugins/StreamChat/Core/LowLevelClient/Events/EventUserUpdated.cs +++ b/Assets/Plugins/StreamChat/Core/LowLevelClient/Events/EventUserUpdated.cs @@ -8,8 +8,6 @@ namespace StreamChat.Core.LowLevelClient.Events public sealed class EventUserUpdated : EventBase, ILoadableFrom { - public System.DateTimeOffset? CreatedAt { get; set; } - public string Type { get; set; } public User User { get; set; } diff --git a/Assets/Plugins/StreamChat/Core/LowLevelClient/Events/EventUserWatchingStart.cs b/Assets/Plugins/StreamChat/Core/LowLevelClient/Events/EventUserWatchingStart.cs index ab07b664..2fe1712a 100644 --- a/Assets/Plugins/StreamChat/Core/LowLevelClient/Events/EventUserWatchingStart.cs +++ b/Assets/Plugins/StreamChat/Core/LowLevelClient/Events/EventUserWatchingStart.cs @@ -14,8 +14,6 @@ public sealed class EventUserWatchingStart : EventBase, public string Cid { get; set; } - public System.DateTimeOffset? CreatedAt { get; set; } - public string Team { get; set; } public string Type { get; set; } diff --git a/Assets/Plugins/StreamChat/Core/LowLevelClient/Events/EventUserWatchingStop.cs b/Assets/Plugins/StreamChat/Core/LowLevelClient/Events/EventUserWatchingStop.cs index 914ab18a..b650bea8 100644 --- a/Assets/Plugins/StreamChat/Core/LowLevelClient/Events/EventUserWatchingStop.cs +++ b/Assets/Plugins/StreamChat/Core/LowLevelClient/Events/EventUserWatchingStop.cs @@ -14,8 +14,6 @@ public sealed class EventUserWatchingStop : EventBase, public string Cid { get; set; } - public System.DateTimeOffset? CreatedAt { get; set; } - public string Type { get; set; } public User User { get; set; } diff --git a/Assets/Plugins/StreamChat/Core/LowLevelClient/IStreamChatLowLevelClient.cs b/Assets/Plugins/StreamChat/Core/LowLevelClient/IStreamChatLowLevelClient.cs index 2d708a28..d6d7961f 100644 --- a/Assets/Plugins/StreamChat/Core/LowLevelClient/IStreamChatLowLevelClient.cs +++ b/Assets/Plugins/StreamChat/Core/LowLevelClient/IStreamChatLowLevelClient.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Threading.Tasks; using StreamChat.Core.Auth; using StreamChat.Core.LowLevelClient.API; @@ -86,5 +87,7 @@ void SetReconnectStrategySettings(ReconnectStrategy reconnectStrategy, float? ex void ConnectUser(AuthCredentials userAuthCredentials); Task DisconnectAsync(bool permanent = false); + + Task FetchAndProcessEventsSinceLastReceivedEvent(IEnumerable channelCids); } } \ No newline at end of file diff --git a/Assets/Plugins/StreamChat/Core/LowLevelClient/Requests/SyncRequest.cs b/Assets/Plugins/StreamChat/Core/LowLevelClient/Requests/SyncRequest.cs new file mode 100644 index 00000000..52af45b2 --- /dev/null +++ b/Assets/Plugins/StreamChat/Core/LowLevelClient/Requests/SyncRequest.cs @@ -0,0 +1,22 @@ +using StreamChat.Core.InternalDTO.Requests; + +namespace StreamChat.Core.LowLevelClient.Requests +{ + public partial class SyncRequest : RequestObjectBase, ISavableTo + { + public System.Collections.Generic.List ChannelCids { get; set; } + public System.DateTimeOffset LastSyncAt { get; set; } + public bool? Watch { get; set; } + + public bool? WithInaccessibleCids { get; set; } + + SyncRequestInternalDTO ISavableTo.SaveToDto() => + new SyncRequestInternalDTO + { + ChannelCids = ChannelCids, + LastSyncAt = LastSyncAt, + Watch = Watch, + WithInaccessibleCids = WithInaccessibleCids, + }; + } +} \ No newline at end of file diff --git a/Assets/Plugins/StreamChat/Core/LowLevelClient/Requests/SyncRequest.cs.meta b/Assets/Plugins/StreamChat/Core/LowLevelClient/Requests/SyncRequest.cs.meta new file mode 100644 index 00000000..a4422c68 --- /dev/null +++ b/Assets/Plugins/StreamChat/Core/LowLevelClient/Requests/SyncRequest.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 5f988a8c8997ced4a9f4da02773f78ca +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/StreamChat/Core/LowLevelClient/Responses/MarkReadResponse.cs b/Assets/Plugins/StreamChat/Core/LowLevelClient/Responses/MarkReadResponse.cs index fb0d3bec..7a3f810f 100644 --- a/Assets/Plugins/StreamChat/Core/LowLevelClient/Responses/MarkReadResponse.cs +++ b/Assets/Plugins/StreamChat/Core/LowLevelClient/Responses/MarkReadResponse.cs @@ -1,4 +1,5 @@ using StreamChat.Core.Helpers; +using StreamChat.Core.InternalDTO.Events; using StreamChat.Core.InternalDTO.Responses; using StreamChat.Core.LowLevelClient.Events; diff --git a/Assets/Plugins/StreamChat/Core/LowLevelClient/Responses/SyncResponse.cs b/Assets/Plugins/StreamChat/Core/LowLevelClient/Responses/SyncResponse.cs new file mode 100644 index 00000000..1311a324 --- /dev/null +++ b/Assets/Plugins/StreamChat/Core/LowLevelClient/Responses/SyncResponse.cs @@ -0,0 +1,31 @@ +using StreamChat.Core.InternalDTO.Responses; + +namespace StreamChat.Core.LowLevelClient.Responses +{ + public partial class SyncResponse : ResponseObjectBase, ILoadableFrom + { + /// + /// Duration of the request in human-readable format + /// + public string Duration { get; set; } + + /// + /// List of events + /// + public System.Collections.Generic.List Events { get; set; } + + /// + /// List of CIDs that user can't access + /// + public System.Collections.Generic.List InaccessibleCids { get; set; } + + SyncResponse ILoadableFrom.LoadFromDto(SyncResponseInternalDTO dto) + { + Duration = dto.Duration; + Events = dto.Events; + InaccessibleCids = dto.InaccessibleCids; + + return this; + } + } +} \ No newline at end of file diff --git a/Assets/Plugins/StreamChat/Core/LowLevelClient/Responses/SyncResponse.cs.meta b/Assets/Plugins/StreamChat/Core/LowLevelClient/Responses/SyncResponse.cs.meta new file mode 100644 index 00000000..949d303a --- /dev/null +++ b/Assets/Plugins/StreamChat/Core/LowLevelClient/Responses/SyncResponse.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 1210d19a56f52f44db3d69d10ac957ed +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/StreamChat/Core/LowLevelClient/StreamChatLowLevelClient.cs b/Assets/Plugins/StreamChat/Core/LowLevelClient/StreamChatLowLevelClient.cs index a44d7a1d..a5422901 100644 --- a/Assets/Plugins/StreamChat/Core/LowLevelClient/StreamChatLowLevelClient.cs +++ b/Assets/Plugins/StreamChat/Core/LowLevelClient/StreamChatLowLevelClient.cs @@ -25,6 +25,9 @@ using StreamChat.Libs.Time; using StreamChat.Libs.Utils; using StreamChat.Libs.Websockets; +using StreamChat.Core.LowLevelClient.Requests; +using System.Linq; + #if STREAM_TESTS_ENABLED using System.Runtime.CompilerServices; #endif @@ -180,6 +183,7 @@ private set if (value == ConnectionState.Disconnected) { + _disconnectionLastEventReceivedAt = _lastEventReceivedAt; Disconnected?.Invoke(); } } @@ -364,7 +368,7 @@ public void Update(float deltaTime) while (_websocketClient.TryDequeueMessage(out var msg)) { #if STREAM_DEBUG_ENABLED - _logs.Info("WS message: " + msg); + _logs.Info(_authCredentials.UserId + " WS message: " + msg); #endif HandleNewWebsocketMessage(msg); } @@ -381,6 +385,47 @@ public void SetReconnectStrategySettings(ReconnectStrategy reconnectStrategy, fl _reconnectScheduler.SetReconnectStrategySettings(reconnectStrategy, exponentialMinInterval, exponentialMaxInterval, constantInterval); } + public async Task FetchAndProcessEventsSinceLastReceivedEvent(IEnumerable channelCids) + { + if (!channelCids.Any() || !_disconnectionLastEventReceivedAt.HasValue) + { + return; + } + + var lastEventReceivedAt = _disconnectionLastEventReceivedAt.Value; + + var currentServerTime = DateTimeOffset.UtcNow.ToOffset(lastEventReceivedAt.Offset); + + // Check if less than 30 days + var diff = lastEventReceivedAt - _timeService.Now; + if (diff.TotalDays > 30) + { + return; + } + + //StreamTodo: according to Android SDK there's an error if there are > 1000 events + + var response = await ChannelApi.SyncAsync(new SyncRequest + { + ChannelCids = channelCids.ToList(), + LastSyncAt = lastEventReceivedAt, + }); + + if(response.Events.Count == 0) + { + return; + } + + foreach(var e in response.Events) + { + // StreamTodo: check if we can not serialized this again. Investigate adding a custom EventsJsonConverter that would populate the list as serialized strings + var serializedMsg = _serializer.Serialize(e); + + //StreamTodo: try block? + HandleNewWebsocketMessage(serializedMsg); + } + } + public void Dispose() { ConnectionState = ConnectionState.Closing; @@ -490,6 +535,16 @@ internal async Task ConnectUserAsync(string apiKey, string u private bool _websocketConnectionFailed; private ITokenProvider _tokenProvider; + /// + /// Date Time of the last received WebSocket event from the API. When calling /sync endpoint use + /// + private DateTimeOffset? _lastEventReceivedAt; + + /// + /// The last value of when the client disconnected. Use this value when calling /sync endpoint + /// + private DateTimeOffset? _disconnectionLastEventReceivedAt; + private async Task RefreshAuthTokenFromProvider() { #if STREAM_DEBUG_ENABLED @@ -730,7 +785,7 @@ private void RegisterEventHandlers() private void RegisterEventType(string key, Action handler, Action internalHandler = null) - where TEvent : ILoadableFrom, new() + where TEvent : EventBase, ILoadableFrom, new() { if (_eventKeyToHandler.ContainsKey(key)) { @@ -743,6 +798,7 @@ private void RegisterEventType(string key, try { var eventObj = DeserializeEvent(serializedContent, out var dto); + _lastEventReceivedAt = eventObj.CreatedAt; handler?.Invoke(eventObj, dto); internalHandler?.Invoke(dto); } diff --git a/Assets/Plugins/StreamChat/Core/State/Caches/Cache.cs b/Assets/Plugins/StreamChat/Core/State/Caches/Cache.cs index 5e90f0bf..c06b1b9f 100644 --- a/Assets/Plugins/StreamChat/Core/State/Caches/Cache.cs +++ b/Assets/Plugins/StreamChat/Core/State/Caches/Cache.cs @@ -29,8 +29,16 @@ public Cache(StreamChatClient stateClient, ISerializer serializer, ILogs logs) LocalUser.RegisterDtoIdMapping(dto => dto.Id); - //In some cases the ChannelMemberInternalDTO.UserId was null - ChannelMembers.RegisterDtoIdMapping(dto => dto.User.Id); + //In some cases the ChannelMemberInternalDTO.UserId was null -> only known case is channelDto.Membership + ChannelMembers.RegisterDtoIdMapping(dto => + { + if(dto.User != null) + { + return dto.User.Id; + } + + return dto.UserId; + }); Messages.RegisterDtoIdMapping(dto => dto.Id); } diff --git a/Assets/Plugins/StreamChat/Core/StatefulModels/StreamChannel.cs b/Assets/Plugins/StreamChat/Core/StatefulModels/StreamChannel.cs index 7044cfc4..d92a1d7d 100644 --- a/Assets/Plugins/StreamChat/Core/StatefulModels/StreamChannel.cs +++ b/Assets/Plugins/StreamChat/Core/StatefulModels/StreamChannel.cs @@ -827,7 +827,18 @@ private StreamMessage InternalAppendOrUpdateMessage(MessageInternalDTO dto) { if (!_messages.ContainsNoAlloc(streamMessage)) { + var lastMessage = _messages.LastOrDefault(); + _messages.Add(streamMessage); + + // If local user sends message during the sync operation. + // It is possible that the locally sent message will be added before the /sync endpoint returns past message events + if (lastMessage != null && streamMessage.CreatedAt < lastMessage.CreatedAt) + { + //StreamTodo: test this more. A good way was to toggle Ethernet on PC and send messages on Android + _messages.Sort(_messageCreateAtComparer); + } + MessageReceived?.Invoke(this, streamMessage); } } @@ -835,6 +846,18 @@ private StreamMessage InternalAppendOrUpdateMessage(MessageInternalDTO dto) return streamMessage; } + //StreamTodo: move this to the right place + private MessageCreateAtComparer _messageCreateAtComparer = new MessageCreateAtComparer(); + + //StreamTodo: move outside and change to internal + private class MessageCreateAtComparer : IComparer + { + public int Compare(IStreamMessage x, IStreamMessage y) + { + return x.CreatedAt.CompareTo(y.CreatedAt); + } + } + //StreamTodo: This deleteBeforeCreatedAt date is the date of event, it does not equal the passed TruncatedAt //Therefore the only way to detect partial truncate in the past would be to query the history private void InternalTruncateMessages(DateTimeOffset? deleteBeforeCreatedAt = null, diff --git a/Assets/Plugins/StreamChat/Core/StreamChatClient.cs b/Assets/Plugins/StreamChat/Core/StreamChatClient.cs index 6196204e..7f58aea5 100644 --- a/Assets/Plugins/StreamChat/Core/StreamChatClient.cs +++ b/Assets/Plugins/StreamChat/Core/StreamChatClient.cs @@ -27,6 +27,8 @@ using StreamChat.Libs.Serialization; using StreamChat.Libs.Time; using StreamChat.Libs.Websockets; +using StreamChat.Core.LowLevelClient.Requests; +using StreamChat.Libs.Utils; namespace StreamChat.Core { @@ -201,11 +203,11 @@ var ownUserDto return UpdateLocalUser(ownUserDto); } - //StreamTodo: test scenario: ConnectUserAsync and immediately all DisconnectUserAsync + //StreamTodo: test scenario: ConnectUserAsync and immediately call DisconnectUserAsync public Task DisconnectUserAsync() { TryCancelWaitingForUserConnection(); - return InternalLowLevelClient.DisconnectAsync(); + return InternalLowLevelClient.DisconnectAsync(permanent: true); } public bool IsLocalUser(IStreamUser user) => LocalUserData.User == user; @@ -603,12 +605,25 @@ internal IStreamLocalUserData UpdateLocalUser(OwnUserInternalDTO ownUserInternal { _localUserData = _cache.TryCreateOrUpdate(ownUserInternalDto); - //StreamTodo: Can we not rely on whoever called TryCreateOrUpdate to update this but make it more reliable? Better to react to some event - // This could be solved if ChannelMutes would be an observable collection - foreach (var channel in _cache.Channels.AllItems) + if(LocalUserData == null) { - var isMuted = LocalUserData.ChannelMutes.Any(_ => _.Channel == channel); - channel.Muted = isMuted; + _logs.Error("Local User Data is null"); + return _localUserData; + } + + if(LocalUserData.ChannelMutes != null) + { + //StreamTodo: Can we not rely on whoever called TryCreateOrUpdate to update this but make it more reliable? Better to react to some event + // This could be solved if ChannelMutes would be an observable collection + foreach (var channel in _cache.Channels.AllItems) + { + var isMuted = LocalUserData.ChannelMutes.Any(_ => _.Channel == channel); + channel.Muted = isMuted; + } + } + else + { + _logs.Info("ChannelMutes is null"); } return _localUserData; @@ -696,7 +711,18 @@ private void OnConnected(HealthCheckEventInternalDTO dto) try { var localUserDto = dto.Me; - UpdateLocalUser(localUserDto); + + // This can sometimes be null. I think it's when the client lost network and believes he's reconnecting + // but the healthcheck timeout didn't pass on server and from the server perspective the client never disconnected + if(localUserDto != null) + { + UpdateLocalUser(localUserDto); + } + else + { + _logs.Warning("OnConnected localUserDto was NULL and current LocalUserData is " + (LocalUserData != null) + " value " + LocalUserData); + } + Connected?.Invoke(LocalUserData); } finally @@ -708,6 +734,18 @@ private void OnConnected(HealthCheckEventInternalDTO dto) _connectUserTaskSource = null; } } + + RestoreStateLostDuringDisconnect().LogIfFailed(); + } + + private Task RestoreStateLostDuringDisconnect() + { + if (!WatchedChannels.Any()) + { + return Task.CompletedTask; + } + + return LowLevelClient.FetchAndProcessEventsSinceLastReceivedEvent(WatchedChannels.Select(c => c.Cid)); } private void OnDisconnected() => Disconnected?.Invoke(); @@ -864,6 +902,12 @@ private void OnMarkReadNotification(NotificationMarkReadEventInternalDTO eventDt private void OnAddedToChannelNotification(NotificationAddedToChannelEventInternalDTO eventDto) { + //StreamTodo: sometimes when I run all tests the eventDto.Channel.Type is null. Inspect how different is this DTO from the channel kept in cache. If its incomplete we shouldn't update the cached value + if (eventDto.Channel.Type == null && eventDto.ChannelType != null) + { + eventDto.Channel.Type = eventDto.ChannelType; + } + var channel = _cache.TryCreateOrUpdate(eventDto.Channel, out var wasCreated); var member = _cache.TryCreateOrUpdate(eventDto.Member); _cache.TryCreateOrUpdate(eventDto.Member.User); diff --git a/Assets/Plugins/StreamChat/Libs/Time/ITimeService.cs b/Assets/Plugins/StreamChat/Libs/Time/ITimeService.cs index d4787f26..e26da97e 100644 --- a/Assets/Plugins/StreamChat/Libs/Time/ITimeService.cs +++ b/Assets/Plugins/StreamChat/Libs/Time/ITimeService.cs @@ -1,4 +1,6 @@ -namespace StreamChat.Libs.Time +using System; + +namespace StreamChat.Libs.Time { /// /// Provides time information @@ -7,5 +9,6 @@ public interface ITimeService { float Time { get; } float DeltaTime { get; } + DateTimeOffset Now { get; } } } \ No newline at end of file diff --git a/Assets/Plugins/StreamChat/Libs/Time/UnityTime.cs b/Assets/Plugins/StreamChat/Libs/Time/UnityTime.cs index b9388b6a..6fb1dfa4 100644 --- a/Assets/Plugins/StreamChat/Libs/Time/UnityTime.cs +++ b/Assets/Plugins/StreamChat/Libs/Time/UnityTime.cs @@ -1,4 +1,6 @@ -namespace StreamChat.Libs.Time +using System; + +namespace StreamChat.Libs.Time { /// /// based on @@ -7,5 +9,6 @@ public class UnityTime : ITimeService { public float Time => UnityEngine.Time.time; public float DeltaTime => UnityEngine.Time.deltaTime; + public DateTimeOffset Now => DateTimeOffset.Now; } } \ 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 585f59c7..4986deb4 100644 --- a/Assets/Plugins/StreamChat/Tests/StatefulClient/BaseStateIntegrationTests.cs +++ b/Assets/Plugins/StreamChat/Tests/StatefulClient/BaseStateIntegrationTests.cs @@ -95,7 +95,7 @@ protected static IEnumerator ConnectAndExecute(Func test) yield return ConnectAndExecuteAsync(test).RunAsIEnumerator(statefulClient: Client); } - protected Task GetConnectedOtherClient() + protected Task GetConnectedOtherClientAsync() => StreamTestClients.Instance.ConnectOtherStateClientAsync(); //StreamTodo: figure out syntax to wrap call in using that will subscribe to observing an event if possible diff --git a/Assets/Plugins/StreamChat/Tests/StatefulClient/ChannelMembersTests.cs b/Assets/Plugins/StreamChat/Tests/StatefulClient/ChannelMembersTests.cs index a3cac1d2..6cf33b8d 100644 --- a/Assets/Plugins/StreamChat/Tests/StatefulClient/ChannelMembersTests.cs +++ b/Assets/Plugins/StreamChat/Tests/StatefulClient/ChannelMembersTests.cs @@ -377,7 +377,7 @@ public IEnumerator When_user_added_to_not_watched_channel_expect_received_channe private async Task When_user_added_to_not_watched_channel_expect_received_channel_being_watched_Async() { - var otherClient = await GetConnectedOtherClient(); + var otherClient = await GetConnectedOtherClientAsync(); var otherClientChannel = await CreateUniqueTempChannelAsync(watch: false, overrideClient: otherClient); var receivedEvent = false; diff --git a/Assets/Plugins/StreamChat/Tests/StatefulClient/ClientStateSyncTests.cs b/Assets/Plugins/StreamChat/Tests/StatefulClient/ClientStateSyncTests.cs new file mode 100644 index 00000000..ebde4c8e --- /dev/null +++ b/Assets/Plugins/StreamChat/Tests/StatefulClient/ClientStateSyncTests.cs @@ -0,0 +1,205 @@ +#if STREAM_TESTS_ENABLED +using NUnit.Framework; +using StreamChat.Core.StatefulModels; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using UnityEngine.TestTools; + +namespace StreamChat.Tests.StatefulClient +{ + /// + /// Tests verifying state recovery after disconnections + /// + internal class ClientStateSyncTests : BaseStateIntegrationTests + { + [UnityTest] + public IEnumerator When_client_reconnects_expect_receiving_missed_messages() + => ConnectAndExecute(When_client_reconnects_expect_receiving_missed_messages_Async); + + private async Task When_client_reconnects_expect_receiving_missed_messages_Async() + { + // Create channel + var channel = await CreateUniqueTempChannelAsync(); + + var otherClient = await GetConnectedOtherClientAsync(); + + // Fetch channel on other client to get it loaded into state layer + var otherClientChannel = await otherClient.GetOrCreateChannelWithIdAsync(channel.Type, channel.Id); + + Assert.AreEqual(channel.Cid, otherClientChannel.Cid); + + var otherClientMessage = await otherClientChannel.SendNewMessageAsync("BEFORE DISCONNECT #1"); + var otherClientMessage2 = await otherClientChannel.SendNewMessageAsync("BEFORE DISCONNECT #2"); + + await otherClientMessage.SendReactionAsync("like"); + + // DISCONNECT + await otherClient.DisconnectUserAsync(); + Assert.IsFalse(otherClient.IsConnected); + + // Send 2 messages on the first client + var message = await channel.SendNewMessageAsync("RECONNECTED #1"); + var message2 = await channel.SendNewMessageAsync("RECONNECTED #2"); + + // Messages should be presents on the connected client + Assert.IsTrue(channel.Messages.Contains(message)); + Assert.IsTrue(channel.Messages.Contains(message2)); + + // And should not be present on the disconnected client + Assert.IsFalse(otherClientChannel.Messages.Any(m => m.Id == message.Id)); + Assert.IsFalse(otherClientChannel.Messages.Any(m => m.Id == message2.Id)); + + // Reconnect other client + await GetConnectedOtherClientAsync(); + + // Wait for sync request to complete + await WaitWhileTrueAsync(() => otherClientChannel.Messages.All(m => m.Id != message.Id)); + + // Messages should now be present on the second client with no duplicates + Assert.IsNotNull(otherClientChannel.Messages.Single(m => m.Id == message.Id)); + Assert.IsNotNull(otherClientChannel.Messages.Single(m => m.Id == message2.Id)); + + // Check for duplicates + var uniqueIds = new HashSet(); + Assert.IsTrue(otherClientChannel.Messages.All(m => uniqueIds.Add(m.Id))); + + Assert.AreEqual(4, otherClientChannel.Messages.Count); + Assert.AreEqual(1, otherClientChannel.Messages.Sum(m => m.ReactionCounts.Values.Sum())); + } + + [UnityTest] + public IEnumerator When_client_reconnects_expect_receiving_missed_messages2() + => ConnectAndExecute(When_client_reconnects_expect_receiving_missed_messages2_Async); + + private async Task When_client_reconnects_expect_receiving_missed_messages2_Async() + { + // Create channel + var channel = await CreateUniqueTempChannelAsync(); + + var otherClient = await GetConnectedOtherClientAsync(); + + // Fetch channel on other client to get it loaded into state layer + var otherClientChannel = await otherClient.GetOrCreateChannelWithIdAsync(channel.Type, channel.Id); + + Assert.AreEqual(channel.Cid, otherClientChannel.Cid); + + var otherClientMessage = await otherClientChannel.SendNewMessageAsync("BEFORE DISCONNECT #1"); + + // DISCONNECT + await otherClient.DisconnectUserAsync(); + Assert.IsFalse(otherClient.IsConnected); + + // Send messages on the first client + var message = await channel.SendNewMessageAsync("RECONNECTED #1"); + var message2 = await channel.SendNewMessageAsync("RECONNECTED #2"); + + await message.SendReactionAsync("heart"); + await message.SendReactionAsync("like"); + + await message2.SendReactionAsync("like"); + await message2.SendReactionAsync("nice"); + await message2.SendReactionAsync("smile"); + + // Messages should be presents on the connected client + Assert.IsTrue(channel.Messages.Contains(message)); + Assert.IsTrue(channel.Messages.Contains(message2)); + + // And should not be present on the disconnected client + Assert.IsFalse(otherClientChannel.Messages.Any(m => m.Id == message.Id)); + Assert.IsFalse(otherClientChannel.Messages.Any(m => m.Id == message2.Id)); + + // Reconnect other client + await GetConnectedOtherClientAsync(); + + // Wait for sync request to complete + await WaitWhileTrueAsync(() => otherClientChannel.Messages.All(m => m.Id != message.Id)); + + // Messages should now be present on the second client with no duplicates + var otherClientMessage1 = otherClientChannel.Messages.Single(m => m.Id == message.Id); + var otherClientMessage2 = otherClientChannel.Messages.Single(m => m.Id == message2.Id); + Assert.IsNotNull(otherClientMessage1); + Assert.IsNotNull(otherClientMessage2); + + // Reactions should now be present on the second client + Assert.AreEqual(2, otherClientMessage1.ReactionCounts.Values.Sum()); + Assert.AreEqual(3, otherClientMessage2.ReactionCounts.Values.Sum()); + Assert.IsTrue(new[] { "like", "heart" }.All(otherClientMessage1.ReactionCounts.Keys.Contains)); + Assert.IsTrue(new[] { "like", "nice", "smile" }.All(otherClientMessage2.ReactionCounts.Keys.Contains)); + + // Check for duplicates + var uniqueIds = new HashSet(); + Assert.IsTrue(otherClientChannel.Messages.All(m => uniqueIds.Add(m.Id))); + + Assert.AreEqual(3, otherClientChannel.Messages.Count); + } + + //StreamTodo: validate that appropriate events are being triggered on the StreamChatClient instance + + + [UnityTest] + public IEnumerator When_client_sends_message_right_after_reconnect_expect_received_older_messages_to_be_in_correct_order() + => ConnectAndExecute(When_client_sends_message_right_after_reconnect_expect_received_older_messages_to_be_in_correct_order_Async); + + private async Task When_client_sends_message_right_after_reconnect_expect_received_older_messages_to_be_in_correct_order_Async() + { + // Create channel + var channel = await CreateUniqueTempChannelAsync(); + + var otherClient = await GetConnectedOtherClientAsync(); + + // Fetch channel on other client to get it loaded into state layer + var otherClientChannel = await otherClient.GetOrCreateChannelWithIdAsync(channel.Type, channel.Id); + + Assert.AreEqual(channel.Cid, otherClientChannel.Cid); + + var otherClientMessage = await otherClientChannel.SendNewMessageAsync("BEFORE DISCONNECT #1"); + + // DISCONNECT + await otherClient.DisconnectUserAsync(); + Assert.IsFalse(otherClient.IsConnected); + + // Send messages on the first client + var message = await channel.SendNewMessageAsync("RECONNECTED #1"); + var message2 = await channel.SendNewMessageAsync("RECONNECTED #2"); + + // Messages should be presents on the connected client + Assert.IsTrue(channel.Messages.Contains(message)); + Assert.IsTrue(channel.Messages.Contains(message2)); + + // And should not be present on the disconnected client + Assert.IsFalse(otherClientChannel.Messages.Any(m => m.Id == message.Id)); + Assert.IsFalse(otherClientChannel.Messages.Any(m => m.Id == message2.Id)); + + // Reconnect other client + await GetConnectedOtherClientAsync(); + + // We do not wait for the /sync to complete + var otherClientMessageAfterReconnect = await otherClientChannel.SendNewMessageAsync("AFTER #1"); + var otherClientMessageAfterReconnect2 = await otherClientChannel.SendNewMessageAsync("AFTER #2"); + + // Wait for sync request to complete + await WaitWhileTrueAsync(() => otherClientChannel.Messages.All(m => m.Id != message2.Id)); + + // Assert correct number of messages + Assert.AreEqual(5, otherClientChannel.Messages.Count); + + // Assert no duplicates + var uniqueIds = new HashSet(); + Assert.IsTrue(otherClientChannel.Messages.All(m => uniqueIds.Add(m.Id))); + + var messages = otherClientChannel.Messages.ToArray(); + // Assert correct order + Assert.AreEqual(0, Array.FindIndex(messages, m => m.Id == otherClientMessage.Id)); + Assert.AreEqual(1, Array.FindIndex(messages, m => m.Id == message.Id)); + Assert.AreEqual(2, Array.FindIndex(messages, m => m.Id == message2.Id)); + Assert.AreEqual(3, Array.FindIndex(messages, m => m.Id == otherClientMessageAfterReconnect.Id)); + Assert.AreEqual(4, Array.FindIndex(messages, m => m.Id == otherClientMessageAfterReconnect2.Id)); + + + } + } +} +#endif \ No newline at end of file diff --git a/Assets/Plugins/StreamChat/Tests/StatefulClient/ClientStateSyncTests.cs.meta b/Assets/Plugins/StreamChat/Tests/StatefulClient/ClientStateSyncTests.cs.meta new file mode 100644 index 00000000..388999d1 --- /dev/null +++ b/Assets/Plugins/StreamChat/Tests/StatefulClient/ClientStateSyncTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 9350f462c0534ff281576ef13ff358cd +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: