feature: vor und nachteile

This commit is contained in:
Hikyu
2026-05-31 13:54:31 +02:00
parent 552a947a1c
commit e3dad17446
23270 changed files with 3024794 additions and 200 deletions
+108
View File
@@ -0,0 +1,108 @@
import {isIPv6, isIPv4} from 'is-ip';
import {createPublicIp, IpNotFoundError} from './core.js';
export class CancelError extends Error {
constructor() {
super('Request was cancelled');
this.name = 'CancelError';
}
get isCanceled() {
return true;
}
}
export {IpNotFoundError} from './core.js';
const defaults = {
timeout: 5000,
};
const urls = {
v4: [
'https://ipv4.icanhazip.com/',
'https://api.ipify.org/',
],
v6: [
'https://ipv6.icanhazip.com/',
'https://api6.ipify.org/',
],
};
const sendXhr = (url, options, version) => {
const xhr = new XMLHttpRequest();
let _reject;
const promise = new Promise((resolve, reject) => {
_reject = reject;
xhr.addEventListener('error', reject, {once: true});
xhr.addEventListener('timeout', reject, {once: true});
xhr.addEventListener('load', () => {
const ip = xhr.responseText.trim();
const method = version === 'v6' ? isIPv6 : isIPv4;
if (!ip || !method(ip)) {
reject();
return;
}
resolve(ip);
}, {once: true});
xhr.open('GET', url);
xhr.timeout = options.timeout;
xhr.send();
});
promise.cancel = () => {
xhr.abort();
_reject(new CancelError());
};
return promise;
};
const queryHttps = (version, options) => {
let request;
const promise = (async function () {
const urls_ = [
...urls[version],
...(options.fallbackUrls ?? []),
];
let lastError;
for (const url of urls_) {
try {
request = sendXhr(url, options, version);
// eslint-disable-next-line no-await-in-loop
const ip = await request;
return ip;
} catch (error) {
lastError = error;
if (error instanceof CancelError) {
throw error;
}
}
}
throw new IpNotFoundError({cause: lastError});
})();
promise.cancel = () => {
request.cancel();
};
return promise;
};
export const publicIp = createPublicIp(publicIpv4, publicIpv6);
export function publicIpv4(options) {
return queryHttps('v4', {...defaults, ...options});
}
export function publicIpv6(options) {
return queryHttps('v6', {...defaults, ...options});
}
+42
View File
@@ -0,0 +1,42 @@
export class IpNotFoundError extends Error {
constructor(options) {
super('Could not get the public IP address', options);
this.name = 'IpNotFoundError';
}
}
export function createPublicIp(publicIpv4, publicIpv6) {
return function publicIp(options) { // eslint-disable-line func-names
const ipv4Promise = publicIpv4(options);
const ipv6Promise = publicIpv6(options);
const promise = (async () => {
try {
const ipv6 = await ipv6Promise;
// eslint-disable-next-line promise/prefer-await-to-then
ipv4Promise.catch(() => {}); // Don't throw when cancelling
ipv4Promise.cancel();
return ipv6;
} catch (ipv6Error) {
if (!(ipv6Error instanceof IpNotFoundError)) {
throw ipv6Error;
}
try {
return await ipv4Promise;
} catch (ipv4Error) {
throw new AggregateError([ipv4Error, ipv6Error]); // eslint-disable-line unicorn/error-message
}
}
})();
promise.cancel = () => {
ipv4Promise.cancel();
ipv6Promise.cancel();
};
return promise;
};
}
+102
View File
@@ -0,0 +1,102 @@
/**
Thrown when the public IP address could not be found.
*/
export class IpNotFoundError extends Error {}
export type Options = {
/**
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.
@default false
*/
readonly onlyHttps?: boolean;
/**
The time in milliseconds until a request is considered timed out.
@default 5000
*/
readonly timeout?: number;
/**
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.
@default []
@example
```
import {publicIpv6} from 'public-ip';
await publicIpv6({
fallbackUrls: [
'https://ifconfig.co/ip'
]
});
```
*/
readonly fallbackUrls?: readonly string[];
};
export type CancelablePromise<T> = Promise<T> & {
cancel(): void;
};
/**
Get your public IP address - very fast!
In Node.js, it queries the DNS records of OpenDNS, Google DNS, and HTTPS services to determine your IP address. In browsers, it uses the excellent [icanhaz](https://github.com/major/icanhaz) and [ipify](https://ipify.org) services through HTTPS.
@returns Your public IPv6 address or as a fallback, your public IPv4 address. A `.cancel()` method is available on the promise, which can be used to cancel the request.
@throws On error or timeout.
@example
```
import {publicIp} from 'public-ip';
console.log(await publicIp()); // Falls back to IPv4
//=> 'fe80::200:f8ff:fe21:67cf'
```
*/
export function publicIp(options?: Options): CancelablePromise<string>;
/**
Get your public IP address - very fast!
In Node.js, it queries the DNS records of OpenDNS, Google DNS, and HTTPS services to determine your IP address. In browsers, it uses the excellent [icanhaz](https://github.com/major/icanhaz) and [ipify](https://ipify.org) services through HTTPS.
@returns Your public IPv4 address. A `.cancel()` method is available on the promise, which can be used to cancel the request.
@throws On error or timeout.
@example
```
import {publicIpv4} from 'public-ip';
console.log(await publicIpv4());
//=> '46.5.21.123'
```
*/
export function publicIpv4(options?: Options): CancelablePromise<string>;
/**
Get your public IP address - very fast!
In Node.js, it queries the DNS records of OpenDNS, Google DNS, and HTTPS services to determine your IP address. In browsers, it uses the excellent [icanhaz](https://github.com/major/icanhaz) and [ipify](https://ipify.org) services through HTTPS.
@returns Your public IPv6 address. A `.cancel()` method is available on the promise, which can be used to cancel the request.
@throws On error or timeout.
@example
```
import {publicIpv6} from 'public-ip';
console.log(await publicIpv6());
//=> 'fe80::200:f8ff:fe21:67cf'
```
*/
export function publicIpv6(options?: Options): CancelablePromise<string>;
export {CancelError} from 'got';
+266
View File
@@ -0,0 +1,266 @@
import {promisify} from 'node:util';
import dgram from 'node:dgram';
import dns from 'dns-socket';
import got, {CancelError} from 'got';
import {isIPv6, isIPv4} from '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.replaceAll('"', ''),
},
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.replaceAll('"', ''),
},
},
];
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 'got';
+9
View File
@@ -0,0 +1,9 @@
MIT License
Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (https://sindresorhus.com)
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:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+75
View File
@@ -0,0 +1,75 @@
{
"name": "public-ip",
"version": "7.0.1",
"description": "Get your public IP address — very fast!",
"license": "MIT",
"repository": "sindresorhus/public-ip",
"funding": "https://github.com/sponsors/sindresorhus",
"author": {
"name": "Sindre Sorhus",
"email": "sindresorhus@gmail.com",
"url": "https://sindresorhus.com"
},
"type": "module",
"exports": {
"types": "./index.d.ts",
"node": "./index.js",
"default": "./browser.js"
},
"sideEffects": false,
"engines": {
"node": ">=18"
},
"scripts": {
"//test": "xo && ava && tsc index.d.ts",
"test": "xo && ava"
},
"files": [
"index.js",
"index.d.ts",
"browser.js",
"core.js"
],
"keywords": [
"get",
"ip",
"ipv4",
"ipv6",
"address",
"external",
"public",
"machine",
"fast",
"opendns",
"dns"
],
"dependencies": {
"dns-socket": "^4.2.2",
"got": "^13.0.0",
"is-ip": "^5.0.1"
},
"devDependencies": {
"@sindresorhus/tsconfig": "^6.0.0",
"ava": "^5.3.1",
"esmock": "^2.6.7",
"sinon": "^18.0.0",
"time-span": "^5.1.0",
"typescript": "^5.5.4",
"xo": "^0.59.2"
},
"xo": {
"envs": [
"node",
"browser"
]
},
"ava": {
"serial": true,
"files": [
"test.js"
],
"nodeArguments": [
"--loader=esmock"
]
}
}
+99
View File
@@ -0,0 +1,99 @@
# public-ip
> Get your public IP address - very fast!
In Node.js, it queries the DNS records of OpenDNS, Google DNS, and HTTPS services to determine your IP address. In browsers, it uses the excellent [icanhaz](https://github.com/major/icanhaz) and [ipify](https://ipify.org) services through HTTPS.
## Install
```sh
npm install public-ip
```
## Usage
```js
import {publicIp, publicIpv4, publicIpv6} from 'public-ip';
console.log(await publicIp()); // Falls back to IPv4
//=> 'fe80::200:f8ff:fe21:67cf'
console.log(await publicIpv6());
//=> 'fe80::200:f8ff:fe21:67cf'
console.log(await publicIpv4());
//=> '46.5.21.123'
```
## API
### publicIp(options?)
Returns a `Promise<string>` with your public IPv4 or IPv6 address. Rejects on error or timeout.
A `.cancel()` method is available on the promise, which can be used to cancel the request.
### publicIpv6(options?)
Returns a `Promise<string>` with your public IPv6 address. Rejects on error or timeout.
A `.cancel()` method is available on the promise, which can be used to cancel the request.
### publicIpv4(options?)
Returns a `Promise<string>` with your public IPv4 address. Rejects on error or timeout.
A `.cancel()` method is available on the promise, which can be used to cancel the request.
#### options
Type: `object`
##### onlyHttps
Type: `boolean`\
Default: `false`
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.
##### fallbackUrls
Type: `string[]`\
Default: `[]`
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.
```js
import {publicIpv6} from 'public-ip';
await publicIpv6({
fallbackUrls: [
'https://ifconfig.co/ip'
]
});
```
##### timeout
Type: `number`\
Default: `5000`
The time in milliseconds until a request is considered timed out.
### IpNotFoundError
Error thrown when the public IP address could not be found.
### CancelError
Error thrown when the operation was canceled.
## Maintainers
- [Sindre Sorhus](https://github.com/sindresorhus)
- [silverwind](https://github.com/silverwind)
## Related
- [public-ip-cli](https://github.com/sindresorhus/public-ip-cli) - CLI for this module
- [internal-ip](https://github.com/sindresorhus/internal-ip) - Get your internal IP address