Skip to content

Commit b0af52f

Browse files
authored
Merge pull request #19 from ipinfo/silvano/eng-650-add-resproxy-support-in-ipinfonode-express-library
Add Residential Proxy API support
2 parents cd5cfe2 + 1f274c2 commit b0af52f

File tree

4 files changed

+154
-13
lines changed

4 files changed

+154
-13
lines changed
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
import { Request, Response, NextFunction } from "express";
2+
import { ipinfoResproxy, originatingIPSelector } from "../src/index";
3+
import { Resproxy } from "node-ipinfo/dist/src/common";
4+
5+
// Mock the node-ipinfo module
6+
const mockLookupResproxy = jest.fn();
7+
jest.mock("node-ipinfo", () => ({
8+
IPinfoWrapper: jest.fn().mockImplementation(() => ({
9+
lookupResproxy: mockLookupResproxy
10+
}))
11+
}));
12+
13+
describe("ipinfoResproxyMiddleware", () => {
14+
const mockToken = "test_token";
15+
let mockReq: Partial<Request> & { ipinfo_resproxy?: Resproxy };
16+
let mockRes: Partial<Response>;
17+
let next: NextFunction;
18+
19+
beforeEach(() => {
20+
// Reset mocks before each test
21+
jest.clearAllMocks();
22+
23+
// Set up default mock response
24+
mockLookupResproxy.mockResolvedValue({
25+
ip: "175.107.211.204",
26+
last_seen: "2026-01-15",
27+
percent_days_seen: 100,
28+
service: "test_service"
29+
});
30+
31+
// Setup mock request/response
32+
mockReq = {
33+
ip: "175.107.211.204",
34+
headers: { "x-forwarded-for": "5.6.7.8, 10.0.0.1" },
35+
header: jest.fn((name: string) => {
36+
if (name.toLowerCase() === "set-cookie") {
37+
return ["mock-cookie-1", "mock-cookie-2"];
38+
}
39+
if (name.toLowerCase() === "x-forwarded-for") {
40+
return "5.6.7.8, 10.0.0.1";
41+
}
42+
return undefined;
43+
}) as jest.MockedFunction<
44+
((name: "set-cookie") => string[] | undefined) &
45+
((name: string) => string | undefined)
46+
>
47+
};
48+
mockRes = {};
49+
next = jest.fn();
50+
});
51+
52+
it("should use defaultIPSelector when no custom selector is provided", async () => {
53+
const middleware = ipinfoResproxy({ token: mockToken });
54+
55+
await middleware(mockReq, mockRes, next);
56+
57+
expect(mockLookupResproxy).toHaveBeenCalledWith("175.107.211.204");
58+
expect(mockReq.ipinfo_resproxy).toEqual({
59+
ip: "175.107.211.204",
60+
last_seen: "2026-01-15",
61+
percent_days_seen: 100,
62+
service: "test_service"
63+
});
64+
expect(next).toHaveBeenCalled();
65+
});
66+
67+
it("should use originatingIPSelector when specified", async () => {
68+
mockLookupResproxy.mockResolvedValue({
69+
ip: "5.6.7.8",
70+
last_seen: "2026-01-15",
71+
percent_days_seen: 50,
72+
service: "proxy_service"
73+
});
74+
75+
const middleware = ipinfoResproxy({
76+
token: mockToken,
77+
ipSelector: originatingIPSelector
78+
});
79+
80+
await middleware(mockReq, mockRes, next);
81+
82+
expect(mockLookupResproxy).toHaveBeenCalledWith("5.6.7.8");
83+
expect(mockReq.ipinfo_resproxy?.ip).toBe("5.6.7.8");
84+
});
85+
86+
it("should use custom ipSelector function when provided", async () => {
87+
const customSelector = jest.fn().mockReturnValue("9.10.11.12");
88+
89+
const middleware = ipinfoResproxy({
90+
token: mockToken,
91+
ipSelector: customSelector
92+
});
93+
94+
await middleware(mockReq, mockRes, next);
95+
96+
expect(customSelector).toHaveBeenCalledWith(mockReq);
97+
expect(mockLookupResproxy).toHaveBeenCalledWith("9.10.11.12");
98+
});
99+
100+
it("should throw IPinfo API errors", async () => {
101+
const errorMessage = "API rate limit exceeded";
102+
mockLookupResproxy.mockRejectedValueOnce(new Error(errorMessage));
103+
const middleware = ipinfoResproxy({ token: mockToken });
104+
105+
await expect(middleware(mockReq, mockRes, next)).rejects.toThrow(
106+
errorMessage
107+
);
108+
109+
expect(mockReq.ipinfo_resproxy).toBeUndefined();
110+
expect(next).not.toHaveBeenCalled();
111+
});
112+
113+
it("should pass through empty response when IP not in resproxy database", async () => {
114+
// Empty object simulates IP not in resproxy database
115+
mockLookupResproxy.mockResolvedValue({});
116+
117+
const middleware = ipinfoResproxy({ token: mockToken });
118+
119+
await middleware(mockReq, mockRes, next);
120+
121+
expect(mockLookupResproxy).toHaveBeenCalledWith("175.107.211.204");
122+
expect(mockReq.ipinfo_resproxy).toEqual({});
123+
expect(next).toHaveBeenCalled();
124+
});
125+
});

package-lock.json

Lines changed: 4 additions & 10 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@
3636
}
3737
},
3838
"dependencies": {
39-
"node-ipinfo": "^4.2.0"
39+
"node-ipinfo": "^4.3.0"
4040
},
4141
"devDependencies": {
4242
"@types/express": "^4.17.23",

src/index.ts

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ import {
1111
IPinfoLite,
1212
IPinfoCore,
1313
IPinfoPlus,
14-
IPBogon
14+
IPBogon,
15+
Resproxy
1516
} from "node-ipinfo/dist/src/common";
1617

1718
type MiddlewareOptions = {
@@ -101,11 +102,32 @@ const ipinfoPlusMiddleware = ({
101102
};
102103
};
103104

105+
const ipinfoResproxyMiddleware = ({
106+
token = "",
107+
cache,
108+
timeout,
109+
ipSelector
110+
}: MiddlewareOptions = {}) => {
111+
const ipinfo = new IPinfoWrapper(token, cache, timeout);
112+
if (ipSelector == null || typeof ipSelector !== "function") {
113+
ipSelector = defaultIPSelector;
114+
}
115+
return async (req: any, _: any, next: any) => {
116+
const ip = ipSelector?.(req) ?? defaultIPSelector(req);
117+
if (ip) {
118+
const resproxy: Resproxy = await ipinfo.lookupResproxy(ip);
119+
req.ipinfo_resproxy = resproxy;
120+
}
121+
next();
122+
};
123+
};
124+
104125
export default ipinfoMiddleware;
105126
export {
106127
defaultIPSelector,
107128
originatingIPSelector,
108129
ipinfoLiteMiddleware as ipinfoLite,
109130
ipinfoCoreMiddleware as ipinfoCore,
110-
ipinfoPlusMiddleware as ipinfoPlus
131+
ipinfoPlusMiddleware as ipinfoPlus,
132+
ipinfoResproxyMiddleware as ipinfoResproxy
111133
};

0 commit comments

Comments
 (0)