'use strict'; const crypto = require('crypto'); function parseBrowser(userAgent) { const ua = String(userAgent || ''); if (/Edg\//i.test(ua)) return 'Edge'; if (/OPR\//i.test(ua) || /Opera/i.test(ua)) return 'Opera'; if (/Firefox\//i.test(ua)) return 'Firefox'; if (/Chrome\//i.test(ua) || /CriOS\//i.test(ua)) return 'Chrome'; if (/Safari\//i.test(ua)) return 'Safari'; if (/curl\//i.test(ua)) return 'curl'; if (/PostmanRuntime\//i.test(ua)) return 'Postman'; return 'Unknown'; } function parseOs(userAgent) { const ua = String(userAgent || ''); if (/iPhone|iPad|iPod/i.test(ua)) return 'iOS'; if (/Android/i.test(ua)) return 'Android'; if (/Windows NT/i.test(ua)) return 'Windows'; if (/Mac OS X|Macintosh/i.test(ua)) return 'macOS'; if (/Linux/i.test(ua)) return 'Linux'; if (/CrOS/i.test(ua)) return 'ChromeOS'; return 'Unknown'; } function parseDeviceType(userAgent) { const ua = String(userAgent || ''); if (/iPad|Tablet/i.test(ua)) return 'tablet'; if (/Mobi|iPhone|Android/i.test(ua)) return 'mobile'; if (/curl|PostmanRuntime/i.test(ua)) return 'api'; return 'desktop'; } function coarseIpPrefix(ipAddress) { const ip = String(ipAddress || '').trim(); if (!ip) return ''; const v4 = ip.match(/(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.\d{1,3}$/); if (v4) return `${v4[1]}.${v4[2]}.${v4[3]}.0/24`; if (ip.includes(':')) { return `${ip.split(':').slice(0, 4).join(':')}::/64`; } return ip; } function buildDeviceFingerprint({ userAgent, ipAddress }) { const browser = parseBrowser(userAgent); const os = parseOs(userAgent); const deviceType = parseDeviceType(userAgent); const ipPrefix = coarseIpPrefix(ipAddress); const source = [browser, os, deviceType, String(userAgent || '').slice(0, 500), ipPrefix].join('|'); const fingerprint = crypto.createHash('sha256').update(source).digest('hex').slice(0, 16); return { browser, os, device_type: deviceType, device_fingerprint: fingerprint, }; } module.exports = { buildDeviceFingerprint, coarseIpPrefix, parseBrowser, parseDeviceType, parseOs, };