123456789const DEFAULT_PROBE_TIMEOUT_MS = 500;10const DEFAULT_PROBE_BATCH = 16;1112export interface PortProbeOptions {13 batch?: number;14 timeoutMs?: number;15 16 probeOne?: (ip: string, port: number, timeoutMs: number) => Promise<boolean>;17}181920212223export async function probeSubnetForPort(24 subnet: string,25 port: number,26 options: PortProbeOptions = {},27): Promise<string | null> {28 const batchSize = options.batch ?? DEFAULT_PROBE_BATCH;29 const timeoutMs = options.timeoutMs ?? DEFAULT_PROBE_TIMEOUT_MS;30 const probe = options.probeOne ?? wsPortOpen;3132 for (let start = 1; start <= 254; start += batchSize) {33 const end = Math.min(start + batchSize - 1, 254);34 const ips: string[] = [];35 for (let i = start; i <= end; i++) ips.push(`${subnet}.${i}`);3637 const results = await Promise.all(38 ips.map(async (ip) => ((await probe(ip, port, timeoutMs)) ? ip : null)),39 );40 const hit = results.find((r): r is string => r !== null);41 if (hit) return hit;42 }43 return null;44}454647function wsPortOpen(ip: string, port: number, timeoutMs: number): Promise<boolean> {48 return new Promise((resolve) => {49 let ws: WebSocket;50 let settled = false;51 const done = (ok: boolean) => {52 if (settled) return;53 settled = true;54 try { ws.close(); } catch { }55 resolve(ok);56 };57 try {58 ws = new WebSocket(`ws://${ip}:${port}/`);59 } catch {60 resolve(false);61 return;62 }63 const t = setTimeout(() => done(false), timeoutMs);64 ws.onopen = () => { clearTimeout(t); done(true); };65 ws.onerror = () => { clearTimeout(t); done(false); };66 ws.onclose = () => { clearTimeout(t); done(false); };67 });68}