@push.rocks/smartnetwork 4.7.4 → 4.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/helpers/ip.d.ts +42 -0
- package/dist_ts/helpers/ip.js +150 -0
- package/dist_ts/index.d.ts +1 -0
- package/dist_ts/index.js +2 -1
- package/dist_ts/smartnetwork.classes.publicip.js +4 -17
- package/package.json +2 -2
- package/readme.md +10 -0
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/helpers/ip.ts +148 -0
- package/ts/index.ts +1 -0
- package/ts/smartnetwork.classes.publicip.ts +3 -17
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
export const commitinfo = {
|
|
5
5
|
name: '@push.rocks/smartnetwork',
|
|
6
|
-
version: '4.
|
|
6
|
+
version: '4.9.0',
|
|
7
7
|
description: 'A toolkit for network diagnostics including speed tests, port availability checks, and more.'
|
|
8
8
|
};
|
|
9
9
|
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiMDBfY29tbWl0aW5mb19kYXRhLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vdHMvMDBfY29tbWl0aW5mb19kYXRhLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOztHQUVHO0FBQ0gsTUFBTSxDQUFDLE1BQU0sVUFBVSxHQUFHO0lBQ3hCLElBQUksRUFBRSwwQkFBMEI7SUFDaEMsT0FBTyxFQUFFLE9BQU87SUFDaEIsV0FBVyxFQUFFLDhGQUE4RjtDQUM1RyxDQUFBIn0=
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure IP address validation helpers.
|
|
3
|
+
* Runtime-agnostic replacements for node:net's isIP/isIPv4/isIPv6, with one
|
|
4
|
+
* intentional divergence: zone-indexed IPv6 ('fe80::1%eth0') is rejected.
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Strict IPv4 validation: four decimal octets 0-255, no leading zeros.
|
|
8
|
+
*/
|
|
9
|
+
export declare function isValidIpv4(ip: string): boolean;
|
|
10
|
+
/**
|
|
11
|
+
* Strict IPv6 validation: hex groups with at most one '::' compression,
|
|
12
|
+
* optional embedded IPv4 tail (e.g. '::ffff:192.168.1.1').
|
|
13
|
+
* Zone indices ('%eth0') are intentionally rejected — node:net.isIPv6 accepts
|
|
14
|
+
* them, but a zone-scoped address is never a valid routable identity.
|
|
15
|
+
*/
|
|
16
|
+
export declare function isValidIpv6(ip: string): boolean;
|
|
17
|
+
/**
|
|
18
|
+
* Returns 4 for a valid IPv4 address, 6 for a valid IPv6 address, 0 otherwise.
|
|
19
|
+
* Behaves like node:net.isIP() except that zone-indexed IPv6 is rejected.
|
|
20
|
+
*/
|
|
21
|
+
export declare function getIpVersion(ip: string): 4 | 6 | 0;
|
|
22
|
+
/**
|
|
23
|
+
* Expands a valid IPv6 address into its eight zero-padded 16-bit groups.
|
|
24
|
+
* Embedded IPv4 tails are converted to their two hex groups.
|
|
25
|
+
* Returns undefined for anything that is not a valid IPv6 address.
|
|
26
|
+
*/
|
|
27
|
+
export declare function expandIpv6(ip: string): string[] | undefined;
|
|
28
|
+
/**
|
|
29
|
+
* Returns the reverse-DNS (PTR) name for a valid IPv4 or IPv6 address:
|
|
30
|
+
* 'in-addr.arpa' for IPv4, 'ip6.arpa' with nibble ordering for IPv6.
|
|
31
|
+
* Returns undefined instead of throwing for invalid input, so lookup code
|
|
32
|
+
* can treat "no address" and "bad address" uniformly.
|
|
33
|
+
*/
|
|
34
|
+
export declare function toReverseDnsName(ip: string): string | undefined;
|
|
35
|
+
/**
|
|
36
|
+
* Returns true when the address is publicly routable: not private (RFC 1918),
|
|
37
|
+
* loopback, link-local, carrier-grade NAT, documentation, benchmarking,
|
|
38
|
+
* multicast, or otherwise reserved space. Invalid input returns false.
|
|
39
|
+
* IPv6 is public when it falls in global unicast 2000::/3 and is not the
|
|
40
|
+
* 2001:db8::/32 documentation range.
|
|
41
|
+
*/
|
|
42
|
+
export declare function isPublicIpAddress(ip: string): boolean;
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure IP address validation helpers.
|
|
3
|
+
* Runtime-agnostic replacements for node:net's isIP/isIPv4/isIPv6, with one
|
|
4
|
+
* intentional divergence: zone-indexed IPv6 ('fe80::1%eth0') is rejected.
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Strict IPv4 validation: four decimal octets 0-255, no leading zeros.
|
|
8
|
+
*/
|
|
9
|
+
export function isValidIpv4(ip) {
|
|
10
|
+
if (!ip)
|
|
11
|
+
return false;
|
|
12
|
+
const parts = ip.split('.');
|
|
13
|
+
if (parts.length !== 4)
|
|
14
|
+
return false;
|
|
15
|
+
return parts.every((part) => {
|
|
16
|
+
if (!/^\d{1,3}$/.test(part))
|
|
17
|
+
return false;
|
|
18
|
+
if (part.length > 1 && part.startsWith('0'))
|
|
19
|
+
return false;
|
|
20
|
+
const num = Number(part);
|
|
21
|
+
return num >= 0 && num <= 255;
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Strict IPv6 validation: hex groups with at most one '::' compression,
|
|
26
|
+
* optional embedded IPv4 tail (e.g. '::ffff:192.168.1.1').
|
|
27
|
+
* Zone indices ('%eth0') are intentionally rejected — node:net.isIPv6 accepts
|
|
28
|
+
* them, but a zone-scoped address is never a valid routable identity.
|
|
29
|
+
*/
|
|
30
|
+
export function isValidIpv6(ip) {
|
|
31
|
+
if (!ip || ip.includes('%'))
|
|
32
|
+
return false;
|
|
33
|
+
const doubleColonSplit = ip.split('::');
|
|
34
|
+
if (doubleColonSplit.length > 2)
|
|
35
|
+
return false;
|
|
36
|
+
const hasCompression = doubleColonSplit.length === 2;
|
|
37
|
+
const parseGroups = (segment) => segment === '' ? [] : segment.split(':');
|
|
38
|
+
const headGroups = parseGroups(doubleColonSplit[0]);
|
|
39
|
+
const tailGroups = hasCompression ? parseGroups(doubleColonSplit[1]) : [];
|
|
40
|
+
const groups = hasCompression ? [...headGroups, ...tailGroups] : headGroups;
|
|
41
|
+
if (groups.some((group) => group === ''))
|
|
42
|
+
return false;
|
|
43
|
+
// An embedded IPv4 tail counts as two 16-bit groups and must be last.
|
|
44
|
+
let groupCount = groups.length;
|
|
45
|
+
const lastGroup = groups[groups.length - 1];
|
|
46
|
+
if (lastGroup !== undefined && lastGroup.includes('.')) {
|
|
47
|
+
if (!isValidIpv4(lastGroup))
|
|
48
|
+
return false;
|
|
49
|
+
groupCount += 1;
|
|
50
|
+
groups.pop();
|
|
51
|
+
}
|
|
52
|
+
if (!groups.every((group) => /^[0-9a-fA-F]{1,4}$/.test(group)))
|
|
53
|
+
return false;
|
|
54
|
+
if (hasCompression) {
|
|
55
|
+
// '::' must stand for at least one zero group.
|
|
56
|
+
return groupCount < 8;
|
|
57
|
+
}
|
|
58
|
+
return groupCount === 8;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Returns 4 for a valid IPv4 address, 6 for a valid IPv6 address, 0 otherwise.
|
|
62
|
+
* Behaves like node:net.isIP() except that zone-indexed IPv6 is rejected.
|
|
63
|
+
*/
|
|
64
|
+
export function getIpVersion(ip) {
|
|
65
|
+
if (isValidIpv4(ip))
|
|
66
|
+
return 4;
|
|
67
|
+
if (isValidIpv6(ip))
|
|
68
|
+
return 6;
|
|
69
|
+
return 0;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Expands a valid IPv6 address into its eight zero-padded 16-bit groups.
|
|
73
|
+
* Embedded IPv4 tails are converted to their two hex groups.
|
|
74
|
+
* Returns undefined for anything that is not a valid IPv6 address.
|
|
75
|
+
*/
|
|
76
|
+
export function expandIpv6(ip) {
|
|
77
|
+
if (!isValidIpv6(ip))
|
|
78
|
+
return undefined;
|
|
79
|
+
let normalized = ip;
|
|
80
|
+
const lastColon = normalized.lastIndexOf(':');
|
|
81
|
+
const tail = normalized.slice(lastColon + 1);
|
|
82
|
+
if (tail.includes('.')) {
|
|
83
|
+
const octets = tail.split('.').map(Number);
|
|
84
|
+
const hexTail = `${((octets[0] << 8) | octets[1]).toString(16)}:${((octets[2] << 8) | octets[3]).toString(16)}`;
|
|
85
|
+
normalized = `${normalized.slice(0, lastColon + 1)}${hexTail}`;
|
|
86
|
+
}
|
|
87
|
+
const halves = normalized.split('::');
|
|
88
|
+
const parseGroups = (segment) => segment === '' ? [] : segment.split(':');
|
|
89
|
+
const left = parseGroups(halves[0]);
|
|
90
|
+
const right = halves.length === 2 ? parseGroups(halves[1]) : [];
|
|
91
|
+
const missing = 8 - left.length - right.length;
|
|
92
|
+
if ((halves.length === 1 && missing !== 0) || missing < 0)
|
|
93
|
+
return undefined;
|
|
94
|
+
const groups = [...left, ...Array.from({ length: missing }, () => '0'), ...right];
|
|
95
|
+
if (groups.length !== 8)
|
|
96
|
+
return undefined;
|
|
97
|
+
return groups.map((group) => group.toLowerCase().padStart(4, '0'));
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Returns the reverse-DNS (PTR) name for a valid IPv4 or IPv6 address:
|
|
101
|
+
* 'in-addr.arpa' for IPv4, 'ip6.arpa' with nibble ordering for IPv6.
|
|
102
|
+
* Returns undefined instead of throwing for invalid input, so lookup code
|
|
103
|
+
* can treat "no address" and "bad address" uniformly.
|
|
104
|
+
*/
|
|
105
|
+
export function toReverseDnsName(ip) {
|
|
106
|
+
const family = getIpVersion(ip);
|
|
107
|
+
if (family === 4) {
|
|
108
|
+
return `${ip.split('.').reverse().join('.')}.in-addr.arpa`;
|
|
109
|
+
}
|
|
110
|
+
if (family === 6) {
|
|
111
|
+
const groups = expandIpv6(ip);
|
|
112
|
+
if (!groups)
|
|
113
|
+
return undefined;
|
|
114
|
+
return `${groups.join('').split('').reverse().join('.')}.ip6.arpa`;
|
|
115
|
+
}
|
|
116
|
+
return undefined;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Returns true when the address is publicly routable: not private (RFC 1918),
|
|
120
|
+
* loopback, link-local, carrier-grade NAT, documentation, benchmarking,
|
|
121
|
+
* multicast, or otherwise reserved space. Invalid input returns false.
|
|
122
|
+
* IPv6 is public when it falls in global unicast 2000::/3 and is not the
|
|
123
|
+
* 2001:db8::/32 documentation range.
|
|
124
|
+
*/
|
|
125
|
+
export function isPublicIpAddress(ip) {
|
|
126
|
+
const family = getIpVersion(ip);
|
|
127
|
+
if (family === 4) {
|
|
128
|
+
const [a, b, c] = ip.split('.').map(Number);
|
|
129
|
+
return !(a === 0 || a === 10 || a === 127 || a >= 224
|
|
130
|
+
|| (a === 100 && b >= 64 && b <= 127)
|
|
131
|
+
|| (a === 169 && b === 254)
|
|
132
|
+
|| (a === 172 && b >= 16 && b <= 31)
|
|
133
|
+
|| (a === 192 && b === 0)
|
|
134
|
+
|| (a === 192 && b === 168)
|
|
135
|
+
|| (a === 198 && (b === 18 || b === 19))
|
|
136
|
+
|| (a === 198 && b === 51 && c === 100)
|
|
137
|
+
|| (a === 203 && b === 0 && c === 113));
|
|
138
|
+
}
|
|
139
|
+
if (family === 6) {
|
|
140
|
+
const groups = expandIpv6(ip);
|
|
141
|
+
if (!groups)
|
|
142
|
+
return false;
|
|
143
|
+
const firstGroup = parseInt(groups[0], 16);
|
|
144
|
+
if (firstGroup < 0x2000 || firstGroup > 0x3fff)
|
|
145
|
+
return false;
|
|
146
|
+
return !(groups[0] === '2001' && groups[1] === '0db8');
|
|
147
|
+
}
|
|
148
|
+
return false;
|
|
149
|
+
}
|
|
150
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaXAuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi90cy9oZWxwZXJzL2lwLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOzs7O0dBSUc7QUFFSDs7R0FFRztBQUNILE1BQU0sVUFBVSxXQUFXLENBQUMsRUFBVTtJQUNwQyxJQUFJLENBQUMsRUFBRTtRQUFFLE9BQU8sS0FBSyxDQUFDO0lBQ3RCLE1BQU0sS0FBSyxHQUFHLEVBQUUsQ0FBQyxLQUFLLENBQUMsR0FBRyxDQUFDLENBQUM7SUFDNUIsSUFBSSxLQUFLLENBQUMsTUFBTSxLQUFLLENBQUM7UUFBRSxPQUFPLEtBQUssQ0FBQztJQUNyQyxPQUFPLEtBQUssQ0FBQyxLQUFLLENBQUMsQ0FBQyxJQUFJLEVBQUUsRUFBRTtRQUMxQixJQUFJLENBQUMsV0FBVyxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUM7WUFBRSxPQUFPLEtBQUssQ0FBQztRQUMxQyxJQUFJLElBQUksQ0FBQyxNQUFNLEdBQUcsQ0FBQyxJQUFJLElBQUksQ0FBQyxVQUFVLENBQUMsR0FBRyxDQUFDO1lBQUUsT0FBTyxLQUFLLENBQUM7UUFDMUQsTUFBTSxHQUFHLEdBQUcsTUFBTSxDQUFDLElBQUksQ0FBQyxDQUFDO1FBQ3pCLE9BQU8sR0FBRyxJQUFJLENBQUMsSUFBSSxHQUFHLElBQUksR0FBRyxDQUFDO0lBQ2hDLENBQUMsQ0FBQyxDQUFDO0FBQ0wsQ0FBQztBQUVEOzs7OztHQUtHO0FBQ0gsTUFBTSxVQUFVLFdBQVcsQ0FBQyxFQUFVO0lBQ3BDLElBQUksQ0FBQyxFQUFFLElBQUksRUFBRSxDQUFDLFFBQVEsQ0FBQyxHQUFHLENBQUM7UUFBRSxPQUFPLEtBQUssQ0FBQztJQUUxQyxNQUFNLGdCQUFnQixHQUFHLEVBQUUsQ0FBQyxLQUFLLENBQUMsSUFBSSxDQUFDLENBQUM7SUFDeEMsSUFBSSxnQkFBZ0IsQ0FBQyxNQUFNLEdBQUcsQ0FBQztRQUFFLE9BQU8sS0FBSyxDQUFDO0lBQzlDLE1BQU0sY0FBYyxH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
|
package/dist_ts/index.d.ts
CHANGED
|
@@ -8,3 +8,4 @@ export { DomainIntelligence } from './smartnetwork.classes.domainintelligence.js
|
|
|
8
8
|
export type { IDomainIntelligenceResult, IDomainIntelligenceOptions } from './smartnetwork.classes.domainintelligence.js';
|
|
9
9
|
export { setLogger, getLogger } from './logging.js';
|
|
10
10
|
export { NetworkError, TimeoutError } from './errors.js';
|
|
11
|
+
export { isValidIpv4, isValidIpv6, getIpVersion, expandIpv6, toReverseDnsName, isPublicIpAddress } from './helpers/ip.js';
|
package/dist_ts/index.js
CHANGED
|
@@ -5,4 +5,5 @@ export { IpIntelligence } from './smartnetwork.classes.ipintelligence.js';
|
|
|
5
5
|
export { DomainIntelligence } from './smartnetwork.classes.domainintelligence.js';
|
|
6
6
|
export { setLogger, getLogger } from './logging.js';
|
|
7
7
|
export { NetworkError, TimeoutError } from './errors.js';
|
|
8
|
-
|
|
8
|
+
export { isValidIpv4, isValidIpv6, getIpVersion, expandIpv6, toReverseDnsName, isPublicIpAddress } from './helpers/ip.js';
|
|
9
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi90cy9pbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxjQUFjLHdDQUF3QyxDQUFDO0FBRXZELE9BQU8sRUFBRSxpQkFBaUIsRUFBRSxNQUFNLHNDQUFzQyxDQUFDO0FBQ3pFLE9BQU8sRUFBRSxRQUFRLEVBQUUsTUFBTSxvQ0FBb0MsQ0FBQztBQUM5RCxPQUFPLEVBQUUsY0FBYyxFQUFFLE1BQU0sMENBQTBDLENBQUM7QUFFMUUsT0FBTyxFQUFFLGtCQUFrQixFQUFFLE1BQU0sOENBQThDLENBQUM7QUFFbEYsT0FBTyxFQUFFLFNBQVMsRUFBRSxTQUFTLEVBQUUsTUFBTSxjQUFjLENBQUM7QUFDcEQsT0FBTyxFQUFFLFlBQVksRUFBRSxZQUFZLEVBQUUsTUFBTSxhQUFhLENBQUM7QUFDekQsT0FBTyxFQUFFLFdBQVcsRUFBRSxXQUFXLEVBQUUsWUFBWSxFQUFFLFVBQVUsRUFBRSxnQkFBZ0IsRUFBRSxpQkFBaUIsRUFBRSxNQUFNLGlCQUFpQixDQUFDIn0=
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { getLogger } from './logging.js';
|
|
2
|
+
import { isValidIpv4, isValidIpv6 } from './helpers/ip.js';
|
|
2
3
|
/**
|
|
3
4
|
* PublicIp class for detecting public IPv4 and IPv6 addresses
|
|
4
5
|
* Uses multiple fallback services for reliability
|
|
@@ -110,27 +111,13 @@ export class PublicIp {
|
|
|
110
111
|
* Validate IPv4 address format
|
|
111
112
|
*/
|
|
112
113
|
isValidIpv4(ip) {
|
|
113
|
-
|
|
114
|
-
return false;
|
|
115
|
-
const ipv4Regex = /^(\d{1,3}\.){3}\d{1,3}$/;
|
|
116
|
-
if (!ipv4Regex.test(ip))
|
|
117
|
-
return false;
|
|
118
|
-
const parts = ip.split('.');
|
|
119
|
-
return parts.every(part => {
|
|
120
|
-
const num = parseInt(part, 10);
|
|
121
|
-
return num >= 0 && num <= 255;
|
|
122
|
-
});
|
|
114
|
+
return isValidIpv4(ip);
|
|
123
115
|
}
|
|
124
116
|
/**
|
|
125
117
|
* Validate IPv6 address format
|
|
126
118
|
*/
|
|
127
119
|
isValidIpv6(ip) {
|
|
128
|
-
|
|
129
|
-
return false;
|
|
130
|
-
// Simplified IPv6 validation - checks for colon-separated hex values
|
|
131
|
-
const ipv6Regex = /^([0-9a-fA-F]{0,4}:){2,7}[0-9a-fA-F]{0,4}$/;
|
|
132
|
-
const ipv6CompressedRegex = /^::([0-9a-fA-F]{0,4}:){0,6}[0-9a-fA-F]{0,4}$|^([0-9a-fA-F]{0,4}:){1,7}:$/;
|
|
133
|
-
return ipv6Regex.test(ip) || ipv6CompressedRegex.test(ip);
|
|
120
|
+
return isValidIpv6(ip);
|
|
134
121
|
}
|
|
135
122
|
}
|
|
136
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
123
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic21hcnRuZXR3b3JrLmNsYXNzZXMucHVibGljaXAuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi90cy9zbWFydG5ldHdvcmsuY2xhc3Nlcy5wdWJsaWNpcC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQUUsU0FBUyxFQUFFLE1BQU0sY0FBYyxDQUFDO0FBQ3pDLE9BQU8sRUFBRSxXQUFXLEVBQUUsV0FBVyxFQUFFLE1BQU0saUJBQWlCLENBQUM7QUFZM0Q7OztHQUdHO0FBQ0gsTUFBTSxPQUFPLFFBQVE7SUEyQm5CLFlBQVksT0FBOEI7UUExQnpCLGFBQVEsR0FBZ0I7WUFDdkM7Z0JBQ0UsSUFBSSxFQUFFLE9BQU87Z0JBQ2IsS0FBSyxFQUFFLG1DQUFtQztnQkFDMUMsS0FBSyxFQUFFLG9DQUFvQzthQUM1QztZQUNEO2dCQUNFLElBQUksRUFBRSxVQUFVO2dCQUNoQixLQUFLLEVBQUUscUJBQXFCO2dCQUM1QixLQUFLLEVBQUUscUJBQXFCO2FBQzdCO1lBQ0Q7Z0JBQ0UsSUFBSSxFQUFFLE9BQU87Z0JBQ2IsS0FBSyxFQUFFLHdCQUF3QjtnQkFDL0IsS0FBSyxFQUFFLHdCQUF3QjthQUNoQztZQUNEO2dCQUNFLElBQUksRUFBRSxXQUFXO2dCQUNqQixLQUFLLEVBQUUsNEJBQTRCO2dCQUNuQyxLQUFLLEVBQUUsNEJBQTRCO2FBQ3BDO1NBQ0YsQ0FBQztRQUdlLFdBQU0sR0FBRyxTQUFTLEVBQUUsQ0FBQztRQUdwQyxJQUFJLENBQUMsT0FBTyxHQUFHLE9BQU8sRUFBRSxPQUFPLElBQUksSUFBSSxDQUFDO0lBQzFDLENBQUM7SUFFRDs7T0FFRztJQUNJLEtBQUssQ0FBQyxhQUFhO1FBQ3hCLEtBQUssTUFBTSxPQUFPLElBQUksSUFBSSxDQUFDLFFBQVEsRUFBRSxDQUFDO1lBQ3BDLElBQUksQ0FBQyxPQUFPLENBQUMsS0FBSztnQkFBRSxTQUFTO1lBRTdCLElBQUksQ0FBQztnQkFDSCxNQUFNLEVBQUUsR0FBRyxNQUFN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
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@push.rocks/smartnetwork",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.9.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "A toolkit for network diagnostics including speed tests, port availability checks, and more.",
|
|
6
6
|
"exports": {
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
"@git.zone/tsrun": "^2.0.4",
|
|
15
15
|
"@git.zone/tsrust": "^1.4.1",
|
|
16
16
|
"@git.zone/tstest": "^3.6.6",
|
|
17
|
-
"@types/node": "^
|
|
17
|
+
"@types/node": "^26.0.1"
|
|
18
18
|
},
|
|
19
19
|
"dependencies": {
|
|
20
20
|
"@push.rocks/smartdns": "^7.9.2",
|
package/readme.md
CHANGED
|
@@ -489,6 +489,16 @@ const monitor = async () => {
|
|
|
489
489
|
| `getIpIntelligence(ip)` | ASN + org + geo + RDAP registration | No |
|
|
490
490
|
| `getDomainIntelligence(domain)` | Registrar, nameservers, events, DNS records, DNSSEC | No |
|
|
491
491
|
|
|
492
|
+
### Standalone helpers
|
|
493
|
+
|
|
494
|
+
Pure functions exported alongside the `SmartNetwork` class — no instance or Rust bridge needed:
|
|
495
|
+
|
|
496
|
+
| Function | Description |
|
|
497
|
+
|----------|-------------|
|
|
498
|
+
| `isValidIpv4(ip)` | Strict IPv4 validation (four decimal octets 0–255, no leading zeros) |
|
|
499
|
+
| `isValidIpv6(ip)` | Strict IPv6 validation (single `::` compression, embedded IPv4 tail; zone indices like `%eth0` are rejected) |
|
|
500
|
+
| `getIpVersion(ip)` | Returns `4`, `6`, or `0` — like `node:net.isIP()`, except zone-indexed IPv6 is rejected |
|
|
501
|
+
|
|
492
502
|
## License and Legal Information
|
|
493
503
|
|
|
494
504
|
This repository contains open-source code licensed under the MIT License. A copy of the license can be found in the [license](./license) file.
|
package/ts/00_commitinfo_data.ts
CHANGED
package/ts/helpers/ip.ts
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure IP address validation helpers.
|
|
3
|
+
* Runtime-agnostic replacements for node:net's isIP/isIPv4/isIPv6, with one
|
|
4
|
+
* intentional divergence: zone-indexed IPv6 ('fe80::1%eth0') is rejected.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Strict IPv4 validation: four decimal octets 0-255, no leading zeros.
|
|
9
|
+
*/
|
|
10
|
+
export function isValidIpv4(ip: string): boolean {
|
|
11
|
+
if (!ip) return false;
|
|
12
|
+
const parts = ip.split('.');
|
|
13
|
+
if (parts.length !== 4) return false;
|
|
14
|
+
return parts.every((part) => {
|
|
15
|
+
if (!/^\d{1,3}$/.test(part)) return false;
|
|
16
|
+
if (part.length > 1 && part.startsWith('0')) return false;
|
|
17
|
+
const num = Number(part);
|
|
18
|
+
return num >= 0 && num <= 255;
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Strict IPv6 validation: hex groups with at most one '::' compression,
|
|
24
|
+
* optional embedded IPv4 tail (e.g. '::ffff:192.168.1.1').
|
|
25
|
+
* Zone indices ('%eth0') are intentionally rejected — node:net.isIPv6 accepts
|
|
26
|
+
* them, but a zone-scoped address is never a valid routable identity.
|
|
27
|
+
*/
|
|
28
|
+
export function isValidIpv6(ip: string): boolean {
|
|
29
|
+
if (!ip || ip.includes('%')) return false;
|
|
30
|
+
|
|
31
|
+
const doubleColonSplit = ip.split('::');
|
|
32
|
+
if (doubleColonSplit.length > 2) return false;
|
|
33
|
+
const hasCompression = doubleColonSplit.length === 2;
|
|
34
|
+
|
|
35
|
+
const parseGroups = (segment: string): string[] =>
|
|
36
|
+
segment === '' ? [] : segment.split(':');
|
|
37
|
+
|
|
38
|
+
const headGroups = parseGroups(doubleColonSplit[0]);
|
|
39
|
+
const tailGroups = hasCompression ? parseGroups(doubleColonSplit[1]) : [];
|
|
40
|
+
const groups = hasCompression ? [...headGroups, ...tailGroups] : headGroups;
|
|
41
|
+
if (groups.some((group) => group === '')) return false;
|
|
42
|
+
|
|
43
|
+
// An embedded IPv4 tail counts as two 16-bit groups and must be last.
|
|
44
|
+
let groupCount = groups.length;
|
|
45
|
+
const lastGroup = groups[groups.length - 1];
|
|
46
|
+
if (lastGroup !== undefined && lastGroup.includes('.')) {
|
|
47
|
+
if (!isValidIpv4(lastGroup)) return false;
|
|
48
|
+
groupCount += 1;
|
|
49
|
+
groups.pop();
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (!groups.every((group) => /^[0-9a-fA-F]{1,4}$/.test(group))) return false;
|
|
53
|
+
|
|
54
|
+
if (hasCompression) {
|
|
55
|
+
// '::' must stand for at least one zero group.
|
|
56
|
+
return groupCount < 8;
|
|
57
|
+
}
|
|
58
|
+
return groupCount === 8;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Returns 4 for a valid IPv4 address, 6 for a valid IPv6 address, 0 otherwise.
|
|
63
|
+
* Behaves like node:net.isIP() except that zone-indexed IPv6 is rejected.
|
|
64
|
+
*/
|
|
65
|
+
export function getIpVersion(ip: string): 4 | 6 | 0 {
|
|
66
|
+
if (isValidIpv4(ip)) return 4;
|
|
67
|
+
if (isValidIpv6(ip)) return 6;
|
|
68
|
+
return 0;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Expands a valid IPv6 address into its eight zero-padded 16-bit groups.
|
|
73
|
+
* Embedded IPv4 tails are converted to their two hex groups.
|
|
74
|
+
* Returns undefined for anything that is not a valid IPv6 address.
|
|
75
|
+
*/
|
|
76
|
+
export function expandIpv6(ip: string): string[] | undefined {
|
|
77
|
+
if (!isValidIpv6(ip)) return undefined;
|
|
78
|
+
|
|
79
|
+
let normalized = ip;
|
|
80
|
+
const lastColon = normalized.lastIndexOf(':');
|
|
81
|
+
const tail = normalized.slice(lastColon + 1);
|
|
82
|
+
if (tail.includes('.')) {
|
|
83
|
+
const octets = tail.split('.').map(Number);
|
|
84
|
+
const hexTail = `${((octets[0] << 8) | octets[1]).toString(16)}:${((octets[2] << 8) | octets[3]).toString(16)}`;
|
|
85
|
+
normalized = `${normalized.slice(0, lastColon + 1)}${hexTail}`;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const halves = normalized.split('::');
|
|
89
|
+
const parseGroups = (segment: string): string[] =>
|
|
90
|
+
segment === '' ? [] : segment.split(':');
|
|
91
|
+
const left = parseGroups(halves[0]);
|
|
92
|
+
const right = halves.length === 2 ? parseGroups(halves[1]) : [];
|
|
93
|
+
const missing = 8 - left.length - right.length;
|
|
94
|
+
if ((halves.length === 1 && missing !== 0) || missing < 0) return undefined;
|
|
95
|
+
const groups = [...left, ...Array.from({ length: missing }, () => '0'), ...right];
|
|
96
|
+
if (groups.length !== 8) return undefined;
|
|
97
|
+
return groups.map((group) => group.toLowerCase().padStart(4, '0'));
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Returns the reverse-DNS (PTR) name for a valid IPv4 or IPv6 address:
|
|
102
|
+
* 'in-addr.arpa' for IPv4, 'ip6.arpa' with nibble ordering for IPv6.
|
|
103
|
+
* Returns undefined instead of throwing for invalid input, so lookup code
|
|
104
|
+
* can treat "no address" and "bad address" uniformly.
|
|
105
|
+
*/
|
|
106
|
+
export function toReverseDnsName(ip: string): string | undefined {
|
|
107
|
+
const family = getIpVersion(ip);
|
|
108
|
+
if (family === 4) {
|
|
109
|
+
return `${ip.split('.').reverse().join('.')}.in-addr.arpa`;
|
|
110
|
+
}
|
|
111
|
+
if (family === 6) {
|
|
112
|
+
const groups = expandIpv6(ip);
|
|
113
|
+
if (!groups) return undefined;
|
|
114
|
+
return `${groups.join('').split('').reverse().join('.')}.ip6.arpa`;
|
|
115
|
+
}
|
|
116
|
+
return undefined;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Returns true when the address is publicly routable: not private (RFC 1918),
|
|
121
|
+
* loopback, link-local, carrier-grade NAT, documentation, benchmarking,
|
|
122
|
+
* multicast, or otherwise reserved space. Invalid input returns false.
|
|
123
|
+
* IPv6 is public when it falls in global unicast 2000::/3 and is not the
|
|
124
|
+
* 2001:db8::/32 documentation range.
|
|
125
|
+
*/
|
|
126
|
+
export function isPublicIpAddress(ip: string): boolean {
|
|
127
|
+
const family = getIpVersion(ip);
|
|
128
|
+
if (family === 4) {
|
|
129
|
+
const [a, b, c] = ip.split('.').map(Number);
|
|
130
|
+
return !(a === 0 || a === 10 || a === 127 || a >= 224
|
|
131
|
+
|| (a === 100 && b >= 64 && b <= 127)
|
|
132
|
+
|| (a === 169 && b === 254)
|
|
133
|
+
|| (a === 172 && b >= 16 && b <= 31)
|
|
134
|
+
|| (a === 192 && b === 0)
|
|
135
|
+
|| (a === 192 && b === 168)
|
|
136
|
+
|| (a === 198 && (b === 18 || b === 19))
|
|
137
|
+
|| (a === 198 && b === 51 && c === 100)
|
|
138
|
+
|| (a === 203 && b === 0 && c === 113));
|
|
139
|
+
}
|
|
140
|
+
if (family === 6) {
|
|
141
|
+
const groups = expandIpv6(ip);
|
|
142
|
+
if (!groups) return false;
|
|
143
|
+
const firstGroup = parseInt(groups[0], 16);
|
|
144
|
+
if (firstGroup < 0x2000 || firstGroup > 0x3fff) return false;
|
|
145
|
+
return !(groups[0] === '2001' && groups[1] === '0db8');
|
|
146
|
+
}
|
|
147
|
+
return false;
|
|
148
|
+
}
|
package/ts/index.ts
CHANGED
|
@@ -8,3 +8,4 @@ export { DomainIntelligence } from './smartnetwork.classes.domainintelligence.js
|
|
|
8
8
|
export type { IDomainIntelligenceResult, IDomainIntelligenceOptions } from './smartnetwork.classes.domainintelligence.js';
|
|
9
9
|
export { setLogger, getLogger } from './logging.js';
|
|
10
10
|
export { NetworkError, TimeoutError } from './errors.js';
|
|
11
|
+
export { isValidIpv4, isValidIpv6, getIpVersion, expandIpv6, toReverseDnsName, isPublicIpAddress } from './helpers/ip.js';
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { getLogger } from './logging.js';
|
|
2
|
+
import { isValidIpv4, isValidIpv6 } from './helpers/ip.js';
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* Service configuration for IP detection
|
|
@@ -136,28 +137,13 @@ export class PublicIp {
|
|
|
136
137
|
* Validate IPv4 address format
|
|
137
138
|
*/
|
|
138
139
|
private isValidIpv4(ip: string): boolean {
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
const ipv4Regex = /^(\d{1,3}\.){3}\d{1,3}$/;
|
|
142
|
-
if (!ipv4Regex.test(ip)) return false;
|
|
143
|
-
|
|
144
|
-
const parts = ip.split('.');
|
|
145
|
-
return parts.every(part => {
|
|
146
|
-
const num = parseInt(part, 10);
|
|
147
|
-
return num >= 0 && num <= 255;
|
|
148
|
-
});
|
|
140
|
+
return isValidIpv4(ip);
|
|
149
141
|
}
|
|
150
142
|
|
|
151
143
|
/**
|
|
152
144
|
* Validate IPv6 address format
|
|
153
145
|
*/
|
|
154
146
|
private isValidIpv6(ip: string): boolean {
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
// Simplified IPv6 validation - checks for colon-separated hex values
|
|
158
|
-
const ipv6Regex = /^([0-9a-fA-F]{0,4}:){2,7}[0-9a-fA-F]{0,4}$/;
|
|
159
|
-
const ipv6CompressedRegex = /^::([0-9a-fA-F]{0,4}:){0,6}[0-9a-fA-F]{0,4}$|^([0-9a-fA-F]{0,4}:){1,7}:$/;
|
|
160
|
-
|
|
161
|
-
return ipv6Regex.test(ip) || ipv6CompressedRegex.test(ip);
|
|
147
|
+
return isValidIpv6(ip);
|
|
162
148
|
}
|
|
163
149
|
}
|