'use strict'; const axios = require('axios'); const UAParser = require('ua-parser-js'); const LOCALHOST = new Set(['::1', '127.0.0.1', '::ffff:127.0.0.1']); const getIP = (req) => { const forwarded = req.headers['x-forwarded-for']; if (forwarded) return forwarded.split(',')[0].trim(); return req.ip || req.connection?.remoteAddress || null; }; const parseUA = (uaString) => { const p = new UAParser(uaString || ''); return { ua: uaString || 'unknown', browser: [p.getBrowser().name, p.getBrowser().version].filter(Boolean).join(' ') || 'unknown', os: [p.getOS().name, p.getOS().version].filter(Boolean).join(' ') || 'unknown', device: p.getDevice().type || 'desktop', }; }; const getGeo = async (ip) => { if (!ip || LOCALHOST.has(ip)) return { country: null, region: null, city: null, lat: null, lon: null }; try { const { data } = await axios.get( `http://ip-api.com/json/${ip}?fields=status,country,regionName,city,lat,lon`, { timeout: 3000 } ); if (data.status !== 'success') return { country: null, region: null, city: null, lat: null, lon: null }; return { country: data.country, region: data.regionName, city: data.city, lat: data.lat, lon: data.lon }; } catch { return { country: null, region: null, city: null, lat: null, lon: null }; } }; /** * Builds a rich session info object from the request. * Used for both login_info and logout_info. * * @param {import('express').Request} req * @param {Object} extras — e.g. { forced_by: admin_user_id } * @returns {Promise} */ const buildSessionInfo = async (req, extras = {}) => { const ip = getIP(req); const geo = await getGeo(ip); return { date: new Date().toISOString(), ip_address: ip, country: geo.country, region: geo.region, city: geo.city, lat: geo.lat, lon: geo.lon, device_info: parseUA(req.headers['user-agent']), ...extras, }; }; module.exports = buildSessionInfo; module.exports.parseUA = parseUA; module.exports.getIP = getIP;