Files
singlechat/server/broadcast.js

866 lines
28 KiB
JavaScript

import crypto from 'crypto';
import { readFileSync, writeFileSync, appendFileSync } from 'fs';
import { join } from 'path';
import axios from 'axios';
const TIMEOUT_SECONDS = 1800; // 30 Minuten
class Client {
constructor(sessionId) {
this.sessionId = sessionId;
this.gender = '';
this.country = '';
this.isoCountryCode = '';
this.userName = '';
this.age = 0;
this.conversations = {};
this.lastActivity = new Date();
this.loginTimeStamp = new Date();
this.blockedUsers = new Set();
this.socket = null; // Socket.IO Socket-Objekt
}
setActivity() {
this.lastActivity = new Date();
}
activitiesTimedOut() {
const secondsSinceActivity = (new Date() - this.lastActivity) / 1000;
return secondsSinceActivity > TIMEOUT_SECONDS;
}
remainingSecondsToTimeout() {
const secondsSinceActivity = (new Date() - this.lastActivity) / 1000;
return Math.max(0, TIMEOUT_SECONDS - secondsSinceActivity);
}
currentlyLoggedInSeconds() {
return Math.floor((new Date() - this.loginTimeStamp) / 1000);
}
toJSON() {
return {
sessionId: this.sessionId,
userName: this.userName,
gender: this.gender,
country: this.country,
isoCountryCode: this.isoCountryCode,
age: this.age
};
}
}
let clients = new Map();
let conversations = new Map(); // Key: "user1:user2" (alphabetisch sortiert)
// Map: Socket-ID -> Express-Session-ID (für Session-Wiederherstellung)
let socketToSessionMap = new Map();
// Exportiere clients Map für Zugriff von außen
export function getClientsMap() {
return clients;
}
// Exportiere Funktion zum Abrufen der Session-ID für einen Socket
export function getSessionIdForSocket(socketId) {
return socketToSessionMap.get(socketId);
}
function extractSessionId(handshakeOrRequest) {
// Unterstützt sowohl Socket.IO handshake als auch Express request
const cookies = handshakeOrRequest.headers?.cookie || handshakeOrRequest.cookies || '';
console.log('extractSessionId - Cookies:', cookies);
const sessionMatch = cookies.match(/connect\.sid=([^;]+)/);
if (sessionMatch) {
let sessionId = sessionMatch[1];
console.log('extractSessionId - Gefundenes Cookie:', sessionId);
// Express-Session speichert die Session-ID als signierten Wert: s:xxxxx.signature
// Die tatsächliche Session-ID ist nur xxxxx
if (sessionId.startsWith('s:')) {
const parts = sessionId.split('.');
if (parts.length > 0) {
sessionId = parts[0].substring(2); // Entferne 's:' Präfix
console.log('extractSessionId - Bereinigte Session-ID:', sessionId);
}
}
return sessionId;
}
console.log('extractSessionId - Kein connect.sid Cookie gefunden');
// Fallback: Generiere temporäre UUID
return crypto.randomUUID();
}
// Exportiere Funktion zum Prüfen des Session-Status
export function getSessionStatus(req) {
// Verwende req.sessionID von Express-Session
const sessionId = req.sessionID || extractSessionId(req);
if (!sessionId) {
return null;
}
const client = clients.get(sessionId);
if (!client || !client.userName) {
return null;
}
return {
sessionId: client.sessionId,
userName: client.userName,
gender: client.gender,
age: client.age,
country: client.country,
isoCountryCode: client.isoCountryCode
};
}
function getConversationKey(user1, user2) {
return [user1, user2].sort().join(':');
}
function logClientLogin(client) {
try {
const logPath = join(process.cwd(), 'logs', 'logins.log');
const logEntry = `${new Date().toISOString()},${client.userName},${client.country},${client.age},${client.gender}\n`;
appendFileSync(logPath, logEntry, 'utf-8');
} catch (error) {
console.error('Fehler beim Loggen des Logins:', error);
}
}
function checkAndLogStart() {
try {
const logPath = join(process.cwd(), 'logs', 'starts.log');
const logEntry = `${new Date().toISOString()}\n`;
appendFileSync(logPath, logEntry, 'utf-8');
} catch (error) {
console.error('Fehler beim Loggen des Starts:', error);
}
}
export function setupBroadcast(io) {
// Länderliste beim Start laden
let countriesMap = {};
async function downloadCountries() {
try {
const response = await axios.get('https://pkgstore.datahub.io/core/country-list/data_csv/data/d7c9d7cfb42cb69f4422dec222dbbaa8/data_csv.csv');
const lines = response.data.split('\n');
countriesMap = {};
// Parse CSV-Zeile mit Berücksichtigung von Anführungszeichen
const parseCSVLine = (line) => {
const result = [];
let current = '';
let inQuotes = false;
for (let i = 0; i < line.length; i++) {
const char = line[i];
if (char === '"') {
// Ignoriere Anführungszeichen, sie werden nicht zum Wert hinzugefügt
inQuotes = !inQuotes;
} else if (char === ',' && !inQuotes) {
result.push(current.trim());
current = '';
} else {
current += char;
}
}
result.push(current.trim());
return result;
};
for (let i = 1; i < lines.length; i++) {
const line = lines[i].trim();
if (!line) continue;
const [name, code] = parseCSVLine(line);
if (name && code) {
// Entferne alle Anführungszeichen (auch am Anfang/Ende)
const cleanName = name.replace(/^["']+|["']+$/g, '').replace(/["']/g, '').trim();
const cleanCode = code.replace(/^["']+|["']+$/g, '').replace(/["']/g, '').trim();
if (cleanName && cleanCode) {
countriesMap[cleanName] = cleanCode.toLowerCase();
}
}
}
console.log(`Länderliste geladen: ${Object.keys(countriesMap).length} Länder`);
} catch (error) {
console.error('Fehler beim Laden der Länderliste:', error);
}
}
downloadCountries();
setInterval(downloadCountries, 24 * 60 * 60 * 1000); // Täglich aktualisieren
// Socket.IO-Verbindungshandler
io.on('connection', (socket) => {
const request = socket.handshake;
// Versuche Session-ID aus Cookie zu extrahieren
let sessionId = extractSessionId(request);
console.log('Socket.IO Connect - Session-ID (aus Cookie):', sessionId);
console.log('Socket.IO Connect - Cookie:', request.headers?.cookie);
// Wenn keine gültige Session-ID gefunden wurde, generiere eine temporäre UUID
// Diese wird beim Login durch die Express-Session-ID ersetzt (siehe handleLogin)
if (!sessionId || sessionId.length < 10 || !sessionId.match(/^[a-zA-Z0-9_-]+$/)) {
sessionId = crypto.randomUUID();
console.log('Socket.IO Connect - Keine gültige Session-ID gefunden, verwende temporäre UUID:', sessionId);
socket.data.temporarySessionId = true; // Markiere als temporär
}
// Speichere Socket-ID mit Session-ID
socket.data.sessionId = sessionId;
let client = clients.get(sessionId);
if (!client) {
console.log('Socket.IO Connect - Neuer Client erstellt für Session-ID:', sessionId);
client = new Client(sessionId);
clients.set(sessionId, client);
} else {
console.log('Socket.IO Connect - Bestehender Client gefunden für Session-ID:', sessionId, 'User:', client.userName);
}
// Speichere Socket mit Session-ID
client.socket = socket;
// Bestätigung an Client senden, inklusive Benutzerdaten falls bereits eingeloggt
const connectedData = { sessionId };
if (client.userName) {
connectedData.user = {
userName: client.userName,
gender: client.gender,
age: client.age,
country: client.country,
isoCountryCode: client.isoCountryCode
};
connectedData.loggedIn = true;
}
socket.emit('connected', connectedData);
socket.on('disconnect', (reason) => {
const client = clients.get(sessionId);
if (client) {
// Setze Socket auf null, damit keine Nachrichten mehr an diesen Client gesendet werden
// ABER: Lösche den Client NICHT, damit die Session beim Reload wiederhergestellt werden kann
client.socket = null;
// Aktualisiere Benutzerliste, damit andere Clients sehen, dass dieser Benutzer offline ist
if (client.userName) {
broadcastUserList();
}
// Client bleibt in der Map, damit Session-Wiederherstellung funktioniert
}
});
socket.on('error', (error) => {
console.error('Socket.IO-Fehler:', error);
});
// Socket.IO Event-Handler: Setze Express-Session-ID
socket.on('setSessionId', (data) => {
const { expressSessionId } = data;
if (expressSessionId) {
console.log('setSessionId - Express-Session-ID erhalten:', expressSessionId);
const currentSessionId = socket.data.sessionId;
if (currentSessionId !== expressSessionId) {
console.log('setSessionId - Aktualisiere Session-ID von', currentSessionId, 'zu', expressSessionId);
// Prüfe, ob bereits ein Client mit dieser Session-ID existiert
let existingClient = clients.get(expressSessionId);
// Wenn kein Client mit dieser Session-ID gefunden wurde, prüfe ob ein Client mit dem aktuellen Socket existiert
// und bereits eingeloggt ist (für Session-Wiederherstellung nach Reload)
if (!existingClient) {
const currentClient = clients.get(currentSessionId);
if (currentClient && currentClient.userName) {
console.log('setSessionId - Client mit Session-ID', currentSessionId, 'ist bereits eingeloggt:', currentClient.userName);
// Verwende bestehenden Client und aktualisiere nur die Session-ID
existingClient = currentClient;
clients.delete(currentSessionId);
clients.set(expressSessionId, existingClient);
existingClient.sessionId = expressSessionId;
} else {
// Wenn kein Client mit der aktuellen Session-ID gefunden wurde, suche nach einem Client,
// der bereits eingeloggt ist und keinen aktiven Socket hat (für Session-Wiederherstellung)
// Dies ist ein Fallback für den Fall, dass die Session-ID beim Reload geändert wurde
for (const [sid, c] of clients.entries()) {
if (c.userName && (!c.socket || !c.socket.connected)) {
console.log('setSessionId - Gefundener Client ohne aktiven Socket:', sid, c.userName);
// Verwende diesen Client und aktualisiere die Session-ID
existingClient = c;
clients.delete(sid);
clients.set(expressSessionId, existingClient);
existingClient.sessionId = expressSessionId;
break;
}
}
}
}
if (existingClient) {
// Verwende bestehenden Client
console.log('setSessionId - Verwende bestehenden Client, User:', existingClient.userName || 'nicht eingeloggt');
existingClient.socket = socket;
socket.data.sessionId = expressSessionId;
// Entferne alten Client, falls unterschiedlich
if (currentSessionId !== expressSessionId && clients.has(currentSessionId)) {
clients.delete(currentSessionId);
}
// Sende Login-Status zurück, falls bereits eingeloggt
if (existingClient.userName) {
console.log('setSessionId - Sende Login-Status zurück für User:', existingClient.userName);
socket.emit('connected', {
sessionId: expressSessionId,
loggedIn: true,
user: existingClient.toJSON()
});
// Aktualisiere Userliste für alle Clients, damit der wiederhergestellte Client die Liste erhält
broadcastUserList();
}
} else {
// Erstelle neuen Client mit Express-Session-ID
console.log('setSessionId - Erstelle neuen Client');
const newClient = new Client(expressSessionId);
newClient.socket = socket;
clients.set(expressSessionId, newClient);
socket.data.sessionId = expressSessionId;
// Entferne alten Client
if (clients.has(currentSessionId)) {
clients.delete(currentSessionId);
}
}
}
}
});
// Socket.IO Event-Handler
// WICHTIG: Hole den Client immer dynamisch basierend auf socket.data.sessionId,
// da sich die Session-ID nach setSessionId ändern kann
socket.on('login', async (data) => {
try {
const currentClient = clients.get(socket.data.sessionId);
if (!currentClient) {
socket.emit('error', { message: 'Client nicht gefunden' });
return;
}
currentClient.setActivity();
await handleLogin(socket, currentClient, data);
} catch (error) {
console.error('Fehler beim Verarbeiten der Login-Nachricht:', error);
socket.emit('error', { message: error.message });
}
});
socket.on('message', async (data) => {
try {
const currentClient = clients.get(socket.data.sessionId);
if (!currentClient) {
socket.emit('error', { message: 'Client nicht gefunden' });
return;
}
currentClient.setActivity();
handleMessage(socket, currentClient, data);
} catch (error) {
console.error('Fehler beim Verarbeiten der Message-Nachricht:', error);
socket.emit('error', { message: error.message });
}
});
socket.on('requestConversation', async (data) => {
try {
const currentClient = clients.get(socket.data.sessionId);
if (!currentClient) {
socket.emit('error', { message: 'Client nicht gefunden' });
return;
}
currentClient.setActivity();
handleRequestConversation(socket, currentClient, data);
} catch (error) {
console.error('Fehler beim Verarbeiten der RequestConversation-Nachricht:', error);
socket.emit('error', { message: error.message });
}
});
socket.on('userSearch', async (data) => {
try {
const currentClient = clients.get(socket.data.sessionId);
if (!currentClient) {
socket.emit('error', { message: 'Client nicht gefunden' });
return;
}
currentClient.setActivity();
handleUserSearch(socket, currentClient, data);
} catch (error) {
console.error('Fehler beim Verarbeiten der UserSearch-Nachricht:', error);
socket.emit('error', { message: error.message });
}
});
socket.on('requestHistory', async () => {
try {
const currentClient = clients.get(socket.data.sessionId);
if (!currentClient) {
socket.emit('error', { message: 'Client nicht gefunden' });
return;
}
currentClient.setActivity();
handleRequestHistory(socket, currentClient);
} catch (error) {
console.error('Fehler beim Verarbeiten der RequestHistory-Nachricht:', error);
socket.emit('error', { message: error.message });
}
});
socket.on('requestOpenConversations', async () => {
try {
const currentClient = clients.get(socket.data.sessionId);
if (!currentClient) {
socket.emit('error', { message: 'Client nicht gefunden' });
return;
}
currentClient.setActivity();
handleRequestOpenConversations(socket, currentClient);
} catch (error) {
console.error('Fehler beim Verarbeiten der RequestOpenConversations-Nachricht:', error);
socket.emit('error', { message: error.message });
}
});
socket.on('blockUser', async (data) => {
try {
const currentClient = clients.get(socket.data.sessionId);
if (!currentClient) {
socket.emit('error', { message: 'Client nicht gefunden' });
return;
}
currentClient.setActivity();
handleBlockUser(socket, currentClient, data);
} catch (error) {
console.error('Fehler beim Verarbeiten der BlockUser-Nachricht:', error);
socket.emit('error', { message: error.message });
}
});
socket.on('unblockUser', async (data) => {
try {
const currentClient = clients.get(socket.data.sessionId);
if (!currentClient) {
socket.emit('error', { message: 'Client nicht gefunden' });
return;
}
currentClient.setActivity();
handleUnblockUser(socket, currentClient, data);
} catch (error) {
console.error('Fehler beim Verarbeiten der UnblockUser-Nachricht:', error);
socket.emit('error', { message: error.message });
}
});
});
async function handleLogin(socket, client, data) {
const { userName, gender, age, country, expressSessionId } = data;
// Validierung
if (!userName || userName.trim().length < 3) {
socket.emit('error', { message: 'Benutzername muss mindestens 3 Zeichen lang sein' });
return;
}
// Wenn eine Express-Session-ID übergeben wurde, verwende diese statt der Socket-ID
if (expressSessionId) {
console.log('handleLogin - Express-Session-ID erhalten:', expressSessionId);
// Entferne alten Client mit temporärer Socket-ID, falls vorhanden
const oldSessionId = socket.data.sessionId;
if (oldSessionId && oldSessionId !== expressSessionId && clients.has(oldSessionId)) {
console.log('handleLogin - Entferne alten Client mit Session-ID:', oldSessionId);
clients.delete(oldSessionId);
}
// Prüfe, ob bereits ein Client mit dieser Session-ID existiert
let existingClient = clients.get(expressSessionId);
if (existingClient) {
// Verwende bestehenden Client
console.log('handleLogin - Verwende bestehenden Client mit Session-ID:', expressSessionId);
client = existingClient;
// Aktualisiere Socket-Verbindung
client.socket = socket;
socket.data.sessionId = expressSessionId;
} else {
// Erstelle neuen Client mit Express-Session-ID
console.log('handleLogin - Erstelle neuen Client mit Session-ID:', expressSessionId);
client = new Client(expressSessionId);
clients.set(expressSessionId, client);
client.socket = socket;
socket.data.sessionId = expressSessionId;
}
} else {
console.log('handleLogin - Keine Express-Session-ID erhalten, verwende Socket-ID');
}
// Prüfe, ob Name bereits verwendet wird
for (const [sid, c] of clients.entries()) {
if (c.userName === userName && sid !== client.sessionId) {
socket.emit('error', { message: 'Dieser Benutzername ist bereits vergeben' });
return;
}
}
client.userName = userName.trim();
client.gender = gender;
client.age = age;
client.country = country;
// ISO-Code aus Länderliste ermitteln
client.isoCountryCode = countriesMap[country] || 'unknown';
client.loginTimeStamp = new Date();
client.setActivity();
// Stelle sicher, dass der Socket gesetzt ist
if (!client.socket) {
console.log('handleLogin - WARNUNG: Socket nicht gesetzt, setze ihn jetzt');
client.socket = socket;
}
console.log('handleLogin - Client nach Login:', {
sessionId: client.sessionId,
userName: client.userName,
hasSocket: !!client.socket,
socketConnected: client.socket ? client.socket.connected : false
});
logClientLogin(client);
checkAndLogStart();
// Benutzerliste an alle senden
broadcastUserList();
// Aktualisiere ungelesene Nachrichten für den neuen Benutzer
updateUnreadCount(client);
console.log('handleLogin - Sende loginSuccess an Socket');
socket.emit('loginSuccess', {
sessionId: client.sessionId,
user: client.toJSON()
});
}
function handleMessage(socket, client, data) {
if (!client.userName) {
socket.emit('error', { message: 'Nicht eingeloggt' });
return;
}
const { toUserName, message, messageId, isImage, imageType } = data;
if (!toUserName) {
socket.emit('error', { message: 'Empfänger fehlt' });
return;
}
// Finde Empfänger
let receiver = null;
for (const [sid, c] of clients.entries()) {
if (c.userName === toUserName) {
receiver = c;
break;
}
}
if (!receiver) {
socket.emit('error', { message: 'Benutzer nicht gefunden' });
return;
}
// Prüfe, ob Empfänger noch verbunden ist
if (!receiver.socket || !receiver.socket.connected) {
// Empfänger ist nicht mehr verbunden, entferne ihn aus der Liste
clients.delete(receiver.sessionId);
broadcastUserList();
socket.emit('error', { message: 'Benutzer nicht gefunden' });
return;
}
// Prüfe Blockierung
if (receiver.blockedUsers.has(client.userName)) {
socket.emit('error', { message: 'Du wurdest von diesem Benutzer blockiert' });
return;
}
// Speichere Nachricht in Konversation
const convKey = getConversationKey(client.userName, toUserName);
if (!conversations.has(convKey)) {
conversations.set(convKey, []);
}
const conversation = conversations.get(convKey);
conversation.push({
from: client.userName,
to: toUserName,
message,
messageId,
timestamp: new Date().toISOString(),
read: false,
isImage: isImage || false,
imageType: imageType || null
});
// Sende an Empfänger (wenn online)
receiver.socket.emit('message', {
from: client.userName,
message,
messageId,
timestamp: new Date().toISOString(),
isImage: isImage || false,
imageType: imageType || null
});
// Bestätigung an Absender
socket.emit('messageSent', {
messageId,
to: toUserName
});
// Aktualisiere ungelesene Nachrichten für den Empfänger
updateUnreadCount(receiver);
}
function handleRequestConversation(socket, client, data) {
if (!client.userName) {
socket.emit('error', { message: 'Nicht eingeloggt' });
return;
}
const { withUserName } = data;
const convKey = getConversationKey(client.userName, withUserName);
const conversation = conversations.get(convKey) || [];
// Markiere alle Nachrichten von diesem Benutzer als gelesen
for (const msg of conversation) {
if (msg.to === client.userName && msg.from === withUserName) {
msg.read = true;
}
}
// Aktualisiere ungelesene Nachrichten nach dem Markieren als gelesen
updateUnreadCount(client);
socket.emit('conversation', {
with: withUserName,
messages: conversation.map(msg => ({
from: msg.from,
message: msg.message,
timestamp: msg.timestamp,
isImage: msg.isImage || false,
imageType: msg.imageType || null
}))
});
}
function handleUserSearch(socket, client, data) {
if (!client.userName) {
socket.emit('error', { message: 'Nicht eingeloggt' });
return;
}
const { nameIncludes, minAge, maxAge, countries, genders } = data;
const results = [];
for (const [sid, c] of clients.entries()) {
if (!c.userName || c.userName === client.userName) continue;
// Name-Filter
if (nameIncludes && !c.userName.toLowerCase().includes(nameIncludes.toLowerCase())) {
continue;
}
// Alter-Filter
if (minAge && c.age < minAge) continue;
if (maxAge && c.age > maxAge) continue;
// Länder-Filter
if (countries && countries.length > 0 && !countries.includes(c.country)) {
continue;
}
// Geschlecht-Filter
if (genders && genders.length > 0 && !genders.includes(c.gender)) {
continue;
}
results.push(c.toJSON());
}
socket.emit('searchResults', {
results
});
}
function handleRequestHistory(socket, client) {
if (!client.userName) {
socket.emit('error', { message: 'Nicht eingeloggt' });
return;
}
const history = [];
for (const [convKey, messages] of conversations.entries()) {
const [user1, user2] = convKey.split(':');
if (user1 === client.userName || user2 === client.userName) {
const otherUser = user1 === client.userName ? user2 : user1;
history.push({
userName: otherUser,
lastMessage: messages.length > 0 ? messages[messages.length - 1] : null
});
}
}
socket.emit('historyResults', {
results: history
});
}
function handleRequestOpenConversations(socket, client) {
if (!client.userName) {
socket.emit('error', { message: 'Nicht eingeloggt' });
return;
}
const inbox = [];
for (const [convKey, messages] of conversations.entries()) {
const [user1, user2] = convKey.split(':');
if (user1 === client.userName || user2 === client.userName) {
const otherUser = user1 === client.userName ? user2 : user1;
const unreadCount = messages.filter(m =>
m.to === client.userName && !m.read
).length;
if (unreadCount > 0) {
inbox.push({
userName: otherUser,
unreadCount
});
}
}
}
socket.emit('inboxResults', {
results: inbox
});
}
function handleBlockUser(socket, client, data) {
if (!client.userName) {
socket.emit('error', { message: 'Nicht eingeloggt' });
return;
}
const { userName } = data;
client.blockedUsers.add(userName);
socket.emit('userBlocked', {
userName
});
}
function handleUnblockUser(socket, client, data) {
if (!client.userName) {
socket.emit('error', { message: 'Nicht eingeloggt' });
return;
}
const { userName } = data;
client.blockedUsers.delete(userName);
socket.emit('userUnblocked', {
userName
});
}
function updateUnreadCount(client) {
if (!client.userName || !client.socket || !client.socket.connected) {
return;
}
// Zähle Personen mit ungelesenen Nachrichten für diesen Benutzer
let unreadPersons = 0;
for (const [convKey, messages] of conversations.entries()) {
const [user1, user2] = convKey.split(':');
if (user1 === client.userName || user2 === client.userName) {
const unreadCount = messages.filter(m =>
m.to === client.userName && !m.read
).length;
if (unreadCount > 0) {
unreadPersons++;
}
}
}
// Sende Update an Client
client.socket.emit('unreadChats', {
count: unreadPersons
});
}
function broadcastUserList() {
// Filtere nur eingeloggte Benutzer mit aktiver Verbindung
const userList = Array.from(clients.values())
.filter(c => {
const hasUserName = !!c.userName;
const hasSocket = !!c.socket;
const isConnected = c.socket && c.socket.connected;
if (hasUserName && (!hasSocket || !isConnected)) {
console.log('broadcastUserList - Client ohne Socket oder nicht verbunden:', c.userName, 'socket:', hasSocket, 'connected:', isConnected);
}
return hasUserName && hasSocket && isConnected;
})
.map(c => c.toJSON());
console.log('broadcastUserList - Sende Userliste mit', userList.length, 'Benutzern:', userList.map(u => u.userName));
// Sende an alle verbundenen Clients
io.emit('userList', {
users: userList
});
}
function broadcastToUser(userName, data) {
// Finde den Client mit diesem Benutzernamen und sende die Nachricht
for (const [sid, c] of clients.entries()) {
if (c.userName === userName && c.socket) {
c.socket.emit(data.type, data);
return;
}
}
}
// Timeout-Check alle 60 Sekunden
setInterval(() => {
for (const [sid, client] of clients.entries()) {
if (client.activitiesTimedOut()) {
console.log(`Client ${client.userName} hat Timeout erreicht`);
clients.delete(sid);
broadcastUserList();
}
}
}, 60000);
}