124 lines
3.1 KiB
JavaScript
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,
|
|
};
|