va-tracker-middleware/src/server.js

504 lines
14 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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.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) {
const email = decoded.email;
res.render('va_register', { title: "VA Registration", email });
} 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) => {
const { sf_token } = req.params;
try {
const decoded = jwt.verify(sf_token, process.env.JWT_SECRET_SF_TOKEN);
if (decoded) {
const email = decoded.email;
res.render('va_client_register', { title: "Client Registration", email });
} else {
res.render('not_found', { title: "Client Registration" });
}
} catch (error) {
res.render('not_found', { 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('/client/projects', async (req, res) => {
try {
// const vaData = await virtualAssistantService.getClientVAs(req.user.id);
res.render('client/dashboard-projects');
} 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');
} 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');
} catch (error) {
console.error('Projects error:', error);
res.status(500).render('client/dashboard-manage-va-tips', { 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).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("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();
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');
// });
// 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");
}
}
console.log("❌ Client 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}`);
});