Skip to content

Commit 484f96c

Browse files
committed
Add support for Lite API
1 parent e5bb53a commit 484f96c

File tree

2 files changed

+143
-2
lines changed

2 files changed

+143
-2
lines changed
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
import { Request, Response, NextFunction } from "express";
2+
import { ipinfoLite, originatingIPSelector } from "../src/index";
3+
import { IPinfoLite } from "node-ipinfo/dist/src/common";
4+
5+
// Mock the node-ipinfo module
6+
const mockLookupIp = jest.fn();
7+
jest.mock("node-ipinfo", () => ({
8+
IPinfoLiteWrapper: jest.fn().mockImplementation(() => ({
9+
lookupIp: mockLookupIp
10+
}))
11+
}));
12+
13+
describe("ipinfoLiteMiddleware", () => {
14+
const mockToken = "test_token";
15+
let mockReq: Partial<Request> & { ipinfo?: IPinfoLite };
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+
mockLookupIp.mockResolvedValue({
25+
ip: "1.2.3.4",
26+
city: "New York",
27+
country: "US",
28+
hostname: "example.com",
29+
org: "Example Org"
30+
});
31+
32+
// Setup mock request/response
33+
mockReq = {
34+
ip: "1.2.3.4",
35+
headers: { "x-forwarded-for": "5.6.7.8, 10.0.0.1" },
36+
header: jest.fn((name: string) => {
37+
if (name.toLowerCase() === "set-cookie") {
38+
return ["mock-cookie-1", "mock-cookie-2"];
39+
}
40+
if (name.toLowerCase() === "x-forwarded-for") {
41+
return "5.6.7.8, 10.0.0.1";
42+
}
43+
return undefined;
44+
}) as jest.MockedFunction<
45+
((name: "set-cookie") => string[] | undefined) &
46+
((name: string) => string | undefined)
47+
>
48+
};
49+
mockRes = {};
50+
next = jest.fn();
51+
});
52+
53+
it("should use defaultIPSelector when no custom selector is provided", async () => {
54+
const middleware = ipinfoLite({ token: mockToken });
55+
56+
await middleware(mockReq, mockRes, next);
57+
58+
expect(mockLookupIp).toHaveBeenCalledWith("1.2.3.4");
59+
expect(mockReq.ipinfo).toEqual({
60+
ip: "1.2.3.4",
61+
city: "New York",
62+
country: "US",
63+
hostname: "example.com",
64+
org: "Example Org"
65+
});
66+
expect(next).toHaveBeenCalled();
67+
});
68+
69+
it("should use originatingIPSelector when specified", async () => {
70+
mockLookupIp.mockResolvedValue({
71+
ip: "5.6.7.8",
72+
city: "San Francisco",
73+
country: "US",
74+
hostname: "proxy.example.com",
75+
org: "Proxy Org"
76+
});
77+
78+
const middleware = ipinfoLite({
79+
token: mockToken,
80+
ipSelector: originatingIPSelector
81+
});
82+
83+
await middleware(mockReq, mockRes, next);
84+
85+
expect(mockLookupIp).toHaveBeenCalledWith("5.6.7.8");
86+
expect(mockReq.ipinfo?.ip).toBe("5.6.7.8");
87+
});
88+
89+
it("should use custom ipSelector function when provided", async () => {
90+
const customSelector = jest.fn().mockReturnValue("9.10.11.12");
91+
92+
const middleware = ipinfoLite({
93+
token: mockToken,
94+
ipSelector: customSelector
95+
});
96+
97+
await middleware(mockReq, mockRes, next);
98+
99+
expect(customSelector).toHaveBeenCalledWith(mockReq);
100+
expect(mockLookupIp).toHaveBeenCalledWith("9.10.11.12");
101+
});
102+
103+
it("should throw IPinfo API errors", async () => {
104+
const errorMessage = "API rate limit exceeded";
105+
mockLookupIp.mockRejectedValueOnce(new Error(errorMessage));
106+
const middleware = ipinfoLite({ token: mockToken });
107+
108+
await expect(middleware(mockReq, mockRes, next)).rejects.toThrow(
109+
errorMessage
110+
);
111+
112+
expect(mockReq.ipinfo).toBeUndefined();
113+
expect(next).not.toHaveBeenCalled();
114+
});
115+
});

src/index.ts

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1-
import { IPinfoWrapper, IPinfo } from "node-ipinfo";
1+
import { IPinfoWrapper, IPinfo, IPinfoLiteWrapper } from "node-ipinfo";
22
import defaultIPSelector from "./ip-selector/default-ip-selector";
33
import originatingIPSelector from "./ip-selector/originating-ip-selector";
4+
import { IPinfoLite } from "node-ipinfo/dist/src/common";
5+
import { IPBogon } from "node-ipinfo/dist/src/common";
46

57
type MiddlewareOptions = {
68
token?: string;
@@ -29,5 +31,29 @@ const ipinfoMiddleware = ({
2931
};
3032
};
3133

34+
const ipinfoLiteMiddleware = ({
35+
token = "",
36+
cache,
37+
timeout,
38+
ipSelector
39+
}: MiddlewareOptions = {}) => {
40+
const ipinfo = new IPinfoLiteWrapper(token, cache, timeout);
41+
if (ipSelector == null || typeof ipSelector !== "function") {
42+
ipSelector = defaultIPSelector;
43+
}
44+
return async (req: any, _: any, next: any) => {
45+
const ip = ipSelector?.(req) ?? defaultIPSelector(req);
46+
if (ip) {
47+
const ipInfo: IPinfoLite | IPBogon = await ipinfo.lookupIp(ip);
48+
req.ipinfo = ipInfo;
49+
}
50+
next();
51+
};
52+
};
53+
3254
export default ipinfoMiddleware;
33-
export { defaultIPSelector, originatingIPSelector };
55+
export {
56+
defaultIPSelector,
57+
originatingIPSelector,
58+
ipinfoLiteMiddleware as ipinfoLite
59+
};

0 commit comments

Comments
 (0)