UNPKG

@needle-tools/networking

Version:
202 lines (185 loc) 6.25 kB
const { saveBlob } = require("./storage.js"); (() => { // import types const Room = require("./room"); const Utils = require("./networking_utils"); const ClientSettings = require("./clientSettings"); const SchemesUtils = require("./schemeUtility"); // max users let currentUserCount = 0; let maxConcurrentUsers = 50; let defaultRoomTimeoutInSeconds = 30; /** * @param {import("express").Express} app * @param {import("../@types/index").NetworkingOptions} options */ module.exports.init = function (app, options) { if (options) { if (options.maxUsers !== undefined && typeof options.maxUsers === "number") { maxConcurrentUsers = options.maxUsers; } if (options.defaultUserTimeout !== undefined && options.defaultUserTimeout > 0) { defaultRoomTimeoutInSeconds = options.defaultUserTimeout; if (options.defaultUserTimeout < 5) console.warn("User Room timeout is set very low: " + options.defaultUserTimeout + " seconds. Consider increasing it to ~30 seconds or more."); } } // close room after x minutes const idleTime = 5 * 60 * 1000; setInterval(function () { module.exports.validateRooms(idleTime); }, 5000); // save rooms every minute setInterval(function () { module.exports.saveRooms(); }, 10 * 1000); } module.exports.onConnection = async function (ws) { if (ws.id === undefined) ws.id = Utils.getUniqueID(); console.log("new user", ws.id); ws.send( JSON.stringify({ key: "connection-start-info", data: { id: ws.id } }) ); ws.on("close", function (arg) { const room = ws.room; if (!room) return; console.log("Connection closed to " + ws.id); room.remove(ws); }); ws.on("message", function (data) { try { const type = typeof data; if (type === "string") { // JSON messages const obj = JSON.parse(data); if (Array.isArray(obj)) { for (let i = 0; i < obj.length; i++) { const msg = obj[i]; const type = msg.key; const payload = msg.data; const raw = JSON.stringify(msg); handleMessage(ws, type, payload, raw); } } else { const type = obj.key; const payload = obj.data; handleMessage(ws, type, payload, data); } } else { // BINARY messages const payload = SchemesUtils.unpackBinaryIntoPayload(data); handleMessage(ws, payload.key, payload, data); // if(ws.room){ // ws.room.broadcast(ws, data, true); // } // const bb = new flatbuffers.ByteBuffer(data); // const type = bb.getBufferIdentifier(); // handleMessage(ws, type, bb, data); // ws.send(data); } } catch (err) { console.error(err); } }); }; module.exports.getUserCount = function () { let sum = 0; for (const room of rooms) { sum += room.userCount(); } currentUserCount = sum; return sum; }; module.exports.saveRooms = function () { for (let i = rooms.length - 1; i >= 0; i--) { const room = rooms[i]; if (room.userCount() > 0 && room.isDirty) { room.saveState(); } } } module.exports.validateRooms = function (maxAge) { const logThreshold = maxAge * 0.5; for (let i = rooms.length - 1; i >= 0; i--) { const room = rooms[i]; if (!room) { room.splice(i, 1); continue; } room.validateClients(); const age = room.timeSinceLastMessage(); if (age > logThreshold) console.log(`Room ${room.id} time since last update: ${age}/${maxAge}`); if (age > maxAge) { console.log(`Room ${room.id} timeout!`); room.saveState(true); room.close(); rooms.splice(i, 1); } } }; /** @type {import("./room.js")[]} */ const rooms = []; function getOrCreateRoom(roomName, viewOnly) { for (const room of rooms) { if (room.id === roomName) { console.log("found " + roomName); return room; } } //if(viewOnly) return; console.log("open new room: " + roomName); const nr = new Room(roomName); nr.clientTimeout = defaultRoomTimeoutInSeconds; nr.loadState(); rooms.push(nr); return nr; } function handleMessage(ws, type, payload, rawData) { if (type === "join-room") { if (currentUserCount > maxConcurrentUsers) { // server is full console.log("Server is full"); return; } let roomName = payload.room; const viewOnly = payload.viewOnly; // if the request is view only we know that the roomName is encrypted here // we now need to get the original name (e.g. if the room is currently not open we still want to be able to open/load the original) // TODO: add check if the room exists (is either open or was saved) if (viewOnly === true) { roomName = Utils.decryptID(roomName); console.log(roomName); } const room = getOrCreateRoom(roomName, viewOnly); if (room) { const cs = new ClientSettings(viewOnly) room.add(ws, cs); } else { console.warn("Could not find room for " + roomName); } return; } else if (type === "leave-room") { const roomName = payload.room; const room = ws.room; if (room && room.id === roomName) { room.remove(ws); } return; } else if (type === "upload-blob") { const md5 = payload.md5; if (!md5) { return ws.send(JSON.stringify({ key: "upload-blob", data: { error: "missing md5" } })); } } if (ws.room) { ws.room.handleMessage(ws, type, payload, rawData); } else if (type === "ping") { // it is a ping but user is not in a room } else { //console.warn("networking: unhandled message", rawData); } } })();