🚀 Socket Launch Week Day 5:Introducing Repository Access Permissions and Custom Roles.Learn more
Sign In

ip2ldb-reader

Package Overview
Dependencies
Maintainers
1
Versions
35
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

ip2ldb-reader - npm Package Compare versions

Comparing version
4.1.0
to
5.0.0
+186
lib/index.d.mts
//#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 };
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;

@@ -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 };
{
"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"
}
}

@@ -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

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 };
// 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
};