Files

67 lines
2.0 KiB
JavaScript

'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<Object>}
*/
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;