2.52.0

(feat): Conditionally emit System.Text.Json as a PackageReference only when targeting pre-net8.0 TFMs (netstandard2.0, net462). The package is wrapped in an ItemGroup with an MSBuild IsTargetFrameworkCompatible condition so it is skipped for net8.0+ where it ships in-box.

2.51.1

(chore): Remove dead #elif NET7_0_OR_GREATER and #else branches inside QueryStringBuilder that could only execute on .NET 6 or .NET 7, which are no longer targeted.

2.51.0

(feat): Add net9.0 target framework to all generated C# SDK projects. The library now targets net462;net8.0;net9.0;netstandard2.0, enabling #if NET9_0_OR_GREATER code paths such as ClientWebSocketOptions.KeepAliveTimeout for transport-level dead connection detection. Test projects now target net9.0 instead of net8.0.

(feat): Add comprehensive WebSocket unit and end-to-end test templates. All generated WebSocket SDKs now ship with 349 tests covering AsyncLock, Event<T>, ReconnectStrategy, DisconnectionInfo, RequestMessage, Query, WebSocketConnection, WebSocketClient, ReconnectionInfo, and WebsocketException. End-to-end tests run against an in-process TestWebSocketServer and cover echo, rapid-fire, reconnection, server close, abort recovery, concurrent clients, large messages, and full lifecycle. Templates are conditionally emitted when the Fern definition includes WebSocket endpoints.

(feat): Expose StateCheckInterval property on WebSocketClient. The property (default: 5 seconds) forwards to WebSocketConnection in ConnectAsync, allowing callers to control how often the background state monitor polls for silent disconnections.

(fix): Fix server-initiated close skipping reconnection. When the server sent a WebSocketMessageType.Close frame, the Listen() method used return which exited entirely — bypassing the ReconnectSynchronized call. Changed to break with a closedByServer flag so reconnection executes with ReconnectionType.ByServer.

(fix): Fix CancelReconnection flag being ignored for server-initiated close. The OnDisconnectionHappened callback fires in the Close handler and the user can set info.CancelReconnection = true, but this flag was never checked before calling ReconnectSynchronized. Now captured into a local cancelReconnect variable and checked before reconnecting.

2.50.0

(feat): Add exponential backoff reconnection strategy for WebSocket connections. A new ReconnectStrategy class provides configurable exponential backoff (doubling intervals from 1s to 60s), optional random jitter (0-25%) to prevent thundering herd, and configurable max attempts. The strategy is exposed as a ReconnectBackoff property on the generated Options class and wired through WebSocketClient to WebSocketConnection.Reconnect(). When max attempts are exhausted, reconnection stops and a disconnection event is raised. The backoff resets on successful reconnection.

2.49.0

(feat): Add per-send timeout to prevent SendAsync hangs. A new SendTimeout property (default: 30 seconds) on WebSocketConnection wraps every SendAsync call with a linked CancellationTokenSource that fires CancelAfter(SendTimeout). When the timeout elapses the connection is aborted and a WebsocketException is thrown, preventing a single dead connection from blocking the send lock for up to 30 minutes. See: https://github.com/dotnet/runtime/issues/125257

2.48.0

(feat): Add bounded send queue with message expiration to WebSocketConnection. The unbounded Channel<T> queues are replaced with configurable bounded channels (SendQueueLimit, default: 10,000) that use BoundedChannelFullMode.DropWrite to reject new messages when the queue is full. A SendCacheItemTimeout property (default: 30 minutes) causes stale messages to be silently dropped during drain, preventing memory growth during network outages and avoiding sending outdated messages after reconnection. Set SendQueueLimit to 0 for unbounded or SendCacheItemTimeout to null to disable expiration.

2.47.0

(feat): Add receive hang detection via a background state monitor task. A new StateCheckInterval property (default: 5 seconds) controls how often the monitor polls WebSocket.State. When the state transitions to Closed, Aborted, or CloseReceived — indicating the TCP connection died without notifying the receive loop — the monitor cancels the pending ReceiveAsync via a linked CancellationTokenSource. This works around known .NET runtime issues where ReceiveAsync hangs indefinitely on silent TCP disconnections (dotnet/runtime#110496) and Abort() fails to cancel a pending receive (dotnet/runtime#44272). Set StateCheckInterval to null to disable.

2.46.0

(feat): Wire up WebSocket reconnection logic. The WebSocketConnection template now contains a complete reconnection implementation ported from the Marfusios library: heartbeat timer (ReconnectTimeout), error-retry timer (ErrorReconnectTimeout), lost-connection delay (LostReconnectTimeout), and Reconnect()/ReconnectOrFail() public methods. The generated WebSocket API classes expose a Reconnecting event and an IsReconnectionEnabled option (default: false) so callers can opt in to automatic reconnection. All timeout properties are configurable with sensible defaults.

2.45.1

(fix): Fix sticky compression on reconnect. DeflateOptions is now explicitly reset to null when EnableCompression is false, preventing a previous non-null value from persisting across connection cycles.

2.45.0

(feat): Add InjectTestMessage internal method to generated WebSocket client classes. This allows unit tests to simulate incoming WebSocket messages by injecting raw JSON strings through the normal OnTextMessage pipeline without requiring a real WebSocket connection. The method is marked internal so it is only accessible via [InternalsVisibleTo] in test projects, keeping the public API surface clean.

2.44.0

(feat): Add queued sending via Channel<T> to the WebSocket connection layer. Two unbounded channels (text and binary) are drained by dedicated background tasks started alongside the connection. The non-blocking Send(string) and Send(byte[]) methods return bool indicating whether the message was queued, while the existing SendInstant async-per-send path remains unchanged. Queues are completed in Dispose before cancellation tokens are signalled. Drain loop errors are surfaced through the ExceptionOccurred event.

2.43.0

(feat): Add per-message deflate compression support (RFC 7692) for WebSocket connections. A new EnableCompression option on the generated WebSocket Options class enables WebSocketDeflateOptions via ClientWebSocketOptions.DangerousDeflateOptions. This can significantly reduce bandwidth for text-heavy (JSON) WebSocket APIs. Warning: Do not use compression when sending data containing secrets (CRIME/BREACH vulnerability). See https://learn.microsoft.com/dotnet/api/system.net.websockets.clientwebsocketoptions.dangerousdeflateoptions for details.

2.42.0

(feat): Add support for HTTP/2 WebSockets via SocketsHttpHandler. A new HttpInvoker property on the WebSocket Options class allows passing an HttpMessageInvoker to enable multiplexing multiple WebSocket streams over a single TCP connection. When set, the connection factory configures HttpVersion.Version20 and uses the ConnectAsync(Uri, HttpMessageInvoker, CancellationToken) overload on .NET 7+. On older targets the invoker is ignored and the standard connect path is used.

2.41.0

(feat): Add ConnectTimeout property to WebSocketConnection and WebSocketClient with a default of 5 seconds. The connection factory call in StartClient now uses a linked CancellationTokenSource with CancelAfter(ConnectTimeout), preventing indefinite hangs when the server is unresponsive.

2.40.0

(feat): Configure PING/PONG keep-alive on ClientWebSocket. The default connection factory now sets KeepAliveInterval (default: 30 s) and, on .NET 9+, KeepAliveTimeout (default: 20 s) to enable the PING/PONG keep-alive strategy that detects unresponsive servers at the transport level. Both values are exposed as public properties on WebSocketConnection for caller customization.

2.38.2

(fix): Add using declaration to JsonDocument in WebSocket OnTextMessage handler. JsonDocument implements IDisposable and holds pooled memory buffers; without disposal, high message throughput causes memory pressure. The generated code now emits using var json = await JsonSerializer.DeserializeAsync<JsonDocument>(stream); so the document is disposed when the method returns.