├── .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 | [](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 |
--------------------------------------------------------------------------------