fix: various fixes to sentry-reported errors and more

This commit is contained in:
Hampus Kraft
2026-02-18 15:38:51 +00:00
parent 302c0d2a0c
commit 0517a966a3
357 changed files with 25420 additions and 16281 deletions

View File

@@ -19,37 +19,15 @@
import dns from 'node:dns';
import {Config} from '@fluxer/api/src/Config';
import {Logger} from '@fluxer/api/src/Logger';
import type {ICacheService} from '@fluxer/cache/src/ICacheService';
import {getRegionDisplayName} from '@fluxer/geo_utils/src/RegionFormatting';
import type {GeoipResult} from '@fluxer/geoip/src/GeoipLookup';
import {formatGeoipLocation, lookupGeoipByIp} from '@fluxer/geoip/src/GeoipLookup';
import {extractClientIp} from '@fluxer/ip_utils/src/ClientIp';
import {isValidIp, normalizeIpString} from '@fluxer/ip_utils/src/IpAddress';
import {ms, seconds} from 'itty-time';
import maxmind, {type CityResponse, type Reader} from 'maxmind';
import {seconds} from 'itty-time';
const REVERSE_DNS_CACHE_TTL_SECONDS = seconds('1 day');
const REVERSE_DNS_CACHE_PREFIX = 'reverse-dns:';
export const UNKNOWN_LOCATION = 'Unknown Location';
export interface GeoipResult {
countryCode: string | null;
normalizedIp: string | null;
city: string | null;
region: string | null;
countryName: string | null;
}
type CacheEntry = {
result: GeoipResult;
expiresAt: number;
};
const geoipCache = new Map<string, CacheEntry>();
let maxmindReader: Reader<CityResponse> | null = null;
let maxmindReaderPromise: Promise<Reader<CityResponse>> | null = null;
export async function lookupGeoip(req: Request): Promise<GeoipResult>;
export async function lookupGeoip(ip: string): Promise<GeoipResult>;
export async function lookupGeoip(input: string | Request): Promise<GeoipResult> {
@@ -58,113 +36,9 @@ export async function lookupGeoip(input: string | Request): Promise<GeoipResult>
? input
: extractClientIp(input, {trustCfConnectingIp: Config.proxy.trust_cf_connecting_ip});
if (!ip) {
return buildFallbackResult('');
return {countryCode: null, normalizedIp: null, city: null, region: null, countryName: null};
}
return lookupGeoipFromString(ip);
}
function buildFallbackResult(clean: string): GeoipResult {
return {
countryCode: null,
normalizedIp: clean || null,
city: null,
region: null,
countryName: null,
};
}
async function ensureMaxmindReader(): Promise<Reader<CityResponse>> {
if (maxmindReader) return maxmindReader;
if (!maxmindReaderPromise) {
const dbPath = Config.geoip.maxmindDbPath;
if (!dbPath) {
throw new Error('Missing MaxMind DB path');
}
maxmindReaderPromise = maxmind
.open<CityResponse>(dbPath)
.then((reader) => {
maxmindReader = reader;
return reader;
})
.catch((error) => {
maxmindReaderPromise = null;
throw error;
});
}
return maxmindReaderPromise;
}
function stateLabel(record?: CityResponse): string | null {
const subdivision = record?.subdivisions?.[0];
if (!subdivision) return null;
return subdivision.names?.en || subdivision.iso_code || null;
}
async function lookupMaxmind(clean: string): Promise<GeoipResult> {
const dbPath = Config.geoip.maxmindDbPath;
if (!dbPath) {
return buildFallbackResult(clean);
}
try {
const reader = await ensureMaxmindReader();
const record = reader.get(clean);
if (!record) return buildFallbackResult(clean);
const isoCode = record.country?.iso_code;
const countryCode = isoCode ? isoCode.toUpperCase() : null;
return {
countryCode,
normalizedIp: clean,
city: record.city?.names?.en ?? null,
region: stateLabel(record),
countryName: record.country?.names?.en ?? (countryCode ? countryDisplayName(countryCode) : null) ?? null,
};
} catch (error) {
const message = (error as Error).message ?? 'unknown';
Logger.warn({error, maxmind_db_path: dbPath, message}, 'MaxMind lookup failed');
return buildFallbackResult(clean);
}
}
async function resolveGeoip(clean: string): Promise<GeoipResult> {
const now = Date.now();
const cached = geoipCache.get(clean);
if (cached && now < cached.expiresAt) {
return cached.result;
}
const result = await lookupMaxmind(clean);
geoipCache.set(clean, {result, expiresAt: now + ms('10 minutes')});
return result;
}
async function lookupGeoipFromString(value: string): Promise<GeoipResult> {
const clean = normalizeIpString(value);
if (!isValidIp(clean)) {
return buildFallbackResult(clean);
}
return resolveGeoip(clean);
}
function countryDisplayName(code: string, locale = 'en'): string | null {
const upper = code.toUpperCase();
if (!isAsciiUpperAlpha2(upper)) return null;
return getRegionDisplayName(upper, {locale}) ?? null;
}
export function formatGeoipLocation(result: GeoipResult): string | null {
const parts: Array<string> = [];
if (result.city) parts.push(result.city);
if (result.region) parts.push(result.region);
const countryLabel = result.countryName ?? result.countryCode;
if (countryLabel) parts.push(countryLabel);
return parts.length > 0 ? parts.join(', ') : null;
return lookupGeoipByIp(ip, Config.geoip.maxmindDbPath);
}
export async function getIpAddressReverse(ip: string, cacheService?: ICacheService): Promise<string | null> {
@@ -190,16 +64,6 @@ export async function getIpAddressReverse(ip: string, cacheService?: ICacheServi
}
export async function getLocationLabelFromIp(ip: string): Promise<string | null> {
const result = await lookupGeoip(ip);
const result = await lookupGeoipByIp(ip, Config.geoip.maxmindDbPath);
return formatGeoipLocation(result);
}
function isAsciiUpperAlpha2(value: string): boolean {
return (
value.length === 2 &&
value.charCodeAt(0) >= 65 &&
value.charCodeAt(0) <= 90 &&
value.charCodeAt(1) >= 65 &&
value.charCodeAt(1) <= 90
);
}