ts-ping
Version:
A modern TypeScript library for performing ICMP ping operations with type-safe results and fluent configuration.
1,055 lines (1,050 loc) • 32 kB
JavaScript
"use strict";
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/index.ts
var index_exports = {};
__export(index_exports, {
Ping: () => Ping,
PingError: () => PingError,
PingErrorUtils: () => PingErrorUtils,
PingResult: () => PingResult,
PingResultLine: () => PingResultLine,
PingStream: () => PingStream,
combineAsyncIterators: () => combineAsyncIterators
});
module.exports = __toCommonJS(index_exports);
// src/ping-result.ts
var PingResultLine = class _PingResultLine {
rawLine;
timeInMs;
constructor(line = "", timeInMs = 0) {
this.rawLine = line.trim();
this.timeInMs = timeInMs;
}
static fromLine(line) {
let timeInMs = 0;
const match = line.match(/time([<=]+)([0-9.]+)\s*ms/i);
if (match && match[2]) {
timeInMs = Number.parseFloat(match[2]);
}
return new _PingResultLine(line, timeInMs);
}
getRawLine() {
return this.rawLine;
}
getTimeInMs() {
return this.timeInMs;
}
toArray() {
return {
line: this.rawLine,
time_in_ms: this.timeInMs
};
}
toString() {
return this.rawLine;
}
};
var PingError = {
HostnameNotFound: "HostnameNotFound",
HostUnreachable: "HostUnreachable",
PermissionDenied: "PermissionDenied",
Timeout: "Timeout",
UnknownError: "UnknownError"
};
var PingErrorUtils = {
from: (value) => {
return Object.values(PingError).includes(value) ? value : PingError.UnknownError;
}
};
var PingResult = class _PingResult {
success;
error;
host;
packetLossPercentage;
numberOfPacketsTransmitted;
numberOfPacketsReceived;
timeoutInSeconds;
intervalInSeconds;
packetSizeInBytes;
ttl;
ipVersion;
minimumTimeInMs;
maximumTimeInMs;
averageTimeInMs;
standardDeviationTimeInMs;
rawOutput;
lines;
constructor(data) {
this.success = data.success;
this.error = data.error;
this.host = data.host;
this.packetLossPercentage = data.packetLossPercentage;
this.numberOfPacketsTransmitted = data.numberOfPacketsTransmitted;
this.numberOfPacketsReceived = data.numberOfPacketsReceived;
this.timeoutInSeconds = data.timeoutInSeconds;
this.intervalInSeconds = data.intervalInSeconds;
this.packetSizeInBytes = data.packetSizeInBytes;
this.ttl = data.ttl;
this.ipVersion = data.ipVersion;
this.minimumTimeInMs = data.minimumTimeInMs;
this.maximumTimeInMs = data.maximumTimeInMs;
this.averageTimeInMs = data.averageTimeInMs;
this.standardDeviationTimeInMs = data.standardDeviationTimeInMs;
this.rawOutput = data.rawOutput;
this.lines = data.lines;
}
isSuccess() {
return this.success;
}
isFailure() {
return !this.success;
}
static fromPingOutput({ output, returnCode, host, timeout, interval, packetSize, ttl, ipVersion }) {
const rawOutput = output.join("\n");
if (returnCode !== 0) {
const error = _PingResult.determineErrorFromOutput(rawOutput);
return new _PingResult({
success: false,
error,
host,
packetLossPercentage: 100,
numberOfPacketsTransmitted: null,
numberOfPacketsReceived: null,
timeoutInSeconds: timeout,
intervalInSeconds: interval,
packetSizeInBytes: packetSize,
ttl,
ipVersion,
minimumTimeInMs: null,
maximumTimeInMs: null,
averageTimeInMs: null,
standardDeviationTimeInMs: null,
rawOutput,
lines: []
});
}
const lines = _PingResult.parsePingLines(output);
let packetLossPercentage = 0;
let numberOfPacketsTransmitted = null;
let numberOfPacketsReceived = null;
let minimumTimeInMs = null;
let maximumTimeInMs = null;
let averageTimeInMs = null;
let standardDeviationTimeInMs = null;
const packetMatch = rawOutput.match(/(\d+)\s+packets?\s+transmitted,\s+(\d+)\s+(?:packets?\s+)?received/i);
if (packetMatch && packetMatch[1] && packetMatch[2]) {
const transmitted = Number.parseInt(packetMatch[1], 10);
const received = Number.parseInt(packetMatch[2], 10);
numberOfPacketsTransmitted = transmitted;
numberOfPacketsReceived = received;
packetLossPercentage = _PingResult.calculatePacketLossPercentage(transmitted, received);
}
const timingMatch = rawOutput.match(/min\/avg\/max\/(?:stddev|mdev)\s*=\s*([0-9.]+)\/([0-9.]+)\/([0-9.]+)\/([0-9.]+)\s*ms/i);
if (timingMatch && timingMatch[1] && timingMatch[2] && timingMatch[3] && timingMatch[4]) {
minimumTimeInMs = Number.parseFloat(timingMatch[1]);
averageTimeInMs = Number.parseFloat(timingMatch[2]);
maximumTimeInMs = Number.parseFloat(timingMatch[3]);
standardDeviationTimeInMs = Number.parseFloat(timingMatch[4]);
}
if (numberOfPacketsTransmitted === null) {
const lossMatch = rawOutput.match(/(\d+)%\s*(packet\s*)?loss/i);
if (lossMatch && lossMatch[1]) {
packetLossPercentage = Number.parseInt(lossMatch[1], 10);
}
}
const success = packetLossPercentage < 100;
return new _PingResult({
success,
error: null,
host,
packetLossPercentage,
numberOfPacketsTransmitted,
numberOfPacketsReceived,
timeoutInSeconds: timeout,
intervalInSeconds: interval,
packetSizeInBytes: packetSize,
ttl,
ipVersion,
minimumTimeInMs,
maximumTimeInMs,
averageTimeInMs,
standardDeviationTimeInMs,
rawOutput,
lines
});
}
/**
* Creates a failed PingResult from an error.
* Used when ping operations throw exceptions.
*/
static fromError(error, host, options) {
const errorType = _PingResult.determineErrorFromMessage(error.message);
return new _PingResult({
success: false,
error: errorType,
host,
packetLossPercentage: 100,
numberOfPacketsTransmitted: null,
numberOfPacketsReceived: null,
timeoutInSeconds: options.timeout,
intervalInSeconds: options.interval,
packetSizeInBytes: options.packetSize,
ttl: options.ttl,
ipVersion: options.ipVersion,
minimumTimeInMs: null,
maximumTimeInMs: null,
averageTimeInMs: null,
standardDeviationTimeInMs: null,
rawOutput: `Error: ${error.message}`,
lines: []
});
}
static determineErrorFromMessage(message) {
const lower = message.toLowerCase();
if (lower.includes("timeout") || lower.includes("timed out")) {
return PingError.Timeout;
}
if (lower.includes("unknown host") || lower.includes("name or service not known")) {
return PingError.HostnameNotFound;
}
if (lower.includes("no route to host") || lower.includes("host unreachable")) {
return PingError.HostUnreachable;
}
if (lower.includes("permission denied")) {
return PingError.PermissionDenied;
}
return PingError.UnknownError;
}
static determineErrorFromOutput(output) {
const lower = output.toLowerCase();
if (lower.includes("unknown host") || lower.includes("name or service not known")) {
return PingError.HostnameNotFound;
}
if (lower.includes("no route to host") || lower.includes("host unreachable")) {
return PingError.HostUnreachable;
}
if (lower.includes("permission denied")) {
return PingError.PermissionDenied;
}
if (lower.includes("timeout") || lower.includes("timed out")) {
return PingError.Timeout;
}
return PingError.UnknownError;
}
static parsePingLines(output) {
return output.map((line) => line.trim()).filter((line) => !!line && _PingResult.isPingResponseLine(line)).map((line) => PingResultLine.fromLine(line));
}
static isPingResponseLine(line) {
return /time[<=]+[0-9.]+\s*ms/i.test(line);
}
static calculatePacketLossPercentage(transmitted, received) {
if (transmitted === 0)
return 100;
return Math.round((transmitted - received) / transmitted * 100);
}
averageResponseTimeInMs() {
if (this.averageTimeInMs != null) {
return this.averageTimeInMs;
}
if (this.lines.length === 0) {
return 0;
}
const total = this.lines.reduce((sum, line) => sum + line.timeInMs, 0);
return total / this.lines.length;
}
toArray() {
return {
success: this.success,
error: this.error,
host: this.host,
packet_loss_percentage: this.packetLossPercentage,
packets_transmitted: this.numberOfPacketsTransmitted,
packets_received: this.numberOfPacketsReceived,
options: {
timeout_in_seconds: this.timeoutInSeconds,
interval: this.intervalInSeconds,
packet_size_in_bytes: this.packetSizeInBytes,
ttl: this.ttl,
ip_version: this.ipVersion
},
timings: {
minimum_time_in_ms: this.minimumTimeInMs,
maximum_time_in_ms: this.maximumTimeInMs,
average_time_in_ms: this.averageTimeInMs,
standard_deviation_time_in_ms: this.standardDeviationTimeInMs
},
raw_output: this.rawOutput,
lines: this.lines.map((line) => line.toArray())
};
}
toString() {
return this.rawOutput;
}
};
// src/ping-stream.ts
var PingStream = class {
ping;
constructor(ping) {
this.ping = ping;
}
/**
* Takes only the first N results from the ping stream.
*
* @param n Number of results to take
*
* @example
* ```typescript
* // Take first 5 ping results
* for await (const result of stream.take(5)) {
* console.log(`Ping ${result.isSuccess() ? 'success' : 'failed'}`)
* }
* ```
*/
async *take(n) {
let count = 0;
for await (const result of this.ping.stream()) {
if (count >= n)
break;
yield result;
count++;
}
}
/**
* Skips failed ping results and only yields successful ones.
*
* @example
* ```typescript
* // Only process successful pings
* for await (const result of stream.skipFailures()) {
* console.log(`Response time: ${result.averageResponseTimeInMs()}ms`)
* }
* ```
*/
async *skipFailures() {
for await (const result of this.ping.stream()) {
if (result.isSuccess()) {
yield result;
}
}
}
/**
* Skips successful ping results and only yields failed ones.
* Useful for monitoring and alerting on failures.
*
* @example
* ```typescript
* // Monitor only failures
* for await (const failure of stream.skipSuccesses()) {
* console.error(`Ping failed: ${failure.error}`)
* await sendAlert(failure)
* }
* ```
*/
async *skipSuccesses() {
for await (const result of this.ping.stream()) {
if (result.isFailure()) {
yield result;
}
}
}
/**
* Creates a sliding window of ping results.
* Each yield contains the last N results.
*
* @param size Size of the sliding window
*
* @example
* ```typescript
* // Process results in sliding windows of 5
* for await (const window of stream.window(5)) {
* const avgLatency = window
* .filter(r => r.isSuccess())
* .reduce((sum, r) => sum + r.averageResponseTimeInMs(), 0) / window.length
* console.log(`Window avg: ${avgLatency}ms`)
* }
* ```
*/
async *window(size) {
const window = [];
for await (const result of this.ping.stream()) {
window.push(result);
if (window.length > size) {
window.shift();
}
if (window.length >= size) {
yield [...window];
}
}
}
/**
* Calculates rolling statistics from a sliding window of ping results.
* Only successful pings are included in the statistics.
*
* @param windowSize Size of the sliding window for calculating stats (default: 10)
*
* @example
* ```typescript
* // Monitor network performance with rolling stats
* for await (const stats of stream.rollingStats(20)) {
* if (stats.jitter > 50) {
* console.warn(`High network jitter detected: ${stats.jitter}ms`)
* }
* if (stats.packetLoss > 5) {
* console.error(`Packet loss detected: ${stats.packetLoss}%`)
* }
* }
* ```
*/
async *rollingStats(windowSize = 10) {
const window = [];
const allResults = [];
for await (const result of this.ping.stream()) {
allResults.push(result);
if (result.isSuccess()) {
window.push(result);
if (window.length > windowSize) {
window.shift();
}
if (window.length >= Math.min(3, windowSize)) {
yield this.calculateStats(window, allResults.slice(-windowSize));
}
}
}
}
/**
* Yields results only when they meet a specific condition.
*
* @param predicate Function that determines if a result should be yielded
*
* @example
* ```typescript
* // Only yield results with high latency
* for await (const slowResult of stream.filter(r =>
* r.isSuccess() && r.averageResponseTimeInMs() > 100
* )) {
* console.warn(`Slow response: ${slowResult.averageResponseTimeInMs()}ms`)
* }
* ```
*/
async *filter(predicate) {
for await (const result of this.ping.stream()) {
if (predicate(result)) {
yield result;
}
}
}
/**
* Transforms each ping result using a mapping function.
*
* @param mapper Function to transform each result
*
* @example
* ```typescript
* // Extract only latency values
* for await (const latency of stream.map(r =>
* r.isSuccess() ? r.averageResponseTimeInMs() : null
* )) {
* if (latency !== null) {
* console.log(`Latency: ${latency}ms`)
* }
* }
* ```
*/
async *map(mapper) {
for await (const result of this.ping.stream()) {
yield mapper(result);
}
}
/**
* Groups results into batches and yields when a batch is full or a timeout occurs.
*
* @param batchSize Maximum number of results per batch
* @param timeoutMs Maximum time to wait before yielding a partial batch (default: 5000ms)
*
* @example
* ```typescript
* // Process results in timed batches
* for await (const batch of stream.batchWithTimeout(10, 5000)) {
* console.log(`Processing batch of ${batch.length} results`)
* await processBatch(batch)
* }
* ```
*/
async *batchWithTimeout(batchSize, timeoutMs = 5e3) {
const batch = [];
let timeoutId = null;
const yieldBatch = () => {
if (batch.length > 0) {
const batchToYield = [...batch];
batch.length = 0;
return batchToYield;
}
return null;
};
const resetTimeout = () => {
if (timeoutId) {
clearTimeout(timeoutId);
}
timeoutId = setTimeout(() => {
const batchToYield = yieldBatch();
if (batchToYield) {
}
}, timeoutMs);
};
try {
for await (const result of this.ping.stream()) {
batch.push(result);
if (batch.length >= batchSize) {
if (timeoutId) {
clearTimeout(timeoutId);
timeoutId = null;
}
yield yieldBatch();
} else if (batch.length === 1) {
resetTimeout();
}
}
} finally {
if (timeoutId) {
clearTimeout(timeoutId);
}
}
const finalBatch = yieldBatch();
if (finalBatch) {
yield finalBatch;
}
}
/**
* Calculates statistics from a window of ping results.
*/
calculateStats(successfulResults, allResults) {
const responseTimes = successfulResults.map((r) => r.averageResponseTimeInMs());
const count = responseTimes.length;
if (count === 0) {
return {
count: 0,
average: 0,
minimum: 0,
maximum: 0,
standardDeviation: 0,
jitter: 0,
packetLoss: 100,
timestamp: /* @__PURE__ */ new Date()
};
}
const sum = responseTimes.reduce((a, b) => a + b, 0);
const average = sum / count;
const minimum = Math.min(...responseTimes);
const maximum = Math.max(...responseTimes);
const variance = responseTimes.reduce((acc, time) => {
const diff = time - average;
return acc + diff * diff;
}, 0) / count;
const standardDeviation = Math.sqrt(variance);
const jitter = responseTimes.reduce((acc, time) => {
return acc + Math.abs(time - average);
}, 0) / count;
const totalPings = allResults.length;
const successfulPings = allResults.filter((r) => r.isSuccess()).length;
const packetLoss = totalPings > 0 ? (totalPings - successfulPings) / totalPings * 100 : 0;
return {
count,
average: Math.round(average * 100) / 100,
// Round to 2 decimal places
minimum: Math.round(minimum * 100) / 100,
maximum: Math.round(maximum * 100) / 100,
standardDeviation: Math.round(standardDeviation * 100) / 100,
jitter: Math.round(jitter * 100) / 100,
packetLoss: Math.round(packetLoss * 100) / 100,
timestamp: /* @__PURE__ */ new Date()
};
}
};
async function* combineAsyncIterators(...iterators) {
for (const iterator of iterators) {
for await (const value of iterator) {
yield value;
}
}
}
// src/ping.ts
var import_node_child_process = require("child_process");
var import_node_net = require("net");
var import_node_os = __toESM(require("os"), 1);
var Ping = class _Ping {
hostname;
timeoutInSeconds;
count;
intervalInSeconds;
packetSizeInBytes;
ttl;
ipVersion;
/**
* Optional AbortSignal for external cancellation of ping operations.
* When the signal is aborted, all ongoing and future ping operations will be cancelled.
*
* @example
* ```typescript
* const abortController = new AbortController()
* const ping = new Ping('google.com').setAbortSignal(abortController.signal)
*
* // Cancel after 5 seconds
* setTimeout(() => abortController.abort(), 5000)
*
* // Or use AbortSignal.timeout for simpler timeout-based cancellation
* const ping2 = new Ping('google.com').setAbortSignal(AbortSignal.timeout(5000))
* ```
*/
abortSignal;
currentCommand;
constructor(hostname, timeoutInSeconds = 5, count = 1, intervalInSeconds = 1, packetSizeInBytes = 56, ttl = 64) {
this.hostname = hostname;
this.timeoutInSeconds = timeoutInSeconds;
this.count = count;
this.intervalInSeconds = intervalInSeconds;
this.packetSizeInBytes = packetSizeInBytes;
this.ttl = ttl;
this.ipVersion = this.autoDetectIPVersion(hostname);
this.currentCommand = [];
}
/**
* Auto-detects the IP version based on the hostname.
* Only sets IP version for IPv6 addresses to ensure proper command selection on macOS.
* IPv4 addresses and hostnames default to undefined (system default).
*/
autoDetectIPVersion(hostname) {
if ((0, import_node_net.isIPv6)(hostname)) {
return 6;
}
return void 0;
}
run() {
const command = this.buildPingCommand();
const result = this.executePingCommand(command);
const combinedOutput = this.combineOutputLines(result);
return PingResult.fromPingOutput({
output: combinedOutput,
returnCode: result.status ?? 1,
host: this.hostname,
timeout: this.timeoutInSeconds,
interval: this.intervalInSeconds,
packetSize: this.packetSizeInBytes,
ttl: this.ttl,
ipVersion: this.ipVersion
});
}
async runAsync() {
if (this.abortSignal?.aborted) {
throw new Error("Operation was aborted");
}
const command = this.buildPingCommand();
const result = await this.executePingCommandAsync(command);
const combinedOutput = this.combineOutputLines(result);
return PingResult.fromPingOutput({
output: combinedOutput,
returnCode: result.status ?? 1,
host: this.hostname,
timeout: this.timeoutInSeconds,
interval: this.intervalInSeconds,
packetSize: this.packetSizeInBytes,
ttl: this.ttl,
ipVersion: this.ipVersion
});
}
/**
* Creates an async generator that yields ping results in real-time.
* Useful for continuous monitoring and streaming ping data.
*
* @example
* ```typescript
* const ping = new Ping('google.com').setInterval(1).setCount(0) // infinite
*
* for await (const result of ping.stream()) {
* if (result.isSuccess()) {
* console.log(`${new Date().toISOString()}: ${result.averageTimeInMs()}ms`)
* } else {
* console.error(`Ping failed: ${result.error}`)
* }
* }
* ```
*/
async *stream() {
let sequenceNumber = 0;
const isInfinite = this.count === 0 || this.count === Infinity;
while (true) {
try {
if (this.abortSignal?.aborted) {
break;
}
const result = await this.runSinglePing(sequenceNumber++);
yield result;
if (!isInfinite && sequenceNumber >= this.count) {
break;
}
if (this.intervalInSeconds > 0) {
await this.sleep(this.intervalInSeconds * 1e3);
}
} catch (error) {
if (error instanceof Error && error.message === "Operation was aborted") {
break;
}
yield PingResult.fromError(error, this.hostname, {
timeout: this.timeoutInSeconds,
interval: this.intervalInSeconds,
packetSize: this.packetSizeInBytes,
ttl: this.ttl
});
}
}
}
/**
* Creates an async generator that yields ping results with filtering and transformation.
*
* @param filter Optional filter function to include only specific results
* @param transform Optional transformation function to modify yielded values
*
* @example
* ```typescript
* // Only yield successful pings with latency values
* for await (const latency of ping.streamWithFilter(
* r => r.isSuccess(),
* r => r.averageTimeInMs()
* )) {
* console.log(`Latency: ${latency}ms`)
* }
* ```
*/
async *streamWithFilter(filter, transform) {
for await (const result of this.stream()) {
if (!filter || filter(result)) {
yield transform ? transform(result) : result;
}
}
}
/**
* Creates an async generator that yields batches of ping results.
* Useful for processing results in chunks or implementing backpressure.
*
* @param bufferSize Number of results to collect before yielding a batch
*
* @example
* ```typescript
* for await (const batch of ping.streamBatched(5)) {
* console.log(`Processing batch of ${batch.length} results`)
* const avgLatency = batch
* .filter(r => r.isSuccess())
* .reduce((sum, r) => sum + r.averageTimeInMs(), 0) / batch.length
* console.log(`Average latency: ${avgLatency}ms`)
* }
* ```
*/
async *streamBatched(bufferSize = 10) {
const buffer = [];
for await (const result of this.stream()) {
buffer.push(result);
if (buffer.length >= bufferSize) {
yield [...buffer];
buffer.length = 0;
}
}
if (buffer.length > 0) {
yield buffer;
}
}
/**
* Runs a single ping operation with sequence number.
* Used internally by the streaming methods.
*/
async runSinglePing(_sequenceNumber) {
const singlePing = new _Ping(
this.hostname,
this.timeoutInSeconds,
1,
// Always ping once
this.intervalInSeconds,
this.packetSizeInBytes,
this.ttl
);
if (this.ipVersion) {
singlePing.setIPVersion(this.ipVersion);
}
if (this.abortSignal) {
singlePing.setAbortSignal(this.abortSignal);
}
return await singlePing.runAsync();
}
/**
* Sleep utility for interval timing.
*/
sleep(ms) {
return new Promise((resolve, reject) => {
if (this.abortSignal?.aborted) {
reject(new Error("Operation was aborted"));
return;
}
const timeoutId = setTimeout(resolve, ms);
const abortListener = () => {
clearTimeout(timeoutId);
reject(new Error("Operation was aborted"));
};
if (this.abortSignal) {
this.abortSignal.addEventListener("abort", abortListener, { once: true });
}
setTimeout(() => {
if (this.abortSignal) {
this.abortSignal.removeEventListener("abort", abortListener);
}
}, ms);
});
}
executePingCommand(commandArray) {
const timeout = this.calculateProcessTimeout() * 1e3;
const command = commandArray[0];
if (!command) {
throw new Error("No command specified");
}
return (0, import_node_child_process.spawnSync)(command, commandArray.slice(1), {
encoding: "utf-8",
timeout
});
}
executePingCommandAsync(commandArray) {
return new Promise((resolve, reject) => {
const timeout = this.calculateProcessTimeout() * 1e3;
const command = commandArray[0];
if (!command) {
reject(new Error("No command specified"));
return;
}
if (this.abortSignal?.aborted) {
reject(new Error("Operation was aborted"));
return;
}
let stdout = "";
let stderr = "";
const child = (0, import_node_child_process.spawn)(command, commandArray.slice(1));
const timeoutId = setTimeout(() => {
child.kill("SIGTERM");
reject(new Error(`Ping command timed out after ${timeout}ms`));
}, timeout);
const abortListener = () => {
child.kill("SIGTERM");
clearTimeout(timeoutId);
reject(new Error("Operation was aborted"));
};
if (this.abortSignal) {
this.abortSignal.addEventListener("abort", abortListener);
}
child.stdout.on("data", (data) => {
stdout += data.toString("utf-8");
});
child.stderr.on("data", (data) => {
stderr += data.toString("utf-8");
});
child.on("close", (code, signal) => {
clearTimeout(timeoutId);
if (this.abortSignal) {
this.abortSignal.removeEventListener("abort", abortListener);
}
const result = {
pid: child.pid || 0,
output: [null, stdout, stderr],
stdout,
stderr,
status: code,
signal,
error: void 0
};
resolve(result);
});
child.on("error", (error) => {
clearTimeout(timeoutId);
if (this.abortSignal) {
this.abortSignal.removeEventListener("abort", abortListener);
}
reject(error);
});
});
}
calculateProcessTimeout() {
const totalPingTime = this.count * (this.timeoutInSeconds + this.intervalInSeconds);
return Math.ceil(totalPingTime) + 5;
}
combineOutputLines(result) {
const stdoutLines = result.stdout?.split("\n") || [];
const stderrLines = result.stderr?.split("\n") || [];
return [...stdoutLines, ...stderrLines].filter(Boolean);
}
setTimeout(timeout) {
this.timeoutInSeconds = timeout;
return this;
}
setCount(count) {
this.count = count;
return this;
}
setInterval(interval) {
this.intervalInSeconds = interval;
return this;
}
setPacketSize(size) {
this.packetSizeInBytes = size;
return this;
}
setTtl(ttl) {
this.ttl = ttl;
return this;
}
setIPVersion(version) {
this.ipVersion = version;
return this;
}
setIPv4() {
return this.setIPVersion(4);
}
setIPv6() {
return this.setIPVersion(6);
}
/**
* Sets an AbortSignal for external cancellation of ping operations.
*
* @param abortSignal The AbortSignal to use for cancellation
* @returns This Ping instance for method chaining
*
* @example
* ```typescript
* const abortController = new AbortController()
* const ping = new Ping('google.com').setAbortSignal(abortController.signal)
*
* // Cancel the operation
* abortController.abort()
*
* // Or use timeout-based cancellation
* const ping2 = new Ping('google.com').setAbortSignal(AbortSignal.timeout(5000))
* ```
*/
setAbortSignal(abortSignal) {
this.abortSignal = abortSignal;
return this;
}
buildPingCommand() {
return this.startWithPingCommand().addIPVersionOption().addPacketCountOption().addTimeoutOption().addOptionalIntervalOption().addOptionalPacketSizeOption().addOptionalTtlOption().addTargetHostname().getCommand();
}
startWithPingCommand() {
if (this.isRunningOnMacOS() && this.ipVersion === 6) {
this.currentCommand = ["ping6"];
} else {
this.currentCommand = ["ping"];
}
return this;
}
addIPVersionOption() {
if (!this.isRunningOnMacOS() && this.ipVersion) {
if (this.ipVersion === 4) {
this.currentCommand.push("-4");
} else if (this.ipVersion === 6) {
this.currentCommand.push("-6");
}
}
return this;
}
getCommand() {
return this.currentCommand;
}
addPacketCountOption() {
if (this.isRunningOnWindows()) {
this.currentCommand.push("-n", String(this.count));
} else {
this.currentCommand.push("-c", String(this.count));
}
return this;
}
addTimeoutOption() {
if (this.isRunningOnWindows()) {
this.currentCommand.push("-w", String(this.convertTimeoutToMilliseconds()));
} else {
this.currentCommand.push("-W");
if (this.isRunningOnMacOS()) {
this.currentCommand.push(String(this.convertTimeoutToMilliseconds()));
} else {
this.currentCommand.push(String(this.timeoutInSeconds));
}
}
return this;
}
addOptionalIntervalOption() {
if (this.intervalInSeconds !== 1 && !this.isRunningOnWindows()) {
this.currentCommand.push("-i", String(this.intervalInSeconds));
}
return this;
}
addOptionalPacketSizeOption() {
if (this.packetSizeInBytes !== 56) {
if (this.isRunningOnWindows()) {
this.currentCommand.push("-l", String(this.packetSizeInBytes));
} else {
this.currentCommand.push("-s", String(this.packetSizeInBytes));
}
}
return this;
}
addOptionalTtlOption() {
if (this.ttl !== 64) {
if (this.isRunningOnWindows()) {
this.currentCommand.push("-i", String(this.ttl));
} else {
this.currentCommand.push("-t", String(this.ttl));
}
}
return this;
}
addTargetHostname() {
this.currentCommand.push(this.hostname);
return this;
}
isRunningOnMacOS() {
return import_node_os.default.platform() === "darwin";
}
isRunningOnWindows() {
return import_node_os.default.platform() === "win32";
}
convertTimeoutToMilliseconds() {
return this.timeoutInSeconds * 1e3;
}
};
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
Ping,
PingError,
PingErrorUtils,
PingResult,
PingResultLine,
PingStream,
combineAsyncIterators
});