Compare commits

..

No commits in common. "915edbec9c9ad811459458600af3531ec0836911" and "c3158ac92528f2778336bc03c4c6e6baa0b3e30b" have entirely different histories.

4 changed files with 82 additions and 120 deletions

View File

@ -1,10 +0,0 @@
import { pingBedrock } from '../index.js';
const [thehive, oasys, frizmine, breadix] = await Promise.allSettled([
pingBedrock('geo.hivebedrock.network'),
pingBedrock('oasys-pe.com'),
pingBedrock('frizmine.ru'),
pingBedrock('play.breadixpe.ru')
]);
console.dir({ thehive, oasys, frizmine, breadix }, { depth: 3 });

View File

@ -9,134 +9,82 @@
'use strict'; 'use strict';
import dgram from 'dgram'; import dgram from 'dgram';
import ByteBuffer from 'bytebuffer';
const START_TIME = new Date().getTime(); const START_TIME = new Date().getTime();
/** /**
* Creates a buffer with the specified length. * Decode Unconnected Ping
* @param {number} length - The length of the buffer. * @param {number} pingId
* @returns {Buffer} - The created buffer. * @returns {import('bytebuffer')}
*/ * @see https://wiki.vg/Raknet_Protocol#Unconnected_Ping
const createBuffer = (length) => {
const buffer = Buffer.alloc(length);
buffer[0] = 0x01;
return buffer;
};
/**
* Writes a BigInt value to the buffer at the specified offset using big-endian byte order.
* @param {Buffer} buffer - The buffer to write to.
* @param {number} value - The BigInt value to write.
* @param {number} offset - The offset in the buffer to write the value.
*/
const writeBigInt64BE = (buffer, value, offset) => {
buffer.writeBigInt64BE(BigInt(value), offset);
};
/**
* Copies the specified hex value to the buffer at the specified offset.
* @param {Buffer} buffer - The buffer to copy to.
* @param {string} hex - The hex value to copy.
* @param {number} offset - The offset in the buffer to copy the value.
*/
const copyHexToBuffer = (buffer, hex, offset) => {
Buffer.from(hex, 'hex').copy(buffer, offset);
};
/**
* Reads a BigInt value from the buffer at the specified offset using big-endian byte order.
* @param {Buffer} buffer - The buffer to read from.
* @param {number} offset - The offset in the buffer to read the value.
* @returns {BigInt} - The read BigInt value.
*/
const readBigInt64BE = (buffer, offset) => {
return buffer.readBigInt64BE(offset);
};
/**
* Reads a string from the buffer at the specified offset.
* @param {Buffer} buffer - The buffer to read from.
* @param {number} offset - The offset in the buffer to read the string.
* @returns {string} - The read string.
*/
const readStringFromBuffer = (buffer, offset) => {
const length = buffer.readUInt16BE(offset);
return buffer.toString('utf8', offset + 2, offset + 2 + length);
};
/**
* Parses the advertise string into an object with properties.
* @param {string} advertiseStr - The advertise string to parse.
* @returns {Object} - The parsed object with properties.
*/
const parseAdvertiseString = (advertiseStr) => {
const parts = advertiseStr.split(';');
return {
gameId: parts[0],
description: parts[1],
protocolVersion: parts[2],
gameVersion: parts[3],
currentPlayers: parts[4],
maxPlayers: parts[5],
name: parts[7],
mode: parts[8]
};
};
/**
* Creates an Unconnected Ping buffer.
* @param {number} pingId - The ping ID.
* @returns {Buffer} - The Unconnected Ping buffer.
* @see {@link https://wiki.vg/Raknet_Protocol#Unconnected_Ping}
*/ */
const UNCONNECTED_PING = (pingId) => { const UNCONNECTED_PING = (pingId) => {
const buffer = createBuffer(35); // 0x01
writeBigInt64BE(buffer, pingId, 1); const bb = new ByteBuffer();
copyHexToBuffer(buffer, '00ffff00fefefefefdfdfdfd12345678', 9); bb.buffer[0] = 0x01;
writeBigInt64BE(buffer, 0, 25); bb.offset = 1;
return buffer; return bb.writeLong(pingId).append('00ffff00fefefefefdfdfdfd12345678', 'hex').writeLong(0).flip().compact();
}; };
/** /**
* Decodes an Unconnected Pong buffer and returns the parsed data. * Decode Unconnected Pong
* @param {Buffer} buffer - The Unconnected Pong buffer. * @param {import('bytebuffer')} buffer
* @returns {Object} - The parsed Unconnected Pong data. * @see https://wiki.vg/Raknet_Protocol#Unconnected_Pong
* @see {@link https://wiki.vg/Raknet_Protocol#Unconnected_Pong}
*/ */
const UNCONNECTED_PONG = (buffer) => { const UNCONNECTED_PONG = (buffer) => {
const pingId = readBigInt64BE(buffer, 1); // 0x1c
const serverId = readBigInt64BE(buffer, 9); buffer.offset = 1;
let offset = 25; const pingId = buffer.readLong();
const serverId = buffer.readLong();
const offset = buffer.offset += 16;
const nameLength = buffer.readShort();
let advertiseStr; let advertiseStr;
try { try {
advertiseStr = readStringFromBuffer(buffer, offset); advertiseStr = buffer.readUTF8String(nameLength);
} catch (err) { } catch (err) {
const length = parseInt(err.message.substr(err.message.indexOf(',') + 2, 3)); advertiseStr = buffer.readUTF8String(parseInt(err.message.substr(err.message.indexOf(',') + 2, 3)));
advertiseStr = buffer.toString('utf8', offset, offset + length);
} }
const parsedAdvertiseStr = parseAdvertiseString(advertiseStr); advertiseStr = advertiseStr.split(/;/g);
const gameId = advertiseStr[0];
const description = advertiseStr[1];
const protocolVersion = advertiseStr[2];
const gameVersion = advertiseStr[3];
const currentPlayers = advertiseStr[4];
const maxPlayers = advertiseStr[5];
const name = advertiseStr[7];
const mode = advertiseStr[8];
return { pingId, advertiseStr, serverId, offset, ...parsedAdvertiseStr }; return {
pingId,
advertiseStr,
serverId,
offset,
gameId,
description,
protocolVersion,
gameVersion,
currentPlayers,
maxPlayers,
name,
mode
};
}; };
/** function ping(host, port = 19132, cb, timeout = 5000) {
* Sends a ping request to the specified host and port.
* @param {string} host - The IP address or hostname of the server.
* @param {number} [port=19132] - The port number.
* @param {function} cb - The callback function to handle the response.
* @param {number} [timeout=5000] - The timeout duration in milliseconds.
*/
const ping = (host, port = 19132, cb, timeout = 5000) => {
const socket = dgram.createSocket('udp4'); const socket = dgram.createSocket('udp4');
// Set manual timeout interval.
// This ensures the connection will NEVER hang regardless of internal state
const timeoutTask = setTimeout(() => { const timeoutTask = setTimeout(() => {
socket.emit('error', new Error('Socket timeout')); socket.emit('error', new Error('Socket timeout'));
}, timeout); }, timeout);
const closeSocket = () => { const closeSocket = () => {
socket.close(); clearTimeout(timeoutTask); socket.close();
clearTimeout(timeoutTask);
}; };
// Generic error handler // Generic error handler
@ -155,17 +103,19 @@ const ping = (host, port = 19132, cb, timeout = 5000) => {
try { try {
const ping = UNCONNECTED_PING(new Date().getTime() - START_TIME); const ping = UNCONNECTED_PING(new Date().getTime() - START_TIME);
socket.send(ping, 0, ping.length, port, host); socket.send(ping.buffer, 0, ping.buffer.length, port, host);
} catch (err) { } catch (err) {
handleError(err); handleError(err);
} }
socket.on('message', (msg) => { socket.on('message', (msg) => {
const id = msg[0]; const buffer = new ByteBuffer().append(msg, 'hex').flip();
const id = buffer.buffer[0];
switch (id) { switch (id) {
// https://wiki.vg/Raknet_Protocol#Unconnected_Ping
case 0x1c: { case 0x1c: {
const pong = UNCONNECTED_PONG(msg); const pong = UNCONNECTED_PONG(buffer);
const clientData = { const clientData = {
version: { version: {
name: pong.name, name: pong.name,
@ -179,6 +129,8 @@ const ping = (host, port = 19132, cb, timeout = 5000) => {
gamemode: pong.mode gamemode: pong.mode
}; };
// Close the socket and clear the timeout task
// This is a general cleanup for success conditions
closeSocket(); closeSocket();
cb(clientData, null); cb(clientData, null);
break; break;
@ -192,16 +144,18 @@ const ping = (host, port = 19132, cb, timeout = 5000) => {
}); });
socket.on('error', (err) => handleError(err)); socket.on('error', (err) => handleError(err));
}; }
/** /**
* Asynchronously ping Minecraft Bedrock server. * Asynchronously ping Minecraft Bedrock server.
*
* The optional `options` argument can be an object with a `ping` (default is `19132`) or/and `timeout` (default is `5000`) property. * The optional `options` argument can be an object with a `ping` (default is `19132`) or/and `timeout` (default is `5000`) property.
*
* @param {string} host The Bedrock server address. * @param {string} host The Bedrock server address.
* @param {import('../types/lib/bedrock.js').PingOptions} options The configuration for pinging Minecraft Bedrock server. * @param {import('../types/index.js').PingOptions} options The configuration for pinging Minecraft Bedrock server.
* @returns {Promise<import('../types/lib/bedrock.js').BedrockPingResponse>} * @returns {Promise<import('../types/lib/bedrock.js').BedrockPingResponse>}
*/ */
export const pingBedrock = (host, options = {}) => { export function pingBedrock(host, options = {}) {
if (!host) throw new Error('Host argument is not provided'); if (!host) throw new Error('Host argument is not provided');
const { port = 19132, timeout = 5000 } = options; const { port = 19132, timeout = 5000 } = options;
@ -211,4 +165,4 @@ export const pingBedrock = (host, options = {}) => {
err ? reject(err) : resolve(res); err ? reject(err) : resolve(res);
}, timeout); }, timeout);
}); });
}; }

View File

@ -1,6 +1,6 @@
{ {
"name": "@minescope/mineping", "name": "@minescope/mineping",
"version": "1.1.0", "version": "1.0.4",
"description": "Ping both Minecraft Bedrock and Java servers.", "description": "Ping both Minecraft Bedrock and Java servers.",
"main": "index.js", "main": "index.js",
"types": "types/index.d.ts", "types": "types/index.d.ts",
@ -18,5 +18,8 @@
"engines": { "engines": {
"node": ">=14" "node": ">=14"
}, },
"license": "MIT" "license": "MIT",
"dependencies": {
"bytebuffer": "^5.0.1"
}
} }

15
yarn.lock Normal file
View File

@ -0,0 +1,15 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
bytebuffer@^5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/bytebuffer/-/bytebuffer-5.0.1.tgz#582eea4b1a873b6d020a48d58df85f0bba6cfddd"
integrity sha1-WC7qSxqHO20CCkjVjfhfC7ps/d0=
dependencies:
long "~3"
long@~3:
version "3.2.0"
resolved "https://registry.yarnpkg.com/long/-/long-3.2.0.tgz#d821b7138ca1cb581c172990ef14db200b5c474b"
integrity sha1-2CG3E4yhy1gcFymQ7xTbIAtcR0s=