@esm2cjs/public-ip
Version:
Get your public IP address — very fast!. This is a fork of sindresorhus/public-ip, but with CommonJS support.
268 lines (221 loc) • 5.09 kB
JavaScript
import {promisify} from 'node:util';
import dgram from 'node:dgram';
import dns from 'dns-socket';
import got, {CancelError} from '@esm2cjs/got';
import {isIPv6, isIPv4} from '@esm2cjs/is-ip';
import {createPublicIp, IpNotFoundError} from './core.js';
export {IpNotFoundError} from './core.js';
const defaults = {
timeout: 5000,
onlyHttps: false,
};
const dnsServers = [
{
v4: {
servers: [
'208.67.222.222',
'208.67.220.220',
'208.67.222.220',
'208.67.220.222',
],
name: 'myip.opendns.com',
type: 'A',
},
v6: {
servers: [
'2620:0:ccc::2',
'2620:0:ccd::2',
],
name: 'myip.opendns.com',
type: 'AAAA',
},
},
{
v4: {
servers: [
'216.239.32.10',
'216.239.34.10',
'216.239.36.10',
'216.239.38.10',
],
name: 'o-o.myaddr.l.google.com',
type: 'TXT',
transform: ip => ip.replace(/"/g, ''),
},
v6: {
servers: [
'2001:4860:4802:32::a',
'2001:4860:4802:34::a',
'2001:4860:4802:36::a',
'2001:4860:4802:38::a',
],
name: 'o-o.myaddr.l.google.com',
type: 'TXT',
transform: ip => ip.replace(/"/g, ''),
},
},
];
const type = {
v4: {
dnsServers: dnsServers.map(({v4: {servers, ...question}}) => ({
servers, question,
})),
httpsUrls: [
'https://icanhazip.com/',
'https://api.ipify.org/',
],
},
v6: {
dnsServers: dnsServers.map(({v6: {servers, ...question}}) => ({
servers, question,
})),
httpsUrls: [
'https://icanhazip.com/',
'https://api6.ipify.org/',
],
},
};
const queryDns = (version, options) => {
const data = type[version];
const socket = dns({
retries: 0,
maxQueries: 1,
socket: dgram.createSocket(version === 'v6' ? 'udp6' : 'udp4'),
timeout: options.timeout,
});
const socketQuery = promisify(socket.query.bind(socket));
const promise = (async () => {
let lastError;
for (const dnsServerInfo of data.dnsServers) {
const {servers, question} = dnsServerInfo;
for (const server of servers) {
if (socket.destroyed) {
return;
}
try {
const {name, type, transform} = question;
// eslint-disable-next-line no-await-in-loop
const dnsResponse = await socketQuery({questions: [{name, type}]}, 53, server);
const {
answers: {
0: {
data,
},
},
} = dnsResponse;
const response = (typeof data === 'string' ? data : data.toString()).trim();
const ip = transform ? transform(response) : response;
const method = version === 'v6' ? isIPv6 : isIPv4;
if (ip && method(ip)) {
socket.destroy();
return ip;
}
} catch (error) {
lastError = error;
}
}
}
socket.destroy();
throw new IpNotFoundError({cause: lastError});
})();
promise.cancel = () => {
socket.destroy();
};
return promise;
};
const queryHttps = (version, options) => {
let cancel;
const promise = (async () => {
try {
const requestOptions = {
dnsLookupIpVersion: version === 'v6' ? 6 : 4,
retry: {
limit: 0,
},
timeout: {
request: options.timeout,
},
};
const urls = [
...type[version].httpsUrls,
...(options.fallbackUrls ?? []),
];
let lastError;
for (const url of urls) {
try {
// Note: We use `.get` to allow for mocking.
const gotPromise = got.get(url, requestOptions);
cancel = gotPromise.cancel;
// eslint-disable-next-line no-await-in-loop
const response = await gotPromise;
const ip = (response.body || '').trim();
const method = version === 'v6' ? isIPv6 : isIPv4;
if (ip && method(ip)) {
return ip;
}
} catch (error) {
lastError = error;
if (error instanceof CancelError) {
throw error;
}
}
}
throw new IpNotFoundError({cause: lastError});
} catch (error) {
// Don't throw a cancellation error for consistency with DNS
if (!(error instanceof CancelError)) {
throw error;
}
}
})();
promise.cancel = function () {
return cancel.apply(this);
};
return promise;
};
const queryAll = (version, options) => {
let cancel;
const promise = (async () => {
let response;
const dnsPromise = queryDns(version, options);
cancel = dnsPromise.cancel;
try {
response = await dnsPromise;
} catch {
const httpsPromise = queryHttps(version, options);
cancel = httpsPromise.cancel;
response = await httpsPromise;
}
return response;
})();
promise.cancel = cancel;
return promise;
};
export const publicIp = createPublicIp(publicIpv4, publicIpv6);
export function publicIpv4(options) {
options = {
...defaults,
...options,
};
if (!options.onlyHttps) {
return queryAll('v4', options);
}
if (options.onlyHttps) {
return queryHttps('v4', options);
}
return queryDns('v4', options);
}
export function publicIpv6(options) {
options = {
...defaults,
...options,
};
if (!options.onlyHttps) {
return queryAll('v6', options);
}
if (options.onlyHttps) {
return queryHttps('v6', options);
}
return queryDns('v6', options);
}
export {CancelError} from '@esm2cjs/got';