Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions Assets/Plugins/StreamChat/Core/Configs/IStreamClientConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,5 +36,13 @@ public interface IStreamClientConfig
/// Does not change server history. See <see cref="StatefulModels.IStreamChannel.MessageCacheWindow"/>.
/// </summary>
MessageCacheWindow DefaultMessageCacheWindow { get; set; }

/// <summary>
/// How the client restores local state after the websocket reconnects. Defaults to
/// <see cref="Configs.StateRecoveryStrategy.ReplayEvents"/>, which preserves the per-event
/// callback behaviour of earlier SDK versions. See <see cref="Configs.StateRecoveryStrategy"/>
/// for when to pick each option.
/// </summary>
StateRecoveryStrategy StateRecoveryStrategy { get; set; }
}
}
64 changes: 64 additions & 0 deletions Assets/Plugins/StreamChat/Core/Configs/StateRecoveryStrategy.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
namespace StreamChat.Core.Configs
{
/// <summary>
/// How <see cref="IStreamChatClient"/> restores local state after the websocket reconnects.
/// Set through <see cref="IStreamClientConfig.StateRecoveryStrategy"/>.
/// </summary>
/// <remarks>
/// Regardless of the strategy, a reconnect always drops the server-side watches that were
/// established before the disconnect. <see cref="ReplayEvents"/> and <see cref="BatchStateUpdate"/>
/// re-establish them; <see cref="Disabled"/> leaves that to you.
/// </remarks>
public enum StateRecoveryStrategy
{
/// <summary>
/// Default, and the behaviour of every SDK version before this option existed.
///
/// The client calls <c>/sync</c> for the channels it was watching and replays each missed
/// event through the normal event pipeline, so every per-event callback
/// (<see cref="StatefulModels.IStreamChannel.MessageReceived"/>,
/// <see cref="StatefulModels.IStreamChannel.ReactionAdded"/>, and so on) fires exactly as it
/// would for a live event. It then re-queries and re-watches those channels unconditionally,
/// which is new: previously a failed or skipped <c>/sync</c> left the channels stale and
/// unwatched for the rest of the connection.
///
/// Choose this when your UI is driven by per-event callbacks. The cost is that a long outage
/// on a busy channel replays up to ~1000 events in a single frame, which is visible as a
/// hitch on mobile.
/// </summary>
ReplayEvents = 0,

/// <summary>
/// Same recovery pipeline as <see cref="ReplayEvents"/>, but the <c>/sync</c> events are
/// applied to local state without raising the per-event callbacks whose effect is observable
/// in model state afterwards. Subscribe to <see cref="IStreamChatClient.StateRecovered"/> and
/// rebuild from <see cref="StatefulModels.IStreamChannel.Messages"/> and friends instead.
///
/// Callbacks that carry information the SDK cannot reconstruct from state are still raised
/// per event: <see cref="StatefulModels.IStreamChannel.CustomEventReceived"/>,
/// <see cref="IStreamChatClient.ChannelDeleted"/>, and the local-user membership and invite
/// notifications.
///
/// Choose this when a long outage causes a frame hitch on resume. This is the cheapest
/// recovery the SDK offers.
/// </summary>
BatchStateUpdate = 1,

/// <summary>
/// The SDK performs no recovery after a reconnect: no <c>/sync</c>, no re-query, no re-watch,
/// and no <see cref="IStreamChatClient.StateRecovered"/>. Local state is left exactly as it
/// was and <see cref="IStreamChatClient.WatchedChannels"/> is left untouched, so it remains
/// the list of what you were watching before the drop.
///
/// Choose this only if you own recovery. Subscribe to
/// <see cref="IStreamChatClient.ConnectionStateChanged"/>, and on the transition to
/// <see cref="LowLevelClient.ConnectionState.Connected"/> re-hydrate and re-watch yourself with
/// <c>QueryChannelsAsync(new[] { ChannelFilter.Cid.In(cids) }, limit: 30)</c> - that single
/// call both refreshes state and re-establishes the watches. Note that
/// <see cref="StatefulModels.IStreamChannel.WatchAsync"/> is a no-op for a channel whose
/// <see cref="StatefulModels.IStreamChannel.IsWatched"/> is still <c>true</c>, which is the
/// case here, so use the query.
/// </summary>
Disabled = 2,
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Assets/Plugins/StreamChat/Core/Configs/StreamClientConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,7 @@ public class StreamClientConfig : IStreamClientConfig
public bool OptimisticMessageInsert { get; set; } = true;

public MessageCacheWindow DefaultMessageCacheWindow { get; set; } = null;

public StateRecoveryStrategy StateRecoveryStrategy { get; set; } = Configs.StateRecoveryStrategy.ReplayEvents;
}
}
27 changes: 27 additions & 0 deletions Assets/Plugins/StreamChat/Core/IStreamChatClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,27 @@ public interface IStreamChatClient : IDisposable, IStreamChatClientEventsListene
/// </summary>
event Action Disconnected;

/// <summary>
/// Raised once after reconnect recovery finishes, on every path: full success, partial
/// success, and a reconnect where there was nothing to recover. Not raised on the initial
/// login, and not raised when
/// <see cref="Configs.IStreamClientConfig.StateRecoveryStrategy"/> is
/// <see cref="Configs.StateRecoveryStrategy.Disabled"/>.
///
/// When it fires, the channels in
/// <see cref="StreamStateRecoveredEventArgs.Channels"/> have fresh state and live watches
/// again. Anything in <see cref="StreamStateRecoveredEventArgs.UnrecoveredChannelCids"/> is
/// still stale and no longer watched.
///
/// This is the signal to rebuild from state after an outage. It is required rather than
/// merely convenient under
/// <see cref="Configs.StateRecoveryStrategy.BatchStateUpdate"/>, where the per-event callbacks
/// are suppressed during recovery, and it is worth handling under
/// <see cref="Configs.StateRecoveryStrategy.ReplayEvents"/> too, because the re-query that
/// follows the event replay merges channel state without raising per-message callbacks.
/// </summary>
event StateRecoveredHandler StateRecovered;

/// <summary>
/// Event fired when connection state with Stream Chat server has changed
/// </summary>
Expand Down Expand Up @@ -129,6 +150,12 @@ public interface IStreamChatClient : IDisposable, IStreamChatClientEventsListene
/// methods may not be watched - check <see cref="IStreamChannel.IsWatched"/> on a specific
/// channel to know its state.
/// </para>
/// <para>
/// Emptied when the connection drops, because the server drops every watch with it, and
/// repopulated by reconnect recovery. If you enumerate this to decide what to restore
/// yourself, read it before the disconnect or use
/// <see cref="Configs.StateRecoveryStrategy.Disabled"/>, which leaves it untouched.
/// </para>
/// </summary>
IReadOnlyList<IStreamChannel> WatchedChannels { get; }

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using System;

namespace StreamChat.Core.LowLevelClient
{
/// <summary>
/// Outcome of one silent <c>/sync</c> history batch. See
/// <see cref="StreamChatLowLevelClient.ApplyHistoryEvents"/>.
/// </summary>
internal sealed class HistorySyncApplyResult
{
/// <summary>
/// <c>created_at</c> of the newest event that was applied successfully, or <c>null</c> when
/// nothing was applied. This is what the <c>/sync</c> watermark advances to.
/// </summary>
public DateTimeOffset? MaxAppliedCreatedAt { get; set; }

/// <summary>
/// Events that threw while being applied. They are skipped, not retried within the batch.
/// </summary>
public int FailedEventCount { get; set; }
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading