@prosopo/ipinfo 0.2.14 → 0.2.15
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.
- package/.turbo/turbo-build$colon$cjs.log +34 -0
- package/.turbo/turbo-build$colon$tsc.log +13 -11
- package/.turbo/turbo-build.log +38 -38
- package/CHANGELOG.md +7 -0
- package/dist/IpInfoService.js +79 -86
- package/dist/backends/ipapi.js +99 -95
- package/dist/backends/maxmind.js +137 -140
- package/dist/cjs/IpInfoService.cjs +89 -0
- package/dist/cjs/backends/ipapi.cjs +104 -0
- package/dist/cjs/backends/maxmind.cjs +169 -0
- package/dist/cjs/index.cjs +4 -0
- package/dist/index.js +4 -2
- package/package.json +2 -2
- package/src/IpInfoService.ts +147 -0
- package/src/backends/ipapi.ts +148 -0
- package/src/backends/maxmind.ts +222 -0
- package/src/index.ts +16 -0
- package/src/types.ts +30 -0
- package/tsconfig.cjs.json +24 -0
- package/tsconfig.json +25 -0
- package/tsconfig.tsbuildinfo +1 -0
- package/tsconfig.types.json +9 -0
- package/.turbo/turbo-typecheck.log +0 -4
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
|
|
2
|
+
> @prosopo/ipinfo@0.2.15 build:cjs
|
|
3
|
+
> NODE_ENV=${NODE_ENV:-development}; vite build --config vite.cjs.config.ts --mode $NODE_ENV
|
|
4
|
+
|
|
5
|
+
ViteCommonJSConfig: .
|
|
6
|
+
{
|
|
7
|
+
tsConfigPaths: [
|
|
8
|
+
'/home/runner/work/captcha/captcha/packages/logger/tsconfig.json',
|
|
9
|
+
'/home/runner/work/captcha/captcha/packages/util/tsconfig.json',
|
|
10
|
+
'/home/runner/work/captcha/captcha/packages/types/tsconfig.json',
|
|
11
|
+
'/home/runner/work/captcha/captcha/packages/locale/tsconfig.json',
|
|
12
|
+
'/home/runner/work/captcha/captcha/packages/util-crypto/tsconfig.json'
|
|
13
|
+
]
|
|
14
|
+
}
|
|
15
|
+
{
|
|
16
|
+
externals: [
|
|
17
|
+
'@prosopo/logger',
|
|
18
|
+
'@prosopo/util',
|
|
19
|
+
'@prosopo/types',
|
|
20
|
+
'@prosopo/locale',
|
|
21
|
+
'@prosopo/util-crypto'
|
|
22
|
+
]
|
|
23
|
+
}
|
|
24
|
+
[36mvite v6.4.1 [32mbuilding SSR bundle for production...[36m[39m
|
|
25
|
+
Bundle build started
|
|
26
|
+
transforming...
|
|
27
|
+
Build end
|
|
28
|
+
[32m✓[39m 4 modules transformed.
|
|
29
|
+
rendering chunks...
|
|
30
|
+
[2mdist/cjs/[22m[36mindex.cjs [39m[1m[2m0.19 kB[22m[1m[22m
|
|
31
|
+
[2mdist/cjs/[22m[36mIpInfoService.cjs [39m[1m[2m2.77 kB[22m[1m[22m
|
|
32
|
+
[2mdist/cjs/[22m[36mbackends/ipapi.cjs [39m[1m[2m3.24 kB[22m[1m[22m
|
|
33
|
+
[2mdist/cjs/[22m[36mbackends/maxmind.cjs [39m[1m[2m5.70 kB[22m[1m[22m
|
|
34
|
+
[32m✓ built in 178ms[39m
|
|
@@ -1,27 +1,29 @@
|
|
|
1
1
|
|
|
2
|
-
> @prosopo/ipinfo@0.2.
|
|
2
|
+
> @prosopo/ipinfo@0.2.15 build:tsc
|
|
3
3
|
> tsc --build --verbose
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
10:13:12 AM - Projects in this build:
|
|
6
6
|
* ../../dev/config/tsconfig.json
|
|
7
|
-
* ../locale/tsconfig.json
|
|
8
7
|
* ../util/tsconfig.json
|
|
8
|
+
* ../logger/tsconfig.json
|
|
9
|
+
* ../locale/tsconfig.json
|
|
9
10
|
* ../util-crypto/tsconfig.json
|
|
10
11
|
* ../types/tsconfig.json
|
|
11
|
-
* ../common/tsconfig.json
|
|
12
12
|
* tsconfig.json
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
10:13:12 AM - Project '../../dev/config/tsconfig.json' is up to date because newest input '../../dev/config/src/webpack/webpack.config.ts' is older than output '../../dev/config/tsconfig.tsbuildinfo'
|
|
15
|
+
|
|
16
|
+
10:13:12 AM - Project '../util/tsconfig.json' is up to date because newest input '../util/src/url.ts' is older than output '../util/tsconfig.tsbuildinfo'
|
|
15
17
|
|
|
16
|
-
|
|
18
|
+
10:13:12 AM - Project '../logger/tsconfig.json' is up to date because newest input '../logger/src/index.ts' is older than output '../logger/tsconfig.tsbuildinfo'
|
|
17
19
|
|
|
18
|
-
|
|
20
|
+
10:13:12 AM - Project '../locale/tsconfig.json' is up to date because newest input '../locale/src/util.ts' is older than output '../locale/tsconfig.tsbuildinfo'
|
|
19
21
|
|
|
20
|
-
|
|
22
|
+
10:13:12 AM - Project '../util-crypto/tsconfig.json' is up to date because newest input '../util-crypto/src/types.ts' is older than output '../util-crypto/tsconfig.tsbuildinfo'
|
|
21
23
|
|
|
22
|
-
|
|
24
|
+
10:13:12 AM - Project '../types/tsconfig.json' is up to date because newest input '../types/src/procaptcha/api.ts' is older than output '../types/tsconfig.tsbuildinfo'
|
|
23
25
|
|
|
24
|
-
|
|
26
|
+
10:13:12 AM - Project 'tsconfig.json' is out of date because output file 'tsconfig.tsbuildinfo' does not exist
|
|
25
27
|
|
|
26
|
-
|
|
28
|
+
10:13:12 AM - Building project '/home/runner/work/captcha/captcha/packages/ipinfo/tsconfig.json'...
|
|
27
29
|
|
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,38 +1,38 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
>
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
>
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
[
|
|
33
|
-
|
|
34
|
-
[2mdist/[22m[
|
|
35
|
-
[2mdist/[22m[
|
|
36
|
-
[2mdist/[22m[36mbackends/
|
|
37
|
-
[
|
|
38
|
-
|
|
1
|
+
|
|
2
|
+
> @prosopo/ipinfo@0.2.15 build
|
|
3
|
+
> npm run build:cross-env -- --mode ${NODE_ENV:-development}
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
> @prosopo/ipinfo@0.2.15 build:cross-env
|
|
7
|
+
> vite build --config vite.esm.config.ts --mode production
|
|
8
|
+
|
|
9
|
+
ViteEsmConfig: .
|
|
10
|
+
{
|
|
11
|
+
tsConfigPaths: [
|
|
12
|
+
'/home/runner/work/captcha/captcha/packages/logger/tsconfig.json',
|
|
13
|
+
'/home/runner/work/captcha/captcha/packages/util/tsconfig.json',
|
|
14
|
+
'/home/runner/work/captcha/captcha/packages/types/tsconfig.json',
|
|
15
|
+
'/home/runner/work/captcha/captcha/packages/locale/tsconfig.json',
|
|
16
|
+
'/home/runner/work/captcha/captcha/packages/util-crypto/tsconfig.json'
|
|
17
|
+
]
|
|
18
|
+
}
|
|
19
|
+
{
|
|
20
|
+
externals: [
|
|
21
|
+
'@prosopo/logger',
|
|
22
|
+
'@prosopo/util',
|
|
23
|
+
'@prosopo/types',
|
|
24
|
+
'@prosopo/locale',
|
|
25
|
+
'@prosopo/util-crypto'
|
|
26
|
+
]
|
|
27
|
+
}
|
|
28
|
+
[36mvite v6.4.1 [32mbuilding SSR bundle for production...[36m[39m
|
|
29
|
+
Bundle build started
|
|
30
|
+
transforming...
|
|
31
|
+
Build end
|
|
32
|
+
[32m✓[39m 4 modules transformed.
|
|
33
|
+
rendering chunks...
|
|
34
|
+
[2mdist/[22m[36mindex.js [39m[1m[2m0.08 kB[22m[1m[22m
|
|
35
|
+
[2mdist/[22m[36mIpInfoService.js [39m[1m[2m2.67 kB[22m[1m[22m
|
|
36
|
+
[2mdist/[22m[36mbackends/ipapi.js [39m[1m[2m3.14 kB[22m[1m[22m
|
|
37
|
+
[2mdist/[22m[36mbackends/maxmind.js [39m[1m[2m4.44 kB[22m[1m[22m
|
|
38
|
+
[32m✓ built in 228ms[39m
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# @prosopo/ipinfo
|
|
2
2
|
|
|
3
|
+
## 0.2.15
|
|
4
|
+
### Patch Changes
|
|
5
|
+
|
|
6
|
+
- 9b18b31: Bump @prosopo/ipinfo so npm accepts the trusted-publishing publish. Version 0.2.14 was previously burned on the registry; this republishes at 0.2.15.
|
|
7
|
+
- Updated dependencies [b03dad1]
|
|
8
|
+
- @prosopo/types@4.3.1
|
|
9
|
+
|
|
3
10
|
## 0.2.14
|
|
4
11
|
### Patch Changes
|
|
5
12
|
|
package/dist/IpInfoService.js
CHANGED
|
@@ -1,96 +1,89 @@
|
|
|
1
1
|
import { IpapiBackend } from "./backends/ipapi.js";
|
|
2
2
|
import { MaxMindBackend } from "./backends/maxmind.js";
|
|
3
3
|
function isNonRoutable(ip) {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
}
|
|
17
|
-
if (normalized === "::1" || normalized === "::")
|
|
18
|
-
return true;
|
|
19
|
-
if (/^f[cd]/i.test(normalized))
|
|
20
|
-
return true;
|
|
21
|
-
if (/^fe[89ab]/i.test(normalized))
|
|
22
|
-
return true;
|
|
23
|
-
return false;
|
|
4
|
+
const normalized = ip.replace(/^::ffff:/i, "");
|
|
5
|
+
if (normalized.startsWith("127.") || normalized.startsWith("10.") || normalized.startsWith("192.168.") || normalized.startsWith("169.254.") || normalized === "0.0.0.0") {
|
|
6
|
+
return true;
|
|
7
|
+
}
|
|
8
|
+
if (normalized.startsWith("172.")) {
|
|
9
|
+
const second = Number.parseInt(normalized.split(".")[1] ?? "", 10);
|
|
10
|
+
if (second >= 16 && second <= 31) return true;
|
|
11
|
+
}
|
|
12
|
+
if (normalized === "::1" || normalized === "::") return true;
|
|
13
|
+
if (/^f[cd]/i.test(normalized)) return true;
|
|
14
|
+
if (/^fe[89ab]/i.test(normalized)) return true;
|
|
15
|
+
return false;
|
|
24
16
|
}
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
}
|
|
37
|
-
if (config.ipapiUrl) {
|
|
38
|
-
this.ipapiBackend = new IpapiBackend({
|
|
39
|
-
baseUrl: config.ipapiUrl,
|
|
40
|
-
apiKey: config.ipapiKey,
|
|
41
|
-
logger: config.logger,
|
|
42
|
-
});
|
|
43
|
-
}
|
|
17
|
+
class IpInfoService {
|
|
18
|
+
constructor(config) {
|
|
19
|
+
this.maxmindBackend = null;
|
|
20
|
+
this.ipapiBackend = null;
|
|
21
|
+
this.config = config;
|
|
22
|
+
if (config.maxmindCityDbPath || config.maxmindAsnDbPath) {
|
|
23
|
+
this.maxmindBackend = new MaxMindBackend({
|
|
24
|
+
cityDbPath: config.maxmindCityDbPath,
|
|
25
|
+
asnDbPath: config.maxmindAsnDbPath,
|
|
26
|
+
logger: config.logger
|
|
27
|
+
});
|
|
44
28
|
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
data: {
|
|
52
|
-
maxmindAvailable: this.maxmindBackend?.isAvailable() ?? false,
|
|
53
|
-
ipapiAvailable: this.ipapiBackend?.isAvailable() ?? false,
|
|
54
|
-
},
|
|
55
|
-
}));
|
|
29
|
+
if (config.ipapiUrl) {
|
|
30
|
+
this.ipapiBackend = new IpapiBackend({
|
|
31
|
+
baseUrl: config.ipapiUrl,
|
|
32
|
+
apiKey: config.ipapiKey,
|
|
33
|
+
logger: config.logger
|
|
34
|
+
});
|
|
56
35
|
}
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
36
|
+
}
|
|
37
|
+
async initialize() {
|
|
38
|
+
if (this.maxmindBackend) {
|
|
39
|
+
await this.maxmindBackend.initialize();
|
|
60
40
|
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
41
|
+
this.config.logger?.info(() => ({
|
|
42
|
+
msg: "IpInfoService initialized",
|
|
43
|
+
data: {
|
|
44
|
+
maxmindAvailable: this.maxmindBackend?.isAvailable() ?? false,
|
|
45
|
+
ipapiAvailable: this.ipapiBackend?.isAvailable() ?? false
|
|
46
|
+
}
|
|
47
|
+
}));
|
|
48
|
+
}
|
|
49
|
+
isAvailable() {
|
|
50
|
+
return (this.maxmindBackend?.isAvailable() ?? false) || (this.ipapiBackend?.isAvailable() ?? false);
|
|
51
|
+
}
|
|
52
|
+
async lookup(ip) {
|
|
53
|
+
if (isNonRoutable(ip)) {
|
|
54
|
+
return {
|
|
55
|
+
isValid: false,
|
|
56
|
+
error: "Non-routable IP address",
|
|
57
|
+
ip
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
if (this.ipapiBackend?.isAvailable()) {
|
|
61
|
+
const result = await this.ipapiBackend.lookup(ip);
|
|
62
|
+
if (result.isValid) {
|
|
63
|
+
return result;
|
|
64
|
+
}
|
|
65
|
+
this.config.logger?.debug(() => ({
|
|
66
|
+
msg: "ipapi.is lookup failed, falling back to MaxMind",
|
|
67
|
+
data: {
|
|
68
|
+
ip,
|
|
69
|
+
error: "error" in result ? result.error : "unknown"
|
|
88
70
|
}
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
71
|
+
}));
|
|
72
|
+
if (this.maxmindBackend?.isAvailable()) {
|
|
73
|
+
return this.maxmindBackend.lookup(ip);
|
|
74
|
+
}
|
|
75
|
+
return result;
|
|
76
|
+
}
|
|
77
|
+
if (this.maxmindBackend?.isAvailable()) {
|
|
78
|
+
return this.maxmindBackend.lookup(ip);
|
|
94
79
|
}
|
|
80
|
+
return {
|
|
81
|
+
isValid: false,
|
|
82
|
+
error: "No IP info backend available",
|
|
83
|
+
ip
|
|
84
|
+
};
|
|
85
|
+
}
|
|
95
86
|
}
|
|
96
|
-
|
|
87
|
+
export {
|
|
88
|
+
IpInfoService
|
|
89
|
+
};
|
package/dist/backends/ipapi.js
CHANGED
|
@@ -1,100 +1,104 @@
|
|
|
1
1
|
const TIMEOUT_MS = 700;
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
}
|
|
50
|
-
const result = {
|
|
51
|
-
ip: data.ip,
|
|
52
|
-
isValid: true,
|
|
53
|
-
isVPN: data.is_vpn,
|
|
54
|
-
isTor: data.is_tor,
|
|
55
|
-
isProxy: data.is_proxy,
|
|
56
|
-
isDatacenter: data.is_datacenter,
|
|
57
|
-
isAbuser: data.is_abuser,
|
|
58
|
-
isMobile: data.is_mobile,
|
|
59
|
-
isSatellite: data.is_satellite,
|
|
60
|
-
isCrawler: data.is_crawler,
|
|
61
|
-
providerName: data.company?.name || data.datacenter?.datacenter,
|
|
62
|
-
providerType: data.company?.type || data.asn?.type,
|
|
63
|
-
asnNumber: data.asn?.asn,
|
|
64
|
-
asnOrganization: data.asn?.org,
|
|
65
|
-
country: data.location?.country,
|
|
66
|
-
countryCode: data.location?.country_code,
|
|
67
|
-
region: data.location?.state,
|
|
68
|
-
city: data.location?.city,
|
|
69
|
-
latitude: data.location?.latitude,
|
|
70
|
-
longitude: data.location?.longitude,
|
|
71
|
-
timezone: data.location?.timezone,
|
|
72
|
-
vpnService: data.vpn?.service,
|
|
73
|
-
vpnType: data.vpn?.type,
|
|
74
|
-
abuserScore: Number.parseFloat(data.asn?.abuser_score.split(" ")[0] || "0"),
|
|
75
|
-
companyAbuserScore: Number.parseFloat(data.company?.abuser_score.split(" ")[0] || "0"),
|
|
76
|
-
};
|
|
77
|
-
return result;
|
|
78
|
-
}
|
|
79
|
-
catch (fetchError) {
|
|
80
|
-
clearTimeout(timeoutId);
|
|
81
|
-
if (fetchError instanceof Error && fetchError.name === "AbortError") {
|
|
82
|
-
return {
|
|
83
|
-
isValid: false,
|
|
84
|
-
error: `Request timed out after ${TIMEOUT_MS}ms`,
|
|
85
|
-
ip,
|
|
86
|
-
};
|
|
87
|
-
}
|
|
88
|
-
throw fetchError;
|
|
89
|
-
}
|
|
2
|
+
class IpapiBackend {
|
|
3
|
+
constructor(config) {
|
|
4
|
+
this.config = config;
|
|
5
|
+
}
|
|
6
|
+
isAvailable() {
|
|
7
|
+
return Boolean(this.config.baseUrl);
|
|
8
|
+
}
|
|
9
|
+
async lookup(ip) {
|
|
10
|
+
try {
|
|
11
|
+
if (!ip || typeof ip !== "string") {
|
|
12
|
+
return {
|
|
13
|
+
isValid: false,
|
|
14
|
+
error: "Invalid IP address provided",
|
|
15
|
+
ip: ip || "undefined"
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
const body = { q: ip };
|
|
19
|
+
if (this.config.apiKey) {
|
|
20
|
+
body.key = this.config.apiKey;
|
|
21
|
+
}
|
|
22
|
+
const controller = new AbortController();
|
|
23
|
+
const timeoutId = setTimeout(() => controller.abort(), TIMEOUT_MS);
|
|
24
|
+
try {
|
|
25
|
+
const response = await fetch(this.config.baseUrl, {
|
|
26
|
+
method: "POST",
|
|
27
|
+
headers: {
|
|
28
|
+
"Content-Type": "application/json",
|
|
29
|
+
Accept: "application/json"
|
|
30
|
+
},
|
|
31
|
+
body: JSON.stringify(body),
|
|
32
|
+
signal: controller.signal
|
|
33
|
+
});
|
|
34
|
+
clearTimeout(timeoutId);
|
|
35
|
+
if (!response.ok) {
|
|
36
|
+
return {
|
|
37
|
+
isValid: false,
|
|
38
|
+
error: `API request failed with status ${response.status}: ${response.statusText}`,
|
|
39
|
+
ip
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
const data = await response.json();
|
|
43
|
+
if (data.is_bogon) {
|
|
44
|
+
return {
|
|
45
|
+
isValid: false,
|
|
46
|
+
error: "IP address is bogon (non-routable)",
|
|
47
|
+
ip
|
|
48
|
+
};
|
|
90
49
|
}
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
50
|
+
const result = {
|
|
51
|
+
ip: data.ip,
|
|
52
|
+
isValid: true,
|
|
53
|
+
isVPN: data.is_vpn,
|
|
54
|
+
isTor: data.is_tor,
|
|
55
|
+
isProxy: data.is_proxy,
|
|
56
|
+
isDatacenter: data.is_datacenter,
|
|
57
|
+
isAbuser: data.is_abuser,
|
|
58
|
+
isMobile: data.is_mobile,
|
|
59
|
+
isSatellite: data.is_satellite,
|
|
60
|
+
isCrawler: data.is_crawler,
|
|
61
|
+
providerName: data.company?.name || data.datacenter?.datacenter,
|
|
62
|
+
providerType: data.company?.type || data.asn?.type,
|
|
63
|
+
asnNumber: data.asn?.asn,
|
|
64
|
+
asnOrganization: data.asn?.org,
|
|
65
|
+
country: data.location?.country,
|
|
66
|
+
countryCode: data.location?.country_code,
|
|
67
|
+
region: data.location?.state,
|
|
68
|
+
city: data.location?.city,
|
|
69
|
+
latitude: data.location?.latitude,
|
|
70
|
+
longitude: data.location?.longitude,
|
|
71
|
+
timezone: data.location?.timezone,
|
|
72
|
+
vpnService: data.vpn?.service,
|
|
73
|
+
vpnType: data.vpn?.type,
|
|
74
|
+
abuserScore: Number.parseFloat(
|
|
75
|
+
data.asn?.abuser_score.split(" ")[0] || "0"
|
|
76
|
+
),
|
|
77
|
+
companyAbuserScore: Number.parseFloat(
|
|
78
|
+
data.company?.abuser_score.split(" ")[0] || "0"
|
|
79
|
+
)
|
|
80
|
+
};
|
|
81
|
+
return result;
|
|
82
|
+
} catch (fetchError) {
|
|
83
|
+
clearTimeout(timeoutId);
|
|
84
|
+
if (fetchError instanceof Error && fetchError.name === "AbortError") {
|
|
85
|
+
return {
|
|
86
|
+
isValid: false,
|
|
87
|
+
error: `Request timed out after ${TIMEOUT_MS}ms`,
|
|
88
|
+
ip
|
|
89
|
+
};
|
|
97
90
|
}
|
|
91
|
+
throw fetchError;
|
|
92
|
+
}
|
|
93
|
+
} catch (error) {
|
|
94
|
+
return {
|
|
95
|
+
isValid: false,
|
|
96
|
+
error: `Network or parsing error: ${error instanceof Error ? error.message : String(error)}`,
|
|
97
|
+
ip
|
|
98
|
+
};
|
|
98
99
|
}
|
|
100
|
+
}
|
|
99
101
|
}
|
|
100
|
-
|
|
102
|
+
export {
|
|
103
|
+
IpapiBackend
|
|
104
|
+
};
|