agent-smith/dist-in/commons/websocket.js
2026-02-26 19:41:09 +01:00

228 lines
20 KiB
JavaScript

import { WebSocketServer, WebSocket } from 'ws';
import fs from 'fs';
import path from 'path';
import chokidar from 'chokidar';
export class WebSocketManager {
static instance;
wss = null;
handlers = new Map();
writeQueue = Promise.resolve();
constructor() {
// Register default handlers
this.registerHandler('log', this.handleLog.bind(this));
this.registerHandler('echo', (ws, payload) => ws.send(JSON.stringify({ type: 'echo', payload })));
this.registerHandler('ping', (ws, payload) => ws.send(JSON.stringify({ type: 'pong', id: payload.id })));
}
static getInstance() {
if (!WebSocketManager.instance) {
WebSocketManager.instance = new WebSocketManager();
}
return WebSocketManager.instance;
}
init(server) {
if (this.wss) {
console.warn('WebSocketServer already initialized');
return;
}
this.wss = new WebSocketServer({ server, path: '/ws' });
this.wss.on('connection', (ws) => {
ws.on('message', (message) => {
try {
const data = JSON.parse(message.toString());
const { command, ...payload } = data;
if (command && this.handlers.has(command)) {
this.handlers.get(command)(ws, payload);
}
else {
console.warn('Unknown command:', command);
}
}
catch (err) {
console.error('Failed to parse message:', err);
}
});
ws.on('close', () => {
});
ws.on('error', (err) => {
console.error('WebSocket error:', err);
});
});
this.initWatcher();
}
initWatcher() {
// Watch for changes in canvas-page-new.json
const logDir = path.join(process.cwd(), 'data');
// Ensure log directory exists
if (!fs.existsSync(logDir)) {
try {
fs.mkdirSync(logDir, { recursive: true });
}
catch (err) {
console.error('Failed to create log directory for watcher:', err);
}
}
const handleFile = async (filePath) => {
// Ignore output files (logs) to prevent infinite loops (Frontend -> Log -> Watcher -> Frontend -> Loop)
const fileName = path.basename(filePath);
const ext = path.extname(filePath).toLowerCase();
// Explicitly allow only specific JSON files (layouts) to trigger updates
// Ignore everything else (logs, dumps, etc.)
if (ext === '.json') {
if (fileName !== 'canvas-page-latest-new.json' && fileName !== 'canvas-page-new.json') {
return;
}
}
else if (fileName.startsWith('canvas-html-latest')) {
return;
}
console.log(`[Watcher] File detected: ${filePath}`);
try {
const ext = path.extname(filePath).toLowerCase();
if (ext === '.json') {
const content = await fs.promises.readFile(filePath, 'utf-8');
if (!content.trim())
return; // Ignore empty writes
try {
const layoutData = JSON.parse(content);
console.log('Broadcasting layout-update (json)...');
this.broadcast({
type: 'layout-update',
data: layoutData
});
}
catch (parseErr) {
console.error(`Failed to parse watched JSON file: ${filePath}`, parseErr);
}
}
else if (ext === '.html' || ext === '.md') {
const content = await fs.promises.readFile(filePath, 'base64');
console.log(`Broadcasting layout-update (${ext})...`);
this.broadcast({
type: 'layout-update',
data: content
});
}
}
catch (err) {
console.error(`Failed to process watched file ${filePath}:`, err);
}
};
chokidar.watch(logDir, {
persistent: true,
ignoreInitial: false,
awaitWriteFinish: {
stabilityThreshold: 100,
pollInterval: 100
}
})
.on('add', handleFile)
.on('change', handleFile);
}
registerHandler(command, handler) {
this.handlers.set(command, handler);
}
broadcast(message) {
if (!this.wss)
return;
const data = JSON.stringify(message);
this.wss.clients.forEach((client) => {
if (client.readyState === WebSocket.OPEN) {
client.send(data);
}
});
}
handleLog(ws, payload) {
// Expected payload: { name: string, options?: { mode?: 'append'|'overwrite', format?: 'json'|'html'|'md' }, message: any, ...others }
const { name, id, options, ...logData } = payload;
if (!name) {
console.warn('Log command missing "name" field');
return;
}
const mode = options?.mode || 'append';
const format = options?.format || 'json';
const logDir = path.join(process.cwd(), 'data');
const extension = format === 'md' ? 'md' : format === 'html' ? 'html' : 'json';
const logFile = path.join(logDir, `${name}.${extension}`);
// Ensure log directory exists
if (!fs.existsSync(logDir)) {
try {
fs.mkdirSync(logDir, { recursive: true });
}
catch (err) {
console.error('Failed to create log directory:', err);
return;
}
}
// Serialize writes using the queue
this.writeQueue = this.writeQueue.then(async () => {
try {
if (format === 'json') {
if (mode === 'overwrite') {
// For overwrite (state capture), write only the message content if available
const content = (logData.message !== undefined) ? logData.message : logData;
const contentToWrite = JSON.stringify(content, null, 2);
await fs.promises.writeFile(logFile, contentToWrite);
}
else {
// For append (logging), read existing, parse, append to array, write back
let records = [];
try {
if (fs.existsSync(logFile)) {
const fileContent = await fs.promises.readFile(logFile, 'utf-8');
if (fileContent.trim()) {
try {
const parsed = JSON.parse(fileContent);
if (Array.isArray(parsed)) {
records = parsed;
}
else {
records = [parsed];
}
}
catch (e) {
// Attempt to parse as NDJSON (newline delimited JSON)
records = fileContent.split('\n')
.filter(line => line.trim())
.map(line => {
try {
return JSON.parse(line);
}
catch {
return null;
}
})
.filter(item => item !== null);
}
}
}
}
catch (readErr) {
console.warn(`Failed to read log file ${logFile}, starting fresh.`, readErr);
}
const logEntry = {
timestamp: new Date().toISOString(),
...logData
};
records.push(logEntry);
await fs.promises.writeFile(logFile, JSON.stringify(records, null, 2));
}
}
else {
// HTML or MD
const message = logData.message;
const content = typeof message === 'string' ? message : JSON.stringify(message);
if (mode === 'append') {
await fs.promises.appendFile(logFile, content + '\n');
}
else {
await fs.promises.writeFile(logFile, content);
}
}
}
catch (err) {
console.error(`Failed to write log file ${logFile}:`, err);
}
});
}
}
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoid2Vic29ja2V0LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL2NvbW1vbnMvd2Vic29ja2V0LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sRUFBRSxlQUFlLEVBQUUsU0FBUyxFQUFFLE1BQU0sSUFBSSxDQUFDO0FBRWhELE9BQU8sRUFBRSxNQUFNLElBQUksQ0FBQztBQUNwQixPQUFPLElBQUksTUFBTSxNQUFNLENBQUM7QUFDeEIsT0FBTyxRQUFRLE1BQU0sVUFBVSxDQUFDO0FBSWhDLE1BQU0sT0FBTyxnQkFBZ0I7SUFDakIsTUFBTSxDQUFDLFFBQVEsQ0FBbUI7SUFDbEMsR0FBRyxHQUEyQixJQUFJLENBQUM7SUFDbkMsUUFBUSxHQUFnQyxJQUFJLEdBQUcsRUFBRSxDQUFDO0lBQ2xELFVBQVUsR0FBa0IsT0FBTyxDQUFDLE9BQU8sRUFBRSxDQUFDO0lBRXREO1FBQ0ksNEJBQTRCO1FBQzVCLElBQUksQ0FBQyxlQUFlLENBQUMsS0FBSyxFQUFFLElBQUksQ0FBQyxTQUFTLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUM7UUFDdkQsSUFBSSxDQUFDLGVBQWUsQ0FBQyxNQUFNLEVBQUUsQ0FBQyxFQUFFLEVBQUUsT0FBTyxFQUFFLEVBQUUsQ0FBQyxFQUFFLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxTQUFTLENBQUMsRUFBRSxJQUFJLEVBQUUsTUFBTSxFQUFFLE9BQU8sRUFBRSxDQUFDLENBQUMsQ0FBQyxDQUFDO1FBQ2xHLElBQUksQ0FBQyxlQUFlLENBQUMsTUFBTSxFQUFFLENBQUMsRUFBRSxFQUFFLE9BQU8sRUFBRSxFQUFFLENBQUMsRUFBRSxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsU0FBUyxDQUFDLEVBQUUsSUFBSSxFQUFFLE1BQU0sRUFBRSxFQUFFLEVBQUUsT0FBTyxDQUFDLEVBQUUsRUFBRSxDQUFDLENBQUMsQ0FBQyxDQUFDO0lBQzdHLENBQUM7SUFFTSxNQUFNLENBQUMs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