diff --git a/nodejs/User.mjs b/nodejs/User.mjs index f05849eaa..bb9646723 100644 --- a/nodejs/User.mjs +++ b/nodejs/User.mjs @@ -1,6 +1,6 @@ -import {io} from "./websocket.js"; -import {getOnlineUSer, getUserStatus} from "./login.mjs"; -import {AWAY_TIME, DEFAULT_STATE} from "./config.mjs"; +import { io } from "./websocket.js"; +import { getOnlineUSer } from "./login.mjs"; +import { AWAY_TIME, DEFAULT_STATE } from "./config.mjs"; class User { userId; @@ -12,130 +12,88 @@ class User { offline = false; away = false; awayTime = 0; + constructor(userId, socket, status) { - this.sockets.push(socket); - this.status = status; - this.initUserAway(); this.userId = userId; + this.status = status; + this.sockets.push(socket); this.offline = status === DEFAULT_STATE; - this.awayTime=AWAY_TIME; + this.awayTime = AWAY_TIME; + this.initUserAway(); // async, kein await im Konstruktor möglich } addSocket(socket) { - if ((this.sockets.indexOf(socket) === -1)) { - this.sockets.push(socket); - } + if (!this.sockets.includes(socket)) this.sockets.push(socket); } removeSocket(socket) { const index = this.sockets.indexOf(socket); - if (index > -1) { // only splice array when item is found - this.sockets.splice(index, 1); // 2nd parameter means remove one item only - } - - } - - setStatus(status) { - if (status === 'offline') { - this.offline = true; - } else { - this.offline = false; - } - this.status = status; - this.initUserAway(); - } - - hasSocket(socket) { - if (socket in this.sockets) { - return true - } - return false; - } - - getSockets() { - return this.sockets + if (index > -1) this.sockets.splice(index, 1); } getStatus() { - if (this.offline || this.sockets.length === 0) { - return 'offline'; - } - if (this.inMeeting.length > 0) { - return 'inMeeting'; - } - if (this.away){ - return 'away'; - } + if (this.offline || this.sockets.length === 0) return 'offline'; + if (this.inMeeting.length > 0) return 'inMeeting'; + if (this.away) return 'away'; return this.status; } - setAlive() { - this.initUserAway() + async sendStatus() { + const onlineUsers = await getOnlineUSer(); + io.emit('sendOnlineUser', JSON.stringify(onlineUsers)); + this.sendToAllSockets('sendUserStatus', this.getStatus()); } - sendStatus() { - io.emit('sendOnlineUser', JSON.stringify(getOnlineUSer())); - this.sendToAllSockets('sendUserStatus',this.status); - } - - getUserId() { - return this.userId; - } - - setuserId(value) { - this._userId = value; - } - - initUserAway() { + async initUserAway() { clearTimeout(this.awayTimer); this.awayTimer = null; - var that = this; + if (this.away === true) { this.away = false; - this.sendStatus(); + await this.sendStatus(); } + this.oldStatus = null; - this.awayTimer = setTimeout(function () { - that.away = true; - that.sendStatus(); - }, 60000 * this.awayTime) + this.awayTimer = setTimeout(async () => { + this.away = true; + await this.sendStatus(); + }, 60000 * this.awayTime); + } + + async setStatus(status) { + this.status = status; + this.offline = status === 'offline'; + await this.initUserAway(); + } + + sendToAllSockets(ev, message) { + for (const socket of this.sockets) socket.emit(ev, message); } enterMeeting(socket) { - if (!this.inMeeting.includes(socket)) { - this.inMeeting.push(socket); - } + if (!this.inMeeting.includes(socket)) this.inMeeting.push(socket); } leaveMeeting(socket) { - for (var i = 0; i < this.inMeeting.length; i++) { - if (this.inMeeting[i] === socket) { - this.inMeeting.splice(i, 1); - } - } + const idx = this.inMeeting.indexOf(socket); + if (idx > -1) this.inMeeting.splice(idx, 1); } checkUserLeftTheApp() { return this.sockets.length === 0; } - setAwayTime(awayTime){ + + async setAwayTime(awayTime) { try { awayTime = parseInt(awayTime); - if (Number.isInteger(awayTime)){ + if (Number.isInteger(awayTime)) { this.awayTime = awayTime; - this.sendToAllSockets('sendUserTimeAway',this.awayTime); + this.sendToAllSockets('sendUserTimeAway', this.awayTime); } - }catch (e) { - console.log(e) - } - } - - sendToAllSockets(ev,message){ - for (var prop in this.sockets) { - var tmpSocket = this.sockets[prop]; - tmpSocket.emit(ev, message); + } catch (e) { + console.error(e); } } } -export {User}; \ No newline at end of file +export { User }; diff --git a/nodejs/config.mjs b/nodejs/config.mjs index b002ce869..0a7dfa190 100644 --- a/nodejs/config.mjs +++ b/nodejs/config.mjs @@ -1,7 +1,12 @@ -export var WEBSOCKET_SECRET=process.env.WEBSOCKET_SECRET ||"MY_SECRET" -export var MERCURE_INTERNAL_URL=process.env.MERCURE_INTERNAL_URL || "/.well-known/mercure"; -export var PORT =process.env.PORT || 3000; -export var AWAY_TIME =process.env.AWAY_TIME || 5; -export var DEFAULT_STATE =process.env.DEFAULT_STATE || 'offline'; -export var KEY_FILE =process.env.KEY_FILE || './tls_certificate/key.pem'; -export var CERT_FILE =process.env.CERT_FILE || './tls_certificate/cert.pem'; \ No newline at end of file +export var WEBSOCKET_SECRET = process.env.WEBSOCKET_SECRET || "MY_SECRET"; +export var MERCURE_INTERNAL_URL = process.env.MERCURE_INTERNAL_URL || "/.well-known/mercure"; +export var PORT = process.env.PORT || 3000; +export var AWAY_TIME = process.env.AWAY_TIME || 5; +export var DEFAULT_STATE = process.env.DEFAULT_STATE || "offline"; +export var KEY_FILE = process.env.KEY_FILE || "./tls_certificate/key.pem"; +export var CERT_FILE = process.env.CERT_FILE || "./tls_certificate/cert.pem"; + +// Redis für Clusterbetrieb +export var REDIS_ENABLED = process.env.REDIS_ENABLED === "true"; +export var REDIS_HOST = process.env.REDIS_HOST || "redis"; +export var REDIS_PORT = process.env.REDIS_PORT || 6379; diff --git a/nodejs/login.mjs b/nodejs/login.mjs index 8933f95a0..00b10e301 100644 --- a/nodejs/login.mjs +++ b/nodejs/login.mjs @@ -1,130 +1,104 @@ -import jwt from 'jsonwebtoken' -import {User} from "./User.mjs"; -import {WEBSOCKET_SECRET} from "./config.mjs"; - -let user = {}; - -export function loginUser(socket) { - - if (jwt.verify(socket.handshake.query.token, WEBSOCKET_SECRET)) { - var userId = getUserId(socket); - console.log('create new user'); - if (userId){ - if (typeof user[userId] === 'undefined') { - user[userId] = new User(userId, socket, getUserInitialOnlineStatus(socket)); - } else { - console.log('add user'); - user[userId].addSocket(socket); - } - return user[userId] - } - } - return null; -} - - -export function disconnectUser(socket) { - var userId = getUserId(socket); - leaveMeeting(socket); - if (user[userId]) { - user[userId].removeSocket(socket); - } -} - -export function checkEmptySockets(socket) { - try { - return user[getUserId(socket)].checkUserLeftTheApp() - }catch (e) { - return false; - } - -} - -export function setStatus(socket, status) { - var userId = getUserId(socket); - user[userId].setStatus(status); - return user[userId]; -} - -export function setAwayTime(socket, awayTime) { - var user = getUserFromSocket(socket); - user.setAwayTime(awayTime); - return user; +import jwt from "jsonwebtoken"; +import { User } from "./User.mjs"; +import { WEBSOCKET_SECRET, REDIS_ENABLED, REDIS_HOST, REDIS_PORT } from "./config.mjs"; + +let redis = null; +let user = {}; // lokaler Fallback + +// Redis verbinden, falls aktiviert +if (REDIS_ENABLED) { + try { + const { createClient } = await import("ioredis"); + redis = createClient({ host: REDIS_HOST, port: REDIS_PORT }); + await redis.connect(); + console.log("🔗 Redis-Client verbunden für globale User-Liste"); + } catch (err) { + console.warn("⚠️ Redis nicht erreichbar – verwende lokalen User-State:", err.message); + redis = null; + } } - -export function stillOnline(socket) { - if (user[getUserId(socket)]) { - user[getUserId(socket)].initUserAway(); +export async function loginUser(socket) { + if (jwt.verify(socket.handshake.query.token, WEBSOCKET_SECRET)) { + const userId = getUserId(socket); + if (!userId) return null; + + const initialStatus = getUserInitialOnlineStatus(socket); + + if (redis) { + try { + await redis.hset("users", userId, JSON.stringify({ id: userId, status: initialStatus, updatedAt: Date.now() })); + } catch (err) { + console.error(`[Redis] Fehler beim Speichern von User ${userId}:`, err.message); + } + } else { + if (!user[userId]) user[userId] = new User(userId, socket, initialStatus); + else user[userId].addSocket(socket); } - return 0; -} -export function enterMeeting(socket) { - if (user[getUserId(socket)]) { - user[getUserId(socket)].enterMeeting(socket); - } - return 0; + return getUserFromSocket(socket); + } + return null; } -export function leaveMeeting(socket) { - if (user[getUserId(socket)]) { - user[getUserId(socket)].leaveMeeting(socket); +export async function disconnectUser(socket) { + const userId = getUserId(socket); + leaveMeeting(socket); + if (redis) { + try { + await redis.hdel("users", userId); + } catch (err) { + console.error(`[Redis] Fehler beim Löschen von User ${userId}:`, err.message); } - return 0; + } else if (user[userId]) { + user[userId].removeSocket(socket); + } } -export function getOnlineUSer() { - var tmpUser = {}; - - for (var prop in user) { - var u = user[prop]; - - var tmpStatus = u.getStatus(); - if (typeof tmpUser[tmpStatus] === 'undefined') { - tmpUser[tmpStatus] = []; +export async function getOnlineUSer() { + if (redis) { + try { + const all = await redis.hgetall("users"); + const result = {}; + for (const [id, val] of Object.entries(all)) { + try { + const parsed = JSON.parse(val); + const st = parsed.status || "online"; + if (!result[st]) result[st] = []; + result[st].push(id); + } catch { + if (!result.online) result.online = []; + result.online.push(id); } - tmpUser[tmpStatus].push(u.getUserId()); + } + return result; + } catch (err) { + console.error("[Redis] Fehler beim Abrufen der User-Liste:", err.message); + return {}; + } + } else { + const tmpUser = {}; + for (const prop in user) { + const u = user[prop]; + const tmpStatus = u.getStatus(); + if (!tmpUser[tmpStatus]) tmpUser[tmpStatus] = []; + tmpUser[tmpStatus].push(u.getUserId()); } return tmpUser; + } } -export function getUserStatus(socket) { - if (user[getUserId(socket)]) { - return user[getUserId(socket)].getStatus(); - } +export function getUserFromSocket(socket) { + const userId = getUserId(socket); + return user[userId] || null; } function getUserId(socket) { - var jwtObj = jwt.decode(socket.handshake.query.token); - var userId = jwtObj.sub - return userId; + const jwtObj = jwt.decode(socket.handshake.query.token); + return jwtObj?.sub; } function getUserInitialOnlineStatus(socket) { - var jwtObj = jwt.decode(socket.handshake.query.token); - return jwtObj.status === 1 ? 'online' : 'offline'; -} - -export function getUserFromSocket(socket) { - var userId = getUserId(socket); - return user[userId] ? user[userId] : null; + const jwtObj = jwt.decode(socket.handshake.query.token); + return jwtObj?.status === 1 ? "online" : "offline"; } -export function getStatusForListOfIds(socket,list) { - var list = JSON.parse(list); - var res = {}; - for (var l of list){ - try { - var tmpUser = user[l]; - if (tmpUser){ - res[l] = tmpUser.getStatus(); - }else { - res[l] = 'offline'; - } - }catch (e) { - res[l] = 'offline'; - } - } - - socket.emit('giveOnlineStatus',JSON.stringify(res)); -} \ No newline at end of file diff --git a/nodejs/package.json b/nodejs/package.json index d0625dbcf..9fb341d50 100644 --- a/nodejs/package.json +++ b/nodejs/package.json @@ -2,17 +2,19 @@ "name": "jitsi-admin-websocket", "version": "1.0.0", "description": "", - "main": "websocket.js", + "main": "server.mjs", "type": "module", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, - "author": "", - "license": "ISC", + "author": "H2 invent GmbH", + "license": "AGPLv3", "dependencies": { "body-parser": "^1.20.0", "express": "^4.18.1", "jsonwebtoken": "^8.5.1", - "socket.io": "^4.5.1" + "socket.io": "^4.5.1", + "redis": "^5.3.1", + "@socket.io/redis-adapter": "^8.2.0" } } diff --git a/nodejs/server.mjs b/nodejs/server.mjs new file mode 100644 index 000000000..c307b9b31 --- /dev/null +++ b/nodejs/server.mjs @@ -0,0 +1,167 @@ +import express from "express"; +import bodyParser from "body-parser"; +import http from "http"; +import https from "https"; +import fs from "fs"; +import jwt from "jsonwebtoken"; +import { Server } from "socket.io"; + +import { checkFileContains } from "./checkCertAndKey.js"; +import { websocketState } from "./websocketState.mjs"; +import { loginUser, getOnlineUSer, getUserId } from "./login.mjs"; +import { + MERCURE_INTERNAL_URL, + PORT, + WEBSOCKET_SECRET, + KEY_FILE, + CERT_FILE, + REDIS_ENABLED, + REDIS_HOST, + REDIS_PORT +} from "./config.mjs"; + +const app = express(); +const router = express.Router(); +let server; + +// HTTP oder HTTPS +try { + if (checkFileContains(CERT_FILE, "BEGIN CERTIFICATE") && checkFileContains(KEY_FILE, "BEGIN PRIVATE KEY")) { + console.log("✅ HTTPS Server wird gestartet."); + server = https.createServer({ + key: fs.readFileSync(KEY_FILE), + cert: fs.readFileSync(CERT_FILE) + }, app); + } else { + console.log("⚠️ HTTPS Zertifikat nicht gefunden, HTTP Server wird gestartet."); + server = http.createServer(app); + } +} catch (err) { + console.error("❌ HTTPS Setup fehlgeschlagen:", err); + server = http.createServer(app); +} + +// Socket.IO Server +export const io = new Server(server, { + path: "/ws", + cors: { + origin: "*", + methods: ["GET", "POST"] + } +}); + +let redis = null; + +// Optional: Redis Adapter für Cluster +if (REDIS_ENABLED) { + try { + const { createAdapter } = await import("@socket.io/redis-adapter"); + const { createClient } = await import("redis"); + + const pubClient = createClient({ url: `redis://${REDIS_HOST}:${REDIS_PORT}` }); + const subClient = pubClient.duplicate(); + + await pubClient.connect(); + await subClient.connect(); + + redis = pubClient; // global für Heartbeat + io.adapter(createAdapter(pubClient, subClient)); + console.log(`🔗 Redis-Adapter aktiviert (${REDIS_HOST}:${REDIS_PORT})`); + } catch (err) { + console.warn("⚠️ Redis-Adapter konnte nicht initialisiert werden, Standalone läuft:", err.message); + } +} else { + console.log("⚙️ Redis deaktiviert – Standalone-Modus"); +} + +// JWT Auth +io.use((socket, next) => { + if (socket.handshake.query?.token) { + jwt.verify(socket.handshake.query.token, WEBSOCKET_SECRET, (err, decoded) => { + if (err) return next(new Error("Authentication error")); + socket.decoded = decoded; + next(); + }); + } else next(new Error("Authentication error")); +}); + +// Socket.IO Events +io.on("connection", async (socket) => { + const jwtObj = jwt.decode(socket.handshake.query.token); + if (jwtObj?.rooms) jwtObj.rooms.forEach(room => socket.join(room)); + + const user = await loginUser(socket); + if (user) { + user.initUserAway?.(); + socket.emit("sendUserStatus", user.getStatus?.()); + socket.emit("sendUserTimeAway", user.awayTime ?? 0); + io.emit("sendOnlineUser", JSON.stringify(await getOnlineUSer())); + } + + socket.on("disconnect", () => websocketState("disconnect", socket, null)); + socket.onAny((event, data) => websocketState(event, socket, data)); +}); + +// Express Middleware +app.use(bodyParser.urlencoded({ extended: false })); +app.use(bodyParser.json()); + +// MERCURE Webhook +router.post(MERCURE_INTERNAL_URL, async (req, res) => { + const authHeader = req.headers.authorization; + if (!authHeader) return res.sendStatus(403); + + const token = authHeader.split(" ")[1]; + jwt.verify(token, WEBSOCKET_SECRET, async (err) => { + if (err) return res.sendStatus(403); + + const data = req.body.data; + const room = req.body.topic; + io.to(room).emit("mercure", data); + res.end("OK"); + }); +}); + +router.get(MERCURE_INTERNAL_URL, (_, res) => res.sendStatus(200)); +router.get("/healthz", (_, res) => res.sendStatus(200)); +app.use("/", router); + +// Server starten +server.listen(PORT, () => { + console.log(`🚀 Server läuft auf Port ${PORT} (${REDIS_ENABLED ? "Cluster" : "Standalone"})`); +}); + +// 🔄 Heartbeat: lokale User alle 10 Sekunden erneut in Redis registrieren +if (REDIS_ENABLED && redis) { + setInterval(async () => { + try { + const usersFromRedis = {}; + + // Alle lokal verbundenen Sockets in Redis registrieren (Heartbeat) + for (const socket of io.sockets.sockets.values()) { + const userId = getUserId(socket); + if (!userId) continue; + + const userData = { + id: userId, + status: socket.decoded?.status === 1 ? 'online' : 'offline', + updatedAt: Date.now() + }; + + await redis.hset("users", userId, JSON.stringify(userData)); + + // Für Testausgabe vorbereiten + const status = userData.status; + if (!usersFromRedis[status]) usersFromRedis[status] = []; + usersFromRedis[status].push(userId); + } + + // 🌐 Test: globale Userliste ausgeben + console.log("🌐 Globale Userliste (Redis + Heartbeat):", usersFromRedis); + + } catch (err) { + console.error("Fehler beim Heartbeat / globale Userliste:", err.message); + } + }, 10000); // alle 10 Sekunden +} + diff --git a/nodejs/websocket.js b/nodejs/websocket.js deleted file mode 100644 index b98727c0c..000000000 --- a/nodejs/websocket.js +++ /dev/null @@ -1,131 +0,0 @@ -import express from 'express'; -import bodyParser from "body-parser"; - -const router = express.Router(); -const app = express(); -import http from 'http' -import https from "https"; -import fs from "fs"; - -import {checkFileContains} from './checkCertAndKey.js'; -import {Server} from "socket.io"; -import jwt from 'jsonwebtoken' - -import {getOnlineUSer, loginUser} from './login.mjs' -import {websocketState} from './websocketState.mjs'; -import {MERCURE_INTERNAL_URL, PORT, WEBSOCKET_SECRET, KEY_FILE, CERT_FILE} from "./config.mjs"; - - -const keyPath = KEY_FILE; -const certPath = CERT_FILE; -let server; -try { - if (checkFileContains(certPath, 'BEGIN CERTIFICATE') && checkFileContains(keyPath, 'BEGIN PRIVATE KEY')) { - console.log('We found the cert and the key file'); - console.log('The cert and key are valid.') - console.log('We start an HTTPS Server.'); - server = https.createServer({ - key: fs.readFileSync(keyPath), - cert: fs.readFileSync(certPath) - }, - app); - } else { - server = http.createServer(app); - } -} catch (err) { - console.error(err) - server = http.createServer(app); -} - - -export const io = new Server(server, { - path: '/ws', - cors: { - origin: "*", - methods: ["GET", "POST"], - } -}); - -io.use(function (socket, next) { - if (socket.handshake.query && socket.handshake.query.token) { - jwt.verify(socket.handshake.query.token, WEBSOCKET_SECRET, function (err, decoded) { - if (err) { - console.log('wrong secret. Check your secrets'); - return next(new Error('Authentication error')); - } - socket.decoded = decoded; - next(); - }); - } else { - - next(new Error('Authentication error')); - } - } -) - -io.on("connection", async (socket) => { - var jwtObj = jwt.decode(socket.handshake.query.token); - for (var i = 0; i < jwtObj.rooms.length; i++) { - socket.join(jwtObj.rooms[i]); - } - var user = loginUser(socket); - - if (user) { - user.initUserAway(); - socket.emit('sendUserStatus', user.getStatus()); - socket.emit('sendUserTimeAway', user.awayTime); - io.emit('sendOnlineUser', JSON.stringify(getOnlineUSer())); - } - - socket.on('disconnect', function () { - websocketState('disconnect', socket, null); - }) - - socket.onAny(function (event, data) { - websocketState(event, socket, data); - }) -}) -app.use(bodyParser.urlencoded({extended: false})); -app.use(bodyParser.json()); -router.post(MERCURE_INTERNAL_URL, (request, response) => { -//code to perform particular action. -//To access POST variable use req.body()methods. - console.log('Receive new Backend Request'); - const authHeader = request.headers.authorization; - if (authHeader) { - const token = authHeader.split(' ')[1]; - - jwt.verify(token, WEBSOCKET_SECRET, (err, user) => { - if (err) { - console.log('Wrong JWT signature'); - return response.sendStatus(403); - } else { - - var data = request.body.data; - var room = request.body.topic; - io.to(room).emit('mercure', data); - - response.end('OK'); - } - }); - } else { - - response.sendStatus(403); - response.end('OK'); - } -}); -router.get(MERCURE_INTERNAL_URL, (request, response) => { -//code to perform particular action. -//To access POST variable use req.body()methods. - return response.sendStatus(200); -}); -router.get('/healthz', (request, response) => { -//code to perform particular action. -//To access POST variable use req.body()methods. - return response.sendStatus(200); -}); - -app.use("/", router); -server.listen(PORT, () => { - console.log('listening on *:' + PORT); -}); \ No newline at end of file diff --git a/nodejs/websocketState.mjs b/nodejs/websocketState.mjs index 07f8cb22b..d612110c5 100644 --- a/nodejs/websocketState.mjs +++ b/nodejs/websocketState.mjs @@ -8,55 +8,67 @@ import { getUserStatus, getUserFromSocket, disconnectUser, - checkEmptySockets, setAwayTime, getStatusForListOfIds + checkEmptySockets, + setAwayTime, + getStatusForListOfIds } from './login.mjs' -import {io} from './websocket.js' -export function websocketState(event, socket, message) { +export async function websocketState(event, socket, message) { switch (event) { case 'disconnect': disconnectUser(socket); - setTimeout(function () { + setTimeout(async function () { if (checkEmptySockets(socket)) { - io.emit('sendOnlineUser', JSON.stringify(getOnlineUSer())); + io.emit('sendOnlineUser', JSON.stringify(await getOnlineUSer())); console.log('Send is Offline'); } - sendStatus(socket); + await sendStatus(socket); }, 7000); break; - case 'login'://fügt den SOcket zu dem USer hinzu. Schickt keine Benachrichtigungen an die anderen Clients + + case 'login': break; - case 'setStatus'://setzt den Status und informiert alle Clients, das sich der Status geändert hat - setStatus(socket, message); - sendStatus(socket); + + case 'setStatus': + await setStatus(socket, message); + await sendStatus(socket); break; + case 'getStatus': - io.emit('sendOnlineUser', JSON.stringify(getOnlineUSer())); + io.emit('sendOnlineUser', JSON.stringify(await getOnlineUSer())); break; + case 'getMyStatus': socket.emit('sendUserStatus', getUserStatus(socket)); break; + case 'stillOnline': stillOnline(socket); break; + case 'enterMeeting': enterMeeting(socket); - sendStatus(socket); + await sendStatus(socket); break; + case 'leaveMeeting': leaveMeeting(socket); - sendStatus(socket); + await sendStatus(socket); break; + case 'openNewIframe': sendNewIframe(socket, message) break; + case 'giveOnlineStatus': getStatusForListOfIds(socket, message); break; + case 'setAwayTime': setAwayTime(socket, message); break; + default: console.log(event); console.log('not known') @@ -64,12 +76,12 @@ export function websocketState(event, socket, message) { } } -function sendStatus(socket) { - sendStatusToOwnUSer(socket); - io.emit('sendOnlineUser', JSON.stringify(getOnlineUSer())); +async function sendStatus(socket) { + await sendStatusToOwnUSer(socket); + io.emit('sendOnlineUser', JSON.stringify(await getOnlineUSer())); } -function sendStatusToOwnUSer(socket) { +async function sendStatusToOwnUSer(socket) { var user = getUserFromSocket(socket) if (user) { user.sendToAllSockets('sendUserStatus', user.getStatus());