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
4 changes: 4 additions & 0 deletions packages/platform-socket.io/adapters/io-adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,10 @@ export class IoAdapter extends AbstractWsAdapter {
return { data: payload };
}

public bindClientDisconnect(client: Socket, callback: Function) {
client.on(DISCONNECT_EVENT, (reason: string) => callback(reason));
}

public async close(server: Server): Promise<void> {
if (this.forceCloseConnections && server.httpServer === this.httpServer) {
return;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@
* @publicApi
*/
export interface OnGatewayDisconnect<T = any> {
handleDisconnect(client: T): any;
handleDisconnect(client: T, reason?: string): any;
}
2 changes: 1 addition & 1 deletion packages/websockets/interfaces/nest-gateway.interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@
export interface NestGateway {
afterInit?: (server: any) => void;
handleConnection?: (...args: any[]) => void;
handleDisconnect?: (client: any) => void;
handleDisconnect?: (client: any, reason?: string) => void;
}
64 changes: 64 additions & 0 deletions packages/websockets/test/web-sockets-controller.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -412,6 +412,70 @@ describe('WebSocketsController', () => {
instance.subscribeDisconnectEvent(gateway, event);
expect(subscribe.called).to.be.true;
});

describe('when handling disconnect events', () => {
let handleDisconnectSpy: sinon.SinonSpy;

beforeEach(() => {
handleDisconnectSpy = sinon.spy();
(gateway as any).handleDisconnect = handleDisconnectSpy;
});

it('should call handleDisconnect with client and reason when data contains both', () => {
const mockClient = { id: 'test-client' };
const mockReason = 'client namespace disconnect';
const disconnectData = { client: mockClient, reason: mockReason };

let subscriptionCallback: Function | undefined;
event.subscribe = (callback: Function) => {
subscriptionCallback = callback;
};

instance.subscribeDisconnectEvent(gateway, event);

if (subscriptionCallback) {
subscriptionCallback(disconnectData);
}

expect(handleDisconnectSpy.calledOnce).to.be.true;
expect(handleDisconnectSpy.calledWith(mockClient, mockReason)).to.be
.true;
});

it('should call handleDisconnect with only client for backward compatibility', () => {
const mockClient = { id: 'test-client' };

let subscriptionCallback: Function | undefined;
event.subscribe = (callback: Function) => {
subscriptionCallback = callback;
};

instance.subscribeDisconnectEvent(gateway, event);

if (subscriptionCallback) {
subscriptionCallback(mockClient);
}

expect(handleDisconnectSpy.calledOnce).to.be.true;
expect(handleDisconnectSpy.calledWith(mockClient)).to.be.true;
});

it('should handle null/undefined data gracefully', () => {
let subscriptionCallback: Function | undefined;
event.subscribe = (callback: Function) => {
subscriptionCallback = callback;
};

instance.subscribeDisconnectEvent(gateway, event);

if (subscriptionCallback) {
subscriptionCallback(null);
}

expect(handleDisconnectSpy.calledOnce).to.be.true;
expect(handleDisconnectSpy.calledWith(null)).to.be.true;
});
});
});
describe('subscribeMessages', () => {
const gateway = new Test();
Expand Down
21 changes: 18 additions & 3 deletions packages/websockets/web-sockets-controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,9 @@ export class WebSocketsController {

const disconnectHook = adapter.bindClientDisconnect;
disconnectHook &&
disconnectHook.call(adapter, client, () => disconnect.next(client));
disconnectHook.call(adapter, client, (reason?: string) =>
disconnect.next({ client, reason }),
);
};
}

Expand All @@ -163,8 +165,21 @@ export class WebSocketsController {
public subscribeDisconnectEvent(instance: NestGateway, event: Subject<any>) {
if (instance.handleDisconnect) {
event
.pipe(distinctUntilChanged())
.subscribe(instance.handleDisconnect.bind(instance));
.pipe(
distinctUntilChanged((prev, curr) => {
const prevClient = prev?.client || prev;
const currClient = curr?.client || curr;
return prevClient === currClient;
}),
)
.subscribe((data: any) => {
if (data && typeof data === 'object' && 'client' in data) {
instance.handleDisconnect!(data.client, data.reason);
} else {
// Backward compatibility: if it's just the client
instance.handleDisconnect!(data);
}
});
}
}

Expand Down