Skip to content

Instantly share code, notes, and snippets.

@pdaug
Created May 15, 2025 11:20
Show Gist options
  • Save pdaug/4b90013aca360835660f7720f821585c to your computer and use it in GitHub Desktop.
Save pdaug/4b90013aca360835660f7720f821585c to your computer and use it in GitHub Desktop.
import { IncomingHttpHeaders } from "http";
// types
import { RequestExtended } from "../types/Request";
import { TypeUserAgentBrowser, TypeUserAgentDevice, TypeUserAgentSystem } from "../types/Utils";
/**
* Class for detecting information about the browser and device from the User-Agent header.
*
* @class
*/
class UserAgent {
req: RequestExtended;
headers: IncomingHttpHeaders;
accept: IncomingHttpHeaders["accept"];
userAgent: IncomingHttpHeaders["user-agent"];
/**
* Creates an instance of the `UserAgent` class.
* @param {RequestExtended} req - The request object with headers.
*/
constructor(req: RequestExtended) {
this.req = req;
this.headers = req.headers;
this.accept = req.headers?.["accept"]?.toLocaleLowerCase();
this.userAgent = req.headers?.["user-agent"]?.toLocaleLowerCase();
return;
}
/**
* Detects the browser based on the `User-Agent` header of the request.
*
* @returns {TypeUserAgentBrowser} The name of the browser (e.g., "chrome", "firefox", "opera", etc.)
*/
browser(): TypeUserAgentBrowser {
let currentBrowser: TypeUserAgentBrowser = "unknown";
if (!this.userAgent) {
return currentBrowser;
}
if (/opera|opr/i.test(this.userAgent)) {
currentBrowser = "opera";
} else if (/edge|edg/i.test(this.userAgent)) {
currentBrowser = "edge";
} else if (/chrome/i.test(this.userAgent)) {
currentBrowser = "chrome";
} else if (/firefox/i.test(this.userAgent)) {
currentBrowser = "firefox";
} else if (/safari/i.test(this.userAgent)) {
currentBrowser = "safari";
} else if (/msie|trident/i.test(this.userAgent)) {
currentBrowser = "internet explorer";
}
return currentBrowser;
}
/**
* Detects the type of device based on the `User-Agent` header and `Accept` header of the request.
*
* @returns {TypeUserAgentDevice} The type of the device ("mobile", "api", "desktop").
*/
device(): TypeUserAgentDevice {
let currentDevice: TypeUserAgentDevice = "desktop";
if (this.userAgent && /mobile|iphone|android|ipad/.test(this.userAgent)) {
currentDevice = "mobile";
}
if (this.userAgent && /postmanruntime/.test(this.userAgent)) {
currentDevice = "api";
}
if (this.userAgent && /curl/.test(this.userAgent)) {
currentDevice = "api";
}
if ((this.accept && this.accept.includes("application/json")) || !this.userAgent) {
currentDevice = "api";
}
return currentDevice;
}
/**
* Detects the operating system from the user-agent string.
*
* @returns {TypeUserAgentSystem} The name of the detected operating system.
* Possible return values: 'windows', 'macos', 'android', 'ios', 'linux', or 'unknown' if no match is found.
*/
system(): TypeUserAgentSystem {
let currentSystem: TypeUserAgentSystem = "unknown";
if (!this.userAgent) {
return currentSystem;
}
if (/windows nt/.test(this.userAgent)) {
currentSystem = "windows";
} else if (/iphone|ipad|ipod/.test(this.userAgent)) {
currentSystem = "ios";
} else if (/mac os x|macintosh/.test(this.userAgent)) {
currentSystem = "macos";
} else if (/android/.test(this.userAgent)) {
currentSystem = "android";
} else if (/linux/.test(this.userAgent)) {
currentSystem = "linux";
}
return currentSystem;
}
/**
* Retrieves the client's IP address from the request headers.
* It first checks the 'x-forwarded-for' header, which may contain a list of IPs if the request passed through proxies.
* If unavailable, it falls back to the remote address from the socket.
*
* @returns {string} The client's IP address as a string.
*/
ip(): string {
const forwardedFor = this.headers?.["x-forwarded-for"];
if (typeof forwardedFor === "string") {
return forwardedFor.split(",")[0].trim();
}
return this.req.socket?.remoteAddress || "";
}
}
export default UserAgent;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment