|
1 | 1 | import { createHash } from 'node:crypto'; |
2 | 2 | import { isIP } from 'node:net'; |
3 | | -import type { City } from '@maxmind/geoip2-node'; |
| 3 | +import type { City, Country } from '@maxmind/geoip2-node'; |
4 | 4 | import { AddressNotFoundError, Reader } from '@maxmind/geoip2-node'; |
5 | 5 |
|
6 | 6 | // import { logger } from '../lib/logger'; |
7 | 7 | const logger = console; |
8 | 8 |
|
9 | 9 | interface GeoIPReader extends Reader { |
10 | 10 | city(ip: string): City; |
| 11 | + country(ip: string): Country; |
11 | 12 | } |
12 | 13 |
|
13 | 14 | const IPV4_URL = process.env.IPV4_URL; |
@@ -126,24 +127,51 @@ export async function getGeoLocation(ip: string) { |
126 | 127 | } |
127 | 128 |
|
128 | 129 | try { |
129 | | - const response = reader.city(ip); |
130 | | - const region = response.subdivisions?.[0]?.names?.en; |
131 | | - const city = response.city?.names?.en; |
132 | | - const country = response.country?.names?.en; |
133 | | - |
134 | | - logger.debug('IP lookup successful', { |
135 | | - ip, |
136 | | - type: ipType, |
137 | | - country, |
138 | | - region, |
139 | | - city, |
140 | | - }); |
141 | | - |
142 | | - return { |
143 | | - country, |
144 | | - region, |
145 | | - city, |
146 | | - }; |
| 130 | + // Try city method first (for full city databases) |
| 131 | + try { |
| 132 | + const response = reader.city(ip); |
| 133 | + const region = response.subdivisions?.[0]?.names?.en; |
| 134 | + const city = response.city?.names?.en; |
| 135 | + const country = response.country?.names?.en; |
| 136 | + |
| 137 | + logger.debug('IP lookup successful (city)', { |
| 138 | + ip, |
| 139 | + type: ipType, |
| 140 | + country, |
| 141 | + region, |
| 142 | + city, |
| 143 | + }); |
| 144 | + |
| 145 | + return { |
| 146 | + country, |
| 147 | + region, |
| 148 | + city, |
| 149 | + }; |
| 150 | + } catch (cityError) { |
| 151 | + // If city method fails, try country method (for country-only databases) |
| 152 | + if ( |
| 153 | + cityError instanceof Error && |
| 154 | + cityError.message.includes('cannot be used with') |
| 155 | + ) { |
| 156 | + logger.debug('Falling back to country lookup', { ip, type: ipType }); |
| 157 | + |
| 158 | + const response = reader.country(ip); |
| 159 | + const country = response.country?.names?.en; |
| 160 | + |
| 161 | + logger.debug('IP lookup successful (country)', { |
| 162 | + ip, |
| 163 | + type: ipType, |
| 164 | + country, |
| 165 | + }); |
| 166 | + |
| 167 | + return { |
| 168 | + country, |
| 169 | + region: undefined, |
| 170 | + city: undefined, |
| 171 | + }; |
| 172 | + } |
| 173 | + throw cityError; |
| 174 | + } |
147 | 175 | } catch (error) { |
148 | 176 | if (error instanceof AddressNotFoundError) { |
149 | 177 | logger.debug('IP not found in database', { ip, type: ipType }); |
|
0 commit comments