Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: WS ping interval automatically stop and start #209

Merged
merged 5 commits into from
Nov 13, 2023
Merged
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: 1 addition & 3 deletions backend/controllers/assembly.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import { User } from "../models/user";
import { Votation, Option } from "../models/vote";
import { RequestWithNtnuiNo } from "../utils/request";
import { AssemblyResponseType } from "../types/assembly";
import { organizerConnections } from "../utils/socketNotifier";

export async function createAssembly(req: RequestWithNtnuiNo, res: Response) {
if (!req.ntnuiNo) {
Expand Down Expand Up @@ -112,8 +111,7 @@ export async function deleteAssembly(req: RequestWithNtnuiNo, res: Response) {
});

await Assembly.deleteOne({ _id: assembly._id });
// Clean up websocket connections for this assembly
organizerConnections.delete(group);

return res.status(200).json({ message: "Assembly successfully deleted" });
}
}
Expand Down
7 changes: 0 additions & 7 deletions backend/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import votationRoutes from "./routes/votation";
import { parse } from "url";
import { lobbyWss } from "./wsServers/lobby";
import { organizerWss } from "./wsServers/organizer";
import { startHeartbeatInterval } from "./utils/socketNotifier";

dotenv.config();

Expand Down Expand Up @@ -64,12 +63,6 @@ server.on("upgrade", function upgrade(request, socket, head) {
}
});

// Start sending pings/Heartbeat to ws-connections to keep connections alive.
// Not started when testing, as Jest will not stop properly if the interval is running.
if (process.env.NODE_ENV !== "test") {
startHeartbeatInterval;
}

try {
// Jest will start app itself when testing, and not run on port 3000 to avoid collisions.
if (process.env.NODE_ENV !== "test") {
Expand Down
63 changes: 40 additions & 23 deletions backend/utils/socketNotifier.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,12 @@ import { NTNUINoFromRequest } from "./wsCookieRetriever";
// Store all active organizer connections, the connections are stored by their respective groupSlug.
// This makes it possible to send messages to all logged in organizers of a group.
// An organizer can be logged in on multiple devices, and multiple organizers can receive events from the same group.
export const organizerConnections = new Map<string, WebSocket[]>();
const organizerConnections = new Map<string, WebSocket[]>();
// Store all active lobby connections, for access when sending messages to assembly participants.
// Maximum one lobby connection per user.
export const lobbyConnections = new Map<number, WebSocket>();
const lobbyConnections = new Map<number, WebSocket>();

let heartBeatInterval: NodeJS.Timeout | undefined;

const sendPing = (ws: WebSocket) => {
if (ws.readyState === WebSocket.OPEN) {
Expand All @@ -25,27 +27,39 @@ const sendPing = (ws: WebSocket) => {
};

// Send ping to all participants to check if they are still connected and prevent the connection from closing.
export const startHeartbeatInterval = setInterval(() => {
lobbyConnections.forEach((ws: WebSocket, userID: number) => {
// Remove connection if it is closed by the client.
if (ws.readyState === WebSocket.CLOSED) {
lobbyConnections.delete(userID);
return;
}
sendPing(ws);
});

organizerConnections.forEach((socketList) => {
socketList.forEach((ws: WebSocket) => {
const startHeartbeatInterval = () => {
heartBeatInterval = setInterval(() => {
lobbyConnections.forEach((ws: WebSocket, userID: number) => {
// Remove connection if it is closed by the client.
if (ws.readyState === WebSocket.CLOSED) {
socketList.splice(socketList.indexOf(ws), 1);
lobbyConnections.delete(userID);
return;
}
sendPing(ws);
});
});
}, 30000); // 30 seconds

organizerConnections.forEach((socketList, key) => {
socketList.forEach((ws: WebSocket) => {
// Remove connection if it is closed by the client.
if (ws.readyState === WebSocket.CLOSED) {
socketList.splice(socketList.indexOf(ws), 1);
if (socketList.length === 0) {
// Remove map of group if there are no more connections for that group.
organizerConnections.delete(key);
}
return;
}
sendPing(ws);
});
});

// Stop sending pings if there are no connections.
if (organizerConnections.size === 0 && lobbyConnections.size === 0) {
clearInterval(heartBeatInterval);
heartBeatInterval = undefined;
}
}, 30000); // 30 seconds
};

export const storeLobbyConnectionByCookie = (
ws: WebSocket,
Expand All @@ -61,13 +75,11 @@ export const storeLobbyConnectionByCookie = (
// Store socket connection on NTNUI ID.
lobbyConnections.set(ntnuiNo, ws);
console.log("User " + ntnuiNo + " connected to lobby");
}
};

export const removeLobbyConnectionByCookie = (req: IncomingMessage) => {
const ntnuiNo = NTNUINoFromRequest(req);
if (ntnuiNo !== null) {
lobbyConnections.delete(ntnuiNo);
// Start sending pings when a new connection appears if it is not already doing so.
if (!heartBeatInterval) {
startHeartbeatInterval();
}
}
};

Expand All @@ -76,6 +88,11 @@ export const storeOrganizerConnection = (groupSlug: string, ws: WebSocket) => {
organizerConnections.set(groupSlug, [ws]);
}
organizerConnections.get(groupSlug)?.push(ws);

if (!heartBeatInterval) {
// Start sending pings when a new connection appears if it is not already doing so.
startHeartbeatInterval();
}
};

export const notifyOneParticipant = (ntnui_no: number, message: string) => {
Expand Down
5 changes: 5 additions & 0 deletions frontend/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="src/assets/ntnui.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<script
defer
data-domain="vote.ntnui.no"
src="https://analytics.ntnui.no/js/script.js"
></script>
<title>NTNUI Vote</title>
</head>
<body>
Expand Down
Loading