@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,147 @@
|
|
|
1
|
+
// Copyright 2021-2026 Prosopo (UK) Ltd.
|
|
2
|
+
//
|
|
3
|
+
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
// you may not use this file except in compliance with the License.
|
|
5
|
+
// You may obtain a copy of the License at
|
|
6
|
+
//
|
|
7
|
+
// http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
//
|
|
9
|
+
// Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
// See the License for the specific language governing permissions and
|
|
13
|
+
// limitations under the License.
|
|
14
|
+
|
|
15
|
+
import type { IPInfoResponse } from "@prosopo/types";
|
|
16
|
+
import { IpapiBackend } from "./backends/ipapi.js";
|
|
17
|
+
import { MaxMindBackend } from "./backends/maxmind.js";
|
|
18
|
+
import type { IIpInfoService, IpInfoServiceConfig } from "./types.js";
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Returns true for loopback, link-local, and private-range IPs that will
|
|
22
|
+
* never yield useful geolocation or threat data from any backend.
|
|
23
|
+
*/
|
|
24
|
+
function isNonRoutable(ip: string): boolean {
|
|
25
|
+
// Strip IPv4-mapped IPv6 prefix (::ffff:127.0.0.1 -> 127.0.0.1)
|
|
26
|
+
const normalized = ip.replace(/^::ffff:/i, "");
|
|
27
|
+
|
|
28
|
+
// IPv4 private / loopback / link-local
|
|
29
|
+
if (
|
|
30
|
+
normalized.startsWith("127.") ||
|
|
31
|
+
normalized.startsWith("10.") ||
|
|
32
|
+
normalized.startsWith("192.168.") ||
|
|
33
|
+
normalized.startsWith("169.254.") ||
|
|
34
|
+
normalized === "0.0.0.0"
|
|
35
|
+
) {
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// 172.16.0.0 – 172.31.255.255
|
|
40
|
+
if (normalized.startsWith("172.")) {
|
|
41
|
+
const second = Number.parseInt(normalized.split(".")[1] ?? "", 10);
|
|
42
|
+
if (second >= 16 && second <= 31) return true;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// IPv6 loopback
|
|
46
|
+
if (normalized === "::1" || normalized === "::") return true;
|
|
47
|
+
|
|
48
|
+
// IPv6 ULA (fc00::/7 — covers fc00:: through fdff::)
|
|
49
|
+
if (/^f[cd]/i.test(normalized)) return true;
|
|
50
|
+
|
|
51
|
+
// IPv6 link-local (fe80::/10)
|
|
52
|
+
if (/^fe[89ab]/i.test(normalized)) return true;
|
|
53
|
+
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export class IpInfoService implements IIpInfoService {
|
|
58
|
+
private maxmindBackend: MaxMindBackend | null = null;
|
|
59
|
+
private ipapiBackend: IpapiBackend | null = null;
|
|
60
|
+
private config: IpInfoServiceConfig;
|
|
61
|
+
|
|
62
|
+
constructor(config: IpInfoServiceConfig) {
|
|
63
|
+
this.config = config;
|
|
64
|
+
|
|
65
|
+
if (config.maxmindCityDbPath || config.maxmindAsnDbPath) {
|
|
66
|
+
this.maxmindBackend = new MaxMindBackend({
|
|
67
|
+
cityDbPath: config.maxmindCityDbPath,
|
|
68
|
+
asnDbPath: config.maxmindAsnDbPath,
|
|
69
|
+
logger: config.logger,
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (config.ipapiUrl) {
|
|
74
|
+
this.ipapiBackend = new IpapiBackend({
|
|
75
|
+
baseUrl: config.ipapiUrl,
|
|
76
|
+
apiKey: config.ipapiKey,
|
|
77
|
+
logger: config.logger,
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
async initialize(): Promise<void> {
|
|
83
|
+
if (this.maxmindBackend) {
|
|
84
|
+
await this.maxmindBackend.initialize();
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
this.config.logger?.info(() => ({
|
|
88
|
+
msg: "IpInfoService initialized",
|
|
89
|
+
data: {
|
|
90
|
+
maxmindAvailable: this.maxmindBackend?.isAvailable() ?? false,
|
|
91
|
+
ipapiAvailable: this.ipapiBackend?.isAvailable() ?? false,
|
|
92
|
+
},
|
|
93
|
+
}));
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
isAvailable(): boolean {
|
|
97
|
+
return (
|
|
98
|
+
(this.maxmindBackend?.isAvailable() ?? false) ||
|
|
99
|
+
(this.ipapiBackend?.isAvailable() ?? false)
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
async lookup(ip: string): Promise<IPInfoResponse> {
|
|
104
|
+
if (isNonRoutable(ip)) {
|
|
105
|
+
return {
|
|
106
|
+
isValid: false,
|
|
107
|
+
error: "Non-routable IP address",
|
|
108
|
+
ip,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Prefer ipapi.is when available (richer threat data)
|
|
113
|
+
if (this.ipapiBackend?.isAvailable()) {
|
|
114
|
+
const result = await this.ipapiBackend.lookup(ip);
|
|
115
|
+
if (result.isValid) {
|
|
116
|
+
return result;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// ipapi.is failed, fall back to MaxMind if available
|
|
120
|
+
this.config.logger?.debug(() => ({
|
|
121
|
+
msg: "ipapi.is lookup failed, falling back to MaxMind",
|
|
122
|
+
data: {
|
|
123
|
+
ip,
|
|
124
|
+
error: "error" in result ? result.error : "unknown",
|
|
125
|
+
},
|
|
126
|
+
}));
|
|
127
|
+
|
|
128
|
+
if (this.maxmindBackend?.isAvailable()) {
|
|
129
|
+
return this.maxmindBackend.lookup(ip);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Return the ipapi error if no MaxMind fallback
|
|
133
|
+
return result;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// MaxMind only
|
|
137
|
+
if (this.maxmindBackend?.isAvailable()) {
|
|
138
|
+
return this.maxmindBackend.lookup(ip);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return {
|
|
142
|
+
isValid: false,
|
|
143
|
+
error: "No IP info backend available",
|
|
144
|
+
ip,
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
// Copyright 2021-2026 Prosopo (UK) Ltd.
|
|
2
|
+
//
|
|
3
|
+
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
// you may not use this file except in compliance with the License.
|
|
5
|
+
// You may obtain a copy of the License at
|
|
6
|
+
//
|
|
7
|
+
// http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
//
|
|
9
|
+
// Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
// See the License for the specific language governing permissions and
|
|
13
|
+
// limitations under the License.
|
|
14
|
+
|
|
15
|
+
import type { Logger } from "@prosopo/logger";
|
|
16
|
+
import type {
|
|
17
|
+
IPApiResponse,
|
|
18
|
+
IPInfoResponse,
|
|
19
|
+
IPInfoResult,
|
|
20
|
+
} from "@prosopo/types";
|
|
21
|
+
|
|
22
|
+
const TIMEOUT_MS = 700;
|
|
23
|
+
|
|
24
|
+
export interface IpapiBackendConfig {
|
|
25
|
+
baseUrl: string;
|
|
26
|
+
apiKey?: string;
|
|
27
|
+
logger?: Logger;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export class IpapiBackend {
|
|
31
|
+
private config: IpapiBackendConfig;
|
|
32
|
+
|
|
33
|
+
constructor(config: IpapiBackendConfig) {
|
|
34
|
+
this.config = config;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
isAvailable(): boolean {
|
|
38
|
+
return Boolean(this.config.baseUrl);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async lookup(ip: string): Promise<IPInfoResponse> {
|
|
42
|
+
try {
|
|
43
|
+
if (!ip || typeof ip !== "string") {
|
|
44
|
+
return {
|
|
45
|
+
isValid: false,
|
|
46
|
+
error: "Invalid IP address provided",
|
|
47
|
+
ip: ip || "undefined",
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const body: { q: string; key?: string } = { q: ip };
|
|
52
|
+
if (this.config.apiKey) {
|
|
53
|
+
body.key = this.config.apiKey;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const controller = new AbortController();
|
|
57
|
+
const timeoutId = setTimeout(() => controller.abort(), TIMEOUT_MS);
|
|
58
|
+
|
|
59
|
+
try {
|
|
60
|
+
const response = await fetch(this.config.baseUrl, {
|
|
61
|
+
method: "POST",
|
|
62
|
+
headers: {
|
|
63
|
+
"Content-Type": "application/json",
|
|
64
|
+
Accept: "application/json",
|
|
65
|
+
},
|
|
66
|
+
body: JSON.stringify(body),
|
|
67
|
+
signal: controller.signal,
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
clearTimeout(timeoutId);
|
|
71
|
+
|
|
72
|
+
if (!response.ok) {
|
|
73
|
+
return {
|
|
74
|
+
isValid: false,
|
|
75
|
+
error: `API request failed with status ${response.status}: ${response.statusText}`,
|
|
76
|
+
ip,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const data: IPApiResponse = (await response.json()) as IPApiResponse;
|
|
81
|
+
|
|
82
|
+
if (data.is_bogon) {
|
|
83
|
+
return {
|
|
84
|
+
isValid: false,
|
|
85
|
+
error: "IP address is bogon (non-routable)",
|
|
86
|
+
ip,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const result: IPInfoResult = {
|
|
91
|
+
ip: data.ip,
|
|
92
|
+
isValid: true,
|
|
93
|
+
|
|
94
|
+
isVPN: data.is_vpn,
|
|
95
|
+
isTor: data.is_tor,
|
|
96
|
+
isProxy: data.is_proxy,
|
|
97
|
+
isDatacenter: data.is_datacenter,
|
|
98
|
+
isAbuser: data.is_abuser,
|
|
99
|
+
isMobile: data.is_mobile,
|
|
100
|
+
isSatellite: data.is_satellite,
|
|
101
|
+
isCrawler: data.is_crawler,
|
|
102
|
+
providerName: data.company?.name || data.datacenter?.datacenter,
|
|
103
|
+
providerType: data.company?.type || data.asn?.type,
|
|
104
|
+
asnNumber: data.asn?.asn,
|
|
105
|
+
asnOrganization: data.asn?.org,
|
|
106
|
+
|
|
107
|
+
country: data.location?.country,
|
|
108
|
+
countryCode: data.location?.country_code,
|
|
109
|
+
region: data.location?.state,
|
|
110
|
+
city: data.location?.city,
|
|
111
|
+
latitude: data.location?.latitude,
|
|
112
|
+
longitude: data.location?.longitude,
|
|
113
|
+
timezone: data.location?.timezone,
|
|
114
|
+
|
|
115
|
+
vpnService: data.vpn?.service,
|
|
116
|
+
vpnType: data.vpn?.type,
|
|
117
|
+
|
|
118
|
+
abuserScore: Number.parseFloat(
|
|
119
|
+
data.asn?.abuser_score.split(" ")[0] || "0",
|
|
120
|
+
),
|
|
121
|
+
companyAbuserScore: Number.parseFloat(
|
|
122
|
+
data.company?.abuser_score.split(" ")[0] || "0",
|
|
123
|
+
),
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
return result;
|
|
127
|
+
} catch (fetchError) {
|
|
128
|
+
clearTimeout(timeoutId);
|
|
129
|
+
|
|
130
|
+
if (fetchError instanceof Error && fetchError.name === "AbortError") {
|
|
131
|
+
return {
|
|
132
|
+
isValid: false,
|
|
133
|
+
error: `Request timed out after ${TIMEOUT_MS}ms`,
|
|
134
|
+
ip,
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
throw fetchError;
|
|
139
|
+
}
|
|
140
|
+
} catch (error) {
|
|
141
|
+
return {
|
|
142
|
+
isValid: false,
|
|
143
|
+
error: `Network or parsing error: ${error instanceof Error ? error.message : String(error)}`,
|
|
144
|
+
ip,
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
// Copyright 2021-2026 Prosopo (UK) Ltd.
|
|
2
|
+
//
|
|
3
|
+
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
// you may not use this file except in compliance with the License.
|
|
5
|
+
// You may obtain a copy of the License at
|
|
6
|
+
//
|
|
7
|
+
// http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
//
|
|
9
|
+
// Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
// See the License for the specific language governing permissions and
|
|
13
|
+
// limitations under the License.
|
|
14
|
+
|
|
15
|
+
import type { Asn, City, ReaderModel } from "@maxmind/geoip2-node";
|
|
16
|
+
import type { Logger } from "@prosopo/logger";
|
|
17
|
+
import type { IPInfoResponse, IPInfoResult } from "@prosopo/types";
|
|
18
|
+
|
|
19
|
+
export interface MaxMindBackendConfig {
|
|
20
|
+
cityDbPath?: string;
|
|
21
|
+
asnDbPath?: string;
|
|
22
|
+
logger?: Logger;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export class MaxMindBackend {
|
|
26
|
+
private cityReader: ReaderModel | null = null;
|
|
27
|
+
private asnReader: ReaderModel | null = null;
|
|
28
|
+
private config: MaxMindBackendConfig;
|
|
29
|
+
|
|
30
|
+
constructor(config: MaxMindBackendConfig) {
|
|
31
|
+
this.config = config;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async initialize(): Promise<void> {
|
|
35
|
+
const { Reader } = await import("@maxmind/geoip2-node");
|
|
36
|
+
|
|
37
|
+
if (this.config.cityDbPath) {
|
|
38
|
+
try {
|
|
39
|
+
this.cityReader = await Reader.open(this.config.cityDbPath);
|
|
40
|
+
this.config.logger?.info(() => ({
|
|
41
|
+
msg: "MaxMind City reader initialized",
|
|
42
|
+
data: { dbPath: this.config.cityDbPath },
|
|
43
|
+
}));
|
|
44
|
+
} catch (error) {
|
|
45
|
+
this.config.logger?.warn(() => ({
|
|
46
|
+
msg: "Failed to initialize MaxMind City reader",
|
|
47
|
+
err: error,
|
|
48
|
+
data: { dbPath: this.config.cityDbPath },
|
|
49
|
+
}));
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (this.config.asnDbPath) {
|
|
54
|
+
try {
|
|
55
|
+
this.asnReader = await Reader.open(this.config.asnDbPath);
|
|
56
|
+
this.config.logger?.info(() => ({
|
|
57
|
+
msg: "MaxMind ASN reader initialized",
|
|
58
|
+
data: { dbPath: this.config.asnDbPath },
|
|
59
|
+
}));
|
|
60
|
+
} catch (error) {
|
|
61
|
+
this.config.logger?.warn(() => ({
|
|
62
|
+
msg: "Failed to initialize MaxMind ASN reader",
|
|
63
|
+
err: error,
|
|
64
|
+
data: { dbPath: this.config.asnDbPath },
|
|
65
|
+
}));
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
isAvailable(): boolean {
|
|
71
|
+
return this.cityReader !== null || this.asnReader !== null;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async lookup(ip: string): Promise<IPInfoResponse> {
|
|
75
|
+
if (!this.isAvailable()) {
|
|
76
|
+
return {
|
|
77
|
+
isValid: false,
|
|
78
|
+
error: "MaxMind readers not initialized",
|
|
79
|
+
ip,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
try {
|
|
84
|
+
let cityData: City | undefined;
|
|
85
|
+
let asnData: Asn | undefined;
|
|
86
|
+
|
|
87
|
+
if (this.cityReader) {
|
|
88
|
+
try {
|
|
89
|
+
cityData = this.cityReader.city(ip);
|
|
90
|
+
} catch (error) {
|
|
91
|
+
this.config.logger?.debug(() => ({
|
|
92
|
+
msg: "MaxMind City lookup failed",
|
|
93
|
+
data: { ip },
|
|
94
|
+
err: error,
|
|
95
|
+
}));
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (this.asnReader) {
|
|
100
|
+
try {
|
|
101
|
+
asnData = this.asnReader.asn(ip);
|
|
102
|
+
} catch (error) {
|
|
103
|
+
this.config.logger?.debug(() => ({
|
|
104
|
+
msg: "MaxMind ASN lookup failed",
|
|
105
|
+
data: { ip },
|
|
106
|
+
err: error,
|
|
107
|
+
}));
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (!cityData && !asnData) {
|
|
112
|
+
return {
|
|
113
|
+
isValid: false,
|
|
114
|
+
error: "No MaxMind data available for IP",
|
|
115
|
+
ip,
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const result: IPInfoResult = {
|
|
120
|
+
ip,
|
|
121
|
+
isValid: true,
|
|
122
|
+
|
|
123
|
+
// Threat indicators - GeoLite2 free DBs do not populate these
|
|
124
|
+
isVPN: cityData?.traits?.isAnonymousVpn ?? false,
|
|
125
|
+
isTor: cityData?.traits?.isTorExitNode ?? false,
|
|
126
|
+
isProxy:
|
|
127
|
+
(cityData?.traits?.isPublicProxy ?? false) ||
|
|
128
|
+
(cityData?.traits?.isResidentialProxy ?? false),
|
|
129
|
+
isDatacenter: cityData?.traits?.isHostingProvider ?? false,
|
|
130
|
+
isAbuser: false,
|
|
131
|
+
isMobile: false,
|
|
132
|
+
isSatellite: cityData?.traits?.isSatelliteProvider ?? false,
|
|
133
|
+
isCrawler: false,
|
|
134
|
+
|
|
135
|
+
// Geolocation from City DB
|
|
136
|
+
country: cityData?.country?.names?.en,
|
|
137
|
+
countryCode: cityData?.country?.isoCode,
|
|
138
|
+
region: cityData?.subdivisions?.[0]?.names?.en,
|
|
139
|
+
city: cityData?.city?.names?.en,
|
|
140
|
+
latitude: cityData?.location?.latitude,
|
|
141
|
+
longitude: cityData?.location?.longitude,
|
|
142
|
+
timezone: cityData?.location?.timeZone,
|
|
143
|
+
|
|
144
|
+
// ASN info - prefer City DB traits, fall back to ASN DB
|
|
145
|
+
asnNumber:
|
|
146
|
+
cityData?.traits?.autonomousSystemNumber ??
|
|
147
|
+
asnData?.autonomousSystemNumber,
|
|
148
|
+
asnOrganization:
|
|
149
|
+
cityData?.traits?.autonomousSystemOrganization ??
|
|
150
|
+
asnData?.autonomousSystemOrganization,
|
|
151
|
+
|
|
152
|
+
// Provider info from ASN
|
|
153
|
+
providerName:
|
|
154
|
+
cityData?.traits?.autonomousSystemOrganization ??
|
|
155
|
+
asnData?.autonomousSystemOrganization,
|
|
156
|
+
providerType: mapUserType(cityData?.traits?.userType),
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
return result;
|
|
160
|
+
} catch (error) {
|
|
161
|
+
return {
|
|
162
|
+
isValid: false,
|
|
163
|
+
error: `MaxMind lookup error: ${error instanceof Error ? error.message : String(error)}`,
|
|
164
|
+
ip,
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
type MaxMindUserType =
|
|
171
|
+
| "business"
|
|
172
|
+
| "cafe"
|
|
173
|
+
| "cellular"
|
|
174
|
+
| "college"
|
|
175
|
+
| "consumer_privacy_network"
|
|
176
|
+
| "content_delivery_network"
|
|
177
|
+
| "dialup"
|
|
178
|
+
| "government"
|
|
179
|
+
| "hosting"
|
|
180
|
+
| "library"
|
|
181
|
+
| "military"
|
|
182
|
+
| "residential"
|
|
183
|
+
| "router"
|
|
184
|
+
| "school"
|
|
185
|
+
| "search_engine_spider"
|
|
186
|
+
| "traveler";
|
|
187
|
+
|
|
188
|
+
type ProviderType =
|
|
189
|
+
| "hosting"
|
|
190
|
+
| "education"
|
|
191
|
+
| "government"
|
|
192
|
+
| "banking"
|
|
193
|
+
| "business"
|
|
194
|
+
| "isp";
|
|
195
|
+
|
|
196
|
+
function mapUserType(
|
|
197
|
+
userType: MaxMindUserType | undefined,
|
|
198
|
+
): ProviderType | undefined {
|
|
199
|
+
switch (userType) {
|
|
200
|
+
case "hosting":
|
|
201
|
+
case "content_delivery_network":
|
|
202
|
+
return "hosting";
|
|
203
|
+
case "college":
|
|
204
|
+
case "school":
|
|
205
|
+
case "library":
|
|
206
|
+
return "education";
|
|
207
|
+
case "government":
|
|
208
|
+
case "military":
|
|
209
|
+
return "government";
|
|
210
|
+
case "business":
|
|
211
|
+
return "business";
|
|
212
|
+
case "residential":
|
|
213
|
+
case "cellular":
|
|
214
|
+
case "dialup":
|
|
215
|
+
case "cafe":
|
|
216
|
+
case "traveler":
|
|
217
|
+
case "router":
|
|
218
|
+
return "isp";
|
|
219
|
+
default:
|
|
220
|
+
return undefined;
|
|
221
|
+
}
|
|
222
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// Copyright 2021-2026 Prosopo (UK) Ltd.
|
|
2
|
+
//
|
|
3
|
+
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
// you may not use this file except in compliance with the License.
|
|
5
|
+
// You may obtain a copy of the License at
|
|
6
|
+
//
|
|
7
|
+
// http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
//
|
|
9
|
+
// Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
// See the License for the specific language governing permissions and
|
|
13
|
+
// limitations under the License.
|
|
14
|
+
|
|
15
|
+
export { IpInfoService } from "./IpInfoService.js";
|
|
16
|
+
export type { IIpInfoService, IpInfoServiceConfig } from "./types.js";
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// Copyright 2021-2026 Prosopo (UK) Ltd.
|
|
2
|
+
//
|
|
3
|
+
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
// you may not use this file except in compliance with the License.
|
|
5
|
+
// You may obtain a copy of the License at
|
|
6
|
+
//
|
|
7
|
+
// http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
//
|
|
9
|
+
// Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
// See the License for the specific language governing permissions and
|
|
13
|
+
// limitations under the License.
|
|
14
|
+
|
|
15
|
+
import type { Logger } from "@prosopo/logger";
|
|
16
|
+
import type { IPInfoResponse } from "@prosopo/types";
|
|
17
|
+
|
|
18
|
+
export interface IIpInfoService {
|
|
19
|
+
initialize(): Promise<void>;
|
|
20
|
+
lookup(ip: string): Promise<IPInfoResponse>;
|
|
21
|
+
isAvailable(): boolean;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface IpInfoServiceConfig {
|
|
25
|
+
maxmindCityDbPath?: string;
|
|
26
|
+
maxmindAsnDbPath?: string;
|
|
27
|
+
ipapiUrl?: string;
|
|
28
|
+
ipapiKey?: string;
|
|
29
|
+
logger?: Logger;
|
|
30
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "../../tsconfig.cjs.json",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"rootDir": "./src",
|
|
5
|
+
"outDir": "./dist/cjs"
|
|
6
|
+
},
|
|
7
|
+
"include": [
|
|
8
|
+
"./src/**/*.ts",
|
|
9
|
+
"./src/**/*.json",
|
|
10
|
+
"./src/**/*.d.ts",
|
|
11
|
+
"./src/**/*.tsx"
|
|
12
|
+
],
|
|
13
|
+
"references": [
|
|
14
|
+
{
|
|
15
|
+
"path": "../../dev/config/tsconfig.cjs.json"
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"path": "../logger/tsconfig.cjs.json"
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"path": "../types/tsconfig.cjs.json"
|
|
22
|
+
}
|
|
23
|
+
]
|
|
24
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "../../tsconfig.esm.json",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"rootDir": "./src",
|
|
5
|
+
"outDir": "./dist"
|
|
6
|
+
},
|
|
7
|
+
"include": [
|
|
8
|
+
"src",
|
|
9
|
+
"src/**/*.json",
|
|
10
|
+
"src/**/*.ts",
|
|
11
|
+
"src/**/*.tsx",
|
|
12
|
+
"src/**/*.d.ts"
|
|
13
|
+
],
|
|
14
|
+
"references": [
|
|
15
|
+
{
|
|
16
|
+
"path": "../../dev/config/tsconfig.json"
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"path": "../logger"
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"path": "../types"
|
|
23
|
+
}
|
|
24
|
+
]
|
|
25
|
+
}
|