Files
SubMiner/src/main/runtime/app-control-server.ts
T
sudacode fed1bd3b42 thread launcher config dir through app control and overlay calls
- startOverlay and isRunningAppControlServerAvailable accept explicit configDir to avoid re-resolving from env mid-flight
- emit connection-change on reconnect when previously connected
- handle errored client sockets in app control server with logWarn and destroy
2026-05-21 03:49:15 -07:00

106 lines
2.9 KiB
TypeScript

import fs from 'node:fs';
import net from 'node:net';
import path from 'node:path';
import {
encodeAppControlResponse,
parseAppControlRequestLine,
type AppControlResponse,
} from '../../shared/app-control';
export interface AppControlServerOptions {
socketPath: string;
platform?: NodeJS.Platform;
handleArgv: (argv: string[]) => void;
logDebug?: (message: string) => void;
logWarn?: (message: string, error?: unknown) => void;
}
export interface AppControlServerHandle {
close: () => void;
}
function prepareSocketPath(socketPath: string, platform: NodeJS.Platform): void {
if (platform === 'win32') return;
fs.mkdirSync(path.dirname(socketPath), { recursive: true });
fs.rmSync(socketPath, { force: true });
}
function cleanupSocketPath(socketPath: string, platform: NodeJS.Platform): void {
if (platform === 'win32') return;
try {
fs.rmSync(socketPath, { force: true });
} catch {
// ignore
}
}
function writeResponse(socket: net.Socket, response: AppControlResponse): void {
socket.end(encodeAppControlResponse(response));
}
export function startAppControlServer(options: AppControlServerOptions): AppControlServerHandle {
const platform = options.platform ?? process.platform;
prepareSocketPath(options.socketPath, platform);
const server = net.createServer((socket) => {
let buffer = '';
let byteCount = 0;
let handled = false;
socket.on('error', (error) => {
if (handled) return;
handled = true;
options.logWarn?.('App control client socket error.', error);
socket.destroy();
});
socket.on('data', (chunk) => {
if (handled) return;
byteCount += chunk.length;
buffer += chunk.toString('utf8');
if (byteCount > 65536) {
handled = true;
writeResponse(socket, { ok: false, error: 'App control request too large' });
return;
}
const newlineIndex = buffer.indexOf('\n');
if (newlineIndex < 0) return;
handled = true;
try {
const request = parseAppControlRequestLine(buffer.slice(0, newlineIndex));
options.handleArgv(request.argv);
writeResponse(socket, { ok: true });
} catch (error) {
options.logWarn?.('Failed to handle app control command.', error);
writeResponse(socket, {
ok: false,
error: error instanceof Error ? error.message : String(error),
});
}
});
});
server.on('error', (error) => {
options.logWarn?.(`App control socket failed: ${options.socketPath}`, error);
});
server.listen(options.socketPath, () => {
options.logDebug?.(`App control socket listening: ${options.socketPath}`);
});
let closed = false;
return {
close: () => {
if (closed) return;
closed = true;
try {
server.close();
} catch {
// ignore
}
cleanupSocketPath(options.socketPath, platform);
},
};
}