diff --git a/__tests__/fixtures/resproxy/175.107.211.204.json b/__tests__/fixtures/resproxy/175.107.211.204.json new file mode 100644 index 0000000..c1c2168 --- /dev/null +++ b/__tests__/fixtures/resproxy/175.107.211.204.json @@ -0,0 +1,6 @@ +{ + "ip": "175.107.211.204", + "last_seen": "2026-01-14", + "percent_days_seen": 50, + "service": "DataImpulse" +} diff --git a/__tests__/fixtures/resproxy/8.8.8.8.json b/__tests__/fixtures/resproxy/8.8.8.8.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/__tests__/fixtures/resproxy/8.8.8.8.json @@ -0,0 +1 @@ +{} diff --git a/__tests__/ipinfoWrapper.test.ts b/__tests__/ipinfoWrapper.test.ts index e5b6ad1..1b32794 100644 --- a/__tests__/ipinfoWrapper.test.ts +++ b/__tests__/ipinfoWrapper.test.ts @@ -1,5 +1,5 @@ import * as dotenv from "dotenv"; -import { AsnResponse, IPinfo } from "../src/common"; +import { AsnResponse, IPinfo, Resproxy } from "../src/common"; import IPinfoWrapper from "../src/ipinfoWrapper"; let ipinfoWrapper: IPinfoWrapper; @@ -195,6 +195,24 @@ describe("IPinfoWrapper", () => { expect(data.bogon).toEqual(true); }); + test("lookupResproxy", async () => { + // test multiple times for cache. + for (let i = 0; i < 5; i++) { + const data: Resproxy = await ipinfoWrapper.lookupResproxy( + "175.107.211.204" + ); + expect(data.ip).toEqual("175.107.211.204"); + expect(data.last_seen).toEqual("2026-01-14"); + expect(data.percent_days_seen).toEqual(50); + expect(data.service).toEqual("DataImpulse"); + } + }); + + test("lookupResproxyEmpty", async () => { + const data: Resproxy = await ipinfoWrapper.lookupResproxy("8.8.8.8"); + expect(data).toEqual({}); + }); + test("Error is thrown for invalid token", async () => { const ipinfo = new IPinfoWrapper("invalid-token"); await expect(ipinfo.lookupIp("1.2.3.4")).rejects.toThrow(); diff --git a/__tests__/jest.setup.js b/__tests__/jest.setup.js index 0f50d87..ed5fe67 100644 --- a/__tests__/jest.setup.js +++ b/__tests__/jest.setup.js @@ -32,6 +32,22 @@ module.exports = async function globalSetup() { method: "POST", path: "/tools/map", response: path.resolve(__dirname, "./fixtures/tools/map.json") + }, + { + method: "GET", + path: "/resproxy/175.107.211.204", + response: path.resolve( + __dirname, + "./fixtures/resproxy/175.107.211.204.json" + ) + }, + { + method: "GET", + path: "/resproxy/8.8.8.8", + response: path.resolve( + __dirname, + "./fixtures/resproxy/8.8.8.8.json" + ) } ] }; diff --git a/package-lock.json b/package-lock.json index 90cff04..ac9514e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -69,7 +69,6 @@ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.0.tgz", "integrity": "sha512-i1SLeK+DzNnQ3LL/CswPCa/E5u4lh1k6IAEphON8F+cXt0t9euTshDru0q7/IqMa1PMPz5RnHuHscF8/ZJsStg==", "dev": true, - "peer": true, "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.26.0", @@ -1334,7 +1333,6 @@ "url": "https://github.com/sponsors/ai" } ], - "peer": true, "dependencies": { "caniuse-lite": "^1.0.30001669", "electron-to-chromium": "^1.5.41", @@ -4817,7 +4815,6 @@ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz", "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==", "dev": true, - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" diff --git a/src/common.ts b/src/common.ts index bade933..d6c1e23 100644 --- a/src/common.ts +++ b/src/common.ts @@ -2,6 +2,7 @@ export const HOST: string = "ipinfo.io"; export const HOST_LITE: string = "api.ipinfo.io/lite"; export const HOST_CORE: string = "api.ipinfo.io/lookup"; export const HOST_PLUS: string = "api.ipinfo.io/lookup"; +export const HOST_RESPROXY: string = "ipinfo.io/resproxy"; // cache version export const CACHE_VSN: string = "1"; @@ -244,6 +245,13 @@ export interface MapResponse { reportUrl: string; } +export interface Resproxy { + ip: string; + last_seen: string; + percent_days_seen: number; + service: string; +} + export interface BatchResponse { [key: string]: | IPinfo diff --git a/src/index.ts b/src/index.ts index dd9a167..08fb8bb 100644 --- a/src/index.ts +++ b/src/index.ts @@ -31,7 +31,8 @@ export { Prefixes6, AsnResponse, MapResponse, - BatchResponse + BatchResponse, + Resproxy } from "./common"; export default IPinfoWrapper; diff --git a/src/ipinfoWrapper.ts b/src/ipinfoWrapper.ts index 7baa88b..7279b80 100644 --- a/src/ipinfoWrapper.ts +++ b/src/ipinfoWrapper.ts @@ -16,6 +16,7 @@ import { AsnResponse, MapResponse, BatchResponse, + Resproxy, BATCH_MAX_SIZE, BATCH_REQ_TIMEOUT_DEFAULT, REQUEST_TIMEOUT_DEFAULT, @@ -215,6 +216,26 @@ export default class IPinfoWrapper { }); } + /** + * Lookup residential proxy information using the IP. + * + * @param ip IP address to check for residential proxy information. + * @return Response containing Resproxy data if the IP is a residential proxy. + */ + public async lookupResproxy(ip: string): Promise { + const cacheKey = `resproxy:${ip}`; + const data = await this.cache.get(IPinfoWrapper.cacheKey(cacheKey)); + if (data) { + return data; + } + + return this.fetchApi(`resproxy/${ip}`).then(async (response) => { + const resproxy = (await response.json()) as Resproxy; + this.cache.set(IPinfoWrapper.cacheKey(cacheKey), resproxy); + return resproxy; + }); + } + /** * Get a mapping of a list of IPs on a world map. *