/** @license MIT * AddonAPIKit - Copyright (c) 2026 ForestOfLight * MCBE-IPC - Copyright (c) 2026 OmniacDev * See LICENSE for details. */ // src/MCBE-IPC/ipc.js import { ScriptEventSource, system } from "@minecraft/server"; var UTIL; (function(UTIL2) { function generate_id() { const r = Math.random() * 4294967296 >>> 0; return r.toString(16).padStart(8, "0").toUpperCase(); } UTIL2.generate_id = generate_id; })(UTIL || (UTIL = {})); var PROTO; (function(PROTO2) { class Buffer { get end() { return this._length + this._offset; } get front() { return this._offset; } get data_view() { return this._data_view; } constructor(size = 256) { this._buffer = new Uint8Array(size); this._data_view = new DataView(this._buffer.buffer); this._length = 0; this._offset = 0; } reserve(amount) { this.ensure_capacity(amount); const end = this.end; this._length += amount; return end; } consume(amount) { if (amount > this._length) throw new Error("not enough bytes"); const front = this.front; this._length -= amount; this._offset += amount; return front; } write(input) { if (typeof input === "number") { const offset = this.reserve(1); this._buffer[offset] = input; } else { const offset = this.reserve(input.length); this._buffer.set(input, offset); } } read(amount) { if (amount === void 0) { const offset = this.consume(1); return this._buffer[offset]; } else { const offset = this.consume(amount); return this._buffer.slice(offset, offset + amount); } } ensure_capacity(size) { if (this.end + size > this._buffer.length) { const larger_buffer = new Uint8Array((this.end + size) * 2); larger_buffer.set(this._buffer.subarray(this._offset, this.end), 0); this._buffer = larger_buffer; this._offset = 0; this._data_view = new DataView(this._buffer.buffer); } } static from_uint8array(array) { const buffer = new Buffer(); buffer._buffer = array; buffer._length = array.length; buffer._offset = 0; buffer._data_view = new DataView(array.buffer); return buffer; } to_uint8array() { return this._buffer.subarray(this._offset, this.end); } } PROTO2.Buffer = Buffer; let MIPS; (function(MIPS2) { function is_valid(str) { return str.startsWith("(0x") && str.endsWith(")"); } MIPS2.is_valid = is_valid; function* serialize(stream) { const uint8array = stream.to_uint8array(); let str = "(0x"; for (let i = 0; i < uint8array.length; i++) { const hex = uint8array[i].toString(16).padStart(2, "0").toUpperCase(); str += hex; yield; } str += ")"; return str; } MIPS2.serialize = serialize; function* deserialize(str) { if (is_valid(str)) { const buffer = new Buffer(); const hex_str = str.slice(3, str.length - 1); for (let i = 0; i < hex_str.length; i++) { const hex = hex_str[i] + hex_str[++i]; buffer.write(parseInt(hex, 16)); yield; } return buffer; } return new Buffer(); } MIPS2.deserialize = deserialize; })(MIPS = PROTO2.MIPS || (PROTO2.MIPS = {})); PROTO2.Void = { *serialize() { }, *deserialize() { } }; PROTO2.Null = { *serialize() { }, *deserialize() { return null; } }; PROTO2.Undefined = { *serialize() { }, *deserialize() { return void 0; } }; PROTO2.Int8 = { *serialize(value, stream) { stream.data_view.setInt8(stream.reserve(1), value); }, *deserialize(stream) { return stream.data_view.getInt8(stream.consume(1)); } }; PROTO2.Int16 = { *serialize(value, stream) { stream.data_view.setInt16(stream.reserve(2), value); }, *deserialize(stream) { return stream.data_view.getInt16(stream.consume(2)); } }; PROTO2.Int32 = { *serialize(value, stream) { stream.data_view.setInt32(stream.reserve(4), value); }, *deserialize(stream) { return stream.data_view.getInt32(stream.consume(4)); } }; PROTO2.UInt8 = { *serialize(value, stream) { stream.data_view.setUint8(stream.reserve(1), value); }, *deserialize(stream) { return stream.data_view.getUint8(stream.consume(1)); } }; PROTO2.UInt16 = { *serialize(value, stream) { stream.data_view.setUint16(stream.reserve(2), value); }, *deserialize(stream) { return stream.data_view.getUint16(stream.consume(2)); } }; PROTO2.UInt32 = { *serialize(value, stream) { stream.data_view.setUint32(stream.reserve(4), value); }, *deserialize(stream) { return stream.data_view.getUint32(stream.consume(4)); } }; PROTO2.UVarInt32 = { *serialize(value, stream) { value >>>= 0; while (value >= 128) { stream.write(value & 127 | 128); value >>>= 7; yield; } stream.write(value); }, *deserialize(stream) { let value = 0; for (let size = 0; size < 5; size++) { const byte = stream.read(); value |= (byte & 127) << size * 7; yield; if ((byte & 128) == 0) break; } return value >>> 0; } }; PROTO2.VarInt32 = { *serialize(value, stream) { const zigzag = value << 1 ^ value >> 31; yield* PROTO2.UVarInt32.serialize(zigzag, stream); }, *deserialize(stream) { const zigzag = yield* PROTO2.UVarInt32.deserialize(stream); return zigzag >>> 1 ^ -(zigzag & 1); } }; PROTO2.Float32 = { *serialize(value, stream) { stream.data_view.setFloat32(stream.reserve(4), value); }, *deserialize(stream) { return stream.data_view.getFloat32(stream.consume(4)); } }; PROTO2.Float64 = { *serialize(value, stream) { stream.data_view.setFloat64(stream.reserve(8), value); }, *deserialize(stream) { return stream.data_view.getFloat64(stream.consume(8)); } }; PROTO2.String = { *serialize(value, stream) { yield* PROTO2.UVarInt32.serialize(value.length, stream); for (let i = 0; i < value.length; i++) { const code = value.charCodeAt(i); yield* PROTO2.UVarInt32.serialize(code, stream); } }, *deserialize(stream) { const length = yield* PROTO2.UVarInt32.deserialize(stream); let value = ""; for (let i = 0; i < length; i++) { const code = yield* PROTO2.UVarInt32.deserialize(stream); value += globalThis.String.fromCharCode(code); } return value; } }; PROTO2.Boolean = { *serialize(value, stream) { stream.write(value ? 1 : 0); }, *deserialize(stream) { return stream.read() !== 0; } }; PROTO2.UInt8Array = { *serialize(value, stream) { yield* PROTO2.UVarInt32.serialize(value.length, stream); stream.write(value); }, *deserialize(stream) { const length = yield* PROTO2.UVarInt32.deserialize(stream); return stream.read(length); } }; PROTO2.Date = { *serialize(value, stream) { yield* PROTO2.Float64.serialize(value.getTime(), stream); }, *deserialize(stream) { return new globalThis.Date(yield* PROTO2.Float64.deserialize(stream)); } }; function Object2(s) { return { *serialize(value, stream) { for (const key in s) { yield* s[key].serialize(value[key], stream); } }, *deserialize(stream) { const result = {}; for (const key in s) { result[key] = yield* s[key].deserialize(stream); } return result; } }; } PROTO2.Object = Object2; function Array2(s) { return { *serialize(value, stream) { yield* PROTO2.UVarInt32.serialize(value.length, stream); for (const item of value) { yield* s.serialize(item, stream); } }, *deserialize(stream) { const result = []; const length = yield* PROTO2.UVarInt32.deserialize(stream); for (let i = 0; i < length; i++) { result[i] = yield* s.deserialize(stream); } return result; } }; } PROTO2.Array = Array2; function Tuple(...s) { return { *serialize(value, stream) { for (let i = 0; i < s.length; i++) { yield* s[i].serialize(value[i], stream); } }, *deserialize(stream) { const result = []; for (let i = 0; i < s.length; i++) { result[i] = yield* s[i].deserialize(stream); } return result; } }; } PROTO2.Tuple = Tuple; function Optional(s) { return { *serialize(value, stream) { const def = value !== void 0; yield* PROTO2.Boolean.serialize(def, stream); if (def) yield* s.serialize(value, stream); }, *deserialize(stream) { const def = yield* PROTO2.Boolean.deserialize(stream); if (def) return yield* s.deserialize(stream); return void 0; } }; } PROTO2.Optional = Optional; function Map2(kS, vS) { return { *serialize(value, stream) { yield* PROTO2.UVarInt32.serialize(value.size, stream); for (const [k, v] of value) { yield* kS.serialize(k, stream); yield* vS.serialize(v, stream); } }, *deserialize(stream) { const size = yield* PROTO2.UVarInt32.deserialize(stream); const result = new globalThis.Map(); for (let i = 0; i < size; i++) { const k = yield* kS.deserialize(stream); const v = yield* vS.deserialize(stream); result.set(k, v); } return result; } }; } PROTO2.Map = Map2; function Set(s) { return { *serialize(set, stream) { yield* PROTO2.UVarInt32.serialize(set.size, stream); for (const v of set) { yield* s.serialize(v, stream); } }, *deserialize(stream) { const size = yield* PROTO2.UVarInt32.deserialize(stream); const result = new globalThis.Set(); for (let i = 0; i < size; i++) { const v = yield* s.deserialize(stream); result.add(v); } return result; } }; } PROTO2.Set = Set; function Cached(s, depth = 16) { const cache = new globalThis.Map(); return { *serialize(value, stream) { const hit = cache.get(value); if (hit !== void 0) { stream.write(hit); cache.delete(value); cache.set(value, hit); } else { const buffer = new PROTO2.Buffer(); yield* s.serialize(value, buffer); const bytes = buffer.to_uint8array(); stream.write(bytes); cache.set(value, bytes); if (cache.size > depth) { const first = cache.keys().next().value; cache.delete(first); } } }, *deserialize(stream) { return yield* s.deserialize(stream); } }; } PROTO2.Cached = Cached; })(PROTO || (PROTO = {})); var NET; (function(NET2) { const Endpoint = PROTO.String; const Meta = PROTO.Object({ guid: PROTO.String, signature: PROTO.String }); const Header = PROTO.Object({ meta: Meta, index: PROTO.UVarInt32, final: PROTO.Boolean }); const LISTENERS = /* @__PURE__ */ new Map(); NET2.SIGNATURE = "mcbe-ipc:v3"; NET2.FRAG_MAX = 2048; function* serialize(buffer, max_size = Infinity) { const uint8array = buffer.to_uint8array(); const result = []; let acc_str = ""; let acc_size = 0; for (let i = 0; i < uint8array.length; i++) { const char_code = uint8array[i] | uint8array[++i] << 8; const utf16_size = char_code <= 127 ? 1 : char_code <= 2047 ? 2 : char_code <= 65535 ? 3 : 4; const char_size = char_code > 255 ? utf16_size : 2; if (acc_size + char_size > max_size) { result.push(acc_str); acc_str = ""; acc_size = 0; } if (char_code > 255) { acc_str += String.fromCharCode(char_code); acc_size += utf16_size; } else { acc_str += char_code.toString(16).padStart(2, "0").toUpperCase(); acc_size += 2; } yield; } result.push(acc_str); return result; } NET2.serialize = serialize; function* deserialize(strings) { const buffer = new PROTO.Buffer(); for (let i = 0; i < strings.length; i++) { const str = strings[i]; for (let j = 0; j < str.length; j++) { const char_code = str.charCodeAt(j); if (char_code <= 255) { const hex = str[j] + str[++j]; const hex_code = parseInt(hex, 16); buffer.write(hex_code & 255); buffer.write(hex_code >> 8); } else { buffer.write(char_code & 255); buffer.write(char_code >> 8); } yield; } yield; } return buffer; } NET2.deserialize = deserialize; system.afterEvents.scriptEventReceive.subscribe((event) => { system.runJob((function* () { if (event.sourceType !== ScriptEventSource.Server) return; const [serialized_endpoint, serialized_header] = event.id.split(":"); if (!PROTO.MIPS.is_valid(serialized_endpoint)) return; const endpoint_stream = yield* PROTO.MIPS.deserialize(serialized_endpoint); const endpoint = yield* Endpoint.deserialize(endpoint_stream); const listeners = LISTENERS.get(endpoint); if (listeners !== void 0 && PROTO.MIPS.is_valid(serialized_header)) { const header_stream = yield* PROTO.MIPS.deserialize(serialized_header); const header = yield* Header.deserialize(header_stream); for (const listener of [...listeners]) { try { yield* listener(header, event.message); } catch (e) { console.error(`[MCBE-IPC] listener error while handling packet on "${endpoint}":`, e); } } } })()); }); function register(endpoint, listener) { let listeners = LISTENERS.get(endpoint); if (listeners === void 0) { listeners = new Array(); LISTENERS.set(endpoint, listeners); } listeners.push(listener); return () => { const idx = listeners.indexOf(listener); if (idx !== -1) listeners.splice(idx, 1); if (listeners.length === 0) { LISTENERS.delete(endpoint); } }; } function* emit(endpoint, serializer, value, options) { const guid = options?.metaOverride?.guid ?? UTIL.generate_id(); const signature = options?.metaOverride?.signature ?? NET2.SIGNATURE; const endpoint_stream = new PROTO.Buffer(); yield* Endpoint.serialize(endpoint, endpoint_stream); const serialized_endpoint = yield* PROTO.MIPS.serialize(endpoint_stream); const packet_stream = new PROTO.Buffer(); yield* serializer.serialize(value, packet_stream); const serialized_packets = yield* serialize(packet_stream, NET2.FRAG_MAX); for (let i = 0; i < serialized_packets.length; i++) { const serialized_packet = serialized_packets[i]; const header = { meta: { guid, signature }, index: i, final: i === serialized_packets.length - 1 }; const header_stream = new PROTO.Buffer(); yield* Header.serialize(header, header_stream); const serialized_header = yield* PROTO.MIPS.serialize(header_stream); system.sendScriptEvent(`${serialized_endpoint}:${serialized_header}`, serialized_packet); } } NET2.emit = emit; function listen(endpoint, deserializer, callback, options) { const buffer = /* @__PURE__ */ new Map(); const listener = function* (header, fragment) { let packet = buffer.get(header.meta.guid); if (packet === void 0) { if (options?.filter?.(header.meta) === false) return; packet = { size: -1, fragments: [], received: 0 }; buffer.set(header.meta.guid, packet); } if (header.final) { packet.size = header.index + 1; } if (packet.fragments[header.index] === void 0) { packet.fragments[header.index] = fragment; packet.received++; } else { throw new Error(`received duplicate fragment ${header.index} for packet ${header.meta.guid}`); } if (packet.size !== -1 && packet.size === packet.received) { const stream = yield* deserialize(packet.fragments); const value = yield* deserializer.deserialize(stream); yield* callback(value, header.meta); buffer.delete(header.meta.guid); } }; return register(endpoint, listener); } NET2.listen = listen; })(NET || (NET = {})); var IPC; (function(IPC2) { function send(channel, serializer, value) { system.runJob(NET.emit(`ipc:${channel}:send`, serializer, value)); } IPC2.send = send; function invoke(channel, serializer, value, deserializer) { const id = UTIL.generate_id(); return new Promise((resolve) => { const terminate = NET.listen(`ipc:${channel}:handle`, deserializer, function* (value2, meta) { if (meta.signature.includes(`+correlation`) && meta.guid !== id) return; resolve(value2); terminate(); }, { filter: (meta) => !meta.signature.includes(`+correlation`) || meta.guid === id }); system.runJob(NET.emit(`ipc:${channel}:invoke`, serializer, value, { metaOverride: { guid: id, signature: `${NET.SIGNATURE}+correlation` } })); }); } IPC2.invoke = invoke; function on(channel, deserializer, listener) { return NET.listen(`ipc:${channel}:send`, deserializer, function* (value) { listener(value); }); } IPC2.on = on; function once(channel, deserializer, listener) { const terminate = NET.listen(`ipc:${channel}:send`, deserializer, function* (value) { listener(value); terminate(); }); return terminate; } IPC2.once = once; function handle(channel, deserializer, serializer, listener) { return NET.listen(`ipc:${channel}:invoke`, deserializer, function* (value, meta) { const result = listener(value); yield* NET.emit(`ipc:${channel}:handle`, serializer, result, { metaOverride: meta.signature.includes(`+correlation`) ? { guid: meta.guid, signature: `${NET.SIGNATURE}+correlation` } : void 0 }); }); } IPC2.handle = handle; })(IPC || (IPC = {})); // src/Errors/APIErrorEnum.js var APIErrorEnum = Object.freeze({ Unknown: 0, Success: 1, Caller: 2, Server: 3 }); // src/Errors/APICallerError.js var APICallerError = class extends Error { constructor(error) { super(error.message); this.errorName = error.name; this.errorMessage = error.message; this.errorCode = APIErrorEnum.Caller; this.name = "APICallerError"; } }; // src/Errors/APIServerError.js var APIServerError = class extends Error { constructor(error) { super(error.message); this.thrownError = error; this.errorCode = APIErrorEnum.Server; this.name = "APIServerError"; } }; // src/Errors/APIVersionMismatchError.js var APIVersionMismatchError = class extends Error { constructor(serverApiVersion, callerApiVersion) { super(`API version numbers do not match (${callerApiVersion} != ${serverApiVersion}). Please use API version ${serverApiVersion}.`); this.name = "APIVersionMismatchError"; } }; // src/APIModels.js var VoidModel = PROTO.Void; var ErrorModel = PROTO.Optional(PROTO.Object({ code: PROTO.Int8, name: PROTO.Optional(PROTO.String), message: PROTO.Optional(PROTO.String) })); var ReturnModelShell = { apiVersion: PROTO.String, data: void 0, error: ErrorModel }; var CallModelShell = { apiVersion: PROTO.String, parameterMap: void 0 }; var EndpointModel = PROTO.String; var EndpointsModel = PROTO.Array(EndpointModel); // src/APIController.js var APIController = class _APIController { #endpoints = {}; constructor() { if (this.constructor === _APIController) throw new Error("Cannot instantiate abstract class 'APIController'"); } get endpoints() { return this.#endpoints; } addEndpoint(endpoint, callback, parameterModel, returnModel) { this.#endpoints[endpoint] = { callback, parameterModel, returnModel }; } }; // src/EndpointsController.js var EndpointsController = class extends APIController { #api; constructor(api) { this.#api = api; this.addEndpoint("endpoints", this.getEndpoints, VoidModel, EndpointsModel); this.addEndpoint("endpoints:has", this.hasEndpoint, EndpointModel, PROTO.Boolean); } getEndpoints() { return this.#api.endpoints; } hasEndpoint(endpoint) { return this.getEndpoints().includes(endpoint); } }; // src/AddonAPI.js var AddonAPI = class { #name; #version; #allEndpoints; constructor(name, version) { this.#name = name; this.#version = version; const endpointsController = new EndpointsController(this); this.setupController(endpointsController); } get name() { return this.#name; } get version() { return this.#version; } get endpointBase() { return this.#name + ":"; } get endpoints() { return this.#allEndpoints; } setupController(apiController) { for (const [endpoint, features] of Object.entries(apiController.endpoints)) { const { callback, parameterModel, returnModel } = features; const boundCallback = callback.bind(apiController); this.#setupEndpoint(endpoint, boundCallback, parameterModel, returnModel); } } #setupEndpoint(endpoint, callback, parameterModel, returnDataModel) { const returnPacketModel = this.#resolveReturnModel(returnDataModel); const endpointPath = this.endpointBase + endpoint; IPC.handle(endpointPath, parameterModel, returnPacketModel, (callPacket) => { const apiVersion = callPacket.apiVersion; const parameters = Object.values(callPacket.parameterMap); return this.#handleCallback(apiVersion, callback, parameters); }); this.#allEndpoints.push(endpointPath); } #handleCallback(apiVersion, callback, parameters) { try { this.#assertVersionsMatch(apiVersion); const returnValue = callback(...parameters); return this.#bundleReturnPacket({ code: APIErrorEnum.Success }, returnValue); } catch (error) { if (error instanceof APICallerError) { const errorPacket2 = this.#resolveErrorPacket(error); return this.#bundleReturnPacket(errorPacket2); } console.error(error); const apiError = new APIServerError(error); const errorPacket = this.#resolveErrorPacket(apiError); return this.#bundleReturnPacket(errorPacket); } } #assertVersionsMatch(versionToCheck) { if (versionToCheck !== this.version) { const apiVersionMismatchError = new APIVersionMismatchError(this.version, versionToCheck); throw new APICallerError(apiVersionMismatchError); } } #resolveReturnModel(returnDataModel) { let returnModel = { ...ReturnModelShell }; returnModel.data = returnDataModel; returnModel = PROTO.Object(returnModel); return returnModel; } #bundleReturnPacket(errorPacket, returnValue = void 0) { return { apiVersion: this.version, data: returnValue, error: errorPacket }; } #resolveErrorPacket(error) { return { code: error.errorCode, name: error.thrownError.name, message: error.thrownError.message }; } }; // src/AddonAPICaller.js import { system as system2 } from "@minecraft/server"; // src/Errors/APIEndpointNotFoundError.js var APIEndpointNotFoundError = class extends Error { constructor(endpoint) { super(`Endpoint "${endpoint}" was not found.`); this.name = "APIEndpointNotFoundError"; } }; // src/AddonAPICaller.js var AddonAPICaller = class { static #validEndpointCache = []; static async call(endpoint, parameterModel, parameterMap, returnDataModel) { if (this.#validEndpointCache.length === 0) { const endpointBase = endpoint.split(":")[0]; await this.#populateValidEndpointCache(endpointBase); } if (this.#validEndpointCache.includes(endpoint)) return await IPC.invoke(endpoint, parameterModel, parameterMap, returnDataModel).then((result) => result.value); else throw new APIEndpointNotFoundError(endpoint); } static async #populateValidEndpointCache(endpointBase) { const endpointsEndpoint = endpointBase + ":endpoints"; const validEndpoints = await IPC.invoke(endpointsEndpoint, VoidModel, void 0, PROTO.Boolean); this.#validEndpointCache.push(...validEndpoints); } }; export { APICallerError, APIController, APIErrorEnum, AddonAPI, AddonAPICaller, PROTO, VoidModel }; /** * @license * MIT License * * Copyright (c) 2026 OmniacDev * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */