diff --git a/lib/bedrock.js b/lib/bedrock.js index 0dc41cf..f231c68 100644 --- a/lib/bedrock.js +++ b/lib/bedrock.js @@ -7,6 +7,9 @@ import dgram from "node:dgram"; import crypto from "node:crypto"; +import createDebug from "debug"; + +const debug = createDebug("mineping:bedrock"); const MAGIC = "00ffff00fefefefefdfdfdfd12345678"; const START_TIME = Date.now(); @@ -177,6 +180,7 @@ const parseUnconnectedPong = (pongPacket) => { motdOffset, motdOffset + motdLength ); + debug("received raw MOTD string: %s", motdString); const rawMotd = parseMotd(motdString); const motd = transformMotd(rawMotd); @@ -197,6 +201,7 @@ export const pingBedrock = (host, options = {}) => { } const { port = 19132, timeout = 5000 } = options; + debug("pinging Bedrock server %s:%d with %dms timeout", host, port, timeout); return new Promise((resolve, reject) => { const socket = dgram.createSocket("udp4"); @@ -215,17 +220,20 @@ export const pingBedrock = (host, options = {}) => { const cleanup = () => { if (isCleanupCompleted) return; isCleanupCompleted = true; + debug("cleaning up resources for %s:%d", host, port); clearTimeout(timeoutTask); socket.close(); }; // Generic error handler socket.on("error", (err) => { + debug("socket error for %s:%d - %s", host, port, err.message); cleanup(); reject(err); }); socket.on("message", (pongPacket) => { + debug("received %d bytes from %s:%d", pongPacket.length, host, port); try { const motd = parseUnconnectedPong(pongPacket); cleanup(); @@ -237,6 +245,8 @@ export const pingBedrock = (host, options = {}) => { try { const pingPacket = createUnconnectedPingFrame(Date.now() - START_TIME); + debug("sending Unconnected Ping packet to %s:%d", host, port); + debug("packet: %o", pingPacket); socket.send(pingPacket, 0, pingPacket.length, port, host); } catch (err) { // Handle any immediate, synchronous errors that might occur when sending the ping packet diff --git a/lib/java.js b/lib/java.js index 00ac3a1..2bcd793 100644 --- a/lib/java.js +++ b/lib/java.js @@ -7,8 +7,11 @@ import net from "node:net"; import dns from "node:dns/promises"; +import createDebug from "debug"; import * as varint from "./varint.js"; +const debug = createDebug("mineping:java"); + /** * Represents the structured and user-friendly response from a server ping. * The fields and their optionality are based on the official protocol documentation. @@ -70,6 +73,7 @@ function processResponse(buffer) { // Check if the full packet has arrived yet. if (buffer.length < offset + packetLength) { + debug("packet incomplete, waiting for more data"); return null; // Incomplete packet, wait for more data. } @@ -86,12 +90,14 @@ function processResponse(buffer) { offset += jsonLengthResult.bytesRead; if (buffer.length < offset + jsonLength) { + debug("JSON string incomplete, waiting for more data"); return null; // Incomplete JSON string, wait for more data. } const jsonString = buffer .subarray(offset, offset + jsonLength) .toString("utf8"); + debug("received raw JSON response"); const response = JSON.parse(jsonString); // Return the response and any data that came after this packet. @@ -101,6 +107,7 @@ function processResponse(buffer) { } catch (err) { // If the buffer is too short for a VarInt, it's a recoverable state. if (err.code === varint.ERR_VARINT_BUFFER_UNDERFLOW) { + debug("buffer underflow while parsing VarInt, waiting for more data"); return null; // Wait for more data. } // For malformed VarInts or JSON, throw the error to reject the promise. @@ -128,26 +135,31 @@ export async function pingJava(host, options = {}) { timeout = 5000, protocolVersion = -1, } = options; + debug("pinging Java server %s with options: %o", host, options); let targetHost = host; let targetPort = fallbackPort; try { + debug("attempting SRV lookup for _minecraft._tcp.%s", host); const srvRecords = await dns.resolveSrv(`_minecraft._tcp.${host}`); if (srvRecords.length > 0) { targetHost = srvRecords[0].name; targetPort = srvRecords[0].port; + debug("SRV lookup successful, new target: %s:%d", targetHost, targetPort); } } catch (err) { // Common errors like ENODATA or ENOTFOUND are expected when a server // does not have an SRV record, so we ignore them and proceed. if (!["ENODATA", "ENOTFOUND"].includes(err.code)) { + debug("SRV lookup for %s failed (%s), using fallback", host, err.code); // For other errors we should re-throw. throw err; } } return new Promise((resolve, reject) => { + debug("creating TCP connection to %s:%d", targetHost, targetPort); const socket = net.createConnection({ host: targetHost, port: targetPort }); // Prevent cleanup tasks from running more than once @@ -164,6 +176,7 @@ export async function pingJava(host, options = {}) { const cleanup = () => { if (isCleanupCompleted) return; isCleanupCompleted = true; + debug("cleaning up resources for %s:%d", targetHost, targetPort); clearTimeout(timeoutTask); socket.destroy(); }; @@ -174,18 +187,25 @@ export async function pingJava(host, options = {}) { // Generic error handler socket.on("error", (err) => { + debug("socket error for %s:%d - %s", targetHost, targetPort, err.message); cleanup(); reject(err); }); socket.on("close", () => { if (!isCleanupCompleted) { + debug("socket for %s:%d closed prematurely", targetHost, targetPort); cleanup(); reject(new Error("Socket closed unexpectedly without a response.")); } }); socket.on("connect", () => { + debug( + "socket connected to %s:%d, sending packets...", + targetHost, + targetPort + ); try { const handshakePacket = createHandshakePacket( host, @@ -204,11 +224,17 @@ export async function pingJava(host, options = {}) { let incomingBuffer = Buffer.alloc(0); socket.on("data", (data) => { + debug( + "received %d bytes of data, total buffer size is now %d bytes", + data.length, + incomingBuffer.length + data.length + ); incomingBuffer = Buffer.concat([incomingBuffer, data]); try { const result = processResponse(incomingBuffer); if (result) { + debug("successfully parsed full response"); // We successfully parsed a response. Clean up before resolving. cleanup(); resolve(result.response); diff --git a/package-lock.json b/package-lock.json index 4382d8c..60532c0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,13 +1,16 @@ { "name": "@minescope/mineping", - "version": "1.6.1", + "version": "1.7.0-beta.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@minescope/mineping", - "version": "1.6.1", + "version": "1.7.0-beta.0", "license": "MIT", + "dependencies": { + "debug": "^4.4.1" + }, "devDependencies": { "vitest": "^3.2.3" }, @@ -917,7 +920,6 @@ "version": "4.4.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", - "dev": true, "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -1067,7 +1069,6 @@ "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, "license": "MIT" }, "node_modules/nanoid": { diff --git a/package.json b/package.json index d4a8308..ffe27e9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@minescope/mineping", - "version": "1.7.0-beta.0", + "version": "1.7.0-beta.1", "description": "Ping both Minecraft Bedrock and Java servers.", "main": "index.js", "type": "module", @@ -23,6 +23,9 @@ "engines": { "node": ">=14" }, + "dependencies": { + "debug": "^4.4.1" + }, "devDependencies": { "vitest": "^3.2.3" }