@minecraft/creator-tools
Version:
Minecraft Creator Tools command line and libraries.
111 lines (110 loc) • 4.38 kB
JavaScript
"use strict";
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const ste_events_1 = require("ste-events");
const Log_1 = __importDefault(require("../core/Log"));
/**
* Parses the Minecraft debug protocol message stream.
*
* The protocol uses length-prefixed messages:
* - 8 hex digits for length + newline (9 bytes total)
* - JSON message body + newline
*
* Example:
* ```
* 00000042\n
* {"type":"event","event":{"type":"StatEvent2",...}}\n
* ```
*/
class DebugMessageStreamParser {
_buffer = Buffer.alloc(0);
_expectedLength = -1;
_onMessage = new ste_events_1.EventDispatcher();
_onError = new ste_events_1.EventDispatcher();
get onMessage() {
return this._onMessage.asEvent();
}
get onError() {
return this._onError.asEvent();
}
/**
* Feed data from the socket into the parser.
*/
write(data) {
// Append new data to buffer
this._buffer = Buffer.concat([this._buffer, data]);
// Process as many complete messages as possible
while (this._processBuffer()) {
// Continue processing
}
}
/**
* Process the buffer and extract a complete message if available.
* Returns true if a message was processed (and we should continue checking).
*/
_processBuffer() {
// If we don't know the expected length yet, try to read it
if (this._expectedLength < 0) {
// Need at least 9 bytes for length header (8 hex + newline)
if (this._buffer.length < 9) {
return false;
}
// Read the length header
const lengthStr = this._buffer.subarray(0, 8).toString("ascii");
const newline = this._buffer[8];
if (newline !== 0x0a) {
// Not a valid length header, report error
this._onError.dispatch(this, new Error(`Invalid length header: expected newline, got ${newline}`));
// Try to recover by finding next valid-looking header
this._buffer = this._buffer.subarray(1);
return true;
}
this._expectedLength = parseInt(lengthStr, 16);
if (isNaN(this._expectedLength) || this._expectedLength < 0) {
this._onError.dispatch(this, new Error(`Invalid length value: ${lengthStr}`));
this._expectedLength = -1;
this._buffer = this._buffer.subarray(9);
return true;
}
// Remove length header from buffer
this._buffer = this._buffer.subarray(9);
}
// Check if we have enough data for the message
if (this._buffer.length < this._expectedLength) {
return false;
}
// Extract the message (length includes trailing newline)
const messageBytes = this._buffer.subarray(0, this._expectedLength);
this._buffer = this._buffer.subarray(this._expectedLength);
// Parse the JSON (trim trailing newline)
const jsonStr = messageBytes.toString("utf8").trim();
try {
const message = JSON.parse(jsonStr);
// Log raw message receipt (truncate large messages) - use verbose since this fires frequently
const msgType = message?.type || "unknown";
const eventType = message?.event?.type || "";
const preview = jsonStr.length > 200 ? jsonStr.substring(0, 200) + "..." : jsonStr;
Log_1.default.verbose(`[DebugParser] Received ${msgType}${eventType ? "/" + eventType : ""} (${jsonStr.length} bytes): ${preview}`);
this._onMessage.dispatch(this, message);
}
catch (e) {
Log_1.default.message(`[DebugParser] JSON parse error: ${e}`);
this._onError.dispatch(this, new Error(`Failed to parse JSON message: ${e}`));
}
// Reset for next message
this._expectedLength = -1;
return true;
}
/**
* Reset the parser state.
*/
reset() {
this._buffer = Buffer.alloc(0);
this._expectedLength = -1;
}
}
exports.default = DebugMessageStreamParser;