is-localhost-ip 2.0.0 → 3.0.1

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.
Files changed (3) hide show
  1. package/README.md +42 -20
  2. package/index.js +38 -31
  3. package/package.json +31 -44
package/README.md CHANGED
@@ -1,12 +1,16 @@
1
- [![codecov](https://codecov.io/gh/tinovyatkin/is-localhost-ip/branch/master/graph/badge.svg)](https://codecov.io/gh/tinovyatkin/is-localhost-ip) ![node](https://img.shields.io/node/v/is-localhost-ip)
1
+ [![codecov](https://codecov.io/gh/tinovyatkin/is-localhost-ip/branch/master/graph/badge.svg)](https://codecov.io/gh/tinovyatkin/is-localhost-ip)
2
+ ![node](https://img.shields.io/node/v/is-localhost-ip)
2
3
 
3
4
  # is-localhost-ip
4
5
 
5
- Comprehensive and robust library to checks whether given host name or IPv4/IPv6 address belongs to the local machine
6
+ Zero-dependency Node.js utility that checks whether a hostname or IPv4/IPv6 address refers to the local machine.
6
7
 
7
- Main difference from other libraries here is comprehensiveness: we start from strict RegExp checks (for IP address first, and then for correctness to be a host name), then fallback to DNS resolver (so it works with something like `john.dev` remapped locally in `hosts` or with local resolver).
8
+ This package aims to be strict and comprehensive:
8
9
 
9
- All this in just _~100 lines of code_ without external dependencies.
10
+ - Validates input as an IP address or a syntactically valid hostname (including bracketed IPv6).
11
+ - Treats private/loopback/link-local ranges as local.
12
+ - Optionally verifies the address exists on the current machine by attempting to bind to it.
13
+ - Falls back to DNS resolution, so it works with hostnames mapped in `/etc/hosts` or a local resolver.
10
14
 
11
15
  ## Installation
12
16
 
@@ -14,37 +18,55 @@ All this in just _~100 lines of code_ without external dependencies.
14
18
  npm i is-localhost-ip
15
19
  # or
16
20
  yarn add is-localhost-ip
21
+ # or
22
+ pnpm add is-localhost-ip
17
23
  ```
18
24
 
19
- ## Example
25
+ Requires Node.js `>=18`.
26
+
27
+ ## Usage
20
28
 
21
29
  ```js
22
- const isLocalhost = require('is-localhost-ip');
30
+ const isLocalhost = require("is-localhost-ip");
23
31
 
24
32
  (async () => {
25
- await isLocalhost('127.0.0.1'); // true
26
- await isLocalhost('::ffff:127.0.0.1'); // true
27
- await isLocalhost('192.168.0.12'); // true
28
- await isLocalhost('192.168.0.12', true); // true only if the local machine has an interface with that address
29
- await isLocalhost('184.55.123.2'); // false
30
-
31
- await isLocalhost('tino.local'); // true
32
- await isLocalhost('localhost'); // true
33
- await isLocalhost('microsoft.com'); // false
33
+ await isLocalhost("127.0.0.1"); // true
34
+ await isLocalhost("::ffff:127.0.0.1"); // true
35
+ await isLocalhost("192.168.0.12"); // true
36
+ await isLocalhost("192.168.0.12", true); // true only if an interface has this address
37
+ await isLocalhost("184.55.123.2"); // false
38
+
39
+ await isLocalhost("tino.local"); // true if it resolves to a local address
40
+ await isLocalhost("localhost"); // true
41
+ await isLocalhost("microsoft.com"); // false
34
42
  })();
35
43
  ```
36
44
 
45
+ ## API
46
+
47
+ ### `isLocalhost(ipOrHostname, canBind?)`
48
+
49
+ Returns a `Promise<boolean>`.
50
+
51
+ - `ipOrHostname` (`string`): IP address (v4/v6) or a hostname.
52
+ - `canBind` (`boolean`, default `false`): when `true`, additionally checks that the local machine can bind to the
53
+ address (i.e., it is configured on a local interface).
54
+
55
+ The function throws for invalid inputs (non-string values or syntactically invalid hostnames).
56
+
37
57
  ## Caveats
38
58
 
39
- Doesn't work with internationalized ([RFC 3492](https://tools.ietf.org/html/rfc3492) or [RFC 5891](https://tools.ietf.org/html/rfc5891)) domain names. If you need that please use wonderful [Punycode.js](https://github.com/bestiejs/punycode.js) to convert the string before passing to this library:
59
+ Internationalized domain names (IDNs) are not supported. If you need IDNs, use
60
+ [Punycode.js](https://github.com/bestiejs/punycode.js) (or another punycode implementation) to convert the input
61
+ to ASCII before calling this function:
40
62
 
41
63
  ```js
42
- const isLocalhost = require('is-localhost-ip');
43
- const punycode = require('punycode');
64
+ const isLocalhost = require("is-localhost-ip");
65
+ const punycode = require("punycode");
44
66
 
45
67
  (async () => {
46
- await isLocalhost(punycode.toASCII('свобода.рф')); // false
47
- await isLocalhost(punycode.toASCII('私の.家')); // true
68
+ await isLocalhost(punycode.toASCII("свобода.рф")); // false
69
+ await isLocalhost(punycode.toASCII("私の.家")); // true
48
70
  })();
49
71
  ```
50
72
 
package/index.js CHANGED
@@ -1,9 +1,9 @@
1
- 'use strict';
1
+ "use strict";
2
2
 
3
- const { isIP, isIPv4 } = require('net');
4
- const { createSocket } = require('dgram');
5
- const { ADDRCONFIG } = require('dns');
6
- const { lookup } = require('dns').promises;
3
+ const { isIP, isIPv4 } = require("node:net");
4
+ const { createSocket } = require("node:dgram");
5
+ const { ADDRCONFIG } = require("node:dns");
6
+ const { lookup } = require("node:dns").promises;
7
7
 
8
8
  /**
9
9
  * Addresses reserved for private networks
@@ -22,15 +22,13 @@ const IP_RANGES = [
22
22
  // 192.168.0.0 - 192.168.255.255
23
23
  /^(:{2}f{4}:)?192\.168(?:\.\d{1,3}){2}$/,
24
24
  // fc00::/7
25
- /^f[cd][\da-f]{2}(::1$|:[\da-f]{1,4}){1,7}$/,
25
+ /^f[cd][\da-f]{2}(::1?$|:[\da-f]{1,4}){1,7}$/,
26
26
  // fe80::/10
27
- /^fe[89ab][\da-f](::1$|:[\da-f]{1,4}){1,7}$/,
27
+ /^fe[89ab][\da-f](::1?$|:[\da-f]{1,4}){1,7}$/,
28
28
  ];
29
29
 
30
30
  // Concat all RegExes from above into one
31
- const IP_TESTER_RE = new RegExp(
32
- `^(${IP_RANGES.map((re) => re.source).join('|')})$`,
33
- );
31
+ const IP_TESTER_RE = new RegExp(`^(${IP_RANGES.map((re) => re.source).join("|")})$`);
34
32
 
35
33
  /**
36
34
  * Syntax validation RegExp for possible valid host names. Permits underscore.
@@ -47,12 +45,12 @@ const VALID_HOSTNAME =
47
45
  * @returns {Promise<boolean>}
48
46
  */
49
47
  async function canBindToIp(ip) {
50
- const socket = createSocket(isIPv4(ip) ? 'udp4' : 'udp6');
48
+ const socket = createSocket(isIPv4(ip) ? "udp4" : "udp6");
51
49
  return new Promise((resolve) => {
52
50
  try {
53
51
  socket
54
- .once('error', () => socket.close(() => resolve(false)))
55
- .once('listening', () => socket.close(() => resolve(true)))
52
+ .once("error", () => socket.close(() => resolve(false)))
53
+ .once("listening", () => socket.close(() => resolve(true)))
56
54
  .unref()
57
55
  .bind(0, ip);
58
56
  } catch {
@@ -69,31 +67,40 @@ async function canBindToIp(ip) {
69
67
  * @returns {Promise.<boolean>} - true, if given strings is a local IP address or DNS names that resolves to local IP
70
68
  */
71
69
  async function isLocalhost(ipOrHostname, canBind = false) {
72
- if (typeof ipOrHostname !== 'string') return false;
70
+ if (typeof ipOrHostname !== "string") {
71
+ throw new TypeError("Invalid ip or hostname provided");
72
+ }
73
+
74
+ // Removes [ and ] around ipv6 hostnames
75
+ const normalizedIpOrHostname = ipOrHostname.replaceAll(/^\[|\]$/g, "");
73
76
 
74
77
  // Check if given string is an IP address
75
- if (isIP(ipOrHostname)) {
76
- if (IP_TESTER_RE.test(ipOrHostname) && !canBind) return true;
77
- return canBindToIp(ipOrHostname);
78
+ if (isIP(normalizedIpOrHostname)) {
79
+ if (IP_TESTER_RE.test(normalizedIpOrHostname) && !canBind) return true;
80
+ return canBindToIp(normalizedIpOrHostname);
78
81
  }
79
82
 
80
83
  // May it be a hostname?
81
- if (!VALID_HOSTNAME.test(ipOrHostname)) return false;
84
+ if (!VALID_HOSTNAME.test(normalizedIpOrHostname)) {
85
+ throw new Error("Invalid ip or hostname provided");
86
+ }
82
87
 
83
88
  // it's a DNS name
84
- try {
85
- const addresses = await lookup(ipOrHostname, {
86
- all: true,
87
- family: 0,
88
- verbatim: true,
89
- hints: ADDRCONFIG,
90
- });
91
- if (!Array.isArray(addresses)) return false;
92
- for (const { address } of addresses) {
93
- if (await isLocalhost(address, canBind)) return true;
94
- }
95
- // eslint-disable-next-line no-empty
96
- } catch {}
89
+ const addresses = await lookup(normalizedIpOrHostname, {
90
+ all: true,
91
+ family: 0,
92
+ verbatim: true,
93
+ hints: ADDRCONFIG,
94
+ });
95
+
96
+ if (!Array.isArray(addresses)) {
97
+ throw new TypeError("DNS Lookup failed.");
98
+ }
99
+
100
+ for (const { address } of addresses) {
101
+ if (await isLocalhost(address, canBind)) return true;
102
+ }
103
+
97
104
  return false;
98
105
  }
99
106
 
package/package.json CHANGED
@@ -1,59 +1,46 @@
1
1
  {
2
2
  "name": "is-localhost-ip",
3
- "version": "2.0.0",
3
+ "version": "3.0.1",
4
4
  "description": "Checks whether given DNS name or IPv4/IPv6 address belongs to a local machine",
5
- "main": "index.js",
6
- "files": [
7
- "index.js",
8
- "index.d.ts"
5
+ "keywords": [
6
+ "check-localhost",
7
+ "dns",
8
+ "ip",
9
+ "is-local-ip",
10
+ "is-localhost",
11
+ "is-loopback",
12
+ "localhost"
9
13
  ],
10
- "types": "index.d.ts",
11
- "engines": {
12
- "node": ">=12"
13
- },
14
- "scripts": {
15
- "test": "jest --coverage --verbose",
16
- "lint": "eslint ."
14
+ "homepage": "https://github.com/tinovyatkin/is-localhost-ip#readme",
15
+ "bugs": {
16
+ "url": "https://github.com/tinovyatkin/is-localhost-ip/issues"
17
17
  },
18
+ "license": "MIT",
19
+ "author": "Konstantin Vyatkin <tino@vtkn.io>",
18
20
  "repository": {
19
21
  "type": "git",
20
22
  "url": "git+https://github.com/tinovyatkin/is-localhost-ip.git"
21
23
  },
22
- "keywords": [
23
- "localhost",
24
- "is-localhost",
25
- "check-localhost",
26
- "is-loopback",
27
- "is-local-ip",
28
- "ip",
29
- "dns"
24
+ "files": [
25
+ "index.d.ts",
26
+ "index.js"
30
27
  ],
31
- "author": "Konstantin Vyatkin <tino@vtkn.io>",
32
- "license": "MIT",
33
- "bugs": {
34
- "url": "https://github.com/tinovyatkin/is-localhost-ip/issues"
28
+ "main": "index.js",
29
+ "browser": false,
30
+ "types": "index.d.ts",
31
+ "scripts": {
32
+ "test": "node --test --experimental-test-module-mocks --experimental-test-coverage --test-reporter=spec --test-reporter-destination=stdout --test-reporter=lcov --test-reporter-destination=coverage/lcov.info __tests__/*.test.js",
33
+ "lint": "oxlint --fix --fix-suggestions",
34
+ "format": "oxfmt --write .",
35
+ "prepare": "lefthook install"
35
36
  },
36
- "homepage": "https://github.com/tinovyatkin/is-localhost-ip#readme",
37
37
  "devDependencies": {
38
- "@types/jest": "28.1.6",
39
- "@types/node": "~12",
40
- "eslint": "8.21.0",
41
- "eslint-config-prettier": "8.5.0",
42
- "eslint-plugin-node": "11.1.0",
43
- "eslint-plugin-prettier": "4.2.1",
44
- "eslint-plugin-regexp": "^1.8.0",
45
- "jest": "28.1.3",
46
- "prettier": "2.7.1"
38
+ "@types/node": "22.15.21",
39
+ "lefthook": "^2.0.15",
40
+ "oxfmt": "^0.24.0",
41
+ "oxlint": "^1.39.0"
47
42
  },
48
- "jest": {
49
- "coverageReporters": [
50
- "text",
51
- "json",
52
- "cobertura",
53
- "lcov"
54
- ],
55
- "coverageProvider": "v8",
56
- "testEnvironment": "node",
57
- "collectCoverage": true
43
+ "engines": {
44
+ "node": ">=18"
58
45
  }
59
46
  }