🚀 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
2.1.6
to
3.0.0
+974
lib/index.cjs
"use strict";
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
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;
};
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 src_exports = {};
__export(src_exports, {
Ip2lReader: () => Ip2lReader,
default: () => Ip2lReader
});
module.exports = __toCommonJS(src_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");
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 };
}
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]
};
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 = 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 (ex) {
}
}
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;
Object.keys(Position).forEach((key) => {
this.offset_[key] = Position[key][dbType] ? Position[key][dbType] - 2 << 2 : 0;
});
Object.keys(Position).forEach((key) => {
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 (ex) {
}
}
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 && 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;
}
Object.keys(this.enabled_).filter((key) => this.enabled_[key]).forEach((key) => {
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;
}
};
// src/csv-reader.ts
var import_node_fs2 = __toESM(require("fs"), 1);
var import_csv_parser = __toESM(require("csv-parser"), 1);
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);
});
}
};
// 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 "";
}
return (this.subdivisionMap_[country] || {})[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 0;
}
const geoNameId = ((this.geoNameIdMap_[country] || {})[region] || {})[city];
return geoNameId != void 0 ? geoNameId : 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();
}
};
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
Ip2lReader
});
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 };
+247
-3

@@ -1,3 +0,246 @@

import { Ip2lOptions, Ip2lData } from './interfaces';
export default class Ip2lReader {
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_;

@@ -25,2 +268,3 @@ private subdivReader_?;

}
export { Ip2lData, Ip2lOptions, Ip2lReader };
export { type Ip2lData, type Ip2lOptions, Ip2lReader, Ip2lReader as default };

@@ -1,97 +0,937 @@

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Ip2lReader = void 0;
const db_reader_1 = require("./db-reader");
const subdiv_reader_1 = require("./subdiv-reader");
const geonameid_reader_1 = require("./geonameid-reader");
const country_info_reader_1 = require("./country-info-reader");
const iata_icao_reader_1 = require("./iata-icao-reader");
class Ip2lReader {
constructor() {
this.dbReader_ = new db_reader_1.DbReader();
// 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);
}
/**
* 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 (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]
};
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 (ex) {
}
}
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;
Object.keys(Position).forEach((key) => {
this.offset_[key] = Position[key][dbType] ? Position[key][dbType] - 2 << 2 : 0;
});
Object.keys(Position).forEach((key) => {
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;
}
// Subdivision support
if (options.subdivisionCsvPath) {
this.subdivReader_ = new subdiv_reader_1.SubdivReader();
await this.subdivReader_.init(options.subdivisionCsvPath, options.reloadOnDbUpdate);
}
}
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 (ex) {
}
}
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 && 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;
}
// GeoName ID support
if (options.geoNameIdCsvPath) {
this.geoNameIdReader_ = new geonameid_reader_1.GeoNameIdReader();
await this.geoNameIdReader_.init(options.geoNameIdCsvPath, options.reloadOnDbUpdate);
if (this.fd_) {
this.readerStatus_ = 1 /* Initializing */;
}
// Country info support
if (options.countryInfoCsvPath) {
this.countryInfoReader_ = new country_info_reader_1.CountryInfoReader();
await this.countryInfoReader_.init(options.countryInfoCsvPath, options.reloadOnDbUpdate);
if (timeout !== null) {
clearTimeout(timeout);
}
if (options.iataIcaoCsvPath) {
this.iataIcaoReader_ = new iata_icao_reader_1.IataIcaoReader();
await this.iataIcaoReader_.init(options.iataIcaoCsvPath, options.reloadOnDbUpdate);
}
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];
}
}
/**
* Query IP2Location database with an IP and get location information
* @param ip IP address
*/
get(ip) {
const ip2lData = this.dbReader_.get(ip);
// Subdivision support is optional
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 (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;
}
// GeoName ID support is optional
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;
}
}
Object.keys(this.enabled_).filter((key) => this.enabled_[key]).forEach((key) => {
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;
}
// Country info support is optional
if (this.countryInfoReader_) {
if (typeof ip2lData.country_short === 'string') {
const countryInfo = this.countryInfoReader_.get(ip2lData.country_short);
ip2lData.country_info = countryInfo;
}
}
}
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;
}
// IATA/ICAO airport info support is optional
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;
}
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 "";
}
return (this.subdivisionMap_[country] || {})[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 0;
}
const geoNameId = ((this.geoNameIdMap_[country] || {})[region] || {})[city];
return geoNameId != void 0 ? geoNameId : 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;
}
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();
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;
}
}
}
}
exports.default = Ip2lReader;
exports.Ip2lReader = Ip2lReader;
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
};
+42
-18
{
"name": "ip2ldb-reader",
"version": "2.1.6",
"version": "3.0.0",
"description": "Reader for IP2Location databases",
"main": "lib/index.js",
"type": "module",
"main": "./lib/index.cjs",
"exports": {
"require": "./lib/index.cjs",
"import": "./lib/index.js"
},
"scripts": {
"build": "npm run lint && npm run clean && npm run compile",
"build-release": "npm run lint && npm run clean && npm run compile-release",
"build": "npm run lint && npm run compile && publint",
"clean": "rimraf lib",
"cli-testing": "node lib/cli-testing.js",
"compile": "tsc --build tsconfig.json",
"compile-release": "tsc --build tsconfig.release.json",
"format": "prettier --write \"src/**/*.ts\"",
"cli-test": "node lib/cli.js",
"compile": "tsup",
"compile:debug": "tsup --env.dev true",
"format": "prettier --write .",
"lint": "eslint src",
"prepare": "npm run clean && npm run compile-release",
"prepare": "npm run compile",
"test": "jest"
},
"engines": {
"node": ">= 18.19.0"
},
"files": [

@@ -41,22 +48,39 @@ "/lib"

"<rootDir>/lib/"
],
"transform": {
"\\.[jt]s$": [
"ts-jest",
{
"useESM": true,
"tsconfig": "tsconfig.dev.json"
}
]
},
"moduleNameMapper": {
"(.+)\\.js": "$1"
},
"extensionsToTreatAsEsm": [
".ts"
]
},
"dependencies": {
"csv-parser": "^3.0.0"
},
"devDependencies": {
"@types/jest": "^29.5.12",
"@types/node": "^18.19.22",
"@typescript-eslint/eslint-plugin": "^7.1.1",
"@typescript-eslint/parser": "^7.1.1",
"@types/node": "^20.12.12",
"@typescript-eslint/eslint-plugin": "^7.9.0",
"@typescript-eslint/parser": "^7.9.0",
"eslint": "^8.57.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-jsdoc": "^48.2.1",
"eslint-plugin-jsdoc": "^48.2.5",
"eslint-plugin-prettier": "^5.1.3",
"jest": "^29.7.0",
"prettier": "^3.2.5",
"rimraf": "^5.0.5",
"publint": "^0.2.8",
"rimraf": "^5.0.7",
"ts-jest": "^29.1.2",
"typescript": "^5.4.2"
},
"dependencies": {
"csv-parser": "^3.0.0"
"tsup": "^8.0.2",
"typescript": "^5.4.5"
}
}
+42
-39

@@ -8,10 +8,10 @@ # ip2ldb-reader

## Installation
## Requirements
This module has been tested with Node.js LTS 14, 16, and 18. Feel free test other versions of Node.js and [let me know](https://github.com/mdmower/ip2ldb-reader/issues) if you encounter compatibility issues.
- Minimum supported Node.js version: 18.19.0
Local installation
## Installation
```
npm install ip2ldb-reader
npm install --save ip2ldb-reader
```

@@ -21,6 +21,9 @@

```JavaScript
Both ES Module and CommonJS exports are available. Importing this module should work seamlessly in either system.
```js
// Import as ES Module
import Ip2lReader from 'ip2ldb-reader';
// Or using require() syntax:
// const Ip2lReader = require('ip2ldb-reader').Ip2lReader;
// Or require as CommonJS
// const {Ip2lReader} = require('ip2ldb-reader');

@@ -46,3 +49,3 @@ // Define database reader options

```JavaScript
```js
{

@@ -82,6 +85,6 @@ // {boolean} Cache database in memory

The object returned by `Ip2lReader.get(ip)` has the following structure:
The object returned by `.get(ip)` has the following structure:
```TypeScript
{
```ts
interface Ip2lData {
ip: string | null;

@@ -92,3 +95,3 @@ ip_no: string | null;

addresstype?: string;
airports?: IataIcaoData[] | null;
airports?: IataIcaoData[];
areacode?: string;

@@ -126,20 +129,20 @@ as?: string;

```TypeScript
CountryInfoData = {
capital: string;
cctld?: string;
country_alpha3_code?: string;
country_code: string;
country_demonym?: string;
country_name?: string;
country_numeric_code?: number | null;
currency_code?: string;
currency_name?: string;
currency_symbol?: string;
idd_code?: number | null;
lang_code?: string;
lang_name?: string;
population?: number | null;
total_area: number | null;
},
```ts
interface CountryInfoData {
capital: string;
cctld?: string;
country_alpha3_code?: string;
country_code: string;
country_demonym?: string;
country_name?: string;
country_numeric_code?: number | null;
currency_code?: string;
currency_name?: string;
currency_symbol?: string;
idd_code?: number | null;
lang_code?: string;
lang_name?: string;
population?: number | null;
total_area: number | null;
}
```

@@ -149,10 +152,10 @@

```TypeScript
IataIcaoData = {
airport: string;
iata: string;
icao: string;
latitude: number | null;
longitude: number | null;
},
```ts
interface IataIcaoData {
airport: string;
iata: string;
icao: string;
latitude: number | null;
longitude: number | null;
}
```

@@ -189,3 +192,3 @@

- Prior to version 2.0, unknown number values were reported as zero (`0`). Beginning with version 2.0, unknown numbers are reported as null (`null`);
- See [releases](https://github.com/mdmower/ip2ldb-reader/releases) for information on feature updates, breaking changes, and bugfixes.

@@ -192,0 +195,0 @@ ## Development

import { CsvReader, ReaderStatus } from './csv-reader';
import { CountryInfoData } from './interfaces';
declare class CountryInfoReader extends CsvReader {
private countryInfoMap_;
constructor();
/**
* Process line from IP2Location country info database
* @param record Individual row from CSV database, broken into key/value pairs based on CSV headers
*/
protected processRecord(record: {
[key: string]: string;
}): void;
/**
* Close database and uninitialize reader
*/
close(): void;
/**
* Get country info from a country code
* @param country ISO 3166-1 country code from IP2Location database
*/
get(country: string): CountryInfoData | null;
}
export { CountryInfoReader, ReaderStatus };
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ReaderStatus = exports.CountryInfoReader = void 0;
const csv_reader_1 = require("./csv-reader");
Object.defineProperty(exports, "ReaderStatus", { enumerable: true, get: function () { return csv_reader_1.ReaderStatus; } });
class CountryInfoReader extends csv_reader_1.CsvReader {
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 !== csv_reader_1.ReaderStatus.Ready) {
return null;
}
if (!country) {
return null;
}
return this.countryInfoMap_[country] || null;
}
}
exports.CountryInfoReader = CountryInfoReader;
declare enum ReaderStatus {
NotInitialized = 0,
Initializing = 1,
Ready = 2
}
declare abstract class CsvReader {
private readerStatus_;
private fsWatcher_;
private reloadPromise_;
protected requiredCsvHeaders_: string[];
constructor();
/**
* Load IP2Location CSV databas
* @param csvPath Filesystem path to IP2Location CSV database
*/
private loadCsv;
/**
* Process line from CSV database
* @param record Individual row from CSV database, broken into key/value pairs based on CSV headers
*/
protected abstract processRecord(record: {
[key: string]: string;
}): void;
/**
* Get reader status
*/
get readerStatus(): ReaderStatus;
/**
* Get DB reload promise
*/
get reloadPromise(): Promise<void>;
/**
* Close database and uninitialize reader
*/
close(): void;
/**
* Initialize reader
* @param dbPath IP2Location CSV database
* @param reloadOnDbUpdate Options for database reader
*/
init(dbPath: string, reloadOnDbUpdate?: boolean): Promise<void>;
/**
* Watch database file for changes and re-init if a change is detected
* @param dbPath Path to watch
*/
private watchDbFile;
}
export { CsvReader, ReaderStatus };
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.ReaderStatus = exports.CsvReader = void 0;
const fs_1 = __importDefault(require("fs"));
const csv_parser_1 = __importDefault(require("csv-parser"));
var ReaderStatus;
(function (ReaderStatus) {
ReaderStatus[ReaderStatus["NotInitialized"] = 0] = "NotInitialized";
ReaderStatus[ReaderStatus["Initializing"] = 1] = "Initializing";
ReaderStatus[ReaderStatus["Ready"] = 2] = "Ready";
})(ReaderStatus || (exports.ReaderStatus = ReaderStatus = {}));
class CsvReader {
constructor() {
this.readerStatus_ = ReaderStatus.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 = fs_1.default.createReadStream(csvPath).pipe((0, csv_parser_1.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_ = ReaderStatus.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_ = ReaderStatus.Initializing;
await this.loadCsv(dbPath);
if (reloadOnDbUpdate) {
this.watchDbFile(dbPath);
}
this.readerStatus_ = ReaderStatus.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 && fs_1.default.existsSync(dbPath)) {
if (this.fsWatcher_ !== null) {
this.fsWatcher_.close();
this.fsWatcher_ = null;
}
this.reloadPromise_ = this.init(dbPath, true).catch(() => undefined);
}
};
this.fsWatcher_ = fs_1.default.watch(dbPath, (eventType, filename) => {
if (!filename) {
return;
}
// Use a 500ms debounce on database changes before init re-runs.
// Since subdivisions are cached in memory, there's no need to
// change the reader status; they'll just update in-place.
if (timeout !== null) {
clearTimeout(timeout);
}
timeout = setTimeout(() => {
timeout = null;
dbChangeHandler(filename);
}, 500);
});
}
}
exports.CsvReader = CsvReader;
import { Ip2lData, Ip2lOptions } from './interfaces';
declare enum ReaderStatus {
NotInitialized = 0,
Initializing = 1,
Ready = 2
}
declare class DbReader {
private readerStatus_;
private dbPath_;
private cacheInMemory_;
private fd_;
private dbCache_;
private fsWatcher_;
private indiciesIPv4_;
private indiciesIPv6_;
private offset_;
private enabled_;
private dbStats_;
constructor();
/**
* Read data from database into a buffer
* @param readbytes Number of bytes to read
* @param pos Offset from beginning of database
*/
private readToBuffer;
/**
* Read 8-bit integer from the database
* @param pos Offset from beginning of database
*/
private readInt8;
/**
* Read 32-bit integer from the database
* @param pos Offset from beginning of database
*/
private readInt32;
/**
* Read 32-bit integer from the database as a BigInt
* @param pos Offset from beginning of database
*/
private readInt32Big;
/**
* Read 32-bit float from the database
* @param pos Offset from beginning of database
*/
private readFloat;
/**
* Read 128-bit integer from the database as a BigInt
* @param pos Offset from beginning of database
*/
private readInt128Big;
/**
* Read string from the database
* @param pos Offset from beginning of database
*/
private readString;
/**
* Read 32-bit integer from a Buffer
* @param pos Offset from beginning of buffer
* @param buff Buffer
*/
private readBufferInt32;
/**
* Read 32-bit float from a buffer
* @param pos Offset from beginning of buffer
* @param buff Buffer
*/
private readBufferFloat;
/**
* (Re)load database
*/
private loadDatabase;
/**
* Get reader status
*/
get readerStatus(): ReaderStatus;
/**
* Close database and uninitialize reader
*/
close(): void;
/**
* Initialize IP2Location database reader
* @param dbPath IP2Location BIN database
* @param options Options for database reader
*/
init(dbPath: string, options?: Ip2lOptions): void;
/**
* Watch database file for changes and re-init if a change is detected
*/
private watchDbFile;
/**
* Populate data object with database query results for an IP address
* @param ipNum IP number
* @param ipVersion IP version
* @param data Output data object
*/
private query;
/**
* Query IP2Location database with an IP and get location information
* @param ip IP address
*/
get(ip: string): Ip2lData;
}
export { DbReader, ReaderStatus };
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.ReaderStatus = exports.DbReader = void 0;
const ip_utils_1 = require("./ip-utils");
const fs_1 = __importDefault(require("fs"));
// prettier-ignore
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],
};
const MAX_SIZE = 65536;
const MAX_IPV4_RANGE = BigInt('4294967295');
const MAX_IPV6_RANGE = BigInt('340282366920938463463374607431768211455');
var ReaderStatus;
(function (ReaderStatus) {
ReaderStatus[ReaderStatus["NotInitialized"] = 0] = "NotInitialized";
ReaderStatus[ReaderStatus["Initializing"] = 1] = "Initializing";
ReaderStatus[ReaderStatus["Ready"] = 2] = "Ready";
})(ReaderStatus || (exports.ReaderStatus = ReaderStatus = {}));
class DbReader {
constructor() {
this.readerStatus_ = ReaderStatus.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_) {
// TODO: A readonly buffer would be nice
// https://github.com/nodejs/node/issues/27080
const buff = this.dbCache_.subarray(pos, pos + readbytes);
return buff.length === readbytes ? buff : undefined;
}
if (!this.fd_) {
throw new Error('Missing file descriptor, cannot read data');
}
const buff = Buffer.alloc(readbytes);
const totalread = fs_1.default.readSync(this.fd_, buff, 0, readbytes, pos);
return totalread === readbytes ? buff : undefined;
}
/**
* 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) : undefined;
}
/**
* 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) : undefined;
}
/**
* 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)) : undefined;
}
/**
* 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) : undefined;
}
/**
* 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') : undefined;
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_ = ReaderStatus.Initializing;
if (this.fd_ !== null) {
try {
fs_1.default.closeSync(this.fd_);
}
catch (ex) { }
}
if (this.cacheInMemory_) {
this.fd_ = null;
this.dbCache_ = fs_1.default.readFileSync(this.dbPath_);
}
else {
this.fd_ = fs_1.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;
// Check if this is a valid BIN. ProductCode should be 1 for BIN files from Jan 2021 onwards.
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.');
}
// Check whether this is a zip file (PK would be the first 2 chars).
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;
// 4 bytes per column
this.dbStats_.ColumnSize = this.dbStats_.DBColumn << 2;
// 4 bytes per column, except IPFrom column which is 16 bytes
this.dbStats_.ColumnSizeIPv6 = 16 + ((this.dbStats_.DBColumn - 1) << 2);
const dbType = this.dbStats_.DBType;
Object.keys(Position).forEach((key) => {
this.offset_[key] = Position[key][dbType] ? (Position[key][dbType] - 2) << 2 : 0;
});
Object.keys(Position).forEach((key) => {
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_ = ReaderStatus.Ready;
}
/**
* Get reader status
*/
get readerStatus() {
return this.readerStatus_;
}
/**
* Close database and uninitialize reader
*/
close() {
this.readerStatus_ = ReaderStatus.NotInitialized;
if (this.fd_ !== null) {
try {
fs_1.default.closeSync(this.fd_);
}
catch (ex) { }
}
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 && 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_1.default.existsSync(this.dbPath_)) {
this.fsWatcher_?.close();
this.loadDatabase();
this.fsWatcher_ = getFsWatch();
}
else {
// TODO: This isn't terrific, since the reader status will be
// marked as 'Ready' if a database suddenly disappears. It
// tends to work ok, though, since .get() will report
// DATABASE_NOT_FOUND right away without making any attempt
// to read from the missing database, and once it reappears,
// the FS watcher will re-init.
this.readerStatus_ = originalState;
}
};
const getFsWatch = () => {
if (!this.dbPath_) {
throw new Error('Path to database not available');
}
return fs_1.default.watch(this.dbPath_, (eventType, filename) => {
if (!filename) {
return;
}
// Use a 500ms debounce on database changes before database reloads.
// If we are reading from a database on disk (the default case), we
// need to flag the reader as initializing right away to avoid
// reading from the old file descriptor.
if (this.fd_) {
this.readerStatus_ = ReaderStatus.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 === undefined || ipto === undefined) {
break;
}
if (ipfrom <= ipNum && ipto > ipNum) {
const firstcol = ipVersion === 6 ? 16 : 4;
const buff = this.readToBuffer(columnSize - firstcol, rowoffset + firstcol - 1);
if (!buff) {
break;
}
Object.keys(this.enabled_)
.filter((key) => this.enabled_[key])
.forEach((key) => {
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 * 1000000) / 1000000 : 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_ === ReaderStatus.NotInitialized) {
data.status = 'NOT_INITIALIZED';
}
else if (this.readerStatus_ === ReaderStatus.Initializing) {
data.status = 'INITIALIZING';
}
else if (!this.dbPath_ || (!this.dbCache_ && !fs_1.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 } = (0, ip_utils_1.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;
}
}
exports.DbReader = DbReader;
import { CsvReader, ReaderStatus } from './csv-reader';
declare class GeoNameIdReader extends CsvReader {
private geoNameIdMap_;
constructor();
/**
* Process line from IP2Location GeoName ID database
* @param record Individual row from CSV database, broken into key/value pairs based on CSV headers
*/
protected processRecord(record: {
[key: string]: string;
}): void;
/**
* Close database and uninitialize reader
*/
close(): void;
/**
* 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: string, region: string, city: string): number | null;
}
export { GeoNameIdReader, ReaderStatus };
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ReaderStatus = exports.GeoNameIdReader = void 0;
const csv_reader_1 = require("./csv-reader");
Object.defineProperty(exports, "ReaderStatus", { enumerable: true, get: function () { return csv_reader_1.ReaderStatus; } });
class GeoNameIdReader extends csv_reader_1.CsvReader {
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 !== csv_reader_1.ReaderStatus.Ready) {
return null;
}
if (!country || !region || !city) {
return 0;
}
const geoNameId = ((this.geoNameIdMap_[country] || {})[region] || {})[city];
return geoNameId != undefined ? geoNameId : null;
}
}
exports.GeoNameIdReader = GeoNameIdReader;
import { CsvReader, ReaderStatus } from './csv-reader';
import { IataIcaoData } from './interfaces';
declare class IataIcaoReader extends CsvReader {
private iataIcaoMap_;
constructor();
/**
* Process line from IP2Location IATA/ICAO airport database
* @param record Individual row from CSV database, broken into key/value pairs based on CSV headers
*/
protected processRecord(record: {
[key: string]: string;
}): void;
/**
* Close database and uninitialize reader
*/
close(): void;
/**
* 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: string, region: string): IataIcaoData[];
}
export { IataIcaoReader, ReaderStatus };
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ReaderStatus = exports.IataIcaoReader = void 0;
const csv_reader_1 = require("./csv-reader");
Object.defineProperty(exports, "ReaderStatus", { enumerable: true, get: function () { return csv_reader_1.ReaderStatus; } });
class IataIcaoReader extends csv_reader_1.CsvReader {
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 !== csv_reader_1.ReaderStatus.Ready) {
return [];
}
if (!country || !region) {
return [];
}
return (this.iataIcaoMap_[country] || {})[region] || [];
}
}
exports.IataIcaoReader = IataIcaoReader;
export 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;
}
export 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;
}
export 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;
}
export 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[];
}
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
/**
* 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
*/
export declare function parseIp(ip: string): {
ip: string;
ipVersion: number;
ipNum: bigint;
};
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.parseIp = void 0;
const net_1 = __importDefault(require("net"));
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_1.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 = net_1.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 = net_1.default.isIP(ip);
}
}
if (ipVersion) {
({ ipNum, ipVersion } = getIpNum(ip, ipVersion));
}
return { ip, ipVersion, ipNum };
}
exports.parseIp = parseIp;
/**
* Get numeric IP and verify version
* @param ip IP address
* @param ipVersion IP version
*/
function getIpNum(ip, ipVersion) {
let ipNum = BigInt(0);
// IPv4
if (ipVersion === 4) {
const d = ip.split('.');
ipNum = BigInt(((+d[0] * 256 + +d[1]) * 256 + +d[2]) * 256 + +d[3]);
}
// IPv6
else if (ipVersion === 6) {
const maxsections = 8; // should have 8 sections
const sectionbits = 16; // 16 bits per section
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;
// Check whther this is a 6to4 or Teredo address, and if
// so, extract the IPv4 number.
if (ipNum >= FROM_6TO4 && ipNum <= TO_6TO4) {
// Sample: 2002:808:808:: --> 8.8.8.8
ipNum = (ipNum >> BigInt(80)) & LAST_32BITS;
ipVersion = 4;
}
else if (ipNum >= FROM_TEREDO && ipNum <= TO_TEREDO) {
// Sample: 2001:0000:4136:E378:8000:63BF:F7F7:F7F7 --> 8.8.8.8
ipNum = ~ipNum & LAST_32BITS;
ipVersion = 4;
}
}
return { ipNum, ipVersion };
}
import { ReaderStatus, CsvReader } from './csv-reader';
interface SampleResult {
[key: string]: string | number;
}
declare class SampleCsvReader extends CsvReader {
private sampleCsvMap_;
constructor();
/**
* Process line from sample CSV database
* @param record Individual row from CSV database, broken into key/value pairs based on CSV headers
*/
protected processRecord(record: {
[key: string]: string;
}): void;
/**
* Close database and uninitialize reader
*/
close(): void;
/**
* Get the sample database content
* @param firstColumn First column name
*/
get(firstColumn: string): SampleResult | null;
}
export { SampleCsvReader, ReaderStatus };
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ReaderStatus = exports.SampleCsvReader = void 0;
const csv_reader_1 = require("./csv-reader");
Object.defineProperty(exports, "ReaderStatus", { enumerable: true, get: function () { return csv_reader_1.ReaderStatus; } });
class SampleCsvReader extends csv_reader_1.CsvReader {
constructor() {
super();
this.sampleCsvMap_ = {};
this.requiredCsvHeaders_ = ['column_1', 'column_2', 'column_3'];
}
/**
* Process line from sample CSV database
* @param record Individual row from CSV database, broken into key/value pairs based on CSV headers
*/
processRecord(record) {
const { column_1, column_2, column_3 } = record;
this.sampleCsvMap_[column_1] = {
column_2,
column_3: Number(column_3),
};
}
/**
* Close database and uninitialize reader
*/
close() {
super.close();
this.sampleCsvMap_ = {};
}
/**
* Get the sample database content
* @param firstColumn First column name
*/
get(firstColumn) {
if (this.readerStatus !== csv_reader_1.ReaderStatus.Ready) {
return null;
}
return this.sampleCsvMap_[firstColumn] || null;
}
}
exports.SampleCsvReader = SampleCsvReader;
import { ReaderStatus, CsvReader } from './csv-reader';
declare class SubdivReader extends CsvReader {
private subdivisionMap_;
constructor();
/**
* Process line from IP2Location subdivision database
* @param record Individual row from CSV database, broken into key/value pairs based on CSV headers
*/
protected processRecord(record: {
[key: string]: string;
}): void;
/**
* Close database and uninitialize reader
*/
close(): void;
/**
* 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: string, region: string): string | null;
}
export { SubdivReader, ReaderStatus };
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ReaderStatus = exports.SubdivReader = void 0;
const csv_reader_1 = require("./csv-reader");
Object.defineProperty(exports, "ReaderStatus", { enumerable: true, get: function () { return csv_reader_1.ReaderStatus; } });
class SubdivReader extends csv_reader_1.CsvReader {
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) : undefined;
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 !== csv_reader_1.ReaderStatus.Ready) {
return null;
}
if (!country || !region) {
return '';
}
return (this.subdivisionMap_[country] || {})[region] || '';
}
}
exports.SubdivReader = SubdivReader;