Skip to content

Commit 977f1d4

Browse files
committed
new ip geo
1 parent 654088b commit 977f1d4

File tree

1 file changed

+95
-81
lines changed

1 file changed

+95
-81
lines changed

apps/basket/src/utils/ip-geo.ts

Lines changed: 95 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -1,79 +1,81 @@
11
import { createHash } from 'node:crypto';
22
import type { City } from '@maxmind/geoip2-node';
3-
import {
4-
AddressNotFoundError,
5-
BadMethodCallError,
6-
Reader,
7-
} from '@maxmind/geoip2-node';
8-
import { logger } from '../lib/logger';
3+
import { AddressNotFoundError, Reader } from '@maxmind/geoip2-node';
4+
5+
// import { logger } from '../lib/logger';
6+
const logger = console;
97

108
interface GeoIPReader extends Reader {
119
city(ip: string): City;
1210
}
1311

14-
const CDN_URL = 'https://cdn.databuddy.cc/GeoLite2-City.mmdb';
12+
const IPV4_URL = process.env.IPV4_URL;
13+
const IPV6_URL = process.env.IPV6_URL;
14+
15+
const CDN_URLS = {
16+
ipv4: IPV4_URL,
17+
ipv6: IPV6_URL,
18+
} as const;
1519

16-
let reader: GeoIPReader | null = null;
20+
let ipv4Reader: GeoIPReader | null = null;
21+
let ipv6Reader: GeoIPReader | null = null;
1722
let isLoading = false;
18-
let loadPromise: Promise<void> | null = null;
19-
let loadError: Error | null = null;
2023

21-
async function loadDatabaseFromCdn(): Promise<Buffer> {
24+
// Initialize databases on module load
25+
loadDatabases().catch((error) => {
26+
logger.error('Failed to initialize GeoIP databases on startup:', { error });
27+
});
28+
29+
async function loadDatabases() {
30+
if (isLoading || (ipv4Reader && ipv6Reader)) {
31+
logger.info('GeoIP databases already loaded or loading');
32+
return;
33+
}
34+
35+
logger.info('Starting to load GeoIP databases...');
36+
isLoading = true;
37+
2238
try {
23-
const response = await fetch(CDN_URL);
24-
if (!response.ok) {
39+
logger.info('Fetching IPv4 and IPv6 databases from CDN...');
40+
const [ipv4Response, ipv6Response] = await Promise.all([
41+
fetch(CDN_URLS.ipv4 || ''),
42+
fetch(CDN_URLS.ipv6 || ''),
43+
]);
44+
45+
if (!ipv4Response.ok) {
2546
throw new Error(
26-
`Failed to fetch database from CDN: ${response.status} ${response.statusText}`
47+
`IPv4 database fetch failed: ${ipv4Response.status} ${ipv4Response.statusText}`
2748
);
2849
}
29-
30-
const arrayBuffer = await response.arrayBuffer();
31-
const dbBuffer = Buffer.from(arrayBuffer);
32-
33-
if (dbBuffer.length < 1_000_000) {
50+
if (!ipv6Response.ok) {
3451
throw new Error(
35-
`Database file seems too small: ${dbBuffer.length} bytes`
52+
`IPv6 database fetch failed: ${ipv6Response.status} ${ipv6Response.statusText}`
3653
);
3754
}
3855

39-
return dbBuffer;
40-
} catch (error) {
41-
logger.error('Failed to load database from CDN:', { error });
42-
throw error;
43-
}
44-
}
56+
logger.info('Converting database responses to buffers...');
57+
const [ipv4Buffer, ipv6Buffer] = await Promise.all([
58+
ipv4Response.arrayBuffer(),
59+
ipv6Response.arrayBuffer(),
60+
]);
4561

46-
function loadDatabase() {
47-
if (loadError) {
48-
throw loadError;
49-
}
62+
logger.info(
63+
`Database sizes - IPv4: ${ipv4Buffer.byteLength} bytes, IPv6: ${ipv6Buffer.byteLength} bytes`
64+
);
5065

51-
if (isLoading && loadPromise) {
52-
return loadPromise;
53-
}
66+
logger.info('Opening database readers...');
67+
ipv4Reader = Reader.openBuffer(Buffer.from(ipv4Buffer)) as GeoIPReader;
68+
ipv6Reader = Reader.openBuffer(Buffer.from(ipv6Buffer)) as GeoIPReader;
5469

55-
if (reader) {
56-
return;
70+
logger.info('GeoIP databases loaded successfully');
71+
} catch (error) {
72+
logger.error('Failed to load GeoIP databases:', {
73+
error: error instanceof Error ? error.message : String(error),
74+
stack: error instanceof Error ? error.stack : undefined,
75+
});
76+
} finally {
77+
isLoading = false;
5778
}
58-
59-
isLoading = true;
60-
loadPromise = (async () => {
61-
try {
62-
const dbBuffer = await loadDatabaseFromCdn();
63-
64-
await new Promise((resolve) => setTimeout(resolve, 100));
65-
66-
reader = Reader.openBuffer(dbBuffer) as GeoIPReader;
67-
} catch (error) {
68-
logger.error('Failed to load GeoIP database:', { error });
69-
loadError = error as Error;
70-
reader = null;
71-
} finally {
72-
isLoading = false;
73-
}
74-
})();
75-
76-
return loadPromise;
7779
}
7880

7981
const ignore = ['127.0.0.1', '::1'];
@@ -83,67 +85,79 @@ const ipv4Regex =
8385

8486
const ipv6Regex = /^(?:[0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}$/;
8587

86-
function isValidIp(ip: string): boolean {
88+
function getIpType(ip: string): 'ipv4' | 'ipv6' | null {
8789
if (!ip) {
88-
return false;
90+
return null;
8991
}
9092

9193
if (ipv4Regex.test(ip)) {
92-
return true;
94+
return 'ipv4';
9395
}
9496

9597
if (ipv6Regex.test(ip)) {
96-
return true;
98+
return 'ipv6';
9799
}
98100

99-
return false;
101+
return null;
100102
}
101103

102104
export async function getGeoLocation(ip: string) {
103-
if (!ip || ignore.includes(ip) || !isValidIp(ip)) {
105+
if (!ip || ignore.includes(ip)) {
106+
logger.debug('IP ignored or empty', { ip, ignored: ignore.includes(ip) });
104107
return { country: undefined, region: undefined, city: undefined };
105108
}
106109

107-
if (!(reader || isLoading || loadError)) {
108-
try {
109-
await loadDatabase();
110-
} catch (error) {
111-
logger.error('Failed to load database for IP lookup:', { error });
112-
return { country: undefined, region: undefined, city: undefined };
113-
}
110+
const ipType = getIpType(ip);
111+
if (!ipType) {
112+
logger.warn('Invalid IP format', { ip });
113+
return { country: undefined, region: undefined, city: undefined };
114114
}
115115

116+
logger.debug('Processing IP lookup', { ip, type: ipType });
117+
118+
await loadDatabases();
119+
120+
const reader = ipType === 'ipv4' ? ipv4Reader : ipv6Reader;
116121
if (!reader) {
122+
logger.error('Database reader not available', {
123+
ip,
124+
type: ipType,
125+
hasIpv4Reader: !!ipv4Reader,
126+
hasIpv6Reader: !!ipv6Reader,
127+
});
117128
return { country: undefined, region: undefined, city: undefined };
118129
}
119130

120131
try {
121132
const response = reader.city(ip);
122-
123-
// Extract region and city data
124133
const region = response.subdivisions?.[0]?.names?.en;
125134
const city = response.city?.names?.en;
135+
const country = response.country?.names?.en;
136+
137+
logger.debug('IP lookup successful', {
138+
ip,
139+
type: ipType,
140+
country,
141+
region,
142+
city,
143+
});
126144

127145
return {
128-
country: response.country?.names?.en,
146+
country,
129147
region,
130148
city,
131149
};
132150
} catch (error) {
133-
// Handle AddressNotFoundError specifically (IP not in database)
134151
if (error instanceof AddressNotFoundError) {
152+
logger.debug('IP not found in database', { ip, type: ipType });
135153
return { country: undefined, region: undefined, city: undefined };
136154
}
137-
138-
// Handle BadMethodCallError (wrong database type)
139-
if (error instanceof BadMethodCallError) {
140-
logger.error(
141-
'Database type mismatch - using city() method with ipinfo database'
142-
);
143-
return { country: undefined, region: undefined, city: undefined };
144-
}
145-
146-
logger.error('Error looking up IP:', { ip, error });
155+
logger.error('Error looking up IP:', {
156+
ip,
157+
type: ipType,
158+
error: error instanceof Error ? error.message : String(error),
159+
stack: error instanceof Error ? error.stack : undefined,
160+
});
147161
return { country: undefined, region: undefined, city: undefined };
148162
}
149163
}

0 commit comments

Comments
 (0)