Skip to content

Conversation

snowykte0426
Copy link

@snowykte0426 snowykte0426 commented Jul 24, 2025

PR Checklist

Please check if your PR fulfills the following requirements:

PR Type

What kind of change does this PR introduce?

  • Bugfix
  • Feature
  • Code style update (formatting, local variables)
  • Refactoring (no functional changes, no api changes)
  • Build related changes
  • CI related changes
  • Other... Please describe:

What is the current behavior?

Currently, when a WebSocket client disconnects, the handleDisconnect
method only receives the client object without any information about why
the disconnection occurred. This makes it difficult for developers to
implement different handling logic based on the disconnect reason (e.g.,
client-initiated vs. network error vs. timeout).

Issue Number: #15437

What is the new behavior?

This PR enhances the WebSocket disconnect handling by providing the
disconnect reason as an optional second parameter to the handleDisconnect
method.

Changes:

  • OnGatewayDisconnect Interface: Added optional reason?: string parameter
    to handleDisconnect method
  • NestGateway Interface: Updated to support the new disconnect reason
    parameter
  • WebSocketsController: Modified to capture and forward disconnect reason
    from events
  • IoAdapter: Enhanced to extract disconnect reason from Socket.IO
    disconnect events

Usage:

  Before (still works):
  @WebSocketGateway()
  export class ChatGateway implements OnGatewayDisconnect {
    handleDisconnect(client: Socket) {
      console.log('Client disconnected:', client.id);
    }
  }

After (new feature):

  @WebSocketGateway()
  export class ChatGateway implements OnGatewayDisconnect {
    handleDisconnect(client: Socket, reason?: string) {
      console.log('Client disconnected:', client.id, 'Reason:', reason);

      // Handle different disconnect scenarios
      switch (reason) {
        case 'client namespace disconnect':
          // Client explicitly disconnected
          break;
        case 'transport close':
          // Connection lost due to network issues
          break;
        case 'ping timeout':
          // Client didn't respond to ping
          break;
      }
    }
  }

Common Disconnect Reasons:

  • 'client namespace disconnect' - Client explicitly called disconnect()
  • 'server namespace disconnect'- Server forced disconnection
  • 'transport close'- Underlying transport was closed
  • 'transport error' - Transport encountered an error
  • 'ping timeout' - Client didn't respond to ping in time

Does this PR introduce a breaking change?

  • Yes
  • No

Other information

Implementation Details:

The implementation modifies the following core components:

  1. Interface Updates: Both OnGatewayDisconnect and NestGateway interfaces
    now support the optional reason parameter
  2. Event Flow: The disconnect reason flows from Socket.IO → IoAdapter →
    WebSocketsController → Gateway
  3. Backward Compatibility: The implementation checks for data format and
    handles both old and new formats seamlessly
  4. Fix Applied: Corrected distinctUntilChanged() operator behavior to
    properly handle both old format (just client) and new format ({ client, reason }) for disconnect events

@coveralls
Copy link

coveralls commented Jul 24, 2025

Pull Request Test Coverage Report for Build ccc32411-c048-48b5-946a-1ddf86b11307

Details

  • 3 of 7 (42.86%) changed or added relevant lines in 1 file are covered.
  • No unchanged relevant lines lost coverage.
  • Overall coverage decreased (-0.03%) to 88.885%

Changes Missing Coverage Covered Lines Changed/Added Lines %
packages/websockets/web-sockets-controller.ts 3 7 42.86%
Totals Coverage Status
Change from base Build a2f4d25f-1d25-415f-b2c5-967298b635ae: -0.03%
Covered Lines: 7285
Relevant Lines: 8196

💛 - Coveralls

This change enhances the WebSocket disconnect handling by providing
the disconnect reason as an optional second parameter to the
handleDisconnect method.

Changes:
- Add optional reason parameter to OnGatewayDisconnect interface
- Update NestGateway interface to support disconnect reason
- Modify WebSocketsController to capture and forward disconnect reason
- Enhance IoAdapter to extract reason from Socket.IO disconnect events
- Maintain full backward compatibility with existing implementations
- Add comprehensive unit and integration tests

The disconnect reason helps developers understand why clients disconnect,
enabling better error handling and debugging. Common reasons include
'client namespace disconnect', 'transport close', 'ping timeout', etc.

This change is fully backward compatible - existing code continues to
work without modification while new code can optionally access the
disconnect reason.

Closes nestjs#15437

Signed-off-by: snowykte0426 <[email protected]>
Comment on lines 38 to 43
const filters = this.filters.filter(
filter => filter.exceptionMetatypes?.length === 0,
);
if (filters.length > 0) {
return filters[0].func(exception, host);
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why(?)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change prioritizes global exception filters (i.e., filters with empty exceptionMetatypes) over more specific ones. Previously, global filters that were intended to catch all exceptions could be skipped because they were processed after specific filters.

The updated logic ensures that if a filter is designed to handle all exception types (as indicated by an empty exceptionMetatypes array), it is invoked first, preserving the intended filter hierarchy and behavior.

Please let me know if I’ve misunderstood any part of this change.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Previously, global filters that were intended to catch all exceptions could be skipped because they were processed after specific filters.

That's the expected behavior.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the clarification! I've removed the exception filter changes
in commit 6cf7ad5 since the current behavior is indeed the intended
behavior.

event
.pipe(distinctUntilChanged())
.subscribe(instance.handleDisconnect.bind(instance));
event.pipe(distinctUntilChanged()).subscribe((data: any) => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dont think distinctUntilChanged will work as expected now

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You’re right — thank you for pointing that out.
Since we’re now emitting objects like { client, reason } instead of just the client, distinctUntilChanged() was comparing object references, which caused the deduplication to fail.

I’ve updated the code to include a custom comparison function for distinctUntilChanged(). This handles both the previous format (just client) and the new one ({ client, reason }) by extracting and comparing the actual client values.

Really appreciate you catching this.

be6b50c

Fix the distinctUntilChanged operator in subscribeDisconnectEvent to properly
compare client objects when using the new { client, reason } format. The
previous implementation would not deduplicate correctly as it compared object
references instead of the actual client instances.

This ensures backward compatibility while properly handling both the old
format (just client) and new format ({ client, reason }) for disconnect events.

Signed-off-by: snowykte0426 <[email protected]>
Remove the changes to exception filter handling in RPC exceptions handler
as the current behavior is the intended behavior according to maintainer
feedback.

Signed-off-by: snowykte0426 <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants