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 expressLayouts from "express-ejs-layouts"; 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.set("view engine", "ejs"); app.use(expressLayouts); // DEFAULT layout for all pages app.set("layout", "layouts/dashboard"); 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", { title: 'Download tracker', version: "3.2.1", layout: 'layouts/main' }); }); 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; // No token → invalid request if (!sf_token) { return res.status(404).render('not_found', { title: 'Page Not Found', layout: 'layouts/error' }); } try { const decoded = jwt.verify(sf_token, process.env.JWT_SECRET_SF_TOKEN); // Token valid return res.render('va_register', { title: 'VA Registration', email: decoded.email, layout: 'layouts/varegisterlayout' }); } catch (error) { // Invalid / expired token return res.status(401).render('not_found', { title: 'Invalid or Expired Link', layout: 'layouts/error' }); } }); app.get('/clientregister/:sf_token?', (req, res) => { const { sf_token } = req.params; // No token → invalid request if (!sf_token) { return res.status(404).render('not_found', { title: 'Page Not Found', layout: 'layouts/error' }); } try { const decoded = jwt.verify(sf_token, process.env.JWT_SECRET_SF_TOKEN); // Token valid return res.render('va_client_register', { title: 'Client Registration', email: decoded.email, layout: 'layouts/vaclientlayout' }); } catch (error) { // Invalid / expired token return res.status(401).render('not_found', { title: 'Invalid or Expired Link', layout: 'layouts/error' }); } }); app.get('/auth/password/reset-form', render_reset_form); app.get("/client/login", (req, res) => { // res.render("client_login") res.render("client/client_login", { layout: "layouts/auth", // ⬅️ override layout title: "DocVA | Login" }); }) app.get("/client/forgot-password", (req, res) => { res.render("forgot_password", { title: "Forgot Password", layout: "layouts/forgotpasswordlayout" }); }) // 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'); res.render('client/dashboard-dashboard', { title: "Client Dashboard", pageScript: "dashboard.js" }); } 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', { title: "Virtual Assistants", pageScript:"dashboard-virtual-assistants.js" }); // 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('/client/projects', async (req, res) => { try { // const vaData = await virtualAssistantService.getClientVAs(req.user.id); // res.render('client/dashboard-projects'); res.render('client/dashboard-projects', { title: "Projects", pageScript:"projects.js" }); } catch (error) { console.error('Projects error:', error); res.status(500).render('client/dashboard-projects', { error: 'Failed to load projects' }); } }); app.get('/client/profile', async (req, res) => { try { // const vaData = await virtualAssistantService.getClientVAs(req.user.id); // res.render('client/dashboard-profile'); res.render('client/dashboard-profile',{ title: "Profile", pageScript:"profile.js" }); } catch (error) { console.error('Projects error:', error); res.status(500).render('client/dashboard-profile', { error: 'Failed to load profile' }); } }); app.get('/client/virtual-assistants/manage-tips', async (req, res) => { try { // const vaData = await virtualAssistantService.getClientVAs(req.user.id); // res.render('client/dashboard-manage-va-tips'); res.render('client/dashboard-manage-va-tips', { title: 'Manage VA Tips', pageScript: 'manage-tips.js' }); } catch (error) { console.error('Projects error:', error); res.status(500).render('client/dashboard-manage-va-tips', { error: 'Failed to load profile' }); } }); app.get('/client/virtual-assistants/view-logs', async (req, res) => { try { // res.render('client/dashboard-va-logs'); res.render('client/dashboard-va-logs', { title: 'VA Logs', pageScript: 'va-logs.js' }); } catch (error) { console.error('Projects error:', error); res.status(500).render('client/dashboard-va-logs', { error: 'Failed to load profile' }); } }); app.get('/client/credits', async (req, res) => { try { // res.render('client/dashboard-va-logs'); res.render('client/dashboard-credits', { title: 'Credits', pageScript: 'va-credits.js' }); } catch (error) { console.error('Projects error:', error); res.status(500).render('client/dashboard-va-logs', { error: 'Failed to load profile' }); } }); app.get("/live/:projectId/:vaId", async (req, res) => { const { projectId, vaId } = req.params; if (!projectId || !vaId) { return res.status(400).render("not_found", { title: "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 res.status(500).render("not_found", { title: "Server Error" }); } if (!clientId) { return res.status(400).render("not_found", { title: "Page not found" }); } res.render("va_live_tracker", { title: "VA Live Tracker", projectId, vaId, clientId, layout: "layouts/valivetrackerlayout" // ✅ ADD THIS }); }); // 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(); socket.data.vaId = vaId.toString(); socket.data.projectId = projectId; // 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, projectId = null, clientId = null }) => { 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 = clientId; // 🧩 Get allowed VAs for this client const client = await ClientModel.findById(clientId); const a = JSON.stringify(client); console.log(a, "client data for allowed VAs"); if (client.projectIds.length !== 0 && client.projectIds.includes(projectId)) { const projectVAIds = new Set(); const populated = await ProjectModel.findById(projectId).populate("vaAssigned"); populated.vaAssigned.forEach((va) => projectVAIds.add(va.vaId.toString())); socket.data.allowedVAs = Array.from(projectVAIds); console.log(`Client ${clientId} can view VAs:`, socket.data.allowedVAs); io.to(vaSocketRespectToClient.get(`${clientId}-${id}-${projectId}-va`)).emit("startSendAppsDetails", { projId: projectId }); } // 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 ${clientId} 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("requestVisibleApps", async ({ token }) => { 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("captureAppsTracker"); } } } catch (error) { console.error("❌ Token verification failed:", err.message); } }); 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(); // 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'); // }); // When client leaves or closes tab socket.on("disconnect", () => { if (socket.data.role === "client") { const { clientId, vaId, projectId } = socket.data; const vaSocketId = vaSocketRespectToClient.get( `${clientId}-${vaId}-${projectId}-va` ); if (vaSocketId) { // io.to(vaSocketId).emit("stopSendAppsDetails"); io.to(vaSocketRespectToClient.get(`${clientId}-${vaId}-${projectId}-va`)).emit("stopSendAppsDetails", { projId: projectId }); } } }); }); app.get("/login", (req, res) => { // res.render("client_login") res.render("client/client_login", { layout: "layouts/auth", // ⬅️ override layout title: "DocVA | Login" }); }) app.get("/forgot-password", (req, res) => { res.render("forgot_password", { title: "Forgot Password", layout: "layouts/forgotpasswordlayout" }); }) app.get("/health", (req, res) => { return res.status(200).json({ "success": true, "message": "Server is Healthy" }) }) app.post("/post-test", (req, res) => { console.log("✅ POST request received at /post-test:", req.body); return res.status(200).json({ "success": true, "message": "POST request received", "data": req.body }) }); app.get("/tnyk/:sf_token", (req, res) => { const sf_token = req.params.sf_token; console.log("Received token:", sf_token); try { const decoded = jwt.verify(sf_token, process.env.JWT_SECRET_SF_TOKEN); if (decoded) { res.render('client/dashboard-dashboard', { sf_token, title: "Client" }); } else { res.render('not_found', { title: "Client" }); } } catch (error) { res.render('not_found', { title: "Client" }); } }); app.use((req, res) => { res.status(404).render("not_found", { layout: "layouts/error", 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}`); });