Skip to content

Commit 57db9ea

Browse files
committed
refactor: convert wsServer to class
1 parent 6fb78b7 commit 57db9ea

File tree

9 files changed

+160
-113
lines changed

9 files changed

+160
-113
lines changed

packages/backend/src/app.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,16 +13,16 @@ import * as http from "http";
1313
import { Logger } from "@core/logger/winston.logger";
1414

1515
import { ENV } from "./common/constants/env.constants";
16-
import { initWebsocketServer } from "./servers/websocket/websocket.server";
1716
import { initExpressServer } from "./servers/express/express.server";
1817
import mongoService from "./common/services/mongo.service";
18+
import { webSocketServer } from "./servers/websocket/websocket.server";
1919

2020
const logger = Logger("app:root");
2121
mongoService;
2222

2323
const app = initExpressServer();
2424
const httpServer: http.Server = http.createServer(app);
25-
initWebsocketServer(httpServer);
25+
webSocketServer.init(httpServer);
2626

2727
const port = ENV.PORT;
2828
httpServer.listen(port, () => {

packages/backend/src/common/constants/error.constants.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,12 @@ export const SocketError = {
129129
status: Status.BAD_REQUEST,
130130
isOperational: true,
131131
},
132+
ServerNotReady: {
133+
description:
134+
"WebSocket server not ready (Did you forget to initialize it?)",
135+
status: Status.INTERNAL_SERVER,
136+
isOperational: false,
137+
},
132138
};
133139

134140
export const SyncError = {

packages/backend/src/servers/websocket/websocket.server.errors.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ import { io as ioc, type Socket as ClientSocket } from "socket.io-client";
44
import { Server as IoServer } from "socket.io";
55
import { SocketError } from "@backend/common/constants/error.constants";
66

7-
import { initWebsocketServer } from "./websocket.server";
87
import { getServerUri } from "./websocket.util";
8+
import { WebSocketServer } from "./websocket.server";
99

1010
describe("WebSocket Server: Error Handling", () => {
1111
let httpServer: HttpServer;
@@ -15,7 +15,7 @@ describe("WebSocket Server: Error Handling", () => {
1515

1616
beforeAll((done) => {
1717
httpServer = createServer();
18-
wsServer = initWebsocketServer(httpServer);
18+
wsServer = new WebSocketServer().init(httpServer);
1919
httpServer.listen(() => {
2020
serverUri = getServerUri(httpServer);
2121
done();

packages/backend/src/servers/websocket/websocket.server.test.ts

Lines changed: 17 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -8,39 +8,40 @@ import {
88
EVENT_CHANGED,
99
} from "@core/constants/websocket.constants";
1010

11-
import { initWebsocketServer } from "./websocket.server";
1211
import { getServerUri } from "./websocket.util";
12+
import { WebSocketServer } from "./websocket.server";
1313

1414
describe("WebSocket Server", () => {
1515
let httpServer: HttpServer;
16-
let io: IoServer;
17-
let serverSocket: ServerSocket;
18-
let clientSocket: ClientSocket;
16+
let wsServer: IoServer;
17+
let client: ClientSocket;
18+
let socket: ServerSocket;
1919

2020
beforeAll((done) => {
2121
httpServer = createServer();
22-
io = initWebsocketServer(httpServer);
22+
wsServer = new WebSocketServer().init(httpServer);
2323
httpServer.listen(() => {
2424
const uri = getServerUri(httpServer);
2525
const userId = "testUser123";
2626

27-
clientSocket = ioc(uri, {
27+
client = ioc(uri, {
2828
query: { userId },
2929
});
3030

31-
io.on("connection", (socket) => {
32-
serverSocket = socket;
31+
wsServer.on("connection", (_socket) => {
32+
socket = _socket;
3333
});
3434

35-
clientSocket.on("connect", done);
35+
client.on("connect", done);
3636
});
3737
});
3838

3939
afterAll((done) => {
40-
io.close()
40+
wsServer
41+
.close()
4142
.then(() => httpServer.close())
4243
.then(() => {
43-
clientSocket.disconnect();
44+
client.disconnect();
4445
done();
4546
})
4647
.catch((err) => {
@@ -50,29 +51,27 @@ describe("WebSocket Server", () => {
5051
});
5152

5253
describe(EVENT_CHANGED, () => {
53-
it("emits event payload to client", (done) => {
54-
clientSocket.on(EVENT_CHANGED, (arg) => {
54+
it("emits eventpayload to client", (done) => {
55+
client.on(EVENT_CHANGED, (arg) => {
5556
expect(arg).toEqual({ _id: "1", title: "Test Event" });
5657
done();
5758
});
5859

5960
const event: Schema_Event = { _id: "1", title: "Test Event" };
60-
serverSocket.emit(EVENT_CHANGED, event);
61+
socket.emit(EVENT_CHANGED, event);
6162
});
62-
63-
it.todo("emits eventChanged after reciving updated event data from Gcal");
6463
});
6564

6665
describe(EVENT_CHANGE_PROCESSED, () => {
6766
it("accepts message from client after successful update", (done) => {
6867
const userId = "client123";
6968

70-
serverSocket.on(EVENT_CHANGE_PROCESSED, (receivedUserId) => {
69+
socket.on(EVENT_CHANGE_PROCESSED, (receivedUserId) => {
7170
expect(receivedUserId).toBe(userId);
7271
done();
7372
});
7473

75-
clientSocket.emit(EVENT_CHANGE_PROCESSED, userId);
74+
client.emit(EVENT_CHANGE_PROCESSED, userId);
7675
});
7776
});
7877
});

packages/backend/src/servers/websocket/websocket.server.ts

Lines changed: 90 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import {
1010
import {
1111
EVENT_CHANGE_PROCESSED,
1212
EVENT_CHANGED,
13+
RESULT_IGNORED,
14+
RESULT_NOTIFIED_CLIENT,
1315
} from "@core/constants/websocket.constants";
1416
import { Logger } from "@core/logger/winston.logger";
1517
import { SocketError } from "@backend/common/constants/error.constants";
@@ -20,79 +22,98 @@ import { handleWsError } from "./websocket.util";
2022

2123
const logger = Logger("app:websocket.server");
2224

23-
let wsServer: CompassSocketServer;
24-
export const connections = new Map<string, string>(); // { userId: socketId }
25+
export class WebSocketServer {
26+
private connections: Map<string, string>;
27+
private wsServer?: CompassSocketServer;
2528

26-
export const notifyClient = (userId: string, server?: CompassSocketServer) => {
27-
const socketServer = server || wsServer;
28-
const socketId = connections.get(userId);
29+
constructor() {
30+
this.connections = new Map<string, string>();
31+
}
32+
33+
public init(server: HttpServer) {
34+
this.wsServer = new SocketIOServer<
35+
ClientToServerEvents,
36+
ServerToClientEvents,
37+
InterServerEvents,
38+
SocketData
39+
>(server, {
40+
cors: {
41+
origin: ENV.ORIGINS_ALLOWED,
42+
credentials: true,
43+
},
44+
});
45+
46+
this.wsServer.use((socket, next) => {
47+
if (socket.handshake) {
48+
const userId = socket.handshake.query["userId"] as string;
49+
50+
if (!userId || userId === "undefined" || userId === "null") {
51+
const err = error(SocketError.InvalidSocketId, "Connection closed");
52+
logger.error("WebSocket Error:\n\t", err);
53+
return next(err);
54+
}
55+
}
2956

30-
if (!socketId) {
31-
logger.warn(
32-
`Event update not sent to client due to missing userId: ${userId}`
57+
next();
58+
});
59+
60+
this.wsServer.on(
61+
"connection",
62+
handleWsError((socket) => {
63+
const userId = socket.handshake.query["userId"] as SocketData["userId"];
64+
65+
logger.debug(`Connection made to: ${userId}`);
66+
this.connections.set(userId, socket.id);
67+
console.log(this.connections);
68+
69+
socket.on(
70+
"disconnect",
71+
handleWsError(() => {
72+
logger.debug(`Disconnecting from: ${userId}`);
73+
this.connections.delete(userId);
74+
})
75+
);
76+
77+
socket.on(
78+
EVENT_CHANGE_PROCESSED,
79+
handleWsError((clientId) => {
80+
logger.debug(`Client successfully processed updated: ${clientId}`);
81+
})
82+
);
83+
})
3384
);
34-
throw error(SocketError.InvalidSocketId, "Event update not sent to client");
85+
86+
this.wsServer.engine.on("connection_error", (err: Error) => {
87+
logger.error(`Connection error: ${err.message}`);
88+
});
89+
90+
return this.wsServer;
3591
}
3692

37-
socketServer.to(socketId).emit(EVENT_CHANGED);
38-
};
39-
40-
export const initWebsocketServer = (server: HttpServer) => {
41-
wsServer = new SocketIOServer<
42-
ClientToServerEvents,
43-
ServerToClientEvents,
44-
InterServerEvents,
45-
SocketData
46-
>(server, {
47-
cors: {
48-
origin: ENV.ORIGINS_ALLOWED,
49-
credentials: true,
50-
},
51-
});
52-
53-
wsServer.use((socket, next) => {
54-
if (socket.handshake) {
55-
const userId = socket.handshake.query["userId"] as string;
56-
57-
if (!userId || userId === "undefined" || userId === "null") {
58-
const err = error(SocketError.InvalidSocketId, "Connection closed");
59-
logger.error("WebSocket Error:\n\t", err);
60-
return next(err);
61-
}
93+
public handleBackgroundCalendarChange(userId: string) {
94+
const socketId = this.connections.get(userId);
95+
96+
const isClientConnected = socketId !== undefined;
97+
if (!isClientConnected) return RESULT_IGNORED;
98+
99+
this.notifyClient(socketId, EVENT_CHANGED);
100+
return RESULT_NOTIFIED_CLIENT;
101+
}
102+
103+
public addConnection(userId: string, socketId: string) {
104+
this.connections.set(userId, socketId);
105+
}
106+
107+
public getConnection(userId: string): string | undefined {
108+
return this.connections.get(userId);
109+
}
110+
111+
private notifyClient(socketId: string, event: keyof ServerToClientEvents) {
112+
if (this.wsServer === undefined) {
113+
throw error(SocketError.ServerNotReady, "Client not notified");
62114
}
115+
this.wsServer.to(socketId).emit(event);
116+
}
117+
}
63118

64-
next();
65-
});
66-
67-
wsServer.on(
68-
"connection",
69-
handleWsError((socket) => {
70-
const userId = socket.handshake.query["userId"] as SocketData["userId"];
71-
72-
logger.debug(`Connection made to: ${userId}`);
73-
connections.set(userId, socket.id);
74-
console.log(connections);
75-
76-
socket.on(
77-
"disconnect",
78-
handleWsError(() => {
79-
logger.debug(`Disconnecting from: ${userId}`);
80-
connections.delete(userId);
81-
})
82-
);
83-
84-
socket.on(
85-
EVENT_CHANGE_PROCESSED,
86-
handleWsError((clientId) => {
87-
logger.debug(`Client successfully processed updated: ${clientId}`);
88-
})
89-
);
90-
})
91-
);
92-
93-
wsServer.engine.on("connection_error", (err: Error) => {
94-
logger.error(`Connection error: ${err.message}`);
95-
});
96-
97-
return wsServer;
98-
};
119+
export const webSocketServer = new WebSocketServer();
Lines changed: 36 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,56 @@
11
import { Server as SocketIOServer } from "socket.io";
2+
import { createServer, Server as HttpServer } from "http";
23
import { CompassSocketServer } from "@core/types/websocket.types";
34
import { EVENT_CHANGED } from "@core/constants/websocket.constants";
4-
import { SocketError } from "@backend/common/constants/error.constants";
55

6-
import { connections, notifyClient } from "./websocket.server";
6+
import { WebSocketServer } from "./websocket.server";
77

88
jest.mock("socket.io");
99

10-
describe("emitEventToUser", () => {
11-
beforeEach(() => {
12-
(SocketIOServer as unknown as jest.Mock).mockClear();
13-
});
10+
describe("handleBackgroundCalendarChange", () => {
11+
let mockHttpServer: jest.Mocked<HttpServer>;
12+
let mockIo: jest.Mocked<CompassSocketServer>;
13+
let webSocketServer: WebSocketServer;
1414

15-
it("emits event to the correct socketId", () => {
16-
const userId = "existingUser";
17-
const socketId = "socket123";
18-
const mockIo = {
15+
beforeAll(() => {
16+
mockIo = {
1917
to: jest.fn().mockReturnThis(),
2018
emit: jest.fn(),
21-
};
22-
19+
use: jest.fn(),
20+
on: jest.fn(),
21+
engine: { on: jest.fn() },
22+
} as unknown as jest.Mocked<CompassSocketServer>;
2323
(SocketIOServer as unknown as jest.Mock).mockImplementation(() => mockIo);
2424

25-
connections.set(userId, socketId);
26-
notifyClient(userId, mockIo as unknown as CompassSocketServer);
25+
webSocketServer = new WebSocketServer();
2726

27+
mockHttpServer = createServer() as jest.Mocked<HttpServer>;
28+
webSocketServer.init(mockHttpServer);
29+
});
30+
31+
afterAll(() => {
32+
jest.clearAllMocks();
33+
});
34+
35+
it("emits event to the correct socketId", (): void => {
36+
const httpServer = createServer();
37+
webSocketServer.init(httpServer);
38+
39+
const userId = "existingUser";
40+
const socketId = "socket123";
41+
webSocketServer.addConnection(userId, socketId);
42+
webSocketServer.handleBackgroundCalendarChange(userId);
43+
44+
// eslint-disable-next-line @typescript-eslint/unbound-method
2845
expect(mockIo.to).toHaveBeenCalledWith(socketId);
46+
// eslint-disable-next-line @typescript-eslint/unbound-method
2947
expect(mockIo.emit).toHaveBeenCalledWith(EVENT_CHANGED);
3048
});
31-
it("throws error if socketId not found", () => {
49+
it("ignores change if no connection between client and ws server", () => {
3250
const userId = "nonexistentUser";
3351

34-
expect(() => notifyClient(userId)).toThrow(
35-
SocketError.InvalidSocketId.description
36-
);
52+
const result = webSocketServer.handleBackgroundCalendarChange(userId);
53+
54+
expect(result).toEqual("IGNORED");
3755
});
3856
});

0 commit comments

Comments
 (0)