Spaces:
Running
Running
| import express from "express"; | |
| import { createServer } from "http"; | |
| import { Server } from "socket.io"; | |
| import path from "path"; | |
| import { fileURLToPath } from "url"; | |
| const __filename = fileURLToPath(import.meta.url); | |
| const __dirname = path.dirname(__filename); | |
| async function startServer() { | |
| const app = express(); | |
| const httpServer = createServer(app); | |
| const io = new Server(httpServer, { | |
| cors: { | |
| origin: "*", | |
| methods: ["GET", "POST"], | |
| }, | |
| // FIX: Force websocket only - prevents the rapid connect/disconnect loop | |
| // that happens when Socket.io keeps falling back to HTTP polling on HF Spaces | |
| transports: ["websocket"], | |
| pingTimeout: 60000, | |
| pingInterval: 25000, | |
| }); | |
| const PORT = parseInt(process.env.PORT || "7860"); | |
| let videoQueue: string[] = []; | |
| let textQueue: string[] = []; | |
| const partners = new Map<string, string>(); | |
| let activeCount = 0; | |
| io.on("connection", (socket) => { | |
| console.log("User connected:", socket.id); | |
| socket.on("join-queue", ({ type }) => { | |
| // FIX: If already in queue, do nothing - prevents spam joining on reconnect | |
| if (videoQueue.includes(socket.id) || textQueue.includes(socket.id)) { | |
| console.log(`${socket.id} already in queue, ignoring duplicate`); | |
| socket.emit("waiting"); | |
| return; | |
| } | |
| // If already matched with someone, do nothing | |
| if (partners.has(socket.id)) { | |
| console.log(`${socket.id} already matched, ignoring join-queue`); | |
| return; | |
| } | |
| console.log(`${socket.id} joining ${type} queue`); | |
| activeCount++; | |
| io.emit("online_count", activeCount); | |
| const queue = type === "video" ? videoQueue : textQueue; | |
| if (queue.length > 0) { | |
| const partnerId = queue.shift()!; | |
| partners.set(socket.id, partnerId); | |
| partners.set(partnerId, socket.id); | |
| console.log(`✅ Matched ${socket.id} with ${partnerId}`); | |
| io.to(socket.id).emit("matched", { partnerId, type, role: "initiator" }); | |
| io.to(partnerId).emit("matched", { partnerId: socket.id, type, role: "receiver" }); | |
| } else { | |
| queue.push(socket.id); | |
| socket.emit("waiting"); | |
| console.log(`${socket.id} waiting in ${type} queue (queue size: ${queue.length})`); | |
| } | |
| }); | |
| socket.on("send-message", (text) => { | |
| const partnerId = partners.get(socket.id); | |
| if (partnerId) { | |
| io.to(partnerId).emit("receive-message", text); | |
| } | |
| }); | |
| socket.on("signal", (data) => { | |
| const partnerId = partners.get(socket.id); | |
| if (partnerId) { | |
| io.to(partnerId).emit("signal", data); | |
| } | |
| }); | |
| socket.on("leave-chat", () => { | |
| handleLeave(socket.id); | |
| }); | |
| socket.on("disconnect", () => { | |
| console.log("User disconnected:", socket.id); | |
| handleLeave(socket.id); | |
| }); | |
| function handleLeave(socketId: string) { | |
| const wasActive = | |
| videoQueue.includes(socketId) || | |
| textQueue.includes(socketId) || | |
| partners.has(socketId); | |
| if (wasActive) { | |
| activeCount = Math.max(0, activeCount - 1); | |
| io.emit("online_count", activeCount); | |
| } | |
| const partnerId = partners.get(socketId); | |
| if (partnerId) { | |
| io.to(partnerId).emit("partner-disconnected"); | |
| partners.delete(partnerId); | |
| } | |
| partners.delete(socketId); | |
| videoQueue = videoQueue.filter((id) => id !== socketId); | |
| textQueue = textQueue.filter((id) => id !== socketId); | |
| } | |
| }); | |
| const distPath = path.join(process.cwd(), "dist"); | |
| app.use(express.static(distPath)); | |
| app.get("*", (req, res) => { | |
| res.sendFile(path.join(distPath, "index.html")); | |
| }); | |
| httpServer.listen(PORT, "0.0.0.0", () => { | |
| console.log(`MingleHub server running on port ${PORT}`); | |
| }); | |
| } | |
| startServer(); | |