feature/IO-2742-redis - Checkpoint, Final cleanup of server.js

Signed-off-by: Dave Richer <dave@imexsystems.ca>
This commit is contained in:
Dave Richer
2024-09-11 20:52:49 -04:00
parent 4486858a86
commit 51c181dab7

View File

@@ -5,17 +5,11 @@ const path = require("path");
const compression = require("compression"); const compression = require("compression");
const cookieParser = require("cookie-parser"); const cookieParser = require("cookie-parser");
const http = require("http"); const http = require("http");
const https = require("https");
const fs = require("fs");
const { Server } = require("socket.io"); const { Server } = require("socket.io");
const { createClient } = require("redis"); const { createClient } = require("redis");
const { createAdapter } = require("@socket.io/redis-adapter"); const { createAdapter } = require("@socket.io/redis-adapter");
const logger = require("./server/utils/logger"); const logger = require("./server/utils/logger");
// This file offers the following exports
// redisClient: Redis client for external use
// io: Socket.IO server for external use
// Load environment variables // Load environment variables
require("dotenv").config({ require("dotenv").config({
path: path.resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`) path: path.resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`)
@@ -96,19 +90,11 @@ const applyRoutes = (app) => {
}; };
/** /**
* Main function to start the server * Apply Redis to the server
* @returns {Promise<void>} * @param server
* @param app
*/ */
const main = async () => { const applySocketIO = async (server, app) => {
const app = express();
const port = process.env.PORT || 5000;
let server;
// In production or default, use HTTP
server = http.createServer(app);
logger.log(`[${process.env.NODE_ENV}] Attempting to start process on port ${port}`, "INFO", "api");
// Redis client setup for Pub/Sub and Key-Value Store // Redis client setup for Pub/Sub and Key-Value Store
const pubClient = createClient({ url: process.env.REDIS_URL || "redis://localhost:6379" }); const pubClient = createClient({ url: process.env.REDIS_URL || "redis://localhost:6379" });
const subClient = pubClient.duplicate(); const subClient = pubClient.duplicate();
@@ -128,7 +114,7 @@ const main = async () => {
process.exit(0); process.exit(0);
}); });
exports.io = new Server(server, { const io = new Server(server, {
path: "/ws", path: "/ws",
adapter: createAdapter(pubClient, subClient), adapter: createAdapter(pubClient, subClient),
cors: { cors: {
@@ -139,39 +125,54 @@ const main = async () => {
} }
}); });
exports.redisClient = pubClient; app.use((req, res, next) => {
req.pubClient = pubClient;
req.io = io;
next();
});
Object.assign(module.exports, { io, pubClient });
return { pubClient, io };
};
/**
* Apply Redis helper functions
* @param pubClient
* @param app
*/
const applyRedisHelpers = (pubClient, app) => {
// Store session data in Redis // Store session data in Redis
exports.setSessionData = async (socketId, key, value) => { const setSessionData = async (socketId, key, value) => {
await pubClient.hSet(`socket:${socketId}`, key, JSON.stringify(value)); // Use Redis pubClient await pubClient.hSet(`socket:${socketId}`, key, JSON.stringify(value)); // Use Redis pubClient
}; };
// Retrieve session data from Redis // Retrieve session data from Redis
exports.getSessionData = async (socketId, key) => { const getSessionData = async (socketId, key) => {
const data = await pubClient.hGet(`socket:${socketId}`, key); const data = await pubClient.hGet(`socket:${socketId}`, key);
return data ? JSON.parse(data) : null; return data ? JSON.parse(data) : null;
}; };
// Clear session data from Redis // Clear session data from Redis
exports.clearSessionData = async (socketId) => { const clearSessionData = async (socketId) => {
await pubClient.del(`socket:${socketId}`); await pubClient.del(`socket:${socketId}`);
}; };
// Store multiple session data in Redis // Store multiple session data in Redis
exports.setMultipleSessionData = async (socketId, keyValues) => { const setMultipleSessionData = async (socketId, keyValues) => {
// keyValues is expected to be an object { key1: value1, key2: value2, ... } // keyValues is expected to be an object { key1: value1, key2: value2, ... }
const entries = Object.entries(keyValues).map(([key, value]) => [key, JSON.stringify(value)]); const entries = Object.entries(keyValues).map(([key, value]) => [key, JSON.stringify(value)]);
await pubClient.hSet(`socket:${socketId}`, ...entries.flat()); await pubClient.hSet(`socket:${socketId}`, ...entries.flat());
}; };
// Retrieve multiple session data from Redis // Retrieve multiple session data from Redis
exports.getMultipleSessionData = async (socketId, keys) => { const getMultipleSessionData = async (socketId, keys) => {
const data = await pubClient.hmGet(`socket:${socketId}`, keys); const data = await pubClient.hmGet(`socket:${socketId}`, keys);
// Redis returns an object with null values for missing keys, so we parse the non-null ones // Redis returns an object with null values for missing keys, so we parse the non-null ones
return Object.fromEntries(keys.map((key, index) => [key, data[index] ? JSON.parse(data[index]) : null])); return Object.fromEntries(keys.map((key, index) => [key, data[index] ? JSON.parse(data[index]) : null]));
}; };
exports.setMultipleFromArraySessionData = async (socketId, keyValueArray) => { const setMultipleFromArraySessionData = async (socketId, keyValueArray) => {
// Use Redis multi/pipeline to batch the commands // Use Redis multi/pipeline to batch the commands
const multi = pubClient.multi(); const multi = pubClient.multi();
@@ -183,7 +184,7 @@ const main = async () => {
}; };
// Helper function to add an item to the end of the Redis list // Helper function to add an item to the end of the Redis list
exports.addItemToEndOfList = async (socketId, key, newItem) => { const addItemToEndOfList = async (socketId, key, newItem) => {
try { try {
await pubClient.rPush(`socket:${socketId}:${key}`, JSON.stringify(newItem)); await pubClient.rPush(`socket:${socketId}:${key}`, JSON.stringify(newItem));
} catch (error) { } catch (error) {
@@ -192,7 +193,7 @@ const main = async () => {
}; };
// Helper function to add an item to the beginning of the Redis list // Helper function to add an item to the beginning of the Redis list
exports.addItemToBeginningOfList = async (socketId, key, newItem) => { const addItemToBeginningOfList = async (socketId, key, newItem) => {
try { try {
await pubClient.lPush(`socket:${socketId}:${key}`, JSON.stringify(newItem)); await pubClient.lPush(`socket:${socketId}:${key}`, JSON.stringify(newItem));
} catch (error) { } catch (error) {
@@ -200,6 +201,31 @@ const main = async () => {
} }
}; };
Object.assign(module.exports, {
setSessionData,
getSessionData,
clearSessionData,
setMultipleSessionData,
getMultipleSessionData,
setMultipleFromArraySessionData,
addItemToEndOfList,
addItemToBeginningOfList
});
app.use((req, res, next) => {
req.sessionUtils = {
setSessionData,
getSessionData,
clearSessionData,
setMultipleSessionData,
getMultipleSessionData,
setMultipleFromArraySessionData,
addItemToEndOfList,
addItemToBeginningOfList
};
next();
});
// // Demo to show how all the helper functions work // // Demo to show how all the helper functions work
// const demoSessionData = async () => { // const demoSessionData = async () => {
// const socketId = "testSocketId"; // const socketId = "testSocketId";
@@ -251,11 +277,23 @@ const main = async () => {
// }; // };
// //
// if (process.env.NODE_ENV === "development") { // if (process.env.NODE_ENV === "development") {
// await demoSessionData(); // demoSessionData();
// } // }
};
/**
* Main function to start the server
* @returns {Promise<void>}
*/
const main = async () => {
const app = express();
const port = process.env.PORT || 5000;
const server = http.createServer(app);
const { pubClient } = await applySocketIO(server, app);
applyRedisHelpers(pubClient, app);
require("./server/web-sockets/web-socket"); require("./server/web-sockets/web-socket");
applyMiddleware(app); applyMiddleware(app);
applyRoutes(app); applyRoutes(app);