UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

1,470 lines (1,469 loc) 48.8 kB
import { a as __require, t as __commonJSMin } from "../../rolldown-runtime-Dr7-SnC6.js"; import { a as asOptionalRecord } from "../../record-coerce-DItp3I4t.js"; import { l as normalizeOptionalString } from "../../string-coerce-CIXf7egm.js"; import { w as resolveStateDir } from "../../paths-D2sRr1a_.js"; import { l as isPrivateOrLoopbackHost } from "../../net-DbNPs6Xm.js"; import "../../string-coerce-runtime-GQa0ehRA.js"; import { t as definePluginEntry } from "../../plugin-entry-zfBGJaNO.js"; import "../../ssrf-runtime-Bum5C6NN.js"; import "../../state-paths-2IWTvznc.js"; import { promisify } from "node:util"; import path from "node:path"; import fs from "node:fs/promises"; import net from "node:net"; import { createHash } from "node:crypto"; import { gunzip } from "node:zlib"; //#region extensions/geolocation/src/config.ts const DEFAULT_DATABASE_URL = "https://download.db-ip.com/free/dbip-city-lite-{yyyy}-{mm}.mmdb.gz"; const DEFAULT_ATTRIBUTION_TEXT = "IP Geolocation by DB-IP"; const DEFAULT_ATTRIBUTION_URL = "https://db-ip.com"; const DEFAULT_REFRESH_DAYS = 30; function positiveRefreshDays(value) { return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : void 0; } function resolveGeolocationSettings(pluginConfig) { const config = asOptionalRecord(pluginConfig); const refreshDays = positiveRefreshDays(config?.refreshDays) ?? DEFAULT_REFRESH_DAYS; return { databaseUrl: normalizeOptionalString(config?.databaseUrl) ?? DEFAULT_DATABASE_URL, attribution: { text: normalizeOptionalString(config?.attributionText) ?? DEFAULT_ATTRIBUTION_TEXT, url: normalizeOptionalString(config?.attributionUrl) ?? DEFAULT_ATTRIBUTION_URL }, refreshMs: refreshDays * 24 * 60 * 60 * 1e3 }; } /** * Monthly builds appear a few days into the month, so the newest published * release is either this month's or the previous one. Callers try in order. */ function expandDatabaseUrls(template, now) { const urls = [now, new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth() - 1, 1))].map((date) => template.replaceAll("{yyyy}", String(date.getUTCFullYear())).replaceAll("{mm}", String(date.getUTCMonth() + 1).padStart(2, "0"))); return [...new Set(urls)]; } //#endregion //#region node_modules/.pnpm/mmdb-lib@3.0.3/node_modules/mmdb-lib/lib/utils.js var require_utils$1 = /* @__PURE__ */ __commonJSMin(((exports) => { Object.defineProperty(exports, "__esModule", { value: true }); const legacyErrorMessage = `Maxmind v2 module has changed API.\n\ Upgrade instructions can be found here: \ https://github.com/runk/node-maxmind/wiki/Migration-guide\n\ If you want to use legacy library then explicitly install maxmind@1`; const assert = (condition, message) => { if (!condition) throw new Error(message); }; exports.default = { assert, legacyErrorMessage }; })); //#endregion //#region node_modules/.pnpm/mmdb-lib@3.0.3/node_modules/mmdb-lib/lib/decoder.js var require_decoder = /* @__PURE__ */ __commonJSMin(((exports) => { var __importDefault = exports && exports.__importDefault || function(mod) { return mod && mod.__esModule ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const utils_1 = __importDefault(require_utils$1()); utils_1.default.assert(typeof BigInt !== "undefined", "Apparently you are using old version of node. Please upgrade to node 10.4.x or above."); const MAX_INT_32 = 2147483647; var DataType; (function(DataType) { DataType[DataType["Extended"] = 0] = "Extended"; DataType[DataType["Pointer"] = 1] = "Pointer"; DataType[DataType["Utf8String"] = 2] = "Utf8String"; DataType[DataType["Double"] = 3] = "Double"; DataType[DataType["Bytes"] = 4] = "Bytes"; DataType[DataType["Uint16"] = 5] = "Uint16"; DataType[DataType["Uint32"] = 6] = "Uint32"; DataType[DataType["Map"] = 7] = "Map"; DataType[DataType["Int32"] = 8] = "Int32"; DataType[DataType["Uint64"] = 9] = "Uint64"; DataType[DataType["Uint128"] = 10] = "Uint128"; DataType[DataType["Array"] = 11] = "Array"; DataType[DataType["Container"] = 12] = "Container"; DataType[DataType["EndMarker"] = 13] = "EndMarker"; DataType[DataType["Boolean"] = 14] = "Boolean"; DataType[DataType["Float"] = 15] = "Float"; })(DataType || (DataType = {})); const pointerValueOffset = [ 0, 2048, 526336, 0 ]; const noCache = { get: () => void 0, set: () => void 0 }; const cursor = (value, offset) => ({ value, offset }); var Decoder = class { constructor(db, baseOffset = 0, cache = noCache) { this.telemetry = {}; utils_1.default.assert(Boolean(db), "Database buffer is required"); this.db = db; this.baseOffset = baseOffset; this.cache = cache; } decode(offset) { let tmp; const ctrlByte = this.db[offset++]; let type = ctrlByte >> 5; if (type === DataType.Pointer) { tmp = this.decodePointer(ctrlByte, offset); return cursor(this.decodeFast(tmp.value).value, tmp.offset); } if (type === DataType.Extended) { tmp = this.db[offset] + 7; if (tmp < 8) throw new Error("Invalid Extended Type at offset " + offset + " val " + tmp); type = tmp; offset++; } const size = this.sizeFromCtrlByte(ctrlByte, offset); return this.decodeByType(type, size.offset, size.value); } decodeFast(offset) { const cached = this.cache.get(offset); if (cached) return cached; const result = this.decode(offset); this.cache.set(offset, result); return result; } decodeByType(type, offset, size) { const newOffset = offset + size; switch (type) { case DataType.Utf8String: return cursor(this.decodeString(offset, size), newOffset); case DataType.Map: return this.decodeMap(size, offset); case DataType.Uint32: return cursor(this.decodeUint(offset, size), newOffset); case DataType.Double: return cursor(this.decodeDouble(offset), newOffset); case DataType.Array: return this.decodeArray(size, offset); case DataType.Boolean: return cursor(this.decodeBoolean(size), offset); case DataType.Float: return cursor(this.decodeFloat(offset), newOffset); case DataType.Bytes: return cursor(this.decodeBytes(offset, size), newOffset); case DataType.Uint16: return cursor(this.decodeUint(offset, size), newOffset); case DataType.Int32: return cursor(this.decodeInt32(offset, size), newOffset); case DataType.Uint64: return cursor(this.decodeBigUint(offset, size), newOffset); case DataType.Uint128: return cursor(this.decodeBigUint(offset, size), newOffset); } throw new Error("Unknown type " + type + " at offset " + offset); } sizeFromCtrlByte(ctrlByte, offset) { const size = ctrlByte & 31; if (size < 29) return cursor(size, offset); if (size === 29) return cursor(29 + this.db[offset], offset + 1); if (size === 30) return cursor(285 + this.db.readUInt16BE(offset), offset + 2); return cursor(65821 + this.db.readUIntBE(offset, 3), offset + 3); } decodeBytes(offset, size) { return this.db.subarray(offset, offset + size); } decodePointer(ctrlByte, offset) { const pointerSize = ctrlByte >> 3 & 3; const pointer = this.baseOffset + pointerValueOffset[pointerSize]; let packed = 0; if (pointerSize === 0) packed = (ctrlByte & 7) << 8 | this.db[offset]; else if (pointerSize === 1) packed = (ctrlByte & 7) << 16 | this.db.readUInt16BE(offset); else if (pointerSize === 2) packed = (ctrlByte & 7) << 24 | this.db.readUIntBE(offset, 3); else packed = this.db.readUInt32BE(offset); offset += pointerSize + 1; return cursor(pointer + packed, offset); } decodeArray(size, offset) { let tmp; const array = new Array(size); for (let i = 0; i < size; i++) { tmp = this.decode(offset); offset = tmp.offset; array[i] = tmp.value; } return cursor(array, offset); } decodeBoolean(size) { return size !== 0; } decodeDouble(offset) { return this.db.readDoubleBE(offset); } decodeFloat(offset) { return this.db.readFloatBE(offset); } decodeMap(size, offset) { let tmp; let key; const map = {}; for (let i = 0; i < size; i++) { tmp = this.decode(offset); key = tmp.value; tmp = this.decode(tmp.offset); offset = tmp.offset; map[key] = tmp.value; } return cursor(map, offset); } decodeInt32(offset, size) { if (size === 0) return 0; if (size < 4) return this.db.readUIntBE(offset, size); return this.db.readInt32BE(offset); } decodeUint(offset, size) { if (size === 0) return 0; if (size <= 4) return this.db.readUIntBE(offset, size); throw new Error(`Invalid size for unsigned integer: ${size}`); } decodeString(offset, size) { const newOffset = offset + size; return newOffset >= MAX_INT_32 ? this.db.subarray(offset, newOffset).toString("utf8") : this.db.toString("utf8", offset, newOffset); } decodeBigUint(offset, size) { if (size > 16) throw new Error(`Invalid size for big unsigned integer: ${size}`); let integer = 0n; for (let i = 0; i < size; i++) { integer <<= 8n; integer |= BigInt(this.db.readUInt8(offset + i)); } return integer; } }; exports.default = Decoder; })); //#endregion //#region node_modules/.pnpm/mmdb-lib@3.0.3/node_modules/mmdb-lib/lib/ip.js var require_ip$1 = /* @__PURE__ */ __commonJSMin(((exports) => { Object.defineProperty(exports, "__esModule", { value: true }); const v4Seg = "(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])"; const v4Str = `(?:${v4Seg}\\.){3}${v4Seg}`; const IPv4Reg = new RegExp(`^${v4Str}$`); const v6Seg = "(?:[0-9a-fA-F]{1,4})"; const IPv6Reg = new RegExp(`^(?:(?:${v6Seg}:){7}(?:${v6Seg}|:)|(?:${v6Seg}:){6}(?:${v4Str}|:${v6Seg}|:)|(?:${v6Seg}:){5}(?::${v4Str}|(?::${v6Seg}){1,2}|:)|(?:${v6Seg}:){4}(?:(?::${v6Seg}){0,1}:${v4Str}|(?::${v6Seg}){1,3}|:)|(?:${v6Seg}:){3}(?:(?::${v6Seg}){0,2}:${v4Str}|(?::${v6Seg}){1,4}|:)|(?:${v6Seg}:){2}(?:(?::${v6Seg}){0,3}:${v4Str}|(?::${v6Seg}){1,5}|:)|(?:${v6Seg}:){1}(?:(?::${v6Seg}){0,4}:${v4Str}|(?::${v6Seg}){1,6}|:)|(?::(?:(?::${v6Seg}){0,5}:${v4Str}|(?::${v6Seg}){1,7}|:)))(?:%[0-9a-zA-Z-.:]{1,})?\$`); const parseIPv4 = (input) => { const ip = input.split(".", 4); return [ parseInt(ip[0]), parseInt(ip[1]), parseInt(ip[2]), parseInt(ip[3]) ]; }; const hex = (v) => { const h = parseInt(v, 10).toString(16); return h.length === 2 ? h : "0" + h; }; const parseIPv6 = (input) => { const addr = [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ]; let i; let parsed; let chunk; const [left, right] = (input.indexOf(".") > -1 ? input.replace(/(\d+)\.(\d+)\.(\d+)\.(\d+)/, (match, a, b, c, d) => { return hex(a) + hex(b) + ":" + hex(c) + hex(d); }) : input).split("::", 2); if (left) { parsed = left.split(":"); for (i = 0; i < parsed.length; i++) { chunk = parseInt(parsed[i], 16); addr[i * 2] = chunk >> 8; addr[i * 2 + 1] = chunk & 255; } } if (right) { parsed = right.split(":"); const offset = 16 - parsed.length * 2; for (i = 0; i < parsed.length; i++) { chunk = parseInt(parsed[i], 16); addr[offset + i * 2] = chunk >> 8; addr[offset + (i * 2 + 1)] = chunk & 255; } } return addr; }; const parse = (ip) => { return ip.indexOf(":") === -1 ? parseIPv4(ip) : parseIPv6(ip); }; const bitAt = (rawAddress, idx) => { const bufIdx = idx >> 3; const bitIdx = 7 ^ idx & 7; return rawAddress[bufIdx] >>> bitIdx & 1; }; const validate = (ip) => IPv4Reg.test(ip) || IPv6Reg.test(ip); exports.default = { bitAt, parse, validate }; })); //#endregion //#region node_modules/.pnpm/mmdb-lib@3.0.3/node_modules/mmdb-lib/lib/metadata.js var require_metadata = /* @__PURE__ */ __commonJSMin(((exports) => { var __importDefault = exports && exports.__importDefault || function(mod) { return mod && mod.__esModule ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.isLegacyFormat = exports.parseMetadata = void 0; const decoder_1 = __importDefault(require_decoder()); const utils_1 = __importDefault(require_utils$1()); const METADATA_START_MARKER = Buffer.from("ABCDEF4D61784D696E642E636F6D", "hex"); const parseMetadata = (db) => { const offset = findStart(db); const metadata = new decoder_1.default(db, offset).decode(offset).value; if (!metadata) throw new Error((0, exports.isLegacyFormat)(db) ? utils_1.default.legacyErrorMessage : "Cannot parse binary database"); utils_1.default.assert([ 24, 28, 32 ].indexOf(metadata.record_size) > -1, "Unsupported record size"); return { binaryFormatMajorVersion: metadata.binary_format_major_version, binaryFormatMinorVersion: metadata.binary_format_minor_version, buildEpoch: /* @__PURE__ */ new Date(Number(metadata.build_epoch) * 1e3), databaseType: metadata.database_type, description: metadata.description, ipVersion: metadata.ip_version, languages: metadata.languages, nodeByteSize: metadata.record_size / 4, nodeCount: metadata.node_count, recordSize: metadata.record_size, searchTreeSize: metadata.node_count * metadata.record_size / 4, treeDepth: Math.pow(2, metadata.ip_version + 1) }; }; exports.parseMetadata = parseMetadata; const findStart = (db) => { let found = 0; let fsize = db.length - 1; const mlen = METADATA_START_MARKER.length - 1; while (found <= mlen && fsize-- > 0) found += db[fsize] === METADATA_START_MARKER[mlen - found] ? 1 : -found; return fsize + found; }; const isLegacyFormat = (db) => { const structureInfoMaxSize = 20; for (let i = 0; i < structureInfoMaxSize; i++) { const delim = db.slice(db.length - 3 - i, db.length - i); if (delim[0] === 255 && delim[1] === 255 && delim[2] === 255) return true; } return false; }; exports.isLegacyFormat = isLegacyFormat; })); //#endregion //#region node_modules/.pnpm/mmdb-lib@3.0.3/node_modules/mmdb-lib/lib/reader/walker.js var require_walker = /* @__PURE__ */ __commonJSMin(((exports) => { Object.defineProperty(exports, "__esModule", { value: true }); const readNodeRight24 = (db) => (offset) => db.readUIntBE(offset + 3, 3); const readNodeLeft24 = (db) => (offset) => db.readUIntBE(offset, 3); const readNodeLeft28 = (db) => (offset) => (db[offset + 3] & 240) << 20 | db.readUIntBE(offset, 3); const readNodeRight28 = (db) => (offset) => (db[offset + 3] & 15) << 24 | db.readUIntBE(offset + 4, 3); const readNodeLeft32 = (db) => (offset) => db.readUInt32BE(offset); const readNodeRight32 = (db) => (offset) => db.readUInt32BE(offset + 4); exports.default = (db, recordSize) => { switch (recordSize) { case 24: return { left: readNodeLeft24(db), right: readNodeRight24(db) }; case 28: return { left: readNodeLeft28(db), right: readNodeRight28(db) }; case 32: return { left: readNodeLeft32(db), right: readNodeRight32(db) }; } throw new Error("Unsupported record size"); }; })); //#endregion //#region node_modules/.pnpm/mmdb-lib@3.0.3/node_modules/mmdb-lib/lib/reader/response.js var require_response = /* @__PURE__ */ __commonJSMin(((exports) => { Object.defineProperty(exports, "__esModule", { value: true }); })); //#endregion //#region node_modules/.pnpm/mmdb-lib@3.0.3/node_modules/mmdb-lib/lib/index.js var require_lib$1 = /* @__PURE__ */ __commonJSMin(((exports) => { var __createBinding = exports && exports.__createBinding || (Object.create ? (function(o, m, k, k2) { if (k2 === void 0) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) desc = { enumerable: true, get: function() { return m[k]; } }; Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === void 0) k2 = k; o[k2] = m[k]; })); var __exportStar = exports && exports.__exportStar || function(m, exports$2) { for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports$2, p)) __createBinding(exports$2, m, p); }; var __importDefault = exports && exports.__importDefault || function(mod) { return mod && mod.__esModule ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.Reader = void 0; const decoder_1 = __importDefault(require_decoder()); const ip_1 = __importDefault(require_ip$1()); const metadata_1 = require_metadata(); const walker_1 = __importDefault(require_walker()); const DATA_SECTION_SEPARATOR_SIZE = 16; var Reader = class { constructor(db, opts = {}) { this.opts = opts; this.load(db); } load(db) { if (!Buffer.isBuffer(db)) throw new Error(`mmdb-lib expects an instance of Buffer, got: ${typeof db}`); this.db = db; this.metadata = (0, metadata_1.parseMetadata)(this.db); this.decoder = new decoder_1.default(this.db, this.metadata.searchTreeSize + DATA_SECTION_SEPARATOR_SIZE, this.opts.cache); this.walker = (0, walker_1.default)(this.db, this.metadata.recordSize); this.ipv4StartNodeNumber = this.ipv4Start(); } get(ipAddress) { const [data] = this.getWithPrefixLength(ipAddress); return data; } getWithPrefixLength(ipAddress) { const [pointer, prefixLength] = this.findAddressInTree(ipAddress); return [pointer ? this.resolveDataPointer(pointer) : null, prefixLength]; } findAddressInTree(ipAddress) { const rawAddress = ip_1.default.parse(ipAddress); const nodeCount = this.metadata.nodeCount; const bitLength = rawAddress.length * 8; let bit; let nodeNumber = 0; let offset; let depth = 0; if (rawAddress.length === 4) nodeNumber = this.ipv4StartNodeNumber; for (; depth < bitLength && nodeNumber < nodeCount; depth++) { bit = ip_1.default.bitAt(rawAddress, depth); offset = nodeNumber * this.metadata.nodeByteSize; nodeNumber = bit ? this.walker.right(offset) : this.walker.left(offset); } if (nodeNumber > nodeCount) return [nodeNumber, depth]; return [null, depth]; } resolveDataPointer(pointer) { const resolved = pointer - this.metadata.nodeCount + this.metadata.searchTreeSize; return this.decoder.decodeFast(resolved).value; } ipv4Start() { if (this.metadata.ipVersion === 4) return 0; const nodeCount = this.metadata.nodeCount; let pointer = 0; let i = 0; for (; i < 96 && pointer < nodeCount; i++) { const offset = pointer * this.metadata.nodeByteSize; pointer = this.walker.left(offset); } return pointer; } }; exports.Reader = Reader; __exportStar(require_response(), exports); })); //#endregion //#region node_modules/.pnpm/tiny-lru@13.0.0/node_modules/tiny-lru/dist/tiny-lru.cjs /** * tiny-lru * * @copyright 2026 Jason Mulligan <jason.mulligan@avoidwork.com> * @license BSD-3-Clause * @version 13.0.0 */ var require_tiny_lru = /* @__PURE__ */ __commonJSMin(((exports) => { /** * A high-performance Least Recently Used (LRU) cache implementation with optional TTL support. * Items are automatically evicted when the cache reaches its maximum size, * removing the least recently used items first. All core operations (get, set, delete) are O(1). * * @class LRU */ var LRU = class { #stats; #onEvict; /** * Creates a new LRU cache instance. * Note: Constructor does not validate parameters. Use lru() factory function for parameter validation. * * @constructor * @param {number} [max=0] - Maximum number of items to store. 0 means unlimited. * @param {number} [ttl=0] - Time to live in milliseconds. 0 means no expiration. * @param {boolean} [resetTTL=false] - Whether to reset TTL when updating existing items via set(). */ constructor(max = 0, ttl = 0, resetTTL = false) { this.first = null; this.items = Object.create(null); this.last = null; this.max = max; this.resetTTL = resetTTL; this.size = 0; this.ttl = ttl; this.#stats = { hits: 0, misses: 0, sets: 0, deletes: 0, evictions: 0 }; this.#onEvict = null; } /** * Removes all items from the cache. * * @returns {LRU} The LRU instance for method chaining. */ clear() { for (let x = this.first; x !== null;) { const next = x.next; x.prev = null; x.next = null; x = next; } this.first = null; this.items = Object.create(null); this.last = null; this.size = 0; this.#stats.hits = 0; this.#stats.misses = 0; this.#stats.sets = 0; this.#stats.deletes = 0; this.#stats.evictions = 0; return this; } /** * Removes an item from the cache by key. * * @param {string} key - The key of the item to delete. * @returns {LRU} The LRU instance for method chaining. */ delete(key) { const item = this.items[key]; if (item !== void 0) { delete this.items[key]; this.size--; this.#stats.deletes++; this.#unlink(item); item.prev = null; item.next = null; } return this; } /** * Returns an array of [key, value] pairs for the specified keys. * When no keys provided, returns all entries in LRU order. * When keys provided, order matches the input array. * * @param {string[]} [keys=this.keys()] - Array of keys to get entries for. Defaults to all keys. * @returns {Array<Array<*>>} Array of [key, value] pairs. */ entries(keys) { if (keys === void 0) keys = this.keys(); const result = Array.from({ length: keys.length }); for (let i = 0; i < keys.length; i++) { const key = keys[i]; const item = this.items[key]; result[i] = [key, item !== void 0 ? item.value : void 0]; } return result; } /** * Removes the least recently used item from the cache. * * @returns {LRU} The LRU instance for method chaining. */ evict() { if (this.size === 0) return this; const item = this.first; delete this.items[item.key]; this.#stats.evictions++; if (--this.size === 0) { this.first = null; this.last = null; } else this.#unlink(item); item.prev = null; item.next = null; if (this.#onEvict !== null) this.#onEvict({ key: item.key, value: item.value, expiry: item.expiry }); return this; } /** * Returns the expiration timestamp for a given key. * * @param {string} key - The key to check expiration for. * @returns {number|undefined} The expiration timestamp in milliseconds, or undefined if key doesn't exist. */ expiresAt(key) { const item = this.items[key]; return item !== void 0 ? item.expiry : void 0; } /** * Checks if an item has expired. * * @param {Object} item - The cache item to check. * @returns {boolean} True if the item has expired, false otherwise. * @private */ #isExpired(item) { if (this.ttl === 0 || item.expiry === 0) return false; return item.expiry <= Date.now(); } /** * Retrieves a value from the cache by key without updating LRU order. * Note: Does not perform TTL checks or remove expired items. * * @param {string} key - The key to retrieve. * @returns {*} The value associated with the key, or undefined if not found. */ peek(key) { const item = this.items[key]; return item !== void 0 ? item.value : void 0; } /** * Retrieves a value from the cache by key. Updates the item's position to most recently used. * * @param {string} key - The key to retrieve. * @returns {*} The value associated with the key, or undefined if not found or expired. */ get(key) { const item = this.items[key]; if (item !== void 0) { if (!this.#isExpired(item)) { this.moveToEnd(item); this.#stats.hits++; return item.value; } this.delete(key); this.#stats.misses++; return; } this.#stats.misses++; } /** * Checks if a key exists in the cache. * * @param {string} key - The key to check for. * @returns {boolean} True if the key exists and is not expired, false otherwise. */ has(key) { const item = this.items[key]; return item !== void 0 && !this.#isExpired(item); } /** * Unlinks an item from the doubly-linked list. * Updates first/last pointers if needed. * Does NOT clear the item's prev/next pointers or delete from items map. * * @private */ #unlink(item) { if (item.prev !== null) item.prev.next = item.next; if (item.next !== null) item.next.prev = item.prev; if (this.first === item) this.first = item.next; if (this.last === item) this.last = item.prev; } /** * Efficiently moves an item to the end of the LRU list (most recently used position). * This is an internal optimization method that avoids the overhead of the full set() operation * when only LRU position needs to be updated. * * @param {Object} item - The cache item with prev/next pointers to reposition. * @private */ moveToEnd(item) { if (this.last === item) return; this.#unlink(item); item.prev = this.last; item.next = null; this.last.next = item; this.last = item; } /** * Returns an array of all keys in the cache, ordered from least to most recently used. * * @returns {string[]} Array of keys in LRU order. */ keys() { const result = Array.from({ length: this.size }); let x = this.first; let i = 0; while (x !== null) { result[i++] = x.key; x = x.next; } return result; } /** * Sets a value in the cache and returns any evicted item. * * @param {string} key - The key to set. * @param {*} value - The value to store. * @returns {Object|null} The evicted item (if any) with shape {key, value, expiry}, or null. */ setWithEvicted(key, value) { let evicted = null; let item = this.items[key]; if (item !== void 0) { item.value = value; if (this.resetTTL) item.expiry = this.ttl > 0 ? Date.now() + this.ttl : this.ttl; this.moveToEnd(item); } else { if (this.max > 0 && this.size === this.max) { evicted = { key: this.first.key, value: this.first.value, expiry: this.first.expiry }; this.evict(); } item = this.items[key] = { expiry: this.ttl > 0 ? Date.now() + this.ttl : this.ttl, key, prev: this.last, next: null, value }; if (++this.size === 1) this.first = item; else this.last.next = item; this.last = item; } this.#stats.sets++; return evicted; } /** * Sets a value in the cache. Updates the item's position to most recently used. * * @param {string} key - The key to set. * @param {*} value - The value to store. * @returns {LRU} The LRU instance for method chaining. */ set(key, value) { let item = this.items[key]; if (item !== void 0) { item.value = value; if (this.resetTTL) item.expiry = this.ttl > 0 ? Date.now() + this.ttl : this.ttl; this.moveToEnd(item); } else { if (this.max > 0 && this.size === this.max) this.evict(); item = this.items[key] = { expiry: this.ttl > 0 ? Date.now() + this.ttl : this.ttl, key, prev: this.last, next: null, value }; if (++this.size === 1) this.first = item; else this.last.next = item; this.last = item; } this.#stats.sets++; return this; } /** * Returns an array of all values in the cache for the specified keys. * When no keys provided, returns all values in LRU order. * When keys provided, order matches the input array. * * @param {string[]} [keys] - Array of keys to get values for. Defaults to all keys. * @returns {Array<*>} Array of values corresponding to the keys. */ values(keys) { if (keys === void 0) { const result = Array.from({ length: this.size }); let i = 0; for (let x = this.first; x !== null; x = x.next) result[i++] = x.value; return result; } const result = Array.from({ length: keys.length }); for (let i = 0; i < keys.length; i++) { const item = this.items[keys[i]]; result[i] = item !== void 0 ? item.value : void 0; } return result; } /** * Iterate over cache items in LRU order (least to most recent). * Note: This method directly accesses items from the linked list without calling * get() or peek(), so it does not update LRU order or check TTL expiration during iteration. * * @param {function(*, any, LRU): void} callback - Function to call for each item. Signature: callback(value, key, cache) * @param {Object} [thisArg] - Value to use as `this` when executing callback. * @returns {LRU} The LRU instance for method chaining. */ forEach(callback, thisArg) { for (let x = this.first; x !== null; x = x.next) callback.call(thisArg, x.value, x.key, this); return this; } /** * Batch retrieve multiple items. * * @param {string[]} keys - Array of keys to retrieve. * @returns {Object} Object mapping keys to values (undefined for missing/expired keys). */ getMany(keys) { const result = Object.create(null); for (let i = 0; i < keys.length; i++) { const key = keys[i]; result[key] = this.get(key); } return result; } /** * Batch existence check - returns true if ALL keys exist. * * @param {string[]} keys - Array of keys to check. * @returns {boolean} True if all keys exist and are not expired. */ hasAll(keys) { for (let i = 0; i < keys.length; i++) if (!this.has(keys[i])) return false; return true; } /** * Batch existence check - returns true if ANY key exists. * * @param {string[]} keys - Array of keys to check. * @returns {boolean} True if any key exists and is not expired. */ hasAny(keys) { for (let i = 0; i < keys.length; i++) if (this.has(keys[i])) return true; return false; } /** * Remove expired items without affecting LRU order. * Unlike get(), this does not move items to the end. * * @returns {number} Number of expired items removed. */ cleanup() { if (this.ttl === 0 || this.size === 0) return 0; let removed = 0; for (let x = this.first; x !== null;) { const next = x.next; if (this.#isExpired(x)) { const key = x.key; if (this.items[key] !== void 0) { delete this.items[key]; this.size--; removed++; this.#unlink(x); x.prev = null; x.next = null; } } x = next; } if (removed > 0) this.#rebuildList(); return removed; } /** * Serialize cache to JSON-compatible format. * * @returns {Array<{key: any, value: *, expiry: number}>} Array of cache items. */ toJSON() { const result = []; for (let x = this.first; x !== null; x = x.next) result.push({ key: x.key, value: x.value, expiry: x.expiry }); return result; } /** * Get cache statistics. * * @returns {Object} Statistics object with hits, misses, sets, deletes, evictions counts. */ stats() { return { ...this.#stats }; } /** * Register callback for evicted items. * * @param {function(Object): void} callback - Function called when item is evicted. Receives {key, value, expiry}. * @returns {LRU} The LRU instance for method chaining. */ onEvict(callback) { if (typeof callback !== "function") throw new TypeError("onEvict callback must be a function"); this.#onEvict = callback; return this; } /** * Get counts of items by TTL status. * * @returns {Object} Object with valid, expired, and noTTL counts. */ sizeByTTL() { if (this.ttl === 0) return { valid: this.size, expired: 0, noTTL: this.size }; const now = Date.now(); let valid = 0; let expired = 0; let noTTL = 0; for (let x = this.first; x !== null; x = x.next) if (x.expiry === 0) { noTTL++; valid++; } else if (x.expiry > now) valid++; else expired++; return { valid, expired, noTTL }; } /** * Get keys filtered by TTL status. * * @returns {Object} Object with valid, expired, and noTTL arrays of keys. */ keysByTTL() { if (this.ttl === 0) return { valid: this.keys(), expired: [], noTTL: this.keys() }; const now = Date.now(); const valid = []; const expired = []; const noTTL = []; for (let x = this.first; x !== null; x = x.next) if (x.expiry === 0) { valid.push(x.key); noTTL.push(x.key); } else if (x.expiry > now) valid.push(x.key); else expired.push(x.key); return { valid, expired, noTTL }; } /** * Get values filtered by TTL status. * * @returns {Object} Object with valid, expired, and noTTL arrays of values. */ valuesByTTL() { const keysByTTL = this.keysByTTL(); return { valid: this.values(keysByTTL.valid), expired: this.values(keysByTTL.expired), noTTL: this.values(keysByTTL.noTTL) }; } /** * Rebuild the doubly-linked list after cleanup by deleting expired items. * This removes nodes that were deleted during cleanup. * * @private */ #rebuildList() { if (this.size === 0) { this.first = null; this.last = null; return; } const keys = this.keys(); this.first = null; this.last = null; for (let i = 0; i < keys.length; i++) { const item = this.items[keys[i]]; if (item !== null && item !== void 0) { if (this.first === null) { this.first = item; item.prev = null; } else { item.prev = this.last; this.last.next = item; } item.next = null; this.last = item; } } } }; /** * Factory function to create a new LRU cache instance with parameter validation. * * @function lru * @param {number} [max=1000] - Maximum number of items to store. Must be >= 0. Use 0 for unlimited size. * @param {number} [ttl=0] - Time to live in milliseconds. Must be >= 0. Use 0 for no expiration. * @param {boolean} [resetTTL=false] - Whether to reset TTL when updating existing items via set(). * @returns {LRU} A new LRU cache instance. * @throws {TypeError} When parameters are invalid (negative numbers or wrong types). */ function lru(max = 1e3, ttl = 0, resetTTL = false) { if (isNaN(max) || max < 0) throw new TypeError("Invalid max value"); if (isNaN(ttl) || ttl < 0) throw new TypeError("Invalid ttl value"); if (typeof resetTTL !== "boolean") throw new TypeError("Invalid resetTTL value"); return new LRU(max, ttl, resetTTL); } exports.LRU = LRU; exports.lru = lru; })); //#endregion //#region node_modules/.pnpm/maxmind@5.0.7/node_modules/maxmind/lib/fs.js var require_fs = /* @__PURE__ */ __commonJSMin(((exports) => { var __importDefault = exports && exports.__importDefault || function(mod) { return mod && mod.__esModule ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const fs_1 = __importDefault(__require("fs")); const util_1 = __importDefault(__require("util")); exports.default = { existsSync: fs_1.default.existsSync, readFile: util_1.default.promisify(fs_1.default.readFile), watchFile: fs_1.default.watchFile, createReadStream: fs_1.default.createReadStream, stat: util_1.default.promisify(fs_1.default.stat) }; })); //#endregion //#region node_modules/.pnpm/maxmind@5.0.7/node_modules/maxmind/lib/ip.js var require_ip = /* @__PURE__ */ __commonJSMin(((exports) => { var __importDefault = exports && exports.__importDefault || function(mod) { return mod && mod.__esModule ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const net_1 = __importDefault(__require("net")); const parseIPv4 = (input) => { const ip = input.split(".", 4); return [ parseInt(ip[0]), parseInt(ip[1]), parseInt(ip[2]), parseInt(ip[3]) ]; }; const hex = (v) => { v = parseInt(v, 10).toString(16); return v.length === 2 ? v : "0" + v; }; const parseIPv6 = (ip) => { const addr = [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ]; let i; let parsed; let chunk; if (ip.indexOf(".") > -1) ip = ip.replace(/(\d+)\.(\d+)\.(\d+)\.(\d+)/, (match, a, b, c, d) => { return hex(a) + hex(b) + ":" + hex(c) + hex(d); }); const [left, right] = ip.split("::", 2); if (left) { parsed = left.split(":"); for (i = 0; i < parsed.length; i++) { chunk = parseInt(parsed[i], 16); addr[i * 2] = chunk >> 8; addr[i * 2 + 1] = chunk & 255; } } if (right) { parsed = right.split(":"); const offset = 16 - parsed.length * 2; for (i = 0; i < parsed.length; i++) { chunk = parseInt(parsed[i], 16); addr[offset + i * 2] = chunk >> 8; addr[offset + (i * 2 + 1)] = chunk & 255; } } return addr; }; const parse = (ip) => { return ip.indexOf(":") === -1 ? parseIPv4(ip) : parseIPv6(ip); }; const bitAt = (rawAddress, idx) => { const bufIdx = idx >> 3; const bitIdx = 7 ^ idx & 7; return rawAddress[bufIdx] >>> bitIdx & 1; }; const validate = (ip) => { const version = net_1.default.isIP(ip); return version === 4 || version === 6; }; exports.default = { bitAt, parse, validate }; })); //#endregion //#region node_modules/.pnpm/maxmind@5.0.7/node_modules/maxmind/lib/is-gzip.js var require_is_gzip = /* @__PURE__ */ __commonJSMin(((exports) => { Object.defineProperty(exports, "__esModule", { value: true }); exports.default = (buf) => { if (!buf || buf.length < 3) return false; return buf[0] === 31 && buf[1] === 139 && buf[2] === 8; }; })); //#endregion //#region node_modules/.pnpm/maxmind@5.0.7/node_modules/maxmind/lib/utils.js var require_utils = /* @__PURE__ */ __commonJSMin(((exports) => { Object.defineProperty(exports, "__esModule", { value: true }); const concat2 = (a, b) => { return a << 8 | b; }; const concat3 = (a, b, c) => { return a << 16 | b << 8 | c; }; const concat4 = (a, b, c, d) => { return a << 24 | b << 16 | c << 8 | d; }; exports.default = { concat2, concat3, concat4, legacyErrorMessage: `Maxmind v2 module has changed API.\n\ Upgrade instructions can be found here: \ https://github.com/runk/node-maxmind/wiki/Migration-guide\n\ If you want to use legacy libary then explicitly install maxmind@1` }; })); //#endregion //#region extensions/geolocation/src/database-store.ts /** * Owns the on-disk MMDB copy: downloads it on first use, reuses it until it * ages past the refresh window, and keeps one opened reader per process. */ var import_lib = (/* @__PURE__ */ __commonJSMin(((exports) => { var __createBinding = exports && exports.__createBinding || (Object.create ? (function(o, m, k, k2) { if (k2 === void 0) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) desc = { enumerable: true, get: function() { return m[k]; } }; Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === void 0) k2 = k; o[k2] = m[k]; })); var __exportStar = exports && exports.__exportStar || function(m, exports$1) { for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports$1, p)) __createBinding(exports$1, m, p); }; var __importDefault = exports && exports.__importDefault || function(mod) { return mod && mod.__esModule ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.Reader = exports.validate = exports.init = exports.openSync = exports.open = void 0; const assert_1 = __importDefault(__require("assert")); const mmdb_lib_1 = require_lib$1(); Object.defineProperty(exports, "Reader", { enumerable: true, get: function() { return mmdb_lib_1.Reader; } }); const tiny_lru_1 = require_tiny_lru(); const fs_1 = __importDefault(require_fs()); const ip_1 = __importDefault(require_ip()); const is_gzip_1 = __importDefault(require_is_gzip()); const utils_1 = __importDefault(require_utils()); const LARGE_FILE_THRESHOLD = 536870912; const STREAM_WATERMARK = 8388608; const readLargeFile = async (filepath, size) => new Promise((resolve, reject) => { let buffer = Buffer.allocUnsafe(size); let offset = 0; const stream = fs_1.default.createReadStream(filepath, { highWaterMark: STREAM_WATERMARK }); stream.on("data", (chunk) => { if (Buffer.isBuffer(chunk)) { chunk.copy(buffer, offset); offset += chunk.length; } else { const bufferChunk = Buffer.from(chunk); bufferChunk.copy(buffer, offset); offset += bufferChunk.length; } }); stream.on("end", () => { stream.close(); resolve(buffer); }); stream.on("error", (err) => { reject(err); }); }); const readFile = async (filepath) => { const fstat = await fs_1.default.stat(filepath); return fstat.size < LARGE_FILE_THRESHOLD ? fs_1.default.readFile(filepath) : readLargeFile(filepath, fstat.size); }; const open = async (filepath, opts, cb) => { (0, assert_1.default)(!cb, utils_1.default.legacyErrorMessage); const database = await readFile(filepath); if ((0, is_gzip_1.default)(database)) throw new Error("Looks like you are passing in a file in gzip format, please use mmdb database instead."); const cache = (0, tiny_lru_1.lru)(opts?.cache?.max || 1e4); const reader = new mmdb_lib_1.Reader(database, { cache }); if (opts && !!opts.watchForUpdates) { if (opts.watchForUpdatesHook && typeof opts.watchForUpdatesHook !== "function") throw new Error("opts.watchForUpdatesHook should be a function"); const watcherOptions = { persistent: opts.watchForUpdatesNonPersistent !== true }; fs_1.default.watchFile(filepath, watcherOptions, async () => { const waitExists = async () => { for (let i = 0; i < 3; i++) { if (fs_1.default.existsSync(filepath)) return true; await new Promise((a) => setTimeout(a, 500)); } return false; }; if (!await waitExists()) return; const updatedDatabase = await readFile(filepath); cache.clear(); reader.load(updatedDatabase); if (opts.watchForUpdatesHook) opts.watchForUpdatesHook(); }); } return reader; }; exports.open = open; const openSync = () => { throw new Error(utils_1.default.legacyErrorMessage); }; exports.openSync = openSync; const init = () => { throw new Error(utils_1.default.legacyErrorMessage); }; exports.init = init; exports.validate = ip_1.default.validate; __exportStar(require_lib$1(), exports); exports.default = { init: exports.init, open: exports.open, openSync: exports.openSync, validate: ip_1.default.validate }; })))(); const gunzipAsync = promisify(gunzip); /** * Reads the body chunk by chunk and fails as soon as the running total passes * the cap, so an oversized response is rejected mid-flight instead of after it * has already been allocated in full. */ async function readBoundedBody(response, limit) { const body = response.body; if (!body) throw new Error("response had no body"); const reader = body.getReader(); const chunks = []; let total = 0; try { for (;;) { const { done, value } = await reader.read(); if (done) break; total += value.byteLength; if (total > limit) throw new Error(`response exceeded the ${limit} byte cap`); chunks.push(Buffer.from(value)); } } finally { await reader.cancel().catch(() => {}); } return Buffer.concat(chunks); } const MAX_COMPRESSED_BYTES = 268435456; const MAX_DATABASE_BYTES = 536870912; const DOWNLOAD_TIMEOUT_MS = 12e4; function databasePath(stateDir, databaseUrl) { const digest = createHash("sha256").update(databaseUrl).digest("hex").slice(0, 12); return path.join(stateDir, "geolocation", `ip-city-${digest}.mmdb`); } async function readFileAge(file, now) { try { const stat = await fs.stat(file); return now.getTime() - stat.mtimeMs; } catch { return; } } async function downloadDatabase(deps, target) { const urls = expandDatabaseUrls(deps.settings.databaseUrl, deps.now()); const failures = []; for (const url of urls) try { const response = await deps.fetchImpl(url, { signal: AbortSignal.timeout(DOWNLOAD_TIMEOUT_MS) }); if (!response.ok) { failures.push(`${url} -> HTTP ${response.status}`); continue; } const raw = await readBoundedBody(response, MAX_COMPRESSED_BYTES); const body = url.endsWith(".gz") ? await gunzipAsync(raw, { maxOutputLength: MAX_DATABASE_BYTES }) : raw; const reader = new import_lib.Reader(body); await fs.mkdir(path.dirname(target), { recursive: true }); const staging = `${target}.partial`; await fs.writeFile(staging, body); await fs.rename(staging, target); deps.logger?.info(`geolocation: downloaded ${body.byteLength} bytes from ${url}`); return reader; } catch (err) { failures.push(`${url} -> ${err instanceof Error ? err.message : String(err)}`); } throw new Error(`geolocation database download failed: ${failures.join("; ")}`); } /** * Returns a reader, downloading or refreshing the database as needed. A stale * copy is preferred over failing: a refresh error must not take lookups down. */ function createGeolocationDatabaseStore(deps) { const target = databasePath(deps.stateDir, deps.settings.databaseUrl); let opened; let inFlight; const openFromDisk = async () => { const age = await readFileAge(target, deps.now()); let downloaded; if (age === void 0) downloaded = await downloadDatabase(deps, target); else if (age > deps.settings.refreshMs) try { downloaded = await downloadDatabase(deps, target); } catch (err) { deps.logger?.warn(`geolocation: refresh failed, serving the cached database: ${err instanceof Error ? err.message : String(err)}`); } const reader = downloaded ?? new import_lib.Reader(await fs.readFile(target)); opened = { reader, loadedAt: deps.now().getTime() }; return { lookup: (ip) => reader.get(ip) }; }; return { async load() { const current = opened; if (current && deps.now().getTime() - current.loadedAt <= deps.settings.refreshMs) return { lookup: (ip) => current.reader.get(ip) }; inFlight ??= openFromDisk().finally(() => { inFlight = void 0; }); return await inFlight; }, databaseFile: target }; } //#endregion //#region extensions/geolocation/src/lookup.ts function englishName(names) { const value = names?.en?.trim(); return value ? value : void 0; } /** * Returns undefined when the database has no usable placement for the address, * so callers can distinguish "not found" from an empty-but-present answer. */ function projectGeolocationRecord(record) { if (!record) return; const result = { ...englishName(record.city?.names) ? { city: englishName(record.city?.names) } : {}, ...englishName(record.subdivisions?.[0]?.names) ? { region: englishName(record.subdivisions?.[0]?.names) } : {}, ...englishName(record.country?.names) ? { country: englishName(record.country?.names) } : {}, ...record.country?.iso_code ? { countryCode: record.country.iso_code } : {} }; return Object.keys(result).length > 0 ? result : void 0; } //#endregion //#region extensions/geolocation/src/lookup-route.ts function sendJson(res, status, body) { const payload = JSON.stringify(body); res.writeHead(status, { "content-type": "application/json; charset=utf-8", "content-length": Buffer.byteLength(payload) }); res.end(payload); } function createGeolocationLookupHandler(deps) { return async (req, res) => { const url = new URL(req.url ?? "/", "http://localhost"); if (!url.pathname.endsWith("/lookup")) return false; const ip = url.searchParams.get("ip")?.trim() ?? ""; if (!net.isIP(ip)) { sendJson(res, 400, { error: "ip must be a valid IPv4 or IPv6 address" }); return true; } if (isPrivateOrLoopbackHost(ip)) { sendJson(res, 200, { found: false, attribution: deps.settings.attribution }); return true; } try { const location = projectGeolocationRecord((await deps.loadDatabase()).lookup(ip)); sendJson(res, 200, { found: Boolean(location), ...location, attribution: deps.settings.attribution }); } catch (err) { const message = err instanceof Error ? err.message : String(err); deps.logger?.warn(`geolocation: lookup unavailable: ${message}`); sendJson(res, 503, { error: "geolocation database unavailable" }); } return true; }; } //#endregion //#region extensions/geolocation/index.ts /** * Geolocation plugin entry. It exposes one authenticated lookup route and keeps * the database download lazy, so an install that nobody queries costs nothing. */ var geolocation_default = definePluginEntry({ id: "geolocation", name: "Geolocation Plugin", description: "Bundled geolocation plugin", register(api) { const settings = resolveGeolocationSettings(api.pluginConfig); const store = createGeolocationDatabaseStore({ stateDir: resolveStateDir(), settings, now: () => /* @__PURE__ */ new Date(), fetchImpl: fetch, logger: api.logger }); api.registerHttpRoute({ path: "/plugins/geolocation", auth: "gateway", match: "prefix", handler: createGeolocationLookupHandler({ loadDatabase: () => store.load(), settings, logger: api.logger }) }); } }); //#endregion export { geolocation_default as default };