├── .gitignore ├── LICENSE ├── README.md ├── SECURITY.md ├── examples ├── 01-basic-ping.js ├── 02-error-handling.js ├── 03-server-dashboard.js └── 04-cli.js ├── index.js ├── lib ├── bedrock.js ├── java.js └── varint.js ├── package-lock.json ├── package.json ├── test ├── bedrock.test.js ├── java.test.js └── varint.test.js ├── tsconfig.json └── types ├── index.d.ts └── lib ├── bedrock.d.ts ├── java.d.ts └── varint.d.ts /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Timofey Gelazoniya 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # mineping 2 | 3 | [![npm version](https://img.shields.io/npm/v/@minescope/mineping.svg)](https://www.npmjs.com/package/@minescope/mineping) 4 | 5 | A simple and efficient JavaScript library for pinging Minecraft servers. It supports both Java and Bedrock editions through a async/await-friendly API. 6 | 7 | `@minescope/mineping` automatically resolves SRV records for Java servers and parses rich status data, including MOTD, player counts, version info, and server icons. Comes with full TypeScript support. 8 | 9 | *Mirror on my [ Git](https://git.zeldon.ru/zeldon/mineping)* 10 | 11 | ## Features 12 | 13 | - **Dual Protocol Support:** Ping both Java and Bedrock servers with a consistent API. 14 | - **SRV Record Resolution:** Automatically resolves SRV records for Java Edition servers, so you don't have to worry about custom ports. 15 | - **Rich Data:** Parses the full response from servers, including player samples, favicons, gamemodes, and more. 16 | - **Lightweight:** Has only **one** runtime dependency — [debug](https://www.npmjs.com/package/debug) (used for tracing). 17 | 18 | ## Requirements 19 | 20 | > **[Node.js](https://nodejs.org/) 14 or newer is required** 21 | 22 | ## Install 23 | 24 | To install `mineping`, simply run the following command: 25 | 26 | ```bash 27 | npm i @minescope/mineping 28 | ``` 29 | 30 | ## API & Usage Examples 31 | 32 | The library exports two main functions: `pingJava` and `pingBedrock`. Both are asynchronous and return a `Promise`. 33 | 34 | ### 1. Basic Server Ping 35 | 36 | Java: 37 | ```js 38 | import { pingJava } from "@minescope/mineping"; 39 | 40 | const data = await pingJava("0.0.0.0"); 41 | console.log(data) 42 | ``` 43 | ```js 44 | { 45 | version: { name: '1.21.5', protocol: 770 }, 46 | enforcesSecureChat: true, 47 | description: '§1Welcome to §2My Minecraft Server!', 48 | players: { max: 20, online: 0 } 49 | } 50 | ``` 51 | 52 | Bedrock: 53 | ```js 54 | import { pingBedrock } from "@minescope/mineping"; 55 | 56 | const data = await pingBedrock("0.0.0.0"); 57 | console.log(data) 58 | ``` 59 | ```js 60 | { 61 | edition: 'MCPE', 62 | name: 'Dedicated Server', 63 | levelName: 'Bedrock level', 64 | gamemode: 'Survival', 65 | version: { protocol: 800, minecraft: '1.21.84' }, 66 | players: { online: 0, max: 10 }, 67 | port: { v4: 19132, v6: 19133 }, 68 | guid: 12143264093420916401n, 69 | isNintendoLimited: false, 70 | isEditorModeEnabled: false 71 | } 72 | ``` 73 | 74 | ## Loading and configuration the module 75 | 76 | ### CommonJS 77 | 78 | `mineping` is an ESM-only module — you are not able to import it with `require()`. 79 | If you cannot switch to ESM, you can use the async `import()` function from CommonJS to load `mineping` asynchronously: 80 | 81 | ```js 82 | const pingJava = (...args) => 83 | import("@minescope/mineping").then((module) => module.pingJava(...args)); 84 | const pingBedrock = (...args) => 85 | import("@minescope/mineping").then((module) => module.pingBedrock(...args)); 86 | ``` 87 | 88 | ## Debugging 89 | 90 | `mineping` uses the [`debug`](https://www.npmjs.com/package/debug) library to provide detailed tracing information, which can be useful for diagnosing connection issues or understanding the library's internal workings. 91 | 92 | To enable debug logs, set the `DEBUG` environment variable when running your script. The library uses two namespaces: 93 | 94 | - `mineping:java` for the Java Edition pinger. 95 | - `mineping:bedrock` for the Bedrock Edition pinger. 96 | 97 | ### Examples 98 | 99 | **Enable all `mineping` debug logs:** 100 | 101 | You can use a wildcard (`*`) to enable all logs from this library. 102 | 103 | ```bash 104 | DEBUG=mineping:* node your-script.js 105 | ``` 106 | 107 |
108 | Click to see output for DEBUG="mineping:*" node examples/01-basic-ping.js 109 | 110 | ```bash 111 | DEBUG="mineping:*" node examples/01-basic-ping.js 112 | mineping:java pinging Java server hypixel.net with options: {} +0ms 113 | mineping:java attempting SRV lookup for _minecraft._tcp.hypixel.net with 5000ms timeout +2ms 114 | mineping:java SRV lookup successful, new target: mc.hypixel.net:25565 +2ms 115 | mineping:java creating TCP connection to mc.hypixel.net:25565 +0ms 116 | mineping:java socket connected to mc.hypixel.net:25565, sending packets... +182ms 117 | mineping:java received 1440 bytes of data, total buffer size is now 1440 bytes +130ms 118 | mineping:java packet incomplete, waiting for more data +0ms 119 | mineping:java received 12960 bytes of data, total buffer size is now 14400 bytes +1ms 120 | mineping:java packet incomplete, waiting for more data +0ms 121 | mineping:java received 1601 bytes of data, total buffer size is now 16001 bytes +129ms 122 | mineping:java received raw JSON response +0ms 123 | mineping:java successfully parsed full response +0ms 124 | mineping:java cleaning up resources for mc.hypixel.net:25565 +0ms 125 | --- Java Server --- 126 | { 127 | version: { name: 'Requires MC 1.8 / 1.21', protocol: 47 }, 128 | players: { max: 200000, online: 28654, sample: [] }, 129 | description: ' §aHypixel Network §c[1.8-1.21]\n' + 130 | ' §6§lSB 0.23 §2§lFORAGING §8§l- §e§lSUMMER EVENT', 131 | favicon: 'data:image/png;base64,iVBORw0K'... 5738 more characters 132 | } 133 | 134 | ==================== 135 | 136 | mineping:bedrock pinging Bedrock server geo.hivebedrock.network:19132 with 5000ms timeout +0ms 137 | mineping:bedrock sending Unconnected Ping packet to geo.hivebedrock.network:19132 +1ms 138 | mineping:bedrock packet: +0ms 139 | mineping:bedrock received 124 bytes from geo.hivebedrock.network:19132 +104ms 140 | mineping:bedrock received raw MOTD string: MCPE;BEDWARS + BUILD BATTLE;121;1.0;13074;100001;-4669279440237021648;Hive Games;Survival +0ms 141 | mineping:bedrock cleaning up resources for geo.hivebedrock.network:19132 +0ms 142 | --- Bedrock Server --- 143 | { 144 | edition: 'MCPE', 145 | name: 'BEDWARS + BUILD BATTLE', 146 | levelName: 'Hive Games', 147 | gamemode: 'Survival', 148 | version: { protocol: 121, minecraft: '1.0' }, 149 | players: { online: 13074, max: 100001 }, 150 | port: { v4: undefined, v6: undefined }, 151 | guid: -4669279440237021648n, 152 | isNintendoLimited: undefined, 153 | isEditorModeEnabled: undefined 154 | } 155 | ``` 156 |
157 | 158 | **_PowerShell_ uses different syntax to set environment variables:** 159 | 160 | ```powershell 161 | $env:DEBUG="mineping:*";node your-script.js 162 | ``` 163 | 164 | ## Acknowledgements 165 | 166 | Special thanks to the following projects for inspiration and protocol details: 167 | 168 | - [mcping](https://github.com/Scetch/mcping) crate for Rust 169 | - [mcping-js](https://github.com/Cryptkeeper/mcping-js) library for querying Minecraft Java Edition servers 170 | - The amazing community at [minecraft.wiki](https://minecraft.wiki/) for documenting the protocols. 171 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Reporting a Vulnerability 2 | 3 | If you believe you have found a security vulnerability, let me know by sending email to timofey@z4n.me I will investigate that and do my best to quickly fix the problem. 4 | 5 | Please don't open an issue to or discuss this security vulnerability in a public place. Thanks for understanding! 6 | -------------------------------------------------------------------------------- /examples/01-basic-ping.js: -------------------------------------------------------------------------------- 1 | import { pingJava, pingBedrock } from "../index.js"; 2 | 3 | try { 4 | const javaData = await pingJava("hypixel.net"); 5 | console.log("--- Java Server ---"); 6 | console.log(javaData); 7 | } catch (error) { 8 | console.error("Could not ping Java server:", error); 9 | } 10 | 11 | console.log("\n" + "=".repeat(20) + "\n"); 12 | 13 | try { 14 | const motd = await pingBedrock("geo.hivebedrock.network"); 15 | console.log("--- Bedrock Server ---"); 16 | console.log(motd); 17 | } catch (error) { 18 | console.error("Could not ping Bedrock server:", error); 19 | } 20 | -------------------------------------------------------------------------------- /examples/02-error-handling.js: -------------------------------------------------------------------------------- 1 | import { pingJava } from "../index.js"; 2 | 3 | const offlineServer = "this.server.does.not.exist"; 4 | const port = 12345; 5 | 6 | console.log(`Pinging an offline server: ${offlineServer}:${port}`); 7 | 8 | try { 9 | // We set a short timeout to fail faster. 10 | const data = await pingJava(offlineServer, { port, timeout: 500 }); 11 | console.log("Success!?", data); 12 | } catch (error) { 13 | console.error("Caught expected error:", error.message); 14 | } 15 | -------------------------------------------------------------------------------- /examples/03-server-dashboard.js: -------------------------------------------------------------------------------- 1 | import { pingJava, pingBedrock } from "../index.js"; 2 | 3 | const servers = [ 4 | { type: "Java", host: "mc.hypixel.net" }, 5 | { type: "Java", host: "play.cubecraft.net" }, 6 | { type: "Java", host: "an-offline-java-server.com" }, 7 | { type: "Bedrock", host: "geo.hivebedrock.network" }, 8 | { type: "Bedrock", host: "buzz.insanitycraft.net" }, 9 | { type: "Bedrock", host: "an.offline.bedrock.server" }, 10 | ]; 11 | 12 | console.log("Pinging all servers..."); 13 | 14 | // Create an array of ping promises 15 | const pingPromises = servers.map((server) => { 16 | if (server.type === "Java") { 17 | return pingJava(server.host, { timeout: 3000 }); 18 | } else { 19 | return pingBedrock(server.host, { timeout: 3000 }); 20 | } 21 | }); 22 | 23 | // Wait for all pings to complete (or fail) 24 | const results = await Promise.allSettled(pingPromises); 25 | 26 | // Process and display results 27 | const displayData = results.map((result, index) => { 28 | const server = servers[index]; 29 | if (result.status === "fulfilled") { 30 | const data = result.value; 31 | return { 32 | Server: `${server.type} - ${server.host}`, 33 | Status: "✅ Online", 34 | Players: `${data.players.online} / ${data.players.max}`, 35 | Version: data.version.name ?? data.version.minecraft, 36 | }; 37 | } else { 38 | return { 39 | Server: `${server.type} - ${server.host}`, 40 | Status: "❌ Offline", 41 | Players: "N/A", 42 | Version: `Error: ${result.reason.message.slice(0, 30)}...`, 43 | }; 44 | } 45 | }); 46 | 47 | console.table(displayData); 48 | -------------------------------------------------------------------------------- /examples/04-cli.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /** 4 | * Usage examples: 5 | * - Java (with custom timeout): node cli.js -j --host="mc.hypixel.net" --timeout 1000 6 | * - Bedrock: node cli.js -b --host="play.timecrack.net" 7 | */ 8 | 9 | import { pingBedrock, pingJava } from "../index.js"; 10 | 11 | const DEFAULT_TIMEOUT = 5000; 12 | const JAVA_DEFAULT_PORT = 25565; 13 | const BEDROCK_DEFAULT_PORT = 19132; 14 | 15 | try { 16 | const args = parseArgs(process.argv.slice(2)); 17 | 18 | if (shouldShowHelp(args)) { 19 | printHelp(); 20 | process.exit(0); 21 | } 22 | 23 | validateArgs(args); 24 | 25 | const port = Number(args.port) || getDefaultPort(args); 26 | const timeout = Number(args.timeout) || DEFAULT_TIMEOUT; 27 | 28 | if (args.j) { 29 | await pingJavaServer(args.host, port, timeout); 30 | } else if (args.b) { 31 | await pingBedrockServer(args.host, port, timeout); 32 | } 33 | } catch (err) { 34 | console.error(`ERROR: ${err.message}`); 35 | process.exit(1); 36 | } 37 | 38 | function parseArgs(rawArgs) { 39 | const args = {}; 40 | 41 | for (let i = 0; i < rawArgs.length; i++) { 42 | const arg = rawArgs[i]; 43 | 44 | if (arg.startsWith("--")) { 45 | // Handle --key=value and --key value formats 46 | const [key, value] = arg.slice(2).split("="); 47 | args[key] = value ?? rawArgs[++i] ?? true; 48 | } else if (arg.startsWith("-")) { 49 | // Handle short flags (-j, -b, -h) 50 | const flags = arg.slice(1).split(""); 51 | flags.forEach((flag) => { 52 | args[flag] = true; 53 | }); 54 | } 55 | } 56 | 57 | return args; 58 | } 59 | 60 | function validateArgs(args) { 61 | if (args.j && args.b) { 62 | printInterestingFacts(); 63 | process.exit(0); 64 | } 65 | 66 | if (!args.host) { 67 | throw new Error("The host argument not found! Use -h or --help."); 68 | } 69 | 70 | if (!args.j && !args.b) { 71 | throw new Error("Must specify either -j or -b flag. Use -h or --help."); 72 | } 73 | 74 | if (args.port && (isNaN(args.port) || args.port < 1 || args.port > 65535)) { 75 | throw new Error("Port must be a number between 1 and 65535"); 76 | } 77 | 78 | if (args.timeout && (isNaN(args.timeout) || args.timeout < 0)) { 79 | throw new Error("Timeout must be a positive number"); 80 | } 81 | } 82 | 83 | function shouldShowHelp(args) { 84 | return args.help || args.h || Object.keys(args).length === 0; 85 | } 86 | 87 | function printHelp() { 88 | console.log(`node cli.js [..] 89 | A simple to use, efficient, and full-featured Minecraft server info parser! 90 | 91 | USAGE: 92 | node cli.js [OPTIONS] --host --port --timeout 93 | 94 | OPTIONS: 95 | -j Use for Minecraft Java Edition 96 | -b Use for Minecraft Bedrock Edition 97 | -h, --help Show this help message 98 | 99 | --host The server address (required) 100 | --port The server port (default: ${JAVA_DEFAULT_PORT} for Java, ${BEDROCK_DEFAULT_PORT} for Bedrock) 101 | --timeout The socket timeout in milliseconds (default: ${DEFAULT_TIMEOUT}) 102 | 103 | P.S. Don't use -j and -b at the same time!`); 104 | } 105 | 106 | function printInterestingFacts() { 107 | console.log(`Some interesting facts about MOTDs on bedrock: 108 | - so far they seem to exclusively use legacy color codes 109 | - the random style has a special impl for periods, they turn into animated 110 | colons that warp up and down rapidly 111 | - motd_2 is ignored? client displays "motd_1 - v{version}", where the 112 | appended version text is considered part of motd_1 for color code processing 113 | - motd_2 seems to mainly be used to return the server software in use (e.g. PocketMine-MP)`); 114 | } 115 | 116 | function getDefaultPort(args) { 117 | return args.j ? JAVA_DEFAULT_PORT : BEDROCK_DEFAULT_PORT; 118 | } 119 | 120 | async function pingJavaServer(host, port, timeout) { 121 | const data = await pingJava(host, { port, timeout }); 122 | console.log(`Host: ${host} 123 | Version: ${data.version.name} (protocol: ${data.version.protocol}) 124 | Players: ${data.players?.online}/${data.players?.max} 125 | Description: ${ 126 | typeof data.description === "string" 127 | ? data.description 128 | : data.description?.text 129 | }`); 130 | } 131 | 132 | async function pingBedrockServer(host, port, timeout) { 133 | const data = await pingBedrock(host, { port, timeout }); 134 | console.log(`Host: ${host} 135 | Edition: ${data.edition} 136 | Version: ${data.version.minecraft} (protocol: ${data.version.protocol}) 137 | Players: ${data.players.online}/${data.players.max} 138 | Name: ${data.name} 139 | Gamemode: ${data.gamemode}`); 140 | } 141 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | export { pingJava } from './lib/java.js'; 2 | export { pingBedrock } from './lib/bedrock.js'; -------------------------------------------------------------------------------- /lib/bedrock.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Implementation of the RakNet ping/pong protocol. 3 | * @see https://minecraft.wiki/w/RakNet 4 | */ 5 | 6 | "use strict"; 7 | 8 | import dgram from "node:dgram"; 9 | import crypto from "node:crypto"; 10 | import createDebug from "debug"; 11 | 12 | const debug = createDebug("mineping:bedrock"); 13 | 14 | const MAGIC = "00ffff00fefefefefdfdfdfd12345678"; 15 | const START_TIME = Date.now(); 16 | const UNCONNECTED_PONG = 0x1c; 17 | 18 | /** 19 | * Representation of raw, semicolon-delimited MOTD string. 20 | * This struct directly mirrors the fields and order from the server response. 21 | * See [`Unconnected Pong Documentation`](https://minecraft.wiki/w/RakNet#Unconnected_Pong) for more details. 22 | * @typedef {object} BedrockMotd 23 | * @property {string} edition - The edition of the server (MCPE or MCEE). 24 | * @property {string} name - The primary name of the server (first line of MOTD). 25 | * @property {number} protocol - The protocol version. 26 | * @property {string} version - The game version (e.g., "1.21.2"). 27 | * @property {number} playerCount - The current number of players online. 28 | * @property {number} playerMax - The maximum number of players allowed. 29 | * @property {bigint} serverGuid - The server's GUID. 30 | * @property {string} subName - The secondary name of the server (second line of MOTD). 31 | * @property {string} gamemode - The default gamemode (e.g., "Survival"). 32 | * @property {boolean} [nintendoLimited] - Whether the server is Nintendo limited. 33 | * @property {number} [port] - The server's IPv4 port, if provided. 34 | * @property {number} [ipv6Port] - The server's IPv6 port, if provided. 35 | * @property {boolean} [editorMode] - Whether the server is in editor mode, if provided. See [Minecraft Editor Mode Documentation](https://learn.microsoft.com/en-us/minecraft/creator/documents/bedrockeditor/editoroverview?view=minecraft-bedrock-stable) for more details. 36 | */ 37 | 38 | /** 39 | * Represents the structured and user-friendly response from a server ping. 40 | * This is the public-facing object that users of the library will receive. 41 | * @typedef {object} BedrockPingResponse 42 | * @property {string} edition - The edition of the server (MCPE or MCEE). 43 | * @property {string} name - The primary name of the server (first line of MOTD). 44 | * @property {string} levelName - The name of the world or level being hosted. 45 | * @property {string} gamemode - The default gamemode of the server. 46 | * @property {{ protocol: number, minecraft: string }} version - Game and protocol versions. 47 | * @property {{ online: number, max: number }} players - Current and maximum player counts. 48 | * @property {{ v4?: number, v6?: number }} port - Announced IPv4 and IPv6 ports. 49 | * @property {bigint} guid - The server's unique 64-bit GUID. 50 | * @property {boolean} [isNintendoLimited] - True if the server restricts Nintendo Switch players. 51 | * @property {boolean} [isEditorModeEnabled] - True if the server is in editor mode. See [Minecraft Editor Mode Documentation](https://learn.microsoft.com/en-us/minecraft/creator/documents/bedrockeditor/editoroverview?view=minecraft-bedrock-stable) for more details. 52 | */ 53 | 54 | /** 55 | * @typedef {object} BedrockPingOptions 56 | * @property {number} [port=19132] - The server port to ping. 57 | * @property {number} [timeout=5000] - The timeout in milliseconds for the request. 58 | */ 59 | 60 | /** 61 | * Creates an Unconnected Ping packet. 62 | * See [Unconnected Ping Documentation](https://minecraft.wiki/w/RakNet#Unconnected_Ping) for more details. 63 | * @param {number} timestamp - The current time delta since the script started. 64 | * @returns {Buffer} 65 | */ 66 | const createUnconnectedPingFrame = (timestamp) => { 67 | const buffer = Buffer.alloc(33); 68 | buffer.writeUInt8(0x01, 0); // Packet ID 69 | buffer.writeBigInt64LE(BigInt(timestamp), 1); // Timestamp 70 | Buffer.from(MAGIC, "hex").copy(buffer, 9); // OFFLINE_MESSAGE_DATA_ID (Magic bytes) 71 | Buffer.from(crypto.randomBytes(8)).copy(buffer, 25); // Client GUID 72 | return buffer; 73 | }; 74 | 75 | /** 76 | * Parses the semicolon-delimited MOTD string into a structured object. 77 | * @param {string} motdString - The raw MOTD string from the server. 78 | * @returns {BedrockMotd} The parsed internal MOTD object. 79 | * @throws {Error} If the MOTD string is missing required fields. 80 | */ 81 | const parseMotd = (motdString) => { 82 | const parts = motdString.split(";"); 83 | 84 | if (parts.length < 5) { 85 | throw new Error( 86 | `Invalid MOTD format: Expected at least 5 fields, but got ${parts.length}.` 87 | ); 88 | } 89 | 90 | const [ 91 | edition, 92 | name, 93 | protocolStr, 94 | version, 95 | playerCountStr, 96 | playerMaxStr, 97 | serverGuidStr, 98 | subName, 99 | gamemode, 100 | nintendoLimitedStr, 101 | port, 102 | ipv6Port, 103 | editorModeStr, 104 | ] = parts; 105 | 106 | let nintendoLimited; 107 | if (nintendoLimitedStr === "0") { 108 | nintendoLimited = true; 109 | } else if (nintendoLimitedStr === "1") { 110 | nintendoLimited = false; 111 | } 112 | 113 | return { 114 | edition, 115 | name, 116 | protocol: Number(protocolStr), 117 | version, 118 | playerCount: Number(playerCountStr), 119 | playerMax: Number(playerMaxStr), 120 | serverGuid: BigInt(serverGuidStr), 121 | subName, 122 | gamemode, 123 | nintendoLimited, 124 | port: port ? Number(port) : undefined, 125 | ipv6Port: ipv6Port ? Number(ipv6Port) : undefined, 126 | editorMode: editorModeStr ? Boolean(Number(editorModeStr)) : undefined, 127 | }; 128 | }; 129 | 130 | /** 131 | * Transforms the raw MOTD object into a user-friendly, nested structure. 132 | * @param {BedrockMotd} motd - The parsed MOTD object. 133 | * @returns {BedrockPingResponse} The final, user-facing response object. 134 | */ 135 | const transformMotd = (motd) => { 136 | return { 137 | edition: motd.edition, 138 | name: motd.name, 139 | levelName: motd.subName, 140 | gamemode: motd.gamemode, 141 | version: { 142 | protocol: motd.protocol, 143 | minecraft: motd.version, 144 | }, 145 | players: { 146 | online: motd.playerCount, 147 | max: motd.playerMax, 148 | }, 149 | port: { 150 | v4: motd.port, 151 | v6: motd.ipv6Port, 152 | }, 153 | guid: motd.serverGuid, 154 | isNintendoLimited: motd.nintendoLimited, 155 | isEditorModeEnabled: motd.editorMode, 156 | }; 157 | }; 158 | 159 | /** 160 | * Extracts the MOTD string from an Unconnected Pong packet and parses it. 161 | * @param {Buffer} pongPacket - The raw pong packet from the server. 162 | * @returns {BedrockPingResponse} The final response object. 163 | * @throws {Error} If the packet is malformed. 164 | */ 165 | const parseUnconnectedPong = (pongPacket) => { 166 | if (!Buffer.isBuffer(pongPacket) || pongPacket.length < 35) { 167 | throw new Error("Invalid pong packet: buffer is too small."); 168 | } 169 | 170 | const packetId = pongPacket.readUInt8(0); 171 | if (packetId !== UNCONNECTED_PONG) { 172 | throw new Error( 173 | `Unexpected packet ID: 0x${packetId.toString(16)}. Expected 0x1c.` 174 | ); 175 | } 176 | 177 | // The MOTD string is prefixed with its length as a 16-bit big-endian integer 178 | const motdLength = pongPacket.readUInt16BE(33); 179 | const motdOffset = 35; 180 | 181 | if (motdOffset + motdLength > pongPacket.length) { 182 | throw new Error("Malformed pong packet: MOTD length exceeds buffer size."); 183 | } 184 | 185 | const motdString = pongPacket.toString( 186 | "utf-8", 187 | motdOffset, 188 | motdOffset + motdLength 189 | ); 190 | debug("received raw MOTD string: %s", motdString); 191 | 192 | const rawMotd = parseMotd(motdString); 193 | const motd = transformMotd(rawMotd); 194 | return motd; 195 | }; 196 | 197 | /** 198 | * Asynchronously pings a Minecraft Bedrock server. 199 | * @param {string} host - The IP address or hostname of the server. 200 | * @param {BedrockPingOptions} [options={}] - Optional configuration. 201 | * @returns {Promise} A promise that resolves with the server's parsed MOTD. 202 | */ 203 | export async function pingBedrock(host, options = {}) { 204 | if (!host) { 205 | throw new Error("Host argument is required."); 206 | } 207 | 208 | const { port = 19132, timeout = 5000 } = options; 209 | debug("pinging Bedrock server %s:%d with %dms timeout", host, port, timeout); 210 | 211 | return new Promise((resolve, reject) => { 212 | const socket = dgram.createSocket("udp4"); 213 | 214 | // Prevent cleanup tasks from running more than once 215 | // in case of multiple error callbacks 216 | let isCleanupCompleted = false; 217 | 218 | // Set a manual timeout interval to ensure 219 | // the connection will NEVER hang regardless of internal state 220 | const timeoutTask = setTimeout(() => { 221 | socket.emit("error", new Error("Socket timeout")); 222 | }, timeout); 223 | 224 | // Idempotent function to handle cleanup tasks, we can safely call it multiple times without side effects 225 | const cleanup = () => { 226 | if (isCleanupCompleted) return; 227 | isCleanupCompleted = true; 228 | debug("cleaning up resources for %s:%d", host, port); 229 | clearTimeout(timeoutTask); 230 | socket.close(); 231 | }; 232 | 233 | // Generic error handler 234 | socket.on("error", (err) => { 235 | debug("socket error for %s:%d - %s", host, port, err.message); 236 | cleanup(); 237 | reject(err); 238 | }); 239 | 240 | socket.on("message", (pongPacket) => { 241 | debug("received %d bytes from %s:%d", pongPacket.length, host, port); 242 | try { 243 | const motd = parseUnconnectedPong(pongPacket); 244 | cleanup(); 245 | resolve(motd); 246 | } catch (err) { 247 | socket.emit("error", err); 248 | } 249 | }); 250 | 251 | try { 252 | const pingPacket = createUnconnectedPingFrame(Date.now() - START_TIME); 253 | debug("sending Unconnected Ping packet to %s:%d", host, port); 254 | debug("packet: %o", pingPacket); 255 | socket.send(pingPacket, 0, pingPacket.length, port, host); 256 | } catch (err) { 257 | // Handle any immediate, synchronous errors that might occur when sending the ping packet 258 | socket.emit("error", err); 259 | } 260 | }); 261 | } 262 | -------------------------------------------------------------------------------- /lib/java.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Implementation of the Java Minecraft ping protocol. 3 | * @see https://minecraft.wiki/w/Java_Edition_protocol/Server_List_Ping 4 | */ 5 | 6 | "use strict"; 7 | 8 | import { createConnection, isIP } from "node:net"; 9 | import { Resolver } from "node:dns/promises"; 10 | import createDebug from "debug"; 11 | import * as varint from "./varint.js"; 12 | 13 | const debug = createDebug("mineping:java"); 14 | 15 | /** 16 | * Represents the structured and user-friendly response from a server ping. 17 | * The fields and their optionality are based on the protocol documentation. 18 | * See [Status Response Documentation](https://minecraft.wiki/w/Java_Edition_protocol/Server_List_Ping#Status_Response) for more details. 19 | * @typedef {object} JavaPingResponse 20 | * @property {{ name: string, protocol: number }} version - Contains the server's version name and protocol number. 21 | * @property {{ max: number, online: number, sample?: Array<{ name: string, id: string }> }} [players] - Player count and a sample of online players. 22 | * @property {object | string} [description] - The server's Message of the Day (MOTD). 23 | * @property {string} [favicon] - A Base64-encoded 64x64 PNG image data URI. 24 | * @property {boolean} [enforcesSecureChat] - True if the server requires clients to have a Mojang-signed public key. 25 | * @property {boolean} [preventsChatReports] - True if a mod is installed to disable chat reporting. 26 | */ 27 | 28 | /** 29 | * @typedef {object} JavaPingOptions 30 | * @property {number} [port=25565] - The fallback port if an SRV record is not found. 31 | * @property {number} [timeout=5000] - The connection timeout in milliseconds. 32 | * @property {number} [protocolVersion=-1] - The protocol version to use in the handshake. `-1` is for auto-detection. 33 | */ 34 | 35 | /** 36 | * Creates the Handshake packet. 37 | * @param {string} host The hostname to connect to 38 | * @param {number} port The port to connect to 39 | * @param {number} protocolVersion The protocol version to use 40 | * @returns {Buffer} The complete Handshake packet 41 | */ 42 | function createHandshakePacket(host, port, protocolVersion) { 43 | const hostBuffer = varint.encodeString(host); 44 | 45 | const payload = [ 46 | varint.encodeVarInt(0x00), // Packet ID 47 | varint.encodeVarInt(protocolVersion), 48 | varint.encodeVarInt(hostBuffer.length), 49 | hostBuffer, 50 | varint.encodeUShort(port), 51 | varint.encodeVarInt(1), // Next state: 1 for Status 52 | ]; 53 | 54 | return varint.concatPackets(payload); 55 | } 56 | 57 | /** 58 | * Creates the Status Request packet. 59 | * @returns {Buffer} The complete Status Request packet 60 | */ 61 | function createStatusRequestPacket() { 62 | const payload = [ 63 | varint.encodeVarInt(0x00), // Packet ID 64 | ]; 65 | return varint.concatPackets(payload); 66 | } 67 | 68 | /** 69 | * Attempts to parse the server status response from the buffer. 70 | * @param {Buffer} buffer The incoming data buffer 71 | * @returns {{ response: JavaPingResponse, remainder: Buffer } | null} The parsed response and the remaining buffer, or null if the packet is incomplete 72 | */ 73 | function processResponse(buffer) { 74 | let offset = 0; 75 | 76 | try { 77 | const packetLengthResult = varint.decodeVarInt(buffer, offset); 78 | const packetLength = packetLengthResult.value; 79 | offset += packetLengthResult.bytesRead; 80 | 81 | // Check if the full packet has arrived yet. 82 | if (buffer.length < offset + packetLength) { 83 | debug("packet incomplete, waiting for more data"); 84 | return null; // Incomplete packet, wait for more data. 85 | } 86 | 87 | const packetIdResult = varint.decodeVarInt(buffer, offset); 88 | if (packetIdResult.value !== 0x00) { 89 | throw new Error( 90 | `Unexpected packet ID: ${packetIdResult.value}. Expected 0x00.` 91 | ); 92 | } 93 | offset += packetIdResult.bytesRead; 94 | 95 | const jsonLengthResult = varint.decodeVarInt(buffer, offset); 96 | const jsonLength = jsonLengthResult.value; 97 | offset += jsonLengthResult.bytesRead; 98 | 99 | if (buffer.length < offset + jsonLength) { 100 | debug("JSON string incomplete, waiting for more data"); 101 | return null; // Incomplete JSON string, wait for more data. 102 | } 103 | 104 | const jsonString = buffer 105 | .subarray(offset, offset + jsonLength) 106 | .toString("utf8"); 107 | debug("received raw JSON response"); 108 | const response = JSON.parse(jsonString); 109 | 110 | // Return the response and any data that came after this packet. 111 | const remainder = buffer.subarray(offset + jsonLength); 112 | 113 | return { response, remainder }; 114 | } catch (err) { 115 | // If the buffer is too short for a VarInt, it's a recoverable state. 116 | if (err instanceof varint.VarIntError) { 117 | if (err.code === varint.ERR_VARINT_BUFFER_UNDERFLOW) { 118 | debug("buffer underflow while parsing VarInt, waiting for more data"); 119 | return null; // Wait for more data. 120 | } 121 | // For malformed VarInts or JSON, throw the error to reject the promise. 122 | throw err; 123 | } 124 | throw err; 125 | } 126 | } 127 | 128 | /** 129 | * Asynchronously Pings a Minecraft Java Edition server. 130 | * This function performs an SRV lookup and then attempts to connect and retrieve the server status. 131 | * @param {string} host - The server address to ping. 132 | * @param {JavaPingOptions} [options={}] - Optional configuration. 133 | * @returns {Promise} A promise that resolves with the server's status. 134 | */ 135 | export async function pingJava(host, options = {}) { 136 | if (typeof host !== "string" || host.trim() === "") { 137 | throw new Error("Host argument is required."); 138 | } 139 | 140 | const { 141 | port: fallbackPort = 25565, 142 | timeout = 5000, 143 | protocolVersion = -1, 144 | } = options; 145 | debug("pinging Java server %s with options: %o", host, options); 146 | 147 | let targetHost = host; 148 | let targetPort = fallbackPort; 149 | 150 | // A list of hostnames that should never have an SRV lookup. 151 | const nonSrvLookableHostnames = ["localhost"]; 152 | 153 | // Check if the host is a valid IP address (v4 or v6). 154 | // net.isIP() returns 0 for invalid IPs, 4 for IPv4, and 6 for IPv6. 155 | const isDirectIp = isIP(host) !== 0; 156 | const isNonLookableHostname = nonSrvLookableHostnames.includes( 157 | host.toLowerCase() 158 | ); 159 | 160 | if (isDirectIp || isNonLookableHostname) { 161 | debug( 162 | "host '%s' is a direct IP or a non-lookable hostname, skipping SRV lookup.", 163 | host 164 | ); 165 | } else { 166 | try { 167 | debug( 168 | "attempting SRV lookup for _minecraft._tcp.%s with %dms timeout", 169 | host, 170 | timeout 171 | ); 172 | const resolver = new Resolver({ timeout, tries: 3 }); 173 | const srvRecords = await resolver.resolveSrv(`_minecraft._tcp.${host}`); 174 | if (srvRecords.length > 0) { 175 | targetHost = srvRecords[0].name; 176 | targetPort = srvRecords[0].port; 177 | debug( 178 | "SRV lookup successful, new target: %s:%d", 179 | targetHost, 180 | targetPort 181 | ); 182 | } 183 | } catch (err) { 184 | // Common errors like ENODATA, ENOTFOUND, or a DNS timeout (ETIMEOUT) are expected 185 | // when a server does not have an SRV record, so we ignore them and proceed. 186 | const nonFatalDnsCodes = ["ENODATA", "ENOTFOUND", "ETIMEOUT"]; 187 | if ( 188 | err instanceof Error && 189 | "code" in err && 190 | nonFatalDnsCodes.includes(err.code) 191 | ) { 192 | debug("SRV lookup for %s failed (%s), using fallback.", host, err.code); 193 | } else { 194 | // Re-throw anything else to fail the operation 195 | debug("SRV lookup for %s failed unexpectedly, re-throwing.", host, err); 196 | throw err; 197 | } 198 | } 199 | } 200 | 201 | return new Promise((resolve, reject) => { 202 | debug("creating TCP connection to %s:%d", targetHost, targetPort); 203 | const socket = createConnection({ host: targetHost, port: targetPort }); 204 | 205 | // Prevent cleanup tasks from running more than once 206 | // in case of multiple error callbacks 207 | let isCleanupCompleted = false; 208 | 209 | // Set a manual timeout interval to ensure 210 | // the connection will NEVER hang regardless of internal state 211 | const timeoutTask = setTimeout(() => { 212 | socket.emit("error", new Error("Socket timeout")); 213 | }, timeout); 214 | 215 | // Idempotent function to handle cleanup tasks, we can safely call it multiple times without side effects 216 | const cleanup = () => { 217 | if (isCleanupCompleted) return; 218 | isCleanupCompleted = true; 219 | debug("cleaning up resources for %s:%d", targetHost, targetPort); 220 | clearTimeout(timeoutTask); 221 | socket.destroy(); 222 | }; 223 | 224 | // #setNoDelay instantly flushes data during read/writes 225 | // This prevents the runtime from delaying the write at all 226 | socket.setNoDelay(true); 227 | 228 | // Generic error handler 229 | socket.on("error", (err) => { 230 | debug("socket error for %s:%d - %s", targetHost, targetPort, err.message); 231 | cleanup(); 232 | reject(err); 233 | }); 234 | 235 | socket.on("close", () => { 236 | if (!isCleanupCompleted) { 237 | debug("socket for %s:%d closed prematurely", targetHost, targetPort); 238 | cleanup(); 239 | reject(new Error("Socket closed unexpectedly without a response.")); 240 | } 241 | }); 242 | 243 | socket.on("connect", () => { 244 | debug( 245 | "socket connected to %s:%d, sending packets...", 246 | targetHost, 247 | targetPort 248 | ); 249 | try { 250 | const handshakePacket = createHandshakePacket( 251 | host, 252 | targetPort, 253 | protocolVersion 254 | ); 255 | const statusRequestPacket = createStatusRequestPacket(); 256 | socket.write(handshakePacket); 257 | socket.write(statusRequestPacket); 258 | } catch (err) { 259 | // Handle synchronous errors during packet creation/writing 260 | socket.emit("error", err); 261 | } 262 | }); 263 | 264 | let incomingBuffer = Buffer.alloc(0); 265 | 266 | socket.on("data", (data) => { 267 | debug( 268 | "received %d bytes of data, total buffer size is now %d bytes", 269 | data.length, 270 | incomingBuffer.length + data.length 271 | ); 272 | incomingBuffer = Buffer.concat([incomingBuffer, data]); 273 | 274 | try { 275 | const result = processResponse(incomingBuffer); 276 | if (result) { 277 | debug("successfully parsed full response"); 278 | // We successfully parsed a response. Clean up before resolving. 279 | cleanup(); 280 | resolve(result.response); 281 | } 282 | // If result is null, we just wait for more data to arrive. 283 | } catch (err) { 284 | socket.emit("error", err); 285 | } 286 | }); 287 | }); 288 | } 289 | -------------------------------------------------------------------------------- /lib/varint.js: -------------------------------------------------------------------------------- 1 | // https://minecraft.wiki/w/Java_Edition_protocol/Data_types 2 | 3 | "use strict"; 4 | 5 | export const ERR_VARINT_BUFFER_UNDERFLOW = "VARINT_BUFFER_UNDERFLOW"; 6 | export const ERR_VARINT_MALFORMED = "VARINT_MALFORMED"; 7 | export const ERR_VARINT_ENCODE_TOO_LARGE = "VARINT_ENCODE_TOO_LARGE"; 8 | 9 | export class VarIntError extends Error { 10 | /** 11 | * @param {string} message The error message. 12 | * @param {string} code The error code. 13 | */ 14 | constructor(message, code) { 15 | super(message); 16 | this.name = "VarIntError"; 17 | this.code = code; 18 | } 19 | } 20 | 21 | /** 22 | * Encodes an integer into a VarInt buffer. 23 | * VarInts are never longer than 5 bytes for the Minecraft protocol. 24 | * @param {number} value The integer to encode 25 | * @returns {Buffer} The encoded VarInt as a buffer 26 | * @throws {VarIntError} if the value is too large to be encoded 27 | */ 28 | export function encodeVarInt(value) { 29 | const buf = Buffer.alloc(5); 30 | let written = 0; 31 | let val = value; 32 | 33 | while (true) { 34 | const byte = val & 0x7f; 35 | val >>>= 7; 36 | 37 | if (val === 0) { 38 | buf.writeUInt8(byte, written++); 39 | break; 40 | } 41 | 42 | buf.writeUInt8(byte | 0x80, written++); 43 | 44 | if (written >= 5 && val > 0) { 45 | throw new VarIntError( 46 | "Value too large for a 5-byte VarInt", 47 | ERR_VARINT_ENCODE_TOO_LARGE 48 | ); 49 | } 50 | } 51 | 52 | return buf.subarray(0, written); 53 | } 54 | 55 | /** 56 | * Encodes a string into a UTF-8 buffer. 57 | * @param {string} value The string to encode 58 | * @returns {Buffer} 59 | */ 60 | export function encodeString(value) { 61 | return Buffer.from(value, "utf-8"); 62 | } 63 | 64 | /** 65 | * Encodes an unsigned short (16-bit big-endian) into a 2-byte buffer. 66 | * @param {number} value The number to encode 67 | * @returns {Buffer} 68 | */ 69 | export function encodeUShort(value) { 70 | const buf = Buffer.alloc(2); 71 | buf.writeUInt16BE(value, 0); 72 | return buf; 73 | } 74 | 75 | /** 76 | * Creates a Minecraft-style packet by concatenating chunks and prefixing the total length as a VarInt. 77 | * @param {Buffer[]} chunks An array of buffers to include in the packet payload 78 | * @returns {Buffer} The complete packet with its length prefix 79 | */ 80 | export function concatPackets(chunks) { 81 | const payload = Buffer.concat(chunks); 82 | const lengthPrefix = encodeVarInt(payload.length); 83 | return Buffer.concat([lengthPrefix, payload]); 84 | } 85 | 86 | /** 87 | * Decodes a VarInt from a buffer. 88 | * Returns the decoded value and the number of bytes it consumed. 89 | * @param {Buffer} buffer The buffer to read from 90 | * @param {number} [offset=0] The starting offset in the buffer 91 | * @returns {{ value: number, bytesRead: number }} 92 | * @throws {VarIntError} if the buffer is too short or the VarInt is malformed 93 | */ 94 | export function decodeVarInt(buffer, offset = 0) { 95 | if (offset >= buffer.length) { 96 | throw new VarIntError( 97 | "Buffer underflow: Cannot decode VarInt at or beyond buffer length.", 98 | ERR_VARINT_BUFFER_UNDERFLOW 99 | ); 100 | } 101 | 102 | // Fast path for single-byte VarInts, which are very common. 103 | const firstByte = buffer.readUInt8(offset); 104 | if ((firstByte & 0x80) === 0) { 105 | return { value: firstByte, bytesRead: 1 }; 106 | } 107 | 108 | let val = firstByte & 0x7f; // Get the first 7 bits 109 | let position = 7; // Bit position for the next byte's data 110 | let bytesRead = 1; // We've read one byte so far 111 | let currentOffset = offset + 1; // Start reading from the next 112 | 113 | // Max 4 more bytes (total 5 bytes for a VarInt) 114 | for (let i = 0; i < 4; i++) { 115 | if (currentOffset >= buffer.length) { 116 | throw new VarIntError( 117 | "Buffer underflow: Incomplete VarInt, expected more bytes.", 118 | ERR_VARINT_BUFFER_UNDERFLOW 119 | ); 120 | } 121 | 122 | const byte = buffer.readUInt8(currentOffset); 123 | bytesRead++; 124 | currentOffset++; 125 | 126 | val |= (byte & 0x7f) << position; 127 | position += 7; 128 | 129 | if ((byte & 0x80) === 0) { 130 | return { value: val, bytesRead: bytesRead }; 131 | } 132 | } 133 | 134 | throw new VarIntError( 135 | "VarInt is too big or malformed: 5 bytes read with continuation bit still set.", 136 | ERR_VARINT_MALFORMED 137 | ); 138 | } 139 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@minescope/mineping", 3 | "version": "1.7.0-beta.1", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "@minescope/mineping", 9 | "version": "1.7.0-beta.1", 10 | "license": "MIT", 11 | "dependencies": { 12 | "debug": "^4.4.1" 13 | }, 14 | "devDependencies": { 15 | "@types/debug": "^4.1.12", 16 | "@types/node": "^24.0.3", 17 | "typescript": "^5.8.3", 18 | "vitest": "^3.2.3" 19 | }, 20 | "engines": { 21 | "node": ">=14" 22 | } 23 | }, 24 | "node_modules/@esbuild/aix-ppc64": { 25 | "version": "0.25.5", 26 | "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.5.tgz", 27 | "integrity": "sha512-9o3TMmpmftaCMepOdA5k/yDw8SfInyzWWTjYTFCX3kPSDJMROQTb8jg+h9Cnwnmm1vOzvxN7gIfB5V2ewpjtGA==", 28 | "cpu": [ 29 | "ppc64" 30 | ], 31 | "dev": true, 32 | "license": "MIT", 33 | "optional": true, 34 | "os": [ 35 | "aix" 36 | ], 37 | "engines": { 38 | "node": ">=18" 39 | } 40 | }, 41 | "node_modules/@esbuild/android-arm": { 42 | "version": "0.25.5", 43 | "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.5.tgz", 44 | "integrity": "sha512-AdJKSPeEHgi7/ZhuIPtcQKr5RQdo6OO2IL87JkianiMYMPbCtot9fxPbrMiBADOWWm3T2si9stAiVsGbTQFkbA==", 45 | "cpu": [ 46 | "arm" 47 | ], 48 | "dev": true, 49 | "license": "MIT", 50 | "optional": true, 51 | "os": [ 52 | "android" 53 | ], 54 | "engines": { 55 | "node": ">=18" 56 | } 57 | }, 58 | "node_modules/@esbuild/android-arm64": { 59 | "version": "0.25.5", 60 | "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.5.tgz", 61 | "integrity": "sha512-VGzGhj4lJO+TVGV1v8ntCZWJktV7SGCs3Pn1GRWI1SBFtRALoomm8k5E9Pmwg3HOAal2VDc2F9+PM/rEY6oIDg==", 62 | "cpu": [ 63 | "arm64" 64 | ], 65 | "dev": true, 66 | "license": "MIT", 67 | "optional": true, 68 | "os": [ 69 | "android" 70 | ], 71 | "engines": { 72 | "node": ">=18" 73 | } 74 | }, 75 | "node_modules/@esbuild/android-x64": { 76 | "version": "0.25.5", 77 | "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.5.tgz", 78 | "integrity": "sha512-D2GyJT1kjvO//drbRT3Hib9XPwQeWd9vZoBJn+bu/lVsOZ13cqNdDeqIF/xQ5/VmWvMduP6AmXvylO/PIc2isw==", 79 | "cpu": [ 80 | "x64" 81 | ], 82 | "dev": true, 83 | "license": "MIT", 84 | "optional": true, 85 | "os": [ 86 | "android" 87 | ], 88 | "engines": { 89 | "node": ">=18" 90 | } 91 | }, 92 | "node_modules/@esbuild/darwin-arm64": { 93 | "version": "0.25.5", 94 | "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.5.tgz", 95 | "integrity": "sha512-GtaBgammVvdF7aPIgH2jxMDdivezgFu6iKpmT+48+F8Hhg5J/sfnDieg0aeG/jfSvkYQU2/pceFPDKlqZzwnfQ==", 96 | "cpu": [ 97 | "arm64" 98 | ], 99 | "dev": true, 100 | "license": "MIT", 101 | "optional": true, 102 | "os": [ 103 | "darwin" 104 | ], 105 | "engines": { 106 | "node": ">=18" 107 | } 108 | }, 109 | "node_modules/@esbuild/darwin-x64": { 110 | "version": "0.25.5", 111 | "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.5.tgz", 112 | "integrity": "sha512-1iT4FVL0dJ76/q1wd7XDsXrSW+oLoquptvh4CLR4kITDtqi2e/xwXwdCVH8hVHU43wgJdsq7Gxuzcs6Iq/7bxQ==", 113 | "cpu": [ 114 | "x64" 115 | ], 116 | "dev": true, 117 | "license": "MIT", 118 | "optional": true, 119 | "os": [ 120 | "darwin" 121 | ], 122 | "engines": { 123 | "node": ">=18" 124 | } 125 | }, 126 | "node_modules/@esbuild/freebsd-arm64": { 127 | "version": "0.25.5", 128 | "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.5.tgz", 129 | "integrity": "sha512-nk4tGP3JThz4La38Uy/gzyXtpkPW8zSAmoUhK9xKKXdBCzKODMc2adkB2+8om9BDYugz+uGV7sLmpTYzvmz6Sw==", 130 | "cpu": [ 131 | "arm64" 132 | ], 133 | "dev": true, 134 | "license": "MIT", 135 | "optional": true, 136 | "os": [ 137 | "freebsd" 138 | ], 139 | "engines": { 140 | "node": ">=18" 141 | } 142 | }, 143 | "node_modules/@esbuild/freebsd-x64": { 144 | "version": "0.25.5", 145 | "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.5.tgz", 146 | "integrity": "sha512-PrikaNjiXdR2laW6OIjlbeuCPrPaAl0IwPIaRv+SMV8CiM8i2LqVUHFC1+8eORgWyY7yhQY+2U2fA55mBzReaw==", 147 | "cpu": [ 148 | "x64" 149 | ], 150 | "dev": true, 151 | "license": "MIT", 152 | "optional": true, 153 | "os": [ 154 | "freebsd" 155 | ], 156 | "engines": { 157 | "node": ">=18" 158 | } 159 | }, 160 | "node_modules/@esbuild/linux-arm": { 161 | "version": "0.25.5", 162 | "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.5.tgz", 163 | "integrity": "sha512-cPzojwW2okgh7ZlRpcBEtsX7WBuqbLrNXqLU89GxWbNt6uIg78ET82qifUy3W6OVww6ZWobWub5oqZOVtwolfw==", 164 | "cpu": [ 165 | "arm" 166 | ], 167 | "dev": true, 168 | "license": "MIT", 169 | "optional": true, 170 | "os": [ 171 | "linux" 172 | ], 173 | "engines": { 174 | "node": ">=18" 175 | } 176 | }, 177 | "node_modules/@esbuild/linux-arm64": { 178 | "version": "0.25.5", 179 | "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.5.tgz", 180 | "integrity": "sha512-Z9kfb1v6ZlGbWj8EJk9T6czVEjjq2ntSYLY2cw6pAZl4oKtfgQuS4HOq41M/BcoLPzrUbNd+R4BXFyH//nHxVg==", 181 | "cpu": [ 182 | "arm64" 183 | ], 184 | "dev": true, 185 | "license": "MIT", 186 | "optional": true, 187 | "os": [ 188 | "linux" 189 | ], 190 | "engines": { 191 | "node": ">=18" 192 | } 193 | }, 194 | "node_modules/@esbuild/linux-ia32": { 195 | "version": "0.25.5", 196 | "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.5.tgz", 197 | "integrity": "sha512-sQ7l00M8bSv36GLV95BVAdhJ2QsIbCuCjh/uYrWiMQSUuV+LpXwIqhgJDcvMTj+VsQmqAHL2yYaasENvJ7CDKA==", 198 | "cpu": [ 199 | "ia32" 200 | ], 201 | "dev": true, 202 | "license": "MIT", 203 | "optional": true, 204 | "os": [ 205 | "linux" 206 | ], 207 | "engines": { 208 | "node": ">=18" 209 | } 210 | }, 211 | "node_modules/@esbuild/linux-loong64": { 212 | "version": "0.25.5", 213 | "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.5.tgz", 214 | "integrity": "sha512-0ur7ae16hDUC4OL5iEnDb0tZHDxYmuQyhKhsPBV8f99f6Z9KQM02g33f93rNH5A30agMS46u2HP6qTdEt6Q1kg==", 215 | "cpu": [ 216 | "loong64" 217 | ], 218 | "dev": true, 219 | "license": "MIT", 220 | "optional": true, 221 | "os": [ 222 | "linux" 223 | ], 224 | "engines": { 225 | "node": ">=18" 226 | } 227 | }, 228 | "node_modules/@esbuild/linux-mips64el": { 229 | "version": "0.25.5", 230 | "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.5.tgz", 231 | "integrity": "sha512-kB/66P1OsHO5zLz0i6X0RxlQ+3cu0mkxS3TKFvkb5lin6uwZ/ttOkP3Z8lfR9mJOBk14ZwZ9182SIIWFGNmqmg==", 232 | "cpu": [ 233 | "mips64el" 234 | ], 235 | "dev": true, 236 | "license": "MIT", 237 | "optional": true, 238 | "os": [ 239 | "linux" 240 | ], 241 | "engines": { 242 | "node": ">=18" 243 | } 244 | }, 245 | "node_modules/@esbuild/linux-ppc64": { 246 | "version": "0.25.5", 247 | "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.5.tgz", 248 | "integrity": "sha512-UZCmJ7r9X2fe2D6jBmkLBMQetXPXIsZjQJCjgwpVDz+YMcS6oFR27alkgGv3Oqkv07bxdvw7fyB71/olceJhkQ==", 249 | "cpu": [ 250 | "ppc64" 251 | ], 252 | "dev": true, 253 | "license": "MIT", 254 | "optional": true, 255 | "os": [ 256 | "linux" 257 | ], 258 | "engines": { 259 | "node": ">=18" 260 | } 261 | }, 262 | "node_modules/@esbuild/linux-riscv64": { 263 | "version": "0.25.5", 264 | "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.5.tgz", 265 | "integrity": "sha512-kTxwu4mLyeOlsVIFPfQo+fQJAV9mh24xL+y+Bm6ej067sYANjyEw1dNHmvoqxJUCMnkBdKpvOn0Ahql6+4VyeA==", 266 | "cpu": [ 267 | "riscv64" 268 | ], 269 | "dev": true, 270 | "license": "MIT", 271 | "optional": true, 272 | "os": [ 273 | "linux" 274 | ], 275 | "engines": { 276 | "node": ">=18" 277 | } 278 | }, 279 | "node_modules/@esbuild/linux-s390x": { 280 | "version": "0.25.5", 281 | "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.5.tgz", 282 | "integrity": "sha512-K2dSKTKfmdh78uJ3NcWFiqyRrimfdinS5ErLSn3vluHNeHVnBAFWC8a4X5N+7FgVE1EjXS1QDZbpqZBjfrqMTQ==", 283 | "cpu": [ 284 | "s390x" 285 | ], 286 | "dev": true, 287 | "license": "MIT", 288 | "optional": true, 289 | "os": [ 290 | "linux" 291 | ], 292 | "engines": { 293 | "node": ">=18" 294 | } 295 | }, 296 | "node_modules/@esbuild/linux-x64": { 297 | "version": "0.25.5", 298 | "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.5.tgz", 299 | "integrity": "sha512-uhj8N2obKTE6pSZ+aMUbqq+1nXxNjZIIjCjGLfsWvVpy7gKCOL6rsY1MhRh9zLtUtAI7vpgLMK6DxjO8Qm9lJw==", 300 | "cpu": [ 301 | "x64" 302 | ], 303 | "dev": true, 304 | "license": "MIT", 305 | "optional": true, 306 | "os": [ 307 | "linux" 308 | ], 309 | "engines": { 310 | "node": ">=18" 311 | } 312 | }, 313 | "node_modules/@esbuild/netbsd-arm64": { 314 | "version": "0.25.5", 315 | "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.5.tgz", 316 | "integrity": "sha512-pwHtMP9viAy1oHPvgxtOv+OkduK5ugofNTVDilIzBLpoWAM16r7b/mxBvfpuQDpRQFMfuVr5aLcn4yveGvBZvw==", 317 | "cpu": [ 318 | "arm64" 319 | ], 320 | "dev": true, 321 | "license": "MIT", 322 | "optional": true, 323 | "os": [ 324 | "netbsd" 325 | ], 326 | "engines": { 327 | "node": ">=18" 328 | } 329 | }, 330 | "node_modules/@esbuild/netbsd-x64": { 331 | "version": "0.25.5", 332 | "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.5.tgz", 333 | "integrity": "sha512-WOb5fKrvVTRMfWFNCroYWWklbnXH0Q5rZppjq0vQIdlsQKuw6mdSihwSo4RV/YdQ5UCKKvBy7/0ZZYLBZKIbwQ==", 334 | "cpu": [ 335 | "x64" 336 | ], 337 | "dev": true, 338 | "license": "MIT", 339 | "optional": true, 340 | "os": [ 341 | "netbsd" 342 | ], 343 | "engines": { 344 | "node": ">=18" 345 | } 346 | }, 347 | "node_modules/@esbuild/openbsd-arm64": { 348 | "version": "0.25.5", 349 | "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.5.tgz", 350 | "integrity": "sha512-7A208+uQKgTxHd0G0uqZO8UjK2R0DDb4fDmERtARjSHWxqMTye4Erz4zZafx7Di9Cv+lNHYuncAkiGFySoD+Mw==", 351 | "cpu": [ 352 | "arm64" 353 | ], 354 | "dev": true, 355 | "license": "MIT", 356 | "optional": true, 357 | "os": [ 358 | "openbsd" 359 | ], 360 | "engines": { 361 | "node": ">=18" 362 | } 363 | }, 364 | "node_modules/@esbuild/openbsd-x64": { 365 | "version": "0.25.5", 366 | "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.5.tgz", 367 | "integrity": "sha512-G4hE405ErTWraiZ8UiSoesH8DaCsMm0Cay4fsFWOOUcz8b8rC6uCvnagr+gnioEjWn0wC+o1/TAHt+It+MpIMg==", 368 | "cpu": [ 369 | "x64" 370 | ], 371 | "dev": true, 372 | "license": "MIT", 373 | "optional": true, 374 | "os": [ 375 | "openbsd" 376 | ], 377 | "engines": { 378 | "node": ">=18" 379 | } 380 | }, 381 | "node_modules/@esbuild/sunos-x64": { 382 | "version": "0.25.5", 383 | "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.5.tgz", 384 | "integrity": "sha512-l+azKShMy7FxzY0Rj4RCt5VD/q8mG/e+mDivgspo+yL8zW7qEwctQ6YqKX34DTEleFAvCIUviCFX1SDZRSyMQA==", 385 | "cpu": [ 386 | "x64" 387 | ], 388 | "dev": true, 389 | "license": "MIT", 390 | "optional": true, 391 | "os": [ 392 | "sunos" 393 | ], 394 | "engines": { 395 | "node": ">=18" 396 | } 397 | }, 398 | "node_modules/@esbuild/win32-arm64": { 399 | "version": "0.25.5", 400 | "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.5.tgz", 401 | "integrity": "sha512-O2S7SNZzdcFG7eFKgvwUEZ2VG9D/sn/eIiz8XRZ1Q/DO5a3s76Xv0mdBzVM5j5R639lXQmPmSo0iRpHqUUrsxw==", 402 | "cpu": [ 403 | "arm64" 404 | ], 405 | "dev": true, 406 | "license": "MIT", 407 | "optional": true, 408 | "os": [ 409 | "win32" 410 | ], 411 | "engines": { 412 | "node": ">=18" 413 | } 414 | }, 415 | "node_modules/@esbuild/win32-ia32": { 416 | "version": "0.25.5", 417 | "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.5.tgz", 418 | "integrity": "sha512-onOJ02pqs9h1iMJ1PQphR+VZv8qBMQ77Klcsqv9CNW2w6yLqoURLcgERAIurY6QE63bbLuqgP9ATqajFLK5AMQ==", 419 | "cpu": [ 420 | "ia32" 421 | ], 422 | "dev": true, 423 | "license": "MIT", 424 | "optional": true, 425 | "os": [ 426 | "win32" 427 | ], 428 | "engines": { 429 | "node": ">=18" 430 | } 431 | }, 432 | "node_modules/@esbuild/win32-x64": { 433 | "version": "0.25.5", 434 | "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.5.tgz", 435 | "integrity": "sha512-TXv6YnJ8ZMVdX+SXWVBo/0p8LTcrUYngpWjvm91TMjjBQii7Oz11Lw5lbDV5Y0TzuhSJHwiH4hEtC1I42mMS0g==", 436 | "cpu": [ 437 | "x64" 438 | ], 439 | "dev": true, 440 | "license": "MIT", 441 | "optional": true, 442 | "os": [ 443 | "win32" 444 | ], 445 | "engines": { 446 | "node": ">=18" 447 | } 448 | }, 449 | "node_modules/@jridgewell/sourcemap-codec": { 450 | "version": "1.5.0", 451 | "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", 452 | "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", 453 | "dev": true, 454 | "license": "MIT" 455 | }, 456 | "node_modules/@rollup/rollup-android-arm-eabi": { 457 | "version": "4.43.0", 458 | "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.43.0.tgz", 459 | "integrity": "sha512-Krjy9awJl6rKbruhQDgivNbD1WuLb8xAclM4IR4cN5pHGAs2oIMMQJEiC3IC/9TZJ+QZkmZhlMO/6MBGxPidpw==", 460 | "cpu": [ 461 | "arm" 462 | ], 463 | "dev": true, 464 | "license": "MIT", 465 | "optional": true, 466 | "os": [ 467 | "android" 468 | ] 469 | }, 470 | "node_modules/@rollup/rollup-android-arm64": { 471 | "version": "4.43.0", 472 | "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.43.0.tgz", 473 | "integrity": "sha512-ss4YJwRt5I63454Rpj+mXCXicakdFmKnUNxr1dLK+5rv5FJgAxnN7s31a5VchRYxCFWdmnDWKd0wbAdTr0J5EA==", 474 | "cpu": [ 475 | "arm64" 476 | ], 477 | "dev": true, 478 | "license": "MIT", 479 | "optional": true, 480 | "os": [ 481 | "android" 482 | ] 483 | }, 484 | "node_modules/@rollup/rollup-darwin-arm64": { 485 | "version": "4.43.0", 486 | "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.43.0.tgz", 487 | "integrity": "sha512-eKoL8ykZ7zz8MjgBenEF2OoTNFAPFz1/lyJ5UmmFSz5jW+7XbH1+MAgCVHy72aG59rbuQLcJeiMrP8qP5d/N0A==", 488 | "cpu": [ 489 | "arm64" 490 | ], 491 | "dev": true, 492 | "license": "MIT", 493 | "optional": true, 494 | "os": [ 495 | "darwin" 496 | ] 497 | }, 498 | "node_modules/@rollup/rollup-darwin-x64": { 499 | "version": "4.43.0", 500 | "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.43.0.tgz", 501 | "integrity": "sha512-SYwXJgaBYW33Wi/q4ubN+ldWC4DzQY62S4Ll2dgfr/dbPoF50dlQwEaEHSKrQdSjC6oIe1WgzosoaNoHCdNuMg==", 502 | "cpu": [ 503 | "x64" 504 | ], 505 | "dev": true, 506 | "license": "MIT", 507 | "optional": true, 508 | "os": [ 509 | "darwin" 510 | ] 511 | }, 512 | "node_modules/@rollup/rollup-freebsd-arm64": { 513 | "version": "4.43.0", 514 | "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.43.0.tgz", 515 | "integrity": "sha512-SV+U5sSo0yujrjzBF7/YidieK2iF6E7MdF6EbYxNz94lA+R0wKl3SiixGyG/9Klab6uNBIqsN7j4Y/Fya7wAjQ==", 516 | "cpu": [ 517 | "arm64" 518 | ], 519 | "dev": true, 520 | "license": "MIT", 521 | "optional": true, 522 | "os": [ 523 | "freebsd" 524 | ] 525 | }, 526 | "node_modules/@rollup/rollup-freebsd-x64": { 527 | "version": "4.43.0", 528 | "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.43.0.tgz", 529 | "integrity": "sha512-J7uCsiV13L/VOeHJBo5SjasKiGxJ0g+nQTrBkAsmQBIdil3KhPnSE9GnRon4ejX1XDdsmK/l30IYLiAaQEO0Cg==", 530 | "cpu": [ 531 | "x64" 532 | ], 533 | "dev": true, 534 | "license": "MIT", 535 | "optional": true, 536 | "os": [ 537 | "freebsd" 538 | ] 539 | }, 540 | "node_modules/@rollup/rollup-linux-arm-gnueabihf": { 541 | "version": "4.43.0", 542 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.43.0.tgz", 543 | "integrity": "sha512-gTJ/JnnjCMc15uwB10TTATBEhK9meBIY+gXP4s0sHD1zHOaIh4Dmy1X9wup18IiY9tTNk5gJc4yx9ctj/fjrIw==", 544 | "cpu": [ 545 | "arm" 546 | ], 547 | "dev": true, 548 | "license": "MIT", 549 | "optional": true, 550 | "os": [ 551 | "linux" 552 | ] 553 | }, 554 | "node_modules/@rollup/rollup-linux-arm-musleabihf": { 555 | "version": "4.43.0", 556 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.43.0.tgz", 557 | "integrity": "sha512-ZJ3gZynL1LDSIvRfz0qXtTNs56n5DI2Mq+WACWZ7yGHFUEirHBRt7fyIk0NsCKhmRhn7WAcjgSkSVVxKlPNFFw==", 558 | "cpu": [ 559 | "arm" 560 | ], 561 | "dev": true, 562 | "license": "MIT", 563 | "optional": true, 564 | "os": [ 565 | "linux" 566 | ] 567 | }, 568 | "node_modules/@rollup/rollup-linux-arm64-gnu": { 569 | "version": "4.43.0", 570 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.43.0.tgz", 571 | "integrity": "sha512-8FnkipasmOOSSlfucGYEu58U8cxEdhziKjPD2FIa0ONVMxvl/hmONtX/7y4vGjdUhjcTHlKlDhw3H9t98fPvyA==", 572 | "cpu": [ 573 | "arm64" 574 | ], 575 | "dev": true, 576 | "license": "MIT", 577 | "optional": true, 578 | "os": [ 579 | "linux" 580 | ] 581 | }, 582 | "node_modules/@rollup/rollup-linux-arm64-musl": { 583 | "version": "4.43.0", 584 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.43.0.tgz", 585 | "integrity": "sha512-KPPyAdlcIZ6S9C3S2cndXDkV0Bb1OSMsX0Eelr2Bay4EsF9yi9u9uzc9RniK3mcUGCLhWY9oLr6er80P5DE6XA==", 586 | "cpu": [ 587 | "arm64" 588 | ], 589 | "dev": true, 590 | "license": "MIT", 591 | "optional": true, 592 | "os": [ 593 | "linux" 594 | ] 595 | }, 596 | "node_modules/@rollup/rollup-linux-loongarch64-gnu": { 597 | "version": "4.43.0", 598 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.43.0.tgz", 599 | "integrity": "sha512-HPGDIH0/ZzAZjvtlXj6g+KDQ9ZMHfSP553za7o2Odegb/BEfwJcR0Sw0RLNpQ9nC6Gy8s+3mSS9xjZ0n3rhcYg==", 600 | "cpu": [ 601 | "loong64" 602 | ], 603 | "dev": true, 604 | "license": "MIT", 605 | "optional": true, 606 | "os": [ 607 | "linux" 608 | ] 609 | }, 610 | "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { 611 | "version": "4.43.0", 612 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.43.0.tgz", 613 | "integrity": "sha512-gEmwbOws4U4GLAJDhhtSPWPXUzDfMRedT3hFMyRAvM9Mrnj+dJIFIeL7otsv2WF3D7GrV0GIewW0y28dOYWkmw==", 614 | "cpu": [ 615 | "ppc64" 616 | ], 617 | "dev": true, 618 | "license": "MIT", 619 | "optional": true, 620 | "os": [ 621 | "linux" 622 | ] 623 | }, 624 | "node_modules/@rollup/rollup-linux-riscv64-gnu": { 625 | "version": "4.43.0", 626 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.43.0.tgz", 627 | "integrity": "sha512-XXKvo2e+wFtXZF/9xoWohHg+MuRnvO29TI5Hqe9xwN5uN8NKUYy7tXUG3EZAlfchufNCTHNGjEx7uN78KsBo0g==", 628 | "cpu": [ 629 | "riscv64" 630 | ], 631 | "dev": true, 632 | "license": "MIT", 633 | "optional": true, 634 | "os": [ 635 | "linux" 636 | ] 637 | }, 638 | "node_modules/@rollup/rollup-linux-riscv64-musl": { 639 | "version": "4.43.0", 640 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.43.0.tgz", 641 | "integrity": "sha512-ruf3hPWhjw6uDFsOAzmbNIvlXFXlBQ4nk57Sec8E8rUxs/AI4HD6xmiiasOOx/3QxS2f5eQMKTAwk7KHwpzr/Q==", 642 | "cpu": [ 643 | "riscv64" 644 | ], 645 | "dev": true, 646 | "license": "MIT", 647 | "optional": true, 648 | "os": [ 649 | "linux" 650 | ] 651 | }, 652 | "node_modules/@rollup/rollup-linux-s390x-gnu": { 653 | "version": "4.43.0", 654 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.43.0.tgz", 655 | "integrity": "sha512-QmNIAqDiEMEvFV15rsSnjoSmO0+eJLoKRD9EAa9rrYNwO/XRCtOGM3A5A0X+wmG+XRrw9Fxdsw+LnyYiZWWcVw==", 656 | "cpu": [ 657 | "s390x" 658 | ], 659 | "dev": true, 660 | "license": "MIT", 661 | "optional": true, 662 | "os": [ 663 | "linux" 664 | ] 665 | }, 666 | "node_modules/@rollup/rollup-linux-x64-gnu": { 667 | "version": "4.43.0", 668 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.43.0.tgz", 669 | "integrity": "sha512-jAHr/S0iiBtFyzjhOkAics/2SrXE092qyqEg96e90L3t9Op8OTzS6+IX0Fy5wCt2+KqeHAkti+eitV0wvblEoQ==", 670 | "cpu": [ 671 | "x64" 672 | ], 673 | "dev": true, 674 | "license": "MIT", 675 | "optional": true, 676 | "os": [ 677 | "linux" 678 | ] 679 | }, 680 | "node_modules/@rollup/rollup-linux-x64-musl": { 681 | "version": "4.43.0", 682 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.43.0.tgz", 683 | "integrity": "sha512-3yATWgdeXyuHtBhrLt98w+5fKurdqvs8B53LaoKD7P7H7FKOONLsBVMNl9ghPQZQuYcceV5CDyPfyfGpMWD9mQ==", 684 | "cpu": [ 685 | "x64" 686 | ], 687 | "dev": true, 688 | "license": "MIT", 689 | "optional": true, 690 | "os": [ 691 | "linux" 692 | ] 693 | }, 694 | "node_modules/@rollup/rollup-win32-arm64-msvc": { 695 | "version": "4.43.0", 696 | "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.43.0.tgz", 697 | "integrity": "sha512-wVzXp2qDSCOpcBCT5WRWLmpJRIzv23valvcTwMHEobkjippNf+C3ys/+wf07poPkeNix0paTNemB2XrHr2TnGw==", 698 | "cpu": [ 699 | "arm64" 700 | ], 701 | "dev": true, 702 | "license": "MIT", 703 | "optional": true, 704 | "os": [ 705 | "win32" 706 | ] 707 | }, 708 | "node_modules/@rollup/rollup-win32-ia32-msvc": { 709 | "version": "4.43.0", 710 | "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.43.0.tgz", 711 | "integrity": "sha512-fYCTEyzf8d+7diCw8b+asvWDCLMjsCEA8alvtAutqJOJp/wL5hs1rWSqJ1vkjgW0L2NB4bsYJrpKkiIPRR9dvw==", 712 | "cpu": [ 713 | "ia32" 714 | ], 715 | "dev": true, 716 | "license": "MIT", 717 | "optional": true, 718 | "os": [ 719 | "win32" 720 | ] 721 | }, 722 | "node_modules/@rollup/rollup-win32-x64-msvc": { 723 | "version": "4.43.0", 724 | "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.43.0.tgz", 725 | "integrity": "sha512-SnGhLiE5rlK0ofq8kzuDkM0g7FN1s5VYY+YSMTibP7CqShxCQvqtNxTARS4xX4PFJfHjG0ZQYX9iGzI3FQh5Aw==", 726 | "cpu": [ 727 | "x64" 728 | ], 729 | "dev": true, 730 | "license": "MIT", 731 | "optional": true, 732 | "os": [ 733 | "win32" 734 | ] 735 | }, 736 | "node_modules/@types/chai": { 737 | "version": "5.2.2", 738 | "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.2.tgz", 739 | "integrity": "sha512-8kB30R7Hwqf40JPiKhVzodJs2Qc1ZJ5zuT3uzw5Hq/dhNCl3G3l83jfpdI1e20BP348+fV7VIL/+FxaXkqBmWg==", 740 | "dev": true, 741 | "license": "MIT", 742 | "dependencies": { 743 | "@types/deep-eql": "*" 744 | } 745 | }, 746 | "node_modules/@types/debug": { 747 | "version": "4.1.12", 748 | "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", 749 | "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==", 750 | "dev": true, 751 | "license": "MIT", 752 | "dependencies": { 753 | "@types/ms": "*" 754 | } 755 | }, 756 | "node_modules/@types/deep-eql": { 757 | "version": "4.0.2", 758 | "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", 759 | "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", 760 | "dev": true, 761 | "license": "MIT" 762 | }, 763 | "node_modules/@types/estree": { 764 | "version": "1.0.8", 765 | "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", 766 | "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", 767 | "dev": true, 768 | "license": "MIT" 769 | }, 770 | "node_modules/@types/ms": { 771 | "version": "2.1.0", 772 | "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", 773 | "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", 774 | "dev": true, 775 | "license": "MIT" 776 | }, 777 | "node_modules/@types/node": { 778 | "version": "24.0.3", 779 | "resolved": "https://registry.npmjs.org/@types/node/-/node-24.0.3.tgz", 780 | "integrity": "sha512-R4I/kzCYAdRLzfiCabn9hxWfbuHS573x+r0dJMkkzThEa7pbrcDWK+9zu3e7aBOouf+rQAciqPFMnxwr0aWgKg==", 781 | "dev": true, 782 | "license": "MIT", 783 | "dependencies": { 784 | "undici-types": "~7.8.0" 785 | } 786 | }, 787 | "node_modules/@vitest/expect": { 788 | "version": "3.2.3", 789 | "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.3.tgz", 790 | "integrity": "sha512-W2RH2TPWVHA1o7UmaFKISPvdicFJH+mjykctJFoAkUw+SPTJTGjUNdKscFBrqM7IPnCVu6zihtKYa7TkZS1dkQ==", 791 | "dev": true, 792 | "license": "MIT", 793 | "dependencies": { 794 | "@types/chai": "^5.2.2", 795 | "@vitest/spy": "3.2.3", 796 | "@vitest/utils": "3.2.3", 797 | "chai": "^5.2.0", 798 | "tinyrainbow": "^2.0.0" 799 | }, 800 | "funding": { 801 | "url": "https://opencollective.com/vitest" 802 | } 803 | }, 804 | "node_modules/@vitest/mocker": { 805 | "version": "3.2.3", 806 | "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.2.3.tgz", 807 | "integrity": "sha512-cP6fIun+Zx8he4rbWvi+Oya6goKQDZK+Yq4hhlggwQBbrlOQ4qtZ+G4nxB6ZnzI9lyIb+JnvyiJnPC2AGbKSPA==", 808 | "dev": true, 809 | "license": "MIT", 810 | "dependencies": { 811 | "@vitest/spy": "3.2.3", 812 | "estree-walker": "^3.0.3", 813 | "magic-string": "^0.30.17" 814 | }, 815 | "funding": { 816 | "url": "https://opencollective.com/vitest" 817 | }, 818 | "peerDependencies": { 819 | "msw": "^2.4.9", 820 | "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" 821 | }, 822 | "peerDependenciesMeta": { 823 | "msw": { 824 | "optional": true 825 | }, 826 | "vite": { 827 | "optional": true 828 | } 829 | } 830 | }, 831 | "node_modules/@vitest/pretty-format": { 832 | "version": "3.2.3", 833 | "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.2.3.tgz", 834 | "integrity": "sha512-yFglXGkr9hW/yEXngO+IKMhP0jxyFw2/qys/CK4fFUZnSltD+MU7dVYGrH8rvPcK/O6feXQA+EU33gjaBBbAng==", 835 | "dev": true, 836 | "license": "MIT", 837 | "dependencies": { 838 | "tinyrainbow": "^2.0.0" 839 | }, 840 | "funding": { 841 | "url": "https://opencollective.com/vitest" 842 | } 843 | }, 844 | "node_modules/@vitest/runner": { 845 | "version": "3.2.3", 846 | "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.2.3.tgz", 847 | "integrity": "sha512-83HWYisT3IpMaU9LN+VN+/nLHVBCSIUKJzGxC5RWUOsK1h3USg7ojL+UXQR3b4o4UBIWCYdD2fxuzM7PQQ1u8w==", 848 | "dev": true, 849 | "license": "MIT", 850 | "dependencies": { 851 | "@vitest/utils": "3.2.3", 852 | "pathe": "^2.0.3", 853 | "strip-literal": "^3.0.0" 854 | }, 855 | "funding": { 856 | "url": "https://opencollective.com/vitest" 857 | } 858 | }, 859 | "node_modules/@vitest/snapshot": { 860 | "version": "3.2.3", 861 | "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.2.3.tgz", 862 | "integrity": "sha512-9gIVWx2+tysDqUmmM1L0hwadyumqssOL1r8KJipwLx5JVYyxvVRfxvMq7DaWbZZsCqZnu/dZedaZQh4iYTtneA==", 863 | "dev": true, 864 | "license": "MIT", 865 | "dependencies": { 866 | "@vitest/pretty-format": "3.2.3", 867 | "magic-string": "^0.30.17", 868 | "pathe": "^2.0.3" 869 | }, 870 | "funding": { 871 | "url": "https://opencollective.com/vitest" 872 | } 873 | }, 874 | "node_modules/@vitest/spy": { 875 | "version": "3.2.3", 876 | "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.2.3.tgz", 877 | "integrity": "sha512-JHu9Wl+7bf6FEejTCREy+DmgWe+rQKbK+y32C/k5f4TBIAlijhJbRBIRIOCEpVevgRsCQR2iHRUH2/qKVM/plw==", 878 | "dev": true, 879 | "license": "MIT", 880 | "dependencies": { 881 | "tinyspy": "^4.0.3" 882 | }, 883 | "funding": { 884 | "url": "https://opencollective.com/vitest" 885 | } 886 | }, 887 | "node_modules/@vitest/utils": { 888 | "version": "3.2.3", 889 | "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.2.3.tgz", 890 | "integrity": "sha512-4zFBCU5Pf+4Z6v+rwnZ1HU1yzOKKvDkMXZrymE2PBlbjKJRlrOxbvpfPSvJTGRIwGoahaOGvp+kbCoxifhzJ1Q==", 891 | "dev": true, 892 | "license": "MIT", 893 | "dependencies": { 894 | "@vitest/pretty-format": "3.2.3", 895 | "loupe": "^3.1.3", 896 | "tinyrainbow": "^2.0.0" 897 | }, 898 | "funding": { 899 | "url": "https://opencollective.com/vitest" 900 | } 901 | }, 902 | "node_modules/assertion-error": { 903 | "version": "2.0.1", 904 | "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", 905 | "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", 906 | "dev": true, 907 | "license": "MIT", 908 | "engines": { 909 | "node": ">=12" 910 | } 911 | }, 912 | "node_modules/cac": { 913 | "version": "6.7.14", 914 | "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", 915 | "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", 916 | "dev": true, 917 | "license": "MIT", 918 | "engines": { 919 | "node": ">=8" 920 | } 921 | }, 922 | "node_modules/chai": { 923 | "version": "5.2.0", 924 | "resolved": "https://registry.npmjs.org/chai/-/chai-5.2.0.tgz", 925 | "integrity": "sha512-mCuXncKXk5iCLhfhwTc0izo0gtEmpz5CtG2y8GiOINBlMVS6v8TMRc5TaLWKS6692m9+dVVfzgeVxR5UxWHTYw==", 926 | "dev": true, 927 | "license": "MIT", 928 | "dependencies": { 929 | "assertion-error": "^2.0.1", 930 | "check-error": "^2.1.1", 931 | "deep-eql": "^5.0.1", 932 | "loupe": "^3.1.0", 933 | "pathval": "^2.0.0" 934 | }, 935 | "engines": { 936 | "node": ">=12" 937 | } 938 | }, 939 | "node_modules/check-error": { 940 | "version": "2.1.1", 941 | "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz", 942 | "integrity": "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==", 943 | "dev": true, 944 | "license": "MIT", 945 | "engines": { 946 | "node": ">= 16" 947 | } 948 | }, 949 | "node_modules/debug": { 950 | "version": "4.4.1", 951 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", 952 | "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", 953 | "license": "MIT", 954 | "dependencies": { 955 | "ms": "^2.1.3" 956 | }, 957 | "engines": { 958 | "node": ">=6.0" 959 | }, 960 | "peerDependenciesMeta": { 961 | "supports-color": { 962 | "optional": true 963 | } 964 | } 965 | }, 966 | "node_modules/deep-eql": { 967 | "version": "5.0.2", 968 | "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", 969 | "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", 970 | "dev": true, 971 | "license": "MIT", 972 | "engines": { 973 | "node": ">=6" 974 | } 975 | }, 976 | "node_modules/es-module-lexer": { 977 | "version": "1.7.0", 978 | "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", 979 | "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", 980 | "dev": true, 981 | "license": "MIT" 982 | }, 983 | "node_modules/esbuild": { 984 | "version": "0.25.5", 985 | "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.5.tgz", 986 | "integrity": "sha512-P8OtKZRv/5J5hhz0cUAdu/cLuPIKXpQl1R9pZtvmHWQvrAUVd0UNIPT4IB4W3rNOqVO0rlqHmCIbSwxh/c9yUQ==", 987 | "dev": true, 988 | "hasInstallScript": true, 989 | "license": "MIT", 990 | "bin": { 991 | "esbuild": "bin/esbuild" 992 | }, 993 | "engines": { 994 | "node": ">=18" 995 | }, 996 | "optionalDependencies": { 997 | "@esbuild/aix-ppc64": "0.25.5", 998 | "@esbuild/android-arm": "0.25.5", 999 | "@esbuild/android-arm64": "0.25.5", 1000 | "@esbuild/android-x64": "0.25.5", 1001 | "@esbuild/darwin-arm64": "0.25.5", 1002 | "@esbuild/darwin-x64": "0.25.5", 1003 | "@esbuild/freebsd-arm64": "0.25.5", 1004 | "@esbuild/freebsd-x64": "0.25.5", 1005 | "@esbuild/linux-arm": "0.25.5", 1006 | "@esbuild/linux-arm64": "0.25.5", 1007 | "@esbuild/linux-ia32": "0.25.5", 1008 | "@esbuild/linux-loong64": "0.25.5", 1009 | "@esbuild/linux-mips64el": "0.25.5", 1010 | "@esbuild/linux-ppc64": "0.25.5", 1011 | "@esbuild/linux-riscv64": "0.25.5", 1012 | "@esbuild/linux-s390x": "0.25.5", 1013 | "@esbuild/linux-x64": "0.25.5", 1014 | "@esbuild/netbsd-arm64": "0.25.5", 1015 | "@esbuild/netbsd-x64": "0.25.5", 1016 | "@esbuild/openbsd-arm64": "0.25.5", 1017 | "@esbuild/openbsd-x64": "0.25.5", 1018 | "@esbuild/sunos-x64": "0.25.5", 1019 | "@esbuild/win32-arm64": "0.25.5", 1020 | "@esbuild/win32-ia32": "0.25.5", 1021 | "@esbuild/win32-x64": "0.25.5" 1022 | } 1023 | }, 1024 | "node_modules/estree-walker": { 1025 | "version": "3.0.3", 1026 | "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", 1027 | "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", 1028 | "dev": true, 1029 | "license": "MIT", 1030 | "dependencies": { 1031 | "@types/estree": "^1.0.0" 1032 | } 1033 | }, 1034 | "node_modules/expect-type": { 1035 | "version": "1.2.1", 1036 | "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.2.1.tgz", 1037 | "integrity": "sha512-/kP8CAwxzLVEeFrMm4kMmy4CCDlpipyA7MYLVrdJIkV0fYF0UaigQHRsxHiuY/GEea+bh4KSv3TIlgr+2UL6bw==", 1038 | "dev": true, 1039 | "license": "Apache-2.0", 1040 | "engines": { 1041 | "node": ">=12.0.0" 1042 | } 1043 | }, 1044 | "node_modules/fdir": { 1045 | "version": "6.4.6", 1046 | "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.6.tgz", 1047 | "integrity": "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==", 1048 | "dev": true, 1049 | "license": "MIT", 1050 | "peerDependencies": { 1051 | "picomatch": "^3 || ^4" 1052 | }, 1053 | "peerDependenciesMeta": { 1054 | "picomatch": { 1055 | "optional": true 1056 | } 1057 | } 1058 | }, 1059 | "node_modules/fsevents": { 1060 | "version": "2.3.3", 1061 | "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", 1062 | "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", 1063 | "dev": true, 1064 | "hasInstallScript": true, 1065 | "license": "MIT", 1066 | "optional": true, 1067 | "os": [ 1068 | "darwin" 1069 | ], 1070 | "engines": { 1071 | "node": "^8.16.0 || ^10.6.0 || >=11.0.0" 1072 | } 1073 | }, 1074 | "node_modules/js-tokens": { 1075 | "version": "9.0.1", 1076 | "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz", 1077 | "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==", 1078 | "dev": true, 1079 | "license": "MIT" 1080 | }, 1081 | "node_modules/loupe": { 1082 | "version": "3.1.3", 1083 | "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.3.tgz", 1084 | "integrity": "sha512-kkIp7XSkP78ZxJEsSxW3712C6teJVoeHHwgo9zJ380de7IYyJ2ISlxojcH2pC5OFLewESmnRi/+XCDIEEVyoug==", 1085 | "dev": true, 1086 | "license": "MIT" 1087 | }, 1088 | "node_modules/magic-string": { 1089 | "version": "0.30.17", 1090 | "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", 1091 | "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", 1092 | "dev": true, 1093 | "license": "MIT", 1094 | "dependencies": { 1095 | "@jridgewell/sourcemap-codec": "^1.5.0" 1096 | } 1097 | }, 1098 | "node_modules/ms": { 1099 | "version": "2.1.3", 1100 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", 1101 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", 1102 | "license": "MIT" 1103 | }, 1104 | "node_modules/nanoid": { 1105 | "version": "3.3.11", 1106 | "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", 1107 | "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", 1108 | "dev": true, 1109 | "funding": [ 1110 | { 1111 | "type": "github", 1112 | "url": "https://github.com/sponsors/ai" 1113 | } 1114 | ], 1115 | "license": "MIT", 1116 | "bin": { 1117 | "nanoid": "bin/nanoid.cjs" 1118 | }, 1119 | "engines": { 1120 | "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" 1121 | } 1122 | }, 1123 | "node_modules/pathe": { 1124 | "version": "2.0.3", 1125 | "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", 1126 | "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", 1127 | "dev": true, 1128 | "license": "MIT" 1129 | }, 1130 | "node_modules/pathval": { 1131 | "version": "2.0.0", 1132 | "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.0.tgz", 1133 | "integrity": "sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==", 1134 | "dev": true, 1135 | "license": "MIT", 1136 | "engines": { 1137 | "node": ">= 14.16" 1138 | } 1139 | }, 1140 | "node_modules/picocolors": { 1141 | "version": "1.1.1", 1142 | "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", 1143 | "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", 1144 | "dev": true, 1145 | "license": "ISC" 1146 | }, 1147 | "node_modules/picomatch": { 1148 | "version": "4.0.2", 1149 | "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", 1150 | "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", 1151 | "dev": true, 1152 | "license": "MIT", 1153 | "engines": { 1154 | "node": ">=12" 1155 | }, 1156 | "funding": { 1157 | "url": "https://github.com/sponsors/jonschlinkert" 1158 | } 1159 | }, 1160 | "node_modules/postcss": { 1161 | "version": "8.5.5", 1162 | "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.5.tgz", 1163 | "integrity": "sha512-d/jtm+rdNT8tpXuHY5MMtcbJFBkhXE6593XVR9UoGCH8jSFGci7jGvMGH5RYd5PBJW+00NZQt6gf7CbagJCrhg==", 1164 | "dev": true, 1165 | "funding": [ 1166 | { 1167 | "type": "opencollective", 1168 | "url": "https://opencollective.com/postcss/" 1169 | }, 1170 | { 1171 | "type": "tidelift", 1172 | "url": "https://tidelift.com/funding/github/npm/postcss" 1173 | }, 1174 | { 1175 | "type": "github", 1176 | "url": "https://github.com/sponsors/ai" 1177 | } 1178 | ], 1179 | "license": "MIT", 1180 | "dependencies": { 1181 | "nanoid": "^3.3.11", 1182 | "picocolors": "^1.1.1", 1183 | "source-map-js": "^1.2.1" 1184 | }, 1185 | "engines": { 1186 | "node": "^10 || ^12 || >=14" 1187 | } 1188 | }, 1189 | "node_modules/rollup": { 1190 | "version": "4.43.0", 1191 | "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.43.0.tgz", 1192 | "integrity": "sha512-wdN2Kd3Twh8MAEOEJZsuxuLKCsBEo4PVNLK6tQWAn10VhsVewQLzcucMgLolRlhFybGxfclbPeEYBaP6RvUFGg==", 1193 | "dev": true, 1194 | "license": "MIT", 1195 | "dependencies": { 1196 | "@types/estree": "1.0.7" 1197 | }, 1198 | "bin": { 1199 | "rollup": "dist/bin/rollup" 1200 | }, 1201 | "engines": { 1202 | "node": ">=18.0.0", 1203 | "npm": ">=8.0.0" 1204 | }, 1205 | "optionalDependencies": { 1206 | "@rollup/rollup-android-arm-eabi": "4.43.0", 1207 | "@rollup/rollup-android-arm64": "4.43.0", 1208 | "@rollup/rollup-darwin-arm64": "4.43.0", 1209 | "@rollup/rollup-darwin-x64": "4.43.0", 1210 | "@rollup/rollup-freebsd-arm64": "4.43.0", 1211 | "@rollup/rollup-freebsd-x64": "4.43.0", 1212 | "@rollup/rollup-linux-arm-gnueabihf": "4.43.0", 1213 | "@rollup/rollup-linux-arm-musleabihf": "4.43.0", 1214 | "@rollup/rollup-linux-arm64-gnu": "4.43.0", 1215 | "@rollup/rollup-linux-arm64-musl": "4.43.0", 1216 | "@rollup/rollup-linux-loongarch64-gnu": "4.43.0", 1217 | "@rollup/rollup-linux-powerpc64le-gnu": "4.43.0", 1218 | "@rollup/rollup-linux-riscv64-gnu": "4.43.0", 1219 | "@rollup/rollup-linux-riscv64-musl": "4.43.0", 1220 | "@rollup/rollup-linux-s390x-gnu": "4.43.0", 1221 | "@rollup/rollup-linux-x64-gnu": "4.43.0", 1222 | "@rollup/rollup-linux-x64-musl": "4.43.0", 1223 | "@rollup/rollup-win32-arm64-msvc": "4.43.0", 1224 | "@rollup/rollup-win32-ia32-msvc": "4.43.0", 1225 | "@rollup/rollup-win32-x64-msvc": "4.43.0", 1226 | "fsevents": "~2.3.2" 1227 | } 1228 | }, 1229 | "node_modules/rollup/node_modules/@types/estree": { 1230 | "version": "1.0.7", 1231 | "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz", 1232 | "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==", 1233 | "dev": true, 1234 | "license": "MIT" 1235 | }, 1236 | "node_modules/siginfo": { 1237 | "version": "2.0.0", 1238 | "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", 1239 | "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", 1240 | "dev": true, 1241 | "license": "ISC" 1242 | }, 1243 | "node_modules/source-map-js": { 1244 | "version": "1.2.1", 1245 | "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", 1246 | "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", 1247 | "dev": true, 1248 | "license": "BSD-3-Clause", 1249 | "engines": { 1250 | "node": ">=0.10.0" 1251 | } 1252 | }, 1253 | "node_modules/stackback": { 1254 | "version": "0.0.2", 1255 | "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", 1256 | "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", 1257 | "dev": true, 1258 | "license": "MIT" 1259 | }, 1260 | "node_modules/std-env": { 1261 | "version": "3.9.0", 1262 | "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.9.0.tgz", 1263 | "integrity": "sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==", 1264 | "dev": true, 1265 | "license": "MIT" 1266 | }, 1267 | "node_modules/strip-literal": { 1268 | "version": "3.0.0", 1269 | "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-3.0.0.tgz", 1270 | "integrity": "sha512-TcccoMhJOM3OebGhSBEmp3UZ2SfDMZUEBdRA/9ynfLi8yYajyWX3JiXArcJt4Umh4vISpspkQIY8ZZoCqjbviA==", 1271 | "dev": true, 1272 | "license": "MIT", 1273 | "dependencies": { 1274 | "js-tokens": "^9.0.1" 1275 | }, 1276 | "funding": { 1277 | "url": "https://github.com/sponsors/antfu" 1278 | } 1279 | }, 1280 | "node_modules/tinybench": { 1281 | "version": "2.9.0", 1282 | "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", 1283 | "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", 1284 | "dev": true, 1285 | "license": "MIT" 1286 | }, 1287 | "node_modules/tinyexec": { 1288 | "version": "0.3.2", 1289 | "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", 1290 | "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", 1291 | "dev": true, 1292 | "license": "MIT" 1293 | }, 1294 | "node_modules/tinyglobby": { 1295 | "version": "0.2.14", 1296 | "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz", 1297 | "integrity": "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==", 1298 | "dev": true, 1299 | "license": "MIT", 1300 | "dependencies": { 1301 | "fdir": "^6.4.4", 1302 | "picomatch": "^4.0.2" 1303 | }, 1304 | "engines": { 1305 | "node": ">=12.0.0" 1306 | }, 1307 | "funding": { 1308 | "url": "https://github.com/sponsors/SuperchupuDev" 1309 | } 1310 | }, 1311 | "node_modules/tinypool": { 1312 | "version": "1.1.0", 1313 | "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.1.0.tgz", 1314 | "integrity": "sha512-7CotroY9a8DKsKprEy/a14aCCm8jYVmR7aFy4fpkZM8sdpNJbKkixuNjgM50yCmip2ezc8z4N7k3oe2+rfRJCQ==", 1315 | "dev": true, 1316 | "license": "MIT", 1317 | "engines": { 1318 | "node": "^18.0.0 || >=20.0.0" 1319 | } 1320 | }, 1321 | "node_modules/tinyrainbow": { 1322 | "version": "2.0.0", 1323 | "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-2.0.0.tgz", 1324 | "integrity": "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==", 1325 | "dev": true, 1326 | "license": "MIT", 1327 | "engines": { 1328 | "node": ">=14.0.0" 1329 | } 1330 | }, 1331 | "node_modules/tinyspy": { 1332 | "version": "4.0.3", 1333 | "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-4.0.3.tgz", 1334 | "integrity": "sha512-t2T/WLB2WRgZ9EpE4jgPJ9w+i66UZfDc8wHh0xrwiRNN+UwH98GIJkTeZqX9rg0i0ptwzqW+uYeIF0T4F8LR7A==", 1335 | "dev": true, 1336 | "license": "MIT", 1337 | "engines": { 1338 | "node": ">=14.0.0" 1339 | } 1340 | }, 1341 | "node_modules/typescript": { 1342 | "version": "5.8.3", 1343 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", 1344 | "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", 1345 | "dev": true, 1346 | "license": "Apache-2.0", 1347 | "bin": { 1348 | "tsc": "bin/tsc", 1349 | "tsserver": "bin/tsserver" 1350 | }, 1351 | "engines": { 1352 | "node": ">=14.17" 1353 | } 1354 | }, 1355 | "node_modules/undici-types": { 1356 | "version": "7.8.0", 1357 | "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.8.0.tgz", 1358 | "integrity": "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==", 1359 | "dev": true, 1360 | "license": "MIT" 1361 | }, 1362 | "node_modules/vite": { 1363 | "version": "6.3.5", 1364 | "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.5.tgz", 1365 | "integrity": "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==", 1366 | "dev": true, 1367 | "license": "MIT", 1368 | "dependencies": { 1369 | "esbuild": "^0.25.0", 1370 | "fdir": "^6.4.4", 1371 | "picomatch": "^4.0.2", 1372 | "postcss": "^8.5.3", 1373 | "rollup": "^4.34.9", 1374 | "tinyglobby": "^0.2.13" 1375 | }, 1376 | "bin": { 1377 | "vite": "bin/vite.js" 1378 | }, 1379 | "engines": { 1380 | "node": "^18.0.0 || ^20.0.0 || >=22.0.0" 1381 | }, 1382 | "funding": { 1383 | "url": "https://github.com/vitejs/vite?sponsor=1" 1384 | }, 1385 | "optionalDependencies": { 1386 | "fsevents": "~2.3.3" 1387 | }, 1388 | "peerDependencies": { 1389 | "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", 1390 | "jiti": ">=1.21.0", 1391 | "less": "*", 1392 | "lightningcss": "^1.21.0", 1393 | "sass": "*", 1394 | "sass-embedded": "*", 1395 | "stylus": "*", 1396 | "sugarss": "*", 1397 | "terser": "^5.16.0", 1398 | "tsx": "^4.8.1", 1399 | "yaml": "^2.4.2" 1400 | }, 1401 | "peerDependenciesMeta": { 1402 | "@types/node": { 1403 | "optional": true 1404 | }, 1405 | "jiti": { 1406 | "optional": true 1407 | }, 1408 | "less": { 1409 | "optional": true 1410 | }, 1411 | "lightningcss": { 1412 | "optional": true 1413 | }, 1414 | "sass": { 1415 | "optional": true 1416 | }, 1417 | "sass-embedded": { 1418 | "optional": true 1419 | }, 1420 | "stylus": { 1421 | "optional": true 1422 | }, 1423 | "sugarss": { 1424 | "optional": true 1425 | }, 1426 | "terser": { 1427 | "optional": true 1428 | }, 1429 | "tsx": { 1430 | "optional": true 1431 | }, 1432 | "yaml": { 1433 | "optional": true 1434 | } 1435 | } 1436 | }, 1437 | "node_modules/vite-node": { 1438 | "version": "3.2.3", 1439 | "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.2.3.tgz", 1440 | "integrity": "sha512-gc8aAifGuDIpZHrPjuHyP4dpQmYXqWw7D1GmDnWeNWP654UEXzVfQ5IHPSK5HaHkwB/+p1atpYpSdw/2kOv8iQ==", 1441 | "dev": true, 1442 | "license": "MIT", 1443 | "dependencies": { 1444 | "cac": "^6.7.14", 1445 | "debug": "^4.4.1", 1446 | "es-module-lexer": "^1.7.0", 1447 | "pathe": "^2.0.3", 1448 | "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" 1449 | }, 1450 | "bin": { 1451 | "vite-node": "vite-node.mjs" 1452 | }, 1453 | "engines": { 1454 | "node": "^18.0.0 || ^20.0.0 || >=22.0.0" 1455 | }, 1456 | "funding": { 1457 | "url": "https://opencollective.com/vitest" 1458 | } 1459 | }, 1460 | "node_modules/vitest": { 1461 | "version": "3.2.3", 1462 | "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.2.3.tgz", 1463 | "integrity": "sha512-E6U2ZFXe3N/t4f5BwUaVCKRLHqUpk1CBWeMh78UT4VaTPH/2dyvH6ALl29JTovEPu9dVKr/K/J4PkXgrMbw4Ww==", 1464 | "dev": true, 1465 | "license": "MIT", 1466 | "dependencies": { 1467 | "@types/chai": "^5.2.2", 1468 | "@vitest/expect": "3.2.3", 1469 | "@vitest/mocker": "3.2.3", 1470 | "@vitest/pretty-format": "^3.2.3", 1471 | "@vitest/runner": "3.2.3", 1472 | "@vitest/snapshot": "3.2.3", 1473 | "@vitest/spy": "3.2.3", 1474 | "@vitest/utils": "3.2.3", 1475 | "chai": "^5.2.0", 1476 | "debug": "^4.4.1", 1477 | "expect-type": "^1.2.1", 1478 | "magic-string": "^0.30.17", 1479 | "pathe": "^2.0.3", 1480 | "picomatch": "^4.0.2", 1481 | "std-env": "^3.9.0", 1482 | "tinybench": "^2.9.0", 1483 | "tinyexec": "^0.3.2", 1484 | "tinyglobby": "^0.2.14", 1485 | "tinypool": "^1.1.0", 1486 | "tinyrainbow": "^2.0.0", 1487 | "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0", 1488 | "vite-node": "3.2.3", 1489 | "why-is-node-running": "^2.3.0" 1490 | }, 1491 | "bin": { 1492 | "vitest": "vitest.mjs" 1493 | }, 1494 | "engines": { 1495 | "node": "^18.0.0 || ^20.0.0 || >=22.0.0" 1496 | }, 1497 | "funding": { 1498 | "url": "https://opencollective.com/vitest" 1499 | }, 1500 | "peerDependencies": { 1501 | "@edge-runtime/vm": "*", 1502 | "@types/debug": "^4.1.12", 1503 | "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", 1504 | "@vitest/browser": "3.2.3", 1505 | "@vitest/ui": "3.2.3", 1506 | "happy-dom": "*", 1507 | "jsdom": "*" 1508 | }, 1509 | "peerDependenciesMeta": { 1510 | "@edge-runtime/vm": { 1511 | "optional": true 1512 | }, 1513 | "@types/debug": { 1514 | "optional": true 1515 | }, 1516 | "@types/node": { 1517 | "optional": true 1518 | }, 1519 | "@vitest/browser": { 1520 | "optional": true 1521 | }, 1522 | "@vitest/ui": { 1523 | "optional": true 1524 | }, 1525 | "happy-dom": { 1526 | "optional": true 1527 | }, 1528 | "jsdom": { 1529 | "optional": true 1530 | } 1531 | } 1532 | }, 1533 | "node_modules/why-is-node-running": { 1534 | "version": "2.3.0", 1535 | "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", 1536 | "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", 1537 | "dev": true, 1538 | "license": "MIT", 1539 | "dependencies": { 1540 | "siginfo": "^2.0.0", 1541 | "stackback": "0.0.2" 1542 | }, 1543 | "bin": { 1544 | "why-is-node-running": "cli.js" 1545 | }, 1546 | "engines": { 1547 | "node": ">=8" 1548 | } 1549 | } 1550 | } 1551 | } 1552 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@minescope/mineping", 3 | "version": "2.0.0", 4 | "description": "Ping both Minecraft Bedrock and Java servers.", 5 | "main": "index.js", 6 | "type": "module", 7 | "types": "types/index.d.ts", 8 | "scripts": { 9 | "test": "vitest run", 10 | "test:watch": "vitest", 11 | "types:check": "tsc --noEmit", 12 | "types:build": "tsc" 13 | }, 14 | "repository": { 15 | "type": "git", 16 | "url": "git://github.com/minescope/mineping.git" 17 | }, 18 | "license": "MIT", 19 | "keywords": [ 20 | "minecraft", 21 | "raknet", 22 | "node", 23 | "mcpe", 24 | "mcbe", 25 | "ping", 26 | "bedrock" 27 | ], 28 | "author": { 29 | "name": "Timofey Gelazoniya", 30 | "email": "timofey@z4n.me", 31 | "url": "https://zeldon.ru" 32 | }, 33 | "engines": { 34 | "node": ">=14" 35 | }, 36 | "dependencies": { 37 | "debug": "^4.4.1" 38 | }, 39 | "devDependencies": { 40 | "@types/debug": "^4.1.12", 41 | "@types/node": "^24.0.3", 42 | "typescript": "^5.8.3", 43 | "vitest": "^3.2.3" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /test/bedrock.test.js: -------------------------------------------------------------------------------- 1 | import dgram from "node:dgram"; 2 | import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; 3 | import { pingBedrock } from "../lib/bedrock.js"; 4 | 5 | vi.mock("node:dgram"); 6 | 7 | describe("bedrock.js", () => { 8 | let mockSocket; 9 | 10 | beforeEach(() => { 11 | // A store for event handlers, closed over by the mockSocket. 12 | const handlers = {}; 13 | 14 | // Create a stateful mock socket to simulate EventEmitter. 15 | mockSocket = { 16 | send: vi.fn(), 17 | close: vi.fn(), 18 | on: vi.fn((event, handler) => { 19 | handlers[event] = handler; 20 | }), 21 | emit: vi.fn((event, ...args) => { 22 | if (handlers[event]) { 23 | handlers[event](...args); 24 | } 25 | }), 26 | }; 27 | 28 | dgram.createSocket = vi.fn().mockReturnValue(mockSocket); 29 | vi.useFakeTimers(); 30 | }); 31 | 32 | afterEach(() => { 33 | vi.restoreAllMocks(); 34 | vi.useRealTimers(); 35 | }); 36 | 37 | it("should ping a 3rd party server and parse MOTD", async () => { 38 | const host = "play.example.com"; 39 | const options = { port: 25565, timeout: 10000 }; 40 | const pingPromise = pingBedrock(host, options); 41 | 42 | const motd = 43 | "MCPE;§l§bOasys§fPE  §eГриф§7, §cДуэли§7, §aКейсы;0;1337;1070;1999;-138584171542148188;oasys-pe.ru;Adventure;1"; 44 | const mockPongPacket = createMockPongPacket(motd); 45 | 46 | mockSocket.emit("message", mockPongPacket); 47 | 48 | const result = await pingPromise; 49 | 50 | expect(dgram.createSocket).toHaveBeenCalledWith("udp4"); 51 | expect(mockSocket.send).toHaveBeenCalledWith( 52 | expect.any(Buffer), 53 | 0, 54 | 33, 55 | options.port, 56 | host 57 | ); 58 | expect(mockSocket.close).toHaveBeenCalled(); 59 | expect(result).toEqual({ 60 | edition: "MCPE", 61 | name: "§l§bOasys§fPE  §eГриф§7, §cДуэли§7, §aКейсы", 62 | levelName: "oasys-pe.ru", 63 | gamemode: "Adventure", 64 | version: { 65 | protocol: 0, 66 | minecraft: "1337", 67 | }, 68 | players: { 69 | online: 1070, 70 | max: 1999, 71 | }, 72 | port: { 73 | v4: undefined, 74 | v6: undefined, 75 | }, 76 | guid: -138584171542148188n, 77 | isNintendoLimited: false, 78 | isEditorModeEnabled: undefined, 79 | }); 80 | }); 81 | 82 | it("should ping a BDS server with default `server.properties` and parse MOTD", async () => { 83 | const host = "play.example.com"; 84 | const options = { port: 25565, timeout: 10000 }; 85 | const pingPromise = pingBedrock(host, options); 86 | 87 | const motd = 88 | "MCPE;Dedicated Server;800;1.21.84;0;10;11546321190880321782;Bedrock level;Survival;1;19132;19133;0;"; 89 | const mockPongPacket = createMockPongPacket(motd); 90 | 91 | mockSocket.emit("message", mockPongPacket); 92 | 93 | const result = await pingPromise; 94 | 95 | expect(dgram.createSocket).toHaveBeenCalledWith("udp4"); 96 | expect(mockSocket.send).toHaveBeenCalledWith( 97 | expect.any(Buffer), 98 | 0, 99 | 33, 100 | options.port, 101 | host 102 | ); 103 | expect(mockSocket.close).toHaveBeenCalled(); 104 | expect(result).toEqual({ 105 | edition: "MCPE", 106 | name: "Dedicated Server", 107 | levelName: "Bedrock level", 108 | gamemode: "Survival", 109 | version: { 110 | protocol: 800, 111 | minecraft: "1.21.84", 112 | }, 113 | players: { 114 | online: 0, 115 | max: 10, 116 | }, 117 | port: { 118 | v4: 19132, 119 | v6: 19133, 120 | }, 121 | guid: 11546321190880321782n, 122 | isNintendoLimited: false, 123 | isEditorModeEnabled: false, 124 | }); 125 | }); 126 | 127 | describe("errors", () => { 128 | it("should throw an error if host is not provided", async () => { 129 | await expect(pingBedrock(null)).rejects.toThrow( 130 | "Host argument is required" 131 | ); 132 | }); 133 | 134 | it("should reject on socket timeout", async () => { 135 | const pingPromise = pingBedrock("play.example.com", { timeout: 1000 }); 136 | 137 | vi.advanceTimersByTime(1000); 138 | 139 | await expect(pingPromise).rejects.toThrow("Socket timeout"); 140 | expect(mockSocket.close).toHaveBeenCalled(); 141 | }); 142 | 143 | it("should reject on a generic socket error", async () => { 144 | const pingPromise = pingBedrock("play.example.com"); 145 | 146 | // Simulate a DNS or network error by emitting it. 147 | mockSocket.emit("error", new Error("EHOSTUNREACH")); 148 | 149 | await expect(pingPromise).rejects.toThrow("EHOSTUNREACH"); 150 | }); 151 | 152 | it("should only reject once, even if multiple errors occur", async () => { 153 | const pingPromise = pingBedrock("play.example.com"); 154 | 155 | // Fire a socket error first. 156 | mockSocket.emit("error", new Error("First error")); 157 | 158 | // Then, try to trigger another error by sending a bad message. 159 | mockSocket.emit("message", Buffer.alloc(0)); 160 | 161 | await expect(pingPromise).rejects.toThrow("First error"); 162 | expect(mockSocket.close).toHaveBeenCalledTimes(1); 163 | }); 164 | }); 165 | }); 166 | 167 | function createMockPongPacket(motd) { 168 | const motdBuffer = Buffer.from(motd, "utf-8"); 169 | const packet = Buffer.alloc(35 + motdBuffer.length); 170 | packet.writeUInt8(0x1c, 0); 171 | packet.writeBigInt64LE(BigInt(Date.now()), 1); 172 | Buffer.from("00ffff00fefefefefdfdfdfd12345678", "hex").copy(packet, 9); 173 | packet.writeUInt16BE(motdBuffer.length, 33); 174 | motdBuffer.copy(packet, 35); 175 | return packet; 176 | } 177 | -------------------------------------------------------------------------------- /test/java.test.js: -------------------------------------------------------------------------------- 1 | import net from "node:net"; 2 | import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; 3 | import { pingJava } from "../lib/java.js"; 4 | import * as varint from "../lib/varint.js"; 5 | 6 | const mockResolveSrv = vi.fn(); 7 | 8 | vi.mock("node:net"); 9 | vi.mock("node:dns/promises", () => ({ 10 | Resolver: vi.fn().mockImplementation(() => ({ 11 | resolveSrv: mockResolveSrv, 12 | })), 13 | })); 14 | 15 | describe("pingJava", () => { 16 | let mockSocket; 17 | 18 | beforeEach(() => { 19 | // Reset mocks before each test. 20 | mockResolveSrv.mockClear(); 21 | // Simulate no SRV record found by default. 22 | mockResolveSrv.mockResolvedValue([]); 23 | 24 | const mockHandlers = {}; 25 | mockSocket = { 26 | write: vi.fn(), 27 | // Make `destroy` emit 'error' if an error is passed. 28 | destroy: vi.fn((err) => { 29 | if (err) { 30 | mockSocket.emit("error", err); 31 | } 32 | }), 33 | setNoDelay: vi.fn(), 34 | on: vi.fn((event, handler) => (mockHandlers[event] = handler)), 35 | emit: vi.fn((event, ...args) => mockHandlers[event]?.(...args)), 36 | }; 37 | net.createConnection.mockReturnValue(mockSocket); 38 | vi.useFakeTimers(); 39 | }); 40 | 41 | afterEach(() => { 42 | vi.restoreAllMocks(); 43 | vi.useRealTimers(); 44 | }); 45 | 46 | it("should ping a server and handle a chunked response", async () => { 47 | const host = "mc.hypixel.net"; 48 | const options = { port: 25565 }; 49 | const pingPromise = pingJava(host, options); 50 | 51 | // Allow the async SRV lookup to complete 52 | await vi.runAllTicks(); 53 | 54 | expect(net.createConnection).toHaveBeenCalledWith({ 55 | host: host, 56 | port: options.port, 57 | }); 58 | 59 | expect(net.createConnection).toHaveBeenCalledWith({ 60 | host, 61 | port: options.port, 62 | }); 63 | 64 | mockSocket.emit("connect"); 65 | expect(mockSocket.write).toHaveBeenCalledTimes(2); 66 | 67 | const mockResponse = { 68 | version: { name: "1.21", protocol: 765 }, 69 | players: { max: 20, online: 5, sample: [] }, 70 | description: "A Minecraft Server", 71 | }; 72 | 73 | const fullPacket = createMockJavaResponse(mockResponse); 74 | const chunk1 = fullPacket.subarray(0, 10); 75 | const chunk2 = fullPacket.subarray(10); 76 | 77 | // Simulate receiving data in chunks 78 | mockSocket.emit("data", chunk1); 79 | mockSocket.emit("data", chunk2); 80 | 81 | const result = await pingPromise; 82 | expect(result).toEqual(mockResponse); 83 | }); 84 | 85 | describe("errors", () => { 86 | it("should throw an error if host is not provided", async () => { 87 | await expect(pingJava(null)).rejects.toThrow("Host argument is required"); 88 | }); 89 | 90 | it("should reject on socket timeout", async () => { 91 | const pingPromise = pingJava("localhost", { timeout: 1000 }); 92 | await vi.runAllTicks(); 93 | mockSocket.emit("connect"); 94 | vi.advanceTimersByTime(1000); 95 | await expect(pingPromise).rejects.toThrow("Socket timeout"); 96 | }); 97 | 98 | it("should reject on connection error", async () => { 99 | const pingPromise = pingJava("localhost"); 100 | await vi.runAllTicks(); 101 | mockSocket.emit("error", new Error("ECONNREFUSED")); 102 | await expect(pingPromise).rejects.toThrow("ECONNREFUSED"); 103 | }); 104 | 105 | it("should reject if the socket closes prematurely without a response", async () => { 106 | const pingPromise = pingJava("localhost"); 107 | 108 | // Allow the initial async operations to complete 109 | await vi.runAllTicks(); 110 | 111 | // Simulate the server accepting the connection and then immediately closing it 112 | mockSocket.emit("connect"); 113 | mockSocket.emit("close"); 114 | 115 | // The promise should reject with our specific 'close' handler message 116 | await expect(pingPromise).rejects.toThrow( 117 | "Socket closed unexpectedly without a response." 118 | ); 119 | }); 120 | 121 | it("should only reject once, even if multiple errors occur", async () => { 122 | const pingPromise = pingJava("localhost"); 123 | await vi.runAllTicks(); 124 | mockSocket.emit("error", new Error("First error")); 125 | mockSocket.emit("error", new Error("Second error")); // Should be ignored 126 | await expect(pingPromise).rejects.toThrow("First error"); 127 | }); 128 | }); 129 | }); 130 | 131 | /** 132 | * Creates a mock Java status response packet according to the protocol. 133 | * Structure: [Overall Length] [Packet ID] [JSON Length] [JSON String] 134 | * @param {object} response The JSON response object 135 | * @returns {Buffer} 136 | */ 137 | function createMockJavaResponse(response) { 138 | const jsonString = JSON.stringify(response); 139 | const jsonBuffer = varint.encodeString(jsonString); 140 | const jsonLength = varint.encodeVarInt(jsonBuffer.length); 141 | const packetId = varint.encodeVarInt(0x00); 142 | const payloadParts = [packetId, jsonLength, jsonBuffer]; 143 | return varint.concatPackets(payloadParts); 144 | } 145 | -------------------------------------------------------------------------------- /test/varint.test.js: -------------------------------------------------------------------------------- 1 | import { describe, it, expect } from "vitest"; 2 | import * as varint from "../lib/varint.js"; 3 | 4 | describe("varint.js", () => { 5 | it("should encode and decode integers symmetrically (round-trip)", () => { 6 | const testValues = [ 7 | 0, 8 | 1, 9 | 127, // Max 1-byte 10 | 128, // Min 2-byte 11 | 255, 12 | 16383, // Max 2-byte 13 | 16384, // Min 3-byte 14 | 2147483647, // Max signed 32-bit int 15 | -1, // Critical edge case (encodes as max unsigned int) 16 | ]; 17 | 18 | testValues.forEach((value) => { 19 | const encoded = varint.encodeVarInt(value); 20 | const { value: decoded } = varint.decodeVarInt(encoded, 0); 21 | expect(decoded, `Value ${value} failed round-trip`).toBe(value); 22 | }); 23 | }); 24 | 25 | it("should decode an integer from a non-zero offset", () => { 26 | // [255 (invalid varint), 128 (valid varint), 127 (valid varint)] 27 | const buffer = Buffer.from([0xff, 0x80, 0x01, 0x7f]); 28 | const { value: decoded } = varint.decodeVarInt(buffer, 1); 29 | expect(decoded).toBe(128); 30 | }); 31 | 32 | it("should throw an error for a malformed varint that is too long", () => { 33 | const invalidBuffer = Buffer.from([0x80, 0x80, 0x80, 0x80, 0x80, 0x80]); 34 | expect(() => varint.decodeVarInt(invalidBuffer, 0)).toThrow( 35 | "VarInt is too big or malformed" 36 | ); 37 | }); 38 | 39 | it("should encode 16-bit unsigned shorts in big-endian format", () => { 40 | expect(varint.encodeUShort(0)).toEqual(Buffer.from([0x00, 0x00])); 41 | expect(varint.encodeUShort(256)).toEqual(Buffer.from([0x01, 0x00])); 42 | expect(varint.encodeUShort(65535)).toEqual(Buffer.from([0xff, 0xff])); 43 | }); 44 | 45 | it("should correctly assemble a Minecraft packet with a length prefix", () => { 46 | const payloadParts = [ 47 | varint.encodeVarInt(0), // protocol 48 | varint.encodeString("mc.example.com"), // host 49 | varint.encodeUShort(25565), // port 50 | ]; 51 | const payload = Buffer.concat(payloadParts); 52 | const finalPacket = varint.concatPackets(payloadParts); 53 | const { value: decodedPacketLength, bytesRead } = varint.decodeVarInt( 54 | finalPacket, 55 | 0 56 | ); 57 | expect(decodedPacketLength).toBe(payload.length); 58 | const decodedPayload = finalPacket.subarray(bytesRead); 59 | expect(decodedPayload).toEqual(payload); 60 | }); 61 | }); 62 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": ["lib/**/*.js", "index.js"], 3 | "compilerOptions": { 4 | "allowJs": true, 5 | "declaration": true, 6 | "emitDeclarationOnly": true, 7 | "outDir": "types", 8 | "removeComments": false 9 | } 10 | } -------------------------------------------------------------------------------- /types/index.d.ts: -------------------------------------------------------------------------------- 1 | export { pingJava } from "./lib/java.js"; 2 | export { pingBedrock } from "./lib/bedrock.js"; 3 | -------------------------------------------------------------------------------- /types/lib/bedrock.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Asynchronously pings a Minecraft Bedrock server. 3 | * @param {string} host - The IP address or hostname of the server. 4 | * @param {BedrockPingOptions} [options={}] - Optional configuration. 5 | * @returns {Promise} A promise that resolves with the server's parsed MOTD. 6 | */ 7 | export function pingBedrock(host: string, options?: BedrockPingOptions): Promise; 8 | /** 9 | * Representation of raw, semicolon-delimited MOTD string. 10 | * This struct directly mirrors the fields and order from the server response. 11 | * See [`Unconnected Pong Documentation`](https://minecraft.wiki/w/RakNet#Unconnected_Pong) for more details. 12 | */ 13 | export type BedrockMotd = { 14 | /** 15 | * - The edition of the server (MCPE or MCEE). 16 | */ 17 | edition: string; 18 | /** 19 | * - The primary name of the server (first line of MOTD). 20 | */ 21 | name: string; 22 | /** 23 | * - The protocol version. 24 | */ 25 | protocol: number; 26 | /** 27 | * - The game version (e.g., "1.21.2"). 28 | */ 29 | version: string; 30 | /** 31 | * - The current number of players online. 32 | */ 33 | playerCount: number; 34 | /** 35 | * - The maximum number of players allowed. 36 | */ 37 | playerMax: number; 38 | /** 39 | * - The server's GUID. 40 | */ 41 | serverGuid: bigint; 42 | /** 43 | * - The secondary name of the server (second line of MOTD). 44 | */ 45 | subName: string; 46 | /** 47 | * - The default gamemode (e.g., "Survival"). 48 | */ 49 | gamemode: string; 50 | /** 51 | * - Whether the server is Nintendo limited. 52 | */ 53 | nintendoLimited?: boolean; 54 | /** 55 | * - The server's IPv4 port, if provided. 56 | */ 57 | port?: number; 58 | /** 59 | * - The server's IPv6 port, if provided. 60 | */ 61 | ipv6Port?: number; 62 | /** 63 | * - Whether the server is in editor mode, if provided. See [Minecraft Editor Mode Documentation](https://learn.microsoft.com/en-us/minecraft/creator/documents/bedrockeditor/editoroverview?view=minecraft-bedrock-stable) for more details. 64 | */ 65 | editorMode?: boolean; 66 | }; 67 | /** 68 | * Represents the structured and user-friendly response from a server ping. 69 | * This is the public-facing object that users of the library will receive. 70 | */ 71 | export type BedrockPingResponse = { 72 | /** 73 | * - The edition of the server (MCPE or MCEE). 74 | */ 75 | edition: string; 76 | /** 77 | * - The primary name of the server (first line of MOTD). 78 | */ 79 | name: string; 80 | /** 81 | * - The name of the world or level being hosted. 82 | */ 83 | levelName: string; 84 | /** 85 | * - The default gamemode of the server. 86 | */ 87 | gamemode: string; 88 | /** 89 | * - Game and protocol versions. 90 | */ 91 | version: { 92 | protocol: number; 93 | minecraft: string; 94 | }; 95 | /** 96 | * - Current and maximum player counts. 97 | */ 98 | players: { 99 | online: number; 100 | max: number; 101 | }; 102 | /** 103 | * - Announced IPv4 and IPv6 ports. 104 | */ 105 | port: { 106 | v4?: number; 107 | v6?: number; 108 | }; 109 | /** 110 | * - The server's unique 64-bit GUID. 111 | */ 112 | guid: bigint; 113 | /** 114 | * - True if the server restricts Nintendo Switch players. 115 | */ 116 | isNintendoLimited?: boolean; 117 | /** 118 | * - True if the server is in editor mode. See [Minecraft Editor Mode Documentation](https://learn.microsoft.com/en-us/minecraft/creator/documents/bedrockeditor/editoroverview?view=minecraft-bedrock-stable) for more details. 119 | */ 120 | isEditorModeEnabled?: boolean; 121 | }; 122 | export type BedrockPingOptions = { 123 | /** 124 | * - The server port to ping. 125 | */ 126 | port?: number; 127 | /** 128 | * - The timeout in milliseconds for the request. 129 | */ 130 | timeout?: number; 131 | }; 132 | -------------------------------------------------------------------------------- /types/lib/java.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Asynchronously Pings a Minecraft Java Edition server. 3 | * This function performs an SRV lookup and then attempts to connect and retrieve the server status. 4 | * @param {string} host - The server address to ping. 5 | * @param {JavaPingOptions} [options={}] - Optional configuration. 6 | * @returns {Promise} A promise that resolves with the server's status. 7 | */ 8 | export function pingJava(host: string, options?: JavaPingOptions): Promise; 9 | /** 10 | * Represents the structured and user-friendly response from a server ping. 11 | * The fields and their optionality are based on the protocol documentation. 12 | * See [Status Response Documentation](https://minecraft.wiki/w/Java_Edition_protocol/Server_List_Ping#Status_Response) for more details. 13 | */ 14 | export type JavaPingResponse = { 15 | /** 16 | * - Contains the server's version name and protocol number. 17 | */ 18 | version: { 19 | name: string; 20 | protocol: number; 21 | }; 22 | /** 23 | * - Player count and a sample of online players. 24 | */ 25 | players?: { 26 | max: number; 27 | online: number; 28 | sample?: Array<{ 29 | name: string; 30 | id: string; 31 | }>; 32 | }; 33 | /** 34 | * - The server's Message of the Day (MOTD). 35 | */ 36 | description?: object | string; 37 | /** 38 | * - A Base64-encoded 64x64 PNG image data URI. 39 | */ 40 | favicon?: string; 41 | /** 42 | * - True if the server requires clients to have a Mojang-signed public key. 43 | */ 44 | enforcesSecureChat?: boolean; 45 | /** 46 | * - True if a mod is installed to disable chat reporting. 47 | */ 48 | preventsChatReports?: boolean; 49 | }; 50 | export type JavaPingOptions = { 51 | /** 52 | * - The fallback port if an SRV record is not found. 53 | */ 54 | port?: number; 55 | /** 56 | * - The connection timeout in milliseconds. 57 | */ 58 | timeout?: number; 59 | /** 60 | * - The protocol version to use in the handshake. `-1` is for auto-detection. 61 | */ 62 | protocolVersion?: number; 63 | }; 64 | -------------------------------------------------------------------------------- /types/lib/varint.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Encodes an integer into a VarInt buffer. 3 | * VarInts are never longer than 5 bytes for the Minecraft protocol. 4 | * @param {number} value The integer to encode 5 | * @returns {Buffer} The encoded VarInt as a buffer 6 | * @throws {VarIntError} if the value is too large to be encoded 7 | */ 8 | export function encodeVarInt(value: number): Buffer; 9 | /** 10 | * Encodes a string into a UTF-8 buffer. 11 | * @param {string} value The string to encode 12 | * @returns {Buffer} 13 | */ 14 | export function encodeString(value: string): Buffer; 15 | /** 16 | * Encodes an unsigned short (16-bit big-endian) into a 2-byte buffer. 17 | * @param {number} value The number to encode 18 | * @returns {Buffer} 19 | */ 20 | export function encodeUShort(value: number): Buffer; 21 | /** 22 | * Creates a Minecraft-style packet by concatenating chunks and prefixing the total length as a VarInt. 23 | * @param {Buffer[]} chunks An array of buffers to include in the packet payload 24 | * @returns {Buffer} The complete packet with its length prefix 25 | */ 26 | export function concatPackets(chunks: Buffer[]): Buffer; 27 | /** 28 | * Decodes a VarInt from a buffer. 29 | * Returns the decoded value and the number of bytes it consumed. 30 | * @param {Buffer} buffer The buffer to read from 31 | * @param {number} [offset=0] The starting offset in the buffer 32 | * @returns {{ value: number, bytesRead: number }} 33 | * @throws {VarIntError} if the buffer is too short or the VarInt is malformed 34 | */ 35 | export function decodeVarInt(buffer: Buffer, offset?: number): { 36 | value: number; 37 | bytesRead: number; 38 | }; 39 | export const ERR_VARINT_BUFFER_UNDERFLOW: "VARINT_BUFFER_UNDERFLOW"; 40 | export const ERR_VARINT_MALFORMED: "VARINT_MALFORMED"; 41 | export const ERR_VARINT_ENCODE_TOO_LARGE: "VARINT_ENCODE_TOO_LARGE"; 42 | export class VarIntError extends Error { 43 | /** 44 | * @param {string} message The error message. 45 | * @param {string} code The error code. 46 | */ 47 | constructor(message: string, code: string); 48 | code: string; 49 | } 50 | --------------------------------------------------------------------------------