mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-05-26 00:55:16 -07:00
fed1bd3b42
- 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
106 lines
2.9 KiB
TypeScript
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);
|
|
},
|
|
};
|
|
}
|