Files
freno-dev/src/server/device-utils.ts
2026-01-21 12:22:19 -05:00

103 lines
2.9 KiB
TypeScript

import type { H3Event } from "vinxi/http";
import { UAParser } from "ua-parser-js";
export interface DeviceInfo {
deviceName?: string;
deviceType?: "desktop" | "mobile" | "tablet";
browser?: string;
os?: string;
}
/**
* Parse user agent string to extract device information
* @param userAgent - User agent string from request headers
* @returns Parsed device information
*/
export function parseDeviceInfo(userAgent: string): DeviceInfo {
const parser = new UAParser(userAgent);
const result = parser.getResult();
// Determine device type
let deviceType: "desktop" | "mobile" | "tablet" = "desktop";
if (result.device.type === "mobile") {
deviceType = "mobile";
} else if (result.device.type === "tablet") {
deviceType = "tablet";
}
// Build device name (e.g., "iPhone 14", "Windows PC", "iPad Pro")
let deviceName: string | undefined;
if (result.device.vendor && result.device.model) {
deviceName = `${result.device.vendor} ${result.device.model}`;
} else if (result.os.name) {
deviceName = `${result.os.name} ${deviceType === "desktop" ? "Computer" : deviceType}`;
}
// Browser info (e.g., "Chrome 120")
const browser =
result.browser.name && result.browser.version
? `${result.browser.name} ${result.browser.version.split(".")[0]}`
: result.browser.name;
// OS info (e.g., "macOS 14.1", "Windows 11", "iOS 17")
const os =
result.os.name && result.os.version
? `${result.os.name} ${result.os.version}`
: result.os.name;
return {
deviceName,
deviceType,
browser,
os
};
}
/**
* Extract device information from H3Event
* @param event - H3Event
* @returns Device information
*/
export function getDeviceInfo(event: H3Event): DeviceInfo {
const userAgent = event.node.req.headers["user-agent"] || "";
return parseDeviceInfo(userAgent);
}
/**
* Generate a human-readable device description
* @param deviceInfo - Device information
* @returns Formatted device string (e.g., "Chrome on macOS", "iPhone")
*/
export function formatDeviceDescription(deviceInfo: DeviceInfo): string {
const parts: string[] = [];
if (deviceInfo.deviceName) {
parts.push(deviceInfo.deviceName);
}
if (deviceInfo.browser) {
parts.push(deviceInfo.browser);
}
if (deviceInfo.os && !deviceInfo.deviceName?.includes(deviceInfo.os)) {
parts.push(`on ${deviceInfo.os}`);
}
return parts.length > 0 ? parts.join(" • ") : "Unknown Device";
}
/**
* Create a short device fingerprint for comparison
* Not cryptographic, just for grouping similar logins
* @param deviceInfo - Device information
* @returns Short fingerprint string
*/
export function createDeviceFingerprint(deviceInfo: DeviceInfo): string {
const parts = [
deviceInfo.deviceType || "unknown",
deviceInfo.os?.split(" ")[0] || "unknown",
deviceInfo.browser?.split(" ")[0] || "unknown"
];
return parts.join("-").toLowerCase();
}