@@ -110,7 +110,7 @@ public class CentrifugeClient : IDisposable, IAsyncDisposable
110110 public event EventHandler < CentrifugeDisconnectedEventArgs > ? Disconnected ;
111111
112112 /// <summary>
113- /// Event raised when an error occurs.
113+ /// Event raised when an error occurs. Mostly for the logging purposes.
114114 /// </summary>
115115 public event EventHandler < CentrifugeErrorEventArgs > ? Error ;
116116
@@ -814,7 +814,16 @@ private async Task StartConnectingAsync(int code, string reason)
814814
815815 private async Task CreateTransportAsync ( )
816816 {
817- _transport ? . Dispose ( ) ;
817+ // Unsubscribe from old transport events before disposing to prevent race conditions
818+ if ( _transport != null )
819+ {
820+ _transport . Opened -= OnTransportOpened ;
821+ _transport . MessageReceived -= OnTransportMessage ;
822+ _transport . Closed -= OnTransportClosed ;
823+ _transport . Error -= OnTransportError ;
824+ _transport . Dispose ( ) ;
825+ _transport = null ;
826+ }
818827
819828 ITransport transport ;
820829
@@ -1489,7 +1498,7 @@ private async Task HandleTransportClosedAsync(TransportClosedEventArgs e)
14891498 string reason = e . Reason ;
14901499 _logger ? . LogDebug ( $ "HandleTransportClosedAsync - processing with code: { code } , reason: '{ reason } '") ;
14911500
1492- // Check for non-reconnectable disconnect codes (matching centrifuge-js behavior)
1501+ // Check for non-reconnectable disconnect codes
14931502 // Codes 3500-3999 and 4500-4999 mean permanent disconnect
14941503 // Also BadProtocol, Unauthorized, and MessageSizeLimit are permanent
14951504 if ( e . Code . HasValue )
@@ -1537,7 +1546,6 @@ private async Task HandleTransportClosedAsync(TransportClosedEventArgs e)
15371546 SetState ( CentrifugeClientState . Connecting ) ;
15381547
15391548 // Move all subscribed subscriptions to subscribing state BEFORE emitting connecting event
1540- // (matching centrifuge-js behavior - see _disconnect method)
15411549 foreach ( var sub in _subscriptions . Values )
15421550 {
15431551 sub . MoveToSubscribing ( CentrifugeSubscribingCodes . TransportClosed , "transport closed" ) ;
@@ -1564,7 +1572,7 @@ private void OnTransportError(object? sender, Exception e)
15641572
15651573 internal async Task HandleSubscribeTimeoutAsync ( )
15661574 {
1567- // Subscribe timeout triggers client disconnect with reconnect, matching centrifuge-js behavior
1575+ // Subscribe timeout triggers client disconnect with reconnect
15681576 if ( _state == CentrifugeClientState . Disconnected )
15691577 {
15701578 return ;
@@ -1578,6 +1586,22 @@ internal async Task HandleSubscribeTimeoutAsync()
15781586 await ScheduleReconnectAsync ( ) . ConfigureAwait ( false ) ;
15791587 }
15801588
1589+ internal async Task HandleUnsubscribeErrorAsync ( )
1590+ {
1591+ // Unsubscribe error triggers client disconnect with reconnect (matching centrifuge-js behavior)
1592+ if ( _state == CentrifugeClientState . Disconnected )
1593+ {
1594+ return ;
1595+ }
1596+
1597+ await StartConnectingAsync ( CentrifugeConnectingCodes . UnsubscribeError , "unsubscribe error" ) . ConfigureAwait ( false ) ;
1598+
1599+ // Properly clean up transport before reconnecting
1600+ await CleanupTransportAsync ( ) . ConfigureAwait ( false ) ;
1601+
1602+ await ScheduleReconnectAsync ( ) . ConfigureAwait ( false ) ;
1603+ }
1604+
15811605 /// <summary>
15821606 /// Cleans up the current transport (unsubscribes events, closes, disposes).
15831607 /// This should be called before reconnecting to prevent duplicate connections.
0 commit comments