const fs = require("fs"); const https = require("https"); const WebSocket = require("ws"); const jwt = require("jsonwebtoken"); const fetch = require("node-fetch"); const db = require("../config/database"); const base_url = "https://backend.spurrinai.com"; const server = https.createServer({ cert: fs.readFileSync("/home/ubuntu/spurrin-backend/certificates/fullchain.pem"), key: fs.readFileSync("/home/ubuntu/spurrin-backend/certificates/privkey.pem") }); const wss = new WebSocket.Server({ server, perMessageDeflate: false }); const userSockets = new Map(); console.log("✅ Secure WebSocket Server running on wss://0.0.0.0:40520"); wss.on("connection", (ws) => { console.log("🔌 New client connected to secondary WebSocket"); ws.on("message", async (message) => { const data = JSON.parse(message); if (data.token && !ws.userId) { try { const decoded = jwt.verify(data.token, process.env.JWT_ACCESS_TOKEN_SECRET); ws.userId = decoded.id; userSockets.set(decoded.id, ws); } catch { ws.userId = null; } } if (data.event === "check-token-expiry") { let decoded; try { decoded = jwt.verify(data.token, process.env.JWT_ACCESS_TOKEN_SECRET); if (!decoded.exp) { emitEvent("check-token-expiry", { expired: 1, message: 'Expiry not set to token' }, decoded.id); return; } const currentTime = Math.floor(Date.now() / 1000); const timeLeft = decoded.exp - currentTime; if (timeLeft > 0) { emitEvent("check-token-expiry", { expired: 0, message: `Token expires in ${Math.floor(timeLeft / 60)} minutes` }, decoded.id); } else { emitEvent("check-token-expiry", { expired: 1, message: "Token expired, please relogin" }, decoded.id); } } catch (error) { emitEvent("check-token-expiry", { expired: 1, message: 'Token malformed', error }, ws.userId); } } if (data.event === "check-latest-token") { if (!data.token) { emitEvent("check-latest-token", { message: 'Access token required' }, ws.userId); return; } const decoded = jwt.decode(data.token); if (!decoded) { emitEvent("check-latest-token", { message: "Invalid token format" }, ws.userId); return; } ws.userId = decoded.id; userSockets.set(decoded.id, ws); let table; if (decoded.role === "Spurrinadmin") table = "super_admins"; else if (["Admin", "Viewer", "Superadmin", 7, 8, 9].includes(decoded.role)) table = "hospital_users"; else if (decoded.role === "AppUser") table = "app_users"; else { emitEvent("check-latest-token", { message: "Invalid role" }, decoded.id); return; } try { const result = await db.query(`SELECT access_token FROM ${table} WHERE id = ?`, [decoded.id]); const currentTime = Math.floor(Date.now() / 1000); const timeLeft = decoded.exp - currentTime; if (result.length > 0 && result[0].access_token === data.token && timeLeft > 0) { emitEvent("check-latest-token", { valid: 1, expired: 0, message: 'Token is valid' }, decoded.id); } else { emitEvent("check-latest-token", { valid: 0, expired: 0, message: 'Invalid token or expired' }, decoded.id); } } catch (error) { emitEvent("check-latest-token", { valid: 0, expired: 0, message: "DB Error", error }, decoded.id); } } if (data.event === "check-notification") { try { const response = await fetch(base_url + "/api/hospitals/check-user-notification", { method: "POST", headers: { "Content-Type": "application/json", "Authorization": `Bearer ${data.token}` }, body: JSON.stringify({ hospital_code: data.hospital_code }) }); const result = await response.json(); emitEvent("check-notification", { data: result, message: "New app users" }, ws.userId); } catch (error) { emitEvent("check-notification", { message: error.message }, ws.userId); } } if (data.event === "get-hospital-users") { if (!data.token) { emitEvent("get-hospital-users", { error: "Token missing" }, ws.userId); return; } try { const decoded = jwt.verify(data.token, process.env.JWT_ACCESS_TOKEN_SECRET); if (decoded.role !== 'Spurrinadmin' && decoded.role !== 6) { emitEvent("get-hospital-users", { error: "Unauthorized access" }, ws.userId); return; } const users = await db.query("SELECT * FROM hospital_users"); emitEvent("get-hospital-users", { data: users }, ws.userId); } catch (error) { emitEvent("get-hospital-users", { error: error.message }, ws.userId); } } if (data.event === "get-forwarded-feedbacks") { if (!data.token) { emitEvent("get-forwarded-feedbacks", { error: "Token missing" }, ws.userId); return; } try { const decoded = jwt.verify(data.token, process.env.JWT_ACCESS_TOKEN_SECRET); if (decoded.role !== 'Spurrinadmin' && decoded.role !== 6) { emitEvent("get-forwarded-feedbacks", { error: "Unauthorized access" }, ws.userId); return; } const query = ` SELECT f.sender_type, f.sender_id, f.receiver_type, f.receiver_id, f.rating, f.purpose, f.information_received, f.feedback_text, f.created_at, f.is_forwarded, f.improvement, h.name_hospital as sender_hospital, h.hospital_code FROM feedback f LEFT JOIN hospitals h ON f.sender_id = h.id AND f.sender_type = 'hospital' WHERE f.receiver_type = 'spurrin' ORDER BY f.created_at DESC `; const feedbacks = await db.query(query); emitEvent("get-forwarded-feedbacks", { message: "Forwarded feedbacks fetched successfully.", data: feedbacks }, ws.userId); } catch (error) { emitEvent("get-forwarded-feedbacks", { error: error.message }, ws.userId); } } // This event retrieves all feedback entries submitted by app users (sender_type = 'appuser') to a specific hospital (receiver_type = 'hospital') based on the hospital's hospital_code, which is derived from the JWT token provided by the user. if (data.event === "get-app-user-byhospital-feedback") { if (!data.token) { emitEvent("get-app-user-byhospital-feedback", { error: "Token missing" }, ws.userId); return; } try { const decoded = jwt.verify(data.token, process.env.JWT_ACCESS_TOKEN_SECRET); // Only hospital users (role 7, 8, or 9) are allowed if (!["Superadmin", "Admin", 7, 8].includes(decoded.role)) { emitEvent("get-app-user-byhospital-feedback", { error: "Unauthorized access" }, ws.userId); return; } console.log("Decoded token-----------------:", decoded); const email = decoded.email; const userId = decoded.id; // Fetch hospital ID using the code const hospitalCheck = await db.query( "SELECT id FROM hospitals WHERE primary_admin_email = ?", [email] ); if (hospitalCheck.length === 0) { emitEvent("get-app-user-byhospital-feedback", { error: "Hospital not found" }, userId); return; } const hospitalId = hospitalCheck[0].id; const query = ` SELECT f.feedback_id, f.sender_type, f.sender_id, f.receiver_type, f.receiver_id, f.rating, f.purpose, f.information_received, f.feedback_text, f.improvement, f.created_at, f.is_forwarded, au.username as user_name, au.email as user_email FROM feedback f LEFT JOIN app_users au ON f.sender_id = au.id AND f.sender_type = 'appuser' WHERE f.receiver_type = 'hospital' AND f.receiver_id = ? ORDER BY f.created_at DESC `; const feedbacks = await db.query(query, [hospitalId]); emitEvent("get-app-user-byhospital-feedback", { message: "Hospital feedbacks fetched successfully.", data: feedbacks }, userId); } catch (error) { emitEvent("get-app-user-byhospital-feedback", { error: error.message }, ws.userId); } } if (data.event === "get-documents-by-hospital") { if (!data.token || !data.hospital_id) { emitEvent("get-documents-by-hospital", { error: "Token or hospital_id missing" }, ws.userId); return; } try { const decoded = jwt.verify(data.token, process.env.JWT_ACCESS_TOKEN_SECRET); const allowedRoles = ['Admin', 'Superadmin', 'Viewer', 7, 8, 9]; // Role-based access check if (!allowedRoles.includes(decoded.role)) { emitEvent("get-documents-by-hospital", { error: "You are not authorized to view documents" }, decoded.id); return; } // Hospital access validation const requestedHospitalId = parseInt(data.hospital_id, 10); if (decoded.hospital_id !== requestedHospitalId) { emitEvent("get-documents-by-hospital", { error: "Unauthorized hospital access" }, decoded.id); return; } // Fetch documents for hospital const documents = await db.query( "SELECT * FROM documents WHERE hospital_id = ?", [requestedHospitalId] ); emitEvent("get-documents-by-hospital", { message: "Documents fetched successfully.", documents }, decoded.id); } catch (error) { emitEvent("get-documents-by-hospital", { error: error.message }, ws.userId); } } if (data.event === "app-usersby-hospitalid") { if (!data.token || !data.id) { emitEvent("app-usersby-hospitalid", { error: "Token or hospital ID missing" }, ws.userId); return; } try { const decoded = jwt.verify(data.token, process.env.JWT_ACCESS_TOKEN_SECRET); const userRole = decoded.role; // Only allowed roles if (!["Superadmin", "Admin", 8, 9].includes(userRole)) { emitEvent("app-usersby-hospitalid", { error: "Unauthorized to view app users" }, decoded.id); return; } // Fetch hospital_code using hospital id const query1 = `SELECT * FROM hospitals WHERE id = ?`; const result1 = await db.query(query1, [data.id]); if (!result1 || !result1[0].hospital_code) { emitEvent("app-usersby-hospitalid", { error: "Hospital not found" }, decoded.id); return; } const hospitalCode = result1[0].hospital_code; // Fetch app users for that hospital_code const query2 = `SELECT * FROM app_users WHERE hospital_code = ?`; const users = await db.query(query2, [hospitalCode]); if (users.length === 0) { emitEvent("app-usersby-hospitalid", { message: "No app users found" }, decoded.id); return; } emitEvent("app-usersby-hospitalid", { message: "App users fetched successfully", data: users }, decoded.id); } catch (error) { emitEvent("app-usersby-hospitalid", { error: error.message }, ws.userId); } } if (data.event === "get-signup-notifications") { if (!data.token) { emitEvent("get-signup-notifications", { error: "Token missing" }, ws.userId); return; } try { const decoded = jwt.verify(data.token, process.env.JWT_ACCESS_TOKEN_SECRET); const allowedRoles = ['Admin', 'Superadmin', 8, 7]; // Role-based access check if (!allowedRoles.includes(decoded.role)) { emitEvent("get-signup-notifications", { error: "You are not authorized!" }, decoded.id); return; } // Fetch hospital_code from the DB const result = await db.query( "SELECT hospital_code FROM hospital_users WHERE id = ?", [decoded.id] ); // Validate result if (!result || result.length === 0 || !result[0].hospital_code) { emitEvent("get-signup-notifications", { error: "Hospital code not found." }, decoded.id); return; } const hospital_code = result[0].hospital_code; // Fetch signup notifications const notifications = await db.query( "SELECT * FROM app_users WHERE hospital_code = ? AND checked = 0", [hospital_code] ); emitEvent("get-signup-notifications", { message: "Notifications fetched successfully.", notifications }, decoded.id); } catch (error) { console.error("Error fetching signup notifications:", error); emitEvent("get-signup-notifications", { error: error.message }, ws.userId); } } if (data.event === "get-app-queries") { if (!data.token || (!data.hospital_code || !data.app_user_id)) { emitEvent("get-app-queries", { error: "Token missing or hospital_code or app_user_id missing" }, ws.userId); return; } try { const decoded = jwt.verify(data.token, process.env.JWT_ACCESS_TOKEN_SECRET); const allowedRoles = ['Admin', 'Superadmin', 8, 7]; // Role-based access check if (!allowedRoles.includes(decoded.role)) { emitEvent("get-app-queries", { error: "You are not authorized!" }, decoded.id); return; } let interaction_logs; // Fetch notifications of new signup if (data.hospital_code.length == 0) { interaction_logs = await db.query( "SELECT * FROM interaction_logs WHERE app_user_id = ?", [data.app_user_id] ); } else if (data.app_user_id.length == 0) { interaction_logs = await db.query( "SELECT * FROM interaction_logs WHERE app_user_id = ?", [data.app_user_id] ); } emitEvent("get-app-queries", { message: "interaction logs fetched successfully.", interaction_logs }, decoded.id); } catch (error) { emitEvent("get-app-queries", { error: error.message }, ws.userId); } } if (data.event === "get-signup-notifications") { if (!data.token) { emitEvent("get-signup-notifications", { error: "Token missing" }, ws.userId); return; } try { const decoded = jwt.verify(data.token, process.env.JWT_ACCESS_TOKEN_SECRET); const allowedRoles = ['Admin', 'Superadmin', 8, 7]; // Role-based access check if (!allowedRoles.includes(decoded.role)) { emitEvent("get-signup-notifications", { error: "You are not authorized!" }, decoded.id); return; } console.log("decoded token ----", decoded) // Fetch hospital_code from hospitals table const result = await db.query( "SELECT hospital_code FROM hospital_users WHERE id = ?", [decoded.id] ); if (!result || result.length === 0 || !result[0].hospital_code) { emitEvent("get-signup-notifications", { error: "Hospital code not found" }, decoded.id); return; } const hospital_code = result[0].hospital_code; // Fetch notifications of new signups const notifications = await db.query( "SELECT * FROM app_users WHERE hospital_code = ? AND checked = 0", [hospital_code] ); emitEvent("get-signup-notifications", { message: "Notifications fetched successfully.", notifications }, decoded.id); } catch (error) { emitEvent("get-signup-notifications", { error: error.message }, ws.userId); } } }); ws.on("close", () => { console.log("❌ Client disconnected from secondary WebSocket"); if (ws.userId && userSockets.has(ws.userId)) { userSockets.delete(ws.userId); } ws.terminate(); }); }); // Add this function before the server.listen() call function emitEvent(event, data, userId = null) { if (userId && userSockets.has(userId)) { const client = userSockets.get(userId); if (client.readyState === WebSocket.OPEN) { client.send(JSON.stringify({ event, data })); } } else { wss.clients.forEach((client) => { if (client.readyState === WebSocket.OPEN) { client.send(JSON.stringify({ event, data })); } }); } } server.listen(40520, () => { console.log("📡 Secure WebSocket server listening on wss://backend.spurrinai.com:40520"); }); module.exports = { wss };