@k03mad/dns-leak 1.1.0 → 2.0.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.
@@ -22,7 +22,7 @@ jobs:
22
22
  uses: actions/checkout@v4
23
23
 
24
24
  - name: Install NodeJS
25
- uses: actions/setup-node@v3
25
+ uses: actions/setup-node@v4
26
26
  with:
27
27
  node-version-file: '.nvmrc'
28
28
 
package/README.md CHANGED
@@ -1,6 +1,8 @@
1
1
  # DNS leak test
2
2
 
3
- Based on: [ipleak.net](https://ipleak.net/) / [API](https://airvpn.org/forums/topic/14737-api/)
3
+ Based on:\
4
+ — [ipleak.net](https://ipleak.net) ([API](https://airvpn.org/forums/topic/14737-api))\
5
+ — [ipwhois.io](https://ipwhois.io) ([API](https://ipwhois.io/documentation))
4
6
 
5
7
  ## Global
6
8
 
@@ -17,20 +19,21 @@ npm i @k03mad/dns-leak
17
19
  ```
18
20
 
19
21
  ```js
20
- import IPLeak from '@k03mad/dns-leak';
22
+ import {IPLeak, IPWhois} from '@k03mad/dns-leak';
21
23
 
22
- // default params (details at the link below)
23
- const api = new IPLeak();
24
+ // default params (details at the ./app/api folder)
25
+ const LeakApi = new IPLeak();
26
+ const WhoisApi = new IPWhois();
24
27
 
25
28
  // get current external ip info
26
- await api.getIpInfo();
29
+ await WhoisApi.getIpInfo();
30
+ await LeakApi.getIpInfo();
27
31
  // get other ip info
28
- await api.getIpInfo({ip: '8.8.8.8'});
32
+ await WhoisApi.getIpInfo({ip: '8.8.8.8'});
33
+ await LeakApi.getIpInfo({ip: '8.8.8.8'});
29
34
 
30
35
  // dns leak check with one request (one request — fewer dns ips)
31
- await api.getDnsInfoOnce();
36
+ await LeakApi.getDnsInfoOnce();
32
37
  // dns leak check with multi requests
33
- await api.getDnsInfoMulti();
38
+ await LeakApi.getDnsInfoMulti();
34
39
  ```
35
-
36
- [Options and parameters with their default values](/app/api/IPLeak.js#L8)
package/app/api/IPLeak.js CHANGED
@@ -3,12 +3,12 @@ import {customAlphabet} from 'nanoid';
3
3
  import {lowercase, numbers} from 'nanoid-dictionary';
4
4
 
5
5
  import {sleep} from '../helpers/promise.js';
6
+ import * as spinner from '../helpers/spinner.js';
6
7
 
7
8
  /** */
8
9
  export default class IPLeak {
9
10
 
10
11
  /**
11
- * All args are not required, the default ones works fine
12
12
  * @param {object} [opts]
13
13
  * @param {number} [opts.dnsRequestsCount] dns leak multi requests count with one session
14
14
  * @param {number} [opts.dnsRequestsRps] dns leak requests rps
@@ -93,15 +93,25 @@ export default class IPLeak {
93
93
  /**
94
94
  * @param {object} [opts]
95
95
  * @param {string} [opts.session]
96
+ * @param {boolean} [opts.isSpinnerEnabled]
96
97
  * @returns {Promise<object>}
97
98
  */
98
- async getDnsInfoMulti({session = this._dnsSessionString} = {}) {
99
- const arrayFromLen = Array.from({length: this._dnsRequestsCount});
99
+ async getDnsInfoMulti({isSpinnerEnabled, session = this._dnsSessionString} = {}) {
100
+ const spinnerName = 'dnsReq';
101
+ const arrayFromLen = Array.from({length: this._dnsRequestsCount - 1});
100
102
 
101
- await Promise.all(arrayFromLen.map(() => this.getDnsInfoOnce({session})));
102
- await sleep(this._dnsRequestsWaitBeforeLastMs);
103
+ spinner.start(spinnerName, isSpinnerEnabled);
104
+
105
+ await Promise.all(arrayFromLen.map(async () => {
106
+ await this.getDnsInfoOnce({session});
107
+ spinner.count(spinnerName, this._dnsRequestsCount);
108
+ }));
103
109
 
110
+ spinner.text(spinnerName, 'Wait for last request');
111
+ await sleep(this._dnsRequestsWaitBeforeLastMs);
104
112
  const info = await this.getDnsInfoOnce({session});
113
+
114
+ spinner.stop(spinnerName);
105
115
  return info;
106
116
  }
107
117
 
@@ -0,0 +1,44 @@
1
+ import {requestCache} from '@k03mad/request';
2
+
3
+ /** */
4
+ export default class IPWhois {
5
+
6
+ /**
7
+ * @param {object} [opts]
8
+ * @param {number} [opts.ipRequestsCacheExpireMs] ip info requests cache ttl ms for same ip
9
+ * @param {number} [opts.ipRequestsRps] ip info requests rps
10
+ */
11
+ constructor({
12
+ ipRequestsCacheExpireMs = 3_600_000,
13
+ ipRequestsRps = 2,
14
+ } = {}) {
15
+ this._ipRequestsCacheExpireMs = ipRequestsCacheExpireMs;
16
+ this._ipRequestsRps = ipRequestsRps;
17
+ }
18
+
19
+ /** */
20
+ get _endpoints() {
21
+ return {
22
+
23
+ /** @param {string} ip */
24
+ ip: ip => `https://ipwho.is/${ip}`,
25
+ };
26
+ }
27
+
28
+ /**
29
+ * @param {object} [opts]
30
+ * @param {string} [opts.ip]
31
+ * @returns {Promise<object>}
32
+ */
33
+ async getIpInfo({ip = ''} = {}) {
34
+ const ipEndpoint = this._endpoints.ip(ip);
35
+
36
+ const {body} = await requestCache(ipEndpoint, {}, {
37
+ expire: this._ipRequestsCacheExpireMs,
38
+ rps: this._ipRequestsRps,
39
+ });
40
+
41
+ return body;
42
+ }
43
+
44
+ }
@@ -0,0 +1,2 @@
1
+ export {default as IPLeak} from './IPLeak.js';
2
+ export {default as IPWhois} from './IPWhois.js';
@@ -1,14 +1,5 @@
1
- import chalk from 'chalk';
2
-
3
- const {bgBlackBright, magenta} = chalk;
4
-
5
1
  /**
6
2
  * @param {any} msg
7
3
  */
8
4
  // eslint-disable-next-line no-console
9
- export const log = (...msg) => console.log(...msg);
10
-
11
- /**
12
- * @param {any} header
13
- */
14
- export const logHeader = header => log(`\n${bgBlackBright(magenta(` ${header} `))}\n`);
5
+ export const log = (...msg) => msg.forEach(elem => console.log(elem));
@@ -0,0 +1,49 @@
1
+ import ora from 'ora';
2
+
3
+ import {bar} from './text.js';
4
+
5
+ export const spinner = {};
6
+
7
+ /**
8
+ * @param {string} name
9
+ * @param {boolean} active
10
+ */
11
+ export const start = (name, active) => {
12
+ if (active) {
13
+ spinner[name] = {
14
+ instance: ora().start(),
15
+ counter: 0,
16
+ };
17
+
18
+ return spinner[name].instance;
19
+ }
20
+
21
+ spinner[name] = {instance: {stop: () => ''}};
22
+ };
23
+
24
+ /**
25
+ * @param {string} name
26
+ * @param {string} msg
27
+ */
28
+ export const text = (name, msg) => {
29
+ spinner[name].instance.text = bar(msg);
30
+ };
31
+
32
+ /**
33
+ * @param {string} name
34
+ * @param {number} total
35
+ */
36
+ export const count = (name, total) => {
37
+ spinner[name].counter++;
38
+
39
+ const len = `${String(spinner[name].counter).padStart(String(total).length, '0')}/${total}`;
40
+ const percent = `${String((spinner[name].counter * 100 / total).toFixed(0)).padStart(2, '0')}%`;
41
+ text(name, `${len} [${percent}]`);
42
+ };
43
+
44
+ /**
45
+ * @param {string} name
46
+ */
47
+ export const stop = name => {
48
+ spinner[name].instance.stop();
49
+ };
@@ -0,0 +1,62 @@
1
+ import chalk from 'chalk';
2
+
3
+ const {bgBlackBright, blue, gray, green, magenta, yellow} = chalk;
4
+
5
+ /**
6
+ * @param {any} msg
7
+ */
8
+ export const header = msg => bgBlackBright(magenta(` ${msg} `));
9
+
10
+ /**
11
+ * @param {any} msg
12
+ */
13
+ export const bar = msg => yellow(msg);
14
+
15
+ /**
16
+ * @param {object} [ipInfo]
17
+ * @param {string} [ipInfo.city]
18
+ * @param {string} [ipInfo.country]
19
+ * @param {string} [ipInfo.ip]
20
+ * @param {string} [ipInfo.region]
21
+ * @param {object} [ipInfo.flag]
22
+ * @param {object} [ipInfo.connection]
23
+ */
24
+ export const formatIpInfo = ({city, connection, country, flag, ip, region} = {}) => {
25
+ let output = '';
26
+
27
+ if (ip) {
28
+ output += `${blue(ip)}\n`;
29
+ }
30
+
31
+ if (connection?.org) {
32
+ output += `${green(connection.org)} `;
33
+ }
34
+
35
+ if (connection?.isp && !connection?.org?.includes(connection?.isp)) {
36
+ if (connection?.org) {
37
+ output += green('/ ');
38
+ }
39
+
40
+ output += `${green(connection.isp)} `;
41
+ }
42
+
43
+ if (connection?.domain) {
44
+ output += gray(`(${connection.domain})`);
45
+ }
46
+
47
+ output += '\n';
48
+
49
+ if (flag?.emoji) {
50
+ output += `${flag.emoji} `;
51
+ }
52
+
53
+ output += [
54
+ ...new Set([
55
+ country,
56
+ region,
57
+ city,
58
+ ]),
59
+ ].filter(Boolean).join(' :: ');
60
+
61
+ return output;
62
+ };
package/app/run.js ADDED
@@ -0,0 +1,37 @@
1
+ #!/usr/bin/env node
2
+ import {IPLeak, IPWhois} from './api/index.js';
3
+ import {log} from './helpers/log.js';
4
+ import * as spinner from './helpers/spinner.js';
5
+ import {formatIpInfo, header} from './helpers/text.js';
6
+
7
+ const LeakApi = new IPLeak();
8
+ const WhoisApi = new IPWhois();
9
+
10
+ const currentIpInfo = await WhoisApi.getIpInfo();
11
+
12
+ log(
13
+ '',
14
+ header('IP'),
15
+ '',
16
+ formatIpInfo(currentIpInfo),
17
+ '',
18
+ header('DNS'),
19
+ '',
20
+ );
21
+
22
+ const dnsInfo = await LeakApi.getDnsInfoMulti({isSpinnerEnabled: true});
23
+ const dnsIps = [...new Set(Object.keys(dnsInfo.ip))];
24
+
25
+ spinner.start(currentIpInfo.ip, true);
26
+
27
+ const dnsData = await Promise.all(dnsIps.map(async ip => {
28
+ const data = await WhoisApi.getIpInfo({ip});
29
+ spinner.count(currentIpInfo.ip, dnsIps.length);
30
+ return data;
31
+ }));
32
+
33
+ spinner.stop(currentIpInfo.ip);
34
+
35
+ dnsData
36
+ .sort((a, b) => a?.ip?.localeCompare(b?.ip))
37
+ .forEach(data => log(formatIpInfo(data), ''));
package/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@k03mad/dns-leak",
3
- "version": "1.1.0",
3
+ "version": "2.0.0",
4
4
  "description": "DNS leak test",
5
5
  "maintainers": [
6
6
  "Kirill Molchanov <k03.mad@gmail.com"
7
7
  ],
8
- "main": "app/api/IPLeak.js",
9
- "bin": "app/index.js",
8
+ "main": "app/api/index.js",
9
+ "bin": "app/run.js",
10
10
  "repository": "k03mad/dns-leak",
11
11
  "license": "MIT",
12
12
  "type": "module",
@@ -16,9 +16,9 @@
16
16
  "dependencies": {
17
17
  "@k03mad/request": "3.2.0",
18
18
  "chalk": "5.3.0",
19
- "country-locale-map": "1.8.15",
20
19
  "nanoid": "5.0.2",
21
- "nanoid-dictionary": "5.0.0-beta.1"
20
+ "nanoid-dictionary": "5.0.0-beta.1",
21
+ "ora": "7.0.1"
22
22
  },
23
23
  "devDependencies": {
24
24
  "@k03mad/eslint-config": "13.3.1",
package/app/helpers/ip.js DELETED
@@ -1,47 +0,0 @@
1
- /* eslint-disable camelcase */
2
-
3
- import chalk from 'chalk';
4
- import clm from 'country-locale-map';
5
-
6
- const {blue, green} = chalk;
7
-
8
- /**
9
- * @param {object} [ipInfo]
10
- * @param {string} [ipInfo.city_name]
11
- * @param {string} [ipInfo.country_code]
12
- * @param {string} [ipInfo.country_name]
13
- * @param {string} [ipInfo.ip]
14
- * @param {string} [ipInfo.isp_name]
15
- * @param {string} [ipInfo.region_name]
16
- */
17
- export const formatIpInfo = ({city_name, country_code, country_name, ip, isp_name, region_name} = {}) => {
18
- let output = '';
19
-
20
- if (ip) {
21
- output += `${blue(ip)} `;
22
- }
23
-
24
- if (isp_name) {
25
- output += green(isp_name);
26
- }
27
-
28
- output += '\n';
29
-
30
- if (country_code) {
31
- const {emoji} = clm.getCountryByAlpha2(country_code);
32
-
33
- if (emoji) {
34
- output += `${emoji} `;
35
- }
36
- }
37
-
38
- output += [
39
- ...new Set([
40
- country_name,
41
- region_name,
42
- city_name,
43
- ]),
44
- ].filter(Boolean).join(' :: ');
45
-
46
- return output;
47
- };
package/app/index.js DELETED
@@ -1,22 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- import IPLeak from './api/IPLeak.js';
4
- import {formatIpInfo} from './helpers/ip.js';
5
- import {log, logHeader} from './helpers/log.js';
6
-
7
- const api = new IPLeak();
8
-
9
- const currentIpInfo = await api.getIpInfo();
10
-
11
- logHeader('IP');
12
- log(formatIpInfo(currentIpInfo));
13
- logHeader('DNS');
14
-
15
- const dnsInfo = await api.getDnsInfoMulti();
16
- const dnsIps = [...new Set(Object.keys(dnsInfo.ip))];
17
-
18
- const dnsData = await Promise.all(dnsIps.map(ip => api.getIpInfo({ip})));
19
-
20
- dnsData
21
- .sort((a, b) => a?.ip?.localeCompare(b?.ip))
22
- .forEach(data => log(formatIpInfo(data), '\n'));