From 32864d9e60e0c879cbc3eddc119c262a79d494e2 Mon Sep 17 00:00:00 2001 From: Harlan Crystal Date: Sat, 25 Jul 2026 21:22:02 -0700 Subject: [PATCH] Add IStreamChatClient.SuspendConnectionAsync (non-permanent disconnect) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds a high-level way to close the websocket WITHOUT ending the user session, for callers that know the client is about to stop pumping Update() — an app being backgrounded. Messages are received on a background timer thread but only processed from Unity's main loop, so a stalled pump lets the receive queue fill with nothing draining it. The only high-level disconnect today is DisconnectUserAsync(), which calls DisconnectAsync(permanent: true) -> ReconnectScheduler.Stop(). That is one-way: _isStopped is never cleared and the scheduler exposes no restart, so a client disconnected that way never auto-recovers. The non-permanent variant existed only on InternalLowLevelClient, which is internal to the StreamChat.Core asmdef and so unreachable from a consumer assembly without an InternalsVisibleTo. So this is a pass-through to DisconnectAsync(permanent: false), which leaves the reconnect scheduler armed. Because the scheduler schedules against the Unity clock (frozen while the app is paused), the reconnect fires on the first frame after resume, re-hydrating missed events via the usual /sync catch-up. Purely additive — no existing behavior changes. It would fold away if the SDK ships its own Suspend()/Unsuspend(), which StreamChatLowLevelClient.DisconnectAsync already contemplates in a TODO. --- Assets/Plugins/StreamChat/Changelog.txt | 1 + Assets/Plugins/StreamChat/Core/IStreamChatClient.cs | 11 +++++++++++ Assets/Plugins/StreamChat/Core/StreamChatClient.cs | 5 +++++ 3 files changed, 17 insertions(+) diff --git a/Assets/Plugins/StreamChat/Changelog.txt b/Assets/Plugins/StreamChat/Changelog.txt index 335bd26b..2018f4aa 100644 --- a/Assets/Plugins/StreamChat/Changelog.txt +++ b/Assets/Plugins/StreamChat/Changelog.txt @@ -14,6 +14,7 @@ Fixes: Unreleased: Features: +* Add IStreamChatClient.SuspendConnectionAsync, which closes the websocket but keeps the user session and the reconnect schedule. Use it when you know the client is about to stop pumping Update (e.g. an app being backgrounded): messages are received on a background timer thread but only handled from Unity's main loop, so a stalled pump lets the receive queue fill with nothing draining it. Watched channels are preserved and the reconnect fires on the next update tick, re-hydrating missed events. Contrast with DisconnectUserAsync, which ends the session permanently and cannot be resumed without connecting the user again. * Add a public StreamApiException constructor (statusCode, code, errorMessage, moreInfo, duration, exceptionFields). StreamApiException is a public, catch-and-branch type (via the StreamApiExceptionExtensions.Is* helpers), but until now it could only be constructed inside the SDK from the internal APIErrorInternalDTO, so integrators could not build one to unit-test their own error handling (e.g. simulating a 403 / code 70 "no access to channels" response). The new constructor maps directly to the type's public properties and keeps APIErrorInternalDTO internal. * Add IStreamClientConfig.OptimisticMessageInsert (default true). When true (the existing behavior), a message you send is inserted into the local channel state and raised via IStreamChannel.MessageReceived immediately, before the server's message.new echo arrives. Set it to false to skip the optimistic local insert and wait for the server echo instead, so every participant - including the sender - observes messages in the same server-defined order. Useful when consistent cross-client ordering matters more than instant local feedback (e.g. a shared, broadcast-ordered feed). diff --git a/Assets/Plugins/StreamChat/Core/IStreamChatClient.cs b/Assets/Plugins/StreamChat/Core/IStreamChatClient.cs index e72e016b..fee9024c 100644 --- a/Assets/Plugins/StreamChat/Core/IStreamChatClient.cs +++ b/Assets/Plugins/StreamChat/Core/IStreamChatClient.cs @@ -344,6 +344,17 @@ Task DeleteMultipleChannelsAsync(IEnumerable + /// Closes the websocket but keeps the user session and the reconnect schedule, + /// for when the caller knows the client is about to stop pumping (an + /// app being backgrounded). Stops the background receive timer so no unbounded backlog + /// builds up while nothing is draining it. Watched channels are preserved and the + /// reconnect fires on the next update tick, re-hydrating missed events. Contrast with + /// , which ends the session permanently and cannot be + /// resumed without connecting the user again. + /// + Task SuspendConnectionAsync(); + bool IsLocalUser(IStreamUser messageUser); /// diff --git a/Assets/Plugins/StreamChat/Core/StreamChatClient.cs b/Assets/Plugins/StreamChat/Core/StreamChatClient.cs index d05928fd..2ec9d7eb 100644 --- a/Assets/Plugins/StreamChat/Core/StreamChatClient.cs +++ b/Assets/Plugins/StreamChat/Core/StreamChatClient.cs @@ -225,6 +225,11 @@ public Task DisconnectUserAsync() return InternalLowLevelClient.DisconnectAsync(permanent: true); } + // permanent: false is the whole point — it leaves the ReconnectScheduler armed + // so the connection comes back on its own once Update ticks again. + /// + public Task SuspendConnectionAsync() => InternalLowLevelClient.DisconnectAsync(permanent: false); + public async Task GetLatestUnreadCountsAsync() { var dto = await InternalLowLevelClient.InternalChannelApi.GetUnreadCountsAsync();