Skip to content

Commit

Permalink
feat: add heartbeat to lobby ws
Browse files Browse the repository at this point in the history
  • Loading branch information
edalholt committed Sep 29, 2023
1 parent 5c06380 commit 3d940d5
Show file tree
Hide file tree
Showing 3 changed files with 49 additions and 14 deletions.
4 changes: 4 additions & 0 deletions backend/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ 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 @@ -63,6 +64,9 @@ server.on("upgrade", function upgrade(request, socket, head) {
}
});

// Start sending pings/Heartbeat to ws-connections to keep connections alive.
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
46 changes: 33 additions & 13 deletions backend/utils/socketNotifier.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,40 +17,60 @@ type OrganizersByGroupSlug = { [key: string]: SocketList };
// This makes it possible to send messages to all logged in organizers of a group.
export const organizerConnections: OrganizersByGroupSlug = {};
// Store all active participant connections, for access when sending messages about assembly.
export const lobbyConnections: SocketList = [];
export const lobbyConnections = new Map<number, WebSocket>();

export function storeLobbyConnectionByCookie(
const sendPing = (ws: WebSocket) => {
if (ws.readyState === WebSocket.OPEN) {
ws.ping();
}
};

// 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) => {
sendPing(ws);
});
}, 30000); // 30 seconds

export const storeLobbyConnectionByCookie = (
ws: WebSocket,
req: IncomingMessage
) {
) => {
const ntnuiNo = NTNUINoFromRequest(req);
if (ntnuiNo !== null) {
// Notify about kicking out old connection if user already is connected.
if (typeof lobbyConnections[ntnuiNo] !== null) {
if (typeof lobbyConnections.get(ntnuiNo) !== null) {
notifyOneParticipant(ntnuiNo, JSON.stringify({ status: "removed" }));
}
// Store socket connection on NTNUI ID.
lobbyConnections[ntnuiNo] = ws;
lobbyConnections.set(ntnuiNo, ws);
}
}
};

export function storeOrganizerConnectionByNTNUINo(
export const removeLobbyConnectionByCookie = (req: IncomingMessage) => {
const ntnuiNo = NTNUINoFromRequest(req);
if (ntnuiNo !== null) {
lobbyConnections.delete(ntnuiNo);
}
};

export const storeOrganizerConnectionByNTNUINo = (
ntnui_no: number,
groupSlug: string,
ws: WebSocket
) {
) => {
if (!organizerConnections[groupSlug]) organizerConnections[groupSlug] = [];
organizerConnections[groupSlug][ntnui_no] = ws;
}
};

export const notifyOneParticipant = (ntnui_no: number, message: string) => {
try {
lobbyConnections[ntnui_no].send(message);
} catch (error) {
const connection = lobbyConnections.get(ntnui_no);
if (connection) connection.send(message);
else {
console.log(
"Could not notify user " +
ntnui_no +
". Is there a problem with the socket URL? (Ignore if testing / dev has restarted)"
" (disconnected). Is there a problem with the socket URL? (Ignore if testing / dev has restarted)"
);
}
};
Expand Down
13 changes: 12 additions & 1 deletion backend/wsServers/lobby.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,20 @@
import { WebSocketServer } from "ws";
import { storeLobbyConnectionByCookie } from "../utils/socketNotifier";
import {
removeLobbyConnectionByCookie,
storeLobbyConnectionByCookie,
} from "../utils/socketNotifier";

export const lobbyWss = new WebSocketServer({ noServer: true });

lobbyWss.on("connection", function connection(ws, req) {
// Store connections to be able to send messages to specific users when needed.
storeLobbyConnectionByCookie(ws, req);

ws.on("pong", () => {
// The client responded to the ping, so the connection is still active.
});

ws.on("close", () => {
removeLobbyConnectionByCookie(req);
});
});

0 comments on commit 3d940d5

Please sign in to comment.