Skip to content

Commit

Permalink
add options & update README
Browse files Browse the repository at this point in the history
  • Loading branch information
sajjadmrx committed Apr 17, 2023
1 parent 17934a5 commit a8971bd
Show file tree
Hide file tree
Showing 6 changed files with 140 additions and 15 deletions.
64 changes: 57 additions & 7 deletions README.MD
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@

# request-details
A tiny Node.js module for retrieving a request's details such as IP address, operating system, and browser information.

a Node.js package that provides a class for handling request details in an Express application. It extracts information related to the user's IP, operating system (OS), browser, device, and CPU from the user agent string in the HTTP request headers.

<p align="center">
<img src="https://img.shields.io/github/languages/top/sajjadmrx/request-details" alt="languages" >
Expand All @@ -16,24 +15,59 @@ npm install request-details
```

## ⚙️ Usage
To use Request-Details in your Express application, you can import the RequestDetail class from the package and create an instance of it with the Express request object. Here's an example:


```js
const RequestDetails = require('request-details').default;
// CommonJS:
import RequestDetails from 'request-details';

app.use(RequestDetails.middleware); // It will add the details to the request object (optional)
import express from "express";

const app = express();

app.get("/", (req, res) => {
const requestDetail = new RequestDetail(req);
// Get IP info
requestDetail.getIpInfo()
.then(ipInfo => {
console.log(ipInfo);
// { ip: '127.0.0.1', country: 'US', city: 'New York', ... }
})
.catch(err => {
console.error(err);
});

// Get OS info
const os = requestDetail.getOs();
console.log(os); // { name: 'Windows', version: '10' }

// Get browser info
const browser = requestDetail.getBrowser();
console.log(browser); // { name: 'Chrome', version: '58.0.3029.110' }

// Get device info
const device = requestDetail.getDevice();
console.log(device); // { type: 'desktop', vendor: 'Unknown', model: 'Unknown' }

// When you do not use middleware:
app.get('/', (req, res) => {
const details = new RequestDetails(req);
console.log(details.getDevice());
// Get CPU info
const cpu = requestDetail.getCPU();
console.log(cpu); // { architecture: 'amd64' }

res.send("Hello world!");
});

app.use(RequestDetails.middleware); // It will add the details to the request object (optional)
app.get('/middleware', async (req, res, next) => {
const ipDetails = await req.info.getIpInfo()
res.json(ipDetails)
})

app.listen(3000, () => {
console.log("Server is running on port 3000");
});

```

## 💡 Features
Expand All @@ -44,3 +78,19 @@ app.get('/middleware', async (req, res, next) => {
* Works with Express, NestJs, and other Node.js web frameworks.
* Supports CommonJS and ES modules for importing in your project.

## 🚀 API

### `RequestDetail`
* `constructor(req: Request, options?: Options)`: Creates an instance of the RequestDetail class with the Express request object and optional options to configure the instance.
* `getIpInfo(): Promise<I_iplocate>`: Get information related to user's IP. Returns a Promise that resolves with the IP information.
* `setOptions(options: Options): void`: Sets the options for the RequestDetail instance.
* `getOs(): OS | null`: Get the operating system (OS) information from the user agent.
* `getBrowser(): Browser | null`: Get the browser information from the user agent.
* `getDevice(): Device | null`: Get the device information from the user agent.
* `getCPU(): CPU | null`: Get the CPU information from the user agent.
* `static fetchUserAgent(userAgent: string)`: Get user agent from a user agent string.
* `static getIpInfoByIp(ip: string, token?: string): Promise<I_iplocate>`: Get IP information for a specific IP address.


## 🤝 Contributing
If you would like to contribute to Request-Details, please open an issue or submit a pull request on the GitHub repository. 🔧💻🔍
10 changes: 10 additions & 0 deletions src/common/interfaces/options.interface.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export interface Options {
iPlocateToken?: string;
}

/**
* Options used in the constructor of RequestDetails class.
*
* @interface Options
* @property {string} iPlocateToken - An optional token to be used with the iPlocate API for IP geolocation. [see](https://www.iplocate.io)
*/
29 changes: 23 additions & 6 deletions src/request_detail.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { Browser } from "./common/interfaces/browser.interface";
import { Device } from "./common/interfaces/Device.interface";
import { CPU } from "./common/interfaces/cpu.interface";
import { OS } from "./common/interfaces/os.interface";
import { Options } from "./common/interfaces/options.interface";

/**
* Declare global namespace for Express module.
Expand Down Expand Up @@ -35,14 +36,18 @@ declare global {
*/
class RequestDetail {
protected ip: string;
private options: Options;

/**
* Creates an instance of `RequestDetail` class.
* Creates an instance of the `RequestDetails` class.
*
* @constructor
* @param {Request} req - The Express request object.
* @param {Options} options - The options to configure the `RequestDetails` instance.
*/
constructor(private req: Request) {
constructor(private req: Request, options: Options = {}) {
this.ip = req.ip;
this.options = options;
}

/**
Expand All @@ -57,7 +62,18 @@ class RequestDetail {
*/
getIpInfo(): Promise<I_iplocate> {
const ip = this.req.ip;
return new Iplocate().getIpInfo(ip);
return new Iplocate(this.options.iPlocateToken).getIpInfo(ip);
}

/**
* Sets the options for the `RequestDetails` instance.
*
* @method
* @param {Options} options - The options to set.
* @returns {void}
*/
setOptions(options: Options): void {
this.options = options;
}

/**
Expand Down Expand Up @@ -127,6 +143,7 @@ class RequestDetail {
/**
* Get IP information for a specific IP address
* @param {string} ip - The IP address
* @param {string | undefined} token - The [iplocate token](https://www.iplocate.io)
* @returns {Promise<I_iplocate>} - A Promise that returns the information related to the specific IP address upon success
*
* @example
Expand All @@ -135,8 +152,8 @@ class RequestDetail {
* console.log(ipInfo); // { ip: '192.168.0.1', country: 'US', city: 'New York', ... }
*
*/
static getIpInfoByIp(ip: string): Promise<I_iplocate> {
return new Iplocate().getIpInfo(ip);
static getIpInfoByIp(ip: string, token?: string): Promise<I_iplocate> {
return new Iplocate(token).getIpInfo(ip);
}

/**
Expand All @@ -151,7 +168,7 @@ class RequestDetail {
*
*/
static middleware(req: Request, res: Response, next: NextFunction) {
req.info = new RequestDetail(req);
req.info = new RequestDetail(req, {});
next();
}
}
Expand Down
41 changes: 40 additions & 1 deletion test/RequestDetail.e2e-spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import request from "supertest";
import express from "express";
import RequestDetail from "../src/request_detail";
import Request_detail from "../src/request_detail";
import { fakeHeader } from "./data/fakeHeader";
import ua from "../src/plugins/ua-parser-js.plugin";
import * as UAParser from "ua-parser-js";

// Create an Express app
const app = express();
Expand All @@ -13,7 +17,15 @@ app.get("/", async (req, res) => {
const ipInfo = await req.info.getIpInfo();
res.json({ ipInfo });
});

app.get("/without-mid", (req, res) => {
// @ts-ignore
req = {
...req,
...fakeHeader,
};
const requestDetail: Request_detail = new RequestDetail(req);
res.json({ os: requestDetail.getOs() });
});
describe("RequestDetail E2E Test", () => {
test("GET / should return ipInfo", async () => {
// Make a request to the Express app
Expand All @@ -28,4 +40,31 @@ describe("RequestDetail E2E Test", () => {
expect(res.body.ipInfo).toHaveProperty("country");
expect(res.body.ipInfo).toHaveProperty("city");
});

test("should return os info", async () => {
const res = await request(app).get("/without-mid");
expect(res.status).toEqual(200);
expect(res.body.os).toEqual({
name: "Windows",
version: "10",
});
});
describe("fetchUserAgent", () => {
it("should return the ua function", () => {
const result = RequestDetail.fetchUserAgent;
expect(result).toEqual(ua);
});
it("should correctly parse OS name from user agent string", () => {
const userAgent =
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36";
jest.mock("ua-parser-js", () => {
return jest.fn(() => ({
getUA: jest.fn(() => userAgent),
}));
});

const result = RequestDetail.fetchUserAgent;
expect(result(userAgent).os.name).toBe("Windows");
});
});
});
4 changes: 3 additions & 1 deletion test/RequestDetail.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,9 @@ describe("RequestDetail", () => {
.spyOn(Iplocate.prototype, "getIpInfo")
.mockImplementation(async () => expectedIpInfo);

const requestDetail = new RequestDetail(req);
const requestDetail = new RequestDetail(req, {
iPlocateToken: "TEST",
});
const ipInfo = await requestDetail.getIpInfo();
expect(Iplocate.prototype.getIpInfo).toHaveBeenCalledWith("127.0.0.1");
expect(ipInfo).toEqual(expectedIpInfo);
Expand Down
7 changes: 7 additions & 0 deletions test/data/fakeHeader.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export const fakeHeader = {
ip: "127.0.0.1",
headers: {
"user-agent":
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3",
},
};

0 comments on commit a8971bd

Please sign in to comment.