119 lines
4.0 KiB
TypeScript
119 lines
4.0 KiB
TypeScript
import { Server, Socket } from "socket.io";
|
|
import jwt from "jsonwebtoken";
|
|
import { eventHandlerMap } from "../events/eventHandaler";
|
|
import { existingUserData } from "../../shared/services/AuthService";
|
|
|
|
interface UserPayload {
|
|
userId: string;
|
|
organization: string;
|
|
[key: string]: any;
|
|
}
|
|
|
|
interface UserSocketInfo {
|
|
socketId: string;
|
|
userId: string;
|
|
organization: string;
|
|
}
|
|
|
|
const connectedUsersByOrg: { [organization: string]: UserSocketInfo[] } = {};
|
|
|
|
export const SocketServer = async (io: Server) => {
|
|
|
|
const namespaceNames = ["/edge", "/project", "/collection"];
|
|
|
|
const onlineUsers: { [organization: string]: Set<string> } = {};
|
|
|
|
|
|
namespaceNames.forEach((nspName) => {
|
|
const namespace = io.of(nspName);
|
|
namespace.use(async (socket: Socket, next) => {
|
|
try {
|
|
const accessToken = socket.handshake.auth.accessToken as string;
|
|
const refreshToken = socket.handshake.auth.refreshToken as string;
|
|
if (!accessToken) return next(new Error("No access token provided"));
|
|
const jwt_secret = process.env.JWT_SECRET as string;
|
|
try {
|
|
const decoded = jwt.verify(accessToken, jwt_secret) as UserPayload;
|
|
|
|
const mailExistance = await existingUserData(decoded.email, decoded.organization);
|
|
if (!mailExistance) {
|
|
return next(new Error("Unauthorized user - not in system"));
|
|
}
|
|
|
|
(socket as any).user = decoded;
|
|
return next();
|
|
|
|
} catch (err: any) {
|
|
if (err.name === "TokenExpiredError") {
|
|
if (!refreshToken) {
|
|
return next(new Error("Token expired and no refresh token provided"));
|
|
|
|
}
|
|
|
|
try {
|
|
const refreshSecret = process.env.REFRESH_JWT_SECRET as string;
|
|
const decodedRefresh = jwt.verify(refreshToken, refreshSecret) as UserPayload;
|
|
const mailExistance = await existingUserData(decodedRefresh.email, decodedRefresh.organization);
|
|
|
|
if (!mailExistance) {
|
|
return next(new Error("Unauthorized user - not in system"));
|
|
}
|
|
|
|
// Generate new access token
|
|
const newAccessToken = jwt.sign(
|
|
{ email: decodedRefresh.email, organization: decodedRefresh.organization },
|
|
jwt_secret,
|
|
{ expiresIn: "3h" } // adjust expiry
|
|
);
|
|
|
|
socket.emit("newAccessToken", newAccessToken);
|
|
|
|
(socket as any).user = decodedRefresh;
|
|
return next();
|
|
} catch (refreshErr) {
|
|
return next(new Error("Refresh token expired or invalid"));
|
|
}
|
|
}
|
|
|
|
return next(new Error("Invalid token"));
|
|
}
|
|
} catch (err) {
|
|
return next(new Error("Authentication error"));
|
|
}
|
|
});
|
|
namespace.on("connection", (socket: Socket) => {
|
|
const user = (socket as any).user as UserPayload;
|
|
const { organization, userId } = user;
|
|
|
|
if (!onlineUsers[organization]) onlineUsers[organization] = new Set();
|
|
onlineUsers[organization].add(socket.id);
|
|
|
|
if (!connectedUsersByOrg[organization]) connectedUsersByOrg[organization] = [];
|
|
connectedUsersByOrg[organization].push({ socketId: socket.id, userId, organization });
|
|
console.log(`✅ Connected to namespace ${nspName}: ${socket.id}`);
|
|
|
|
// Common event handler
|
|
socket.onAny((event: string, data: any, callback: any) => {
|
|
const handler = eventHandlerMap[event];
|
|
if (handler) {
|
|
handler(event, socket, io, data, connectedUsersByOrg, callback);
|
|
} else {
|
|
console.warn(` No handler found for event: ${event}`);
|
|
}
|
|
});
|
|
|
|
socket.on("disconnect", () => {
|
|
onlineUsers[organization]?.delete(socket.id);
|
|
if (onlineUsers[organization]?.size === 0) {
|
|
delete onlineUsers[organization];
|
|
}
|
|
connectedUsersByOrg[organization] = connectedUsersByOrg[organization]?.filter(
|
|
(u) => u.socketId !== socket.id
|
|
);
|
|
});
|
|
});
|
|
});
|
|
|
|
return io;
|
|
};
|