Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
# Changelog

## 3.0.1

- Hide API keys when printing clients and settings, and in request errors and typed bulk error messages.
- Stop redirects from sending lookup data or custom headers to another server.
- Keep the HTTP status if an error response cannot be read. Reject missing or extra rows in typed bulk results.
- Add Arabic (`ar`) for single and bulk lookups. Include CDN, proxy, bot and corporate gateway details in typed results.
- Fix TypeScript imports in CommonJS projects. Preserve names such as `__proto__` in headers and JSON output.

If your code handles every language separately, add a case for `ar`.

## 3.0.0

- Initial TypeScript SDK release for `/v3/ipgeo` and `/v3/ipgeo-bulk`
Expand Down
10 changes: 6 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,8 @@ Paid plans still need `include` for optional modules. `fields` and `excludes` on

You can also pass a custom `HttpTransport` as the second constructor argument.

If you set `baseUrl`, use the final API address. In Node.js, the built-in client follows redirects only within the same protocol, host and port. Browsers hide the redirect address, so the client stops at a redirect.

## Available Methods

| Method | Returns | Notes |
Expand All @@ -209,7 +211,7 @@ You can also pass a custom `HttpTransport` as the second constructor argument.
|--------|------------|-------|
| `ip` | Single lookup | IPv4, IPv6, or domain. Omit it for caller IP lookup. Domain lookup requires a paid plan. |
| `ips` | Bulk lookup | Iterable of 1 to 50,000 IPs or domains. |
| `lang` | Single and bulk | One of `en`, `de`, `ru`, `ja`, `fr`, `cn`, `es`, `cs`, `it`, `ko`, `fa`, `pt`. |
| `lang` | Single and bulk | One of `en`, `de`, `ru`, `ja`, `fr`, `cn`, `es`, `cs`, `it`, `ko`, `fa`, `pt`, `ar` (Arabic). |
| `include` | Single and bulk | Iterable of module names such as `security`, `abuse`, `user_agent`, `hostname`, `liveHostname`, `hostnameFallbackLive`, `geo_accuracy`, `dma_code`, or `*`. |
| `fields` | Single and bulk | Iterable of field paths to keep, for example `["location.country_name", "security.threat_score"]`. |
| `excludes` | Single and bulk | Iterable of field paths to remove from the response. |
Expand Down Expand Up @@ -413,7 +415,7 @@ for (const result of response.data) {

### Success and Error Results

Each bulk result is either a success with `data` or an error with `error.message`. TypeScript narrows it automatically on `result.success`.
Each bulk result is either a success with `data` or an error with `error.message`. TypeScript narrows it automatically on `result.success`. If the API sends a different number of results than you requested, the typed lookup throws `SerializationError`. The raw lookup returns the response as received.

```ts
const response = await client.bulkLookupIpGeolocation({
Expand Down Expand Up @@ -465,7 +467,7 @@ console.log(response.metadata.firstHeaderValue("x-credits-charged")); // the sam

## JSON Helpers

Use `toJson()` or `toPrettyJson()` when you want a stable JSON view of SDK objects.
Use `toJson()` or `toPrettyJson()` to print SDK objects as JSON. When you print a client or its settings as JSON, the SDK shows `[REDACTED]` in place of your API key. `config.apiKey` still gives you the key.

```ts
import { toPrettyJson } from "ip-geolocation-api-sdk-typescript";
Expand All @@ -479,7 +481,7 @@ console.log(toPrettyJson(response.data));

## Error Handling

All SDK errors extend `IpGeolocationError`.
All SDK errors extend `IpGeolocationError`. If an API error includes your key, the SDK hides it. This also applies to error messages in typed bulk results. If the SDK receives an HTTP status but can’t read the error body, you still get the status code.

Runtime and validation errors:

Expand Down
1 change: 1 addition & 0 deletions index.d.mts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./dist/index.cjs";
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 10 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"author": "IPGeolocation.io",
"name": "ip-geolocation-api-sdk-typescript",
"version": "3.0.0",
"version": "3.0.1",
"description": "Official TypeScript SDK for the IPGeolocation IP Location API with single and bulk lookup, typed JSON, and raw JSON/XML support.",
"license": "MIT",
"type": "module",
Expand All @@ -10,14 +10,20 @@
"types": "./dist/index.d.ts",
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js",
"require": "./dist/index.cjs"
"import": {
"types": "./index.d.mts",
"default": "./dist/index.js"
},
"require": {
"types": "./dist/index.d.cts",
"default": "./dist/index.cjs"
}
},
"./package.json": "./package.json"
},
"files": [
"dist",
"index.d.mts",
"README.md",
"CHANGELOG.md",
"LICENSE"
Expand Down
6 changes: 6 additions & 0 deletions packages/types/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog

## 3.0.1

- Add Arabic (`ar`) and the CDN, proxy, bot and corporate gateway response fields.
- Fix TypeScript imports in CommonJS projects.
- Correct the lookup and bulk examples and fix the GitHub link.

## 3.0.0

- Initial v3 release of the public TypeScript types package.
Expand Down
75 changes: 17 additions & 58 deletions packages/types/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,74 +103,33 @@ This package is type declarations plus minimal JS stubs for package resolution.

## Quick Start

This example uses your own `fetch` wrapper and the public request and response types from this package.
This Node.js example requests the country name with `fetch`. The API sends `country_name`, so the example copies it to the SDK’s `countryName` field.

```ts
import type {
ApiResponse,
IpGeolocationResponse,
LookupIpGeolocationRequestInit,
} from "ip-geolocation-api-typescript-types";

async function lookupIp(
request: LookupIpGeolocationRequestInit,
): Promise<ApiResponse<IpGeolocationResponse>> {
const url = new URL("https://api.ipgeolocation.io/v3/ipgeo");

url.searchParams.set("apiKey", process.env.IPGEO_API_KEY!);

if (request.ip) {
url.searchParams.set("ip", request.ip);
}

if (request.lang) {
url.searchParams.set("lang", request.lang);
}

if (request.include) {
url.searchParams.set("include", [...request.include].join(","));
}
import type { IpGeolocationResponse } from "ip-geolocation-api-typescript-types";

if (request.fields) {
url.searchParams.set("fields", [...request.fields].join(","));
}
async function lookupCountry(ip: string): Promise<IpGeolocationResponse> {
const apiKey = process.env.IPGEO_API_KEY;
if (!apiKey) throw new Error("Set IPGEO_API_KEY before making a lookup.");

if (request.excludes) {
url.searchParams.set("excludes", [...request.excludes].join(","));
}
const url = new URL("https://api.ipgeolocation.io/v3/ipgeo");
url.searchParams.set("apiKey", apiKey);
url.searchParams.set("ip", ip);
url.searchParams.set("fields", "location.country_name");

if (request.output) {
url.searchParams.set("output", request.output);
}
const response = await fetch(url, { redirect: "error" });
if (!response.ok) throw new Error(`Lookup failed (HTTP ${response.status}).`);

const response = await fetch(url.toString(), {
// Adapt request.headers here if your wrapper supports custom headers.
});

const data = (await response.json()) as IpGeolocationResponse;

return {
data,
metadata: {
statusCode: response.status,
durationMs: 0,
creditsCharged: undefined,
successfulRecords: undefined,
rawHeaders: {},
headerValues() {
return [];
},
firstHeaderValue() {
return undefined;
},
},
};
const body = await response.json();
const countryName: unknown = body?.location?.country_name;
if (typeof countryName !== "string") throw new Error("No country name in the response.");
return { location: { countryName } };
}
```

## Bulk Result Typing

Bulk lookups return a mixed array of success and error results. `BulkLookupResult` matches that shape.
Use this function with rows returned by the SDK’s typed bulk lookup. If you use `fetch` yourself, convert each successful row’s field names and wrap it as `{ success: true, data }`. Wrap each error as `{ success: false, error: { message } }`.

```ts
import type { BulkLookupResult } from "ip-geolocation-api-typescript-types";
Expand Down Expand Up @@ -225,7 +184,7 @@ const headers: RequestHeaders = [
## Links

- [npm package](https://www.npmjs.com/package/ip-geolocation-api-typescript-types)
- [GitHub repository](https://github.com/IPGeolocation/ip-geolocation-api-typescript-sdk/tree/main/packages/types)
- [GitHub repository](https://github.com/IPGeolocation/ip-geolocation-api-typescript-sdk/tree/master/packages/types)
- [API documentation](https://ipgeolocation.io/documentation/ip-location-api.html)
- [Pricing](https://ipgeolocation.io/pricing.html)
- [Authentication](https://ipgeolocation.io/documentation/api-authentication.html)
4 changes: 2 additions & 2 deletions packages/types/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 10 additions & 5 deletions packages/types/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"author": "IPGeolocation.io",
"name": "ip-geolocation-api-typescript-types",
"version": "3.0.0",
"version": "3.0.1",
"description": "Public TypeScript types for the IPGeolocation IP Location API v3 SDKs.",
"license": "MIT",
"type": "module",
Expand All @@ -10,9 +10,14 @@
"types": "./dist/index.d.ts",
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js",
"require": "./dist/index.cjs"
"import": {
"types": "./dist/index.d.ts",
"default": "./dist/index.js"
},
"require": {
"types": "./dist/index.d.cts",
"default": "./dist/index.cjs"
}
},
"./package.json": "./package.json"
},
Expand All @@ -33,7 +38,7 @@
"bugs": {
"url": "https://github.com/IPGeolocation/ip-geolocation-api-typescript-sdk/issues"
},
"homepage": "https://github.com/IPGeolocation/ip-geolocation-api-typescript-sdk/tree/main/packages/types",
"homepage": "https://github.com/IPGeolocation/ip-geolocation-api-typescript-sdk/tree/master/packages/types",
"keywords": [
"ipgeolocation",
"geolocation",
Expand Down
16 changes: 15 additions & 1 deletion packages/types/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ export type Language =
| "it"
| "ko"
| "fa"
| "pt";
| "pt"
| "ar";

export type ResponseFormat = "json" | "xml";

Expand Down Expand Up @@ -155,6 +156,8 @@ export interface Network {
connectionType?: string;
route?: string;
isAnycast?: boolean;
isCdn?: boolean;
cdnProviderName?: string;
}

export interface Security {
Expand All @@ -164,6 +167,9 @@ export interface Security {
proxyProviderNames?: readonly string[];
proxyConfidenceScore?: number;
proxyLastSeen?: string;
proxyDaysSeen7d?: number;
proxyDaysSeen30d?: number;
proxyProviderCount30d?: number;
isResidentialProxy?: boolean;
isVpn?: boolean;
vpnProviderNames?: readonly string[];
Expand All @@ -174,6 +180,14 @@ export interface Security {
isAnonymous?: boolean;
isKnownAttacker?: boolean;
isBot?: boolean;
botConfidenceScore?: number;
botOperatorName?: string;
botType?: string;
botLastSeen?: string;
isKnownGoodBot?: boolean;
isCorporateGateway?: boolean;
corporateGatewayType?: string;
corporateGatewayProviderName?: string;
isSpam?: boolean;
isCloudProvider?: boolean;
cloudProviderName?: string;
Expand Down
38 changes: 35 additions & 3 deletions scripts/check-packages.cjs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
const assert = require('node:assert/strict');
const { access } = require('node:fs/promises');
const { access, copyFile, mkdir, mkdtemp, rm, symlink } = require('node:fs/promises');
const { execFileSync } = require('node:child_process');
const { tmpdir } = require('node:os');
const { createRequire } = require('node:module');
const path = require('node:path');
const { pathToFileURL } = require('node:url');
Expand All @@ -9,14 +11,19 @@ async function main() {
const esm = await import(pkg.name);
const cjs = require(pkg.name);
assert.deepEqual(Object.keys(esm).sort(), Object.keys(cjs).sort());
assert.equal(esm.getVersion(), cjs.getVersion());
assert.equal(esm.getVersion(), pkg.version);
assert.equal(cjs.getVersion(), pkg.version);
await access(path.resolve(__dirname, '..', pkg.types));
await access(path.resolve(__dirname, '..', 'dist/index.d.cts'));

for (const sdk of [esm, cjs]) {
const config = new sdk.IpGeolocationClientConfig({ apiKey: 'package-test' });
assert.equal(config.baseUrl, 'https://api.ipgeolocation.io');
assert.equal(Object.isFrozen(config), true);
for (const output of [esm.toJson(config), cjs.toPrettyJson(config), JSON.stringify(config)]) {
assert.equal(JSON.parse(output).apiKey, '[REDACTED]');
}
assert.equal(sdk.IpGeolocationClient.defaultUserAgent(), 'ipgeolocation-typescript-sdk/' + pkg.version);
const request = new sdk.LookupIpGeolocationRequest({ ip: '2001:db8::1' });
assert.equal(request.ip, '2001:db8::1');
assert.equal(request.output, sdk.ResponseFormat.JSON);
Expand All @@ -29,12 +36,37 @@ async function main() {
const typesManifest = path.join(typesRoot, 'package.json');
const typesPkg = require(typesManifest);
const typesCjs = createRequire(typesManifest)(typesPkg.name);
const typesEsm = await import(pathToFileURL(path.join(typesRoot, typesPkg.exports['.'].import)).href);
const typesEsm = await import(pathToFileURL(path.join(typesRoot, typesPkg.exports['.'].import.default)).href);
assert.deepEqual(Object.keys(typesEsm), []);
assert.deepEqual(Object.keys(typesCjs), []);
await access(path.join(typesRoot, typesPkg.types));
await access(path.join(typesRoot, 'dist/index.d.cts'));
assert.equal(typesPkg.version, pkg.version);
await checkConsumerTypes(pkg, typesPkg, typesRoot);
console.log(`SDK and types package ESM, CommonJS and declarations checked on ${process.version}`);
}

async function checkConsumerTypes(sdk, types, typesRoot) {
const consumer = await mkdtemp(path.join(tmpdir(), 'ipgeo-types-'));
try {
const modules = path.join(consumer, 'node_modules');
await mkdir(modules);
const root = path.resolve(__dirname, '..');
await symlink(root, path.join(modules, sdk.name), 'junction');
await symlink(typesRoot, path.join(modules, types.name), 'junction');
const fixture = path.join(root, 'tests/package-consumer.ts');
for (const file of ['consumer.cts', 'consumer.mts']) {
await copyFile(fixture, path.join(consumer, file));
}
await copyFile(path.join(root, 'tests/package-esm.mts'), path.join(consumer, 'package-esm.mts'));
for (const mode of ['Node16', 'NodeNext']) {
execFileSync(process.execPath, [require.resolve('typescript/bin/tsc'), '--strict', '--noEmit',
'--target', 'ES2020', '--module', mode, '--moduleResolution', mode, 'consumer.cts', 'consumer.mts', 'package-esm.mts'],
{ cwd: consumer, stdio: 'inherit' });
}
} finally {
await rm(consumer, { recursive: true, force: true });
}
}

main().catch((error) => { console.error(error); process.exitCode = 1; });
Loading