ip2ldb-reader
Advanced tools
+186
| //#region src/interfaces.d.ts | ||
| interface Ip2lOptions { | ||
| /** Reload database when file changes (default: false) */ | ||
| reloadOnDbUpdate?: boolean; | ||
| /** Cache database in memory (default: false) */ | ||
| cacheDatabaseInMemory?: boolean; | ||
| /** Path to IP2Location subdivision CSV database (default: undefined) */ | ||
| subdivisionCsvPath?: string; | ||
| /** Path to IP2Location GeoNameID CSV database (default: undefined) */ | ||
| geoNameIdCsvPath?: string; | ||
| /** Path to IP2Location Country Info CSV database (default: undefined) */ | ||
| countryInfoCsvPath?: string; | ||
| /** Path to IATA/ICAO airport CSV database (default: undefined) */ | ||
| iataIcaoCsvPath?: string; | ||
| /** Path to IP2Location Continent Multilingual CSV database (default: undefined) */ | ||
| continentCsvPath?: string; | ||
| /** Path to IP2Location Olson Time Zone CSV database (default: undefined) */ | ||
| olsonTzCsvPath?: string; | ||
| } | ||
| interface CountryInfoData { | ||
| [key: string]: string | number | null | undefined; | ||
| /** Capital of the country */ | ||
| capital: string; | ||
| /** Country-Code Top-Level Domain */ | ||
| cctld?: string; | ||
| /** Three-character country code based on ISO 3166 */ | ||
| country_alpha3_code?: string; | ||
| /** Two-character country code based on ISO 3166 */ | ||
| country_code: string; | ||
| /** Demonym of the country */ | ||
| country_demonym?: string; | ||
| /** Country name based on ISO 3166 */ | ||
| country_name?: string; | ||
| /** Three-character country numeric code based on ISO 3166 */ | ||
| country_numeric_code?: number | null; | ||
| /** Currency code based on ISO 4217 */ | ||
| currency_code?: string; | ||
| /** Currency name */ | ||
| currency_name?: string; | ||
| /** Currency symbol */ | ||
| currency_symbol?: string; | ||
| /** The IDD prefix to call the city from another country */ | ||
| idd_code?: number | null; | ||
| /** Language code based on ISO 639 */ | ||
| lang_code?: string; | ||
| /** Language name */ | ||
| lang_name?: string; | ||
| /** Population of year 2014 */ | ||
| population?: number | null; | ||
| /** Total area in square-km */ | ||
| total_area: number | null; | ||
| } | ||
| interface ContinentData { | ||
| /** Two-character continent code (AF, AN, AS, EU, NA, OC, SA) */ | ||
| continent_code: string; | ||
| /** Continent name */ | ||
| continent: string; | ||
| } | ||
| interface OlsonTzData { | ||
| /** Time zone abbreviation */ | ||
| abbreviation: string; | ||
| /** The UTC ISO-8601 date when Daylight Saving Time ends */ | ||
| dst_end: string | null; | ||
| /** The UTC ISO-8601 date when Daylight Saving Time begins */ | ||
| dst_start: string | null; | ||
| /** Olson time zone */ | ||
| olson_tz: string; | ||
| } | ||
| interface IataIcaoData { | ||
| [key: string]: string | number | null | undefined; | ||
| /** Airport name */ | ||
| airport: string; | ||
| /** Three-character code of IATA airport code */ | ||
| iata: string; | ||
| /** Four-character code of ICAO airport code */ | ||
| icao: string; | ||
| /** Latitude of the airport */ | ||
| latitude: number | null; | ||
| /** Longitude of the airport */ | ||
| longitude: number | null; | ||
| } | ||
| interface Ip2lData { | ||
| /** IP address */ | ||
| ip: string | null; | ||
| /** IP number */ | ||
| ip_no: string | null; | ||
| /** Status of geolocation query */ | ||
| status: string | null; | ||
| /** Address type */ | ||
| addresstype?: string; | ||
| /** IATA/ICAO airport info */ | ||
| airports?: IataIcaoData[]; | ||
| /** A varying length number assigned to geographic areas for calls between cities */ | ||
| areacode?: string; | ||
| /** Autonomous system */ | ||
| as?: string; | ||
| /** CIDR range for the whole autonomous system */ | ||
| ascidr?: string; | ||
| /** Domain name of the autonomous system registrant */ | ||
| asdomain?: string; | ||
| /** Autonomous system number */ | ||
| asn?: string; | ||
| /** Usage type of the autonomous system registrant */ | ||
| asusagetype?: string; | ||
| /** IAB category */ | ||
| category?: string; | ||
| /** City name */ | ||
| city?: string; | ||
| /** Continent */ | ||
| continent?: ContinentData | null; | ||
| /** Country info */ | ||
| country_info?: CountryInfoData | null; | ||
| /** Country name */ | ||
| country_long?: string; | ||
| /** ISO 3166-1 country code */ | ||
| country_short?: string; | ||
| /** District name */ | ||
| district?: string; | ||
| /** Internet domain name associated with IP address range */ | ||
| domain?: string; | ||
| /** Average height of city above sea level in meters (m) */ | ||
| elevation?: string; | ||
| /** GeoName ID */ | ||
| geoname_id?: number | null; | ||
| /** The IDD prefix to call the city from another country */ | ||
| iddcode?: string; | ||
| /** Internet Service Provider or company name */ | ||
| isp?: string; | ||
| /** City latitude; defaults to capital city latitude if city is unknown */ | ||
| latitude?: number | null; | ||
| /** City longitude; defaults to capital city longitude if city is unknown */ | ||
| longitude?: number | null; | ||
| /** Mobile Country Code (MCC) */ | ||
| mcc?: string; | ||
| /** Mobile Network Code (MNC) */ | ||
| mnc?: string; | ||
| /** Commercial brand associated with the mobile carrier */ | ||
| mobilebrand?: string; | ||
| /** Internet connection type (DIAL, DSL, COMP) */ | ||
| netspeed?: string; | ||
| /** Olson time zone */ | ||
| olson_timezone?: OlsonTzData | null; | ||
| /** Region or state name */ | ||
| region?: string; | ||
| /** Subdivision part of ISO 3166-2 country-subdivision code */ | ||
| subdivision?: string | null; | ||
| /** UTC time zone (DST is supported) */ | ||
| timezone?: string; | ||
| /** Usage type classification of ISP or company */ | ||
| usagetype?: string; | ||
| /** The code of the nearest weather observation station */ | ||
| weatherstationcode?: string; | ||
| /** The name of the nearest weather observation station */ | ||
| weatherstationname?: string; | ||
| /** ZIP/Postal code */ | ||
| zipcode?: string; | ||
| } | ||
| //#endregion | ||
| //#region src/index.d.ts | ||
| declare class Ip2lReader { | ||
| private dbReader_; | ||
| private subdivReader_?; | ||
| private geoNameIdReader_?; | ||
| private countryInfoReader_?; | ||
| private iataIcaoReader_?; | ||
| private continentReader_?; | ||
| private olsonTzReader_?; | ||
| constructor(); | ||
| /** | ||
| * Initialize IP2Location database reader(s) | ||
| * @param dbPath IP2Location BIN database | ||
| * @param options Options for database reader | ||
| */ | ||
| init(dbPath: string, options?: Ip2lOptions): Promise<void>; | ||
| /** | ||
| * Query IP2Location database with an IP and get location information | ||
| * @param ip IP address | ||
| */ | ||
| get(ip: string): Ip2lData; | ||
| /** | ||
| * Close IP2Location database(s) and uninitialize reader(s) | ||
| */ | ||
| close(): void; | ||
| } | ||
| //#endregion | ||
| export { Ip2lData, Ip2lOptions, Ip2lReader, Ip2lReader as default }; |
+1678
| import fs from "node:fs"; | ||
| import net from "node:net"; | ||
| import { parse } from "csv-parse"; | ||
| //#region src/ip-utils.ts | ||
| const FROM_6TO4 = BigInt("42545680458834377588178886921629466624"); | ||
| const TO_6TO4 = BigInt("42550872755692912415807417417958686719"); | ||
| const FROM_TEREDO = BigInt("42540488161975842760550356425300246528"); | ||
| const TO_TEREDO = BigInt("42540488241204005274814694018844196863"); | ||
| const LAST_32BITS = BigInt("4294967295"); | ||
| /** | ||
| * Check whether an IPv6 address can be expressed as IPv4 | ||
| * and use IPv4 if available. Remove zone index from link | ||
| * local IPv6 address. Calculate IP number. | ||
| * @param ip IP address | ||
| */ | ||
| function parseIp(ip) { | ||
| let ipVersion = net.isIP(ip); | ||
| let ipNum = BigInt(0); | ||
| if (ipVersion === 6) { | ||
| if (/^[:0]+:F{4}:(\d+\.){3}\d+$/i.test(ip)) { | ||
| ip = ip.replace(/^[:0]+:F{4}:/i, ""); | ||
| ipVersion = net.isIP(ip); | ||
| } else if (/^[:0]+F{4}(:[\dA-Z]{4}){2}$/i.test(ip)) { | ||
| ip = (ip.replace(/^[:0]+F{4}:/i, "").replace(/:/, "").match(/../g) ?? []).map((b) => parseInt("0x" + b)).join("."); | ||
| ipVersion = net.isIP(ip); | ||
| } | ||
| } | ||
| if (ipVersion) ({ipNum, ipVersion} = getIpNum(ip, ipVersion)); | ||
| return { | ||
| ip, | ||
| ipVersion, | ||
| ipNum | ||
| }; | ||
| } | ||
| /** | ||
| * Get numeric IP and verify version | ||
| * @param ip IP address | ||
| * @param ipVersion IP version | ||
| */ | ||
| function getIpNum(ip, ipVersion) { | ||
| let ipNum = BigInt(0); | ||
| if (ipVersion === 4) { | ||
| const d = ip.split("."); | ||
| ipNum = BigInt(((+d[0] * 256 + +d[1]) * 256 + +d[2]) * 256 + +d[3]); | ||
| } else if (ipVersion === 6) { | ||
| const maxsections = 8; | ||
| const sectionbits = 16; | ||
| const m = ip.split("::"); | ||
| let total = BigInt(0); | ||
| if (m.length === 2) { | ||
| const arrLeft = m[0] !== "" ? m[0].split(":") : []; | ||
| const arrRight = m[1] !== "" ? m[1].split(":") : []; | ||
| for (let x = 0; x < arrLeft.length; x++) total += BigInt(parseInt("0x" + arrLeft[x])) << BigInt((maxsections - (x + 1)) * sectionbits); | ||
| for (let x = 0; x < arrRight.length; x++) total += BigInt(parseInt("0x" + arrRight[x])) << BigInt((arrRight.length - (x + 1)) * sectionbits); | ||
| } else if (m.length === 1) { | ||
| const arr = m[0].split(":"); | ||
| for (let x = 0; x < arr.length; x++) total += BigInt(parseInt("0x" + arr[x])) << BigInt((maxsections - (x + 1)) * sectionbits); | ||
| } | ||
| ipNum = total; | ||
| if (ipNum >= FROM_6TO4 && ipNum <= TO_6TO4) { | ||
| ipNum = ipNum >> BigInt(80) & LAST_32BITS; | ||
| ipVersion = 4; | ||
| } else if (ipNum >= FROM_TEREDO && ipNum <= TO_TEREDO) { | ||
| ipNum = ~ipNum & LAST_32BITS; | ||
| ipVersion = 4; | ||
| } | ||
| } | ||
| return { | ||
| ipNum, | ||
| ipVersion | ||
| }; | ||
| } | ||
| //#endregion | ||
| //#region src/db-reader.ts | ||
| const Position = { | ||
| country: [ | ||
| 0, | ||
| 2, | ||
| 2, | ||
| 2, | ||
| 2, | ||
| 2, | ||
| 2, | ||
| 2, | ||
| 2, | ||
| 2, | ||
| 2, | ||
| 2, | ||
| 2, | ||
| 2, | ||
| 2, | ||
| 2, | ||
| 2, | ||
| 2, | ||
| 2, | ||
| 2, | ||
| 2, | ||
| 2, | ||
| 2, | ||
| 2, | ||
| 2, | ||
| 2, | ||
| 2 | ||
| ], | ||
| region: [ | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 3, | ||
| 3, | ||
| 3, | ||
| 3, | ||
| 3, | ||
| 3, | ||
| 3, | ||
| 3, | ||
| 3, | ||
| 3, | ||
| 3, | ||
| 3, | ||
| 3, | ||
| 3, | ||
| 3, | ||
| 3, | ||
| 3, | ||
| 3, | ||
| 3, | ||
| 3, | ||
| 3, | ||
| 3, | ||
| 3, | ||
| 3 | ||
| ], | ||
| city: [ | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 4, | ||
| 4, | ||
| 4, | ||
| 4, | ||
| 4, | ||
| 4, | ||
| 4, | ||
| 4, | ||
| 4, | ||
| 4, | ||
| 4, | ||
| 4, | ||
| 4, | ||
| 4, | ||
| 4, | ||
| 4, | ||
| 4, | ||
| 4, | ||
| 4, | ||
| 4, | ||
| 4, | ||
| 4, | ||
| 4, | ||
| 4 | ||
| ], | ||
| isp: [ | ||
| 0, | ||
| 0, | ||
| 3, | ||
| 0, | ||
| 5, | ||
| 0, | ||
| 7, | ||
| 5, | ||
| 7, | ||
| 0, | ||
| 8, | ||
| 0, | ||
| 9, | ||
| 0, | ||
| 9, | ||
| 0, | ||
| 9, | ||
| 0, | ||
| 9, | ||
| 7, | ||
| 9, | ||
| 0, | ||
| 9, | ||
| 7, | ||
| 9, | ||
| 9, | ||
| 9 | ||
| ], | ||
| latitude: [ | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 5, | ||
| 5, | ||
| 0, | ||
| 5, | ||
| 5, | ||
| 5, | ||
| 5, | ||
| 5, | ||
| 5, | ||
| 5, | ||
| 5, | ||
| 5, | ||
| 5, | ||
| 5, | ||
| 5, | ||
| 5, | ||
| 5, | ||
| 5, | ||
| 5, | ||
| 5, | ||
| 5, | ||
| 5 | ||
| ], | ||
| longitude: [ | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 6, | ||
| 6, | ||
| 0, | ||
| 6, | ||
| 6, | ||
| 6, | ||
| 6, | ||
| 6, | ||
| 6, | ||
| 6, | ||
| 6, | ||
| 6, | ||
| 6, | ||
| 6, | ||
| 6, | ||
| 6, | ||
| 6, | ||
| 6, | ||
| 6, | ||
| 6, | ||
| 6, | ||
| 6 | ||
| ], | ||
| domain: [ | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 6, | ||
| 8, | ||
| 0, | ||
| 9, | ||
| 0, | ||
| 10, | ||
| 0, | ||
| 10, | ||
| 0, | ||
| 10, | ||
| 0, | ||
| 10, | ||
| 8, | ||
| 10, | ||
| 0, | ||
| 10, | ||
| 8, | ||
| 10, | ||
| 10, | ||
| 10 | ||
| ], | ||
| zipcode: [ | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 7, | ||
| 7, | ||
| 7, | ||
| 7, | ||
| 0, | ||
| 7, | ||
| 7, | ||
| 7, | ||
| 0, | ||
| 7, | ||
| 0, | ||
| 7, | ||
| 7, | ||
| 7, | ||
| 0, | ||
| 7, | ||
| 7, | ||
| 7 | ||
| ], | ||
| timezone: [ | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 8, | ||
| 8, | ||
| 7, | ||
| 8, | ||
| 8, | ||
| 8, | ||
| 7, | ||
| 8, | ||
| 0, | ||
| 8, | ||
| 8, | ||
| 8, | ||
| 0, | ||
| 8, | ||
| 8, | ||
| 8 | ||
| ], | ||
| netspeed: [ | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 8, | ||
| 11, | ||
| 0, | ||
| 11, | ||
| 8, | ||
| 11, | ||
| 0, | ||
| 11, | ||
| 0, | ||
| 11, | ||
| 0, | ||
| 11, | ||
| 11, | ||
| 11 | ||
| ], | ||
| iddcode: [ | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 9, | ||
| 12, | ||
| 0, | ||
| 12, | ||
| 0, | ||
| 12, | ||
| 9, | ||
| 12, | ||
| 0, | ||
| 12, | ||
| 12, | ||
| 12 | ||
| ], | ||
| areacode: [ | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 10, | ||
| 13, | ||
| 0, | ||
| 13, | ||
| 0, | ||
| 13, | ||
| 10, | ||
| 13, | ||
| 0, | ||
| 13, | ||
| 13, | ||
| 13 | ||
| ], | ||
| weatherstationcode: [ | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 9, | ||
| 14, | ||
| 0, | ||
| 14, | ||
| 0, | ||
| 14, | ||
| 0, | ||
| 14, | ||
| 14, | ||
| 14 | ||
| ], | ||
| weatherstationname: [ | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 10, | ||
| 15, | ||
| 0, | ||
| 15, | ||
| 0, | ||
| 15, | ||
| 0, | ||
| 15, | ||
| 15, | ||
| 15 | ||
| ], | ||
| mcc: [ | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 9, | ||
| 16, | ||
| 0, | ||
| 16, | ||
| 9, | ||
| 16, | ||
| 16, | ||
| 16 | ||
| ], | ||
| mnc: [ | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 10, | ||
| 17, | ||
| 0, | ||
| 17, | ||
| 10, | ||
| 17, | ||
| 17, | ||
| 17 | ||
| ], | ||
| mobilebrand: [ | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 11, | ||
| 18, | ||
| 0, | ||
| 18, | ||
| 11, | ||
| 18, | ||
| 18, | ||
| 18 | ||
| ], | ||
| elevation: [ | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 11, | ||
| 19, | ||
| 0, | ||
| 19, | ||
| 19, | ||
| 19 | ||
| ], | ||
| usagetype: [ | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 12, | ||
| 20, | ||
| 20, | ||
| 20 | ||
| ], | ||
| addresstype: [ | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 21, | ||
| 21 | ||
| ], | ||
| category: [ | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 22, | ||
| 22 | ||
| ], | ||
| district: [ | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 23 | ||
| ], | ||
| asn: [ | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 24 | ||
| ], | ||
| as: [ | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 25 | ||
| ], | ||
| asdomain: [ | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 26 | ||
| ], | ||
| asusagetype: [ | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 27 | ||
| ], | ||
| ascidr: [ | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 28 | ||
| ] | ||
| }; | ||
| const MAX_SIZE = 65536; | ||
| const MAX_IPV4_RANGE = BigInt("4294967295"); | ||
| const MAX_IPV6_RANGE = BigInt("340282366920938463463374607431768211455"); | ||
| var DbReader = class { | ||
| readerStatus_; | ||
| dbPath_; | ||
| cacheInMemory_; | ||
| fd_; | ||
| dbCache_; | ||
| fsWatcher_; | ||
| indiciesIPv4_; | ||
| indiciesIPv6_; | ||
| offset_; | ||
| enabled_; | ||
| dbStats_; | ||
| constructor() { | ||
| this.readerStatus_ = 0; | ||
| this.dbPath_ = null; | ||
| this.cacheInMemory_ = false; | ||
| this.fd_ = null; | ||
| this.dbCache_ = null; | ||
| this.fsWatcher_ = null; | ||
| this.indiciesIPv4_ = []; | ||
| this.indiciesIPv6_ = []; | ||
| this.offset_ = {}; | ||
| this.enabled_ = {}; | ||
| this.dbStats_ = { | ||
| DBType: 0, | ||
| DBColumn: 0, | ||
| DBYear: 0, | ||
| DBMonth: 0, | ||
| DBDay: 0, | ||
| DBCount: 0, | ||
| DBCountIPv6: 0, | ||
| BaseAddr: 0, | ||
| BaseAddrIPv6: 0, | ||
| IndexBaseAddr: 0, | ||
| IndexBaseAddrIPv6: 0, | ||
| ColumnSize: 0, | ||
| ColumnSizeIPv6: 0, | ||
| ProductCode: 0, | ||
| ProductType: 0, | ||
| FileSize: 0, | ||
| Indexed: false, | ||
| IndexedIPv6: false, | ||
| OldBIN: false | ||
| }; | ||
| } | ||
| /** | ||
| * Read data from database into a buffer | ||
| * @param readbytes Number of bytes to read | ||
| * @param pos Offset from beginning of database | ||
| */ | ||
| readToBuffer(readbytes, pos) { | ||
| if (this.dbCache_) { | ||
| const buff = this.dbCache_.subarray(pos, pos + readbytes); | ||
| return buff.length === readbytes ? buff : void 0; | ||
| } | ||
| if (!this.fd_) throw new Error("Missing file descriptor, cannot read data"); | ||
| const buff = Buffer.alloc(readbytes); | ||
| return fs.readSync(this.fd_, buff, 0, readbytes, pos) === readbytes ? buff : void 0; | ||
| } | ||
| /** | ||
| * Read 8-bit integer from the database | ||
| * @param pos Offset from beginning of database | ||
| */ | ||
| readInt8(pos) { | ||
| const buff = this.readToBuffer(1, pos - 1); | ||
| return buff ? buff.readUInt8(0) : void 0; | ||
| } | ||
| /** | ||
| * Read 32-bit integer from the database | ||
| * @param pos Offset from beginning of database | ||
| */ | ||
| readInt32(pos) { | ||
| const buff = this.readToBuffer(4, pos - 1); | ||
| return buff ? buff.readUInt32LE(0) : void 0; | ||
| } | ||
| /** | ||
| * Read 32-bit integer from the database as a BigInt | ||
| * @param pos Offset from beginning of database | ||
| */ | ||
| readInt32Big(pos) { | ||
| const buff = this.readToBuffer(4, pos - 1); | ||
| return buff ? BigInt(buff.readUInt32LE(0)) : void 0; | ||
| } | ||
| /** | ||
| * Read 32-bit float from the database | ||
| * @param pos Offset from beginning of database | ||
| */ | ||
| readFloat(pos) { | ||
| const buff = this.readToBuffer(4, pos - 1); | ||
| return buff ? buff.readFloatLE(0) : void 0; | ||
| } | ||
| /** | ||
| * Read 128-bit integer from the database as a BigInt | ||
| * @param pos Offset from beginning of database | ||
| */ | ||
| readInt128Big(pos) { | ||
| const buff = this.readToBuffer(16, pos - 1); | ||
| if (!buff) return; | ||
| let ret = BigInt(0); | ||
| for (let x = 0; x < 16; x++) ret += BigInt(buff.readUInt8(x)) << BigInt(8 * x); | ||
| return ret; | ||
| } | ||
| /** | ||
| * Read string from the database | ||
| * @param pos Offset from beginning of database | ||
| */ | ||
| readString(pos) { | ||
| const strBytes = this.readInt8(pos + 1); | ||
| if (!strBytes) return; | ||
| const buff = this.readToBuffer(strBytes, pos + 1); | ||
| const str = buff ? buff.toString("utf8") : void 0; | ||
| return str === "-" ? "" : str; | ||
| } | ||
| /** | ||
| * Read 32-bit integer from a Buffer | ||
| * @param pos Offset from beginning of buffer | ||
| * @param buff Buffer | ||
| */ | ||
| readBufferInt32(pos, buff) { | ||
| return buff.readUInt32LE(pos); | ||
| } | ||
| /** | ||
| * Read 32-bit float from a buffer | ||
| * @param pos Offset from beginning of buffer | ||
| * @param buff Buffer | ||
| */ | ||
| readBufferFloat(pos, buff) { | ||
| return buff.readFloatLE(pos); | ||
| } | ||
| /** | ||
| * (Re)load database | ||
| */ | ||
| loadDatabase() { | ||
| if (!this.dbPath_) throw new Error("Path to database not available"); | ||
| this.readerStatus_ = 1; | ||
| if (this.fd_ !== null) try { | ||
| fs.closeSync(this.fd_); | ||
| } catch {} | ||
| if (this.cacheInMemory_) { | ||
| this.fd_ = null; | ||
| this.dbCache_ = fs.readFileSync(this.dbPath_); | ||
| } else { | ||
| this.fd_ = fs.openSync(this.dbPath_, "r"); | ||
| this.dbCache_ = null; | ||
| } | ||
| this.dbStats_.DBType = this.readInt8(1) ?? 0; | ||
| this.dbStats_.DBColumn = this.readInt8(2) ?? 0; | ||
| this.dbStats_.DBYear = this.readInt8(3) ?? 0; | ||
| this.dbStats_.DBMonth = this.readInt8(4) ?? 0; | ||
| this.dbStats_.DBDay = this.readInt8(5) ?? 0; | ||
| this.dbStats_.DBCount = this.readInt32(6) ?? 0; | ||
| this.dbStats_.BaseAddr = this.readInt32(10) ?? 0; | ||
| this.dbStats_.DBCountIPv6 = this.readInt32(14) ?? 0; | ||
| this.dbStats_.BaseAddrIPv6 = this.readInt32(18) ?? 0; | ||
| this.dbStats_.IndexBaseAddr = this.readInt32(22) ?? 0; | ||
| this.dbStats_.IndexBaseAddrIPv6 = this.readInt32(26) ?? 0; | ||
| this.dbStats_.ProductCode = this.readInt8(30) ?? 0; | ||
| this.dbStats_.ProductType = this.readInt8(31) ?? 0; | ||
| this.dbStats_.FileSize = this.readInt32(32) ?? 0; | ||
| if (this.dbStats_.ProductCode !== 1 && this.dbStats_.DBYear >= 21) throw new Error("Incorrect IP2Location BIN file format. Please make sure that you are using the latest IP2Location BIN file."); | ||
| if (this.dbStats_.DBType == "P".charCodeAt(0) && this.dbStats_.DBColumn === "K".charCodeAt(0)) throw new Error("Incorrect IP2Location BIN file format. Please uncompress zip file."); | ||
| this.dbStats_.Indexed = this.dbStats_.IndexBaseAddr > 0; | ||
| this.dbStats_.OldBIN = !this.dbStats_.DBCountIPv6; | ||
| this.dbStats_.IndexedIPv6 = !this.dbStats_.OldBIN && this.dbStats_.IndexBaseAddrIPv6 > 0; | ||
| this.dbStats_.ColumnSize = this.dbStats_.DBColumn << 2; | ||
| this.dbStats_.ColumnSizeIPv6 = 16 + (this.dbStats_.DBColumn - 1 << 2); | ||
| const dbType = this.dbStats_.DBType; | ||
| for (const key of Object.keys(Position)) { | ||
| this.offset_[key] = Position[key][dbType] ? Position[key][dbType] - 2 << 2 : 0; | ||
| this.enabled_[key] = Boolean(Position[key][dbType]); | ||
| } | ||
| this.indiciesIPv4_ = new Array(MAX_SIZE); | ||
| this.indiciesIPv6_ = new Array(MAX_SIZE); | ||
| if (this.dbStats_.Indexed) { | ||
| let pointer = this.dbStats_.IndexBaseAddr; | ||
| for (let x = 0; x < MAX_SIZE; x++) { | ||
| this.indiciesIPv4_[x] = [this.readInt32(pointer) ?? 0, this.readInt32(pointer + 4) ?? 0]; | ||
| pointer += 8; | ||
| } | ||
| if (this.dbStats_.IndexedIPv6) for (let x = 0; x < MAX_SIZE; x++) { | ||
| this.indiciesIPv6_[x] = [this.readInt32(pointer) ?? 0, this.readInt32(pointer + 4) ?? 0]; | ||
| pointer += 8; | ||
| } | ||
| } | ||
| this.readerStatus_ = 2; | ||
| } | ||
| /** | ||
| * Get reader status | ||
| */ | ||
| get readerStatus() { | ||
| return this.readerStatus_; | ||
| } | ||
| /** | ||
| * Close database and uninitialize reader | ||
| */ | ||
| close() { | ||
| this.readerStatus_ = 0; | ||
| if (this.fd_ !== null) try { | ||
| fs.closeSync(this.fd_); | ||
| } catch {} | ||
| if (this.fsWatcher_ !== null) this.fsWatcher_.close(); | ||
| this.dbPath_ = null; | ||
| this.cacheInMemory_ = false; | ||
| this.fd_ = null; | ||
| this.dbCache_ = null; | ||
| this.fsWatcher_ = null; | ||
| this.indiciesIPv4_ = []; | ||
| this.indiciesIPv6_ = []; | ||
| this.offset_ = {}; | ||
| this.enabled_ = {}; | ||
| this.dbStats_ = { | ||
| DBType: 0, | ||
| DBColumn: 0, | ||
| DBYear: 0, | ||
| DBMonth: 0, | ||
| DBDay: 0, | ||
| DBCount: 0, | ||
| DBCountIPv6: 0, | ||
| BaseAddr: 0, | ||
| BaseAddrIPv6: 0, | ||
| IndexBaseAddr: 0, | ||
| IndexBaseAddrIPv6: 0, | ||
| ColumnSize: 0, | ||
| ColumnSizeIPv6: 0, | ||
| ProductCode: 0, | ||
| ProductType: 0, | ||
| FileSize: 0, | ||
| Indexed: false, | ||
| IndexedIPv6: false, | ||
| OldBIN: false | ||
| }; | ||
| } | ||
| /** | ||
| * Initialize IP2Location database reader | ||
| * @param dbPath IP2Location BIN database | ||
| * @param options Options for database reader | ||
| */ | ||
| init(dbPath, options) { | ||
| if (!dbPath) throw new Error("Must specify path to database"); | ||
| this.dbPath_ = dbPath; | ||
| this.cacheInMemory_ = options?.cacheDatabaseInMemory ?? false; | ||
| this.loadDatabase(); | ||
| if (options?.reloadOnDbUpdate) this.watchDbFile(); | ||
| } | ||
| /** | ||
| * Watch database file for changes and re-init if a change is detected | ||
| */ | ||
| watchDbFile() { | ||
| let timeout = null; | ||
| const originalState = this.readerStatus_; | ||
| const dbChangeHandler = (filename) => { | ||
| if (filename && this.dbPath_ && fs.existsSync(this.dbPath_)) { | ||
| this.fsWatcher_?.close(); | ||
| this.loadDatabase(); | ||
| this.fsWatcher_ = getFsWatch(); | ||
| } else this.readerStatus_ = originalState; | ||
| }; | ||
| const getFsWatch = () => { | ||
| if (!this.dbPath_) throw new Error("Path to database not available"); | ||
| return fs.watch(this.dbPath_, (eventType, filename) => { | ||
| if (!filename) return; | ||
| if (this.fd_) this.readerStatus_ = 1; | ||
| if (timeout !== null) clearTimeout(timeout); | ||
| timeout = setTimeout(() => { | ||
| timeout = null; | ||
| dbChangeHandler(filename); | ||
| }, 500); | ||
| }); | ||
| }; | ||
| this.fsWatcher_ = getFsWatch(); | ||
| } | ||
| /** | ||
| * Populate data object with database query results for an IP address | ||
| * @param ipNum IP number | ||
| * @param ipVersion IP version | ||
| * @param data Output data object | ||
| */ | ||
| query(ipNum, ipVersion, data) { | ||
| let low = 0; | ||
| let high; | ||
| let maxIpRange; | ||
| let baseAddr; | ||
| let columnSize; | ||
| if (ipVersion === 6) { | ||
| maxIpRange = MAX_IPV6_RANGE; | ||
| high = this.dbStats_.DBCountIPv6; | ||
| baseAddr = this.dbStats_.BaseAddrIPv6; | ||
| columnSize = this.dbStats_.ColumnSizeIPv6; | ||
| if (this.dbStats_.IndexedIPv6) { | ||
| const indexaddr = Number(ipNum >> BigInt(112)); | ||
| low = this.indiciesIPv6_[indexaddr][0]; | ||
| high = this.indiciesIPv6_[indexaddr][1]; | ||
| } | ||
| } else { | ||
| maxIpRange = MAX_IPV4_RANGE; | ||
| high = this.dbStats_.DBCount; | ||
| baseAddr = this.dbStats_.BaseAddr; | ||
| columnSize = this.dbStats_.ColumnSize; | ||
| if (this.dbStats_.Indexed) { | ||
| const indexaddr = Number(ipNum) >>> 16; | ||
| low = this.indiciesIPv4_[indexaddr][0]; | ||
| high = this.indiciesIPv4_[indexaddr][1]; | ||
| } | ||
| } | ||
| if (ipNum >= maxIpRange) ipNum = maxIpRange - BigInt(1); | ||
| while (low <= high) { | ||
| const mid = Math.floor((low + high) / 2); | ||
| const rowoffset = baseAddr + mid * columnSize; | ||
| const rowoffset2 = rowoffset + columnSize; | ||
| const ipfrom = ipVersion === 6 ? this.readInt128Big(rowoffset) : this.readInt32Big(rowoffset); | ||
| const ipto = ipVersion === 6 ? this.readInt128Big(rowoffset2) : this.readInt32Big(rowoffset2); | ||
| if (ipfrom === void 0 || ipto === void 0) break; | ||
| if (ipfrom <= ipNum && ipto > ipNum) { | ||
| const firstcol = ipVersion === 6 ? 16 : 4; | ||
| const buff = this.readToBuffer(columnSize - firstcol, rowoffset + firstcol - 1); | ||
| if (!buff) break; | ||
| const enabledKeys = Object.keys(this.enabled_).filter((key) => this.enabled_[key]); | ||
| for (const key of enabledKeys) if (key === "country") { | ||
| const countrypos = this.readBufferInt32(this.offset_[key], buff); | ||
| data.country_short = this.readString(countrypos) ?? ""; | ||
| data.country_long = this.readString(countrypos + 3) ?? ""; | ||
| } else if (key === "longitude" || key === "latitude") { | ||
| const num = this.readBufferFloat(this.offset_[key], buff); | ||
| data[key] = num !== 0 ? Math.round(num * 1e6) / 1e6 : null; | ||
| } else data[key] = this.readString(this.readBufferInt32(this.offset_[key], buff)) ?? ""; | ||
| data.status = "OK"; | ||
| return; | ||
| } else if (ipfrom > ipNum) high = mid - 1; | ||
| else low = mid + 1; | ||
| } | ||
| data.status = "IP_ADDRESS_NOT_FOUND"; | ||
| } | ||
| /** | ||
| * Query IP2Location database with an IP and get location information | ||
| * @param ip IP address | ||
| */ | ||
| get(ip) { | ||
| const data = { | ||
| ip, | ||
| ip_no: "", | ||
| status: "" | ||
| }; | ||
| if (this.readerStatus_ === 0) data.status = "NOT_INITIALIZED"; | ||
| else if (this.readerStatus_ === 1) data.status = "INITIALIZING"; | ||
| else if (!this.dbPath_ || !this.dbCache_ && !fs.existsSync(this.dbPath_)) data.status = "DATABASE_NOT_FOUND"; | ||
| else if (!this.dbStats_.DBType) data.status = "NOT_INITIALIZED"; | ||
| if (data.status) return data; | ||
| const { ipVersion, ipNum } = parseIp(ip); | ||
| if (!ipVersion) data.status = "INVALID_IP_ADDRESS"; | ||
| else if (ipVersion === 6 && this.dbStats_.OldBIN) data.status = "IPV6_NOT_SUPPORTED"; | ||
| if (data.status) return data; | ||
| data.ip_no = ipNum.toString(); | ||
| this.query(ipNum, ipVersion, data); | ||
| return data; | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/csv-reader.ts | ||
| var CsvReader = class { | ||
| readerStatus_; | ||
| fsWatcher_; | ||
| reloadPromise_; | ||
| requiredCsvHeaders_; | ||
| constructor() { | ||
| this.readerStatus_ = 0; | ||
| this.fsWatcher_ = null; | ||
| this.reloadPromise_ = Promise.resolve(); | ||
| this.requiredCsvHeaders_ = []; | ||
| } | ||
| /** | ||
| * Load IP2Location CSV databas | ||
| * @param csvPath Filesystem path to IP2Location CSV database | ||
| */ | ||
| async loadCsv(csvPath) { | ||
| const parser = fs.createReadStream(csvPath).pipe(parse({ columns: (header) => { | ||
| if (!this.requiredCsvHeaders_.every((h) => header.includes(h))) throw new Error("CSV database does not have expected headings"); | ||
| return header; | ||
| } })); | ||
| for await (const record of parser) this.processRecord(record); | ||
| } | ||
| /** | ||
| * Get reader status | ||
| */ | ||
| get readerStatus() { | ||
| return this.readerStatus_; | ||
| } | ||
| /** | ||
| * Get DB reload promise | ||
| */ | ||
| get reloadPromise() { | ||
| return this.reloadPromise_; | ||
| } | ||
| /** | ||
| * Close database and uninitialize reader | ||
| */ | ||
| close() { | ||
| this.readerStatus_ = 0; | ||
| if (this.fsWatcher_ !== null) this.fsWatcher_.close(); | ||
| this.fsWatcher_ = null; | ||
| this.resetData(); | ||
| } | ||
| /** | ||
| * Initialize reader | ||
| * @param dbPath IP2Location CSV database | ||
| * @param reloadOnDbUpdate Options for database reader | ||
| */ | ||
| async init(dbPath, reloadOnDbUpdate) { | ||
| if (!dbPath) throw new Error("Must specify path to CSV database"); | ||
| this.readerStatus_ = 1; | ||
| this.resetData(); | ||
| await this.loadCsv(dbPath); | ||
| if (reloadOnDbUpdate) this.watchDbFile(dbPath); | ||
| this.readerStatus_ = 2; | ||
| } | ||
| /** | ||
| * Watch database file for changes and re-init if a change is detected | ||
| * @param dbPath Path to watch | ||
| */ | ||
| watchDbFile(dbPath) { | ||
| let timeout = null; | ||
| const dbChangeHandler = (filename) => { | ||
| if (filename && fs.existsSync(dbPath)) { | ||
| if (this.fsWatcher_ !== null) { | ||
| this.fsWatcher_.close(); | ||
| this.fsWatcher_ = null; | ||
| } | ||
| this.reloadPromise_ = this.init(dbPath, true).catch(() => void 0); | ||
| } | ||
| }; | ||
| this.fsWatcher_ = fs.watch(dbPath, (eventType, filename) => { | ||
| if (!filename) return; | ||
| if (timeout !== null) clearTimeout(timeout); | ||
| timeout = setTimeout(() => { | ||
| timeout = null; | ||
| dbChangeHandler(filename); | ||
| }, 500); | ||
| }); | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/subdiv-reader.ts | ||
| var SubdivReader = class extends CsvReader { | ||
| subdivisionMap_; | ||
| constructor() { | ||
| super(); | ||
| this.subdivisionMap_ = {}; | ||
| this.requiredCsvHeaders_ = [ | ||
| "country_code", | ||
| "subdivision_name", | ||
| "code" | ||
| ]; | ||
| } | ||
| /** | ||
| * Process line from IP2Location subdivision database | ||
| * @param record Individual row from CSV database, broken into key/value pairs based on CSV headers | ||
| */ | ||
| processRecord(record) { | ||
| const { country_code, subdivision_name, code } = record; | ||
| const subdivisionCode = code?.length > 3 ? code.substring(3) : void 0; | ||
| if (!subdivisionCode) return; | ||
| const countryMap = this.subdivisionMap_[country_code] ?? {}; | ||
| countryMap[subdivision_name] = subdivisionCode; | ||
| this.subdivisionMap_[country_code] = countryMap; | ||
| } | ||
| /** | ||
| * Reset stored data | ||
| */ | ||
| resetData() { | ||
| this.subdivisionMap_ = {}; | ||
| } | ||
| /** | ||
| * Get the ISO 3166-2 subdivision part from a country code and region | ||
| * @param country ISO 3166-1 country code from IP2Location database | ||
| * @param region Region from from IP2Location database | ||
| */ | ||
| get(country, region) { | ||
| if (this.readerStatus !== 2) return null; | ||
| if (!country || !region) return null; | ||
| return this.subdivisionMap_[country]?.[region] ?? null; | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/geonameid-reader.ts | ||
| var GeoNameIdReader = class extends CsvReader { | ||
| geoNameIdMap_; | ||
| constructor() { | ||
| super(); | ||
| this.geoNameIdMap_ = {}; | ||
| this.requiredCsvHeaders_ = [ | ||
| "country_code", | ||
| "region_name", | ||
| "city_name", | ||
| "geonameid" | ||
| ]; | ||
| } | ||
| /** | ||
| * Process line from IP2Location GeoName ID database | ||
| * @param record Individual row from CSV database, broken into key/value pairs based on CSV headers | ||
| */ | ||
| processRecord(record) { | ||
| const { country_code, region_name, city_name, geonameid } = record; | ||
| if (!/^\d+$/.test(geonameid)) return; | ||
| const countryMap = this.geoNameIdMap_[country_code] ?? {}; | ||
| const regionMap = countryMap[region_name] ?? {}; | ||
| regionMap[city_name] = parseInt(geonameid); | ||
| countryMap[region_name] = regionMap; | ||
| this.geoNameIdMap_[country_code] = countryMap; | ||
| } | ||
| /** | ||
| * Reset stored data | ||
| */ | ||
| resetData() { | ||
| this.geoNameIdMap_ = {}; | ||
| } | ||
| /** | ||
| * Get the GeoName ID from a country code, region, and city | ||
| * @param country ISO 3166-1 country code from IP2Location database | ||
| * @param region Region from from IP2Location database | ||
| * @param city City from IP2Location database | ||
| */ | ||
| get(country, region, city) { | ||
| if (this.readerStatus !== 2) return null; | ||
| if (!country || !region || !city) return null; | ||
| return this.geoNameIdMap_[country]?.[region]?.[city] ?? null; | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/country-info-reader.ts | ||
| var CountryInfoReader = class extends CsvReader { | ||
| countryInfoMap_; | ||
| constructor() { | ||
| super(); | ||
| this.countryInfoMap_ = {}; | ||
| this.requiredCsvHeaders_ = [ | ||
| "country_code", | ||
| "capital", | ||
| "total_area" | ||
| ]; | ||
| } | ||
| /** | ||
| * Process line from IP2Location country info database | ||
| * @param record Individual row from CSV database, broken into key/value pairs based on CSV headers | ||
| */ | ||
| processRecord(record) { | ||
| const normalizeStr = (val) => { | ||
| return val.replace(/^-$/, ""); | ||
| }; | ||
| const normalizeNum = (val) => { | ||
| const numStr = normalizeStr(val).trim(); | ||
| const num = numStr ? Number(numStr) : null; | ||
| return num != null && !Number.isNaN(num) ? num : null; | ||
| }; | ||
| const countryInfoData = { | ||
| country_code: "", | ||
| capital: "", | ||
| total_area: null | ||
| }; | ||
| for (const key in record) countryInfoData[key] = [ | ||
| "country_numeric_code", | ||
| "total_area", | ||
| "population", | ||
| "idd_code" | ||
| ].includes(key) ? normalizeNum(record[key]) : normalizeStr(record[key]); | ||
| if (countryInfoData.country_code) this.countryInfoMap_[countryInfoData.country_code] = countryInfoData; | ||
| } | ||
| /** | ||
| * Reset stored data | ||
| */ | ||
| resetData() { | ||
| this.countryInfoMap_ = {}; | ||
| } | ||
| /** | ||
| * Get country info from a country code | ||
| * @param country ISO 3166-1 country code from IP2Location database | ||
| */ | ||
| get(country) { | ||
| if (this.readerStatus !== 2) return null; | ||
| if (!country) return null; | ||
| return this.countryInfoMap_[country] ?? null; | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/iata-icao-reader.ts | ||
| var IataIcaoReader = class extends CsvReader { | ||
| iataIcaoMap_; | ||
| constructor() { | ||
| super(); | ||
| this.iataIcaoMap_ = {}; | ||
| this.requiredCsvHeaders_ = [ | ||
| "country_code", | ||
| "region_name", | ||
| "iata", | ||
| "icao", | ||
| "latitude", | ||
| "longitude" | ||
| ]; | ||
| } | ||
| /** | ||
| * Process line from IP2Location IATA/ICAO airport database | ||
| * @param record Individual row from CSV database, broken into key/value pairs based on CSV headers | ||
| */ | ||
| processRecord(record) { | ||
| const normalizeStr = (val) => { | ||
| return val.replace(/^-$/, ""); | ||
| }; | ||
| const normalizeNum = (val) => { | ||
| const numStr = normalizeStr(val).trim(); | ||
| const num = numStr ? Number(numStr) : null; | ||
| return num != null && !Number.isNaN(num) ? num : null; | ||
| }; | ||
| const airportOutputData = { | ||
| iata: "", | ||
| icao: "", | ||
| airport: "", | ||
| latitude: null, | ||
| longitude: null | ||
| }; | ||
| const { country_code, region_name } = record; | ||
| for (const key in record) { | ||
| if (["country_code", "region_name"].includes(key)) continue; | ||
| airportOutputData[key] = ["latitude", "longitude"].includes(key) ? normalizeNum(record[key]) : normalizeStr(record[key]); | ||
| } | ||
| const countryMap = this.iataIcaoMap_[country_code] ?? {}; | ||
| const regionArray = countryMap[region_name] ?? []; | ||
| regionArray.push(airportOutputData); | ||
| countryMap[region_name] = regionArray; | ||
| this.iataIcaoMap_[country_code] = countryMap; | ||
| } | ||
| /** | ||
| * Reset stored data | ||
| */ | ||
| resetData() { | ||
| this.iataIcaoMap_ = {}; | ||
| } | ||
| /** | ||
| * Get IATA/ICAO airport info from country code and region name | ||
| * @param country ISO 3166-1 country code from IP2Location database | ||
| * @param region Region from from IP2Location database | ||
| */ | ||
| get(country, region) { | ||
| if (this.readerStatus !== 2) return []; | ||
| if (!country || !region) return []; | ||
| return this.iataIcaoMap_[country]?.[region] ?? []; | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/continent-reader.ts | ||
| var ContinentReader = class extends CsvReader { | ||
| continentMap_; | ||
| constructor() { | ||
| super(); | ||
| this.continentMap_ = {}; | ||
| this.requiredCsvHeaders_ = [ | ||
| "lang", | ||
| "country_alpha2_code", | ||
| "continent_code", | ||
| "continent" | ||
| ]; | ||
| } | ||
| /** | ||
| * Process line from IP2Location continent database | ||
| * @param record Individual row from CSV database, broken into key/value pairs based on CSV headers | ||
| */ | ||
| processRecord(record) { | ||
| if (record.lang !== "EN") return; | ||
| const { country_alpha2_code, continent_code, continent } = record; | ||
| if (!country_alpha2_code) return; | ||
| this.continentMap_[country_alpha2_code] = { | ||
| continent_code, | ||
| continent | ||
| }; | ||
| } | ||
| /** | ||
| * Reset stored data | ||
| */ | ||
| resetData() { | ||
| this.continentMap_ = {}; | ||
| } | ||
| /** | ||
| * Get continent info from a country code | ||
| * @param country ISO 3166-1 country code from IP2Location database | ||
| */ | ||
| get(country) { | ||
| if (this.readerStatus !== 2) return null; | ||
| if (!country) return null; | ||
| return this.continentMap_[country] ?? null; | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/olson-tz-reader.ts | ||
| var OlsonTzReader = class extends CsvReader { | ||
| olsonTzMap_; | ||
| constructor() { | ||
| super(); | ||
| this.olsonTzMap_ = {}; | ||
| this.requiredCsvHeaders_ = [ | ||
| "country_code", | ||
| "region_name", | ||
| "city_name", | ||
| "olson_tz", | ||
| "abbreviation", | ||
| "dst_start", | ||
| "dst_end" | ||
| ]; | ||
| } | ||
| /** | ||
| * Process line from IP2Location Olson TZ database | ||
| * @param record Individual row from CSV database, broken into key/value pairs based on CSV headers | ||
| */ | ||
| processRecord(record) { | ||
| const { country_code, region_name, city_name, olson_tz, abbreviation, dst_start, dst_end } = record; | ||
| if (!olson_tz) return; | ||
| const countryMap = this.olsonTzMap_[country_code] ?? {}; | ||
| const regionMap = countryMap[region_name] ?? {}; | ||
| regionMap[city_name] = { | ||
| olson_tz, | ||
| abbreviation, | ||
| dst_start: dst_start || null, | ||
| dst_end: dst_end || null | ||
| }; | ||
| countryMap[region_name] = regionMap; | ||
| this.olsonTzMap_[country_code] = countryMap; | ||
| } | ||
| /** | ||
| * Reset stored data | ||
| */ | ||
| resetData() { | ||
| this.olsonTzMap_ = {}; | ||
| } | ||
| /** | ||
| * Get the Olson Time Zone from a country code, region, and city | ||
| * @param country ISO 3166-1 country code from IP2Location database | ||
| * @param region Region from from IP2Location database | ||
| * @param city City from IP2Location database | ||
| */ | ||
| get(country, region, city) { | ||
| if (this.readerStatus !== 2) return null; | ||
| if (!country || !region || !city) return null; | ||
| return this.olsonTzMap_[country]?.[region]?.[city] ?? null; | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/index.ts | ||
| var Ip2lReader = class { | ||
| dbReader_; | ||
| subdivReader_; | ||
| geoNameIdReader_; | ||
| countryInfoReader_; | ||
| iataIcaoReader_; | ||
| continentReader_; | ||
| olsonTzReader_; | ||
| constructor() { | ||
| this.dbReader_ = new DbReader(); | ||
| } | ||
| /** | ||
| * Initialize IP2Location database reader(s) | ||
| * @param dbPath IP2Location BIN database | ||
| * @param options Options for database reader | ||
| */ | ||
| async init(dbPath, options) { | ||
| this.dbReader_.init(dbPath, options); | ||
| if (!options) return; | ||
| if (options.subdivisionCsvPath) { | ||
| this.subdivReader_ = new SubdivReader(); | ||
| await this.subdivReader_.init(options.subdivisionCsvPath, options.reloadOnDbUpdate); | ||
| } | ||
| if (options.geoNameIdCsvPath) { | ||
| this.geoNameIdReader_ = new GeoNameIdReader(); | ||
| await this.geoNameIdReader_.init(options.geoNameIdCsvPath, options.reloadOnDbUpdate); | ||
| } | ||
| if (options.countryInfoCsvPath) { | ||
| this.countryInfoReader_ = new CountryInfoReader(); | ||
| await this.countryInfoReader_.init(options.countryInfoCsvPath, options.reloadOnDbUpdate); | ||
| } | ||
| if (options.iataIcaoCsvPath) { | ||
| this.iataIcaoReader_ = new IataIcaoReader(); | ||
| await this.iataIcaoReader_.init(options.iataIcaoCsvPath, options.reloadOnDbUpdate); | ||
| } | ||
| if (options.continentCsvPath) { | ||
| this.continentReader_ = new ContinentReader(); | ||
| await this.continentReader_.init(options.continentCsvPath, options.reloadOnDbUpdate); | ||
| } | ||
| if (options.olsonTzCsvPath) { | ||
| this.olsonTzReader_ = new OlsonTzReader(); | ||
| await this.olsonTzReader_.init(options.olsonTzCsvPath, options.reloadOnDbUpdate); | ||
| } | ||
| } | ||
| /** | ||
| * Query IP2Location database with an IP and get location information | ||
| * @param ip IP address | ||
| */ | ||
| get(ip) { | ||
| const ip2lData = this.dbReader_.get(ip); | ||
| if (this.subdivReader_ && typeof ip2lData.country_short === "string" && typeof ip2lData.region === "string") ip2lData.subdivision = this.subdivReader_.get(ip2lData.country_short, ip2lData.region); | ||
| if (this.geoNameIdReader_ && typeof ip2lData.country_short === "string" && typeof ip2lData.region === "string" && typeof ip2lData.city === "string") ip2lData.geoname_id = this.geoNameIdReader_.get(ip2lData.country_short, ip2lData.region, ip2lData.city); | ||
| if (this.countryInfoReader_ && typeof ip2lData.country_short === "string") ip2lData.country_info = this.countryInfoReader_.get(ip2lData.country_short); | ||
| if (this.iataIcaoReader_ && typeof ip2lData.country_short === "string" && typeof ip2lData.region === "string") ip2lData.airports = this.iataIcaoReader_.get(ip2lData.country_short, ip2lData.region); | ||
| if (this.continentReader_ && typeof ip2lData.country_short === "string") ip2lData.continent = this.continentReader_.get(ip2lData.country_short); | ||
| if (this.olsonTzReader_ && typeof ip2lData.country_short === "string" && typeof ip2lData.region === "string" && typeof ip2lData.city === "string") ip2lData.olson_timezone = this.olsonTzReader_.get(ip2lData.country_short, ip2lData.region, ip2lData.city); | ||
| return ip2lData; | ||
| } | ||
| /** | ||
| * Close IP2Location database(s) and uninitialize reader(s) | ||
| */ | ||
| close() { | ||
| this.dbReader_.close(); | ||
| this.subdivReader_?.close(); | ||
| this.geoNameIdReader_?.close(); | ||
| this.countryInfoReader_?.close(); | ||
| this.iataIcaoReader_?.close(); | ||
| this.continentReader_?.close(); | ||
| this.olsonTzReader_?.close(); | ||
| } | ||
| }; | ||
| //#endregion | ||
| export { Ip2lReader, Ip2lReader as default }; |
+1680
-950
@@ -1,2 +0,6 @@ | ||
| "use strict"; | ||
| Object.defineProperties(exports, { | ||
| __esModule: { value: true }, | ||
| [Symbol.toStringTag]: { value: "Module" } | ||
| }); | ||
| //#region \0rolldown/runtime.js | ||
| var __create = Object.create; | ||
@@ -8,971 +12,1697 @@ var __defProp = Object.defineProperty; | ||
| var __hasOwnProp = Object.prototype.hasOwnProperty; | ||
| var __export = (target, all) => { | ||
| for (var name in all) | ||
| __defProp(target, name, { get: all[name], enumerable: true }); | ||
| }; | ||
| var __copyProps = (to, from, except, desc) => { | ||
| if (from && typeof from === "object" || typeof from === "function") { | ||
| for (let key of __getOwnPropNames(from)) | ||
| if (!__hasOwnProp.call(to, key) && key !== except) | ||
| __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); | ||
| } | ||
| return to; | ||
| if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) { | ||
| key = keys[i]; | ||
| if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { | ||
| get: ((k) => from[k]).bind(null, key), | ||
| enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable | ||
| }); | ||
| } | ||
| return to; | ||
| }; | ||
| var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( | ||
| // If the importer is in node compatibility mode or this is not an ESM | ||
| // file that has been converted to a CommonJS file using a Babel- | ||
| // compatible transform (i.e. "__esModule" has not been set), then set | ||
| // "default" to the CommonJS "module.exports" for node compatibility. | ||
| isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, | ||
| mod | ||
| )); | ||
| var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); | ||
| // src/index.ts | ||
| var index_exports = {}; | ||
| __export(index_exports, { | ||
| Ip2lReader: () => Ip2lReader, | ||
| default: () => Ip2lReader | ||
| }); | ||
| module.exports = __toCommonJS(index_exports); | ||
| // src/db-reader.ts | ||
| var import_node_fs = __toESM(require("fs"), 1); | ||
| // src/ip-utils.ts | ||
| var import_node_net = __toESM(require("net"), 1); | ||
| var FROM_6TO4 = BigInt("42545680458834377588178886921629466624"); | ||
| var TO_6TO4 = BigInt("42550872755692912415807417417958686719"); | ||
| var FROM_TEREDO = BigInt("42540488161975842760550356425300246528"); | ||
| var TO_TEREDO = BigInt("42540488241204005274814694018844196863"); | ||
| var LAST_32BITS = BigInt("4294967295"); | ||
| var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { | ||
| value: mod, | ||
| enumerable: true | ||
| }) : target, mod)); | ||
| //#endregion | ||
| let node_fs = require("node:fs"); | ||
| node_fs = __toESM(node_fs, 1); | ||
| let node_net = require("node:net"); | ||
| node_net = __toESM(node_net, 1); | ||
| let csv_parse = require("csv-parse"); | ||
| //#region src/ip-utils.ts | ||
| const FROM_6TO4 = BigInt("42545680458834377588178886921629466624"); | ||
| const TO_6TO4 = BigInt("42550872755692912415807417417958686719"); | ||
| const FROM_TEREDO = BigInt("42540488161975842760550356425300246528"); | ||
| const TO_TEREDO = BigInt("42540488241204005274814694018844196863"); | ||
| const LAST_32BITS = BigInt("4294967295"); | ||
| /** | ||
| * Check whether an IPv6 address can be expressed as IPv4 | ||
| * and use IPv4 if available. Remove zone index from link | ||
| * local IPv6 address. Calculate IP number. | ||
| * @param ip IP address | ||
| */ | ||
| function parseIp(ip) { | ||
| let ipVersion = import_node_net.default.isIP(ip); | ||
| let ipNum = BigInt(0); | ||
| if (ipVersion === 6) { | ||
| if (/^[:0]+:F{4}:(\d+\.){3}\d+$/i.test(ip)) { | ||
| ip = ip.replace(/^[:0]+:F{4}:/i, ""); | ||
| ipVersion = import_node_net.default.isIP(ip); | ||
| } else if (/^[:0]+F{4}(:[\dA-Z]{4}){2}$/i.test(ip)) { | ||
| const tmp = ip.replace(/^[:0]+F{4}:/i, "").replace(/:/, ""); | ||
| ip = (tmp.match(/../g) ?? []).map((b) => parseInt("0x" + b)).join("."); | ||
| ipVersion = import_node_net.default.isIP(ip); | ||
| } | ||
| } | ||
| if (ipVersion) { | ||
| ({ ipNum, ipVersion } = getIpNum(ip, ipVersion)); | ||
| } | ||
| return { ip, ipVersion, ipNum }; | ||
| let ipVersion = node_net.default.isIP(ip); | ||
| let ipNum = BigInt(0); | ||
| if (ipVersion === 6) { | ||
| if (/^[:0]+:F{4}:(\d+\.){3}\d+$/i.test(ip)) { | ||
| ip = ip.replace(/^[:0]+:F{4}:/i, ""); | ||
| ipVersion = node_net.default.isIP(ip); | ||
| } else if (/^[:0]+F{4}(:[\dA-Z]{4}){2}$/i.test(ip)) { | ||
| ip = (ip.replace(/^[:0]+F{4}:/i, "").replace(/:/, "").match(/../g) ?? []).map((b) => parseInt("0x" + b)).join("."); | ||
| ipVersion = node_net.default.isIP(ip); | ||
| } | ||
| } | ||
| if (ipVersion) ({ipNum, ipVersion} = getIpNum(ip, ipVersion)); | ||
| return { | ||
| ip, | ||
| ipVersion, | ||
| ipNum | ||
| }; | ||
| } | ||
| /** | ||
| * Get numeric IP and verify version | ||
| * @param ip IP address | ||
| * @param ipVersion IP version | ||
| */ | ||
| function getIpNum(ip, ipVersion) { | ||
| let ipNum = BigInt(0); | ||
| if (ipVersion === 4) { | ||
| const d = ip.split("."); | ||
| ipNum = BigInt(((+d[0] * 256 + +d[1]) * 256 + +d[2]) * 256 + +d[3]); | ||
| } else if (ipVersion === 6) { | ||
| const maxsections = 8; | ||
| const sectionbits = 16; | ||
| const m = ip.split("::"); | ||
| let total = BigInt(0); | ||
| if (m.length === 2) { | ||
| const arrLeft = m[0] !== "" ? m[0].split(":") : []; | ||
| const arrRight = m[1] !== "" ? m[1].split(":") : []; | ||
| for (let x = 0; x < arrLeft.length; x++) { | ||
| total += BigInt(parseInt("0x" + arrLeft[x])) << BigInt((maxsections - (x + 1)) * sectionbits); | ||
| } | ||
| for (let x = 0; x < arrRight.length; x++) { | ||
| total += BigInt(parseInt("0x" + arrRight[x])) << BigInt((arrRight.length - (x + 1)) * sectionbits); | ||
| } | ||
| } else if (m.length === 1) { | ||
| const arr = m[0].split(":"); | ||
| for (let x = 0; x < arr.length; x++) { | ||
| total += BigInt(parseInt("0x" + arr[x])) << BigInt((maxsections - (x + 1)) * sectionbits); | ||
| } | ||
| } | ||
| ipNum = total; | ||
| if (ipNum >= FROM_6TO4 && ipNum <= TO_6TO4) { | ||
| ipNum = ipNum >> BigInt(80) & LAST_32BITS; | ||
| ipVersion = 4; | ||
| } else if (ipNum >= FROM_TEREDO && ipNum <= TO_TEREDO) { | ||
| ipNum = ~ipNum & LAST_32BITS; | ||
| ipVersion = 4; | ||
| } | ||
| } | ||
| return { ipNum, ipVersion }; | ||
| let ipNum = BigInt(0); | ||
| if (ipVersion === 4) { | ||
| const d = ip.split("."); | ||
| ipNum = BigInt(((+d[0] * 256 + +d[1]) * 256 + +d[2]) * 256 + +d[3]); | ||
| } else if (ipVersion === 6) { | ||
| const maxsections = 8; | ||
| const sectionbits = 16; | ||
| const m = ip.split("::"); | ||
| let total = BigInt(0); | ||
| if (m.length === 2) { | ||
| const arrLeft = m[0] !== "" ? m[0].split(":") : []; | ||
| const arrRight = m[1] !== "" ? m[1].split(":") : []; | ||
| for (let x = 0; x < arrLeft.length; x++) total += BigInt(parseInt("0x" + arrLeft[x])) << BigInt((maxsections - (x + 1)) * sectionbits); | ||
| for (let x = 0; x < arrRight.length; x++) total += BigInt(parseInt("0x" + arrRight[x])) << BigInt((arrRight.length - (x + 1)) * sectionbits); | ||
| } else if (m.length === 1) { | ||
| const arr = m[0].split(":"); | ||
| for (let x = 0; x < arr.length; x++) total += BigInt(parseInt("0x" + arr[x])) << BigInt((maxsections - (x + 1)) * sectionbits); | ||
| } | ||
| ipNum = total; | ||
| if (ipNum >= FROM_6TO4 && ipNum <= TO_6TO4) { | ||
| ipNum = ipNum >> BigInt(80) & LAST_32BITS; | ||
| ipVersion = 4; | ||
| } else if (ipNum >= FROM_TEREDO && ipNum <= TO_TEREDO) { | ||
| ipNum = ~ipNum & LAST_32BITS; | ||
| ipVersion = 4; | ||
| } | ||
| } | ||
| return { | ||
| ipNum, | ||
| ipVersion | ||
| }; | ||
| } | ||
| // src/db-reader.ts | ||
| var Position = { | ||
| country: [0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2], | ||
| region: [0, 0, 0, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3], | ||
| city: [0, 0, 0, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4], | ||
| isp: [0, 0, 3, 0, 5, 0, 7, 5, 7, 0, 8, 0, 9, 0, 9, 0, 9, 0, 9, 7, 9, 0, 9, 7, 9, 9, 9], | ||
| latitude: [0, 0, 0, 0, 0, 5, 5, 0, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5], | ||
| longitude: [0, 0, 0, 0, 0, 6, 6, 0, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6], | ||
| domain: [0, 0, 0, 0, 0, 0, 0, 6, 8, 0, 9, 0, 10, 0, 10, 0, 10, 0, 10, 8, 10, 0, 10, 8, 10, 10, 10], | ||
| zipcode: [0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 7, 7, 7, 0, 7, 7, 7, 0, 7, 0, 7, 7, 7, 0, 7, 7, 7], | ||
| timezone: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 8, 7, 8, 8, 8, 7, 8, 0, 8, 8, 8, 0, 8, 8, 8], | ||
| netspeed: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 11, 0, 11, 8, 11, 0, 11, 0, 11, 0, 11, 11, 11], | ||
| iddcode: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 12, 0, 12, 0, 12, 9, 12, 0, 12, 12, 12], | ||
| areacode: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 13, 0, 13, 0, 13, 10, 13, 0, 13, 13, 13], | ||
| weatherstationcode: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 14, 0, 14, 0, 14, 0, 14, 14, 14], | ||
| weatherstationname: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 15, 0, 15, 0, 15, 0, 15, 15, 15], | ||
| mcc: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 16, 0, 16, 9, 16, 16, 16], | ||
| mnc: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 17, 0, 17, 10, 17, 17, 17], | ||
| mobilebrand: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 11, 18, 0, 18, 11, 18, 18, 18], | ||
| elevation: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 11, 19, 0, 19, 19, 19], | ||
| usagetype: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 20, 20, 20], | ||
| addresstype: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 21, 21], | ||
| category: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 22, 22], | ||
| district: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 23], | ||
| asn: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 24], | ||
| as: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 25], | ||
| asdomain: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 26], | ||
| asusagetype: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 27], | ||
| ascidr: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 28] | ||
| //#endregion | ||
| //#region src/db-reader.ts | ||
| const Position = { | ||
| country: [ | ||
| 0, | ||
| 2, | ||
| 2, | ||
| 2, | ||
| 2, | ||
| 2, | ||
| 2, | ||
| 2, | ||
| 2, | ||
| 2, | ||
| 2, | ||
| 2, | ||
| 2, | ||
| 2, | ||
| 2, | ||
| 2, | ||
| 2, | ||
| 2, | ||
| 2, | ||
| 2, | ||
| 2, | ||
| 2, | ||
| 2, | ||
| 2, | ||
| 2, | ||
| 2, | ||
| 2 | ||
| ], | ||
| region: [ | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 3, | ||
| 3, | ||
| 3, | ||
| 3, | ||
| 3, | ||
| 3, | ||
| 3, | ||
| 3, | ||
| 3, | ||
| 3, | ||
| 3, | ||
| 3, | ||
| 3, | ||
| 3, | ||
| 3, | ||
| 3, | ||
| 3, | ||
| 3, | ||
| 3, | ||
| 3, | ||
| 3, | ||
| 3, | ||
| 3, | ||
| 3 | ||
| ], | ||
| city: [ | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 4, | ||
| 4, | ||
| 4, | ||
| 4, | ||
| 4, | ||
| 4, | ||
| 4, | ||
| 4, | ||
| 4, | ||
| 4, | ||
| 4, | ||
| 4, | ||
| 4, | ||
| 4, | ||
| 4, | ||
| 4, | ||
| 4, | ||
| 4, | ||
| 4, | ||
| 4, | ||
| 4, | ||
| 4, | ||
| 4, | ||
| 4 | ||
| ], | ||
| isp: [ | ||
| 0, | ||
| 0, | ||
| 3, | ||
| 0, | ||
| 5, | ||
| 0, | ||
| 7, | ||
| 5, | ||
| 7, | ||
| 0, | ||
| 8, | ||
| 0, | ||
| 9, | ||
| 0, | ||
| 9, | ||
| 0, | ||
| 9, | ||
| 0, | ||
| 9, | ||
| 7, | ||
| 9, | ||
| 0, | ||
| 9, | ||
| 7, | ||
| 9, | ||
| 9, | ||
| 9 | ||
| ], | ||
| latitude: [ | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 5, | ||
| 5, | ||
| 0, | ||
| 5, | ||
| 5, | ||
| 5, | ||
| 5, | ||
| 5, | ||
| 5, | ||
| 5, | ||
| 5, | ||
| 5, | ||
| 5, | ||
| 5, | ||
| 5, | ||
| 5, | ||
| 5, | ||
| 5, | ||
| 5, | ||
| 5, | ||
| 5, | ||
| 5 | ||
| ], | ||
| longitude: [ | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 6, | ||
| 6, | ||
| 0, | ||
| 6, | ||
| 6, | ||
| 6, | ||
| 6, | ||
| 6, | ||
| 6, | ||
| 6, | ||
| 6, | ||
| 6, | ||
| 6, | ||
| 6, | ||
| 6, | ||
| 6, | ||
| 6, | ||
| 6, | ||
| 6, | ||
| 6, | ||
| 6, | ||
| 6 | ||
| ], | ||
| domain: [ | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 6, | ||
| 8, | ||
| 0, | ||
| 9, | ||
| 0, | ||
| 10, | ||
| 0, | ||
| 10, | ||
| 0, | ||
| 10, | ||
| 0, | ||
| 10, | ||
| 8, | ||
| 10, | ||
| 0, | ||
| 10, | ||
| 8, | ||
| 10, | ||
| 10, | ||
| 10 | ||
| ], | ||
| zipcode: [ | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 7, | ||
| 7, | ||
| 7, | ||
| 7, | ||
| 0, | ||
| 7, | ||
| 7, | ||
| 7, | ||
| 0, | ||
| 7, | ||
| 0, | ||
| 7, | ||
| 7, | ||
| 7, | ||
| 0, | ||
| 7, | ||
| 7, | ||
| 7 | ||
| ], | ||
| timezone: [ | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 8, | ||
| 8, | ||
| 7, | ||
| 8, | ||
| 8, | ||
| 8, | ||
| 7, | ||
| 8, | ||
| 0, | ||
| 8, | ||
| 8, | ||
| 8, | ||
| 0, | ||
| 8, | ||
| 8, | ||
| 8 | ||
| ], | ||
| netspeed: [ | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 8, | ||
| 11, | ||
| 0, | ||
| 11, | ||
| 8, | ||
| 11, | ||
| 0, | ||
| 11, | ||
| 0, | ||
| 11, | ||
| 0, | ||
| 11, | ||
| 11, | ||
| 11 | ||
| ], | ||
| iddcode: [ | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 9, | ||
| 12, | ||
| 0, | ||
| 12, | ||
| 0, | ||
| 12, | ||
| 9, | ||
| 12, | ||
| 0, | ||
| 12, | ||
| 12, | ||
| 12 | ||
| ], | ||
| areacode: [ | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 10, | ||
| 13, | ||
| 0, | ||
| 13, | ||
| 0, | ||
| 13, | ||
| 10, | ||
| 13, | ||
| 0, | ||
| 13, | ||
| 13, | ||
| 13 | ||
| ], | ||
| weatherstationcode: [ | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 9, | ||
| 14, | ||
| 0, | ||
| 14, | ||
| 0, | ||
| 14, | ||
| 0, | ||
| 14, | ||
| 14, | ||
| 14 | ||
| ], | ||
| weatherstationname: [ | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 10, | ||
| 15, | ||
| 0, | ||
| 15, | ||
| 0, | ||
| 15, | ||
| 0, | ||
| 15, | ||
| 15, | ||
| 15 | ||
| ], | ||
| mcc: [ | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 9, | ||
| 16, | ||
| 0, | ||
| 16, | ||
| 9, | ||
| 16, | ||
| 16, | ||
| 16 | ||
| ], | ||
| mnc: [ | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 10, | ||
| 17, | ||
| 0, | ||
| 17, | ||
| 10, | ||
| 17, | ||
| 17, | ||
| 17 | ||
| ], | ||
| mobilebrand: [ | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 11, | ||
| 18, | ||
| 0, | ||
| 18, | ||
| 11, | ||
| 18, | ||
| 18, | ||
| 18 | ||
| ], | ||
| elevation: [ | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 11, | ||
| 19, | ||
| 0, | ||
| 19, | ||
| 19, | ||
| 19 | ||
| ], | ||
| usagetype: [ | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 12, | ||
| 20, | ||
| 20, | ||
| 20 | ||
| ], | ||
| addresstype: [ | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 21, | ||
| 21 | ||
| ], | ||
| category: [ | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 22, | ||
| 22 | ||
| ], | ||
| district: [ | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 23 | ||
| ], | ||
| asn: [ | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 24 | ||
| ], | ||
| as: [ | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 25 | ||
| ], | ||
| asdomain: [ | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 26 | ||
| ], | ||
| asusagetype: [ | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 27 | ||
| ], | ||
| ascidr: [ | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| 28 | ||
| ] | ||
| }; | ||
| var MAX_SIZE = 65536; | ||
| var MAX_IPV4_RANGE = BigInt("4294967295"); | ||
| var MAX_IPV6_RANGE = BigInt("340282366920938463463374607431768211455"); | ||
| const MAX_SIZE = 65536; | ||
| const MAX_IPV4_RANGE = BigInt("4294967295"); | ||
| const MAX_IPV6_RANGE = BigInt("340282366920938463463374607431768211455"); | ||
| var DbReader = class { | ||
| readerStatus_; | ||
| dbPath_; | ||
| cacheInMemory_; | ||
| fd_; | ||
| dbCache_; | ||
| fsWatcher_; | ||
| indiciesIPv4_; | ||
| indiciesIPv6_; | ||
| offset_; | ||
| enabled_; | ||
| dbStats_; | ||
| constructor() { | ||
| this.readerStatus_ = 0 /* NotInitialized */; | ||
| this.dbPath_ = null; | ||
| this.cacheInMemory_ = false; | ||
| this.fd_ = null; | ||
| this.dbCache_ = null; | ||
| this.fsWatcher_ = null; | ||
| this.indiciesIPv4_ = []; | ||
| this.indiciesIPv6_ = []; | ||
| this.offset_ = {}; | ||
| this.enabled_ = {}; | ||
| this.dbStats_ = { | ||
| DBType: 0, | ||
| DBColumn: 0, | ||
| DBYear: 0, | ||
| DBMonth: 0, | ||
| DBDay: 0, | ||
| DBCount: 0, | ||
| DBCountIPv6: 0, | ||
| BaseAddr: 0, | ||
| BaseAddrIPv6: 0, | ||
| IndexBaseAddr: 0, | ||
| IndexBaseAddrIPv6: 0, | ||
| ColumnSize: 0, | ||
| ColumnSizeIPv6: 0, | ||
| ProductCode: 0, | ||
| ProductType: 0, | ||
| FileSize: 0, | ||
| Indexed: false, | ||
| IndexedIPv6: false, | ||
| OldBIN: false | ||
| }; | ||
| } | ||
| /** | ||
| * Read data from database into a buffer | ||
| * @param readbytes Number of bytes to read | ||
| * @param pos Offset from beginning of database | ||
| */ | ||
| readToBuffer(readbytes, pos) { | ||
| if (this.dbCache_) { | ||
| const buff2 = this.dbCache_.subarray(pos, pos + readbytes); | ||
| return buff2.length === readbytes ? buff2 : void 0; | ||
| } | ||
| if (!this.fd_) { | ||
| throw new Error("Missing file descriptor, cannot read data"); | ||
| } | ||
| const buff = Buffer.alloc(readbytes); | ||
| const totalread = import_node_fs.default.readSync(this.fd_, buff, 0, readbytes, pos); | ||
| return totalread === readbytes ? buff : void 0; | ||
| } | ||
| /** | ||
| * Read 8-bit integer from the database | ||
| * @param pos Offset from beginning of database | ||
| */ | ||
| readInt8(pos) { | ||
| const buff = this.readToBuffer(1, pos - 1); | ||
| return buff ? buff.readUInt8(0) : void 0; | ||
| } | ||
| /** | ||
| * Read 32-bit integer from the database | ||
| * @param pos Offset from beginning of database | ||
| */ | ||
| readInt32(pos) { | ||
| const buff = this.readToBuffer(4, pos - 1); | ||
| return buff ? buff.readUInt32LE(0) : void 0; | ||
| } | ||
| /** | ||
| * Read 32-bit integer from the database as a BigInt | ||
| * @param pos Offset from beginning of database | ||
| */ | ||
| readInt32Big(pos) { | ||
| const buff = this.readToBuffer(4, pos - 1); | ||
| return buff ? BigInt(buff.readUInt32LE(0)) : void 0; | ||
| } | ||
| /** | ||
| * Read 32-bit float from the database | ||
| * @param pos Offset from beginning of database | ||
| */ | ||
| readFloat(pos) { | ||
| const buff = this.readToBuffer(4, pos - 1); | ||
| return buff ? buff.readFloatLE(0) : void 0; | ||
| } | ||
| /** | ||
| * Read 128-bit integer from the database as a BigInt | ||
| * @param pos Offset from beginning of database | ||
| */ | ||
| readInt128Big(pos) { | ||
| const buff = this.readToBuffer(16, pos - 1); | ||
| if (!buff) { | ||
| return; | ||
| } | ||
| let ret = BigInt(0); | ||
| for (let x = 0; x < 16; x++) { | ||
| ret += BigInt(buff.readUInt8(x)) << BigInt(8 * x); | ||
| } | ||
| return ret; | ||
| } | ||
| /** | ||
| * Read string from the database | ||
| * @param pos Offset from beginning of database | ||
| */ | ||
| readString(pos) { | ||
| const strBytes = this.readInt8(pos + 1); | ||
| if (!strBytes) { | ||
| return; | ||
| } | ||
| const buff = this.readToBuffer(strBytes, pos + 1); | ||
| const str = buff ? buff.toString("utf8") : void 0; | ||
| return str === "-" ? "" : str; | ||
| } | ||
| /** | ||
| * Read 32-bit integer from a Buffer | ||
| * @param pos Offset from beginning of buffer | ||
| * @param buff Buffer | ||
| */ | ||
| readBufferInt32(pos, buff) { | ||
| return buff.readUInt32LE(pos); | ||
| } | ||
| /** | ||
| * Read 32-bit float from a buffer | ||
| * @param pos Offset from beginning of buffer | ||
| * @param buff Buffer | ||
| */ | ||
| readBufferFloat(pos, buff) { | ||
| return buff.readFloatLE(pos); | ||
| } | ||
| /** | ||
| * (Re)load database | ||
| */ | ||
| loadDatabase() { | ||
| if (!this.dbPath_) { | ||
| throw new Error("Path to database not available"); | ||
| } | ||
| this.readerStatus_ = 1 /* Initializing */; | ||
| if (this.fd_ !== null) { | ||
| try { | ||
| import_node_fs.default.closeSync(this.fd_); | ||
| } catch { | ||
| } | ||
| } | ||
| if (this.cacheInMemory_) { | ||
| this.fd_ = null; | ||
| this.dbCache_ = import_node_fs.default.readFileSync(this.dbPath_); | ||
| } else { | ||
| this.fd_ = import_node_fs.default.openSync(this.dbPath_, "r"); | ||
| this.dbCache_ = null; | ||
| } | ||
| this.dbStats_.DBType = this.readInt8(1) ?? 0; | ||
| this.dbStats_.DBColumn = this.readInt8(2) ?? 0; | ||
| this.dbStats_.DBYear = this.readInt8(3) ?? 0; | ||
| this.dbStats_.DBMonth = this.readInt8(4) ?? 0; | ||
| this.dbStats_.DBDay = this.readInt8(5) ?? 0; | ||
| this.dbStats_.DBCount = this.readInt32(6) ?? 0; | ||
| this.dbStats_.BaseAddr = this.readInt32(10) ?? 0; | ||
| this.dbStats_.DBCountIPv6 = this.readInt32(14) ?? 0; | ||
| this.dbStats_.BaseAddrIPv6 = this.readInt32(18) ?? 0; | ||
| this.dbStats_.IndexBaseAddr = this.readInt32(22) ?? 0; | ||
| this.dbStats_.IndexBaseAddrIPv6 = this.readInt32(26) ?? 0; | ||
| this.dbStats_.ProductCode = this.readInt8(30) ?? 0; | ||
| this.dbStats_.ProductType = this.readInt8(31) ?? 0; | ||
| this.dbStats_.FileSize = this.readInt32(32) ?? 0; | ||
| if (this.dbStats_.ProductCode !== 1 && this.dbStats_.DBYear >= 21) { | ||
| throw new Error( | ||
| "Incorrect IP2Location BIN file format. Please make sure that you are using the latest IP2Location BIN file." | ||
| ); | ||
| } | ||
| if (this.dbStats_.DBType == "P".charCodeAt(0) && this.dbStats_.DBColumn === "K".charCodeAt(0)) { | ||
| throw new Error("Incorrect IP2Location BIN file format. Please uncompress zip file."); | ||
| } | ||
| this.dbStats_.Indexed = this.dbStats_.IndexBaseAddr > 0; | ||
| this.dbStats_.OldBIN = !this.dbStats_.DBCountIPv6; | ||
| this.dbStats_.IndexedIPv6 = !this.dbStats_.OldBIN && this.dbStats_.IndexBaseAddrIPv6 > 0; | ||
| this.dbStats_.ColumnSize = this.dbStats_.DBColumn << 2; | ||
| this.dbStats_.ColumnSizeIPv6 = 16 + (this.dbStats_.DBColumn - 1 << 2); | ||
| const dbType = this.dbStats_.DBType; | ||
| for (const key of Object.keys(Position)) { | ||
| this.offset_[key] = Position[key][dbType] ? Position[key][dbType] - 2 << 2 : 0; | ||
| this.enabled_[key] = Boolean(Position[key][dbType]); | ||
| } | ||
| this.indiciesIPv4_ = new Array(MAX_SIZE); | ||
| this.indiciesIPv6_ = new Array(MAX_SIZE); | ||
| if (this.dbStats_.Indexed) { | ||
| let pointer = this.dbStats_.IndexBaseAddr; | ||
| for (let x = 0; x < MAX_SIZE; x++) { | ||
| this.indiciesIPv4_[x] = [this.readInt32(pointer) ?? 0, this.readInt32(pointer + 4) ?? 0]; | ||
| pointer += 8; | ||
| } | ||
| if (this.dbStats_.IndexedIPv6) { | ||
| for (let x = 0; x < MAX_SIZE; x++) { | ||
| this.indiciesIPv6_[x] = [this.readInt32(pointer) ?? 0, this.readInt32(pointer + 4) ?? 0]; | ||
| pointer += 8; | ||
| } | ||
| } | ||
| } | ||
| this.readerStatus_ = 2 /* Ready */; | ||
| } | ||
| /** | ||
| * Get reader status | ||
| */ | ||
| get readerStatus() { | ||
| return this.readerStatus_; | ||
| } | ||
| /** | ||
| * Close database and uninitialize reader | ||
| */ | ||
| close() { | ||
| this.readerStatus_ = 0 /* NotInitialized */; | ||
| if (this.fd_ !== null) { | ||
| try { | ||
| import_node_fs.default.closeSync(this.fd_); | ||
| } catch { | ||
| } | ||
| } | ||
| if (this.fsWatcher_ !== null) { | ||
| this.fsWatcher_.close(); | ||
| } | ||
| this.dbPath_ = null; | ||
| this.cacheInMemory_ = false; | ||
| this.fd_ = null; | ||
| this.dbCache_ = null; | ||
| this.fsWatcher_ = null; | ||
| this.indiciesIPv4_ = []; | ||
| this.indiciesIPv6_ = []; | ||
| this.offset_ = {}; | ||
| this.enabled_ = {}; | ||
| this.dbStats_ = { | ||
| DBType: 0, | ||
| DBColumn: 0, | ||
| DBYear: 0, | ||
| DBMonth: 0, | ||
| DBDay: 0, | ||
| DBCount: 0, | ||
| DBCountIPv6: 0, | ||
| BaseAddr: 0, | ||
| BaseAddrIPv6: 0, | ||
| IndexBaseAddr: 0, | ||
| IndexBaseAddrIPv6: 0, | ||
| ColumnSize: 0, | ||
| ColumnSizeIPv6: 0, | ||
| ProductCode: 0, | ||
| ProductType: 0, | ||
| FileSize: 0, | ||
| Indexed: false, | ||
| IndexedIPv6: false, | ||
| OldBIN: false | ||
| }; | ||
| } | ||
| /** | ||
| * Initialize IP2Location database reader | ||
| * @param dbPath IP2Location BIN database | ||
| * @param options Options for database reader | ||
| */ | ||
| init(dbPath, options) { | ||
| if (!dbPath) { | ||
| throw new Error("Must specify path to database"); | ||
| } | ||
| this.dbPath_ = dbPath; | ||
| this.cacheInMemory_ = options?.cacheDatabaseInMemory ?? false; | ||
| this.loadDatabase(); | ||
| if (options?.reloadOnDbUpdate) { | ||
| this.watchDbFile(); | ||
| } | ||
| } | ||
| /** | ||
| * Watch database file for changes and re-init if a change is detected | ||
| */ | ||
| watchDbFile() { | ||
| let timeout = null; | ||
| const originalState = this.readerStatus_; | ||
| const dbChangeHandler = (filename) => { | ||
| if (filename && this.dbPath_ && import_node_fs.default.existsSync(this.dbPath_)) { | ||
| this.fsWatcher_?.close(); | ||
| this.loadDatabase(); | ||
| this.fsWatcher_ = getFsWatch(); | ||
| } else { | ||
| this.readerStatus_ = originalState; | ||
| } | ||
| }; | ||
| const getFsWatch = () => { | ||
| if (!this.dbPath_) { | ||
| throw new Error("Path to database not available"); | ||
| } | ||
| return import_node_fs.default.watch(this.dbPath_, (eventType, filename) => { | ||
| if (!filename) { | ||
| return; | ||
| } | ||
| if (this.fd_) { | ||
| this.readerStatus_ = 1 /* Initializing */; | ||
| } | ||
| if (timeout !== null) { | ||
| clearTimeout(timeout); | ||
| } | ||
| timeout = setTimeout(() => { | ||
| timeout = null; | ||
| dbChangeHandler(filename); | ||
| }, 500); | ||
| }); | ||
| }; | ||
| this.fsWatcher_ = getFsWatch(); | ||
| } | ||
| /** | ||
| * Populate data object with database query results for an IP address | ||
| * @param ipNum IP number | ||
| * @param ipVersion IP version | ||
| * @param data Output data object | ||
| */ | ||
| query(ipNum, ipVersion, data) { | ||
| let low = 0; | ||
| let high; | ||
| let maxIpRange; | ||
| let baseAddr; | ||
| let columnSize; | ||
| if (ipVersion === 6) { | ||
| maxIpRange = MAX_IPV6_RANGE; | ||
| high = this.dbStats_.DBCountIPv6; | ||
| baseAddr = this.dbStats_.BaseAddrIPv6; | ||
| columnSize = this.dbStats_.ColumnSizeIPv6; | ||
| if (this.dbStats_.IndexedIPv6) { | ||
| const indexaddr = Number(ipNum >> BigInt(112)); | ||
| low = this.indiciesIPv6_[indexaddr][0]; | ||
| high = this.indiciesIPv6_[indexaddr][1]; | ||
| } | ||
| } else { | ||
| maxIpRange = MAX_IPV4_RANGE; | ||
| high = this.dbStats_.DBCount; | ||
| baseAddr = this.dbStats_.BaseAddr; | ||
| columnSize = this.dbStats_.ColumnSize; | ||
| if (this.dbStats_.Indexed) { | ||
| const indexaddr = Number(ipNum) >>> 16; | ||
| low = this.indiciesIPv4_[indexaddr][0]; | ||
| high = this.indiciesIPv4_[indexaddr][1]; | ||
| } | ||
| } | ||
| if (ipNum >= maxIpRange) { | ||
| ipNum = maxIpRange - BigInt(1); | ||
| } | ||
| while (low <= high) { | ||
| const mid = Math.floor((low + high) / 2); | ||
| const rowoffset = baseAddr + mid * columnSize; | ||
| const rowoffset2 = rowoffset + columnSize; | ||
| const ipfrom = ipVersion === 6 ? this.readInt128Big(rowoffset) : this.readInt32Big(rowoffset); | ||
| const ipto = ipVersion === 6 ? this.readInt128Big(rowoffset2) : this.readInt32Big(rowoffset2); | ||
| if (ipfrom === void 0 || ipto === void 0) { | ||
| break; | ||
| } | ||
| if (ipfrom <= ipNum && ipto > ipNum) { | ||
| const firstcol = ipVersion === 6 ? 16 : 4; | ||
| const buff = this.readToBuffer(columnSize - firstcol, rowoffset + firstcol - 1); | ||
| if (!buff) { | ||
| break; | ||
| } | ||
| const enabledKeys = Object.keys(this.enabled_).filter( | ||
| (key) => this.enabled_[key] | ||
| ); | ||
| for (const key of enabledKeys) { | ||
| if (key === "country") { | ||
| const countrypos = this.readBufferInt32(this.offset_[key], buff); | ||
| data.country_short = this.readString(countrypos) ?? ""; | ||
| data.country_long = this.readString(countrypos + 3) ?? ""; | ||
| } else if (key === "longitude" || key === "latitude") { | ||
| const num = this.readBufferFloat(this.offset_[key], buff); | ||
| data[key] = num !== 0 ? Math.round(num * 1e6) / 1e6 : null; | ||
| } else { | ||
| data[key] = this.readString(this.readBufferInt32(this.offset_[key], buff)) ?? ""; | ||
| } | ||
| } | ||
| data.status = "OK"; | ||
| return; | ||
| } else { | ||
| if (ipfrom > ipNum) { | ||
| high = mid - 1; | ||
| } else { | ||
| low = mid + 1; | ||
| } | ||
| } | ||
| } | ||
| data.status = "IP_ADDRESS_NOT_FOUND"; | ||
| } | ||
| /** | ||
| * Query IP2Location database with an IP and get location information | ||
| * @param ip IP address | ||
| */ | ||
| get(ip) { | ||
| const data = { | ||
| ip, | ||
| ip_no: "", | ||
| status: "" | ||
| }; | ||
| if (this.readerStatus_ === 0 /* NotInitialized */) { | ||
| data.status = "NOT_INITIALIZED"; | ||
| } else if (this.readerStatus_ === 1 /* Initializing */) { | ||
| data.status = "INITIALIZING"; | ||
| } else if (!this.dbPath_ || !this.dbCache_ && !import_node_fs.default.existsSync(this.dbPath_)) { | ||
| data.status = "DATABASE_NOT_FOUND"; | ||
| } else if (!this.dbStats_.DBType) { | ||
| data.status = "NOT_INITIALIZED"; | ||
| } | ||
| if (data.status) { | ||
| return data; | ||
| } | ||
| let ipVersion; | ||
| let ipNum; | ||
| ({ ip, ipVersion, ipNum } = parseIp(ip)); | ||
| if (!ipVersion) { | ||
| data.status = "INVALID_IP_ADDRESS"; | ||
| } else if (ipVersion === 6 && this.dbStats_.OldBIN) { | ||
| data.status = "IPV6_NOT_SUPPORTED"; | ||
| } | ||
| if (data.status) { | ||
| return data; | ||
| } | ||
| data.ip_no = ipNum.toString(); | ||
| this.query(ipNum, ipVersion, data); | ||
| return data; | ||
| } | ||
| readerStatus_; | ||
| dbPath_; | ||
| cacheInMemory_; | ||
| fd_; | ||
| dbCache_; | ||
| fsWatcher_; | ||
| indiciesIPv4_; | ||
| indiciesIPv6_; | ||
| offset_; | ||
| enabled_; | ||
| dbStats_; | ||
| constructor() { | ||
| this.readerStatus_ = 0; | ||
| this.dbPath_ = null; | ||
| this.cacheInMemory_ = false; | ||
| this.fd_ = null; | ||
| this.dbCache_ = null; | ||
| this.fsWatcher_ = null; | ||
| this.indiciesIPv4_ = []; | ||
| this.indiciesIPv6_ = []; | ||
| this.offset_ = {}; | ||
| this.enabled_ = {}; | ||
| this.dbStats_ = { | ||
| DBType: 0, | ||
| DBColumn: 0, | ||
| DBYear: 0, | ||
| DBMonth: 0, | ||
| DBDay: 0, | ||
| DBCount: 0, | ||
| DBCountIPv6: 0, | ||
| BaseAddr: 0, | ||
| BaseAddrIPv6: 0, | ||
| IndexBaseAddr: 0, | ||
| IndexBaseAddrIPv6: 0, | ||
| ColumnSize: 0, | ||
| ColumnSizeIPv6: 0, | ||
| ProductCode: 0, | ||
| ProductType: 0, | ||
| FileSize: 0, | ||
| Indexed: false, | ||
| IndexedIPv6: false, | ||
| OldBIN: false | ||
| }; | ||
| } | ||
| /** | ||
| * Read data from database into a buffer | ||
| * @param readbytes Number of bytes to read | ||
| * @param pos Offset from beginning of database | ||
| */ | ||
| readToBuffer(readbytes, pos) { | ||
| if (this.dbCache_) { | ||
| const buff = this.dbCache_.subarray(pos, pos + readbytes); | ||
| return buff.length === readbytes ? buff : void 0; | ||
| } | ||
| if (!this.fd_) throw new Error("Missing file descriptor, cannot read data"); | ||
| const buff = Buffer.alloc(readbytes); | ||
| return node_fs.default.readSync(this.fd_, buff, 0, readbytes, pos) === readbytes ? buff : void 0; | ||
| } | ||
| /** | ||
| * Read 8-bit integer from the database | ||
| * @param pos Offset from beginning of database | ||
| */ | ||
| readInt8(pos) { | ||
| const buff = this.readToBuffer(1, pos - 1); | ||
| return buff ? buff.readUInt8(0) : void 0; | ||
| } | ||
| /** | ||
| * Read 32-bit integer from the database | ||
| * @param pos Offset from beginning of database | ||
| */ | ||
| readInt32(pos) { | ||
| const buff = this.readToBuffer(4, pos - 1); | ||
| return buff ? buff.readUInt32LE(0) : void 0; | ||
| } | ||
| /** | ||
| * Read 32-bit integer from the database as a BigInt | ||
| * @param pos Offset from beginning of database | ||
| */ | ||
| readInt32Big(pos) { | ||
| const buff = this.readToBuffer(4, pos - 1); | ||
| return buff ? BigInt(buff.readUInt32LE(0)) : void 0; | ||
| } | ||
| /** | ||
| * Read 32-bit float from the database | ||
| * @param pos Offset from beginning of database | ||
| */ | ||
| readFloat(pos) { | ||
| const buff = this.readToBuffer(4, pos - 1); | ||
| return buff ? buff.readFloatLE(0) : void 0; | ||
| } | ||
| /** | ||
| * Read 128-bit integer from the database as a BigInt | ||
| * @param pos Offset from beginning of database | ||
| */ | ||
| readInt128Big(pos) { | ||
| const buff = this.readToBuffer(16, pos - 1); | ||
| if (!buff) return; | ||
| let ret = BigInt(0); | ||
| for (let x = 0; x < 16; x++) ret += BigInt(buff.readUInt8(x)) << BigInt(8 * x); | ||
| return ret; | ||
| } | ||
| /** | ||
| * Read string from the database | ||
| * @param pos Offset from beginning of database | ||
| */ | ||
| readString(pos) { | ||
| const strBytes = this.readInt8(pos + 1); | ||
| if (!strBytes) return; | ||
| const buff = this.readToBuffer(strBytes, pos + 1); | ||
| const str = buff ? buff.toString("utf8") : void 0; | ||
| return str === "-" ? "" : str; | ||
| } | ||
| /** | ||
| * Read 32-bit integer from a Buffer | ||
| * @param pos Offset from beginning of buffer | ||
| * @param buff Buffer | ||
| */ | ||
| readBufferInt32(pos, buff) { | ||
| return buff.readUInt32LE(pos); | ||
| } | ||
| /** | ||
| * Read 32-bit float from a buffer | ||
| * @param pos Offset from beginning of buffer | ||
| * @param buff Buffer | ||
| */ | ||
| readBufferFloat(pos, buff) { | ||
| return buff.readFloatLE(pos); | ||
| } | ||
| /** | ||
| * (Re)load database | ||
| */ | ||
| loadDatabase() { | ||
| if (!this.dbPath_) throw new Error("Path to database not available"); | ||
| this.readerStatus_ = 1; | ||
| if (this.fd_ !== null) try { | ||
| node_fs.default.closeSync(this.fd_); | ||
| } catch {} | ||
| if (this.cacheInMemory_) { | ||
| this.fd_ = null; | ||
| this.dbCache_ = node_fs.default.readFileSync(this.dbPath_); | ||
| } else { | ||
| this.fd_ = node_fs.default.openSync(this.dbPath_, "r"); | ||
| this.dbCache_ = null; | ||
| } | ||
| this.dbStats_.DBType = this.readInt8(1) ?? 0; | ||
| this.dbStats_.DBColumn = this.readInt8(2) ?? 0; | ||
| this.dbStats_.DBYear = this.readInt8(3) ?? 0; | ||
| this.dbStats_.DBMonth = this.readInt8(4) ?? 0; | ||
| this.dbStats_.DBDay = this.readInt8(5) ?? 0; | ||
| this.dbStats_.DBCount = this.readInt32(6) ?? 0; | ||
| this.dbStats_.BaseAddr = this.readInt32(10) ?? 0; | ||
| this.dbStats_.DBCountIPv6 = this.readInt32(14) ?? 0; | ||
| this.dbStats_.BaseAddrIPv6 = this.readInt32(18) ?? 0; | ||
| this.dbStats_.IndexBaseAddr = this.readInt32(22) ?? 0; | ||
| this.dbStats_.IndexBaseAddrIPv6 = this.readInt32(26) ?? 0; | ||
| this.dbStats_.ProductCode = this.readInt8(30) ?? 0; | ||
| this.dbStats_.ProductType = this.readInt8(31) ?? 0; | ||
| this.dbStats_.FileSize = this.readInt32(32) ?? 0; | ||
| if (this.dbStats_.ProductCode !== 1 && this.dbStats_.DBYear >= 21) throw new Error("Incorrect IP2Location BIN file format. Please make sure that you are using the latest IP2Location BIN file."); | ||
| if (this.dbStats_.DBType == "P".charCodeAt(0) && this.dbStats_.DBColumn === "K".charCodeAt(0)) throw new Error("Incorrect IP2Location BIN file format. Please uncompress zip file."); | ||
| this.dbStats_.Indexed = this.dbStats_.IndexBaseAddr > 0; | ||
| this.dbStats_.OldBIN = !this.dbStats_.DBCountIPv6; | ||
| this.dbStats_.IndexedIPv6 = !this.dbStats_.OldBIN && this.dbStats_.IndexBaseAddrIPv6 > 0; | ||
| this.dbStats_.ColumnSize = this.dbStats_.DBColumn << 2; | ||
| this.dbStats_.ColumnSizeIPv6 = 16 + (this.dbStats_.DBColumn - 1 << 2); | ||
| const dbType = this.dbStats_.DBType; | ||
| for (const key of Object.keys(Position)) { | ||
| this.offset_[key] = Position[key][dbType] ? Position[key][dbType] - 2 << 2 : 0; | ||
| this.enabled_[key] = Boolean(Position[key][dbType]); | ||
| } | ||
| this.indiciesIPv4_ = new Array(MAX_SIZE); | ||
| this.indiciesIPv6_ = new Array(MAX_SIZE); | ||
| if (this.dbStats_.Indexed) { | ||
| let pointer = this.dbStats_.IndexBaseAddr; | ||
| for (let x = 0; x < MAX_SIZE; x++) { | ||
| this.indiciesIPv4_[x] = [this.readInt32(pointer) ?? 0, this.readInt32(pointer + 4) ?? 0]; | ||
| pointer += 8; | ||
| } | ||
| if (this.dbStats_.IndexedIPv6) for (let x = 0; x < MAX_SIZE; x++) { | ||
| this.indiciesIPv6_[x] = [this.readInt32(pointer) ?? 0, this.readInt32(pointer + 4) ?? 0]; | ||
| pointer += 8; | ||
| } | ||
| } | ||
| this.readerStatus_ = 2; | ||
| } | ||
| /** | ||
| * Get reader status | ||
| */ | ||
| get readerStatus() { | ||
| return this.readerStatus_; | ||
| } | ||
| /** | ||
| * Close database and uninitialize reader | ||
| */ | ||
| close() { | ||
| this.readerStatus_ = 0; | ||
| if (this.fd_ !== null) try { | ||
| node_fs.default.closeSync(this.fd_); | ||
| } catch {} | ||
| if (this.fsWatcher_ !== null) this.fsWatcher_.close(); | ||
| this.dbPath_ = null; | ||
| this.cacheInMemory_ = false; | ||
| this.fd_ = null; | ||
| this.dbCache_ = null; | ||
| this.fsWatcher_ = null; | ||
| this.indiciesIPv4_ = []; | ||
| this.indiciesIPv6_ = []; | ||
| this.offset_ = {}; | ||
| this.enabled_ = {}; | ||
| this.dbStats_ = { | ||
| DBType: 0, | ||
| DBColumn: 0, | ||
| DBYear: 0, | ||
| DBMonth: 0, | ||
| DBDay: 0, | ||
| DBCount: 0, | ||
| DBCountIPv6: 0, | ||
| BaseAddr: 0, | ||
| BaseAddrIPv6: 0, | ||
| IndexBaseAddr: 0, | ||
| IndexBaseAddrIPv6: 0, | ||
| ColumnSize: 0, | ||
| ColumnSizeIPv6: 0, | ||
| ProductCode: 0, | ||
| ProductType: 0, | ||
| FileSize: 0, | ||
| Indexed: false, | ||
| IndexedIPv6: false, | ||
| OldBIN: false | ||
| }; | ||
| } | ||
| /** | ||
| * Initialize IP2Location database reader | ||
| * @param dbPath IP2Location BIN database | ||
| * @param options Options for database reader | ||
| */ | ||
| init(dbPath, options) { | ||
| if (!dbPath) throw new Error("Must specify path to database"); | ||
| this.dbPath_ = dbPath; | ||
| this.cacheInMemory_ = options?.cacheDatabaseInMemory ?? false; | ||
| this.loadDatabase(); | ||
| if (options?.reloadOnDbUpdate) this.watchDbFile(); | ||
| } | ||
| /** | ||
| * Watch database file for changes and re-init if a change is detected | ||
| */ | ||
| watchDbFile() { | ||
| let timeout = null; | ||
| const originalState = this.readerStatus_; | ||
| const dbChangeHandler = (filename) => { | ||
| if (filename && this.dbPath_ && node_fs.default.existsSync(this.dbPath_)) { | ||
| this.fsWatcher_?.close(); | ||
| this.loadDatabase(); | ||
| this.fsWatcher_ = getFsWatch(); | ||
| } else this.readerStatus_ = originalState; | ||
| }; | ||
| const getFsWatch = () => { | ||
| if (!this.dbPath_) throw new Error("Path to database not available"); | ||
| return node_fs.default.watch(this.dbPath_, (eventType, filename) => { | ||
| if (!filename) return; | ||
| if (this.fd_) this.readerStatus_ = 1; | ||
| if (timeout !== null) clearTimeout(timeout); | ||
| timeout = setTimeout(() => { | ||
| timeout = null; | ||
| dbChangeHandler(filename); | ||
| }, 500); | ||
| }); | ||
| }; | ||
| this.fsWatcher_ = getFsWatch(); | ||
| } | ||
| /** | ||
| * Populate data object with database query results for an IP address | ||
| * @param ipNum IP number | ||
| * @param ipVersion IP version | ||
| * @param data Output data object | ||
| */ | ||
| query(ipNum, ipVersion, data) { | ||
| let low = 0; | ||
| let high; | ||
| let maxIpRange; | ||
| let baseAddr; | ||
| let columnSize; | ||
| if (ipVersion === 6) { | ||
| maxIpRange = MAX_IPV6_RANGE; | ||
| high = this.dbStats_.DBCountIPv6; | ||
| baseAddr = this.dbStats_.BaseAddrIPv6; | ||
| columnSize = this.dbStats_.ColumnSizeIPv6; | ||
| if (this.dbStats_.IndexedIPv6) { | ||
| const indexaddr = Number(ipNum >> BigInt(112)); | ||
| low = this.indiciesIPv6_[indexaddr][0]; | ||
| high = this.indiciesIPv6_[indexaddr][1]; | ||
| } | ||
| } else { | ||
| maxIpRange = MAX_IPV4_RANGE; | ||
| high = this.dbStats_.DBCount; | ||
| baseAddr = this.dbStats_.BaseAddr; | ||
| columnSize = this.dbStats_.ColumnSize; | ||
| if (this.dbStats_.Indexed) { | ||
| const indexaddr = Number(ipNum) >>> 16; | ||
| low = this.indiciesIPv4_[indexaddr][0]; | ||
| high = this.indiciesIPv4_[indexaddr][1]; | ||
| } | ||
| } | ||
| if (ipNum >= maxIpRange) ipNum = maxIpRange - BigInt(1); | ||
| while (low <= high) { | ||
| const mid = Math.floor((low + high) / 2); | ||
| const rowoffset = baseAddr + mid * columnSize; | ||
| const rowoffset2 = rowoffset + columnSize; | ||
| const ipfrom = ipVersion === 6 ? this.readInt128Big(rowoffset) : this.readInt32Big(rowoffset); | ||
| const ipto = ipVersion === 6 ? this.readInt128Big(rowoffset2) : this.readInt32Big(rowoffset2); | ||
| if (ipfrom === void 0 || ipto === void 0) break; | ||
| if (ipfrom <= ipNum && ipto > ipNum) { | ||
| const firstcol = ipVersion === 6 ? 16 : 4; | ||
| const buff = this.readToBuffer(columnSize - firstcol, rowoffset + firstcol - 1); | ||
| if (!buff) break; | ||
| const enabledKeys = Object.keys(this.enabled_).filter((key) => this.enabled_[key]); | ||
| for (const key of enabledKeys) if (key === "country") { | ||
| const countrypos = this.readBufferInt32(this.offset_[key], buff); | ||
| data.country_short = this.readString(countrypos) ?? ""; | ||
| data.country_long = this.readString(countrypos + 3) ?? ""; | ||
| } else if (key === "longitude" || key === "latitude") { | ||
| const num = this.readBufferFloat(this.offset_[key], buff); | ||
| data[key] = num !== 0 ? Math.round(num * 1e6) / 1e6 : null; | ||
| } else data[key] = this.readString(this.readBufferInt32(this.offset_[key], buff)) ?? ""; | ||
| data.status = "OK"; | ||
| return; | ||
| } else if (ipfrom > ipNum) high = mid - 1; | ||
| else low = mid + 1; | ||
| } | ||
| data.status = "IP_ADDRESS_NOT_FOUND"; | ||
| } | ||
| /** | ||
| * Query IP2Location database with an IP and get location information | ||
| * @param ip IP address | ||
| */ | ||
| get(ip) { | ||
| const data = { | ||
| ip, | ||
| ip_no: "", | ||
| status: "" | ||
| }; | ||
| if (this.readerStatus_ === 0) data.status = "NOT_INITIALIZED"; | ||
| else if (this.readerStatus_ === 1) data.status = "INITIALIZING"; | ||
| else if (!this.dbPath_ || !this.dbCache_ && !node_fs.default.existsSync(this.dbPath_)) data.status = "DATABASE_NOT_FOUND"; | ||
| else if (!this.dbStats_.DBType) data.status = "NOT_INITIALIZED"; | ||
| if (data.status) return data; | ||
| const { ipVersion, ipNum } = parseIp(ip); | ||
| if (!ipVersion) data.status = "INVALID_IP_ADDRESS"; | ||
| else if (ipVersion === 6 && this.dbStats_.OldBIN) data.status = "IPV6_NOT_SUPPORTED"; | ||
| if (data.status) return data; | ||
| data.ip_no = ipNum.toString(); | ||
| this.query(ipNum, ipVersion, data); | ||
| return data; | ||
| } | ||
| }; | ||
| // src/csv-reader.ts | ||
| var import_node_fs2 = __toESM(require("fs"), 1); | ||
| var import_csv_parser = __toESM(require("csv-parser"), 1); | ||
| //#endregion | ||
| //#region src/csv-reader.ts | ||
| var CsvReader = class { | ||
| readerStatus_; | ||
| fsWatcher_; | ||
| reloadPromise_; | ||
| requiredCsvHeaders_; | ||
| constructor() { | ||
| this.readerStatus_ = 0 /* NotInitialized */; | ||
| this.fsWatcher_ = null; | ||
| this.reloadPromise_ = Promise.resolve(); | ||
| this.requiredCsvHeaders_ = []; | ||
| } | ||
| /** | ||
| * Load IP2Location CSV databas | ||
| * @param csvPath Filesystem path to IP2Location CSV database | ||
| */ | ||
| async loadCsv(csvPath) { | ||
| const parser = import_node_fs2.default.createReadStream(csvPath).pipe((0, import_csv_parser.default)()); | ||
| let firstRecord = true; | ||
| for await (const record of parser) { | ||
| const inputData = record; | ||
| if (firstRecord && Object.keys(inputData).filter((key) => this.requiredCsvHeaders_.includes(key)).length !== this.requiredCsvHeaders_.length) { | ||
| throw new Error("CSV database does not have expected headings"); | ||
| } | ||
| firstRecord = false; | ||
| this.processRecord(inputData); | ||
| } | ||
| } | ||
| /** | ||
| * Get reader status | ||
| */ | ||
| get readerStatus() { | ||
| return this.readerStatus_; | ||
| } | ||
| /** | ||
| * Get DB reload promise | ||
| */ | ||
| get reloadPromise() { | ||
| return this.reloadPromise_; | ||
| } | ||
| /** | ||
| * Close database and uninitialize reader | ||
| */ | ||
| close() { | ||
| this.readerStatus_ = 0 /* NotInitialized */; | ||
| if (this.fsWatcher_ !== null) { | ||
| this.fsWatcher_.close(); | ||
| } | ||
| this.fsWatcher_ = null; | ||
| } | ||
| /** | ||
| * Initialize reader | ||
| * @param dbPath IP2Location CSV database | ||
| * @param reloadOnDbUpdate Options for database reader | ||
| */ | ||
| async init(dbPath, reloadOnDbUpdate) { | ||
| if (!dbPath) { | ||
| throw new Error("Must specify path to CSV database"); | ||
| } | ||
| this.readerStatus_ = 1 /* Initializing */; | ||
| await this.loadCsv(dbPath); | ||
| if (reloadOnDbUpdate) { | ||
| this.watchDbFile(dbPath); | ||
| } | ||
| this.readerStatus_ = 2 /* Ready */; | ||
| } | ||
| /** | ||
| * Watch database file for changes and re-init if a change is detected | ||
| * @param dbPath Path to watch | ||
| */ | ||
| watchDbFile(dbPath) { | ||
| let timeout = null; | ||
| const dbChangeHandler = (filename) => { | ||
| if (filename && import_node_fs2.default.existsSync(dbPath)) { | ||
| if (this.fsWatcher_ !== null) { | ||
| this.fsWatcher_.close(); | ||
| this.fsWatcher_ = null; | ||
| } | ||
| this.reloadPromise_ = this.init(dbPath, true).catch(() => void 0); | ||
| } | ||
| }; | ||
| this.fsWatcher_ = import_node_fs2.default.watch(dbPath, (eventType, filename) => { | ||
| if (!filename) { | ||
| return; | ||
| } | ||
| if (timeout !== null) { | ||
| clearTimeout(timeout); | ||
| } | ||
| timeout = setTimeout(() => { | ||
| timeout = null; | ||
| dbChangeHandler(filename); | ||
| }, 500); | ||
| }); | ||
| } | ||
| readerStatus_; | ||
| fsWatcher_; | ||
| reloadPromise_; | ||
| requiredCsvHeaders_; | ||
| constructor() { | ||
| this.readerStatus_ = 0; | ||
| this.fsWatcher_ = null; | ||
| this.reloadPromise_ = Promise.resolve(); | ||
| this.requiredCsvHeaders_ = []; | ||
| } | ||
| /** | ||
| * Load IP2Location CSV databas | ||
| * @param csvPath Filesystem path to IP2Location CSV database | ||
| */ | ||
| async loadCsv(csvPath) { | ||
| const parser = node_fs.default.createReadStream(csvPath).pipe((0, csv_parse.parse)({ columns: (header) => { | ||
| if (!this.requiredCsvHeaders_.every((h) => header.includes(h))) throw new Error("CSV database does not have expected headings"); | ||
| return header; | ||
| } })); | ||
| for await (const record of parser) this.processRecord(record); | ||
| } | ||
| /** | ||
| * Get reader status | ||
| */ | ||
| get readerStatus() { | ||
| return this.readerStatus_; | ||
| } | ||
| /** | ||
| * Get DB reload promise | ||
| */ | ||
| get reloadPromise() { | ||
| return this.reloadPromise_; | ||
| } | ||
| /** | ||
| * Close database and uninitialize reader | ||
| */ | ||
| close() { | ||
| this.readerStatus_ = 0; | ||
| if (this.fsWatcher_ !== null) this.fsWatcher_.close(); | ||
| this.fsWatcher_ = null; | ||
| this.resetData(); | ||
| } | ||
| /** | ||
| * Initialize reader | ||
| * @param dbPath IP2Location CSV database | ||
| * @param reloadOnDbUpdate Options for database reader | ||
| */ | ||
| async init(dbPath, reloadOnDbUpdate) { | ||
| if (!dbPath) throw new Error("Must specify path to CSV database"); | ||
| this.readerStatus_ = 1; | ||
| this.resetData(); | ||
| await this.loadCsv(dbPath); | ||
| if (reloadOnDbUpdate) this.watchDbFile(dbPath); | ||
| this.readerStatus_ = 2; | ||
| } | ||
| /** | ||
| * Watch database file for changes and re-init if a change is detected | ||
| * @param dbPath Path to watch | ||
| */ | ||
| watchDbFile(dbPath) { | ||
| let timeout = null; | ||
| const dbChangeHandler = (filename) => { | ||
| if (filename && node_fs.default.existsSync(dbPath)) { | ||
| if (this.fsWatcher_ !== null) { | ||
| this.fsWatcher_.close(); | ||
| this.fsWatcher_ = null; | ||
| } | ||
| this.reloadPromise_ = this.init(dbPath, true).catch(() => void 0); | ||
| } | ||
| }; | ||
| this.fsWatcher_ = node_fs.default.watch(dbPath, (eventType, filename) => { | ||
| if (!filename) return; | ||
| if (timeout !== null) clearTimeout(timeout); | ||
| timeout = setTimeout(() => { | ||
| timeout = null; | ||
| dbChangeHandler(filename); | ||
| }, 500); | ||
| }); | ||
| } | ||
| }; | ||
| // src/subdiv-reader.ts | ||
| //#endregion | ||
| //#region src/subdiv-reader.ts | ||
| var SubdivReader = class extends CsvReader { | ||
| subdivisionMap_; | ||
| constructor() { | ||
| super(); | ||
| this.subdivisionMap_ = {}; | ||
| this.requiredCsvHeaders_ = ["country_code", "subdivision_name", "code"]; | ||
| } | ||
| /** | ||
| * Process line from IP2Location subdivision database | ||
| * @param record Individual row from CSV database, broken into key/value pairs based on CSV headers | ||
| */ | ||
| processRecord(record) { | ||
| const { country_code, subdivision_name, code } = record; | ||
| const subdivisionCode = code?.length > 3 ? code.substring(3) : void 0; | ||
| if (!subdivisionCode) { | ||
| return; | ||
| } | ||
| const countryMap = this.subdivisionMap_[country_code] ?? {}; | ||
| countryMap[subdivision_name] = subdivisionCode; | ||
| this.subdivisionMap_[country_code] = countryMap; | ||
| } | ||
| /** | ||
| * Close database and uninitialize reader | ||
| */ | ||
| close() { | ||
| super.close(); | ||
| this.subdivisionMap_ = {}; | ||
| } | ||
| /** | ||
| * Get the ISO 3166-2 subdivision part from a country code and region | ||
| * @param country ISO 3166-1 country code from IP2Location database | ||
| * @param region Region from from IP2Location database | ||
| */ | ||
| get(country, region) { | ||
| if (this.readerStatus !== 2 /* Ready */) { | ||
| return null; | ||
| } | ||
| if (!country || !region) { | ||
| return null; | ||
| } | ||
| return this.subdivisionMap_[country]?.[region] ?? null; | ||
| } | ||
| subdivisionMap_; | ||
| constructor() { | ||
| super(); | ||
| this.subdivisionMap_ = {}; | ||
| this.requiredCsvHeaders_ = [ | ||
| "country_code", | ||
| "subdivision_name", | ||
| "code" | ||
| ]; | ||
| } | ||
| /** | ||
| * Process line from IP2Location subdivision database | ||
| * @param record Individual row from CSV database, broken into key/value pairs based on CSV headers | ||
| */ | ||
| processRecord(record) { | ||
| const { country_code, subdivision_name, code } = record; | ||
| const subdivisionCode = code?.length > 3 ? code.substring(3) : void 0; | ||
| if (!subdivisionCode) return; | ||
| const countryMap = this.subdivisionMap_[country_code] ?? {}; | ||
| countryMap[subdivision_name] = subdivisionCode; | ||
| this.subdivisionMap_[country_code] = countryMap; | ||
| } | ||
| /** | ||
| * Reset stored data | ||
| */ | ||
| resetData() { | ||
| this.subdivisionMap_ = {}; | ||
| } | ||
| /** | ||
| * Get the ISO 3166-2 subdivision part from a country code and region | ||
| * @param country ISO 3166-1 country code from IP2Location database | ||
| * @param region Region from from IP2Location database | ||
| */ | ||
| get(country, region) { | ||
| if (this.readerStatus !== 2) return null; | ||
| if (!country || !region) return null; | ||
| return this.subdivisionMap_[country]?.[region] ?? null; | ||
| } | ||
| }; | ||
| // src/geonameid-reader.ts | ||
| //#endregion | ||
| //#region src/geonameid-reader.ts | ||
| var GeoNameIdReader = class extends CsvReader { | ||
| geoNameIdMap_; | ||
| constructor() { | ||
| super(); | ||
| this.geoNameIdMap_ = {}; | ||
| this.requiredCsvHeaders_ = ["country_code", "region_name", "city_name", "geonameid"]; | ||
| } | ||
| /** | ||
| * Process line from IP2Location GeoName ID database | ||
| * @param record Individual row from CSV database, broken into key/value pairs based on CSV headers | ||
| */ | ||
| processRecord(record) { | ||
| const { country_code, region_name, city_name, geonameid } = record; | ||
| if (!/^\d+$/.test(geonameid)) { | ||
| return; | ||
| } | ||
| const countryMap = this.geoNameIdMap_[country_code] ?? {}; | ||
| const regionMap = countryMap[region_name] ?? {}; | ||
| regionMap[city_name] = parseInt(geonameid); | ||
| countryMap[region_name] = regionMap; | ||
| this.geoNameIdMap_[country_code] = countryMap; | ||
| } | ||
| /** | ||
| * Close database and uninitialize reader | ||
| */ | ||
| close() { | ||
| super.close(); | ||
| this.geoNameIdMap_ = {}; | ||
| } | ||
| /** | ||
| * Get the GeoName ID from a country code, region, and city | ||
| * @param country ISO 3166-1 country code from IP2Location database | ||
| * @param region Region from from IP2Location database | ||
| * @param city City from IP2Location database | ||
| */ | ||
| get(country, region, city) { | ||
| if (this.readerStatus !== 2 /* Ready */) { | ||
| return null; | ||
| } | ||
| if (!country || !region || !city) { | ||
| return null; | ||
| } | ||
| return this.geoNameIdMap_[country]?.[region]?.[city] ?? null; | ||
| } | ||
| geoNameIdMap_; | ||
| constructor() { | ||
| super(); | ||
| this.geoNameIdMap_ = {}; | ||
| this.requiredCsvHeaders_ = [ | ||
| "country_code", | ||
| "region_name", | ||
| "city_name", | ||
| "geonameid" | ||
| ]; | ||
| } | ||
| /** | ||
| * Process line from IP2Location GeoName ID database | ||
| * @param record Individual row from CSV database, broken into key/value pairs based on CSV headers | ||
| */ | ||
| processRecord(record) { | ||
| const { country_code, region_name, city_name, geonameid } = record; | ||
| if (!/^\d+$/.test(geonameid)) return; | ||
| const countryMap = this.geoNameIdMap_[country_code] ?? {}; | ||
| const regionMap = countryMap[region_name] ?? {}; | ||
| regionMap[city_name] = parseInt(geonameid); | ||
| countryMap[region_name] = regionMap; | ||
| this.geoNameIdMap_[country_code] = countryMap; | ||
| } | ||
| /** | ||
| * Reset stored data | ||
| */ | ||
| resetData() { | ||
| this.geoNameIdMap_ = {}; | ||
| } | ||
| /** | ||
| * Get the GeoName ID from a country code, region, and city | ||
| * @param country ISO 3166-1 country code from IP2Location database | ||
| * @param region Region from from IP2Location database | ||
| * @param city City from IP2Location database | ||
| */ | ||
| get(country, region, city) { | ||
| if (this.readerStatus !== 2) return null; | ||
| if (!country || !region || !city) return null; | ||
| return this.geoNameIdMap_[country]?.[region]?.[city] ?? null; | ||
| } | ||
| }; | ||
| // src/country-info-reader.ts | ||
| //#endregion | ||
| //#region src/country-info-reader.ts | ||
| var CountryInfoReader = class extends CsvReader { | ||
| countryInfoMap_; | ||
| constructor() { | ||
| super(); | ||
| this.countryInfoMap_ = {}; | ||
| this.requiredCsvHeaders_ = ["country_code", "capital", "total_area"]; | ||
| } | ||
| /** | ||
| * Process line from IP2Location country info database | ||
| * @param record Individual row from CSV database, broken into key/value pairs based on CSV headers | ||
| */ | ||
| processRecord(record) { | ||
| const normalizeStr = (val) => { | ||
| return val.replace(/^-$/, ""); | ||
| }; | ||
| const normalizeNum = (val) => { | ||
| const numStr = normalizeStr(val).trim(); | ||
| const num = numStr ? Number(numStr) : null; | ||
| return num != null && !Number.isNaN(num) ? num : null; | ||
| }; | ||
| const countryInfoData = { | ||
| country_code: "", | ||
| capital: "", | ||
| total_area: null | ||
| }; | ||
| for (const key in record) { | ||
| countryInfoData[key] = [ | ||
| "country_numeric_code", | ||
| "total_area", | ||
| "population", | ||
| "idd_code" | ||
| ].includes(key) ? normalizeNum(record[key]) : normalizeStr(record[key]); | ||
| } | ||
| if (countryInfoData.country_code) { | ||
| this.countryInfoMap_[countryInfoData.country_code] = countryInfoData; | ||
| } | ||
| } | ||
| /** | ||
| * Close database and uninitialize reader | ||
| */ | ||
| close() { | ||
| super.close(); | ||
| this.countryInfoMap_ = {}; | ||
| } | ||
| /** | ||
| * Get country info from a country code | ||
| * @param country ISO 3166-1 country code from IP2Location database | ||
| */ | ||
| get(country) { | ||
| if (this.readerStatus !== 2 /* Ready */) { | ||
| return null; | ||
| } | ||
| if (!country) { | ||
| return null; | ||
| } | ||
| return this.countryInfoMap_[country] ?? null; | ||
| } | ||
| countryInfoMap_; | ||
| constructor() { | ||
| super(); | ||
| this.countryInfoMap_ = {}; | ||
| this.requiredCsvHeaders_ = [ | ||
| "country_code", | ||
| "capital", | ||
| "total_area" | ||
| ]; | ||
| } | ||
| /** | ||
| * Process line from IP2Location country info database | ||
| * @param record Individual row from CSV database, broken into key/value pairs based on CSV headers | ||
| */ | ||
| processRecord(record) { | ||
| const normalizeStr = (val) => { | ||
| return val.replace(/^-$/, ""); | ||
| }; | ||
| const normalizeNum = (val) => { | ||
| const numStr = normalizeStr(val).trim(); | ||
| const num = numStr ? Number(numStr) : null; | ||
| return num != null && !Number.isNaN(num) ? num : null; | ||
| }; | ||
| const countryInfoData = { | ||
| country_code: "", | ||
| capital: "", | ||
| total_area: null | ||
| }; | ||
| for (const key in record) countryInfoData[key] = [ | ||
| "country_numeric_code", | ||
| "total_area", | ||
| "population", | ||
| "idd_code" | ||
| ].includes(key) ? normalizeNum(record[key]) : normalizeStr(record[key]); | ||
| if (countryInfoData.country_code) this.countryInfoMap_[countryInfoData.country_code] = countryInfoData; | ||
| } | ||
| /** | ||
| * Reset stored data | ||
| */ | ||
| resetData() { | ||
| this.countryInfoMap_ = {}; | ||
| } | ||
| /** | ||
| * Get country info from a country code | ||
| * @param country ISO 3166-1 country code from IP2Location database | ||
| */ | ||
| get(country) { | ||
| if (this.readerStatus !== 2) return null; | ||
| if (!country) return null; | ||
| return this.countryInfoMap_[country] ?? null; | ||
| } | ||
| }; | ||
| // src/iata-icao-reader.ts | ||
| //#endregion | ||
| //#region src/iata-icao-reader.ts | ||
| var IataIcaoReader = class extends CsvReader { | ||
| iataIcaoMap_; | ||
| constructor() { | ||
| super(); | ||
| this.iataIcaoMap_ = {}; | ||
| this.requiredCsvHeaders_ = [ | ||
| "country_code", | ||
| "region_name", | ||
| "iata", | ||
| "icao", | ||
| "latitude", | ||
| "longitude" | ||
| ]; | ||
| } | ||
| /** | ||
| * Process line from IP2Location IATA/ICAO airport database | ||
| * @param record Individual row from CSV database, broken into key/value pairs based on CSV headers | ||
| */ | ||
| processRecord(record) { | ||
| const normalizeStr = (val) => { | ||
| return val.replace(/^-$/, ""); | ||
| }; | ||
| const normalizeNum = (val) => { | ||
| const numStr = normalizeStr(val).trim(); | ||
| const num = numStr ? Number(numStr) : null; | ||
| return num != null && !Number.isNaN(num) ? num : null; | ||
| }; | ||
| const airportOutputData = { | ||
| iata: "", | ||
| icao: "", | ||
| airport: "", | ||
| latitude: null, | ||
| longitude: null | ||
| }; | ||
| const { country_code, region_name } = record; | ||
| for (const key in record) { | ||
| if (["country_code", "region_name"].includes(key)) { | ||
| continue; | ||
| } | ||
| airportOutputData[key] = ["latitude", "longitude"].includes(key) ? normalizeNum(record[key]) : normalizeStr(record[key]); | ||
| } | ||
| const countryMap = this.iataIcaoMap_[country_code] ?? {}; | ||
| const regionArray = countryMap[region_name] ?? []; | ||
| regionArray.push(airportOutputData); | ||
| countryMap[region_name] = regionArray; | ||
| this.iataIcaoMap_[country_code] = countryMap; | ||
| } | ||
| /** | ||
| * Close database and uninitialize reader | ||
| */ | ||
| close() { | ||
| super.close(); | ||
| this.iataIcaoMap_ = {}; | ||
| } | ||
| /** | ||
| * Get IATA/ICAO airport info from country code and region name | ||
| * @param country ISO 3166-1 country code from IP2Location database | ||
| * @param region Region from from IP2Location database | ||
| */ | ||
| get(country, region) { | ||
| if (this.readerStatus !== 2 /* Ready */) { | ||
| return []; | ||
| } | ||
| if (!country || !region) { | ||
| return []; | ||
| } | ||
| return this.iataIcaoMap_[country]?.[region] ?? []; | ||
| } | ||
| iataIcaoMap_; | ||
| constructor() { | ||
| super(); | ||
| this.iataIcaoMap_ = {}; | ||
| this.requiredCsvHeaders_ = [ | ||
| "country_code", | ||
| "region_name", | ||
| "iata", | ||
| "icao", | ||
| "latitude", | ||
| "longitude" | ||
| ]; | ||
| } | ||
| /** | ||
| * Process line from IP2Location IATA/ICAO airport database | ||
| * @param record Individual row from CSV database, broken into key/value pairs based on CSV headers | ||
| */ | ||
| processRecord(record) { | ||
| const normalizeStr = (val) => { | ||
| return val.replace(/^-$/, ""); | ||
| }; | ||
| const normalizeNum = (val) => { | ||
| const numStr = normalizeStr(val).trim(); | ||
| const num = numStr ? Number(numStr) : null; | ||
| return num != null && !Number.isNaN(num) ? num : null; | ||
| }; | ||
| const airportOutputData = { | ||
| iata: "", | ||
| icao: "", | ||
| airport: "", | ||
| latitude: null, | ||
| longitude: null | ||
| }; | ||
| const { country_code, region_name } = record; | ||
| for (const key in record) { | ||
| if (["country_code", "region_name"].includes(key)) continue; | ||
| airportOutputData[key] = ["latitude", "longitude"].includes(key) ? normalizeNum(record[key]) : normalizeStr(record[key]); | ||
| } | ||
| const countryMap = this.iataIcaoMap_[country_code] ?? {}; | ||
| const regionArray = countryMap[region_name] ?? []; | ||
| regionArray.push(airportOutputData); | ||
| countryMap[region_name] = regionArray; | ||
| this.iataIcaoMap_[country_code] = countryMap; | ||
| } | ||
| /** | ||
| * Reset stored data | ||
| */ | ||
| resetData() { | ||
| this.iataIcaoMap_ = {}; | ||
| } | ||
| /** | ||
| * Get IATA/ICAO airport info from country code and region name | ||
| * @param country ISO 3166-1 country code from IP2Location database | ||
| * @param region Region from from IP2Location database | ||
| */ | ||
| get(country, region) { | ||
| if (this.readerStatus !== 2) return []; | ||
| if (!country || !region) return []; | ||
| return this.iataIcaoMap_[country]?.[region] ?? []; | ||
| } | ||
| }; | ||
| // src/index.ts | ||
| //#endregion | ||
| //#region src/continent-reader.ts | ||
| var ContinentReader = class extends CsvReader { | ||
| continentMap_; | ||
| constructor() { | ||
| super(); | ||
| this.continentMap_ = {}; | ||
| this.requiredCsvHeaders_ = [ | ||
| "lang", | ||
| "country_alpha2_code", | ||
| "continent_code", | ||
| "continent" | ||
| ]; | ||
| } | ||
| /** | ||
| * Process line from IP2Location continent database | ||
| * @param record Individual row from CSV database, broken into key/value pairs based on CSV headers | ||
| */ | ||
| processRecord(record) { | ||
| if (record.lang !== "EN") return; | ||
| const { country_alpha2_code, continent_code, continent } = record; | ||
| if (!country_alpha2_code) return; | ||
| this.continentMap_[country_alpha2_code] = { | ||
| continent_code, | ||
| continent | ||
| }; | ||
| } | ||
| /** | ||
| * Reset stored data | ||
| */ | ||
| resetData() { | ||
| this.continentMap_ = {}; | ||
| } | ||
| /** | ||
| * Get continent info from a country code | ||
| * @param country ISO 3166-1 country code from IP2Location database | ||
| */ | ||
| get(country) { | ||
| if (this.readerStatus !== 2) return null; | ||
| if (!country) return null; | ||
| return this.continentMap_[country] ?? null; | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/olson-tz-reader.ts | ||
| var OlsonTzReader = class extends CsvReader { | ||
| olsonTzMap_; | ||
| constructor() { | ||
| super(); | ||
| this.olsonTzMap_ = {}; | ||
| this.requiredCsvHeaders_ = [ | ||
| "country_code", | ||
| "region_name", | ||
| "city_name", | ||
| "olson_tz", | ||
| "abbreviation", | ||
| "dst_start", | ||
| "dst_end" | ||
| ]; | ||
| } | ||
| /** | ||
| * Process line from IP2Location Olson TZ database | ||
| * @param record Individual row from CSV database, broken into key/value pairs based on CSV headers | ||
| */ | ||
| processRecord(record) { | ||
| const { country_code, region_name, city_name, olson_tz, abbreviation, dst_start, dst_end } = record; | ||
| if (!olson_tz) return; | ||
| const countryMap = this.olsonTzMap_[country_code] ?? {}; | ||
| const regionMap = countryMap[region_name] ?? {}; | ||
| regionMap[city_name] = { | ||
| olson_tz, | ||
| abbreviation, | ||
| dst_start: dst_start || null, | ||
| dst_end: dst_end || null | ||
| }; | ||
| countryMap[region_name] = regionMap; | ||
| this.olsonTzMap_[country_code] = countryMap; | ||
| } | ||
| /** | ||
| * Reset stored data | ||
| */ | ||
| resetData() { | ||
| this.olsonTzMap_ = {}; | ||
| } | ||
| /** | ||
| * Get the Olson Time Zone from a country code, region, and city | ||
| * @param country ISO 3166-1 country code from IP2Location database | ||
| * @param region Region from from IP2Location database | ||
| * @param city City from IP2Location database | ||
| */ | ||
| get(country, region, city) { | ||
| if (this.readerStatus !== 2) return null; | ||
| if (!country || !region || !city) return null; | ||
| return this.olsonTzMap_[country]?.[region]?.[city] ?? null; | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/index.ts | ||
| var Ip2lReader = class { | ||
| dbReader_; | ||
| subdivReader_; | ||
| geoNameIdReader_; | ||
| countryInfoReader_; | ||
| iataIcaoReader_; | ||
| constructor() { | ||
| this.dbReader_ = new DbReader(); | ||
| } | ||
| /** | ||
| * Initialize IP2Location database reader(s) | ||
| * @param dbPath IP2Location BIN database | ||
| * @param options Options for database reader | ||
| */ | ||
| async init(dbPath, options) { | ||
| this.dbReader_.init(dbPath, options); | ||
| if (!options) { | ||
| return; | ||
| } | ||
| if (options.subdivisionCsvPath) { | ||
| this.subdivReader_ = new SubdivReader(); | ||
| await this.subdivReader_.init(options.subdivisionCsvPath, options.reloadOnDbUpdate); | ||
| } | ||
| if (options.geoNameIdCsvPath) { | ||
| this.geoNameIdReader_ = new GeoNameIdReader(); | ||
| await this.geoNameIdReader_.init(options.geoNameIdCsvPath, options.reloadOnDbUpdate); | ||
| } | ||
| if (options.countryInfoCsvPath) { | ||
| this.countryInfoReader_ = new CountryInfoReader(); | ||
| await this.countryInfoReader_.init(options.countryInfoCsvPath, options.reloadOnDbUpdate); | ||
| } | ||
| if (options.iataIcaoCsvPath) { | ||
| this.iataIcaoReader_ = new IataIcaoReader(); | ||
| await this.iataIcaoReader_.init(options.iataIcaoCsvPath, options.reloadOnDbUpdate); | ||
| } | ||
| } | ||
| /** | ||
| * Query IP2Location database with an IP and get location information | ||
| * @param ip IP address | ||
| */ | ||
| get(ip) { | ||
| const ip2lData = this.dbReader_.get(ip); | ||
| if (this.subdivReader_) { | ||
| if (typeof ip2lData.country_short === "string" && typeof ip2lData.region === "string") { | ||
| const subdivision = this.subdivReader_.get(ip2lData.country_short, ip2lData.region); | ||
| if (subdivision !== null) { | ||
| ip2lData.subdivision = subdivision; | ||
| } | ||
| } | ||
| } | ||
| if (this.geoNameIdReader_) { | ||
| if (typeof ip2lData.country_short === "string" && typeof ip2lData.region === "string" && typeof ip2lData.city === "string") { | ||
| const geoNameId = this.geoNameIdReader_.get( | ||
| ip2lData.country_short, | ||
| ip2lData.region, | ||
| ip2lData.city | ||
| ); | ||
| if (geoNameId !== null) { | ||
| ip2lData.geoname_id = geoNameId; | ||
| } | ||
| } | ||
| } | ||
| if (this.countryInfoReader_) { | ||
| if (typeof ip2lData.country_short === "string") { | ||
| const countryInfo = this.countryInfoReader_.get(ip2lData.country_short); | ||
| ip2lData.country_info = countryInfo; | ||
| } | ||
| } | ||
| if (this.iataIcaoReader_) { | ||
| if (typeof ip2lData.country_short === "string" && typeof ip2lData.region === "string") { | ||
| const airports = this.iataIcaoReader_.get(ip2lData.country_short, ip2lData.region); | ||
| ip2lData.airports = airports; | ||
| } | ||
| } | ||
| return ip2lData; | ||
| } | ||
| /** | ||
| * Close IP2Location database(s) and uninitialize reader(s) | ||
| */ | ||
| close() { | ||
| this.dbReader_.close(); | ||
| this.subdivReader_?.close(); | ||
| this.geoNameIdReader_?.close(); | ||
| this.countryInfoReader_?.close(); | ||
| this.iataIcaoReader_?.close(); | ||
| } | ||
| dbReader_; | ||
| subdivReader_; | ||
| geoNameIdReader_; | ||
| countryInfoReader_; | ||
| iataIcaoReader_; | ||
| continentReader_; | ||
| olsonTzReader_; | ||
| constructor() { | ||
| this.dbReader_ = new DbReader(); | ||
| } | ||
| /** | ||
| * Initialize IP2Location database reader(s) | ||
| * @param dbPath IP2Location BIN database | ||
| * @param options Options for database reader | ||
| */ | ||
| async init(dbPath, options) { | ||
| this.dbReader_.init(dbPath, options); | ||
| if (!options) return; | ||
| if (options.subdivisionCsvPath) { | ||
| this.subdivReader_ = new SubdivReader(); | ||
| await this.subdivReader_.init(options.subdivisionCsvPath, options.reloadOnDbUpdate); | ||
| } | ||
| if (options.geoNameIdCsvPath) { | ||
| this.geoNameIdReader_ = new GeoNameIdReader(); | ||
| await this.geoNameIdReader_.init(options.geoNameIdCsvPath, options.reloadOnDbUpdate); | ||
| } | ||
| if (options.countryInfoCsvPath) { | ||
| this.countryInfoReader_ = new CountryInfoReader(); | ||
| await this.countryInfoReader_.init(options.countryInfoCsvPath, options.reloadOnDbUpdate); | ||
| } | ||
| if (options.iataIcaoCsvPath) { | ||
| this.iataIcaoReader_ = new IataIcaoReader(); | ||
| await this.iataIcaoReader_.init(options.iataIcaoCsvPath, options.reloadOnDbUpdate); | ||
| } | ||
| if (options.continentCsvPath) { | ||
| this.continentReader_ = new ContinentReader(); | ||
| await this.continentReader_.init(options.continentCsvPath, options.reloadOnDbUpdate); | ||
| } | ||
| if (options.olsonTzCsvPath) { | ||
| this.olsonTzReader_ = new OlsonTzReader(); | ||
| await this.olsonTzReader_.init(options.olsonTzCsvPath, options.reloadOnDbUpdate); | ||
| } | ||
| } | ||
| /** | ||
| * Query IP2Location database with an IP and get location information | ||
| * @param ip IP address | ||
| */ | ||
| get(ip) { | ||
| const ip2lData = this.dbReader_.get(ip); | ||
| if (this.subdivReader_ && typeof ip2lData.country_short === "string" && typeof ip2lData.region === "string") ip2lData.subdivision = this.subdivReader_.get(ip2lData.country_short, ip2lData.region); | ||
| if (this.geoNameIdReader_ && typeof ip2lData.country_short === "string" && typeof ip2lData.region === "string" && typeof ip2lData.city === "string") ip2lData.geoname_id = this.geoNameIdReader_.get(ip2lData.country_short, ip2lData.region, ip2lData.city); | ||
| if (this.countryInfoReader_ && typeof ip2lData.country_short === "string") ip2lData.country_info = this.countryInfoReader_.get(ip2lData.country_short); | ||
| if (this.iataIcaoReader_ && typeof ip2lData.country_short === "string" && typeof ip2lData.region === "string") ip2lData.airports = this.iataIcaoReader_.get(ip2lData.country_short, ip2lData.region); | ||
| if (this.continentReader_ && typeof ip2lData.country_short === "string") ip2lData.continent = this.continentReader_.get(ip2lData.country_short); | ||
| if (this.olsonTzReader_ && typeof ip2lData.country_short === "string" && typeof ip2lData.region === "string" && typeof ip2lData.city === "string") ip2lData.olson_timezone = this.olsonTzReader_.get(ip2lData.country_short, ip2lData.region, ip2lData.city); | ||
| return ip2lData; | ||
| } | ||
| /** | ||
| * Close IP2Location database(s) and uninitialize reader(s) | ||
| */ | ||
| close() { | ||
| this.dbReader_.close(); | ||
| this.subdivReader_?.close(); | ||
| this.geoNameIdReader_?.close(); | ||
| this.countryInfoReader_?.close(); | ||
| this.iataIcaoReader_?.close(); | ||
| this.continentReader_?.close(); | ||
| this.olsonTzReader_?.close(); | ||
| } | ||
| }; | ||
| // Annotate the CommonJS export names for ESM import in node: | ||
| 0 && (module.exports = { | ||
| Ip2lReader | ||
| }); | ||
| //#endregion | ||
| exports.Ip2lReader = Ip2lReader; | ||
| exports.default = Ip2lReader; |
+176
-259
@@ -0,269 +1,186 @@ | ||
| //#region src/interfaces.d.ts | ||
| interface Ip2lOptions { | ||
| /** | ||
| * Reload database when file changes (default: false) | ||
| */ | ||
| reloadOnDbUpdate?: boolean; | ||
| /** | ||
| * Cache database in memory (default: false) | ||
| */ | ||
| cacheDatabaseInMemory?: boolean; | ||
| /** | ||
| * Path to IP2Location subdivision CSV database (default: undefined) | ||
| */ | ||
| subdivisionCsvPath?: string; | ||
| /** | ||
| * Path to IP2Location GeoNameID CSV database (default: undefined) | ||
| */ | ||
| geoNameIdCsvPath?: string; | ||
| /** | ||
| * Path to IP2Location Country Info CSV database (default: undefined) | ||
| */ | ||
| countryInfoCsvPath?: string; | ||
| /** | ||
| * Path to IATA/ICAO airport CSV database (default: undefined) | ||
| */ | ||
| iataIcaoCsvPath?: string; | ||
| /** Reload database when file changes (default: false) */ | ||
| reloadOnDbUpdate?: boolean; | ||
| /** Cache database in memory (default: false) */ | ||
| cacheDatabaseInMemory?: boolean; | ||
| /** Path to IP2Location subdivision CSV database (default: undefined) */ | ||
| subdivisionCsvPath?: string; | ||
| /** Path to IP2Location GeoNameID CSV database (default: undefined) */ | ||
| geoNameIdCsvPath?: string; | ||
| /** Path to IP2Location Country Info CSV database (default: undefined) */ | ||
| countryInfoCsvPath?: string; | ||
| /** Path to IATA/ICAO airport CSV database (default: undefined) */ | ||
| iataIcaoCsvPath?: string; | ||
| /** Path to IP2Location Continent Multilingual CSV database (default: undefined) */ | ||
| continentCsvPath?: string; | ||
| /** Path to IP2Location Olson Time Zone CSV database (default: undefined) */ | ||
| olsonTzCsvPath?: string; | ||
| } | ||
| interface CountryInfoData { | ||
| [key: string]: string | number | null | undefined; | ||
| /** | ||
| * Two-character country code based on ISO 3166 | ||
| */ | ||
| country_code: string; | ||
| /** | ||
| * Country name based on ISO 3166 | ||
| */ | ||
| country_name?: string; | ||
| /** | ||
| * Three-character country code based on ISO 3166 | ||
| */ | ||
| country_alpha3_code?: string; | ||
| /** | ||
| * Three-character country numeric code based on ISO 3166 | ||
| */ | ||
| country_numeric_code?: number | null; | ||
| /** | ||
| * Capital of the country | ||
| */ | ||
| capital: string; | ||
| /** | ||
| * Demonym of the country | ||
| */ | ||
| country_demonym?: string; | ||
| /** | ||
| * Total area in square-km | ||
| */ | ||
| total_area: number | null; | ||
| /** | ||
| * Population of year 2014 | ||
| */ | ||
| population?: number | null; | ||
| /** | ||
| * The IDD prefix to call the city from another country | ||
| */ | ||
| idd_code?: number | null; | ||
| /** | ||
| * Currency code based on ISO 4217 | ||
| */ | ||
| currency_code?: string; | ||
| /** | ||
| * Currency name | ||
| */ | ||
| currency_name?: string; | ||
| /** | ||
| * Currency symbol | ||
| */ | ||
| currency_symbol?: string; | ||
| /** | ||
| * Language code based on ISO 639 | ||
| */ | ||
| lang_code?: string; | ||
| /** | ||
| * Language name | ||
| */ | ||
| lang_name?: string; | ||
| /** | ||
| * Country-Code Top-Level Domain | ||
| */ | ||
| cctld?: string; | ||
| [key: string]: string | number | null | undefined; | ||
| /** Capital of the country */ | ||
| capital: string; | ||
| /** Country-Code Top-Level Domain */ | ||
| cctld?: string; | ||
| /** Three-character country code based on ISO 3166 */ | ||
| country_alpha3_code?: string; | ||
| /** Two-character country code based on ISO 3166 */ | ||
| country_code: string; | ||
| /** Demonym of the country */ | ||
| country_demonym?: string; | ||
| /** Country name based on ISO 3166 */ | ||
| country_name?: string; | ||
| /** Three-character country numeric code based on ISO 3166 */ | ||
| country_numeric_code?: number | null; | ||
| /** Currency code based on ISO 4217 */ | ||
| currency_code?: string; | ||
| /** Currency name */ | ||
| currency_name?: string; | ||
| /** Currency symbol */ | ||
| currency_symbol?: string; | ||
| /** The IDD prefix to call the city from another country */ | ||
| idd_code?: number | null; | ||
| /** Language code based on ISO 639 */ | ||
| lang_code?: string; | ||
| /** Language name */ | ||
| lang_name?: string; | ||
| /** Population of year 2014 */ | ||
| population?: number | null; | ||
| /** Total area in square-km */ | ||
| total_area: number | null; | ||
| } | ||
| interface ContinentData { | ||
| /** Two-character continent code (AF, AN, AS, EU, NA, OC, SA) */ | ||
| continent_code: string; | ||
| /** Continent name */ | ||
| continent: string; | ||
| } | ||
| interface OlsonTzData { | ||
| /** Time zone abbreviation */ | ||
| abbreviation: string; | ||
| /** The UTC ISO-8601 date when Daylight Saving Time ends */ | ||
| dst_end: string | null; | ||
| /** The UTC ISO-8601 date when Daylight Saving Time begins */ | ||
| dst_start: string | null; | ||
| /** Olson time zone */ | ||
| olson_tz: string; | ||
| } | ||
| interface IataIcaoData { | ||
| [key: string]: string | number | null | undefined; | ||
| /** | ||
| * Three-character code of IATA airport code | ||
| */ | ||
| iata: string; | ||
| /** | ||
| * Four-character code of ICAO airport code | ||
| */ | ||
| icao: string; | ||
| /** | ||
| * Airport name | ||
| */ | ||
| airport: string; | ||
| /** | ||
| * Latitude of the airport | ||
| */ | ||
| latitude: number | null; | ||
| /** | ||
| * Longitude of the airport | ||
| */ | ||
| longitude: number | null; | ||
| [key: string]: string | number | null | undefined; | ||
| /** Airport name */ | ||
| airport: string; | ||
| /** Three-character code of IATA airport code */ | ||
| iata: string; | ||
| /** Four-character code of ICAO airport code */ | ||
| icao: string; | ||
| /** Latitude of the airport */ | ||
| latitude: number | null; | ||
| /** Longitude of the airport */ | ||
| longitude: number | null; | ||
| } | ||
| interface Ip2lData { | ||
| [key: string]: string | number | CountryInfoData | IataIcaoData[] | null | undefined; | ||
| /** | ||
| * IP address | ||
| */ | ||
| ip: string | null; | ||
| /** | ||
| * IP number | ||
| */ | ||
| ip_no: string | null; | ||
| /** | ||
| * Status of geolocation query | ||
| */ | ||
| status: string | null; | ||
| /** | ||
| * ISO 3166-1 country code | ||
| */ | ||
| country_short?: string; | ||
| /** | ||
| * Country name | ||
| */ | ||
| country_long?: string; | ||
| /** | ||
| * Country info | ||
| */ | ||
| country_info?: CountryInfoData | null; | ||
| /** | ||
| * Subdivision part of ISO 3166-2 country-subdivision code | ||
| */ | ||
| subdivision?: string; | ||
| /** | ||
| * Region or state name | ||
| */ | ||
| region?: string; | ||
| /** | ||
| * City name | ||
| */ | ||
| city?: string; | ||
| /** | ||
| * Internet Service Provider or company name | ||
| */ | ||
| isp?: string; | ||
| /** | ||
| * City latitude; defaults to capital city latitude if city is unknown | ||
| */ | ||
| latitude?: number | null; | ||
| /** | ||
| * City longitude; defaults to capital city longitude if city is unknown | ||
| */ | ||
| longitude?: number | null; | ||
| /** | ||
| * Internet domain name associated with IP address range | ||
| */ | ||
| domain?: string; | ||
| /** | ||
| * ZIP/Postal code | ||
| */ | ||
| zipcode?: string; | ||
| /** | ||
| * UTC time zone (DST is supported) | ||
| */ | ||
| timezone?: string; | ||
| /** | ||
| * Internet connection type (DIAL, DSL, COMP) | ||
| */ | ||
| netspeed?: string; | ||
| /** | ||
| * The IDD prefix to call the city from another country | ||
| */ | ||
| iddcode?: string; | ||
| /** | ||
| * A varying length number assigned to geographic areas for calls between cities | ||
| */ | ||
| areacode?: string; | ||
| /** | ||
| * The code of the nearest weather observation station | ||
| */ | ||
| weatherstationcode?: string; | ||
| /** | ||
| * The name of the nearest weather observation station | ||
| */ | ||
| weatherstationname?: string; | ||
| /** | ||
| * Mobile Country Code (MCC) | ||
| */ | ||
| mcc?: string; | ||
| /** | ||
| * Mobile Network Code (MNC) | ||
| */ | ||
| mnc?: string; | ||
| /** | ||
| * Commercial brand associated with the mobile carrier | ||
| */ | ||
| mobilebrand?: string; | ||
| /** | ||
| * Average height of city above sea level in meters (m) | ||
| */ | ||
| elevation?: string; | ||
| /** | ||
| * Usage type classification of ISP or company | ||
| */ | ||
| usagetype?: string; | ||
| /** | ||
| * Address type | ||
| */ | ||
| addresstype?: string; | ||
| /** | ||
| * IAB category | ||
| */ | ||
| category?: string; | ||
| /** | ||
| * GeoName ID | ||
| */ | ||
| geoname_id?: number | null; | ||
| /** | ||
| * IATA/ICAO airport info | ||
| */ | ||
| airports?: IataIcaoData[]; | ||
| /** | ||
| * District name | ||
| */ | ||
| district?: string; | ||
| /** | ||
| * Autonomous system number | ||
| */ | ||
| asn?: string; | ||
| /** | ||
| * Autonomous system | ||
| */ | ||
| as?: string; | ||
| /** IP address */ | ||
| ip: string | null; | ||
| /** IP number */ | ||
| ip_no: string | null; | ||
| /** Status of geolocation query */ | ||
| status: string | null; | ||
| /** Address type */ | ||
| addresstype?: string; | ||
| /** IATA/ICAO airport info */ | ||
| airports?: IataIcaoData[]; | ||
| /** A varying length number assigned to geographic areas for calls between cities */ | ||
| areacode?: string; | ||
| /** Autonomous system */ | ||
| as?: string; | ||
| /** CIDR range for the whole autonomous system */ | ||
| ascidr?: string; | ||
| /** Domain name of the autonomous system registrant */ | ||
| asdomain?: string; | ||
| /** Autonomous system number */ | ||
| asn?: string; | ||
| /** Usage type of the autonomous system registrant */ | ||
| asusagetype?: string; | ||
| /** IAB category */ | ||
| category?: string; | ||
| /** City name */ | ||
| city?: string; | ||
| /** Continent */ | ||
| continent?: ContinentData | null; | ||
| /** Country info */ | ||
| country_info?: CountryInfoData | null; | ||
| /** Country name */ | ||
| country_long?: string; | ||
| /** ISO 3166-1 country code */ | ||
| country_short?: string; | ||
| /** District name */ | ||
| district?: string; | ||
| /** Internet domain name associated with IP address range */ | ||
| domain?: string; | ||
| /** Average height of city above sea level in meters (m) */ | ||
| elevation?: string; | ||
| /** GeoName ID */ | ||
| geoname_id?: number | null; | ||
| /** The IDD prefix to call the city from another country */ | ||
| iddcode?: string; | ||
| /** Internet Service Provider or company name */ | ||
| isp?: string; | ||
| /** City latitude; defaults to capital city latitude if city is unknown */ | ||
| latitude?: number | null; | ||
| /** City longitude; defaults to capital city longitude if city is unknown */ | ||
| longitude?: number | null; | ||
| /** Mobile Country Code (MCC) */ | ||
| mcc?: string; | ||
| /** Mobile Network Code (MNC) */ | ||
| mnc?: string; | ||
| /** Commercial brand associated with the mobile carrier */ | ||
| mobilebrand?: string; | ||
| /** Internet connection type (DIAL, DSL, COMP) */ | ||
| netspeed?: string; | ||
| /** Olson time zone */ | ||
| olson_timezone?: OlsonTzData | null; | ||
| /** Region or state name */ | ||
| region?: string; | ||
| /** Subdivision part of ISO 3166-2 country-subdivision code */ | ||
| subdivision?: string | null; | ||
| /** UTC time zone (DST is supported) */ | ||
| timezone?: string; | ||
| /** Usage type classification of ISP or company */ | ||
| usagetype?: string; | ||
| /** The code of the nearest weather observation station */ | ||
| weatherstationcode?: string; | ||
| /** The name of the nearest weather observation station */ | ||
| weatherstationname?: string; | ||
| /** ZIP/Postal code */ | ||
| zipcode?: string; | ||
| } | ||
| //#endregion | ||
| //#region src/index.d.ts | ||
| declare class Ip2lReader { | ||
| private dbReader_; | ||
| private subdivReader_?; | ||
| private geoNameIdReader_?; | ||
| private countryInfoReader_?; | ||
| private iataIcaoReader_?; | ||
| constructor(); | ||
| /** | ||
| * Initialize IP2Location database reader(s) | ||
| * @param dbPath IP2Location BIN database | ||
| * @param options Options for database reader | ||
| */ | ||
| init(dbPath: string, options?: Ip2lOptions): Promise<void>; | ||
| /** | ||
| * Query IP2Location database with an IP and get location information | ||
| * @param ip IP address | ||
| */ | ||
| get(ip: string): Ip2lData; | ||
| /** | ||
| * Close IP2Location database(s) and uninitialize reader(s) | ||
| */ | ||
| close(): void; | ||
| private dbReader_; | ||
| private subdivReader_?; | ||
| private geoNameIdReader_?; | ||
| private countryInfoReader_?; | ||
| private iataIcaoReader_?; | ||
| private continentReader_?; | ||
| private olsonTzReader_?; | ||
| constructor(); | ||
| /** | ||
| * Initialize IP2Location database reader(s) | ||
| * @param dbPath IP2Location BIN database | ||
| * @param options Options for database reader | ||
| */ | ||
| init(dbPath: string, options?: Ip2lOptions): Promise<void>; | ||
| /** | ||
| * Query IP2Location database with an IP and get location information | ||
| * @param ip IP address | ||
| */ | ||
| get(ip: string): Ip2lData; | ||
| /** | ||
| * Close IP2Location database(s) and uninitialize reader(s) | ||
| */ | ||
| close(): void; | ||
| } | ||
| export { type Ip2lData, type Ip2lOptions, Ip2lReader, Ip2lReader as default }; | ||
| //#endregion | ||
| export { Ip2lData, Ip2lOptions, Ip2lReader, Ip2lReader as default }; |
+23
-20
| { | ||
| "name": "ip2ldb-reader", | ||
| "version": "4.1.0", | ||
| "version": "5.0.0", | ||
| "description": "Reader for IP2Location databases", | ||
@@ -9,11 +9,12 @@ "type": "module", | ||
| "require": "./lib/index.cjs", | ||
| "import": "./lib/index.js" | ||
| "import": "./lib/index.mjs" | ||
| }, | ||
| "scripts": { | ||
| "build": "npm run lint && npm run compile && publint", | ||
| "build": "npm run check-types && npm run lint && npm run compile && publint", | ||
| "clean": "rimraf lib", | ||
| "cli": "node lib/cli.js", | ||
| "cli": "node lib/cli.mjs", | ||
| "cli:debug": "tsx src/cli.ts", | ||
| "compile": "tsup", | ||
| "compile:debug": "tsup --env.dev true", | ||
| "compile": "tsdown", | ||
| "compile:debug": "tsdown --env.dev true", | ||
| "check-types": "tsc --noEmit && tsc --noEmit -p tests", | ||
| "format": "prettier --write .", | ||
@@ -45,21 +46,23 @@ "lint": "eslint .", | ||
| "dependencies": { | ||
| "csv-parser": "^3.2.0" | ||
| "csv-parse": "^6.2.1" | ||
| }, | ||
| "devDependencies": { | ||
| "@eslint/js": "^9.39.2", | ||
| "@types/node": "^20.19.27", | ||
| "@vitest/eslint-plugin": "^1.5.2", | ||
| "eslint": "^9.39.2", | ||
| "@eslint/js": "^10.0.1", | ||
| "@types/node": "^20.19.39", | ||
| "@vitest/eslint-plugin": "^1.6.16", | ||
| "eslint": "^10.3.0", | ||
| "eslint-config-prettier": "^10.1.8", | ||
| "eslint-plugin-jsdoc": "^61.5.0", | ||
| "eslint-plugin-prettier": "^5.5.4", | ||
| "prettier": "^3.7.4", | ||
| "publint": "^0.3.16", | ||
| "rimraf": "^6.1.2", | ||
| "tsup": "^8.5.1", | ||
| "eslint-plugin-jsdoc": "^62.9.0", | ||
| "eslint-plugin-prettier": "^5.5.5", | ||
| "globals": "^17.6.0", | ||
| "jiti": "^2.6.1", | ||
| "prettier": "^3.8.3", | ||
| "publint": "^0.3.18", | ||
| "rimraf": "^6.1.3", | ||
| "tsdown": "^0.21.10", | ||
| "tsx": "^4.21.0", | ||
| "typescript": "^5.9.3", | ||
| "typescript-eslint": "^8.49.0", | ||
| "vitest": "^4.0.15" | ||
| "typescript": "^6.0.3", | ||
| "typescript-eslint": "^8.59.1", | ||
| "vitest": "^4.1.5" | ||
| } | ||
| } |
+40
-0
@@ -66,2 +66,8 @@ # ip2ldb-reader | ||
| iataIcaoCsvPath: undefined, | ||
| // {string} Path to continent multilingual CSV database | ||
| continentCsvPath: undefined, | ||
| // {string} Path to Olson time zone CSV database | ||
| olsonTzCsvPath: undefined, | ||
| } | ||
@@ -77,5 +83,12 @@ ``` | ||
| - `subdivisionCsvPath` - When a filesystem path to the [IP2Location ISO 3166-2 Subdivision Code CSV database](https://www.ip2location.com/free/iso3166-2) is provided, the country code and region will be used to identify the corresponding subdivision code. | ||
| - Requires DB3 or higher database that includes both country and region. | ||
| - `geoNameIdCsvPath` - When a filesystem path to the [IP2Location GeoName ID CSV database](https://www.ip2location.com/free/geoname-id) is provided, the country code, region, and city will be used to identify the corresponding GeoName ID. | ||
| - Requires DB3 or higher database that includes all of country, region, and city. | ||
| - `countryInfoCsvPath` - When a filesystem path to the [IP2Location Country Information CSV database](https://www.ip2location.com/free/country-information) is provided, the country code will be used to identify additional country information (capital, population, currency, language, etc.). | ||
| - `iataIcaoCsvPath` - When a filesystem path to the [IP2Location IATA/ICAO airport CSV database](https://github.com/ip2location/ip2location-iata-icao) is provided, the country code and region will be used to identify airports in the region. | ||
| - Requires DB3 or higher database that includes both country and region. | ||
| - `continentCsvPath` - When a filesystem path to the [IP2Location Continent Multilingual CSV database](https://www.ip2location.com/free/continent-multilingual) is provided, the country code will be used to identify the corresponding continent (English language only). | ||
| - `olsonTzCsvPath` - When a filesystem path to the [IP2Location Olson Time Zone CSV database](https://www.ip2location.com/free/olson-timezone) is provided, the country code, region, and city will be used to identify the corresponding time zone. | ||
| - Requires DB3 or higher database that includes all of country, region, and city. | ||
| - DB11 and higher databases include basic "shift from UTC" timezone information. This CSV database outputs more information, including the Olson time zone name and DST start/end dates. | ||
@@ -96,5 +109,9 @@ ## Return | ||
| as?: string; | ||
| ascidr?: string; | ||
| asdomain?: string; | ||
| asn?: string; | ||
| asusagetype?: string; | ||
| category?: string; | ||
| city?: string; | ||
| continent?: ContinentData | null; | ||
| country_info?: CountryInfoData | null; | ||
@@ -115,2 +132,3 @@ country_long?: string; | ||
| netspeed?: string; | ||
| olson_timezone?: OlsonTzData | null; | ||
| region?: string; | ||
@@ -160,2 +178,22 @@ subdivision?: string | null; | ||
| and | ||
| ```ts | ||
| interface ContinentData { | ||
| continent_code: string; | ||
| continent: string; | ||
| } | ||
| ``` | ||
| and | ||
| ```ts | ||
| interface OlsonTzData { | ||
| abbreviation: string; | ||
| dst_end: string | null; | ||
| dst_start: string | null; | ||
| olson_tz: string; | ||
| } | ||
| ``` | ||
| Properties suffixed by `?` only exist if the database supports them. For example, when using a DB1 (country only) database, a sample return object looks like | ||
@@ -202,2 +240,4 @@ | ||
| - [IP2LOCATION-IATA-ICAO.CSV](https://github.com/ip2location/ip2location-iata-icao) - IATA/ICAO airport database in CSV format | ||
| - [IP2LOCATION-OLSON-TIMEZONE.CSV](https://www.ip2location.com/free/olson-timezone) - Olson Time Zone database in CSV format | ||
| - [IP2LOCATION-CONTINENT-MULTILINGUAL.CSV](https://www.ip2location.com/free/continent-multilingual) - Continent Multilingual database in CSV format | ||
| - [IP2LOCATION-LITE-DB1.BIN](https://lite.ip2location.com/database/db1-ip-country) - LITE IP-COUNTRY DB1 IPv4 database in BIN format | ||
@@ -204,0 +244,0 @@ - [IP2LOCATION-SAMPLE-DB26.IPV6.BIN](https://www.ip2location.com/database/db26-ip-country-region-city-latitude-longitude-zipcode-timezone-isp-domain-netspeed-areacode-weather-mobile-elevation-usagetype-addresstype-category-district-asn) - Sample DB26 IPv6 database in BIN format |
-269
| interface Ip2lOptions { | ||
| /** | ||
| * Reload database when file changes (default: false) | ||
| */ | ||
| reloadOnDbUpdate?: boolean; | ||
| /** | ||
| * Cache database in memory (default: false) | ||
| */ | ||
| cacheDatabaseInMemory?: boolean; | ||
| /** | ||
| * Path to IP2Location subdivision CSV database (default: undefined) | ||
| */ | ||
| subdivisionCsvPath?: string; | ||
| /** | ||
| * Path to IP2Location GeoNameID CSV database (default: undefined) | ||
| */ | ||
| geoNameIdCsvPath?: string; | ||
| /** | ||
| * Path to IP2Location Country Info CSV database (default: undefined) | ||
| */ | ||
| countryInfoCsvPath?: string; | ||
| /** | ||
| * Path to IATA/ICAO airport CSV database (default: undefined) | ||
| */ | ||
| iataIcaoCsvPath?: string; | ||
| } | ||
| interface CountryInfoData { | ||
| [key: string]: string | number | null | undefined; | ||
| /** | ||
| * Two-character country code based on ISO 3166 | ||
| */ | ||
| country_code: string; | ||
| /** | ||
| * Country name based on ISO 3166 | ||
| */ | ||
| country_name?: string; | ||
| /** | ||
| * Three-character country code based on ISO 3166 | ||
| */ | ||
| country_alpha3_code?: string; | ||
| /** | ||
| * Three-character country numeric code based on ISO 3166 | ||
| */ | ||
| country_numeric_code?: number | null; | ||
| /** | ||
| * Capital of the country | ||
| */ | ||
| capital: string; | ||
| /** | ||
| * Demonym of the country | ||
| */ | ||
| country_demonym?: string; | ||
| /** | ||
| * Total area in square-km | ||
| */ | ||
| total_area: number | null; | ||
| /** | ||
| * Population of year 2014 | ||
| */ | ||
| population?: number | null; | ||
| /** | ||
| * The IDD prefix to call the city from another country | ||
| */ | ||
| idd_code?: number | null; | ||
| /** | ||
| * Currency code based on ISO 4217 | ||
| */ | ||
| currency_code?: string; | ||
| /** | ||
| * Currency name | ||
| */ | ||
| currency_name?: string; | ||
| /** | ||
| * Currency symbol | ||
| */ | ||
| currency_symbol?: string; | ||
| /** | ||
| * Language code based on ISO 639 | ||
| */ | ||
| lang_code?: string; | ||
| /** | ||
| * Language name | ||
| */ | ||
| lang_name?: string; | ||
| /** | ||
| * Country-Code Top-Level Domain | ||
| */ | ||
| cctld?: string; | ||
| } | ||
| interface IataIcaoData { | ||
| [key: string]: string | number | null | undefined; | ||
| /** | ||
| * Three-character code of IATA airport code | ||
| */ | ||
| iata: string; | ||
| /** | ||
| * Four-character code of ICAO airport code | ||
| */ | ||
| icao: string; | ||
| /** | ||
| * Airport name | ||
| */ | ||
| airport: string; | ||
| /** | ||
| * Latitude of the airport | ||
| */ | ||
| latitude: number | null; | ||
| /** | ||
| * Longitude of the airport | ||
| */ | ||
| longitude: number | null; | ||
| } | ||
| interface Ip2lData { | ||
| [key: string]: string | number | CountryInfoData | IataIcaoData[] | null | undefined; | ||
| /** | ||
| * IP address | ||
| */ | ||
| ip: string | null; | ||
| /** | ||
| * IP number | ||
| */ | ||
| ip_no: string | null; | ||
| /** | ||
| * Status of geolocation query | ||
| */ | ||
| status: string | null; | ||
| /** | ||
| * ISO 3166-1 country code | ||
| */ | ||
| country_short?: string; | ||
| /** | ||
| * Country name | ||
| */ | ||
| country_long?: string; | ||
| /** | ||
| * Country info | ||
| */ | ||
| country_info?: CountryInfoData | null; | ||
| /** | ||
| * Subdivision part of ISO 3166-2 country-subdivision code | ||
| */ | ||
| subdivision?: string; | ||
| /** | ||
| * Region or state name | ||
| */ | ||
| region?: string; | ||
| /** | ||
| * City name | ||
| */ | ||
| city?: string; | ||
| /** | ||
| * Internet Service Provider or company name | ||
| */ | ||
| isp?: string; | ||
| /** | ||
| * City latitude; defaults to capital city latitude if city is unknown | ||
| */ | ||
| latitude?: number | null; | ||
| /** | ||
| * City longitude; defaults to capital city longitude if city is unknown | ||
| */ | ||
| longitude?: number | null; | ||
| /** | ||
| * Internet domain name associated with IP address range | ||
| */ | ||
| domain?: string; | ||
| /** | ||
| * ZIP/Postal code | ||
| */ | ||
| zipcode?: string; | ||
| /** | ||
| * UTC time zone (DST is supported) | ||
| */ | ||
| timezone?: string; | ||
| /** | ||
| * Internet connection type (DIAL, DSL, COMP) | ||
| */ | ||
| netspeed?: string; | ||
| /** | ||
| * The IDD prefix to call the city from another country | ||
| */ | ||
| iddcode?: string; | ||
| /** | ||
| * A varying length number assigned to geographic areas for calls between cities | ||
| */ | ||
| areacode?: string; | ||
| /** | ||
| * The code of the nearest weather observation station | ||
| */ | ||
| weatherstationcode?: string; | ||
| /** | ||
| * The name of the nearest weather observation station | ||
| */ | ||
| weatherstationname?: string; | ||
| /** | ||
| * Mobile Country Code (MCC) | ||
| */ | ||
| mcc?: string; | ||
| /** | ||
| * Mobile Network Code (MNC) | ||
| */ | ||
| mnc?: string; | ||
| /** | ||
| * Commercial brand associated with the mobile carrier | ||
| */ | ||
| mobilebrand?: string; | ||
| /** | ||
| * Average height of city above sea level in meters (m) | ||
| */ | ||
| elevation?: string; | ||
| /** | ||
| * Usage type classification of ISP or company | ||
| */ | ||
| usagetype?: string; | ||
| /** | ||
| * Address type | ||
| */ | ||
| addresstype?: string; | ||
| /** | ||
| * IAB category | ||
| */ | ||
| category?: string; | ||
| /** | ||
| * GeoName ID | ||
| */ | ||
| geoname_id?: number | null; | ||
| /** | ||
| * IATA/ICAO airport info | ||
| */ | ||
| airports?: IataIcaoData[]; | ||
| /** | ||
| * District name | ||
| */ | ||
| district?: string; | ||
| /** | ||
| * Autonomous system number | ||
| */ | ||
| asn?: string; | ||
| /** | ||
| * Autonomous system | ||
| */ | ||
| as?: string; | ||
| } | ||
| declare class Ip2lReader { | ||
| private dbReader_; | ||
| private subdivReader_?; | ||
| private geoNameIdReader_?; | ||
| private countryInfoReader_?; | ||
| private iataIcaoReader_?; | ||
| constructor(); | ||
| /** | ||
| * Initialize IP2Location database reader(s) | ||
| * @param dbPath IP2Location BIN database | ||
| * @param options Options for database reader | ||
| */ | ||
| init(dbPath: string, options?: Ip2lOptions): Promise<void>; | ||
| /** | ||
| * Query IP2Location database with an IP and get location information | ||
| * @param ip IP address | ||
| */ | ||
| get(ip: string): Ip2lData; | ||
| /** | ||
| * Close IP2Location database(s) and uninitialize reader(s) | ||
| */ | ||
| close(): void; | ||
| } | ||
| export { type Ip2lData, type Ip2lOptions, Ip2lReader, Ip2lReader as default }; |
-940
| // src/db-reader.ts | ||
| import fs from "fs"; | ||
| // src/ip-utils.ts | ||
| import net from "net"; | ||
| var FROM_6TO4 = BigInt("42545680458834377588178886921629466624"); | ||
| var TO_6TO4 = BigInt("42550872755692912415807417417958686719"); | ||
| var FROM_TEREDO = BigInt("42540488161975842760550356425300246528"); | ||
| var TO_TEREDO = BigInt("42540488241204005274814694018844196863"); | ||
| var LAST_32BITS = BigInt("4294967295"); | ||
| function parseIp(ip) { | ||
| let ipVersion = net.isIP(ip); | ||
| let ipNum = BigInt(0); | ||
| if (ipVersion === 6) { | ||
| if (/^[:0]+:F{4}:(\d+\.){3}\d+$/i.test(ip)) { | ||
| ip = ip.replace(/^[:0]+:F{4}:/i, ""); | ||
| ipVersion = net.isIP(ip); | ||
| } else if (/^[:0]+F{4}(:[\dA-Z]{4}){2}$/i.test(ip)) { | ||
| const tmp = ip.replace(/^[:0]+F{4}:/i, "").replace(/:/, ""); | ||
| ip = (tmp.match(/../g) ?? []).map((b) => parseInt("0x" + b)).join("."); | ||
| ipVersion = net.isIP(ip); | ||
| } | ||
| } | ||
| if (ipVersion) { | ||
| ({ ipNum, ipVersion } = getIpNum(ip, ipVersion)); | ||
| } | ||
| return { ip, ipVersion, ipNum }; | ||
| } | ||
| function getIpNum(ip, ipVersion) { | ||
| let ipNum = BigInt(0); | ||
| if (ipVersion === 4) { | ||
| const d = ip.split("."); | ||
| ipNum = BigInt(((+d[0] * 256 + +d[1]) * 256 + +d[2]) * 256 + +d[3]); | ||
| } else if (ipVersion === 6) { | ||
| const maxsections = 8; | ||
| const sectionbits = 16; | ||
| const m = ip.split("::"); | ||
| let total = BigInt(0); | ||
| if (m.length === 2) { | ||
| const arrLeft = m[0] !== "" ? m[0].split(":") : []; | ||
| const arrRight = m[1] !== "" ? m[1].split(":") : []; | ||
| for (let x = 0; x < arrLeft.length; x++) { | ||
| total += BigInt(parseInt("0x" + arrLeft[x])) << BigInt((maxsections - (x + 1)) * sectionbits); | ||
| } | ||
| for (let x = 0; x < arrRight.length; x++) { | ||
| total += BigInt(parseInt("0x" + arrRight[x])) << BigInt((arrRight.length - (x + 1)) * sectionbits); | ||
| } | ||
| } else if (m.length === 1) { | ||
| const arr = m[0].split(":"); | ||
| for (let x = 0; x < arr.length; x++) { | ||
| total += BigInt(parseInt("0x" + arr[x])) << BigInt((maxsections - (x + 1)) * sectionbits); | ||
| } | ||
| } | ||
| ipNum = total; | ||
| if (ipNum >= FROM_6TO4 && ipNum <= TO_6TO4) { | ||
| ipNum = ipNum >> BigInt(80) & LAST_32BITS; | ||
| ipVersion = 4; | ||
| } else if (ipNum >= FROM_TEREDO && ipNum <= TO_TEREDO) { | ||
| ipNum = ~ipNum & LAST_32BITS; | ||
| ipVersion = 4; | ||
| } | ||
| } | ||
| return { ipNum, ipVersion }; | ||
| } | ||
| // src/db-reader.ts | ||
| var Position = { | ||
| country: [0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2], | ||
| region: [0, 0, 0, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3], | ||
| city: [0, 0, 0, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4], | ||
| isp: [0, 0, 3, 0, 5, 0, 7, 5, 7, 0, 8, 0, 9, 0, 9, 0, 9, 0, 9, 7, 9, 0, 9, 7, 9, 9, 9], | ||
| latitude: [0, 0, 0, 0, 0, 5, 5, 0, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5], | ||
| longitude: [0, 0, 0, 0, 0, 6, 6, 0, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6], | ||
| domain: [0, 0, 0, 0, 0, 0, 0, 6, 8, 0, 9, 0, 10, 0, 10, 0, 10, 0, 10, 8, 10, 0, 10, 8, 10, 10, 10], | ||
| zipcode: [0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 7, 7, 7, 0, 7, 7, 7, 0, 7, 0, 7, 7, 7, 0, 7, 7, 7], | ||
| timezone: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 8, 7, 8, 8, 8, 7, 8, 0, 8, 8, 8, 0, 8, 8, 8], | ||
| netspeed: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 11, 0, 11, 8, 11, 0, 11, 0, 11, 0, 11, 11, 11], | ||
| iddcode: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 12, 0, 12, 0, 12, 9, 12, 0, 12, 12, 12], | ||
| areacode: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 13, 0, 13, 0, 13, 10, 13, 0, 13, 13, 13], | ||
| weatherstationcode: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 14, 0, 14, 0, 14, 0, 14, 14, 14], | ||
| weatherstationname: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 15, 0, 15, 0, 15, 0, 15, 15, 15], | ||
| mcc: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 16, 0, 16, 9, 16, 16, 16], | ||
| mnc: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 17, 0, 17, 10, 17, 17, 17], | ||
| mobilebrand: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 11, 18, 0, 18, 11, 18, 18, 18], | ||
| elevation: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 11, 19, 0, 19, 19, 19], | ||
| usagetype: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 20, 20, 20], | ||
| addresstype: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 21, 21], | ||
| category: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 22, 22], | ||
| district: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 23], | ||
| asn: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 24], | ||
| as: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 25], | ||
| asdomain: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 26], | ||
| asusagetype: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 27], | ||
| ascidr: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 28] | ||
| }; | ||
| var MAX_SIZE = 65536; | ||
| var MAX_IPV4_RANGE = BigInt("4294967295"); | ||
| var MAX_IPV6_RANGE = BigInt("340282366920938463463374607431768211455"); | ||
| var DbReader = class { | ||
| readerStatus_; | ||
| dbPath_; | ||
| cacheInMemory_; | ||
| fd_; | ||
| dbCache_; | ||
| fsWatcher_; | ||
| indiciesIPv4_; | ||
| indiciesIPv6_; | ||
| offset_; | ||
| enabled_; | ||
| dbStats_; | ||
| constructor() { | ||
| this.readerStatus_ = 0 /* NotInitialized */; | ||
| this.dbPath_ = null; | ||
| this.cacheInMemory_ = false; | ||
| this.fd_ = null; | ||
| this.dbCache_ = null; | ||
| this.fsWatcher_ = null; | ||
| this.indiciesIPv4_ = []; | ||
| this.indiciesIPv6_ = []; | ||
| this.offset_ = {}; | ||
| this.enabled_ = {}; | ||
| this.dbStats_ = { | ||
| DBType: 0, | ||
| DBColumn: 0, | ||
| DBYear: 0, | ||
| DBMonth: 0, | ||
| DBDay: 0, | ||
| DBCount: 0, | ||
| DBCountIPv6: 0, | ||
| BaseAddr: 0, | ||
| BaseAddrIPv6: 0, | ||
| IndexBaseAddr: 0, | ||
| IndexBaseAddrIPv6: 0, | ||
| ColumnSize: 0, | ||
| ColumnSizeIPv6: 0, | ||
| ProductCode: 0, | ||
| ProductType: 0, | ||
| FileSize: 0, | ||
| Indexed: false, | ||
| IndexedIPv6: false, | ||
| OldBIN: false | ||
| }; | ||
| } | ||
| /** | ||
| * Read data from database into a buffer | ||
| * @param readbytes Number of bytes to read | ||
| * @param pos Offset from beginning of database | ||
| */ | ||
| readToBuffer(readbytes, pos) { | ||
| if (this.dbCache_) { | ||
| const buff2 = this.dbCache_.subarray(pos, pos + readbytes); | ||
| return buff2.length === readbytes ? buff2 : void 0; | ||
| } | ||
| if (!this.fd_) { | ||
| throw new Error("Missing file descriptor, cannot read data"); | ||
| } | ||
| const buff = Buffer.alloc(readbytes); | ||
| const totalread = fs.readSync(this.fd_, buff, 0, readbytes, pos); | ||
| return totalread === readbytes ? buff : void 0; | ||
| } | ||
| /** | ||
| * Read 8-bit integer from the database | ||
| * @param pos Offset from beginning of database | ||
| */ | ||
| readInt8(pos) { | ||
| const buff = this.readToBuffer(1, pos - 1); | ||
| return buff ? buff.readUInt8(0) : void 0; | ||
| } | ||
| /** | ||
| * Read 32-bit integer from the database | ||
| * @param pos Offset from beginning of database | ||
| */ | ||
| readInt32(pos) { | ||
| const buff = this.readToBuffer(4, pos - 1); | ||
| return buff ? buff.readUInt32LE(0) : void 0; | ||
| } | ||
| /** | ||
| * Read 32-bit integer from the database as a BigInt | ||
| * @param pos Offset from beginning of database | ||
| */ | ||
| readInt32Big(pos) { | ||
| const buff = this.readToBuffer(4, pos - 1); | ||
| return buff ? BigInt(buff.readUInt32LE(0)) : void 0; | ||
| } | ||
| /** | ||
| * Read 32-bit float from the database | ||
| * @param pos Offset from beginning of database | ||
| */ | ||
| readFloat(pos) { | ||
| const buff = this.readToBuffer(4, pos - 1); | ||
| return buff ? buff.readFloatLE(0) : void 0; | ||
| } | ||
| /** | ||
| * Read 128-bit integer from the database as a BigInt | ||
| * @param pos Offset from beginning of database | ||
| */ | ||
| readInt128Big(pos) { | ||
| const buff = this.readToBuffer(16, pos - 1); | ||
| if (!buff) { | ||
| return; | ||
| } | ||
| let ret = BigInt(0); | ||
| for (let x = 0; x < 16; x++) { | ||
| ret += BigInt(buff.readUInt8(x)) << BigInt(8 * x); | ||
| } | ||
| return ret; | ||
| } | ||
| /** | ||
| * Read string from the database | ||
| * @param pos Offset from beginning of database | ||
| */ | ||
| readString(pos) { | ||
| const strBytes = this.readInt8(pos + 1); | ||
| if (!strBytes) { | ||
| return; | ||
| } | ||
| const buff = this.readToBuffer(strBytes, pos + 1); | ||
| const str = buff ? buff.toString("utf8") : void 0; | ||
| return str === "-" ? "" : str; | ||
| } | ||
| /** | ||
| * Read 32-bit integer from a Buffer | ||
| * @param pos Offset from beginning of buffer | ||
| * @param buff Buffer | ||
| */ | ||
| readBufferInt32(pos, buff) { | ||
| return buff.readUInt32LE(pos); | ||
| } | ||
| /** | ||
| * Read 32-bit float from a buffer | ||
| * @param pos Offset from beginning of buffer | ||
| * @param buff Buffer | ||
| */ | ||
| readBufferFloat(pos, buff) { | ||
| return buff.readFloatLE(pos); | ||
| } | ||
| /** | ||
| * (Re)load database | ||
| */ | ||
| loadDatabase() { | ||
| if (!this.dbPath_) { | ||
| throw new Error("Path to database not available"); | ||
| } | ||
| this.readerStatus_ = 1 /* Initializing */; | ||
| if (this.fd_ !== null) { | ||
| try { | ||
| fs.closeSync(this.fd_); | ||
| } catch { | ||
| } | ||
| } | ||
| if (this.cacheInMemory_) { | ||
| this.fd_ = null; | ||
| this.dbCache_ = fs.readFileSync(this.dbPath_); | ||
| } else { | ||
| this.fd_ = fs.openSync(this.dbPath_, "r"); | ||
| this.dbCache_ = null; | ||
| } | ||
| this.dbStats_.DBType = this.readInt8(1) ?? 0; | ||
| this.dbStats_.DBColumn = this.readInt8(2) ?? 0; | ||
| this.dbStats_.DBYear = this.readInt8(3) ?? 0; | ||
| this.dbStats_.DBMonth = this.readInt8(4) ?? 0; | ||
| this.dbStats_.DBDay = this.readInt8(5) ?? 0; | ||
| this.dbStats_.DBCount = this.readInt32(6) ?? 0; | ||
| this.dbStats_.BaseAddr = this.readInt32(10) ?? 0; | ||
| this.dbStats_.DBCountIPv6 = this.readInt32(14) ?? 0; | ||
| this.dbStats_.BaseAddrIPv6 = this.readInt32(18) ?? 0; | ||
| this.dbStats_.IndexBaseAddr = this.readInt32(22) ?? 0; | ||
| this.dbStats_.IndexBaseAddrIPv6 = this.readInt32(26) ?? 0; | ||
| this.dbStats_.ProductCode = this.readInt8(30) ?? 0; | ||
| this.dbStats_.ProductType = this.readInt8(31) ?? 0; | ||
| this.dbStats_.FileSize = this.readInt32(32) ?? 0; | ||
| if (this.dbStats_.ProductCode !== 1 && this.dbStats_.DBYear >= 21) { | ||
| throw new Error( | ||
| "Incorrect IP2Location BIN file format. Please make sure that you are using the latest IP2Location BIN file." | ||
| ); | ||
| } | ||
| if (this.dbStats_.DBType == "P".charCodeAt(0) && this.dbStats_.DBColumn === "K".charCodeAt(0)) { | ||
| throw new Error("Incorrect IP2Location BIN file format. Please uncompress zip file."); | ||
| } | ||
| this.dbStats_.Indexed = this.dbStats_.IndexBaseAddr > 0; | ||
| this.dbStats_.OldBIN = !this.dbStats_.DBCountIPv6; | ||
| this.dbStats_.IndexedIPv6 = !this.dbStats_.OldBIN && this.dbStats_.IndexBaseAddrIPv6 > 0; | ||
| this.dbStats_.ColumnSize = this.dbStats_.DBColumn << 2; | ||
| this.dbStats_.ColumnSizeIPv6 = 16 + (this.dbStats_.DBColumn - 1 << 2); | ||
| const dbType = this.dbStats_.DBType; | ||
| for (const key of Object.keys(Position)) { | ||
| this.offset_[key] = Position[key][dbType] ? Position[key][dbType] - 2 << 2 : 0; | ||
| this.enabled_[key] = Boolean(Position[key][dbType]); | ||
| } | ||
| this.indiciesIPv4_ = new Array(MAX_SIZE); | ||
| this.indiciesIPv6_ = new Array(MAX_SIZE); | ||
| if (this.dbStats_.Indexed) { | ||
| let pointer = this.dbStats_.IndexBaseAddr; | ||
| for (let x = 0; x < MAX_SIZE; x++) { | ||
| this.indiciesIPv4_[x] = [this.readInt32(pointer) ?? 0, this.readInt32(pointer + 4) ?? 0]; | ||
| pointer += 8; | ||
| } | ||
| if (this.dbStats_.IndexedIPv6) { | ||
| for (let x = 0; x < MAX_SIZE; x++) { | ||
| this.indiciesIPv6_[x] = [this.readInt32(pointer) ?? 0, this.readInt32(pointer + 4) ?? 0]; | ||
| pointer += 8; | ||
| } | ||
| } | ||
| } | ||
| this.readerStatus_ = 2 /* Ready */; | ||
| } | ||
| /** | ||
| * Get reader status | ||
| */ | ||
| get readerStatus() { | ||
| return this.readerStatus_; | ||
| } | ||
| /** | ||
| * Close database and uninitialize reader | ||
| */ | ||
| close() { | ||
| this.readerStatus_ = 0 /* NotInitialized */; | ||
| if (this.fd_ !== null) { | ||
| try { | ||
| fs.closeSync(this.fd_); | ||
| } catch { | ||
| } | ||
| } | ||
| if (this.fsWatcher_ !== null) { | ||
| this.fsWatcher_.close(); | ||
| } | ||
| this.dbPath_ = null; | ||
| this.cacheInMemory_ = false; | ||
| this.fd_ = null; | ||
| this.dbCache_ = null; | ||
| this.fsWatcher_ = null; | ||
| this.indiciesIPv4_ = []; | ||
| this.indiciesIPv6_ = []; | ||
| this.offset_ = {}; | ||
| this.enabled_ = {}; | ||
| this.dbStats_ = { | ||
| DBType: 0, | ||
| DBColumn: 0, | ||
| DBYear: 0, | ||
| DBMonth: 0, | ||
| DBDay: 0, | ||
| DBCount: 0, | ||
| DBCountIPv6: 0, | ||
| BaseAddr: 0, | ||
| BaseAddrIPv6: 0, | ||
| IndexBaseAddr: 0, | ||
| IndexBaseAddrIPv6: 0, | ||
| ColumnSize: 0, | ||
| ColumnSizeIPv6: 0, | ||
| ProductCode: 0, | ||
| ProductType: 0, | ||
| FileSize: 0, | ||
| Indexed: false, | ||
| IndexedIPv6: false, | ||
| OldBIN: false | ||
| }; | ||
| } | ||
| /** | ||
| * Initialize IP2Location database reader | ||
| * @param dbPath IP2Location BIN database | ||
| * @param options Options for database reader | ||
| */ | ||
| init(dbPath, options) { | ||
| if (!dbPath) { | ||
| throw new Error("Must specify path to database"); | ||
| } | ||
| this.dbPath_ = dbPath; | ||
| this.cacheInMemory_ = options?.cacheDatabaseInMemory ?? false; | ||
| this.loadDatabase(); | ||
| if (options?.reloadOnDbUpdate) { | ||
| this.watchDbFile(); | ||
| } | ||
| } | ||
| /** | ||
| * Watch database file for changes and re-init if a change is detected | ||
| */ | ||
| watchDbFile() { | ||
| let timeout = null; | ||
| const originalState = this.readerStatus_; | ||
| const dbChangeHandler = (filename) => { | ||
| if (filename && this.dbPath_ && fs.existsSync(this.dbPath_)) { | ||
| this.fsWatcher_?.close(); | ||
| this.loadDatabase(); | ||
| this.fsWatcher_ = getFsWatch(); | ||
| } else { | ||
| this.readerStatus_ = originalState; | ||
| } | ||
| }; | ||
| const getFsWatch = () => { | ||
| if (!this.dbPath_) { | ||
| throw new Error("Path to database not available"); | ||
| } | ||
| return fs.watch(this.dbPath_, (eventType, filename) => { | ||
| if (!filename) { | ||
| return; | ||
| } | ||
| if (this.fd_) { | ||
| this.readerStatus_ = 1 /* Initializing */; | ||
| } | ||
| if (timeout !== null) { | ||
| clearTimeout(timeout); | ||
| } | ||
| timeout = setTimeout(() => { | ||
| timeout = null; | ||
| dbChangeHandler(filename); | ||
| }, 500); | ||
| }); | ||
| }; | ||
| this.fsWatcher_ = getFsWatch(); | ||
| } | ||
| /** | ||
| * Populate data object with database query results for an IP address | ||
| * @param ipNum IP number | ||
| * @param ipVersion IP version | ||
| * @param data Output data object | ||
| */ | ||
| query(ipNum, ipVersion, data) { | ||
| let low = 0; | ||
| let high; | ||
| let maxIpRange; | ||
| let baseAddr; | ||
| let columnSize; | ||
| if (ipVersion === 6) { | ||
| maxIpRange = MAX_IPV6_RANGE; | ||
| high = this.dbStats_.DBCountIPv6; | ||
| baseAddr = this.dbStats_.BaseAddrIPv6; | ||
| columnSize = this.dbStats_.ColumnSizeIPv6; | ||
| if (this.dbStats_.IndexedIPv6) { | ||
| const indexaddr = Number(ipNum >> BigInt(112)); | ||
| low = this.indiciesIPv6_[indexaddr][0]; | ||
| high = this.indiciesIPv6_[indexaddr][1]; | ||
| } | ||
| } else { | ||
| maxIpRange = MAX_IPV4_RANGE; | ||
| high = this.dbStats_.DBCount; | ||
| baseAddr = this.dbStats_.BaseAddr; | ||
| columnSize = this.dbStats_.ColumnSize; | ||
| if (this.dbStats_.Indexed) { | ||
| const indexaddr = Number(ipNum) >>> 16; | ||
| low = this.indiciesIPv4_[indexaddr][0]; | ||
| high = this.indiciesIPv4_[indexaddr][1]; | ||
| } | ||
| } | ||
| if (ipNum >= maxIpRange) { | ||
| ipNum = maxIpRange - BigInt(1); | ||
| } | ||
| while (low <= high) { | ||
| const mid = Math.floor((low + high) / 2); | ||
| const rowoffset = baseAddr + mid * columnSize; | ||
| const rowoffset2 = rowoffset + columnSize; | ||
| const ipfrom = ipVersion === 6 ? this.readInt128Big(rowoffset) : this.readInt32Big(rowoffset); | ||
| const ipto = ipVersion === 6 ? this.readInt128Big(rowoffset2) : this.readInt32Big(rowoffset2); | ||
| if (ipfrom === void 0 || ipto === void 0) { | ||
| break; | ||
| } | ||
| if (ipfrom <= ipNum && ipto > ipNum) { | ||
| const firstcol = ipVersion === 6 ? 16 : 4; | ||
| const buff = this.readToBuffer(columnSize - firstcol, rowoffset + firstcol - 1); | ||
| if (!buff) { | ||
| break; | ||
| } | ||
| const enabledKeys = Object.keys(this.enabled_).filter( | ||
| (key) => this.enabled_[key] | ||
| ); | ||
| for (const key of enabledKeys) { | ||
| if (key === "country") { | ||
| const countrypos = this.readBufferInt32(this.offset_[key], buff); | ||
| data.country_short = this.readString(countrypos) ?? ""; | ||
| data.country_long = this.readString(countrypos + 3) ?? ""; | ||
| } else if (key === "longitude" || key === "latitude") { | ||
| const num = this.readBufferFloat(this.offset_[key], buff); | ||
| data[key] = num !== 0 ? Math.round(num * 1e6) / 1e6 : null; | ||
| } else { | ||
| data[key] = this.readString(this.readBufferInt32(this.offset_[key], buff)) ?? ""; | ||
| } | ||
| } | ||
| data.status = "OK"; | ||
| return; | ||
| } else { | ||
| if (ipfrom > ipNum) { | ||
| high = mid - 1; | ||
| } else { | ||
| low = mid + 1; | ||
| } | ||
| } | ||
| } | ||
| data.status = "IP_ADDRESS_NOT_FOUND"; | ||
| } | ||
| /** | ||
| * Query IP2Location database with an IP and get location information | ||
| * @param ip IP address | ||
| */ | ||
| get(ip) { | ||
| const data = { | ||
| ip, | ||
| ip_no: "", | ||
| status: "" | ||
| }; | ||
| if (this.readerStatus_ === 0 /* NotInitialized */) { | ||
| data.status = "NOT_INITIALIZED"; | ||
| } else if (this.readerStatus_ === 1 /* Initializing */) { | ||
| data.status = "INITIALIZING"; | ||
| } else if (!this.dbPath_ || !this.dbCache_ && !fs.existsSync(this.dbPath_)) { | ||
| data.status = "DATABASE_NOT_FOUND"; | ||
| } else if (!this.dbStats_.DBType) { | ||
| data.status = "NOT_INITIALIZED"; | ||
| } | ||
| if (data.status) { | ||
| return data; | ||
| } | ||
| let ipVersion; | ||
| let ipNum; | ||
| ({ ip, ipVersion, ipNum } = parseIp(ip)); | ||
| if (!ipVersion) { | ||
| data.status = "INVALID_IP_ADDRESS"; | ||
| } else if (ipVersion === 6 && this.dbStats_.OldBIN) { | ||
| data.status = "IPV6_NOT_SUPPORTED"; | ||
| } | ||
| if (data.status) { | ||
| return data; | ||
| } | ||
| data.ip_no = ipNum.toString(); | ||
| this.query(ipNum, ipVersion, data); | ||
| return data; | ||
| } | ||
| }; | ||
| // src/csv-reader.ts | ||
| import fs2 from "fs"; | ||
| import csvParser from "csv-parser"; | ||
| var CsvReader = class { | ||
| readerStatus_; | ||
| fsWatcher_; | ||
| reloadPromise_; | ||
| requiredCsvHeaders_; | ||
| constructor() { | ||
| this.readerStatus_ = 0 /* NotInitialized */; | ||
| this.fsWatcher_ = null; | ||
| this.reloadPromise_ = Promise.resolve(); | ||
| this.requiredCsvHeaders_ = []; | ||
| } | ||
| /** | ||
| * Load IP2Location CSV databas | ||
| * @param csvPath Filesystem path to IP2Location CSV database | ||
| */ | ||
| async loadCsv(csvPath) { | ||
| const parser = fs2.createReadStream(csvPath).pipe(csvParser()); | ||
| let firstRecord = true; | ||
| for await (const record of parser) { | ||
| const inputData = record; | ||
| if (firstRecord && Object.keys(inputData).filter((key) => this.requiredCsvHeaders_.includes(key)).length !== this.requiredCsvHeaders_.length) { | ||
| throw new Error("CSV database does not have expected headings"); | ||
| } | ||
| firstRecord = false; | ||
| this.processRecord(inputData); | ||
| } | ||
| } | ||
| /** | ||
| * Get reader status | ||
| */ | ||
| get readerStatus() { | ||
| return this.readerStatus_; | ||
| } | ||
| /** | ||
| * Get DB reload promise | ||
| */ | ||
| get reloadPromise() { | ||
| return this.reloadPromise_; | ||
| } | ||
| /** | ||
| * Close database and uninitialize reader | ||
| */ | ||
| close() { | ||
| this.readerStatus_ = 0 /* NotInitialized */; | ||
| if (this.fsWatcher_ !== null) { | ||
| this.fsWatcher_.close(); | ||
| } | ||
| this.fsWatcher_ = null; | ||
| } | ||
| /** | ||
| * Initialize reader | ||
| * @param dbPath IP2Location CSV database | ||
| * @param reloadOnDbUpdate Options for database reader | ||
| */ | ||
| async init(dbPath, reloadOnDbUpdate) { | ||
| if (!dbPath) { | ||
| throw new Error("Must specify path to CSV database"); | ||
| } | ||
| this.readerStatus_ = 1 /* Initializing */; | ||
| await this.loadCsv(dbPath); | ||
| if (reloadOnDbUpdate) { | ||
| this.watchDbFile(dbPath); | ||
| } | ||
| this.readerStatus_ = 2 /* Ready */; | ||
| } | ||
| /** | ||
| * Watch database file for changes and re-init if a change is detected | ||
| * @param dbPath Path to watch | ||
| */ | ||
| watchDbFile(dbPath) { | ||
| let timeout = null; | ||
| const dbChangeHandler = (filename) => { | ||
| if (filename && fs2.existsSync(dbPath)) { | ||
| if (this.fsWatcher_ !== null) { | ||
| this.fsWatcher_.close(); | ||
| this.fsWatcher_ = null; | ||
| } | ||
| this.reloadPromise_ = this.init(dbPath, true).catch(() => void 0); | ||
| } | ||
| }; | ||
| this.fsWatcher_ = fs2.watch(dbPath, (eventType, filename) => { | ||
| if (!filename) { | ||
| return; | ||
| } | ||
| if (timeout !== null) { | ||
| clearTimeout(timeout); | ||
| } | ||
| timeout = setTimeout(() => { | ||
| timeout = null; | ||
| dbChangeHandler(filename); | ||
| }, 500); | ||
| }); | ||
| } | ||
| }; | ||
| // src/subdiv-reader.ts | ||
| var SubdivReader = class extends CsvReader { | ||
| subdivisionMap_; | ||
| constructor() { | ||
| super(); | ||
| this.subdivisionMap_ = {}; | ||
| this.requiredCsvHeaders_ = ["country_code", "subdivision_name", "code"]; | ||
| } | ||
| /** | ||
| * Process line from IP2Location subdivision database | ||
| * @param record Individual row from CSV database, broken into key/value pairs based on CSV headers | ||
| */ | ||
| processRecord(record) { | ||
| const { country_code, subdivision_name, code } = record; | ||
| const subdivisionCode = code?.length > 3 ? code.substring(3) : void 0; | ||
| if (!subdivisionCode) { | ||
| return; | ||
| } | ||
| const countryMap = this.subdivisionMap_[country_code] ?? {}; | ||
| countryMap[subdivision_name] = subdivisionCode; | ||
| this.subdivisionMap_[country_code] = countryMap; | ||
| } | ||
| /** | ||
| * Close database and uninitialize reader | ||
| */ | ||
| close() { | ||
| super.close(); | ||
| this.subdivisionMap_ = {}; | ||
| } | ||
| /** | ||
| * Get the ISO 3166-2 subdivision part from a country code and region | ||
| * @param country ISO 3166-1 country code from IP2Location database | ||
| * @param region Region from from IP2Location database | ||
| */ | ||
| get(country, region) { | ||
| if (this.readerStatus !== 2 /* Ready */) { | ||
| return null; | ||
| } | ||
| if (!country || !region) { | ||
| return null; | ||
| } | ||
| return this.subdivisionMap_[country]?.[region] ?? null; | ||
| } | ||
| }; | ||
| // src/geonameid-reader.ts | ||
| var GeoNameIdReader = class extends CsvReader { | ||
| geoNameIdMap_; | ||
| constructor() { | ||
| super(); | ||
| this.geoNameIdMap_ = {}; | ||
| this.requiredCsvHeaders_ = ["country_code", "region_name", "city_name", "geonameid"]; | ||
| } | ||
| /** | ||
| * Process line from IP2Location GeoName ID database | ||
| * @param record Individual row from CSV database, broken into key/value pairs based on CSV headers | ||
| */ | ||
| processRecord(record) { | ||
| const { country_code, region_name, city_name, geonameid } = record; | ||
| if (!/^\d+$/.test(geonameid)) { | ||
| return; | ||
| } | ||
| const countryMap = this.geoNameIdMap_[country_code] ?? {}; | ||
| const regionMap = countryMap[region_name] ?? {}; | ||
| regionMap[city_name] = parseInt(geonameid); | ||
| countryMap[region_name] = regionMap; | ||
| this.geoNameIdMap_[country_code] = countryMap; | ||
| } | ||
| /** | ||
| * Close database and uninitialize reader | ||
| */ | ||
| close() { | ||
| super.close(); | ||
| this.geoNameIdMap_ = {}; | ||
| } | ||
| /** | ||
| * Get the GeoName ID from a country code, region, and city | ||
| * @param country ISO 3166-1 country code from IP2Location database | ||
| * @param region Region from from IP2Location database | ||
| * @param city City from IP2Location database | ||
| */ | ||
| get(country, region, city) { | ||
| if (this.readerStatus !== 2 /* Ready */) { | ||
| return null; | ||
| } | ||
| if (!country || !region || !city) { | ||
| return null; | ||
| } | ||
| return this.geoNameIdMap_[country]?.[region]?.[city] ?? null; | ||
| } | ||
| }; | ||
| // src/country-info-reader.ts | ||
| var CountryInfoReader = class extends CsvReader { | ||
| countryInfoMap_; | ||
| constructor() { | ||
| super(); | ||
| this.countryInfoMap_ = {}; | ||
| this.requiredCsvHeaders_ = ["country_code", "capital", "total_area"]; | ||
| } | ||
| /** | ||
| * Process line from IP2Location country info database | ||
| * @param record Individual row from CSV database, broken into key/value pairs based on CSV headers | ||
| */ | ||
| processRecord(record) { | ||
| const normalizeStr = (val) => { | ||
| return val.replace(/^-$/, ""); | ||
| }; | ||
| const normalizeNum = (val) => { | ||
| const numStr = normalizeStr(val).trim(); | ||
| const num = numStr ? Number(numStr) : null; | ||
| return num != null && !Number.isNaN(num) ? num : null; | ||
| }; | ||
| const countryInfoData = { | ||
| country_code: "", | ||
| capital: "", | ||
| total_area: null | ||
| }; | ||
| for (const key in record) { | ||
| countryInfoData[key] = [ | ||
| "country_numeric_code", | ||
| "total_area", | ||
| "population", | ||
| "idd_code" | ||
| ].includes(key) ? normalizeNum(record[key]) : normalizeStr(record[key]); | ||
| } | ||
| if (countryInfoData.country_code) { | ||
| this.countryInfoMap_[countryInfoData.country_code] = countryInfoData; | ||
| } | ||
| } | ||
| /** | ||
| * Close database and uninitialize reader | ||
| */ | ||
| close() { | ||
| super.close(); | ||
| this.countryInfoMap_ = {}; | ||
| } | ||
| /** | ||
| * Get country info from a country code | ||
| * @param country ISO 3166-1 country code from IP2Location database | ||
| */ | ||
| get(country) { | ||
| if (this.readerStatus !== 2 /* Ready */) { | ||
| return null; | ||
| } | ||
| if (!country) { | ||
| return null; | ||
| } | ||
| return this.countryInfoMap_[country] ?? null; | ||
| } | ||
| }; | ||
| // src/iata-icao-reader.ts | ||
| var IataIcaoReader = class extends CsvReader { | ||
| iataIcaoMap_; | ||
| constructor() { | ||
| super(); | ||
| this.iataIcaoMap_ = {}; | ||
| this.requiredCsvHeaders_ = [ | ||
| "country_code", | ||
| "region_name", | ||
| "iata", | ||
| "icao", | ||
| "latitude", | ||
| "longitude" | ||
| ]; | ||
| } | ||
| /** | ||
| * Process line from IP2Location IATA/ICAO airport database | ||
| * @param record Individual row from CSV database, broken into key/value pairs based on CSV headers | ||
| */ | ||
| processRecord(record) { | ||
| const normalizeStr = (val) => { | ||
| return val.replace(/^-$/, ""); | ||
| }; | ||
| const normalizeNum = (val) => { | ||
| const numStr = normalizeStr(val).trim(); | ||
| const num = numStr ? Number(numStr) : null; | ||
| return num != null && !Number.isNaN(num) ? num : null; | ||
| }; | ||
| const airportOutputData = { | ||
| iata: "", | ||
| icao: "", | ||
| airport: "", | ||
| latitude: null, | ||
| longitude: null | ||
| }; | ||
| const { country_code, region_name } = record; | ||
| for (const key in record) { | ||
| if (["country_code", "region_name"].includes(key)) { | ||
| continue; | ||
| } | ||
| airportOutputData[key] = ["latitude", "longitude"].includes(key) ? normalizeNum(record[key]) : normalizeStr(record[key]); | ||
| } | ||
| const countryMap = this.iataIcaoMap_[country_code] ?? {}; | ||
| const regionArray = countryMap[region_name] ?? []; | ||
| regionArray.push(airportOutputData); | ||
| countryMap[region_name] = regionArray; | ||
| this.iataIcaoMap_[country_code] = countryMap; | ||
| } | ||
| /** | ||
| * Close database and uninitialize reader | ||
| */ | ||
| close() { | ||
| super.close(); | ||
| this.iataIcaoMap_ = {}; | ||
| } | ||
| /** | ||
| * Get IATA/ICAO airport info from country code and region name | ||
| * @param country ISO 3166-1 country code from IP2Location database | ||
| * @param region Region from from IP2Location database | ||
| */ | ||
| get(country, region) { | ||
| if (this.readerStatus !== 2 /* Ready */) { | ||
| return []; | ||
| } | ||
| if (!country || !region) { | ||
| return []; | ||
| } | ||
| return this.iataIcaoMap_[country]?.[region] ?? []; | ||
| } | ||
| }; | ||
| // src/index.ts | ||
| var Ip2lReader = class { | ||
| dbReader_; | ||
| subdivReader_; | ||
| geoNameIdReader_; | ||
| countryInfoReader_; | ||
| iataIcaoReader_; | ||
| constructor() { | ||
| this.dbReader_ = new DbReader(); | ||
| } | ||
| /** | ||
| * Initialize IP2Location database reader(s) | ||
| * @param dbPath IP2Location BIN database | ||
| * @param options Options for database reader | ||
| */ | ||
| async init(dbPath, options) { | ||
| this.dbReader_.init(dbPath, options); | ||
| if (!options) { | ||
| return; | ||
| } | ||
| if (options.subdivisionCsvPath) { | ||
| this.subdivReader_ = new SubdivReader(); | ||
| await this.subdivReader_.init(options.subdivisionCsvPath, options.reloadOnDbUpdate); | ||
| } | ||
| if (options.geoNameIdCsvPath) { | ||
| this.geoNameIdReader_ = new GeoNameIdReader(); | ||
| await this.geoNameIdReader_.init(options.geoNameIdCsvPath, options.reloadOnDbUpdate); | ||
| } | ||
| if (options.countryInfoCsvPath) { | ||
| this.countryInfoReader_ = new CountryInfoReader(); | ||
| await this.countryInfoReader_.init(options.countryInfoCsvPath, options.reloadOnDbUpdate); | ||
| } | ||
| if (options.iataIcaoCsvPath) { | ||
| this.iataIcaoReader_ = new IataIcaoReader(); | ||
| await this.iataIcaoReader_.init(options.iataIcaoCsvPath, options.reloadOnDbUpdate); | ||
| } | ||
| } | ||
| /** | ||
| * Query IP2Location database with an IP and get location information | ||
| * @param ip IP address | ||
| */ | ||
| get(ip) { | ||
| const ip2lData = this.dbReader_.get(ip); | ||
| if (this.subdivReader_) { | ||
| if (typeof ip2lData.country_short === "string" && typeof ip2lData.region === "string") { | ||
| const subdivision = this.subdivReader_.get(ip2lData.country_short, ip2lData.region); | ||
| if (subdivision !== null) { | ||
| ip2lData.subdivision = subdivision; | ||
| } | ||
| } | ||
| } | ||
| if (this.geoNameIdReader_) { | ||
| if (typeof ip2lData.country_short === "string" && typeof ip2lData.region === "string" && typeof ip2lData.city === "string") { | ||
| const geoNameId = this.geoNameIdReader_.get( | ||
| ip2lData.country_short, | ||
| ip2lData.region, | ||
| ip2lData.city | ||
| ); | ||
| if (geoNameId !== null) { | ||
| ip2lData.geoname_id = geoNameId; | ||
| } | ||
| } | ||
| } | ||
| if (this.countryInfoReader_) { | ||
| if (typeof ip2lData.country_short === "string") { | ||
| const countryInfo = this.countryInfoReader_.get(ip2lData.country_short); | ||
| ip2lData.country_info = countryInfo; | ||
| } | ||
| } | ||
| if (this.iataIcaoReader_) { | ||
| if (typeof ip2lData.country_short === "string" && typeof ip2lData.region === "string") { | ||
| const airports = this.iataIcaoReader_.get(ip2lData.country_short, ip2lData.region); | ||
| ip2lData.airports = airports; | ||
| } | ||
| } | ||
| return ip2lData; | ||
| } | ||
| /** | ||
| * Close IP2Location database(s) and uninitialize reader(s) | ||
| */ | ||
| close() { | ||
| this.dbReader_.close(); | ||
| this.subdivReader_?.close(); | ||
| this.geoNameIdReader_?.close(); | ||
| this.countryInfoReader_?.close(); | ||
| this.iataIcaoReader_?.close(); | ||
| } | ||
| }; | ||
| export { | ||
| Ip2lReader, | ||
| Ip2lReader as default | ||
| }; |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Network access
Supply chain riskThis module accesses the network.
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
89188
6.21%3385
56.28%241
19.9%0
-100%1
-50%17
13.33%+ Added
+ Added
- Removed
- Removed