initial
This commit is contained in:
171
ts/helpers/helpers.iprange.ts
Normal file
171
ts/helpers/helpers.iprange.ts
Normal file
@@ -0,0 +1,171 @@
|
||||
/**
|
||||
* IP Range utility functions for network scanning
|
||||
*/
|
||||
|
||||
/**
|
||||
* Validates an IPv4 address
|
||||
*/
|
||||
export function isValidIp(ip: string): boolean {
|
||||
const parts = ip.split('.');
|
||||
if (parts.length !== 4) return false;
|
||||
|
||||
return parts.every((part) => {
|
||||
const num = parseInt(part, 10);
|
||||
return !isNaN(num) && num >= 0 && num <= 255 && part === num.toString();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts an IPv4 address to a 32-bit number
|
||||
*/
|
||||
export function ipToNumber(ip: string): number {
|
||||
const parts = ip.split('.').map((p) => parseInt(p, 10));
|
||||
return ((parts[0] << 24) | (parts[1] << 16) | (parts[2] << 8) | parts[3]) >>> 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a 32-bit number to an IPv4 address
|
||||
*/
|
||||
export function numberToIp(num: number): string {
|
||||
return [
|
||||
(num >>> 24) & 255,
|
||||
(num >>> 16) & 255,
|
||||
(num >>> 8) & 255,
|
||||
num & 255,
|
||||
].join('.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates an array of IP addresses from a start to end range
|
||||
* Excludes network address (.0) and broadcast address (.255) for /24 ranges
|
||||
*/
|
||||
export function ipRangeToIps(startIp: string, endIp: string): string[] {
|
||||
if (!isValidIp(startIp) || !isValidIp(endIp)) {
|
||||
throw new Error(`Invalid IP address: ${!isValidIp(startIp) ? startIp : endIp}`);
|
||||
}
|
||||
|
||||
const start = ipToNumber(startIp);
|
||||
const end = ipToNumber(endIp);
|
||||
|
||||
if (start > end) {
|
||||
throw new Error(`Start IP (${startIp}) must be less than or equal to end IP (${endIp})`);
|
||||
}
|
||||
|
||||
const ips: string[] = [];
|
||||
for (let i = start; i <= end; i++) {
|
||||
ips.push(numberToIp(i));
|
||||
}
|
||||
|
||||
return ips;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses CIDR notation and returns an array of usable host IPs
|
||||
* Excludes network address and broadcast address
|
||||
* Example: "192.168.1.0/24" returns 192.168.1.1 through 192.168.1.254
|
||||
*/
|
||||
export function cidrToIps(cidr: string): string[] {
|
||||
const match = cidr.match(/^(\d+\.\d+\.\d+\.\d+)\/(\d+)$/);
|
||||
if (!match) {
|
||||
throw new Error(`Invalid CIDR notation: ${cidr}`);
|
||||
}
|
||||
|
||||
const [, networkIp, prefixStr] = match;
|
||||
const prefix = parseInt(prefixStr, 10);
|
||||
|
||||
if (!isValidIp(networkIp)) {
|
||||
throw new Error(`Invalid network address: ${networkIp}`);
|
||||
}
|
||||
|
||||
if (prefix < 0 || prefix > 32) {
|
||||
throw new Error(`Invalid prefix length: ${prefix}`);
|
||||
}
|
||||
|
||||
// For /32, just return the single IP
|
||||
if (prefix === 32) {
|
||||
return [networkIp];
|
||||
}
|
||||
|
||||
// For /31 (point-to-point), return both IPs
|
||||
if (prefix === 31) {
|
||||
const networkNum = ipToNumber(networkIp);
|
||||
const mask = (0xffffffff << (32 - prefix)) >>> 0;
|
||||
const network = (networkNum & mask) >>> 0;
|
||||
return [numberToIp(network), numberToIp(network + 1)];
|
||||
}
|
||||
|
||||
// Calculate network and broadcast addresses
|
||||
const networkNum = ipToNumber(networkIp);
|
||||
const mask = (0xffffffff << (32 - prefix)) >>> 0;
|
||||
const network = (networkNum & mask) >>> 0;
|
||||
const broadcast = (network | (~mask >>> 0)) >>> 0;
|
||||
|
||||
// Generate usable host IPs (exclude network and broadcast)
|
||||
const ips: string[] = [];
|
||||
for (let i = network + 1; i < broadcast; i++) {
|
||||
ips.push(numberToIp(i));
|
||||
}
|
||||
|
||||
return ips;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the local network interfaces and returns the first non-loopback IPv4 subnet
|
||||
* Returns CIDR notation (e.g., "192.168.1.0/24")
|
||||
*/
|
||||
export function getLocalSubnet(): string | null {
|
||||
try {
|
||||
const os = require('os');
|
||||
const interfaces = os.networkInterfaces();
|
||||
|
||||
for (const name of Object.keys(interfaces)) {
|
||||
const iface = interfaces[name];
|
||||
if (!iface) continue;
|
||||
|
||||
for (const info of iface) {
|
||||
// Skip loopback and non-IPv4
|
||||
if (info.family !== 'IPv4' || info.internal) continue;
|
||||
|
||||
// Calculate the network address from IP and netmask
|
||||
const ip = ipToNumber(info.address);
|
||||
const mask = ipToNumber(info.netmask);
|
||||
const network = (ip & mask) >>> 0;
|
||||
|
||||
// Calculate prefix length from netmask
|
||||
const maskBits = info.netmask.split('.').reduce((acc: number, octet: string) => {
|
||||
const byte = parseInt(octet, 10);
|
||||
let bits = 0;
|
||||
for (let i = 7; i >= 0; i--) {
|
||||
if ((byte >> i) & 1) bits++;
|
||||
else break;
|
||||
}
|
||||
return acc + bits;
|
||||
}, 0);
|
||||
|
||||
return `${numberToIp(network)}/${maskBits}`;
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// os module might not be available
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Counts the number of IPs in a CIDR range (excluding network and broadcast)
|
||||
*/
|
||||
export function countIpsInCidr(cidr: string): number {
|
||||
const match = cidr.match(/^(\d+\.\d+\.\d+\.\d+)\/(\d+)$/);
|
||||
if (!match) {
|
||||
throw new Error(`Invalid CIDR notation: ${cidr}`);
|
||||
}
|
||||
|
||||
const prefix = parseInt(match[2], 10);
|
||||
|
||||
if (prefix === 32) return 1;
|
||||
if (prefix === 31) return 2;
|
||||
|
||||
// 2^(32-prefix) - 2 (minus network and broadcast)
|
||||
return Math.pow(2, 32 - prefix) - 2;
|
||||
}
|
||||
100
ts/helpers/helpers.retry.ts
Normal file
100
ts/helpers/helpers.retry.ts
Normal file
@@ -0,0 +1,100 @@
|
||||
import * as plugins from '../plugins.js';
|
||||
import type { IRetryOptions } from '../interfaces/index.js';
|
||||
|
||||
const defaultRetryOptions: Required<IRetryOptions> = {
|
||||
maxRetries: 5,
|
||||
baseDelay: 1000,
|
||||
maxDelay: 16000,
|
||||
multiplier: 2,
|
||||
jitter: true,
|
||||
};
|
||||
|
||||
/**
|
||||
* Calculates the delay for a retry attempt using exponential backoff
|
||||
*/
|
||||
function calculateDelay(
|
||||
attempt: number,
|
||||
options: Required<IRetryOptions>
|
||||
): number {
|
||||
const delay = Math.min(
|
||||
options.baseDelay * Math.pow(options.multiplier, attempt),
|
||||
options.maxDelay
|
||||
);
|
||||
|
||||
if (options.jitter) {
|
||||
// Add random jitter of +/- 25%
|
||||
const jitterRange = delay * 0.25;
|
||||
return delay + (Math.random() * jitterRange * 2 - jitterRange);
|
||||
}
|
||||
|
||||
return delay;
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes a function with retry logic using exponential backoff
|
||||
*
|
||||
* @param fn - The async function to execute
|
||||
* @param options - Retry configuration options
|
||||
* @returns The result of the function
|
||||
* @throws The last error if all retries fail
|
||||
*/
|
||||
export async function withRetry<T>(
|
||||
fn: () => Promise<T>,
|
||||
options?: IRetryOptions
|
||||
): Promise<T> {
|
||||
const opts: Required<IRetryOptions> = { ...defaultRetryOptions, ...options };
|
||||
let lastError: Error | undefined;
|
||||
|
||||
for (let attempt = 0; attempt <= opts.maxRetries; attempt++) {
|
||||
try {
|
||||
return await fn();
|
||||
} catch (error) {
|
||||
lastError = error instanceof Error ? error : new Error(String(error));
|
||||
|
||||
if (attempt === opts.maxRetries) {
|
||||
break;
|
||||
}
|
||||
|
||||
const delay = calculateDelay(attempt, opts);
|
||||
await plugins.smartdelay.delayFor(delay);
|
||||
}
|
||||
}
|
||||
|
||||
throw lastError ?? new Error('Retry failed with unknown error');
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a retryable version of an async function
|
||||
*
|
||||
* @param fn - The async function to wrap
|
||||
* @param options - Retry configuration options
|
||||
* @returns A wrapped function that retries on failure
|
||||
*/
|
||||
export function createRetryable<TArgs extends unknown[], TResult>(
|
||||
fn: (...args: TArgs) => Promise<TResult>,
|
||||
options?: IRetryOptions
|
||||
): (...args: TArgs) => Promise<TResult> {
|
||||
return (...args: TArgs) => withRetry(() => fn(...args), options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retry decorator for class methods
|
||||
* Note: Use as a wrapper function since TC39 decorators have different semantics
|
||||
*/
|
||||
export function retryMethod<T extends Record<string, unknown>>(
|
||||
target: T,
|
||||
methodName: keyof T & string,
|
||||
options?: IRetryOptions
|
||||
): void {
|
||||
const originalMethod = target[methodName];
|
||||
if (typeof originalMethod !== 'function') {
|
||||
throw new Error(`${methodName} is not a function`);
|
||||
}
|
||||
|
||||
target[methodName] = createRetryable(
|
||||
originalMethod.bind(target) as (...args: unknown[]) => Promise<unknown>,
|
||||
options
|
||||
) as T[typeof methodName];
|
||||
}
|
||||
|
||||
export { defaultRetryOptions };
|
||||
Reference in New Issue
Block a user