Files
lsp/services/log.service.js
2025-09-30 19:05:40 +02:00

124 lines
3.1 KiB
JavaScript

// services/logService.js
const WebSocket = require('ws');
const decodeURIComponentSafe = str => {
try {
return decodeURIComponent(str);
} catch {
return str; // fallback
}
};
const wssList = []; // all WS servers
const sockets = []; // { ws, name }
const originalConsole = {};
let clients = new Map(); // ws => username
// ASCII check
function isAscii(str) {
return /^[\x00-\x7F]*$/.test(str);
}
const attachLogService = async function (server, basePath = '/logs') {
try {
const wss = new WebSocket.Server({ noServer: true });
wssList.push(wss);
// Save original console methods once
["log", "error", "warn", "info", "debug"].forEach(fn => {
if (!originalConsole[fn]) originalConsole[fn] = console[fn];
});
// Override console methods (only once)
if (!originalConsole._overridden) {
["log", "error", "warn", "info", "debug"].forEach(fn => {
console[fn] = (...args) => {
originalConsole[fn].apply(console, args);
const msg = args.map(a => (typeof a === "string" ? a : JSON.stringify(a))).join(" ");
broadcast({ type: fn, message: msg, client: "server" });
};
});
originalConsole._overridden = true;
}
// Handle HTTP upgrade requests
server.on("upgrade", (request, socket, head) => {
const urlParts = request.url.split('/').filter(Boolean); // e.g. ['logs','😎']
if (urlParts[0] === basePath.replace(/^\/+/, '') && urlParts[1]) {
// Decode URL safely, preserving emojis
const clientName = decodeURIComponentSafe(urlParts[1]);
wss.handleUpgrade(request, socket, head, ws => {
ws.clientName = clientName;
wss.emit('connection', ws, request);
clients.set(ws, clientName);
sendClientsUpdate();
});
} else {
socket.destroy();
}
});
// WS connection
wss.on('connection', ws => {
sockets.push(ws);
ws.on("message", rawMsg => {
const msg = rawMsg.toString();
broadcast({ type: "message", message: msg, client: ws.clientName });
});
ws.on("close", () => {
const index = sockets.indexOf(ws);
if (index !== -1) sockets.splice(index, 1);
clients.delete(ws);
sendClientsUpdate();
});
});
return { success: true };
} catch (e) {
throw new Error(e.message);
}
};
const detachLogService = async function () {
try {
Object.keys(originalConsole).forEach(fn => {
if (fn !== "_overridden") console[fn] = originalConsole[fn];
});
sockets.forEach(ws => ws.close());
sockets.length = 0;
wssList.forEach(wss => wss.close());
wssList.length = 0;
return { success: true };
} catch (e) {
throw new Error(e.message);
}
};
// Broadcast helper
function broadcast(obj) {
// Add timestamp
obj.timestamp = new Date().toISOString();
const msg = JSON.stringify(obj);
sockets.forEach(ws => {
if (ws.readyState === WebSocket.OPEN) ws.send(msg);
});
}
function sendClientsUpdate() {
const clientNames = Array.from(clients.values());
broadcast({ clients: clientNames });
}
module.exports = {
attachLogService,
detachLogService,
};