/* Created by Anne Lefebvre and Anuj Panthri - 2023 Handles the HTTP file serving and the WebSocket connect for HapticTouch */ import express, { Application, Request, Response } from "express"; import * as http from "http"; import * as WebSocket from "ws"; import { HapticLinkServer } from "./socket/hapticLinkServer"; import { registerRoutes } from "./socket/routes"; import WebSocketWrapper from "./socket/WebSocketAdapter"; import pino from "pino"; import path from "path"; // If started with --silent, then logging will be disabled. (() => { const args = process.argv; if (args.includes("--silent")) { return main(false); } return main(true); })(); /** * Starts HTTP(S) and WS server, initializes HapticLinkServer and handles routing. * @param {boolean} logging - Defaults true */ function main(logging: boolean = true) { // Added by Anuj const PING_INTERVAL = parseInt(process.env.PING_INTERVAL as string, 10) || 5 * 1000; interface LooseWebSocket extends WebSocket { isAlive?: boolean } const port: number = parseInt(process.env.PORT as string, 10) || 3000; const logger = pino({ level: logging ? "info" : "silent", formatters: { bindings(bindings) { return { level: bindings.level, time: bindings.time, msg: bindings.msg, }; }, }, }); const app: Application = express(); const server = http.createServer(app); const wss = new WebSocket.Server({ server }); // The entire build/web directory is statically served. app.use(express.static(path.join(__dirname, "../../client/build/web"))); // Catch all. If we want to add pages later, then we should probably change this. app.get("*", (_req: Request, res: Response) => { res.sendFile(path.join(__dirname + "/../../client/build/web/index.html")); }); // Routes are in socket/routes/*.ts // registerRoutes imports and adds them all to router const hapticLink = new HapticLinkServer(); registerRoutes(hapticLink); // When a user sends a message, the router checks if that route is available // and then calls the handler. It also generates a User object for them to store // data for later requests such as an ID wss.on("connection", (ws: LooseWebSocket) => { logger.info("Client Connected"); const wsw = new WebSocketWrapper(ws); ws.isAlive = true; // Added by Anuj ws.on("message", (message: string) => { logger.info(`Received Message: ${message}`); hapticLink.handleRoute(wsw, message); }); // Added by Anuj // When receive pong from a client this is triggered ws.on('pong', () => { ws.isAlive = true; logger.info("received pong"); }); // Added by Anuj // When receive ping from a client this is triggered ws.on('ping', () => { logger.info("received ping"); }); // When a user disconnects, their account is removed, and they are removed from all groups ws.on("close", () => { hapticLink.removeUser(wsw); }); ws.on("error", () => { hapticLink.removeUser(wsw); }); ws.send("Welcome"); }); // Added by Anuj const interval = setInterval(function ping() { wss.clients.forEach((ws) => { const client = ws as LooseWebSocket; if (client.isAlive === false) return ws.terminate(); ws.ping(); client.isAlive = false; }); }, PING_INTERVAL); // Added by Anuj wss.on('close', function close() { clearInterval(interval); }); server.listen(port, () => { logger.info(`Server is running on port ${port}`); }); }