Skip to content

Commit e53d7fe

Browse files
committedDec 27, 2021
Require Node.js 14 and move to ESM
1 parent 4fcb341 commit e53d7fe

14 files changed

+212
-174
lines changed
 

‎browser.d.ts

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export {default} from './index.js';
2+
export * from './index.js';

‎browser.js

+21-11
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
1-
'use strict';
2-
const isIp = require('is-ip');
1+
import isIp from 'is-ip';
32

4-
class CancelError extends Error {
3+
export class CancelError extends Error {
54
constructor() {
65
super('Request was cancelled');
76
this.name = 'CancelError';
@@ -13,18 +12,18 @@ class CancelError extends Error {
1312
}
1413

1514
const defaults = {
16-
timeout: 5000
15+
timeout: 5000,
1716
};
1817

1918
const urls = {
2019
v4: [
2120
'https://ipv4.icanhazip.com/',
22-
'https://api.ipify.org/'
21+
'https://api.ipify.org/',
2322
],
2423
v6: [
2524
'https://ipv6.icanhazip.com/',
26-
'https://api6.ipify.org/'
27-
]
25+
'https://api6.ipify.org/',
26+
],
2827
};
2928

3029
const sendXhr = (url, options, version) => {
@@ -63,21 +62,28 @@ const sendXhr = (url, options, version) => {
6362
const queryHttps = (version, options) => {
6463
let request;
6564
const promise = (async function () {
66-
const urls_ = [].concat.apply(urls[version], options.fallbackUrls || []);
65+
const urls_ = [
66+
...urls[version],
67+
...(options.fallbackUrls ?? []),
68+
];
69+
70+
let lastError;
6771
for (const url of urls_) {
6872
try {
6973
request = sendXhr(url, options, version);
7074
// eslint-disable-next-line no-await-in-loop
7175
const ip = await request;
7276
return ip;
7377
} catch (error) {
78+
lastError = error;
79+
7480
if (error instanceof CancelError) {
7581
throw error;
7682
}
7783
}
7884
}
7985

80-
throw new Error('Couldn\'t find your IP');
86+
throw new Error('Could not find your IP address', {cause: lastError});
8187
})();
8288

8389
promise.cancel = () => {
@@ -87,6 +93,10 @@ const queryHttps = (version, options) => {
8793
return promise;
8894
};
8995

90-
module.exports.v4 = options => queryHttps('v4', {...defaults, ...options});
96+
const publicIp = {};
97+
98+
publicIp.v4 = options => queryHttps('v4', {...defaults, ...options});
99+
100+
publicIp.v6 = options => queryHttps('v6', {...defaults, ...options});
91101

92-
module.exports.v6 = options => queryHttps('v6', {...defaults, ...options});
102+
export default publicIp;

‎index.d.ts

+51-53
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,45 @@
1-
declare namespace publicIp {
2-
interface Options {
3-
/**
4-
Use a HTTPS check using the [icanhazip.com](https://github.com/major/icanhaz) service instead of the DNS query. [ipify.org](https://www.ipify.org) is used as a fallback if `icanhazip.com` fails. This check is much more secure and tamper-proof, but also a lot slower. __This option is only available in the Node.js version__. The default behaviour is to check against DNS before using HTTPS fallback. If set to `true`, it will _only_ check against HTTPS.
5-
6-
@default false
7-
*/
8-
readonly onlyHttps?: boolean;
9-
10-
/**
11-
The time in milliseconds until a request is considered timed out.
12-
13-
@default 5000
14-
*/
15-
readonly timeout?: number;
16-
17-
/**
18-
Add your own custom HTTPS endpoints to get the public IP from. They will only be used if everything else fails. Any service used as fallback _must_ return the IP as a plain string.
19-
20-
@default []
21-
22-
@example
23-
```
24-
import publicIp = require('public-ip');
25-
26-
(async () => {
27-
await publicIp.v6({
28-
fallbackUrls: [
29-
'https://ifconfig.co/ip'
30-
]
31-
});
32-
})();
33-
```
34-
*/
35-
readonly fallbackUrls?: readonly string[];
36-
}
37-
38-
type CancelablePromise<T> = Promise<T> & {
39-
cancel(): void;
40-
};
1+
export interface Options {
2+
/**
3+
Use a HTTPS check using the [icanhazip.com](https://github.com/major/icanhaz) service instead of the DNS query. [ipify.org](https://www.ipify.org) is used as a fallback if `icanhazip.com` fails. This check is much more secure and tamper-proof, but also a lot slower.
4+
5+
__This option is only available in the Node.js version__.
6+
7+
The default behaviour is to check against DNS before using HTTPS fallback. If set to `true`, it will _only_ check against HTTPS.
8+
9+
@default false
10+
*/
11+
readonly onlyHttps?: boolean;
12+
13+
/**
14+
The time in milliseconds until a request is considered timed out.
15+
16+
@default 5000
17+
*/
18+
readonly timeout?: number;
19+
20+
/**
21+
Add your own custom HTTPS endpoints to get the public IP from. They will only be used if everything else fails. Any service used as fallback _must_ return the IP as a plain string.
22+
23+
@default []
24+
25+
@example
26+
```
27+
import publicIp from 'public-ip';
28+
29+
await publicIp.v6({
30+
fallbackUrls: [
31+
'https://ifconfig.co/ip'
32+
]
33+
});
34+
```
35+
*/
36+
readonly fallbackUrls?: readonly string[];
4137
}
4238

39+
export type CancelablePromise<T> = Promise<T> & {
40+
cancel(): void;
41+
};
42+
4343
declare const publicIp: {
4444
/**
4545
Get your public IP address - very fast!
@@ -51,15 +51,13 @@ declare const publicIp: {
5151
5252
@example
5353
```
54-
import publicIp = require('public-ip');
54+
import publicIp from 'public-ip';
5555
56-
(async () => {
57-
console.log(await publicIp.v4());
58-
//=> '46.5.21.123'
59-
})();
56+
console.log(await publicIp.v4());
57+
//=> '46.5.21.123'
6058
```
6159
*/
62-
v4(options?: publicIp.Options): publicIp.CancelablePromise<string>;
60+
v4(options?: Options): CancelablePromise<string>;
6361

6462
/**
6563
Get your public IP address - very fast!
@@ -71,15 +69,15 @@ declare const publicIp: {
7169
7270
@example
7371
```
74-
import publicIp = require('public-ip');
72+
import publicIp from 'public-ip';
7573
76-
(async () => {
77-
console.log(await publicIp.v6());
78-
//=> 'fe80::200:f8ff:fe21:67cf'
79-
})();
74+
console.log(await publicIp.v6());
75+
//=> 'fe80::200:f8ff:fe21:67cf'
8076
```
8177
*/
82-
v6(options?: publicIp.Options): publicIp.CancelablePromise<string>;
78+
v6(options?: Options): CancelablePromise<string>;
8379
};
8480

85-
export = publicIp;
81+
export default publicIp;
82+
83+
export {CancelError} from 'got';

‎index.js

+63-42
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
1-
'use strict';
2-
const {promisify} = require('util');
3-
const dgram = require('dgram');
4-
const dns = require('dns-socket');
5-
const {get: got, CancelError} = require('got');
6-
const isIp = require('is-ip');
1+
import {promisify} from 'node:util';
2+
import dgram from 'node:dgram';
3+
import dns from 'dns-socket';
4+
import got, {CancelError} from 'got';
5+
import isIp from 'is-ip';
76

87
const defaults = {
98
timeout: 5000,
10-
onlyHttps: false
9+
onlyHttps: false,
1110
};
1211

1312
const dnsServers = [
@@ -17,65 +16,65 @@ const dnsServers = [
1716
'208.67.222.222',
1817
'208.67.220.220',
1918
'208.67.222.220',
20-
'208.67.220.222'
19+
'208.67.220.222',
2120
],
2221
name: 'myip.opendns.com',
23-
type: 'A'
22+
type: 'A',
2423
},
2524
v6: {
2625
servers: [
2726
'2620:0:ccc::2',
28-
'2620:0:ccd::2'
27+
'2620:0:ccd::2',
2928
],
3029
name: 'myip.opendns.com',
31-
type: 'AAAA'
32-
}
30+
type: 'AAAA',
31+
},
3332
},
3433
{
3534
v4: {
3635
servers: [
3736
'216.239.32.10',
3837
'216.239.34.10',
3938
'216.239.36.10',
40-
'216.239.38.10'
39+
'216.239.38.10',
4140
],
4241
name: 'o-o.myaddr.l.google.com',
4342
type: 'TXT',
44-
transform: ip => ip.replace(/"/g, '')
43+
transform: ip => ip.replace(/"/g, ''),
4544
},
4645
v6: {
4746
servers: [
4847
'2001:4860:4802:32::a',
4948
'2001:4860:4802:34::a',
5049
'2001:4860:4802:36::a',
51-
'2001:4860:4802:38::a'
50+
'2001:4860:4802:38::a',
5251
],
5352
name: 'o-o.myaddr.l.google.com',
5453
type: 'TXT',
55-
transform: ip => ip.replace(/"/g, '')
56-
}
57-
}
54+
transform: ip => ip.replace(/"/g, ''),
55+
},
56+
},
5857
];
5958

6059
const type = {
6160
v4: {
6261
dnsServers: dnsServers.map(({v4: {servers, ...question}}) => ({
63-
servers, question
62+
servers, question,
6463
})),
6564
httpsUrls: [
6665
'https://icanhazip.com/',
67-
'https://api.ipify.org/'
68-
]
66+
'https://api.ipify.org/',
67+
],
6968
},
7069
v6: {
7170
dnsServers: dnsServers.map(({v6: {servers, ...question}}) => ({
72-
servers, question
71+
servers, question,
7372
})),
7473
httpsUrls: [
7574
'https://icanhazip.com/',
76-
'https://api6.ipify.org/'
77-
]
78-
}
75+
'https://api6.ipify.org/',
76+
],
77+
},
7978
};
8079

8180
const queryDns = (version, options) => {
@@ -85,12 +84,14 @@ const queryDns = (version, options) => {
8584
retries: 0,
8685
maxQueries: 1,
8786
socket: dgram.createSocket(version === 'v6' ? 'udp6' : 'udp4'),
88-
timeout: options.timeout
87+
timeout: options.timeout,
8988
});
9089

9190
const socketQuery = promisify(socket.query.bind(socket));
9291

9392
const promise = (async () => {
93+
let lastError;
94+
9495
for (const dnsServerInfo of data.dnsServers) {
9596
const {servers, question} = dnsServerInfo;
9697
for (const server of servers) {
@@ -107,9 +108,9 @@ const queryDns = (version, options) => {
107108
const {
108109
answers: {
109110
0: {
110-
data
111-
}
112-
}
111+
data,
112+
},
113+
},
113114
} = dnsResponse;
114115

115116
const response = (typeof data === 'string' ? data : data.toString()).trim();
@@ -120,13 +121,15 @@ const queryDns = (version, options) => {
120121
socket.destroy();
121122
return ip;
122123
}
123-
} catch (_) {}
124+
} catch (error) {
125+
lastError = error;
126+
}
124127
}
125128
}
126129

127130
socket.destroy();
128131

129-
throw new Error('Couldn\'t find your IP');
132+
throw new Error('Could not find your IP address', {cause: lastError});
130133
})();
131134

132135
promise.cancel = () => {
@@ -142,16 +145,25 @@ const queryHttps = (version, options) => {
142145
const promise = (async () => {
143146
try {
144147
const requestOptions = {
145-
family: version === 'v6' ? 6 : 4,
146-
retries: 0,
147-
timeout: options.timeout
148+
dnsLookupIpVersion: version === 'v6' ? 6 : 4,
149+
retry: {
150+
limit: 0,
151+
},
152+
timeout: {
153+
request: options.timeout,
154+
},
148155
};
149156

150-
const urls = [].concat.apply(type[version].httpsUrls, options.fallbackUrls || []);
157+
const urls = [
158+
...type[version].httpsUrls,
159+
...(options.fallbackUrls ?? []),
160+
];
151161

162+
let lastError;
152163
for (const url of urls) {
153164
try {
154-
const gotPromise = got(url, requestOptions);
165+
// Note: We use `.get` to allow for mocking.
166+
const gotPromise = got.get(url, requestOptions);
155167
cancel = gotPromise.cancel;
156168

157169
// eslint-disable-next-line no-await-in-loop
@@ -163,13 +175,16 @@ const queryHttps = (version, options) => {
163175
return ip;
164176
}
165177
} catch (error) {
178+
lastError = error;
179+
166180
if (error instanceof CancelError) {
167181
throw error;
168182
}
169183
}
170184
}
171185

172-
throw new Error('Couldn\'t find your IP');
186+
const errorDescription = lastError ? `: ${lastError.message}` : '';
187+
throw new Error(`Could not find your IP address${errorDescription}`, {cause: lastError});
173188
} catch (error) {
174189
// Don't throw a cancellation error for consistency with DNS
175190
if (!(error instanceof CancelError)) {
@@ -193,7 +208,7 @@ const queryAll = (version, options) => {
193208
cancel = dnsPromise.cancel;
194209
try {
195210
response = await dnsPromise;
196-
} catch (_) {
211+
} catch {
197212
const httpsPromise = queryHttps(version, options);
198213
cancel = httpsPromise.cancel;
199214
response = await httpsPromise;
@@ -207,10 +222,12 @@ const queryAll = (version, options) => {
207222
return promise;
208223
};
209224

210-
module.exports.v4 = options => {
225+
const publicIp = {};
226+
227+
publicIp.v4 = options => {
211228
options = {
212229
...defaults,
213-
...options
230+
...options,
214231
};
215232

216233
if (!options.onlyHttps) {
@@ -224,10 +241,10 @@ module.exports.v4 = options => {
224241
return queryDns('v4', options);
225242
};
226243

227-
module.exports.v6 = options => {
244+
publicIp.v6 = options => {
228245
options = {
229246
...defaults,
230-
...options
247+
...options,
231248
};
232249

233250
if (!options.onlyHttps) {
@@ -240,3 +257,7 @@ module.exports.v6 = options => {
240257

241258
return queryDns('v6', options);
242259
};
260+
261+
export default publicIp;
262+
263+
export {CancelError} from 'got';

‎index.test-d.ts

+9-11
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,14 @@
11
import {expectType} from 'tsd';
2-
import publicIp = require('.');
2+
import publicIp, {CancelablePromise} from './index.js';
33

4-
const options: publicIp.Options = {};
5-
6-
expectType<publicIp.CancelablePromise<string>>(publicIp.v4());
7-
expectType<publicIp.CancelablePromise<string>>(publicIp.v4({onlyHttps: true}));
8-
expectType<publicIp.CancelablePromise<string>>(publicIp.v4({timeout: 10}));
9-
expectType<publicIp.CancelablePromise<string>>(publicIp.v4({fallbackUrls: ['https://ifconfig.io']}));
4+
expectType<CancelablePromise<string>>(publicIp.v4());
5+
expectType<CancelablePromise<string>>(publicIp.v4({onlyHttps: true}));
6+
expectType<CancelablePromise<string>>(publicIp.v4({timeout: 10}));
7+
expectType<CancelablePromise<string>>(publicIp.v4({fallbackUrls: ['https://ifconfig.io']}));
108
publicIp.v4().cancel();
119

12-
expectType<publicIp.CancelablePromise<string>>(publicIp.v6());
13-
expectType<publicIp.CancelablePromise<string>>(publicIp.v6({onlyHttps: true}));
14-
expectType<publicIp.CancelablePromise<string>>(publicIp.v6({timeout: 10}));
15-
expectType<publicIp.CancelablePromise<string>>(publicIp.v6({fallbackUrls: ['https://ifconfig.io']}));
10+
expectType<CancelablePromise<string>>(publicIp.v6());
11+
expectType<CancelablePromise<string>>(publicIp.v6({onlyHttps: true}));
12+
expectType<CancelablePromise<string>>(publicIp.v6({timeout: 10}));
13+
expectType<CancelablePromise<string>>(publicIp.v6({fallbackUrls: ['https://ifconfig.io']}));
1614
publicIp.v6().cancel();

‎license

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
MIT License
22

3-
Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (sindresorhus.com)
3+
Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (https://sindresorhus.com)
44

55
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
66

‎mocks/dns-socket.js

+5-3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
'use strict';
2-
const stub = require('./stub');
1+
import dnsSocket_ from 'dns-socket';
2+
import stub from './stub.js';
33

4-
module.exports = stub(require('dns-socket').prototype, 'query', -2);
4+
const dnsSocket = stub(dnsSocket_.prototype, 'query', -2);
5+
6+
export default dnsSocket;

‎mocks/got.js

+5-3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
'use strict';
2-
const stub = require('./stub');
1+
import got_ from 'got';
2+
import stub from './stub.js';
33

4-
module.exports = stub(require('got'), 'get', 0);
4+
const got = stub(got_, 'get', 0);
5+
6+
export default got;

‎mocks/stub.js

+4-5
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
1-
'use strict';
2-
const sinon = require('sinon');
1+
import sinon from 'sinon';
32

4-
module.exports = (objectPath, propertyName, ignoreIndex) => {
3+
export default function stub(objectPath, propertyName, ignoreIndex) {
54
let ignoreRegExp;
65
let ignored = [];
76

@@ -31,6 +30,6 @@ module.exports = (objectPath, propertyName, ignoreIndex) => {
3130
ignoreRegExp = undefined;
3231
ignored = [];
3332
objectPath[propertyName].resetHistory();
34-
}
33+
},
3534
};
36-
};
35+
}

‎package.json

+21-10
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "public-ip",
33
"version": "4.0.4",
4-
"description": "Get your public IP address - very fast!",
4+
"description": "Get your public IP address very fast!",
55
"license": "MIT",
66
"repository": "sindresorhus/public-ip",
77
"funding": "https://github.com/sponsors/sindresorhus",
@@ -10,16 +10,22 @@
1010
"email": "sindresorhus@gmail.com",
1111
"url": "https://sindresorhus.com"
1212
},
13+
"type": "module",
14+
"exports": {
15+
"node": "./index.js",
16+
"default": "./browser.js"
17+
},
1318
"engines": {
14-
"node": ">=8"
19+
"node": "^14.13.1 || >=16.0.0"
1520
},
1621
"scripts": {
17-
"test": "xo && ava test.js && tsd"
22+
"test": "xo && ava && tsd"
1823
},
1924
"files": [
2025
"index.js",
2126
"index.d.ts",
22-
"browser.js"
27+
"browser.js",
28+
"browser.d.ts"
2329
],
2430
"keywords": [
2531
"get",
@@ -36,20 +42,25 @@
3642
],
3743
"dependencies": {
3844
"dns-socket": "^4.2.2",
39-
"got": "^9.6.0",
45+
"got": "^12.0.0",
4046
"is-ip": "^3.1.0"
4147
},
4248
"devDependencies": {
43-
"ava": "^2.2.0",
44-
"sinon": "^7.4.1",
45-
"tsd": "^0.11.0",
46-
"xo": "^0.25.3"
49+
"ava": "^3.15.0",
50+
"sinon": "^12.0.1",
51+
"tsd": "^0.19.0",
52+
"xo": "^0.47.0"
4753
},
48-
"browser": "browser.js",
4954
"xo": {
5055
"envs": [
5156
"node",
5257
"browser"
5358
]
59+
},
60+
"ava": {
61+
"serial": true,
62+
"files": [
63+
"test.js"
64+
]
5465
}
5566
}

‎readme.md

+14-18
Original file line numberDiff line numberDiff line change
@@ -6,22 +6,20 @@ In Node.js, it queries the DNS records of OpenDNS, Google DNS, and HTTPS service
66

77
## Install
88

9-
```
10-
$ npm install public-ip
9+
```sh
10+
npm install public-ip
1111
```
1212

1313
## Usage
1414

1515
```js
16-
const publicIp = require('public-ip');
16+
import publicIp from 'public-ip';
1717

18-
(async () => {
19-
console.log(await publicIp.v4());
20-
//=> '46.5.21.123'
18+
console.log(await publicIp.v4());
19+
//=> '46.5.21.123'
2120

22-
console.log(await publicIp.v6());
23-
//=> 'fe80::200:f8ff:fe21:67cf'
24-
})();
21+
console.log(await publicIp.v6());
22+
//=> 'fe80::200:f8ff:fe21:67cf'
2523
```
2624

2725
## API
@@ -50,15 +48,13 @@ Default: `[]`
5048
Add your own custom HTTPS endpoints to get the public IP from. They will only be used if everything else fails. Any service used as fallback *must* return the IP as a plain string.
5149

5250
```js
53-
const publicIp = require('public-ip');
54-
55-
(async () => {
56-
await publicIp.v6({
57-
fallbackUrls: [
58-
'https://ifconfig.co/ip'
59-
]
60-
});
61-
})();
51+
import publicIp from 'public-ip';
52+
53+
await publicIp.v6({
54+
fallbackUrls: [
55+
'https://ifconfig.co/ip'
56+
]
57+
});
6258
```
6359

6460
##### timeout

‎test-browser.html

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
<!doctype html>
2+
<script type="module" src="test-browser.js"></script>

‎test-browser.js

+8-12
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,9 @@
1-
// Need to test manually in DevTools
2-
// $ browserify test-browser.js | pbcopy
3-
'use strict';
4-
const publicIp = require('./browser');
1+
// Comment out the `is-ip` dependency, launch a local server, and then load the HTMl file.
2+
import publicIp from './browser.js';
53

6-
(async () => {
7-
console.log('IP:', await publicIp.v4());
8-
console.log('IP:', await publicIp.v4({
9-
fallbackUrls: [
10-
'https://ifconfig.me'
11-
]
12-
}));
13-
})();
4+
console.log('IP:', await publicIp.v4());
5+
console.log('IP:', await publicIp.v4({
6+
fallbackUrls: [
7+
'https://ifconfig.me',
8+
],
9+
}));

‎test.js

+6-5
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
import {serial as test} from 'ava';
1+
import process from 'node:process';
2+
import test from 'ava';
23
import isIp from 'is-ip';
3-
import dnsStub from './mocks/dns-socket';
4-
import gotStub from './mocks/got';
5-
import publicIp from '.';
4+
import dnsStub from './mocks/dns-socket.js';
5+
import gotStub from './mocks/got.js';
6+
import publicIp from './index.js';
67

78
test.afterEach.always(() => {
89
dnsStub.restore();
@@ -41,7 +42,7 @@ test('IPv4 HTTPS uses custom URLs', async t => {
4142
gotStub.ignore(/com|org/);
4243
t.true(isIp.v4(await publicIp.v4({onlyHttps: true, fallbackUrls: [
4344
'https://ifconfig.co/ip',
44-
'https://ifconfig.io/ip'
45+
'https://ifconfig.io/ip',
4546
]})));
4647
t.is(gotStub.ignored(), 2);
4748
t.is(dnsStub.called(), 0);

0 commit comments

Comments
 (0)
Please sign in to comment.