UNPKG

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
"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 });