import dotenv from "dotenv"; dotenv.config() import express from 'express'; import { createServer } from 'http'; import mongoose from "mongoose"; import bodyParser from "body-parser"; import { Server } from 'socket.io'; import cors from 'cors'; import path from 'path'; import { fileURLToPath } from 'url'; import apiRoutes from './routes/index.js' import { render_reset_form } from "./controllers/passwordReset.controllers.js"; import jwt from "jsonwebtoken"; const app = express(); const server = new createServer(app); const io = new Server(server); app.on('certificate-error', (event, webContents, url, error, certificate, callback) => { console.log("⚠️ Certificate error:", url); // Allow for ngrok only if (url.includes("ngrok")) { event.preventDefault(); callback(true); // approve the certificate } else { callback(false); // reject others } }); app.on('certificate-error', (event, webContents, url, error, certificate, callback) => { console.log("⚠️ Certificate error:", url); // Allow for ngrok only if (url.includes("ngrok")) { event.preventDefault(); callback(true); // approve the certificate } else { callback(false); // reject others } }); app.set("view engine", "ejs"); const PORT = process.env.PORT || 8000; app.use(cors({ sameSite: "None" })) const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); app.use("/public", express.static(path.join(__dirname, "..", "public"))); app.use(bodyParser.json({ limit: '40mb' })); app.use(bodyParser.urlencoded({ extended: true, limit: '40mb' })); app.use(bodyParser.text()); // API Route app.use('/api', apiRoutes); app.get("/download-tracker", (req, res) => { res.render("download_tracker", { version: "3.2.1" }); }); app.get("/download-file", (req, res) => { const os = req.query.os || "windows"; if (os === "windows") { return res.download("./files/docTracker-3.2.1.exe"); } else { return res.download("./files/docTracker-3.2.1.dmg"); } }); app.get('/varegister/:sf_token?', (req, res) => { const { sf_token } = req.params; try { const decoded = jwt.verify(sf_token, process.env.JWT_SECRET_SF_TOKEN); if (decoded) { res.render('va_register', { title: "VA Registration" }); } else { res.render('not_found', { title: "VA Registration" }); } } catch (error) { res.render('not_found', { title: "VA Registration" }); } // if(verified) { // res.render('va_register', { title: "VA Registration" }); // } else { // res.render('not_founf', { title: "VA Registration" }); // } }); app.get('/clientregister/:sf_token?', (req, res) => { // portalId: RECORDid => 8990928342309 // email // const { sf_token } = useParams(); res.render('va_client_register', { title: "Client Registration" }); }); app.get('/auth/password/reset-form', render_reset_form); app.get('/health', (req, res) => { res.status(201).send({ status: "Server is healthy" }); }) app.get("/client/login", (req, res) => { res.render("client_login") }) app.get("/client/forgot-password", (req, res) => { res.render("forgot_password") }) // Client Dashboard Routes with new file names app.get('/client/dashboard', async (req, res) => { try { // const dashboardData = await dashboardService.getClientDashboard(req.user.id); // res.render('client/dashboard-dashboard', dashboardData); res.render('client/dashboard-dashboard'); } catch (error) { console.error('Dashboard error:', error); res.status(500).render('client/dashboard-dashboard', { error: 'Failed to load dashboard' }); } }); app.get('/client/virtual-assistants', async (req, res) => { try { // const vaData = await virtualAssistantService.getClientVAs(req.user.id); res.render('client/dashboard-virtual-assistants'); // res.render('client/dashboard-virtual-assistants', vaData); } catch (error) { console.error('Virtual assistants error:', error); res.status(500).render('client/dashboard-virtual-assistants', { error: 'Failed to load virtual assistants' }); } }); app.get("/live/:projectId/:vaId", async (req, res) => { const { projectId, vaId } = req.params; if (!projectId || !vaId) { return res.status(400).send({ "message": "Page not found" }); } let clientId = null; try { const clientRes = await ProjectModel.findById(projectId); if (clientRes) { clientId = clientRes.clientId.toString(); } } catch (err) { console.error("Error fetching client for project:", err.message); return; } if (!clientId) return res.status(400).send({ "message": "Page not found" }); res.render("va_live_tracker", { title: "VA Live Tracker", projectId, vaId, clientId }) }) app.get("/live/:projectId/:vaId", async (req, res) => { const { projectId, vaId } = req.params; if (!projectId || !vaId) { return res.status(400).send({ "message": "Page not found" }); } let clientId = null; try { const clientRes = await ProjectModel.findById(projectId); if (clientRes) { clientId = clientRes.clientId.toString(); } } catch (err) { console.error("Error fetching client for project:", err.message); return; } if (!clientId) return res.status(400).send({ "message": "Page not found" }); res.render("va_live_tracker", { title: "VA Live Tracker", projectId, vaId, clientId }) }) // Handling Socket import { VAModel, ProjectModel, ClientModel } from './models/vaModel.js'; const vaSocketRespectToClient = new Map(); // Handling Socket io.use(async (socket, next) => { try { const token = socket.handshake.auth?.token; const projectId = socket.handshake.auth?.projectId; const userType = socket.handshake.auth?.userType; const vaId = socket.handshake.auth?.vaId; console.log("🔐 Received token:", token); if (!token) { return next(new Error("Not Authorized: Missing Token")); } const decoded = jwt.verify(token, process.env.JWT_SECRET_ACCESS_TOKEN); if (!decoded?.email) { return next(new Error("Invalid token payload")); } /* ---------------------- 1️⃣ Check if VA ---------------------- */ let vaUser = null; let clientData = null; if (userType === "va") { vaUser = await VAModel.findOne({ "userDetails.email": decoded.email }); clientData = await ProjectModel.findById(projectId).populate("clientId"); } else if (userType === "client") { clientData = await ProjectModel.findById(projectId).populate("clientId"); } if (clientData == null) return next(new Error("Invalid Project ID")); const vaProjClientId = clientData.clientId._id; if (vaUser && vaUser.accessToken === token) { socket.data.role = "va"; socket.data.vaId = vaUser._id.toString(); socket.data.vaEmail = decoded.email; const a = vaProjClientId.toString(); const b = clientData.clientId._id.toString(); const c = JSON.stringify(clientData); console.log(a, b, c, "vaProjClientId vs clientData.clientId"); vaSocketRespectToClient.set(`${vaProjClientId.toString()}-${vaUser._id.toString()}-${projectId.toString()}-va`, socket.id) console.log("✅ Authenticated VA:", vaUser.userDetails.email); return next(); } /* ---------------------- 2️⃣ Check if Client ---------------------- */ // const clientUser = await ClientModel.findOne({ "userDetails.email": decoded.email }); // if (clientUser) { // socket.data.role = "client"; // socket.data.clientId = clientUser._id.toString(); // // Get all VA IDs assigned to this client // socket.data.allowedVAs = clientUser.vaIds.map((va) => va.toString()); // console.log("✅ Authenticated Client:", clientUser.userDetails.email); // return next(); // } const clientUser = clientData.clientId; if (clientUser) { socket.data.role = "client"; socket.data.clientId = clientUser._id.toString(); // Get all VA IDs assigned to this client socket.data.allowedVAs = clientData.vaAssigned.map((va) => va.toString()); vaSocketRespectToClient.set(`${vaProjClientId.toString()}-${vaId.toString()}-${projectId.toString()}-client`, socket.id) // console.log("✅ Authenticated Client:", clientUser.userDetails.email); return next(); } return next(new Error("Not Authorized: Invalid User")); } catch (error) { console.error("❌ Socket Auth Error:", error.message); next(new Error("Authentication failed: " + error.message)); } }); io.on("connection", (socket) => { console.log("User connected:", socket.id); // Register role and ID (client or VA) socket.on("register", async ({ role, id }) => { if (role === "va") { socket.data.role = "va"; socket.data.vaId = id; console.log(`VA registered: ${id}`); } if (role === "client") { socket.data.role = "client"; socket.data.clientId = id; // 🧩 Get allowed VAs for this client const client = await ClientModel.findById(id) .populate("projectIds") // to access their projects .populate("vaIds"); // optional if you track directly // Collect all VA IDs from their projects const projectVAIds = new Set(); for (const project of client.projectIds) { const populated = await ProjectModel.findById(project._id).populate("vaAssigned"); populated.vaAssigned.forEach((va) => projectVAIds.add(va._id.toString())); } // Store allowed VA IDs in socket memory socket.data.allowedVAs = Array.from(projectVAIds); console.log(`Client ${id} can view VAs:`, socket.data.allowedVAs); } }); // When VA sends live image socket.on("sendImage", async ({ token, image, title, projectId }) => { try { // Verify token const decoded = jwt.verify(token, process.env.JWT_SECRET_ACCESS_TOKEN); const vaEmail = decoded.email; // Find VA in DB const vaUser = await VAModel.findOne({ "userDetails.email": vaEmail }); if (!vaUser) { console.log("❌ Unauthorized VA"); return; } const project = await ProjectModel.findById(projectId); if (!project) { console.log("❌ Invalid Project ID"); return; } const clientId = project.clientId.toString(); const vaId = vaUser._id.toString(); const clientVaUniqueId = `${clientId}-${vaId}-${projectId}-client`; const socketId = vaSocketRespectToClient.get(clientVaUniqueId); console.log("🟢 Received image from VA:", vaId); // Broadcast image only to allowed clients // for (let [_, clientSocket] of io.sockets.sockets) { // if ( // clientSocket.data.role === "client" && // clientSocket.data.allowedVAs?.includes(vaId) // ) { // clientSocket.emit("receiveImage", image, title); // } // } // Can be like clientId-projectId-vaId to uniquely identify socket.to(socketId).emit("receiveImage", image, title); } catch (err) { console.error("❌ Token verification failed:", err.message); } }); socket.on("selectApp", ({ appTitle, clientId, vaId, projectId }) => { console.log(`VA selected app: ${appTitle}`); console.log(vaSocketRespectToClient.get(`${clientId}-${vaId}-${projectId}-va`), "vaSocketRespectToClient[clientId]") // You can forward this info to the respective Electron client socket.to(vaSocketRespectToClient.get(`${clientId}-${vaId}-${projectId}-va`)).emit("appToCapture", { appTitle }); }); socket.on("getVisibleApps", async ({ token, apps }) => { try { // Verify token const decoded = jwt.verify(token, process.env.JWT_SECRET_ACCESS_TOKEN); const vaEmail = decoded.email; // Find VA in DB const vaUser = await VAModel.findOne({ "userDetails.email": vaEmail }); if (!vaUser) { console.log("❌ Unauthorized VA"); return; } const vaId = vaUser._id.toString(); console.log("🟢 Received Apps from VA:", vaId); // Broadcast image only to allowed clients for (let [_, clientSocket] of io.sockets.sockets) { if ( clientSocket.data.role === "client" && clientSocket.data.allowedVAs?.includes(vaId) ) { clientSocket.emit("receiveVisibleApps", { apps, vaId }); } } } catch (err) { console.error("❌ Token verification failed:", err.message); } }); socket.on('disconnect', () => { console.log('a user Disconnected'); }); }); app.use((req, res) => { res.status(404).render("not_found", { title: "404 - Page Not Found" }); }); server.listen(PORT, () => { mongoose .connect( process.env.MONGO_URI, ) .then(() => console.log("mongodb connected")) .catch((err) => { console.log(err.message); process.exit(1); }); console.log(`Express started on http://localhost:${PORT}`); });