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