feat(smartnetwork): Add randomizable port search and switch DNS resolution to @push.rocks/smartdns; export smartdns and update docs
This commit is contained in:
@@ -3,6 +3,6 @@
|
||||
*/
|
||||
export const commitinfo = {
|
||||
name: '@push.rocks/smartnetwork',
|
||||
version: '4.2.0',
|
||||
version: '4.3.0',
|
||||
description: 'A toolkit for network diagnostics including speed tests, port availability checks, and more.'
|
||||
}
|
||||
|
@@ -24,6 +24,15 @@ export interface Hop {
|
||||
ip: string;
|
||||
rtt: number | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Options for the findFreePort method
|
||||
*/
|
||||
export interface IFindFreePortOptions {
|
||||
/** If true, selects a random available port within the range instead of the first one */
|
||||
randomize?: boolean;
|
||||
}
|
||||
|
||||
export class SmartNetwork {
|
||||
/** Static registry for external plugins */
|
||||
public static pluginsRegistry: Map<string, any> = new Map();
|
||||
@@ -148,9 +157,10 @@ export class SmartNetwork {
|
||||
* Find the first available port within a given range
|
||||
* @param startPort The start of the port range (inclusive)
|
||||
* @param endPort The end of the port range (inclusive)
|
||||
* @returns The first available port number, or null if no ports are available
|
||||
* @param options Optional configuration for port selection behavior
|
||||
* @returns The first available port number (or random if options.randomize is true), or null if no ports are available
|
||||
*/
|
||||
public async findFreePort(startPort: number, endPort: number): Promise<number | null> {
|
||||
public async findFreePort(startPort: number, endPort: number, options?: IFindFreePortOptions): Promise<number | null> {
|
||||
// Validate port range
|
||||
if (startPort < 1 || startPort > 65535 || endPort < 1 || endPort > 65535) {
|
||||
throw new NetworkError('Port numbers must be between 1 and 65535', 'EINVAL');
|
||||
@@ -159,16 +169,38 @@ export class SmartNetwork {
|
||||
throw new NetworkError('Start port must be less than or equal to end port', 'EINVAL');
|
||||
}
|
||||
|
||||
// Check each port in the range
|
||||
for (let port = startPort; port <= endPort; port++) {
|
||||
const isUnused = await this.isLocalPortUnused(port);
|
||||
if (isUnused) {
|
||||
return port;
|
||||
// If randomize option is true, collect all available ports and select randomly
|
||||
if (options?.randomize) {
|
||||
const availablePorts: number[] = [];
|
||||
|
||||
// Scan the range to find all available ports
|
||||
for (let port = startPort; port <= endPort; port++) {
|
||||
const isUnused = await this.isLocalPortUnused(port);
|
||||
if (isUnused) {
|
||||
availablePorts.push(port);
|
||||
}
|
||||
}
|
||||
|
||||
// If there are available ports, select one randomly
|
||||
if (availablePorts.length > 0) {
|
||||
const randomIndex = Math.floor(Math.random() * availablePorts.length);
|
||||
return availablePorts[randomIndex];
|
||||
}
|
||||
|
||||
// No free port found in the range
|
||||
return null;
|
||||
} else {
|
||||
// Default behavior: return the first available port (sequential search)
|
||||
for (let port = startPort; port <= endPort; port++) {
|
||||
const isUnused = await this.isLocalPortUnused(port);
|
||||
if (isUnused) {
|
||||
return port;
|
||||
}
|
||||
}
|
||||
|
||||
// No free port found in the range
|
||||
return null;
|
||||
}
|
||||
|
||||
// No free port found in the range
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -277,13 +309,30 @@ export class SmartNetwork {
|
||||
host: string,
|
||||
): Promise<{ A: string[]; AAAA: string[]; MX: { exchange: string; priority: number }[] }> {
|
||||
try {
|
||||
const dns = await import('dns');
|
||||
const { resolve4, resolve6, resolveMx } = dns.promises;
|
||||
const [A, AAAA, MX] = await Promise.all([
|
||||
resolve4(host).catch(() => []),
|
||||
resolve6(host).catch(() => []),
|
||||
resolveMx(host).catch(() => []),
|
||||
const dnsClient = new plugins.smartdns.dnsClientMod.Smartdns({
|
||||
strategy: 'prefer-system', // Try system resolver first (handles localhost), fallback to DoH
|
||||
allowDohFallback: true,
|
||||
});
|
||||
|
||||
const [aRecords, aaaaRecords, mxRecords] = await Promise.all([
|
||||
dnsClient.getRecordsA(host).catch((): any[] => []),
|
||||
dnsClient.getRecordsAAAA(host).catch((): any[] => []),
|
||||
dnsClient.getRecords(host, 'MX').catch((): any[] => []),
|
||||
]);
|
||||
|
||||
// Extract values from the record objects
|
||||
const A = aRecords.map((record: any) => record.value);
|
||||
const AAAA = aaaaRecords.map((record: any) => record.value);
|
||||
|
||||
// Parse MX records - the value contains "priority exchange"
|
||||
const MX = mxRecords.map((record: any) => {
|
||||
const parts = record.value.split(' ');
|
||||
return {
|
||||
priority: parseInt(parts[0], 10),
|
||||
exchange: parts[1] || '',
|
||||
};
|
||||
});
|
||||
|
||||
return { A, AAAA, MX };
|
||||
} catch (err: any) {
|
||||
throw new NetworkError(err.message, err.code);
|
||||
|
@@ -6,11 +6,12 @@ import * as perfHooks from 'perf_hooks';
|
||||
export { os, https, perfHooks };
|
||||
|
||||
// @pushrocks scope
|
||||
import * as smartdns from '@push.rocks/smartdns';
|
||||
import * as smartping from '@push.rocks/smartping';
|
||||
import * as smartpromise from '@push.rocks/smartpromise';
|
||||
import * as smartstring from '@push.rocks/smartstring';
|
||||
|
||||
export { smartpromise, smartping, smartstring };
|
||||
export { smartdns, smartpromise, smartping, smartstring };
|
||||
|
||||
// @third party scope
|
||||
// @ts-ignore
|
||||
|
Reference in New Issue
Block a user