@prosopo/ipinfo 0.2.40 → 0.3.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.
- package/.turbo/turbo-build$colon$cjs.log +12 -10
- package/.turbo/turbo-build$colon$tsc.log +10 -10
- package/.turbo/turbo-build.log +14 -11
- package/CHANGELOG.md +19 -0
- package/dist/IpInfoService.d.ts +8 -1
- package/dist/IpInfoService.d.ts.map +1 -1
- package/dist/IpInfoService.js +77 -84
- package/dist/IpInfoService.js.map +1 -1
- package/dist/_virtual/_rolldown/runtime.js +3 -0
- package/dist/backends/ipapi.d.ts +6 -0
- package/dist/backends/ipapi.d.ts.map +1 -1
- package/dist/backends/ipapi.js +117 -104
- package/dist/backends/ipapi.js.map +1 -1
- package/dist/backends/maxmind.d.ts +4 -0
- package/dist/backends/maxmind.d.ts.map +1 -1
- package/dist/backends/maxmind.js +126 -145
- package/dist/backends/maxmind.js.map +1 -1
- package/dist/cjs/IpInfoService.cjs +80 -86
- package/dist/cjs/backends/ipapi.cjs +118 -104
- package/dist/cjs/backends/maxmind.cjs +124 -165
- package/dist/cjs/index.cjs +7 -3
- package/dist/index.d.ts +6 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -4
- package/dist/index.js.map +1 -1
- package/dist/tests/ipInfoService.unit.test.d.ts +2 -0
- package/dist/tests/ipInfoService.unit.test.d.ts.map +1 -0
- package/dist/tests/ipInfoService.unit.test.js +244 -0
- package/dist/tests/ipInfoService.unit.test.js.map +1 -0
- package/dist/tests/ipapi.unit.test.d.ts +2 -0
- package/dist/tests/ipapi.unit.test.d.ts.map +1 -0
- package/dist/tests/ipapi.unit.test.js +337 -0
- package/dist/tests/ipapi.unit.test.js.map +1 -0
- package/dist/tests/ipinfo.test-d.d.ts +2 -0
- package/dist/tests/ipinfo.test-d.d.ts.map +1 -0
- package/dist/tests/ipinfo.test-d.js +97 -0
- package/dist/tests/ipinfo.test-d.js.map +1 -0
- package/dist/tests/maxmind.unit.test.d.ts +2 -0
- package/dist/tests/maxmind.unit.test.d.ts.map +1 -0
- package/dist/tests/maxmind.unit.test.js +391 -0
- package/dist/tests/maxmind.unit.test.js.map +1 -0
- package/package.json +10 -8
- package/src/IpInfoService.ts +19 -2
- package/src/backends/ipapi.ts +57 -10
- package/src/backends/maxmind.ts +22 -4
- package/src/index.ts +9 -1
- package/src/tests/ipInfoService.unit.test.ts +422 -0
- package/src/tests/ipapi.unit.test.ts +509 -0
- package/src/tests/ipinfo.test-d.ts +189 -0
- package/src/tests/maxmind.unit.test.ts +562 -0
- package/tsconfig.tsbuildinfo +1 -1
- package/vite.test.config.ts +18 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
> @prosopo/ipinfo@0.
|
|
2
|
+
> @prosopo/ipinfo@0.3.0 build:cjs
|
|
3
3
|
> NODE_ENV=${NODE_ENV:-development}; vite build --config vite.cjs.config.ts --mode $NODE_ENV
|
|
4
4
|
|
|
5
5
|
ViteCommonJSConfig: .
|
|
@@ -12,6 +12,7 @@ ViteCommonJSConfig: .
|
|
|
12
12
|
'/home/runner/work/captcha/captcha/packages/util-crypto/tsconfig.json'
|
|
13
13
|
]
|
|
14
14
|
}
|
|
15
|
+
[33mThe plugin "vite-tsconfig-paths" is detected. Vite now supports tsconfig paths resolution natively via the [1mresolve.tsconfigPaths[22m option. You can remove the plugin and set [1mresolve.tsconfigPaths: true[22m in your Vite config instead.[39m
|
|
15
16
|
{
|
|
16
17
|
externals: [
|
|
17
18
|
'@prosopo/logger',
|
|
@@ -21,14 +22,15 @@ ViteCommonJSConfig: .
|
|
|
21
22
|
'@prosopo/util-crypto'
|
|
22
23
|
]
|
|
23
24
|
}
|
|
24
|
-
[36mvite
|
|
25
|
+
[36mvite v8.1.5 [32mbuilding ssr environment for production...[36m[39m
|
|
25
26
|
Bundle build started
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
[32m✓[39m 4 modules transformed.
|
|
27
|
+
[2K
|
|
28
|
+
✓ 5 modules transformed.
|
|
29
29
|
rendering chunks...
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
30
|
+
computing gzip size...
|
|
31
|
+
dist/cjs/index.cjs 0.47 kB │ gzip: 0.21 kB
|
|
32
|
+
dist/cjs/IpInfoService.cjs 2.80 kB │ gzip: 0.98 kB
|
|
33
|
+
dist/cjs/backends/maxmind.cjs 3.82 kB │ gzip: 1.14 kB
|
|
34
|
+
dist/cjs/backends/ipapi.cjs 4.11 kB │ gzip: 1.75 kB
|
|
35
|
+
|
|
36
|
+
[32m✓ built in 63ms[39m
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
|
|
2
|
-
> @prosopo/ipinfo@0.
|
|
2
|
+
> @prosopo/ipinfo@0.3.0 build:tsc
|
|
3
3
|
> tsc --build --verbose
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
8:59:04 PM - Projects in this build:
|
|
6
6
|
* ../../dev/config/tsconfig.json
|
|
7
7
|
* ../util/tsconfig.json
|
|
8
8
|
* ../logger/tsconfig.json
|
|
@@ -11,19 +11,19 @@
|
|
|
11
11
|
* ../types/tsconfig.json
|
|
12
12
|
* tsconfig.json
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
8:59:04 PM - 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
15
|
|
|
16
|
-
|
|
16
|
+
8:59:04 PM - Project '../util/tsconfig.json' is up to date because newest input '../util/src/url.ts' is older than output '../util/tsconfig.tsbuildinfo'
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
8:59:04 PM - Project '../logger/tsconfig.json' is up to date because newest input '../logger/src/index.ts' is older than output '../logger/tsconfig.tsbuildinfo'
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
8:59:04 PM - Project '../locale/tsconfig.json' is up to date because newest input '../locale/src/translationKey.ts' is older than output '../locale/tsconfig.tsbuildinfo'
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
8:59:04 PM - 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'
|
|
23
23
|
|
|
24
|
-
|
|
24
|
+
8:59:04 PM - Project '../types/tsconfig.json' is up to date because newest input '../types/src/provider/api.ts' is older than output '../types/tsconfig.tsbuildinfo'
|
|
25
25
|
|
|
26
|
-
|
|
26
|
+
8:59:04 PM - Project 'tsconfig.json' is out of date because output file 'tsconfig.tsbuildinfo' does not exist
|
|
27
27
|
|
|
28
|
-
|
|
28
|
+
8:59:04 PM - Building project '/home/runner/work/captcha/captcha/packages/ipinfo/tsconfig.json'...
|
|
29
29
|
|
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
|
|
2
|
-
> @prosopo/ipinfo@0.
|
|
2
|
+
> @prosopo/ipinfo@0.3.0 build
|
|
3
3
|
> npm run build:cross-env -- --mode ${NODE_ENV:-development}
|
|
4
4
|
|
|
5
5
|
|
|
6
|
-
> @prosopo/ipinfo@0.
|
|
6
|
+
> @prosopo/ipinfo@0.3.0 build:cross-env
|
|
7
7
|
> vite build --config vite.esm.config.ts --mode production
|
|
8
8
|
|
|
9
9
|
ViteEsmConfig: .
|
|
@@ -25,14 +25,17 @@ ViteEsmConfig: .
|
|
|
25
25
|
'@prosopo/util-crypto'
|
|
26
26
|
]
|
|
27
27
|
}
|
|
28
|
-
[
|
|
28
|
+
[33mThe plugin "vite-tsconfig-paths" is detected. Vite now supports tsconfig paths resolution natively via the [1mresolve.tsconfigPaths[22m option. You can remove the plugin and set [1mresolve.tsconfigPaths: true[22m in your Vite config instead.[39m
|
|
29
|
+
[36mvite v8.1.5 [32mbuilding ssr environment for production...[36m[39m
|
|
29
30
|
Bundle build started
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
[32m✓[39m 4 modules transformed.
|
|
31
|
+
[2K
|
|
32
|
+
✓ 5 modules transformed.
|
|
33
33
|
rendering chunks...
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
34
|
+
computing gzip size...
|
|
35
|
+
dist/_virtual/_rolldown/runtime.js 0.04 kB │ gzip: 0.06 kB
|
|
36
|
+
dist/index.js 0.28 kB │ gzip: 0.15 kB
|
|
37
|
+
dist/IpInfoService.js 2.73 kB │ gzip: 0.97 kB
|
|
38
|
+
dist/backends/maxmind.js 3.80 kB │ gzip: 1.14 kB
|
|
39
|
+
dist/backends/ipapi.js 4.07 kB │ gzip: 1.75 kB
|
|
40
|
+
|
|
41
|
+
[32m✓ built in 80ms[39m
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,24 @@
|
|
|
1
1
|
# @prosopo/ipinfo
|
|
2
2
|
|
|
3
|
+
## 0.3.0
|
|
4
|
+
### Minor Changes
|
|
5
|
+
|
|
6
|
+
- 270a8d8: Add unit and type tests for `@prosopo/ipinfo`, with the injection seams needed to write them.
|
|
7
|
+
|
|
8
|
+
- `IpapiBackend` accepts an injected `fetch` and a configurable `timeoutMs`; `MaxMindBackend` accepts an injected `openReader`; `IpInfoService` accepts injected backends.
|
|
9
|
+
- `parseAbuserScore` no longer throws when the upstream omits `abuser_score`. The field is declared required by the response type but is not validated on the wire, and a missing value used to turn an otherwise successful lookup into a generic "Network or parsing error".
|
|
10
|
+
- `IPInfoResult.isValid` is now the literal `true` rather than `boolean`, making `IPInfoResponse` an actually discriminated union. Previously `if (!res.isValid)` narrowed to nothing, so `res.error` did not compile and consumers had to cast.
|
|
11
|
+
- The backends, their config types and the injection seams are re-exported from the package entrypoint, along with `isNonRoutable`.
|
|
12
|
+
|
|
13
|
+
### Patch Changes
|
|
14
|
+
|
|
15
|
+
- e14fce6: chore(deps): bump vite to 6.4.3 and mongoose to 8.24.1, and adjust types for the mongoose 8.24 Document/ObjectId changes
|
|
16
|
+
- Updated dependencies [103318c]
|
|
17
|
+
- Updated dependencies [270a8d8]
|
|
18
|
+
- Updated dependencies [e14fce6]
|
|
19
|
+
- @prosopo/types@4.10.0
|
|
20
|
+
- @prosopo/logger@2.0.4
|
|
21
|
+
|
|
3
22
|
## 0.2.40
|
|
4
23
|
### Patch Changes
|
|
5
24
|
|
package/dist/IpInfoService.d.ts
CHANGED
|
@@ -1,10 +1,17 @@
|
|
|
1
1
|
import type { IPInfoResponse } from "@prosopo/types";
|
|
2
|
+
import { IpapiBackend } from "./backends/ipapi.js";
|
|
3
|
+
import { MaxMindBackend } from "./backends/maxmind.js";
|
|
2
4
|
import type { IIpInfoService, IpInfoServiceConfig } from "./types.js";
|
|
5
|
+
export declare function isNonRoutable(ip: string): boolean;
|
|
6
|
+
export interface IpInfoBackends {
|
|
7
|
+
maxmind?: MaxMindBackend | null;
|
|
8
|
+
ipapi?: IpapiBackend | null;
|
|
9
|
+
}
|
|
3
10
|
export declare class IpInfoService implements IIpInfoService {
|
|
4
11
|
private maxmindBackend;
|
|
5
12
|
private ipapiBackend;
|
|
6
13
|
private config;
|
|
7
|
-
constructor(config: IpInfoServiceConfig);
|
|
14
|
+
constructor(config: IpInfoServiceConfig, backends?: IpInfoBackends);
|
|
8
15
|
initialize(): Promise<void>;
|
|
9
16
|
isAvailable(): boolean;
|
|
10
17
|
lookup(ip: string): Promise<IPInfoResponse>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"IpInfoService.d.ts","sourceRoot":"","sources":["../src/IpInfoService.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"IpInfoService.d.ts","sourceRoot":"","sources":["../src/IpInfoService.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AACrD,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,KAAK,EAAE,cAAc,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAMtE,wBAAgB,aAAa,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CA+BjD;AAQD,MAAM,WAAW,cAAc;IAC9B,OAAO,CAAC,EAAE,cAAc,GAAG,IAAI,CAAC;IAChC,KAAK,CAAC,EAAE,YAAY,GAAG,IAAI,CAAC;CAC5B;AAED,qBAAa,aAAc,YAAW,cAAc;IACnD,OAAO,CAAC,cAAc,CAA+B;IACrD,OAAO,CAAC,YAAY,CAA6B;IACjD,OAAO,CAAC,MAAM,CAAsB;gBAExB,MAAM,EAAE,mBAAmB,EAAE,QAAQ,CAAC,EAAE,cAAc;IA0B5D,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAcjC,WAAW,IAAI,OAAO;IAOhB,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC;CA4CjD"}
|
package/dist/IpInfoService.js
CHANGED
|
@@ -1,89 +1,82 @@
|
|
|
1
1
|
import { IpapiBackend } from "./backends/ipapi.js";
|
|
2
2
|
import { MaxMindBackend } from "./backends/maxmind.js";
|
|
3
|
+
//#region src/IpInfoService.ts
|
|
4
|
+
/**
|
|
5
|
+
* Returns true for loopback, link-local, and private-range IPs that will
|
|
6
|
+
* never yield useful geolocation or threat data from any backend.
|
|
7
|
+
*/
|
|
3
8
|
function isNonRoutable(ip) {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
if (/^fe[89ab]/i.test(normalized)) return true;
|
|
15
|
-
return false;
|
|
9
|
+
const normalized = ip.replace(/^::ffff:/i, "");
|
|
10
|
+
if (normalized.startsWith("127.") || normalized.startsWith("10.") || normalized.startsWith("192.168.") || normalized.startsWith("169.254.") || normalized === "0.0.0.0") return true;
|
|
11
|
+
if (normalized.startsWith("172.")) {
|
|
12
|
+
const second = Number.parseInt(normalized.split(".")[1] ?? "", 10);
|
|
13
|
+
if (second >= 16 && second <= 31) return true;
|
|
14
|
+
}
|
|
15
|
+
if (normalized === "::1" || normalized === "::") return true;
|
|
16
|
+
if (/^f[cd]/i.test(normalized)) return true;
|
|
17
|
+
if (/^fe[89ab]/i.test(normalized)) return true;
|
|
18
|
+
return false;
|
|
16
19
|
}
|
|
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
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
if (this.maxmindBackend?.isAvailable()) {
|
|
78
|
-
return this.maxmindBackend.lookup(ip);
|
|
79
|
-
}
|
|
80
|
-
return {
|
|
81
|
-
isValid: false,
|
|
82
|
-
error: "No IP info backend available",
|
|
83
|
-
ip
|
|
84
|
-
};
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
export {
|
|
88
|
-
IpInfoService
|
|
20
|
+
var IpInfoService = class {
|
|
21
|
+
constructor(config, backends) {
|
|
22
|
+
this.maxmindBackend = null;
|
|
23
|
+
this.ipapiBackend = null;
|
|
24
|
+
this.config = config;
|
|
25
|
+
if (backends) {
|
|
26
|
+
this.maxmindBackend = backends.maxmind ?? null;
|
|
27
|
+
this.ipapiBackend = backends.ipapi ?? null;
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
if (config.maxmindCityDbPath || config.maxmindAsnDbPath) this.maxmindBackend = new MaxMindBackend({
|
|
31
|
+
cityDbPath: config.maxmindCityDbPath,
|
|
32
|
+
asnDbPath: config.maxmindAsnDbPath,
|
|
33
|
+
logger: config.logger
|
|
34
|
+
});
|
|
35
|
+
if (config.ipapiUrl) this.ipapiBackend = new IpapiBackend({
|
|
36
|
+
baseUrl: config.ipapiUrl,
|
|
37
|
+
apiKey: config.ipapiKey,
|
|
38
|
+
logger: config.logger
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
async initialize() {
|
|
42
|
+
if (this.maxmindBackend) await this.maxmindBackend.initialize();
|
|
43
|
+
this.config.logger?.info(() => ({
|
|
44
|
+
msg: "IpInfoService initialized",
|
|
45
|
+
data: {
|
|
46
|
+
maxmindAvailable: this.maxmindBackend?.isAvailable() ?? false,
|
|
47
|
+
ipapiAvailable: this.ipapiBackend?.isAvailable() ?? false
|
|
48
|
+
}
|
|
49
|
+
}));
|
|
50
|
+
}
|
|
51
|
+
isAvailable() {
|
|
52
|
+
return (this.maxmindBackend?.isAvailable() ?? false) || (this.ipapiBackend?.isAvailable() ?? false);
|
|
53
|
+
}
|
|
54
|
+
async lookup(ip) {
|
|
55
|
+
if (isNonRoutable(ip)) return {
|
|
56
|
+
isValid: false,
|
|
57
|
+
error: "Non-routable IP address",
|
|
58
|
+
ip
|
|
59
|
+
};
|
|
60
|
+
if (this.ipapiBackend?.isAvailable()) {
|
|
61
|
+
const result = await this.ipapiBackend.lookup(ip);
|
|
62
|
+
if (result.isValid) return result;
|
|
63
|
+
this.config.logger?.debug(() => ({
|
|
64
|
+
msg: "ipapi.is lookup failed, falling back to MaxMind",
|
|
65
|
+
data: {
|
|
66
|
+
ip,
|
|
67
|
+
error: "error" in result ? result.error : "unknown"
|
|
68
|
+
}
|
|
69
|
+
}));
|
|
70
|
+
if (this.maxmindBackend?.isAvailable()) return this.maxmindBackend.lookup(ip);
|
|
71
|
+
return result;
|
|
72
|
+
}
|
|
73
|
+
if (this.maxmindBackend?.isAvailable()) return this.maxmindBackend.lookup(ip);
|
|
74
|
+
return {
|
|
75
|
+
isValid: false,
|
|
76
|
+
error: "No IP info backend available",
|
|
77
|
+
ip
|
|
78
|
+
};
|
|
79
|
+
}
|
|
89
80
|
};
|
|
81
|
+
//#endregion
|
|
82
|
+
export { IpInfoService, isNonRoutable };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"IpInfoService.js","sourceRoot":"","sources":["../src/IpInfoService.ts"],"names":[],"mappings":"AAeA,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAOvD,
|
|
1
|
+
{"version":3,"file":"IpInfoService.js","sourceRoot":"","sources":["../src/IpInfoService.ts"],"names":[],"mappings":"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"}
|
package/dist/backends/ipapi.d.ts
CHANGED
|
@@ -1,14 +1,20 @@
|
|
|
1
1
|
import type { Logger } from "@prosopo/logger";
|
|
2
2
|
import type { IPInfoResponse } from "@prosopo/types";
|
|
3
|
+
export declare const DEFAULT_TIMEOUT_MS = 700;
|
|
4
|
+
export type FetchFn = (url: string, init: RequestInit) => Promise<globalThis.Response>;
|
|
3
5
|
export interface IpapiBackendConfig {
|
|
4
6
|
baseUrl: string;
|
|
5
7
|
apiKey?: string;
|
|
6
8
|
logger?: Logger;
|
|
9
|
+
fetch?: FetchFn;
|
|
10
|
+
timeoutMs?: number;
|
|
7
11
|
}
|
|
12
|
+
export declare const parseAbuserScore: (score: string | undefined) => number | undefined;
|
|
8
13
|
export declare class IpapiBackend {
|
|
9
14
|
private config;
|
|
10
15
|
constructor(config: IpapiBackendConfig);
|
|
11
16
|
isAvailable(): boolean;
|
|
17
|
+
private get timeoutMs();
|
|
12
18
|
lookup(ip: string): Promise<IPInfoResponse>;
|
|
13
19
|
}
|
|
14
20
|
//# sourceMappingURL=ipapi.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ipapi.d.ts","sourceRoot":"","sources":["../../src/backends/ipapi.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AAC9C,OAAO,KAAK,EAEX,cAAc,EAEd,MAAM,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"ipapi.d.ts","sourceRoot":"","sources":["../../src/backends/ipapi.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AAC9C,OAAO,KAAK,EAEX,cAAc,EAEd,MAAM,gBAAgB,CAAC;AAMxB,eAAO,MAAM,kBAAkB,MAAM,CAAC;AAGtC,MAAM,MAAM,OAAO,GAAG,CACrB,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,WAAW,KACb,OAAO,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC;AAElC,MAAM,WAAW,kBAAkB;IAClC,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAEhB,KAAK,CAAC,EAAE,OAAO,CAAC;IAEhB,SAAS,CAAC,EAAE,MAAM,CAAC;CACnB;AAuBD,eAAO,MAAM,gBAAgB,UACrB,MAAM,GAAG,SAAS,KACvB,MAAM,GAAG,SAOX,CAAC;AAEF,qBAAa,YAAY;IACxB,OAAO,CAAC,MAAM,CAAqB;gBAEvB,MAAM,EAAE,kBAAkB;IAItC,WAAW,IAAI,OAAO;IAItB,OAAO,KAAK,SAAS,GAEpB;IAEK,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC;CAyGjD"}
|
package/dist/backends/ipapi.js
CHANGED
|
@@ -1,105 +1,118 @@
|
|
|
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
|
-
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
|
-
};
|
|
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
|
-
datacenterName: data.datacenter?.datacenter,
|
|
66
|
-
country: data.location?.country,
|
|
67
|
-
countryCode: data.location?.country_code,
|
|
68
|
-
region: data.location?.state,
|
|
69
|
-
city: data.location?.city,
|
|
70
|
-
latitude: data.location?.latitude,
|
|
71
|
-
longitude: data.location?.longitude,
|
|
72
|
-
timezone: data.location?.timezone,
|
|
73
|
-
vpnService: data.vpn?.service,
|
|
74
|
-
vpnType: data.vpn?.type,
|
|
75
|
-
abuserScore: Number.parseFloat(
|
|
76
|
-
data.asn?.abuser_score.split(" ")[0] || "0"
|
|
77
|
-
),
|
|
78
|
-
companyAbuserScore: Number.parseFloat(
|
|
79
|
-
data.company?.abuser_score.split(" ")[0] || "0"
|
|
80
|
-
)
|
|
81
|
-
};
|
|
82
|
-
return result;
|
|
83
|
-
} catch (fetchError) {
|
|
84
|
-
clearTimeout(timeoutId);
|
|
85
|
-
if (fetchError instanceof Error && fetchError.name === "AbortError") {
|
|
86
|
-
return {
|
|
87
|
-
isValid: false,
|
|
88
|
-
error: `Request timed out after ${TIMEOUT_MS}ms`,
|
|
89
|
-
ip
|
|
90
|
-
};
|
|
91
|
-
}
|
|
92
|
-
throw fetchError;
|
|
93
|
-
}
|
|
94
|
-
} catch (error) {
|
|
95
|
-
return {
|
|
96
|
-
isValid: false,
|
|
97
|
-
error: `Network or parsing error: ${error instanceof Error ? error.message : String(error)}`,
|
|
98
|
-
ip
|
|
99
|
-
};
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
export {
|
|
104
|
-
IpapiBackend
|
|
1
|
+
/**
|
|
2
|
+
* Parse an upstream abuser score, which arrives as a string like "0.0012 (Low)".
|
|
3
|
+
*
|
|
4
|
+
* The field is declared required by the response type but is not guaranteed by
|
|
5
|
+
* the wire: it comes from `response.json()`, which is cast, not validated. A
|
|
6
|
+
* missing value used to throw, and the throw was caught far above as a generic
|
|
7
|
+
* "Network or parsing error" — discarding an otherwise complete and successful
|
|
8
|
+
* lookup over one absent score.
|
|
9
|
+
*
|
|
10
|
+
* Returns `undefined` — not 0 — when the field is absent or unparseable. The
|
|
11
|
+
* scale is 0..1 with 0 meaning "clean" (see `abuserScoreThreshold`, declared
|
|
12
|
+
* `{min: 0, max: 1}`), so a literal 0 would assert cleanliness we have not
|
|
13
|
+
* established. `undefined` says "unknown" instead, and the one consumer
|
|
14
|
+
* (checkTrafficFilter) already resolves it with `?? 0`, so the effective
|
|
15
|
+
* blocking behaviour is unchanged while the distinction stays available.
|
|
16
|
+
*
|
|
17
|
+
* Fail-closed alternatives were rejected: 1 turns any upstream formatting
|
|
18
|
+
* change into a silent max-severity block indistinguishable from a genuine
|
|
19
|
+
* 1.0, and throwing reintroduces exactly the bug above — a cosmetic sub-field
|
|
20
|
+
* collapsing a good lookup into a total failure.
|
|
21
|
+
*/
|
|
22
|
+
var parseAbuserScore = (score) => {
|
|
23
|
+
const head = score?.split(" ")[0];
|
|
24
|
+
if (!head) return;
|
|
25
|
+
const parsed = Number.parseFloat(head);
|
|
26
|
+
return Number.isNaN(parsed) ? void 0 : parsed;
|
|
105
27
|
};
|
|
28
|
+
var IpapiBackend = class {
|
|
29
|
+
constructor(config) {
|
|
30
|
+
this.config = config;
|
|
31
|
+
}
|
|
32
|
+
isAvailable() {
|
|
33
|
+
return Boolean(this.config.baseUrl);
|
|
34
|
+
}
|
|
35
|
+
get timeoutMs() {
|
|
36
|
+
return this.config.timeoutMs ?? 700;
|
|
37
|
+
}
|
|
38
|
+
async lookup(ip) {
|
|
39
|
+
try {
|
|
40
|
+
if (!ip || typeof ip !== "string") return {
|
|
41
|
+
isValid: false,
|
|
42
|
+
error: "Invalid IP address provided",
|
|
43
|
+
ip: ip || "undefined"
|
|
44
|
+
};
|
|
45
|
+
const body = { q: ip };
|
|
46
|
+
if (this.config.apiKey) body.key = this.config.apiKey;
|
|
47
|
+
const controller = new AbortController();
|
|
48
|
+
const timeoutId = setTimeout(() => controller.abort(), this.timeoutMs);
|
|
49
|
+
try {
|
|
50
|
+
const response = await (this.config.fetch ?? globalThis.fetch)(this.config.baseUrl, {
|
|
51
|
+
method: "POST",
|
|
52
|
+
headers: {
|
|
53
|
+
"Content-Type": "application/json",
|
|
54
|
+
Accept: "application/json"
|
|
55
|
+
},
|
|
56
|
+
body: JSON.stringify(body),
|
|
57
|
+
signal: controller.signal
|
|
58
|
+
});
|
|
59
|
+
clearTimeout(timeoutId);
|
|
60
|
+
if (!response.ok) return {
|
|
61
|
+
isValid: false,
|
|
62
|
+
error: `API request failed with status ${response.status}: ${response.statusText}`,
|
|
63
|
+
ip
|
|
64
|
+
};
|
|
65
|
+
const data = await response.json();
|
|
66
|
+
if (data.is_bogon) return {
|
|
67
|
+
isValid: false,
|
|
68
|
+
error: "IP address is bogon (non-routable)",
|
|
69
|
+
ip
|
|
70
|
+
};
|
|
71
|
+
return {
|
|
72
|
+
ip: data.ip,
|
|
73
|
+
isValid: true,
|
|
74
|
+
isVPN: data.is_vpn,
|
|
75
|
+
isTor: data.is_tor,
|
|
76
|
+
isProxy: data.is_proxy,
|
|
77
|
+
isDatacenter: data.is_datacenter,
|
|
78
|
+
isAbuser: data.is_abuser,
|
|
79
|
+
isMobile: data.is_mobile,
|
|
80
|
+
isSatellite: data.is_satellite,
|
|
81
|
+
isCrawler: data.is_crawler,
|
|
82
|
+
providerName: data.company?.name || data.datacenter?.datacenter,
|
|
83
|
+
providerType: data.company?.type || data.asn?.type,
|
|
84
|
+
asnNumber: data.asn?.asn,
|
|
85
|
+
asnOrganization: data.asn?.org,
|
|
86
|
+
datacenterName: data.datacenter?.datacenter,
|
|
87
|
+
country: data.location?.country,
|
|
88
|
+
countryCode: data.location?.country_code,
|
|
89
|
+
region: data.location?.state,
|
|
90
|
+
city: data.location?.city,
|
|
91
|
+
latitude: data.location?.latitude,
|
|
92
|
+
longitude: data.location?.longitude,
|
|
93
|
+
timezone: data.location?.timezone,
|
|
94
|
+
vpnService: data.vpn?.service,
|
|
95
|
+
vpnType: data.vpn?.type,
|
|
96
|
+
abuserScore: parseAbuserScore(data.asn?.abuser_score),
|
|
97
|
+
companyAbuserScore: parseAbuserScore(data.company?.abuser_score)
|
|
98
|
+
};
|
|
99
|
+
} catch (fetchError) {
|
|
100
|
+
clearTimeout(timeoutId);
|
|
101
|
+
if (fetchError instanceof Error && fetchError.name === "AbortError") return {
|
|
102
|
+
isValid: false,
|
|
103
|
+
error: `Request timed out after ${this.timeoutMs}ms`,
|
|
104
|
+
ip
|
|
105
|
+
};
|
|
106
|
+
throw fetchError;
|
|
107
|
+
}
|
|
108
|
+
} catch (error) {
|
|
109
|
+
return {
|
|
110
|
+
isValid: false,
|
|
111
|
+
error: `Network or parsing error: ${error instanceof Error ? error.message : String(error)}`,
|
|
112
|
+
ip
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
//#endregion
|
|
118
|
+
export { IpapiBackend, parseAbuserScore };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ipapi.js","sourceRoot":"","sources":["../../src/backends/ipapi.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"ipapi.js","sourceRoot":"","sources":["../../src/backends/ipapi.ts"],"names":[],"mappings":"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"}
|