import React, { useCallback, useEffect } from 'react'; import { useSetRecoilState } from 'recoil'; import net from 'net'; import { remote } from 'electron'; import { logLinesState, isLogServerRunningState, logServerPortState, } from '../store'; import config from '../config.json'; import { logsText } from '../helpers/static-text'; import { SocketMessage, SocketMessageType, LogLine, LogType } from '../types'; import { debugConsole } from '../helpers/console-log'; function tryJsonParse<T>(fileText: string) { try { return JSON.parse(fileText) as T; } catch (error) { debugConsole.error(`${logsText.messageParseError}: ${error}`); return fileText; } } function getLogLine(lineText: string): LogLine { const socketMessage = tryJsonParse<SocketMessage>(lineText); if (typeof socketMessage === 'string') { return { type: 'Message', count: 1, id: 0, text: socketMessage, modName: 'Unknown', }; } return { type: SocketMessageType[socketMessage.type] as LogType, count: 1, id: 0, text: socketMessage.message, modName: socketMessage.senderName, }; } function getSimpleLine(text: string, type: LogType = 'Message'): LogLine { return { type, text, count: 1, id: 0, modName: 'Mod Manager', }; } async function showFatalMessageDialog(line: LogLine) { const browserWindow = new remote.BrowserWindow({ show: false, alwaysOnTop: true, }); await remote.dialog.showMessageBox(browserWindow, { type: 'error', title: line.modName, message: line.text, }); browserWindow.destroy(); } export const LogsSubscription: React.FunctionComponent = () => { const setLines = useSetRecoilState(logLinesState); const setIsServerRunning = useSetRecoilState(isLogServerRunningState); const setServerPort = useSetRecoilState(logServerPortState); const writeLogLine = useCallback( (line: LogLine) => { if (line.type === SocketMessageType[SocketMessageType.Fatal]) { showFatalMessageDialog(line); } setLines((prevLines) => { const lastIndex = prevLines.length - 1; const lastItem = prevLines[lastIndex]; if (prevLines.length > 0 && line.text === lastItem.text) { return [ ...prevLines.slice(0, lastIndex), { ...lastItem, count: lastItem.count + 1, }, ]; } return [ ...prevLines, { ...line, id: prevLines.length + 1, }, ].filter((l) => { for (let i = 0; i < config.ignoredErrors.length; i += 1) { if (l.text.includes(config.ignoredErrors[i])) { return false; } } return true; }); }); }, [setLines] ); const writeSimpleText = useCallback( (textLine: string, type?: LogType) => { writeLogLine(getSimpleLine(textLine, type)); }, [writeLogLine] ); useEffect(() => { debugConsole.log('useEffect: LogsSubscription create server'); function writeLogText(...textLines: string[]) { textLines.forEach((textLine) => writeLogLine(getLogLine(textLine))); } function signalServerOpen() { setIsServerRunning(true); writeSimpleText(logsText.connectedToConsole, 'Info'); } function signalServerClosed() { setIsServerRunning(false); writeSimpleText(logsText.disconnectedFromConsole, 'Info'); } const netServer = net.createServer((socket) => { socket.on('data', (data) => { const dataLines = data .toString() .split('\n') .filter((line) => line); writeLogText(...dataLines); }); socket.on('error', (error) => { writeSimpleText( `${logsText.socketError}: ${error.toString()}`, 'Error' ); signalServerClosed(); }); socket.on('end', signalServerClosed); }); netServer.on('connection', signalServerOpen); netServer.on('close', signalServerClosed); netServer.on('listening', () => { const { port } = netServer.address() as net.AddressInfo; writeSimpleText(logsText.consoleServerStart(port), 'Info'); setServerPort(port); }); netServer.listen(0, '127.0.0.1'); return signalServerClosed; }, [writeLogLine, writeSimpleText, setServerPort, setIsServerRunning]); return null; };