"use strict"; const path = require("path"); const reader = require("@maxmind/geoip2-node").Reader; const AppConstants = require("./app-constants"); let locationDb, timestamp; async function openLocationDb() { if (locationDb && isFresh()) return console.warn("Location database already open."); try { const dbPath = path.join(AppConstants.GEOIP_GEOLITE2_PATH, AppConstants.GEOIP_GEOLITE2_CITY_FILENAME); locationDb = await reader.open(dbPath); } catch (e) { return console.warn("Could not open location database:", e.message); } timestamp = Date.now(); return true; } async function readLocationData(ip, locales) { let locationArr; if (!isFresh()) await openLocationDb(); try { const data = locationDb.city(ip); const countryName = data.country?.names[locales.find(locale => data.country?.names[locale])]; // find valid locale key and return its value const cityName = data.city?.names[locales.find(locale => data.city?.names[locale])]; const subdivisionName = data.subdivisions?.[0].isoCode; const subdivisionFiltered = /[A-z]{2,}/.test(subdivisionName) ? subdivisionName : null; // return strings that are 2 or more letters, or null (avoid unfamiliar subdivisions like `E` or `09`) locationArr = [cityName, subdivisionFiltered, countryName].filter(str => str); // [city name, state code, country code] with non-null items. } catch (e) { return console.warn("Could not read location from database:", e.message); } return { shortLocation: locationArr.slice(0, 2).join(", "), // shows the first two location values from the ones available fullLocation: locationArr.join(", "), // shows up to three location values from the ones available }; } function isFresh() { if (Date.now() - timestamp < 259200000) return true; // 1000ms * 60s * 60m * 24h * 3 elapsed time is less than 24hrs return false; } module.exports = { openLocationDb, readLocationData, };