Skip to content

Commit dcfb2ba

Browse files
authored
Merge pull request #17 from ipinfo/silvano/eng-510-add-core-bundle-support-in-ipinfonode-express-library
Add support for Core bundle
2 parents 58e71e3 + 5c373b8 commit dcfb2ba

File tree

4 files changed

+151
-11
lines changed

4 files changed

+151
-11
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 { ipinfoCore, originatingIPSelector } from "../src/index";
3+
import { IPinfoCore } from "node-ipinfo/dist/src/common";
4+
5+
// Mock the node-ipinfo module
6+
const mockLookupIp = jest.fn();
7+
jest.mock("node-ipinfo", () => ({
8+
IPinfoCoreWrapper: jest.fn().mockImplementation(() => ({
9+
lookupIp: mockLookupIp
10+
}))
11+
}));
12+
13+
describe("ipinfoCoreMiddleware", () => {
14+
const mockToken = "test_token";
15+
let mockReq: Partial<Request> & { ipinfo?: IPinfoCore };
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 = ipinfoCore({ 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 = ipinfoCore({
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 = ipinfoCore({
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 = ipinfoCore({ 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+
});

package-lock.json

Lines changed: 6 additions & 6 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.1.0"
39+
"node-ipinfo": "^4.2.0"
4040
},
4141
"devDependencies": {
4242
"@types/express": "^4.17.23",

src/index.ts

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
1-
import { IPinfoWrapper, IPinfo, IPinfoLiteWrapper } from "node-ipinfo";
1+
import {
2+
IPinfoWrapper,
3+
IPinfo,
4+
IPinfoLiteWrapper,
5+
IPinfoCoreWrapper
6+
} from "node-ipinfo";
27
import defaultIPSelector from "./ip-selector/default-ip-selector";
38
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";
9+
import { IPinfoLite, IPinfoCore, IPBogon } from "node-ipinfo/dist/src/common";
610

711
type MiddlewareOptions = {
812
token?: string;
@@ -51,9 +55,30 @@ const ipinfoLiteMiddleware = ({
5155
};
5256
};
5357

58+
const ipinfoCoreMiddleware = ({
59+
token = "",
60+
cache,
61+
timeout,
62+
ipSelector
63+
}: MiddlewareOptions = {}) => {
64+
const ipinfo = new IPinfoCoreWrapper(token, cache, timeout);
65+
if (ipSelector == null || typeof ipSelector !== "function") {
66+
ipSelector = defaultIPSelector;
67+
}
68+
return async (req: any, _: any, next: any) => {
69+
const ip = ipSelector?.(req) ?? defaultIPSelector(req);
70+
if (ip) {
71+
const ipInfo: IPinfoCore | IPBogon = await ipinfo.lookupIp(ip);
72+
req.ipinfo = ipInfo;
73+
}
74+
next();
75+
};
76+
};
77+
5478
export default ipinfoMiddleware;
5579
export {
5680
defaultIPSelector,
5781
originatingIPSelector,
58-
ipinfoLiteMiddleware as ipinfoLite
82+
ipinfoLiteMiddleware as ipinfoLite,
83+
ipinfoCoreMiddleware as ipinfoCore
5984
};

0 commit comments

Comments
 (0)