const { admin } = require("../firebase/firebase-handler"); const redisSocketEvents = ({ io, redisHelpers: { setSessionData, clearSessionData }, // Note: Used if we persist user to Redis ioHelpers: { getBodyshopRoom }, logger }) => { // Logging helper functions const createLogEvent = (socket, level, message) => { logger.log("ioredis-log-event", level, socket?.user?.email, null, { wsmessage: message }); }; // Socket Auth Middleware const authMiddleware = (socket, next) => { try { if (socket.handshake.auth.token) { admin .auth() .verifyIdToken(socket.handshake.auth.token) .then((user) => { socket.user = user; // Note: if we ever want to capture user data across sockets // Uncomment the following line and then remove the next() to a second then() // return setSessionData(socket.id, "user", user); next(); }) .catch((error) => { next(new Error(`Authentication error: ${error.message}`)); }); } else { next(new Error("Authentication error - no authorization token.")); } } catch (error) { logger.log("websocket-connection-error", "error", null, null, { ...error }); next(new Error(`Authentication error ${error}`)); } }; // Register Socket Events const registerSocketEvents = (socket) => { createLogEvent(socket, "debug", `Registering RedisIO Socket Events.`); // Token Update Events const registerUpdateEvents = (socket) => { const updateToken = async (newToken) => { try { // noinspection UnnecessaryLocalVariableJS const user = await admin.auth().verifyIdToken(newToken, true); socket.user = user; // If We ever want to persist user Data across workers // await setSessionData(socket.id, "user", user); createLogEvent(socket, "debug", "Token updated successfully"); socket.emit("token-updated", { success: true }); } catch (error) { if (error.code === "auth/id-token-expired") { createLogEvent(socket, "WARNING", "Stale token received, waiting for new token"); socket.emit("token-updated", { success: false, error: "Stale token." }); } else { createLogEvent(socket, "error", `Token update failed: ${error.message}`); socket.emit("token-updated", { success: false, error: error.message }); // For any other errors, optionally disconnect the socket socket.disconnect(); } } }; socket.on("update-token", updateToken); }; // Room Broadcast Events const registerRoomAndBroadcastEvents = (socket) => { const joinBodyshopRoom = (bodyshopUUID) => { try { const room = getBodyshopRoom(bodyshopUUID); socket.join(room); // createLogEvent(socket, "debug", `Client joined bodyshop room: ${room}`); } catch (error) { createLogEvent(socket, "error", `Error joining room: ${error}`); } }; const leaveBodyshopRoom = (bodyshopUUID) => { try { const room = getBodyshopRoom(bodyshopUUID); socket.leave(room); createLogEvent(socket, "debug", `Client left bodyshop room: ${room}`); } catch (error) { createLogEvent(socket, "error", `Error joining room: ${error}`); } }; const broadcastToBodyshopRoom = (bodyshopUUID, message) => { try { const room = getBodyshopRoom(bodyshopUUID); io.to(room).emit("bodyshop-message", message); // We do not need this as these can be debugged live // createLogEvent(socket, "debug", `Broadcast message to bodyshop ${room}`); } catch (error) { createLogEvent(socket, "error", `Error getting room: ${error}`); } }; socket.on("join-bodyshop-room", joinBodyshopRoom); socket.on("leave-bodyshop-room", leaveBodyshopRoom); socket.on("broadcast-to-bodyshop", broadcastToBodyshopRoom); }; // Disconnect Events const registerDisconnectEvents = (socket) => { const disconnect = () => { createLogEvent(socket, "debug", `User disconnected.`); const rooms = Array.from(socket.rooms).filter((room) => room !== socket.id); for (const room of rooms) { socket.leave(room); } // If we ever want to persist the user across workers // clearSessionData(socket.id); }; socket.on("disconnect", disconnect); }; // Call Handlers registerRoomAndBroadcastEvents(socket); registerUpdateEvents(socket); registerDisconnectEvents(socket); }; // Associate Middleware and Handlers io.use(authMiddleware); io.on("connection", registerSocketEvents); }; module.exports = { redisSocketEvents };