mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-06-16 15:13:31 -07:00
111 lines
2.8 KiB
TypeScript
111 lines
2.8 KiB
TypeScript
import fs from 'node:fs';
|
|
import { execFileSync } from 'node:child_process';
|
|
import path from 'node:path';
|
|
|
|
export type BackgroundStatsServerState = {
|
|
pid: number;
|
|
port: number;
|
|
startedAtMs: number;
|
|
};
|
|
|
|
export function readBackgroundStatsServerState(
|
|
statePath: string,
|
|
): BackgroundStatsServerState | null {
|
|
try {
|
|
const raw = JSON.parse(
|
|
fs.readFileSync(statePath, 'utf8'),
|
|
) as Partial<BackgroundStatsServerState>;
|
|
const pid = raw.pid;
|
|
const port = raw.port;
|
|
const startedAtMs = raw.startedAtMs;
|
|
if (
|
|
typeof pid !== 'number' ||
|
|
!Number.isInteger(pid) ||
|
|
pid <= 0 ||
|
|
typeof port !== 'number' ||
|
|
!Number.isInteger(port) ||
|
|
port <= 0 ||
|
|
typeof startedAtMs !== 'number' ||
|
|
!Number.isInteger(startedAtMs) ||
|
|
startedAtMs <= 0
|
|
) {
|
|
return null;
|
|
}
|
|
return {
|
|
pid,
|
|
port,
|
|
startedAtMs,
|
|
};
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
export function writeBackgroundStatsServerState(
|
|
statePath: string,
|
|
state: BackgroundStatsServerState,
|
|
): void {
|
|
fs.mkdirSync(path.dirname(statePath), { recursive: true });
|
|
fs.writeFileSync(statePath, JSON.stringify(state, null, 2), 'utf8');
|
|
}
|
|
|
|
export function removeBackgroundStatsServerState(statePath: string): void {
|
|
try {
|
|
fs.rmSync(statePath, { force: true });
|
|
} catch {
|
|
// ignore
|
|
}
|
|
}
|
|
|
|
export function isBackgroundStatsServerProcessAlive(pid: number): boolean {
|
|
try {
|
|
process.kill(pid, 0);
|
|
return true;
|
|
} catch {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
function readProcessStartedAtMs(pid: number): number | null {
|
|
try {
|
|
if (process.platform === 'win32') {
|
|
const output = execFileSync(
|
|
'powershell.exe',
|
|
[
|
|
'-NoProfile',
|
|
'-Command',
|
|
`(Get-CimInstance Win32_Process -Filter "ProcessId=${pid}").CreationDate.ToUniversalTime().ToString("o")`,
|
|
],
|
|
{ encoding: 'utf8', timeout: 1000 },
|
|
).trim();
|
|
const parsed = Date.parse(output);
|
|
return Number.isFinite(parsed) ? parsed : null;
|
|
}
|
|
|
|
const output = execFileSync('ps', ['-o', 'lstart=', '-p', String(pid)], {
|
|
encoding: 'utf8',
|
|
timeout: 1000,
|
|
}).trim();
|
|
const parsed = Date.parse(output);
|
|
return Number.isFinite(parsed) ? parsed : null;
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
export function verifyBackgroundStatsServerIdentity(pid: number, startedAtMs: number): boolean {
|
|
const processStartedAtMs = readProcessStartedAtMs(pid);
|
|
if (processStartedAtMs === null) {
|
|
return false;
|
|
}
|
|
const earliestAllowedStateWriteMs = processStartedAtMs;
|
|
const latestAllowedStateWriteMs = processStartedAtMs + 60_000;
|
|
return startedAtMs >= earliestAllowedStateWriteMs && startedAtMs <= latestAllowedStateWriteMs;
|
|
}
|
|
|
|
export function resolveBackgroundStatsServerUrl(
|
|
state: Pick<BackgroundStatsServerState, 'port'>,
|
|
): string {
|
|
return `http://127.0.0.1:${state.port}`;
|
|
}
|