Mã nguồn đã công bố
Tải mã nguồn dạng ZIP
CH-J Server Manager
Duyệt thư mục và tệp của một bản phát hành ứng dụng cụ thể.
CH-J / Server Manager
368 tệp
core-20261017T201717Z-057348c4a70f
Ngày thêm: Chưa ghi nhận ngày · Ngày dựng:
1
"use strict";3
const { execFile } = require("node:child_process");4
const net = require("node:net");5
const { performance } = require("node:perf_hooks");7
const PING_COUNT = 3;8
// Reply lines only (they carry TTL/hlim); statistics lines such as "Minimum = 5ms" are ignored.9
const REPLY_LINE = /\b(?:ttl|hlim)\s*=\s*\d+/i;10
const REPLY_TIME = /([=<])\s*(\d+(?:[.,]\d+)?)\s*ms\b/i;12
function median(values) {13
const sorted = [...values].sort((left, right) => left - right);14
if (!sorted.length) return null;15
const middle = Math.floor(sorted.length / 2);16
return sorted.length % 2 ? sorted[middle] : (sorted[middle - 1] + sorted[middle]) / 2;17
}19
// Parses per-reply times from macOS, Linux (iputils) and localized Windows ping output.20
// "<1ms" (Windows) is an upper bound and is reported as 1 ms with lessThan=true.21
function parsePingReplies(stdout) {22
const replies = [];23
for (const line of String(stdout || "").split(/\r?\n/)) {24
if (!REPLY_LINE.test(line)) continue;25
const match = line.match(REPLY_TIME);26
if (!match) continue;27
const value = Number(match[2].replace(",", "."));28
if (Number.isFinite(value) && value >= 0) replies.push({ ms: match[1] === "<" ? Math.max(value, 1) : value, lessThan: match[1] === "<" });29
}30
return replies;31
}33
// First exact reply time (kept for callers that need a single value).34
function parsePing(stdout) {35
const reply = parsePingReplies(stdout).find((entry) => !entry.lessThan && entry.ms > 0);36
return reply ? reply.ms : null;37
}39
function classifyFailure(error, output) {40
const text = `${output || ""}\n${error?.message || ""}`;41
if (error?.code === "ENOENT") return { status: "unsupported", reason: "The ping utility is not available on this computer." };42
if (/Operation not permitted|Permission denied|Access is denied|not allowed/i.test(text)) return { status: "denied", reason: "The operating system denied sending ICMP (for example the macOS Local Network permission or a security policy)." };43
if (/unknown host|cannot resolve|could not find host|Name or service not known/i.test(text)) return { status: "error", reason: "The address could not be resolved for ping." };44
if (/No route to host|Network is unreachable|Destination (host|net) unreachable|unreachable|nedosažiteln|nicht erreichbar/i.test(text)) return { status: "unreachable", reason: "The target is unreachable for ICMP (no route or an unreachable reply)." };45
return { status: "timeout", reason: "No ICMP echo reply before the timeout; the server or a firewall may block ICMP." };46
}48
function pingCommand(address, platform) {49
const ipv6 = net.isIP(address) === 6;50
if (platform === "win32") return { executable: "ping.exe", args: [ipv6 ? "-6" : "-4", "-n", String(PING_COUNT), "-w", "2000", address] };51
if (platform === "darwin") return { executable: ipv6 ? "/sbin/ping6" : "/sbin/ping", args: ["-n", "-c", String(PING_COUNT), ...(ipv6 ? [] : ["-W", "2000"]), address] };52
return { executable: "ping", args: [ipv6 ? "-6" : "-4", "-n", "-c", String(PING_COUNT), "-W", "2", address] };53
}55
// Local ICMP echo from the CH-J computer to the address actually used by the SSH connection (IPv4 or IPv6).56
// Arguments are passed separately without a shell; no root or sudo is needed on macOS, Linux or Windows.57
function measurePing(address, { signal, platform = process.platform, run = execFile } = {}) {58
const family = net.isIP(address);59
if (!family) return Promise.resolve({ ms: null, status: "error", reason: "No IP address is known for this session.", family: null, sent: 0, received: 0, loss: null });60
if (!["win32", "darwin", "linux"].includes(platform)) return Promise.resolve({ ms: null, status: "unsupported", reason: `Ping is not supported on ${platform}.`, family, sent: 0, received: 0, loss: null });61
const { executable, args } = pingCommand(address, platform);62
return new Promise((resolve) => {63
run(executable, args, { shell: false, windowsHide: true, timeout: 9000, maxBuffer: 64 * 1024, signal, env: { ...process.env, LC_ALL: "C", LANG: "C" } },64
(error, stdout, stderr) => {65
const replies = parsePingReplies(stdout);66
const received = Math.min(replies.length, PING_COUNT);67
if (received) {68
resolve({ ms: median(replies.map((reply) => reply.ms)), lessThan: replies.every((reply) => reply.lessThan), status: "ok", reason: "", family, sent: PING_COUNT, received, loss: (PING_COUNT - received) / PING_COUNT });69
return;70
}71
if (error?.name === "AbortError") { resolve({ ms: null, status: "aborted", reason: "", family, sent: 0, received: 0, loss: null }); return; }72
resolve({ ms: null, ...classifyFailure(error, `${stdout || ""}\n${stderr || ""}`), family, sent: PING_COUNT, received: 0, loss: error?.killed ? null : 1 });73
});74
});75
}77
class LatencyMonitor {78
constructor({ ping = measurePing, now = () => performance.now(), intervalMs = 10000, sshSamples = 3 } = {}) {79
this.ping = ping;80
this.now = now;81
this.intervalMs = intervalMs;82
this.sshSamples = sshSamples;83
}85
// request() performs one SSH round trip. It may resolve with { ms, method }; otherwise the elapsed86
// monotonic time around the call is used. Samples run sequentially so channels are never flooded.87
async _ssh(request) {88
const samples = [];89
let method = null;90
let error = null;91
for (let index = 0; index < this.sshSamples; index += 1) {92
const started = this.now();93
try {94
const result = await request();95
const ms = typeof result?.ms === "number" ? result.ms : this.now() - started;96
method = result?.method || method || "command";97
if (Number.isFinite(ms) && ms > 0) samples.push(ms);98
} catch (cause) { error = cause; break; }99
}100
return { ms: median(samples), method, samples: samples.length, error: samples.length ? null : (error?.message || "SSH round trip failed") };101
}103
start(record, request, publish) {104
const controller = new AbortController();105
let timer;106
let stopped = false;107
const sample = async () => {108
// ICMP and SSH are independent: a blocked ping never hides the SSH measurement and vice versa.109
const [ping, ssh] = await Promise.all([110
Promise.resolve().then(() => this.ping(record.address, { signal: controller.signal })).catch((error) => ({ ms: null, status: "error", reason: error?.message || String(error) })),111
this._ssh(request)112
]);113
if (stopped) return;114
const pingResult = typeof ping === "number" ? { ms: ping, status: "ok" } : (ping || { ms: null, status: "timeout" });115
const valid = (value) => typeof value === "number" && Number.isFinite(value) && value > 0 ? value : null;116
publish({117
pingMs: valid(pingResult.ms),118
pingStatus: pingResult.status || "error",119
pingReason: pingResult.reason || "",120
pingLoss: typeof pingResult.loss === "number" ? pingResult.loss : null,121
pingFamily: pingResult.family || net.isIP(record.address) || null,122
pingLessThan: pingResult.lessThan === true,123
sshRttMs: valid(ssh.ms),124
sshRttMethod: ssh.method,125
sshRttSamples: ssh.samples,126
sshRttError: ssh.error,127
measuredAt: new Date().toISOString()128
});129
// Schedule only after both finish: no overlapping probes or intervals.130
timer = setTimeout(sample, this.intervalMs);131
timer.unref?.();132
};133
void sample();134
return () => { stopped = true; clearTimeout(timer); controller.abort(); };135
}136
}138
module.exports = { LatencyMonitor, PING_COUNT, measurePing, median, parsePing, parsePingReplies, pingCommand };SHA-256
3ed95d5d82915d7ca2cfe3849f4d4a2fc033b8990f31748a0f1e1804a87da163CH-J Proprietary Software License 1.14 · Mã nguồn được cung cấp theo CH-J Proprietary Software License 1.14. Việc công khai mã nguồn không thay đổi các điều khoản giấy phép và không cấp thêm quyền nào.
SHA-256 của tệp nén
cf4455b698f0f28e456a37b1834976a0194d8486acb7101052d59a838745cca3