diff --git a/__tests__/ipinfo-core-middleware.test.ts b/__tests__/ipinfo-core-middleware.test.ts new file mode 100644 index 0000000..1e5c584 --- /dev/null +++ b/__tests__/ipinfo-core-middleware.test.ts @@ -0,0 +1,115 @@ +import { Request, Response, NextFunction } from "express"; +import { ipinfoCore, originatingIPSelector } from "../src/index"; +import { IPinfoCore } from "node-ipinfo/dist/src/common"; + +// Mock the node-ipinfo module +const mockLookupIp = jest.fn(); +jest.mock("node-ipinfo", () => ({ + IPinfoCoreWrapper: jest.fn().mockImplementation(() => ({ + lookupIp: mockLookupIp + })) +})); + +describe("ipinfoCoreMiddleware", () => { + const mockToken = "test_token"; + let mockReq: Partial & { ipinfo?: IPinfoCore }; + let mockRes: Partial; + let next: NextFunction; + + beforeEach(() => { + // Reset mocks before each test + jest.clearAllMocks(); + + // Set up default mock response + mockLookupIp.mockResolvedValue({ + ip: "1.2.3.4", + city: "New York", + country: "US", + hostname: "example.com", + org: "Example Org" + }); + + // Setup mock request/response + mockReq = { + ip: "1.2.3.4", + headers: { "x-forwarded-for": "5.6.7.8, 10.0.0.1" }, + header: jest.fn((name: string) => { + if (name.toLowerCase() === "set-cookie") { + return ["mock-cookie-1", "mock-cookie-2"]; + } + if (name.toLowerCase() === "x-forwarded-for") { + return "5.6.7.8, 10.0.0.1"; + } + return undefined; + }) as jest.MockedFunction< + ((name: "set-cookie") => string[] | undefined) & + ((name: string) => string | undefined) + > + }; + mockRes = {}; + next = jest.fn(); + }); + + it("should use defaultIPSelector when no custom selector is provided", async () => { + const middleware = ipinfoCore({ token: mockToken }); + + await middleware(mockReq, mockRes, next); + + expect(mockLookupIp).toHaveBeenCalledWith("1.2.3.4"); + expect(mockReq.ipinfo).toEqual({ + ip: "1.2.3.4", + city: "New York", + country: "US", + hostname: "example.com", + org: "Example Org" + }); + expect(next).toHaveBeenCalled(); + }); + + it("should use originatingIPSelector when specified", async () => { + mockLookupIp.mockResolvedValue({ + ip: "5.6.7.8", + city: "San Francisco", + country: "US", + hostname: "proxy.example.com", + org: "Proxy Org" + }); + + const middleware = ipinfoCore({ + token: mockToken, + ipSelector: originatingIPSelector + }); + + await middleware(mockReq, mockRes, next); + + expect(mockLookupIp).toHaveBeenCalledWith("5.6.7.8"); + expect(mockReq.ipinfo?.ip).toBe("5.6.7.8"); + }); + + it("should use custom ipSelector function when provided", async () => { + const customSelector = jest.fn().mockReturnValue("9.10.11.12"); + + const middleware = ipinfoCore({ + token: mockToken, + ipSelector: customSelector + }); + + await middleware(mockReq, mockRes, next); + + expect(customSelector).toHaveBeenCalledWith(mockReq); + expect(mockLookupIp).toHaveBeenCalledWith("9.10.11.12"); + }); + + it("should throw IPinfo API errors", async () => { + const errorMessage = "API rate limit exceeded"; + mockLookupIp.mockRejectedValueOnce(new Error(errorMessage)); + const middleware = ipinfoCore({ token: mockToken }); + + await expect(middleware(mockReq, mockRes, next)).rejects.toThrow( + errorMessage + ); + + expect(mockReq.ipinfo).toBeUndefined(); + expect(next).not.toHaveBeenCalled(); + }); +}); diff --git a/package-lock.json b/package-lock.json index 724e766..16dd18d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,15 +1,15 @@ { "name": "ipinfo-express", - "version": "2.0.2", + "version": "2.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "ipinfo-express", - "version": "2.0.2", + "version": "2.1.0", "license": "Apache-2.0", "dependencies": { - "node-ipinfo": "^4.1.0" + "node-ipinfo": "^4.2.0" }, "devDependencies": { "@types/express": "^4.17.23", @@ -4112,9 +4112,9 @@ "license": "MIT" }, "node_modules/node-ipinfo": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/node-ipinfo/-/node-ipinfo-4.1.0.tgz", - "integrity": "sha512-5HJA50uc6zplOV8LmxflE9rtMyyoiQkHibH4F2Jn1A/91yt4uOqle6oR3RtmNesmbrixXTymVONKntjfaQk6qQ==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/node-ipinfo/-/node-ipinfo-4.2.0.tgz", + "integrity": "sha512-B/tMcJl3MLSWqI9dxENkHH1yb9MFavByDuT1F/USE3Vyv21UTsr8QZ7hF2vte0G1OlS2uzIPNlI3f+66iM8sGg==", "license": "Apache-2.0", "dependencies": { "lru-cache": "^7.18.3", diff --git a/package.json b/package.json index 4976b93..516b9f6 100644 --- a/package.json +++ b/package.json @@ -36,7 +36,7 @@ } }, "dependencies": { - "node-ipinfo": "^4.1.0" + "node-ipinfo": "^4.2.0" }, "devDependencies": { "@types/express": "^4.17.23", diff --git a/src/index.ts b/src/index.ts index b268896..d464222 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,8 +1,12 @@ -import { IPinfoWrapper, IPinfo, IPinfoLiteWrapper } from "node-ipinfo"; +import { + IPinfoWrapper, + IPinfo, + IPinfoLiteWrapper, + IPinfoCoreWrapper +} from "node-ipinfo"; import defaultIPSelector from "./ip-selector/default-ip-selector"; import originatingIPSelector from "./ip-selector/originating-ip-selector"; -import { IPinfoLite } from "node-ipinfo/dist/src/common"; -import { IPBogon } from "node-ipinfo/dist/src/common"; +import { IPinfoLite, IPinfoCore, IPBogon } from "node-ipinfo/dist/src/common"; type MiddlewareOptions = { token?: string; @@ -51,9 +55,30 @@ const ipinfoLiteMiddleware = ({ }; }; +const ipinfoCoreMiddleware = ({ + token = "", + cache, + timeout, + ipSelector +}: MiddlewareOptions = {}) => { + const ipinfo = new IPinfoCoreWrapper(token, cache, timeout); + if (ipSelector == null || typeof ipSelector !== "function") { + ipSelector = defaultIPSelector; + } + return async (req: any, _: any, next: any) => { + const ip = ipSelector?.(req) ?? defaultIPSelector(req); + if (ip) { + const ipInfo: IPinfoCore | IPBogon = await ipinfo.lookupIp(ip); + req.ipinfo = ipInfo; + } + next(); + }; +}; + export default ipinfoMiddleware; export { defaultIPSelector, originatingIPSelector, - ipinfoLiteMiddleware as ipinfoLite + ipinfoLiteMiddleware as ipinfoLite, + ipinfoCoreMiddleware as ipinfoCore };