Skip to content

Commit 64cbf1c

Browse files
authored
feat: disconnecting event support (#537)
1 parent 2784018 commit 64cbf1c

File tree

5 files changed

+134
-21
lines changed

5 files changed

+134
-21
lines changed

README.md

Lines changed: 28 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -90,13 +90,14 @@ Use class-based controllers to handle websocket events. Helps to organize your c
9090

9191
## More usage examples
9292

93-
#### Run code on socket client connect / disconnect
93+
#### Run code on socket client connect / disconnect / disconnecting
9494

9595
Controller action marked with `@OnConnect()` decorator is called once new client connected.
9696
Controller action marked with `@OnDisconnect()` decorator is called once client disconnected.
97+
Controller action marked with `@OnDisconnecting()` decorator is called when the client is disconnecting, before the disconnect event.
9798

9899
```typescript
99-
import { SocketController, OnConnect, OnDisconnect } from 'socket-controllers';
100+
import { SocketController, OnConnect, OnDisconnect, OnDisconnecting } from 'socket-controllers';
100101

101102
@SocketController()
102103
export class MessageController {
@@ -109,6 +110,11 @@ export class MessageController {
109110
save() {
110111
console.log('client disconnected');
111112
}
113+
114+
@OnDisconnecting()
115+
save() {
116+
console.log('client is disconnecting');
117+
}
112118
}
113119
```
114120

@@ -440,25 +446,26 @@ export class MessageController {
440446
441447
## Decorators Reference
442448

443-
| Signature | Description |
444-
| ---------------------------------------------- | -----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
445-
| `@SocketController(namespace?: string\|Regex)` | Registers a class to be a socket controller that can listen to websocket events and respond to them. |
446-
| `@OnMessage(messageName: string)` | Registers controller's action to be executed when socket receives message with given name. |
447-
| `@OnConnect()` | Registers controller's action to be executed when client connects to the socket. |
448-
| `@OnDisconnect()` | Registers controller's action to be executed when client disconnects from the socket. |
449-
| `@ConnectedSocket()` | Injects connected client's socket object to the controller action. |
450-
| `@SocketIO()` | Injects socket.io object that initialized a connection. |
451-
| `@MessageBody()` | Injects received message body. |
452-
| `@SocketQueryParam(paramName: string)` | Injects query parameter from the received socket request. |
453-
| `@SocketId()` | Injects socket id from the received request. |
454-
| `@SocketRequest()` | Injects request object received by socket. |
455-
| `@SocketRooms()` | Injects rooms of the connected socket client. |
456-
| `@NspParams()` | Injects dynamic namespace params. |
457-
| `@NspParam(paramName: string)` | Injects param from the dynamic namespace. |
458-
| `@Middleware()` | Registers a new middleware to be registered in the socket.io. |
459-
| `@EmitOnSuccess(messageName: string)` | If this decorator is set then after controller action will emit message with the given name after action execution. It will emit message only if controller succeed without errors. If result is a Promise then it will wait until promise is resolved and emit a message. |
460-
| `@EmitOnFail(messageName: string)` | If this decorator is set then after controller action will emit message with the given name after action execution. It will emit message only if controller throw an exception. If result is a Promise then it will wait until promise throw an error and emit a message. |
461-
| `@SkipEmitOnEmptyResult()` | Used in conjunction with @EmitOnSuccess and @EmitOnFail decorators. If result returned by controller action is null or undefined then messages will not be emitted by @EmitOnSuccess or @EmitOnFail decorators. | |
449+
| Signature | Description |
450+
|----------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
451+
| `@SocketController(namespace?: string\ | Regex)` | Registers a class to be a socket controller that can listen to websocket events and respond to them. |
452+
| `@OnMessage(messageName: string)` | Registers controller's action to be executed when socket receives message with given name. |
453+
| `@OnConnect()` | Registers controller's action to be executed when client connects to the socket. |
454+
| `@OnDisconnect()` | Registers controller's action to be executed when client disconnects from the socket. |
455+
| `@OnDisconnecting()` | Registers controller's action to be executed when client is disconnecting from the socket. |
456+
| `@ConnectedSocket()` | Injects connected client's socket object to the controller action. |
457+
| `@SocketIO()` | Injects socket.io object that initialized a connection. |
458+
| `@MessageBody()` | Injects received message body. |
459+
| `@SocketQueryParam(paramName: string)` | Injects query parameter from the received socket request. |
460+
| `@SocketId()` | Injects socket id from the received request. |
461+
| `@SocketRequest()` | Injects request object received by socket. |
462+
| `@SocketRooms()` | Injects rooms of the connected socket client. |
463+
| `@NspParams()` | Injects dynamic namespace params. |
464+
| `@NspParam(paramName: string)` | Injects param from the dynamic namespace. |
465+
| `@Middleware()` | Registers a new middleware to be registered in the socket.io. |
466+
| `@EmitOnSuccess(messageName: string)` | If this decorator is set then after controller action will emit message with the given name after action execution. It will emit message only if controller succeed without errors. If result is a Promise then it will wait until promise is resolved and emit a message. |
467+
| `@EmitOnFail(messageName: string)` | If this decorator is set then after controller action will emit message with the given name after action execution. It will emit message only if controller throw an exception. If result is a Promise then it will wait until promise throw an error and emit a message. |
468+
| `@SkipEmitOnEmptyResult()` | Used in conjunction with @EmitOnSuccess and @EmitOnFail decorators. If result returned by controller action is null or undefined then messages will not be emitted by @EmitOnSuccess or @EmitOnFail decorators. | |
462469

463470
## Samples
464471

src/SocketControllers.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,9 @@ export class SocketControllers {
163163
const disconnectedAction = Object.values(controller.metadata.actions || {}).find(
164164
action => action.type === ActionType.DISCONNECT
165165
);
166+
const disconnectingAction = Object.values(controller.metadata.actions || {}).find(
167+
action => action.type === ActionType.DISCONNECTING
168+
);
166169
const messageActions = Object.values(controller.metadata.actions || {}).filter(
167170
action => action.type === ActionType.MESSAGE
168171
);
@@ -177,6 +180,12 @@ export class SocketControllers {
177180
});
178181
}
179182

183+
if (disconnectingAction) {
184+
socket.on('disconnecting', () => {
185+
this.executeAction(socket, controller, disconnectingAction);
186+
});
187+
}
188+
180189
for (const messageAction of messageActions) {
181190
socket.on(messageAction.options.name, (message: any) => {
182191
this.executeAction(socket, controller, messageAction, message);

src/decorators/OnDisconnecting.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { addActionToControllerMetadata } from '../util/add-action-to-controller-metadata';
2+
import { ActionType } from '../types/enums/ActionType';
3+
4+
export function OnDisconnecting(): Function {
5+
return function (object: Object, methodName: string) {
6+
addActionToControllerMetadata(object.constructor, {
7+
methodName,
8+
type: ActionType.DISCONNECTING,
9+
options: {},
10+
});
11+
};
12+
}

src/types/enums/ActionType.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,5 @@ export enum ActionType {
22
MESSAGE,
33
CONNECT,
44
DISCONNECT,
5+
DISCONNECTING,
56
}
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
import { createServer, Server as HttpServer } from 'http';
2+
import { Server } from 'socket.io';
3+
import { io, Socket } from 'socket.io-client';
4+
import { SocketControllers } from '../../src/SocketControllers';
5+
import { Container, Service } from 'typedi';
6+
import { SocketController } from '../../src/decorators/SocketController';
7+
import { OnConnect } from '../../src/decorators/OnConnect';
8+
import { ConnectedSocket } from '../../src/decorators/ConnectedSocket';
9+
import { waitForEvent } from '../utilities/waitForEvent';
10+
import { OnDisconnect } from '../../src';
11+
import { waitForTime } from '../utilities/waitForTime';
12+
import { OnDisconnecting } from '../../src/decorators/OnDisconnecting';
13+
14+
describe('OnDisconnecting', () => {
15+
const PORT = 8080;
16+
const PATH_FOR_CLIENT = `ws://localhost:${PORT}`;
17+
18+
let httpServer: HttpServer;
19+
let wsApp: Server;
20+
let wsClient: Socket;
21+
let testResult = [];
22+
let socketControllers: SocketControllers;
23+
24+
beforeEach(done => {
25+
httpServer = createServer();
26+
wsApp = new Server(httpServer, {
27+
cors: {
28+
origin: '*',
29+
},
30+
});
31+
httpServer.listen(PORT, () => {
32+
done();
33+
});
34+
});
35+
36+
afterEach(() => {
37+
testResult = [];
38+
39+
Container.reset();
40+
wsClient.close();
41+
wsClient = null;
42+
socketControllers = null;
43+
return new Promise(resolve => {
44+
if (wsApp)
45+
return wsApp.close(() => {
46+
resolve(null);
47+
});
48+
resolve(null);
49+
});
50+
});
51+
52+
it('OnDisconnect is called correctly', async () => {
53+
@SocketController('/string')
54+
@Service()
55+
class TestController {
56+
@OnConnect()
57+
connected(@ConnectedSocket() socket: Socket) {
58+
socket.emit('connected');
59+
}
60+
61+
@OnDisconnect()
62+
disconnected() {
63+
testResult.push('disconnected');
64+
}
65+
66+
@OnDisconnecting()
67+
disconnecting() {
68+
testResult.push('disconnecting');
69+
}
70+
}
71+
72+
socketControllers = new SocketControllers({
73+
io: wsApp,
74+
container: Container,
75+
controllers: [TestController],
76+
});
77+
wsClient = io(PATH_FOR_CLIENT + '/string', { reconnection: false, timeout: 5000, forceNew: true });
78+
79+
await waitForEvent(wsClient, 'connected');
80+
wsClient.disconnect();
81+
await waitForTime(1000);
82+
expect(testResult).toEqual(['disconnecting', 'disconnected']);
83+
});
84+
});

0 commit comments

Comments
 (0)