ip2ldb-reader
Advanced tools
+974
| "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 | ||
| }); |
+269
| interface Ip2lOptions { | ||
| /** | ||
| * Reload database when file changes (default: false) | ||
| */ | ||
| reloadOnDbUpdate?: boolean; | ||
| /** | ||
| * Cache database in memory (default: false) | ||
| */ | ||
| cacheDatabaseInMemory?: boolean; | ||
| /** | ||
| * Path to IP2Location subdivision CSV database (default: undefined) | ||
| */ | ||
| subdivisionCsvPath?: string; | ||
| /** | ||
| * Path to IP2Location GeoNameID CSV database (default: undefined) | ||
| */ | ||
| geoNameIdCsvPath?: string; | ||
| /** | ||
| * Path to IP2Location Country Info CSV database (default: undefined) | ||
| */ | ||
| countryInfoCsvPath?: string; | ||
| /** | ||
| * Path to IATA/ICAO airport CSV database (default: undefined) | ||
| */ | ||
| iataIcaoCsvPath?: string; | ||
| } | ||
| interface CountryInfoData { | ||
| [key: string]: string | number | null | undefined; | ||
| /** | ||
| * Two-character country code based on ISO 3166 | ||
| */ | ||
| country_code: string; | ||
| /** | ||
| * Country name based on ISO 3166 | ||
| */ | ||
| country_name?: string; | ||
| /** | ||
| * Three-character country code based on ISO 3166 | ||
| */ | ||
| country_alpha3_code?: string; | ||
| /** | ||
| * Three-character country numeric code based on ISO 3166 | ||
| */ | ||
| country_numeric_code?: number | null; | ||
| /** | ||
| * Capital of the country | ||
| */ | ||
| capital: string; | ||
| /** | ||
| * Demonym of the country | ||
| */ | ||
| country_demonym?: string; | ||
| /** | ||
| * Total area in square-km | ||
| */ | ||
| total_area: number | null; | ||
| /** | ||
| * Population of year 2014 | ||
| */ | ||
| population?: number | null; | ||
| /** | ||
| * The IDD prefix to call the city from another country | ||
| */ | ||
| idd_code?: number | null; | ||
| /** | ||
| * Currency code based on ISO 4217 | ||
| */ | ||
| currency_code?: string; | ||
| /** | ||
| * Currency name | ||
| */ | ||
| currency_name?: string; | ||
| /** | ||
| * Currency symbol | ||
| */ | ||
| currency_symbol?: string; | ||
| /** | ||
| * Language code based on ISO 639 | ||
| */ | ||
| lang_code?: string; | ||
| /** | ||
| * Language name | ||
| */ | ||
| lang_name?: string; | ||
| /** | ||
| * Country-Code Top-Level Domain | ||
| */ | ||
| cctld?: string; | ||
| } | ||
| interface IataIcaoData { | ||
| [key: string]: string | number | null | undefined; | ||
| /** | ||
| * Three-character code of IATA airport code | ||
| */ | ||
| iata: string; | ||
| /** | ||
| * Four-character code of ICAO airport code | ||
| */ | ||
| icao: string; | ||
| /** | ||
| * Airport name | ||
| */ | ||
| airport: string; | ||
| /** | ||
| * Latitude of the airport | ||
| */ | ||
| latitude: number | null; | ||
| /** | ||
| * Longitude of the airport | ||
| */ | ||
| longitude: number | null; | ||
| } | ||
| interface Ip2lData { | ||
| [key: string]: string | number | CountryInfoData | IataIcaoData[] | null | undefined; | ||
| /** | ||
| * IP address | ||
| */ | ||
| ip: string | null; | ||
| /** | ||
| * IP number | ||
| */ | ||
| ip_no: string | null; | ||
| /** | ||
| * Status of geolocation query | ||
| */ | ||
| status: string | null; | ||
| /** | ||
| * ISO 3166-1 country code | ||
| */ | ||
| country_short?: string; | ||
| /** | ||
| * Country name | ||
| */ | ||
| country_long?: string; | ||
| /** | ||
| * Country info | ||
| */ | ||
| country_info?: CountryInfoData | null; | ||
| /** | ||
| * Subdivision part of ISO 3166-2 country-subdivision code | ||
| */ | ||
| subdivision?: string; | ||
| /** | ||
| * Region or state name | ||
| */ | ||
| region?: string; | ||
| /** | ||
| * City name | ||
| */ | ||
| city?: string; | ||
| /** | ||
| * Internet Service Provider or company name | ||
| */ | ||
| isp?: string; | ||
| /** | ||
| * City latitude; defaults to capital city latitude if city is unknown | ||
| */ | ||
| latitude?: number | null; | ||
| /** | ||
| * City longitude; defaults to capital city longitude if city is unknown | ||
| */ | ||
| longitude?: number | null; | ||
| /** | ||
| * Internet domain name associated with IP address range | ||
| */ | ||
| domain?: string; | ||
| /** | ||
| * ZIP/Postal code | ||
| */ | ||
| zipcode?: string; | ||
| /** | ||
| * UTC time zone (DST is supported) | ||
| */ | ||
| timezone?: string; | ||
| /** | ||
| * Internet connection type (DIAL, DSL, COMP) | ||
| */ | ||
| netspeed?: string; | ||
| /** | ||
| * The IDD prefix to call the city from another country | ||
| */ | ||
| iddcode?: string; | ||
| /** | ||
| * A varying length number assigned to geographic areas for calls between cities | ||
| */ | ||
| areacode?: string; | ||
| /** | ||
| * The code of the nearest weather observation station | ||
| */ | ||
| weatherstationcode?: string; | ||
| /** | ||
| * The name of the nearest weather observation station | ||
| */ | ||
| weatherstationname?: string; | ||
| /** | ||
| * Mobile Country Code (MCC) | ||
| */ | ||
| mcc?: string; | ||
| /** | ||
| * Mobile Network Code (MNC) | ||
| */ | ||
| mnc?: string; | ||
| /** | ||
| * Commercial brand associated with the mobile carrier | ||
| */ | ||
| mobilebrand?: string; | ||
| /** | ||
| * Average height of city above sea level in meters (m) | ||
| */ | ||
| elevation?: string; | ||
| /** | ||
| * Usage type classification of ISP or company | ||
| */ | ||
| usagetype?: string; | ||
| /** | ||
| * Address type | ||
| */ | ||
| addresstype?: string; | ||
| /** | ||
| * IAB category | ||
| */ | ||
| category?: string; | ||
| /** | ||
| * GeoName ID | ||
| */ | ||
| geoname_id?: number | null; | ||
| /** | ||
| * IATA/ICAO airport info | ||
| */ | ||
| airports?: IataIcaoData[]; | ||
| /** | ||
| * District name | ||
| */ | ||
| district?: string; | ||
| /** | ||
| * Autonomous system number | ||
| */ | ||
| asn?: string; | ||
| /** | ||
| * Autonomous system | ||
| */ | ||
| as?: string; | ||
| } | ||
| declare class Ip2lReader { | ||
| private dbReader_; | ||
| private subdivReader_?; | ||
| private geoNameIdReader_?; | ||
| private countryInfoReader_?; | ||
| private iataIcaoReader_?; | ||
| constructor(); | ||
| /** | ||
| * Initialize IP2Location database reader(s) | ||
| * @param dbPath IP2Location BIN database | ||
| * @param options Options for database reader | ||
| */ | ||
| init(dbPath: string, options?: Ip2lOptions): Promise<void>; | ||
| /** | ||
| * Query IP2Location database with an IP and get location information | ||
| * @param ip IP address | ||
| */ | ||
| get(ip: string): Ip2lData; | ||
| /** | ||
| * Close IP2Location database(s) and uninitialize reader(s) | ||
| */ | ||
| close(): void; | ||
| } | ||
| export { type Ip2lData, type Ip2lOptions, Ip2lReader, Ip2lReader as default }; |
+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 }; |
+925
-85
@@ -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 }; |
-488
| "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; |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Network access
Supply chain riskThis module accesses the network.
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
83981
28.57%2160
34.58%201
1.52%Yes
NaN15
15.38%7
-69.57%4
100%3
200%