ip-finder-client 1.0.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/README.md +209 -0
- package/dist/index.d.ts +96 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +223 -0
- package/dist/index.mjs +196 -0
- package/dist/types.d.ts +78 -0
- package/dist/types.d.ts.map +1 -0
- package/package.json +52 -0
package/README.md
ADDED
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
# ip-finder-client
|
|
2
|
+
|
|
3
|
+
A lightweight, zero-dependency Node.js client for the IP Finder API. Get IP geolocation, ASN information, and network details with ease.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install ip-finder-client
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
yarn add ip-finder-client
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
pnpm add ip-finder-client
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Quick Start
|
|
20
|
+
|
|
21
|
+
```typescript
|
|
22
|
+
import { IPFinder } from 'ip-finder-client';
|
|
23
|
+
|
|
24
|
+
const client = new IPFinder({
|
|
25
|
+
apiKey: 'your-api-key',
|
|
26
|
+
baseUrl: 'https://api.your-domain.com' // Your API endpoint
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
// Look up an IP address
|
|
30
|
+
const result = await client.lookup('8.8.8.8');
|
|
31
|
+
|
|
32
|
+
console.log(result.geo?.country); // "United States"
|
|
33
|
+
console.log(result.geo?.city); // "Mountain View"
|
|
34
|
+
console.log(result.asn?.organization); // "Google LLC"
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Configuration
|
|
38
|
+
|
|
39
|
+
```typescript
|
|
40
|
+
const client = new IPFinder({
|
|
41
|
+
// Required: Your API key
|
|
42
|
+
apiKey: 'your-api-key',
|
|
43
|
+
|
|
44
|
+
// Optional: API base URL (defaults to production)
|
|
45
|
+
baseUrl: 'https://api.your-domain.com',
|
|
46
|
+
|
|
47
|
+
// Optional: Request timeout in ms (default: 10000)
|
|
48
|
+
timeout: 5000,
|
|
49
|
+
|
|
50
|
+
// Optional: Number of retries for failed requests (default: 2)
|
|
51
|
+
retries: 3,
|
|
52
|
+
});
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## API Reference
|
|
56
|
+
|
|
57
|
+
### `lookup(ip: string): Promise<IPLookupResult>`
|
|
58
|
+
|
|
59
|
+
Look up information for a single IP address.
|
|
60
|
+
|
|
61
|
+
```typescript
|
|
62
|
+
const result = await client.lookup('8.8.8.8');
|
|
63
|
+
|
|
64
|
+
// Result structure:
|
|
65
|
+
{
|
|
66
|
+
ip: '8.8.8.8',
|
|
67
|
+
geo: {
|
|
68
|
+
city: 'Mountain View',
|
|
69
|
+
region: 'California',
|
|
70
|
+
regionCode: 'CA',
|
|
71
|
+
country: 'United States',
|
|
72
|
+
countryCode: 'US',
|
|
73
|
+
continent: 'North America',
|
|
74
|
+
continentCode: 'NA',
|
|
75
|
+
latitude: 37.4056,
|
|
76
|
+
longitude: -122.0775,
|
|
77
|
+
timezone: 'America/Los_Angeles',
|
|
78
|
+
postalCode: '94043'
|
|
79
|
+
},
|
|
80
|
+
asn: {
|
|
81
|
+
asn: 15169,
|
|
82
|
+
organization: 'Google LLC',
|
|
83
|
+
isp: 'Google LLC'
|
|
84
|
+
},
|
|
85
|
+
network: {
|
|
86
|
+
cidr: '8.8.8.0/24',
|
|
87
|
+
startIp: '8.8.8.0',
|
|
88
|
+
endIp: '8.8.8.255',
|
|
89
|
+
type: 'assigned',
|
|
90
|
+
registry: 'arin'
|
|
91
|
+
},
|
|
92
|
+
version: 4
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### `lookupBatch(ips: string[]): Promise<(IPLookupResult | IPFinderError)[]>`
|
|
97
|
+
|
|
98
|
+
Look up multiple IP addresses in parallel.
|
|
99
|
+
|
|
100
|
+
```typescript
|
|
101
|
+
const results = await client.lookupBatch(['8.8.8.8', '1.1.1.1', '9.9.9.9']);
|
|
102
|
+
|
|
103
|
+
results.forEach(result => {
|
|
104
|
+
if (result instanceof IPFinderError) {
|
|
105
|
+
console.error(`Error: ${result.message}`);
|
|
106
|
+
} else {
|
|
107
|
+
console.log(`${result.ip}: ${result.geo?.country}`);
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### `healthCheck(): Promise<boolean>`
|
|
113
|
+
|
|
114
|
+
Check if the API is healthy and reachable.
|
|
115
|
+
|
|
116
|
+
```typescript
|
|
117
|
+
const isHealthy = await client.healthCheck();
|
|
118
|
+
console.log(isHealthy ? 'API is up!' : 'API is down');
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### Rate Limiting
|
|
122
|
+
|
|
123
|
+
Rate limit information is available after each request:
|
|
124
|
+
|
|
125
|
+
```typescript
|
|
126
|
+
await client.lookup('8.8.8.8');
|
|
127
|
+
|
|
128
|
+
console.log(client.rateLimit);
|
|
129
|
+
// { limit: 1000, remaining: 999, reset: 1703980800 }
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
## Error Handling
|
|
133
|
+
|
|
134
|
+
```typescript
|
|
135
|
+
import { IPFinder, IPFinderError } from 'ip-finder-client';
|
|
136
|
+
|
|
137
|
+
try {
|
|
138
|
+
const result = await client.lookup('invalid-ip');
|
|
139
|
+
} catch (error) {
|
|
140
|
+
if (error instanceof IPFinderError) {
|
|
141
|
+
console.error(`API Error: ${error.message}`);
|
|
142
|
+
console.error(`Status Code: ${error.statusCode}`);
|
|
143
|
+
console.error(`Error Code: ${error.code}`);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### Error Codes
|
|
149
|
+
|
|
150
|
+
| Code | Description |
|
|
151
|
+
|------|-------------|
|
|
152
|
+
| `TIMEOUT` | Request timed out |
|
|
153
|
+
| `NETWORK_ERROR` | Network connectivity issue |
|
|
154
|
+
| `BATCH_ERROR` | Error during batch lookup |
|
|
155
|
+
|
|
156
|
+
### HTTP Status Codes
|
|
157
|
+
|
|
158
|
+
| Status | Description |
|
|
159
|
+
|--------|-------------|
|
|
160
|
+
| 400 | Invalid IP address format |
|
|
161
|
+
| 401 | Invalid or missing API key |
|
|
162
|
+
| 404 | IP not found in database |
|
|
163
|
+
| 429 | Rate limit exceeded |
|
|
164
|
+
| 500 | Server error |
|
|
165
|
+
|
|
166
|
+
## CommonJS Usage
|
|
167
|
+
|
|
168
|
+
```javascript
|
|
169
|
+
const { IPFinder } = require('ip-finder-client');
|
|
170
|
+
|
|
171
|
+
const client = new IPFinder({ apiKey: 'your-api-key' });
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
## Factory Function
|
|
175
|
+
|
|
176
|
+
Alternatively, use the factory function:
|
|
177
|
+
|
|
178
|
+
```typescript
|
|
179
|
+
import { createClient } from 'ip-finder-client';
|
|
180
|
+
|
|
181
|
+
const client = createClient({
|
|
182
|
+
apiKey: 'your-api-key',
|
|
183
|
+
baseUrl: 'https://api.your-domain.com'
|
|
184
|
+
});
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
## TypeScript Support
|
|
188
|
+
|
|
189
|
+
Full TypeScript support with exported types:
|
|
190
|
+
|
|
191
|
+
```typescript
|
|
192
|
+
import type {
|
|
193
|
+
IPFinderConfig,
|
|
194
|
+
IPLookupResult,
|
|
195
|
+
GeoLocation,
|
|
196
|
+
ASNInfo,
|
|
197
|
+
NetworkInfo,
|
|
198
|
+
RateLimitInfo
|
|
199
|
+
} from 'ip-finder-client';
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
## Requirements
|
|
203
|
+
|
|
204
|
+
- Node.js 18+ (uses native `fetch`)
|
|
205
|
+
|
|
206
|
+
## License
|
|
207
|
+
|
|
208
|
+
MIT
|
|
209
|
+
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import type { IPFinderConfig, IPLookupResult, APIError, RateLimitInfo, GeoLocation, ASNInfo, NetworkInfo } from './types';
|
|
2
|
+
export type { IPFinderConfig, IPLookupResult, APIError, RateLimitInfo, GeoLocation, ASNInfo, NetworkInfo, };
|
|
3
|
+
/**
|
|
4
|
+
* Custom error class for IP Finder API errors
|
|
5
|
+
*/
|
|
6
|
+
export declare class IPFinderError extends Error {
|
|
7
|
+
readonly statusCode: number;
|
|
8
|
+
readonly code?: string;
|
|
9
|
+
constructor(message: string, statusCode: number, code?: string);
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* IP Finder client for looking up IP address information
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```ts
|
|
16
|
+
* import { IPFinder } from 'ip-finder-client';
|
|
17
|
+
*
|
|
18
|
+
* const client = new IPFinder({ apiKey: 'your-api-key' });
|
|
19
|
+
* const result = await client.lookup('8.8.8.8');
|
|
20
|
+
* console.log(result.geo?.country); // "United States"
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
export declare class IPFinder {
|
|
24
|
+
private readonly apiKey;
|
|
25
|
+
private readonly baseUrl;
|
|
26
|
+
private readonly timeout;
|
|
27
|
+
private readonly retries;
|
|
28
|
+
/** Rate limit info from the last request */
|
|
29
|
+
rateLimit?: RateLimitInfo;
|
|
30
|
+
constructor(config: IPFinderConfig);
|
|
31
|
+
/**
|
|
32
|
+
* Look up information for an IP address
|
|
33
|
+
*
|
|
34
|
+
* @param ip - IPv4 or IPv6 address to look up
|
|
35
|
+
* @returns IP lookup result with geo, ASN, and network information
|
|
36
|
+
* @throws {IPFinderError} When the API returns an error
|
|
37
|
+
*
|
|
38
|
+
* @example
|
|
39
|
+
* ```ts
|
|
40
|
+
* const result = await client.lookup('8.8.8.8');
|
|
41
|
+
* console.log(result.geo?.city); // "Mountain View"
|
|
42
|
+
* console.log(result.asn?.organization); // "Google LLC"
|
|
43
|
+
* ```
|
|
44
|
+
*/
|
|
45
|
+
lookup(ip: string): Promise<IPLookupResult>;
|
|
46
|
+
/**
|
|
47
|
+
* Look up the current request's IP address (useful for "what's my IP" functionality)
|
|
48
|
+
* Note: This requires your API to support a special endpoint for this
|
|
49
|
+
*
|
|
50
|
+
* @returns IP lookup result for the requesting IP
|
|
51
|
+
*/
|
|
52
|
+
lookupSelf(): Promise<IPLookupResult>;
|
|
53
|
+
/**
|
|
54
|
+
* Batch lookup multiple IP addresses
|
|
55
|
+
*
|
|
56
|
+
* @param ips - Array of IP addresses to look up
|
|
57
|
+
* @returns Array of lookup results (or errors for failed lookups)
|
|
58
|
+
*
|
|
59
|
+
* @example
|
|
60
|
+
* ```ts
|
|
61
|
+
* const results = await client.lookupBatch(['8.8.8.8', '1.1.1.1']);
|
|
62
|
+
* results.forEach(r => console.log(r.ip, r.geo?.country));
|
|
63
|
+
* ```
|
|
64
|
+
*/
|
|
65
|
+
lookupBatch(ips: string[]): Promise<(IPLookupResult | IPFinderError)[]>;
|
|
66
|
+
/**
|
|
67
|
+
* Check if the API is healthy and reachable
|
|
68
|
+
*
|
|
69
|
+
* @returns true if the API is healthy
|
|
70
|
+
*/
|
|
71
|
+
healthCheck(): Promise<boolean>;
|
|
72
|
+
private fetch;
|
|
73
|
+
private parseRateLimitHeaders;
|
|
74
|
+
private sleep;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Create a new IP Finder client instance
|
|
78
|
+
*
|
|
79
|
+
* @param config - Client configuration
|
|
80
|
+
* @returns New IPFinder instance
|
|
81
|
+
*
|
|
82
|
+
* @example
|
|
83
|
+
* ```ts
|
|
84
|
+
* import { createClient } from 'ip-finder-client';
|
|
85
|
+
*
|
|
86
|
+
* const client = createClient({
|
|
87
|
+
* apiKey: 'your-api-key',
|
|
88
|
+
* baseUrl: 'https://api.example.com'
|
|
89
|
+
* });
|
|
90
|
+
*
|
|
91
|
+
* const result = await client.lookup('8.8.8.8');
|
|
92
|
+
* ```
|
|
93
|
+
*/
|
|
94
|
+
export declare function createClient(config: IPFinderConfig): IPFinder;
|
|
95
|
+
export default IPFinder;
|
|
96
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,cAAc,EACd,cAAc,EACd,QAAQ,EACR,aAAa,EACb,WAAW,EACX,OAAO,EACP,WAAW,EACZ,MAAM,SAAS,CAAC;AAGjB,YAAY,EACV,cAAc,EACd,cAAc,EACd,QAAQ,EACR,aAAa,EACb,WAAW,EACX,OAAO,EACP,WAAW,GACZ,CAAC;AAMF;;GAEG;AACH,qBAAa,aAAc,SAAQ,KAAK;IACtC,SAAgB,UAAU,EAAE,MAAM,CAAC;IACnC,SAAgB,IAAI,CAAC,EAAE,MAAM,CAAC;gBAElB,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM;CAO/D;AAED;;;;;;;;;;;GAWG;AACH,qBAAa,QAAQ;IACnB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IAEjC,4CAA4C;IACrC,SAAS,CAAC,EAAE,aAAa,CAAC;gBAErB,MAAM,EAAE,cAAc;IAWlC;;;;;;;;;;;;;OAaG;IACG,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC;IA+BjD;;;;;OAKG;IACG,UAAU,IAAI,OAAO,CAAC,cAAc,CAAC;IAO3C;;;;;;;;;;;OAWG;IACG,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,CAAC,cAAc,GAAG,aAAa,CAAC,EAAE,CAAC;IAqB7E;;;;OAIG;IACG,WAAW,IAAI,OAAO,CAAC,OAAO,CAAC;YAgBvB,KAAK;IAuDnB,OAAO,CAAC,qBAAqB;IAc7B,OAAO,CAAC,KAAK;CAGd;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,cAAc,GAAG,QAAQ,CAE7D;AAGD,eAAe,QAAQ,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var src_exports = {};
|
|
22
|
+
__export(src_exports, {
|
|
23
|
+
IPFinder: () => IPFinder,
|
|
24
|
+
IPFinderError: () => IPFinderError,
|
|
25
|
+
createClient: () => createClient,
|
|
26
|
+
default: () => src_default
|
|
27
|
+
});
|
|
28
|
+
module.exports = __toCommonJS(src_exports);
|
|
29
|
+
var DEFAULT_BASE_URL = "https://your-api-domain.com";
|
|
30
|
+
var DEFAULT_TIMEOUT = 1e4;
|
|
31
|
+
var DEFAULT_RETRIES = 2;
|
|
32
|
+
var IPFinderError = class _IPFinderError extends Error {
|
|
33
|
+
statusCode;
|
|
34
|
+
code;
|
|
35
|
+
constructor(message, statusCode, code) {
|
|
36
|
+
super(message);
|
|
37
|
+
this.name = "IPFinderError";
|
|
38
|
+
this.statusCode = statusCode;
|
|
39
|
+
this.code = code;
|
|
40
|
+
Object.setPrototypeOf(this, _IPFinderError.prototype);
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
var IPFinder = class {
|
|
44
|
+
apiKey;
|
|
45
|
+
baseUrl;
|
|
46
|
+
timeout;
|
|
47
|
+
retries;
|
|
48
|
+
/** Rate limit info from the last request */
|
|
49
|
+
rateLimit;
|
|
50
|
+
constructor(config) {
|
|
51
|
+
if (!config.apiKey) {
|
|
52
|
+
throw new Error("API key is required");
|
|
53
|
+
}
|
|
54
|
+
this.apiKey = config.apiKey;
|
|
55
|
+
this.baseUrl = (config.baseUrl || DEFAULT_BASE_URL).replace(/\/$/, "");
|
|
56
|
+
this.timeout = config.timeout ?? DEFAULT_TIMEOUT;
|
|
57
|
+
this.retries = config.retries ?? DEFAULT_RETRIES;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Look up information for an IP address
|
|
61
|
+
*
|
|
62
|
+
* @param ip - IPv4 or IPv6 address to look up
|
|
63
|
+
* @returns IP lookup result with geo, ASN, and network information
|
|
64
|
+
* @throws {IPFinderError} When the API returns an error
|
|
65
|
+
*
|
|
66
|
+
* @example
|
|
67
|
+
* ```ts
|
|
68
|
+
* const result = await client.lookup('8.8.8.8');
|
|
69
|
+
* console.log(result.geo?.city); // "Mountain View"
|
|
70
|
+
* console.log(result.asn?.organization); // "Google LLC"
|
|
71
|
+
* ```
|
|
72
|
+
*/
|
|
73
|
+
async lookup(ip) {
|
|
74
|
+
if (!ip || typeof ip !== "string") {
|
|
75
|
+
throw new Error("IP address is required and must be a string");
|
|
76
|
+
}
|
|
77
|
+
const url = `${this.baseUrl}/v1/ip/${encodeURIComponent(ip.trim())}`;
|
|
78
|
+
let lastError = null;
|
|
79
|
+
for (let attempt = 0; attempt <= this.retries; attempt++) {
|
|
80
|
+
try {
|
|
81
|
+
const response = await this.fetch(url);
|
|
82
|
+
return response;
|
|
83
|
+
} catch (err) {
|
|
84
|
+
lastError = err;
|
|
85
|
+
if (err instanceof IPFinderError && err.statusCode >= 400 && err.statusCode < 500) {
|
|
86
|
+
throw err;
|
|
87
|
+
}
|
|
88
|
+
if (attempt < this.retries) {
|
|
89
|
+
await this.sleep(Math.pow(2, attempt) * 100);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
throw lastError || new Error("Request failed");
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Look up the current request's IP address (useful for "what's my IP" functionality)
|
|
97
|
+
* Note: This requires your API to support a special endpoint for this
|
|
98
|
+
*
|
|
99
|
+
* @returns IP lookup result for the requesting IP
|
|
100
|
+
*/
|
|
101
|
+
async lookupSelf() {
|
|
102
|
+
const url = `${this.baseUrl}/v1/ip/me`;
|
|
103
|
+
const response = await this.fetch(url);
|
|
104
|
+
return response;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Batch lookup multiple IP addresses
|
|
108
|
+
*
|
|
109
|
+
* @param ips - Array of IP addresses to look up
|
|
110
|
+
* @returns Array of lookup results (or errors for failed lookups)
|
|
111
|
+
*
|
|
112
|
+
* @example
|
|
113
|
+
* ```ts
|
|
114
|
+
* const results = await client.lookupBatch(['8.8.8.8', '1.1.1.1']);
|
|
115
|
+
* results.forEach(r => console.log(r.ip, r.geo?.country));
|
|
116
|
+
* ```
|
|
117
|
+
*/
|
|
118
|
+
async lookupBatch(ips) {
|
|
119
|
+
const results = await Promise.allSettled(
|
|
120
|
+
ips.map((ip) => this.lookup(ip))
|
|
121
|
+
);
|
|
122
|
+
return results.map((result, index) => {
|
|
123
|
+
if (result.status === "fulfilled") {
|
|
124
|
+
return result.value;
|
|
125
|
+
}
|
|
126
|
+
const err = result.reason;
|
|
127
|
+
if (err instanceof IPFinderError) {
|
|
128
|
+
return err;
|
|
129
|
+
}
|
|
130
|
+
return new IPFinderError(
|
|
131
|
+
err?.message || "Unknown error",
|
|
132
|
+
0,
|
|
133
|
+
"BATCH_ERROR"
|
|
134
|
+
);
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Check if the API is healthy and reachable
|
|
139
|
+
*
|
|
140
|
+
* @returns true if the API is healthy
|
|
141
|
+
*/
|
|
142
|
+
async healthCheck() {
|
|
143
|
+
try {
|
|
144
|
+
const controller = new AbortController();
|
|
145
|
+
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
146
|
+
const response = await fetch(`${this.baseUrl}/healthz`, {
|
|
147
|
+
signal: controller.signal
|
|
148
|
+
});
|
|
149
|
+
clearTimeout(timeoutId);
|
|
150
|
+
return response.ok;
|
|
151
|
+
} catch {
|
|
152
|
+
return false;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
async fetch(url) {
|
|
156
|
+
const controller = new AbortController();
|
|
157
|
+
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
158
|
+
try {
|
|
159
|
+
const response = await fetch(url, {
|
|
160
|
+
method: "GET",
|
|
161
|
+
headers: {
|
|
162
|
+
"X-API-Key": this.apiKey,
|
|
163
|
+
"Accept": "application/json",
|
|
164
|
+
"User-Agent": "ip-finder-client/1.0.0"
|
|
165
|
+
},
|
|
166
|
+
signal: controller.signal
|
|
167
|
+
});
|
|
168
|
+
clearTimeout(timeoutId);
|
|
169
|
+
this.parseRateLimitHeaders(response.headers);
|
|
170
|
+
if (!response.ok) {
|
|
171
|
+
let errorBody = null;
|
|
172
|
+
try {
|
|
173
|
+
errorBody = await response.json();
|
|
174
|
+
} catch {
|
|
175
|
+
}
|
|
176
|
+
throw new IPFinderError(
|
|
177
|
+
errorBody?.error || `HTTP ${response.status}`,
|
|
178
|
+
response.status,
|
|
179
|
+
errorBody?.code
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
const data = await response.json();
|
|
183
|
+
return data;
|
|
184
|
+
} catch (err) {
|
|
185
|
+
clearTimeout(timeoutId);
|
|
186
|
+
if (err instanceof IPFinderError) {
|
|
187
|
+
throw err;
|
|
188
|
+
}
|
|
189
|
+
if (err instanceof Error) {
|
|
190
|
+
if (err.name === "AbortError") {
|
|
191
|
+
throw new IPFinderError("Request timeout", 408, "TIMEOUT");
|
|
192
|
+
}
|
|
193
|
+
throw new IPFinderError(err.message, 0, "NETWORK_ERROR");
|
|
194
|
+
}
|
|
195
|
+
throw new IPFinderError("Unknown error", 0, "UNKNOWN");
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
parseRateLimitHeaders(headers) {
|
|
199
|
+
const limit = headers.get("X-RateLimit-Limit");
|
|
200
|
+
const remaining = headers.get("X-RateLimit-Remaining");
|
|
201
|
+
const reset = headers.get("X-RateLimit-Reset");
|
|
202
|
+
if (limit && remaining && reset) {
|
|
203
|
+
this.rateLimit = {
|
|
204
|
+
limit: parseInt(limit, 10),
|
|
205
|
+
remaining: parseInt(remaining, 10),
|
|
206
|
+
reset: parseInt(reset, 10)
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
sleep(ms) {
|
|
211
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
212
|
+
}
|
|
213
|
+
};
|
|
214
|
+
function createClient(config) {
|
|
215
|
+
return new IPFinder(config);
|
|
216
|
+
}
|
|
217
|
+
var src_default = IPFinder;
|
|
218
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
219
|
+
0 && (module.exports = {
|
|
220
|
+
IPFinder,
|
|
221
|
+
IPFinderError,
|
|
222
|
+
createClient
|
|
223
|
+
});
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
var DEFAULT_BASE_URL = "https://your-api-domain.com";
|
|
3
|
+
var DEFAULT_TIMEOUT = 1e4;
|
|
4
|
+
var DEFAULT_RETRIES = 2;
|
|
5
|
+
var IPFinderError = class _IPFinderError extends Error {
|
|
6
|
+
statusCode;
|
|
7
|
+
code;
|
|
8
|
+
constructor(message, statusCode, code) {
|
|
9
|
+
super(message);
|
|
10
|
+
this.name = "IPFinderError";
|
|
11
|
+
this.statusCode = statusCode;
|
|
12
|
+
this.code = code;
|
|
13
|
+
Object.setPrototypeOf(this, _IPFinderError.prototype);
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
var IPFinder = class {
|
|
17
|
+
apiKey;
|
|
18
|
+
baseUrl;
|
|
19
|
+
timeout;
|
|
20
|
+
retries;
|
|
21
|
+
/** Rate limit info from the last request */
|
|
22
|
+
rateLimit;
|
|
23
|
+
constructor(config) {
|
|
24
|
+
if (!config.apiKey) {
|
|
25
|
+
throw new Error("API key is required");
|
|
26
|
+
}
|
|
27
|
+
this.apiKey = config.apiKey;
|
|
28
|
+
this.baseUrl = (config.baseUrl || DEFAULT_BASE_URL).replace(/\/$/, "");
|
|
29
|
+
this.timeout = config.timeout ?? DEFAULT_TIMEOUT;
|
|
30
|
+
this.retries = config.retries ?? DEFAULT_RETRIES;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Look up information for an IP address
|
|
34
|
+
*
|
|
35
|
+
* @param ip - IPv4 or IPv6 address to look up
|
|
36
|
+
* @returns IP lookup result with geo, ASN, and network information
|
|
37
|
+
* @throws {IPFinderError} When the API returns an error
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* ```ts
|
|
41
|
+
* const result = await client.lookup('8.8.8.8');
|
|
42
|
+
* console.log(result.geo?.city); // "Mountain View"
|
|
43
|
+
* console.log(result.asn?.organization); // "Google LLC"
|
|
44
|
+
* ```
|
|
45
|
+
*/
|
|
46
|
+
async lookup(ip) {
|
|
47
|
+
if (!ip || typeof ip !== "string") {
|
|
48
|
+
throw new Error("IP address is required and must be a string");
|
|
49
|
+
}
|
|
50
|
+
const url = `${this.baseUrl}/v1/ip/${encodeURIComponent(ip.trim())}`;
|
|
51
|
+
let lastError = null;
|
|
52
|
+
for (let attempt = 0; attempt <= this.retries; attempt++) {
|
|
53
|
+
try {
|
|
54
|
+
const response = await this.fetch(url);
|
|
55
|
+
return response;
|
|
56
|
+
} catch (err) {
|
|
57
|
+
lastError = err;
|
|
58
|
+
if (err instanceof IPFinderError && err.statusCode >= 400 && err.statusCode < 500) {
|
|
59
|
+
throw err;
|
|
60
|
+
}
|
|
61
|
+
if (attempt < this.retries) {
|
|
62
|
+
await this.sleep(Math.pow(2, attempt) * 100);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
throw lastError || new Error("Request failed");
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Look up the current request's IP address (useful for "what's my IP" functionality)
|
|
70
|
+
* Note: This requires your API to support a special endpoint for this
|
|
71
|
+
*
|
|
72
|
+
* @returns IP lookup result for the requesting IP
|
|
73
|
+
*/
|
|
74
|
+
async lookupSelf() {
|
|
75
|
+
const url = `${this.baseUrl}/v1/ip/me`;
|
|
76
|
+
const response = await this.fetch(url);
|
|
77
|
+
return response;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Batch lookup multiple IP addresses
|
|
81
|
+
*
|
|
82
|
+
* @param ips - Array of IP addresses to look up
|
|
83
|
+
* @returns Array of lookup results (or errors for failed lookups)
|
|
84
|
+
*
|
|
85
|
+
* @example
|
|
86
|
+
* ```ts
|
|
87
|
+
* const results = await client.lookupBatch(['8.8.8.8', '1.1.1.1']);
|
|
88
|
+
* results.forEach(r => console.log(r.ip, r.geo?.country));
|
|
89
|
+
* ```
|
|
90
|
+
*/
|
|
91
|
+
async lookupBatch(ips) {
|
|
92
|
+
const results = await Promise.allSettled(
|
|
93
|
+
ips.map((ip) => this.lookup(ip))
|
|
94
|
+
);
|
|
95
|
+
return results.map((result, index) => {
|
|
96
|
+
if (result.status === "fulfilled") {
|
|
97
|
+
return result.value;
|
|
98
|
+
}
|
|
99
|
+
const err = result.reason;
|
|
100
|
+
if (err instanceof IPFinderError) {
|
|
101
|
+
return err;
|
|
102
|
+
}
|
|
103
|
+
return new IPFinderError(
|
|
104
|
+
err?.message || "Unknown error",
|
|
105
|
+
0,
|
|
106
|
+
"BATCH_ERROR"
|
|
107
|
+
);
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Check if the API is healthy and reachable
|
|
112
|
+
*
|
|
113
|
+
* @returns true if the API is healthy
|
|
114
|
+
*/
|
|
115
|
+
async healthCheck() {
|
|
116
|
+
try {
|
|
117
|
+
const controller = new AbortController();
|
|
118
|
+
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
119
|
+
const response = await fetch(`${this.baseUrl}/healthz`, {
|
|
120
|
+
signal: controller.signal
|
|
121
|
+
});
|
|
122
|
+
clearTimeout(timeoutId);
|
|
123
|
+
return response.ok;
|
|
124
|
+
} catch {
|
|
125
|
+
return false;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
async fetch(url) {
|
|
129
|
+
const controller = new AbortController();
|
|
130
|
+
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
131
|
+
try {
|
|
132
|
+
const response = await fetch(url, {
|
|
133
|
+
method: "GET",
|
|
134
|
+
headers: {
|
|
135
|
+
"X-API-Key": this.apiKey,
|
|
136
|
+
"Accept": "application/json",
|
|
137
|
+
"User-Agent": "ip-finder-client/1.0.0"
|
|
138
|
+
},
|
|
139
|
+
signal: controller.signal
|
|
140
|
+
});
|
|
141
|
+
clearTimeout(timeoutId);
|
|
142
|
+
this.parseRateLimitHeaders(response.headers);
|
|
143
|
+
if (!response.ok) {
|
|
144
|
+
let errorBody = null;
|
|
145
|
+
try {
|
|
146
|
+
errorBody = await response.json();
|
|
147
|
+
} catch {
|
|
148
|
+
}
|
|
149
|
+
throw new IPFinderError(
|
|
150
|
+
errorBody?.error || `HTTP ${response.status}`,
|
|
151
|
+
response.status,
|
|
152
|
+
errorBody?.code
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
const data = await response.json();
|
|
156
|
+
return data;
|
|
157
|
+
} catch (err) {
|
|
158
|
+
clearTimeout(timeoutId);
|
|
159
|
+
if (err instanceof IPFinderError) {
|
|
160
|
+
throw err;
|
|
161
|
+
}
|
|
162
|
+
if (err instanceof Error) {
|
|
163
|
+
if (err.name === "AbortError") {
|
|
164
|
+
throw new IPFinderError("Request timeout", 408, "TIMEOUT");
|
|
165
|
+
}
|
|
166
|
+
throw new IPFinderError(err.message, 0, "NETWORK_ERROR");
|
|
167
|
+
}
|
|
168
|
+
throw new IPFinderError("Unknown error", 0, "UNKNOWN");
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
parseRateLimitHeaders(headers) {
|
|
172
|
+
const limit = headers.get("X-RateLimit-Limit");
|
|
173
|
+
const remaining = headers.get("X-RateLimit-Remaining");
|
|
174
|
+
const reset = headers.get("X-RateLimit-Reset");
|
|
175
|
+
if (limit && remaining && reset) {
|
|
176
|
+
this.rateLimit = {
|
|
177
|
+
limit: parseInt(limit, 10),
|
|
178
|
+
remaining: parseInt(remaining, 10),
|
|
179
|
+
reset: parseInt(reset, 10)
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
sleep(ms) {
|
|
184
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
185
|
+
}
|
|
186
|
+
};
|
|
187
|
+
function createClient(config) {
|
|
188
|
+
return new IPFinder(config);
|
|
189
|
+
}
|
|
190
|
+
var src_default = IPFinder;
|
|
191
|
+
export {
|
|
192
|
+
IPFinder,
|
|
193
|
+
IPFinderError,
|
|
194
|
+
createClient,
|
|
195
|
+
src_default as default
|
|
196
|
+
};
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration options for the IP Finder client
|
|
3
|
+
*/
|
|
4
|
+
export interface IPFinderConfig {
|
|
5
|
+
/** Your API key for authentication */
|
|
6
|
+
apiKey: string;
|
|
7
|
+
/** Base URL of the IP Finder API (optional, defaults to production) */
|
|
8
|
+
baseUrl?: string;
|
|
9
|
+
/** Request timeout in milliseconds (default: 10000) */
|
|
10
|
+
timeout?: number;
|
|
11
|
+
/** Number of retry attempts for failed requests (default: 2) */
|
|
12
|
+
retries?: number;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Geographic location information
|
|
16
|
+
*/
|
|
17
|
+
export interface GeoLocation {
|
|
18
|
+
city?: string;
|
|
19
|
+
region?: string;
|
|
20
|
+
regionCode?: string;
|
|
21
|
+
country?: string;
|
|
22
|
+
countryCode?: string;
|
|
23
|
+
continent?: string;
|
|
24
|
+
continentCode?: string;
|
|
25
|
+
latitude?: number;
|
|
26
|
+
longitude?: number;
|
|
27
|
+
timezone?: string;
|
|
28
|
+
postalCode?: string;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* ASN (Autonomous System Number) information
|
|
32
|
+
*/
|
|
33
|
+
export interface ASNInfo {
|
|
34
|
+
asn?: number;
|
|
35
|
+
organization?: string;
|
|
36
|
+
isp?: string;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Network allocation information
|
|
40
|
+
*/
|
|
41
|
+
export interface NetworkInfo {
|
|
42
|
+
cidr?: string;
|
|
43
|
+
startIp?: string;
|
|
44
|
+
endIp?: string;
|
|
45
|
+
type?: string;
|
|
46
|
+
registry?: string;
|
|
47
|
+
registryCountry?: string;
|
|
48
|
+
allocatedDate?: string;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Complete IP lookup result
|
|
52
|
+
*/
|
|
53
|
+
export interface IPLookupResult {
|
|
54
|
+
ip: string;
|
|
55
|
+
geo?: GeoLocation;
|
|
56
|
+
asn?: ASNInfo;
|
|
57
|
+
network?: NetworkInfo;
|
|
58
|
+
isPrivate?: boolean;
|
|
59
|
+
isReserved?: boolean;
|
|
60
|
+
version?: 4 | 6;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Error response from the API
|
|
64
|
+
*/
|
|
65
|
+
export interface APIError {
|
|
66
|
+
error: string;
|
|
67
|
+
code?: string;
|
|
68
|
+
statusCode: number;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Rate limit information returned in response headers
|
|
72
|
+
*/
|
|
73
|
+
export interface RateLimitInfo {
|
|
74
|
+
limit: number;
|
|
75
|
+
remaining: number;
|
|
76
|
+
reset: number;
|
|
77
|
+
}
|
|
78
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,sCAAsC;IACtC,MAAM,EAAE,MAAM,CAAC;IACf,uEAAuE;IACvE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,uDAAuD;IACvD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,gEAAgE;IAChE,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;GAEG;AACH,MAAM,WAAW,OAAO;IACtB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,GAAG,CAAC,EAAE,WAAW,CAAC;IAClB,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,OAAO,CAAC,EAAE,WAAW,CAAC;IACtB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,OAAO,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC;CACjB;AAED;;GAEG;AACH,MAAM,WAAW,QAAQ;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;CACpB;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;CACf"}
|
package/package.json
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "ip-finder-client",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Lightweight client SDK for IP Finder API - Get IP geolocation, ASN, and network information",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"module": "dist/index.mjs",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"require": "./dist/index.js",
|
|
11
|
+
"import": "./dist/index.mjs",
|
|
12
|
+
"types": "./dist/index.d.ts"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist"
|
|
17
|
+
],
|
|
18
|
+
"scripts": {
|
|
19
|
+
"build": "npm run build:cjs && npm run build:esm && npm run build:types",
|
|
20
|
+
"build:cjs": "esbuild src/index.ts --bundle --platform=node --target=node18 --outfile=dist/index.js --format=cjs",
|
|
21
|
+
"build:esm": "esbuild src/index.ts --bundle --platform=node --target=node18 --outfile=dist/index.mjs --format=esm",
|
|
22
|
+
"build:types": "tsc --emitDeclarationOnly --declaration --outDir dist",
|
|
23
|
+
"prepublishOnly": "npm run build",
|
|
24
|
+
"test": "node test/test.js"
|
|
25
|
+
},
|
|
26
|
+
"keywords": [
|
|
27
|
+
"ip",
|
|
28
|
+
"geolocation",
|
|
29
|
+
"ip-lookup",
|
|
30
|
+
"geoip",
|
|
31
|
+
"asn",
|
|
32
|
+
"whois",
|
|
33
|
+
"ip-address",
|
|
34
|
+
"network",
|
|
35
|
+
"ipv4",
|
|
36
|
+
"ipv6"
|
|
37
|
+
],
|
|
38
|
+
"author": "",
|
|
39
|
+
"license": "MIT",
|
|
40
|
+
"repository": {
|
|
41
|
+
"type": "git",
|
|
42
|
+
"url": ""
|
|
43
|
+
},
|
|
44
|
+
"engines": {
|
|
45
|
+
"node": ">=18.0.0"
|
|
46
|
+
},
|
|
47
|
+
"devDependencies": {
|
|
48
|
+
"@types/node": "^25.0.3",
|
|
49
|
+
"esbuild": "^0.20.0",
|
|
50
|
+
"typescript": "^5.3.0"
|
|
51
|
+
}
|
|
52
|
+
}
|