Initial commit
This commit is contained in:
@@ -0,0 +1,593 @@
|
||||
/**
|
||||
* @license
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2025 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.
|
||||
*/
|
||||
import { ScriptEventSource, system, world } from '@minecraft/server';
|
||||
export var PROTO;
|
||||
(function (PROTO) {
|
||||
class ByteQueue {
|
||||
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;
|
||||
}
|
||||
write(...values) {
|
||||
this.ensure_capacity(values.length);
|
||||
this._buffer.set(values, this.end);
|
||||
this._length += values.length;
|
||||
}
|
||||
read(amount = 1) {
|
||||
if (this._length > 0) {
|
||||
const max_amount = amount > this._length ? this._length : amount;
|
||||
const values = this._buffer.subarray(this._offset, this._offset + max_amount);
|
||||
this._length -= max_amount;
|
||||
this._offset += max_amount;
|
||||
return globalThis.Array.from(values);
|
||||
}
|
||||
return [];
|
||||
}
|
||||
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 byte_queue = new ByteQueue();
|
||||
byte_queue._buffer = array;
|
||||
byte_queue._length = array.length;
|
||||
byte_queue._offset = 0;
|
||||
byte_queue._data_view = new DataView(array.buffer);
|
||||
return byte_queue;
|
||||
}
|
||||
to_uint8array() {
|
||||
return this._buffer.subarray(this._offset, this.end);
|
||||
}
|
||||
}
|
||||
PROTO.ByteQueue = ByteQueue;
|
||||
let MIPS;
|
||||
(function (MIPS) {
|
||||
function* serialize(byte_queue) {
|
||||
const uint8array = byte_queue.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;
|
||||
}
|
||||
MIPS.serialize = serialize;
|
||||
function* deserialize(str) {
|
||||
if (str.startsWith('(0x') && str.endsWith(')')) {
|
||||
const result = [];
|
||||
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];
|
||||
result.push(parseInt(hex, 16));
|
||||
yield;
|
||||
}
|
||||
return ByteQueue.from_uint8array(new Uint8Array(result));
|
||||
}
|
||||
return new ByteQueue();
|
||||
}
|
||||
MIPS.deserialize = deserialize;
|
||||
})(MIPS = PROTO.MIPS || (PROTO.MIPS = {}));
|
||||
PROTO.Void = {
|
||||
*serialize() { },
|
||||
*deserialize() { }
|
||||
};
|
||||
PROTO.Null = {
|
||||
*serialize() { },
|
||||
*deserialize() {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
PROTO.Undefined = {
|
||||
*serialize() { },
|
||||
*deserialize() {
|
||||
return undefined;
|
||||
}
|
||||
};
|
||||
PROTO.Int8 = {
|
||||
*serialize(value, stream) {
|
||||
const length = 1;
|
||||
stream.write(...globalThis.Array(length).fill(0));
|
||||
stream.data_view.setInt8(stream.end - length, value);
|
||||
},
|
||||
*deserialize(stream) {
|
||||
const value = stream.data_view.getInt8(stream.front);
|
||||
stream.read(1);
|
||||
return value;
|
||||
}
|
||||
};
|
||||
PROTO.Int16 = {
|
||||
*serialize(value, stream) {
|
||||
const length = 2;
|
||||
stream.write(...globalThis.Array(length).fill(0));
|
||||
stream.data_view.setInt16(stream.end - length, value);
|
||||
},
|
||||
*deserialize(stream) {
|
||||
const value = stream.data_view.getInt16(stream.front);
|
||||
stream.read(2);
|
||||
return value;
|
||||
}
|
||||
};
|
||||
PROTO.Int32 = {
|
||||
*serialize(value, stream) {
|
||||
const length = 4;
|
||||
stream.write(...globalThis.Array(length).fill(0));
|
||||
stream.data_view.setInt32(stream.end - length, value);
|
||||
},
|
||||
*deserialize(stream) {
|
||||
const value = stream.data_view.getInt32(stream.front);
|
||||
stream.read(4);
|
||||
return value;
|
||||
}
|
||||
};
|
||||
PROTO.UInt8 = {
|
||||
*serialize(value, stream) {
|
||||
const length = 1;
|
||||
stream.write(...globalThis.Array(length).fill(0));
|
||||
stream.data_view.setUint8(stream.end - length, value);
|
||||
},
|
||||
*deserialize(stream) {
|
||||
const value = stream.data_view.getUint8(stream.front);
|
||||
stream.read(1);
|
||||
return value;
|
||||
}
|
||||
};
|
||||
PROTO.UInt16 = {
|
||||
*serialize(value, stream) {
|
||||
const length = 2;
|
||||
stream.write(...globalThis.Array(length).fill(0));
|
||||
stream.data_view.setUint16(stream.end - length, value);
|
||||
},
|
||||
*deserialize(stream) {
|
||||
const value = stream.data_view.getUint16(stream.front);
|
||||
stream.read(2);
|
||||
return value;
|
||||
}
|
||||
};
|
||||
PROTO.UInt32 = {
|
||||
*serialize(value, stream) {
|
||||
const length = 4;
|
||||
stream.write(...globalThis.Array(length).fill(0));
|
||||
stream.data_view.setUint32(stream.end - length, value);
|
||||
},
|
||||
*deserialize(stream) {
|
||||
const value = stream.data_view.getUint32(stream.front);
|
||||
stream.read(4);
|
||||
return value;
|
||||
}
|
||||
};
|
||||
PROTO.UVarInt32 = {
|
||||
*serialize(value, stream) {
|
||||
while (value >= 0x80) {
|
||||
stream.write((value & 0x7f) | 0x80);
|
||||
value >>= 7;
|
||||
yield;
|
||||
}
|
||||
stream.write(value);
|
||||
},
|
||||
*deserialize(stream) {
|
||||
let value = 0;
|
||||
let size = 0;
|
||||
let byte;
|
||||
do {
|
||||
byte = stream.read()[0];
|
||||
value |= (byte & 0x7f) << (size * 7);
|
||||
size += 1;
|
||||
yield;
|
||||
} while ((byte & 0x80) !== 0 && size < 10);
|
||||
return value;
|
||||
}
|
||||
};
|
||||
PROTO.Float32 = {
|
||||
*serialize(value, stream) {
|
||||
const length = 4;
|
||||
stream.write(...globalThis.Array(length).fill(0));
|
||||
stream.data_view.setFloat32(stream.end - length, value);
|
||||
},
|
||||
*deserialize(stream) {
|
||||
const value = stream.data_view.getFloat32(stream.front);
|
||||
stream.read(4);
|
||||
return value;
|
||||
}
|
||||
};
|
||||
PROTO.Float64 = {
|
||||
*serialize(value, stream) {
|
||||
const length = 8;
|
||||
stream.write(...globalThis.Array(length).fill(0));
|
||||
stream.data_view.setFloat64(stream.end - length, value);
|
||||
},
|
||||
*deserialize(stream) {
|
||||
const value = stream.data_view.getFloat64(stream.front);
|
||||
stream.read(8);
|
||||
return value;
|
||||
}
|
||||
};
|
||||
PROTO.String = {
|
||||
*serialize(value, stream) {
|
||||
yield* PROTO.UVarInt32.serialize(value.length, stream);
|
||||
for (let i = 0; i < value.length; i++) {
|
||||
const code = value.charCodeAt(i);
|
||||
yield* PROTO.UVarInt32.serialize(code, stream);
|
||||
}
|
||||
},
|
||||
*deserialize(stream) {
|
||||
const length = yield* PROTO.UVarInt32.deserialize(stream);
|
||||
let value = '';
|
||||
for (let i = 0; i < length; i++) {
|
||||
const code = yield* PROTO.UVarInt32.deserialize(stream);
|
||||
value += globalThis.String.fromCharCode(code);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
};
|
||||
PROTO.Boolean = {
|
||||
*serialize(value, stream) {
|
||||
stream.write(value ? 1 : 0);
|
||||
},
|
||||
*deserialize(stream) {
|
||||
const value = stream.read()[0];
|
||||
return value === 1;
|
||||
}
|
||||
};
|
||||
PROTO.UInt8Array = {
|
||||
*serialize(value, stream) {
|
||||
yield* PROTO.UVarInt32.serialize(value.length, stream);
|
||||
stream.write(...value);
|
||||
},
|
||||
*deserialize(stream) {
|
||||
const length = yield* PROTO.UVarInt32.deserialize(stream);
|
||||
return new Uint8Array(stream.read(length));
|
||||
}
|
||||
};
|
||||
PROTO.Date = {
|
||||
*serialize(value, stream) {
|
||||
yield* PROTO.Float64.serialize(value.getTime(), stream);
|
||||
},
|
||||
*deserialize(stream) {
|
||||
return new globalThis.Date(yield* PROTO.Float64.deserialize(stream));
|
||||
}
|
||||
};
|
||||
function Object(obj) {
|
||||
return {
|
||||
*serialize(value, stream) {
|
||||
for (const key in obj) {
|
||||
yield* obj[key].serialize(value[key], stream);
|
||||
}
|
||||
},
|
||||
*deserialize(stream) {
|
||||
const result = {};
|
||||
for (const key in obj) {
|
||||
result[key] = yield* obj[key].deserialize(stream);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
};
|
||||
}
|
||||
PROTO.Object = Object;
|
||||
function Array(value) {
|
||||
return {
|
||||
*serialize(array, stream) {
|
||||
yield* PROTO.UVarInt32.serialize(array.length, stream);
|
||||
for (const item of array) {
|
||||
yield* value.serialize(item, stream);
|
||||
}
|
||||
},
|
||||
*deserialize(stream) {
|
||||
const result = [];
|
||||
const length = yield* PROTO.UVarInt32.deserialize(stream);
|
||||
for (let i = 0; i < length; i++) {
|
||||
result[i] = yield* value.deserialize(stream);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
};
|
||||
}
|
||||
PROTO.Array = Array;
|
||||
function Tuple(...values) {
|
||||
return {
|
||||
*serialize(tuple, stream) {
|
||||
for (let i = 0; i < values.length; i++) {
|
||||
yield* values[i].serialize(tuple[i], stream);
|
||||
}
|
||||
},
|
||||
*deserialize(stream) {
|
||||
const result = [];
|
||||
for (let i = 0; i < values.length; i++) {
|
||||
result[i] = yield* values[i].deserialize(stream);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
};
|
||||
}
|
||||
PROTO.Tuple = Tuple;
|
||||
function Optional(value) {
|
||||
return {
|
||||
*serialize(optional, stream) {
|
||||
yield* PROTO.Boolean.serialize(optional !== undefined, stream);
|
||||
if (optional !== undefined) {
|
||||
yield* value.serialize(optional, stream);
|
||||
}
|
||||
},
|
||||
*deserialize(stream) {
|
||||
const defined = yield* PROTO.Boolean.deserialize(stream);
|
||||
if (defined) {
|
||||
return yield* value.deserialize(stream);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
};
|
||||
}
|
||||
PROTO.Optional = Optional;
|
||||
function Map(key, value) {
|
||||
return {
|
||||
*serialize(map, stream) {
|
||||
yield* PROTO.UVarInt32.serialize(map.size, stream);
|
||||
for (const [k, v] of map.entries()) {
|
||||
yield* key.serialize(k, stream);
|
||||
yield* value.serialize(v, stream);
|
||||
}
|
||||
},
|
||||
*deserialize(stream) {
|
||||
const size = yield* PROTO.UVarInt32.deserialize(stream);
|
||||
const result = new globalThis.Map();
|
||||
for (let i = 0; i < size; i++) {
|
||||
const k = yield* key.deserialize(stream);
|
||||
const v = yield* value.deserialize(stream);
|
||||
result.set(k, v);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
};
|
||||
}
|
||||
PROTO.Map = Map;
|
||||
function Set(value) {
|
||||
return {
|
||||
*serialize(set, stream) {
|
||||
yield* PROTO.UVarInt32.serialize(set.size, stream);
|
||||
for (const [_, v] of set.entries()) {
|
||||
yield* value.serialize(v, stream);
|
||||
}
|
||||
},
|
||||
*deserialize(stream) {
|
||||
const size = yield* PROTO.UVarInt32.deserialize(stream);
|
||||
const result = new globalThis.Set();
|
||||
for (let i = 0; i < size; i++) {
|
||||
const v = yield* value.deserialize(stream);
|
||||
result.add(v);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
};
|
||||
}
|
||||
PROTO.Set = Set;
|
||||
PROTO.Endpoint = PROTO.String;
|
||||
PROTO.Header = PROTO.Object({
|
||||
guid: PROTO.String,
|
||||
encoding: PROTO.String,
|
||||
index: PROTO.UVarInt32,
|
||||
final: PROTO.Boolean
|
||||
});
|
||||
})(PROTO || (PROTO = {}));
|
||||
export var NET;
|
||||
(function (NET) {
|
||||
const FRAG_MAX = 2048;
|
||||
const ENCODING = 'mcbe-ipc:v3';
|
||||
const ENDPOINTS = new Map();
|
||||
function* serialize(byte_queue, max_size = Infinity) {
|
||||
const uint8array = byte_queue.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 <= 0x7f ? 1 : char_code <= 0x7ff ? 2 : char_code <= 0xffff ? 3 : 4;
|
||||
const char_size = char_code > 0xff ? utf16_size : 2;
|
||||
if (acc_size + char_size > max_size) {
|
||||
result.push(acc_str);
|
||||
acc_str = '';
|
||||
acc_size = 0;
|
||||
}
|
||||
if (char_code > 0xff) {
|
||||
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;
|
||||
}
|
||||
NET.serialize = serialize;
|
||||
function* deserialize(strings) {
|
||||
const result = [];
|
||||
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 <= 0xff) {
|
||||
const hex = str[j] + str[++j];
|
||||
const hex_code = parseInt(hex, 16);
|
||||
result.push(hex_code & 0xff);
|
||||
result.push(hex_code >> 8);
|
||||
}
|
||||
else {
|
||||
result.push(char_code & 0xff);
|
||||
result.push(char_code >> 8);
|
||||
}
|
||||
yield;
|
||||
}
|
||||
yield;
|
||||
}
|
||||
return PROTO.ByteQueue.from_uint8array(new Uint8Array(result));
|
||||
}
|
||||
NET.deserialize = deserialize;
|
||||
system.afterEvents.scriptEventReceive.subscribe(event => {
|
||||
system.runJob((function* () {
|
||||
const [serialized_endpoint, serialized_header] = event.id.split(':');
|
||||
const endpoint_stream = yield* PROTO.MIPS.deserialize(serialized_endpoint);
|
||||
const endpoint = yield* PROTO.Endpoint.deserialize(endpoint_stream);
|
||||
const listeners = ENDPOINTS.get(endpoint);
|
||||
if (event.sourceType === ScriptEventSource.Server && listeners) {
|
||||
const header_stream = yield* PROTO.MIPS.deserialize(serialized_header);
|
||||
const header = yield* PROTO.Header.deserialize(header_stream);
|
||||
for (let i = 0; i < listeners.length; i++) {
|
||||
yield* listeners[i](header, event.message);
|
||||
}
|
||||
}
|
||||
})());
|
||||
});
|
||||
function create_listener(endpoint, listener) {
|
||||
let listeners = ENDPOINTS.get(endpoint);
|
||||
if (!listeners) {
|
||||
listeners = new Array();
|
||||
ENDPOINTS.set(endpoint, listeners);
|
||||
}
|
||||
listeners.push(listener);
|
||||
return () => {
|
||||
const idx = listeners.indexOf(listener);
|
||||
if (idx !== -1)
|
||||
listeners.splice(idx, 1);
|
||||
if (listeners.length === 0) {
|
||||
ENDPOINTS.delete(endpoint);
|
||||
}
|
||||
};
|
||||
}
|
||||
function generate_id() {
|
||||
const r = (Math.random() * 0x100000000) >>> 0;
|
||||
return ((r & 0xff).toString(16).padStart(2, '0') +
|
||||
((r >> 8) & 0xff).toString(16).padStart(2, '0') +
|
||||
((r >> 16) & 0xff).toString(16).padStart(2, '0') +
|
||||
((r >> 24) & 0xff).toString(16).padStart(2, '0')).toUpperCase();
|
||||
}
|
||||
function* emit(endpoint, serializer, value) {
|
||||
const guid = generate_id();
|
||||
const endpoint_stream = new PROTO.ByteQueue();
|
||||
yield* PROTO.Endpoint.serialize(endpoint, endpoint_stream);
|
||||
const serialized_endpoint = yield* PROTO.MIPS.serialize(endpoint_stream);
|
||||
const RUN = function* (header, serialized_packet) {
|
||||
const header_stream = new PROTO.ByteQueue();
|
||||
yield* PROTO.Header.serialize(header, header_stream);
|
||||
const serialized_header = yield* PROTO.MIPS.serialize(header_stream);
|
||||
world
|
||||
.getDimension('overworld')
|
||||
.runCommand(`scriptevent ${serialized_endpoint}:${serialized_header} ${serialized_packet}`);
|
||||
};
|
||||
const packet_stream = new PROTO.ByteQueue();
|
||||
yield* serializer.serialize(value, packet_stream);
|
||||
const serialized_packets = yield* serialize(packet_stream, FRAG_MAX);
|
||||
for (let i = 0; i < serialized_packets.length; i++) {
|
||||
const serialized_packet = serialized_packets[i];
|
||||
yield* RUN({ guid, encoding: ENCODING, index: i, final: i === serialized_packets.length - 1 }, serialized_packet);
|
||||
}
|
||||
}
|
||||
NET.emit = emit;
|
||||
function listen(endpoint, serializer, callback) {
|
||||
const buffer = new Map();
|
||||
const listener = function* (payload, serialized_packet) {
|
||||
let fragment = buffer.get(payload.guid);
|
||||
if (!fragment) {
|
||||
fragment = { size: -1, serialized_packets: [], data_size: 0 };
|
||||
buffer.set(payload.guid, fragment);
|
||||
}
|
||||
if (payload.final) {
|
||||
fragment.size = payload.index + 1;
|
||||
}
|
||||
fragment.serialized_packets[payload.index] = serialized_packet;
|
||||
fragment.data_size += payload.index + 1;
|
||||
if (fragment.size !== -1 && fragment.data_size === (fragment.size * (fragment.size + 1)) / 2) {
|
||||
const stream = yield* deserialize(fragment.serialized_packets);
|
||||
const value = yield* serializer.deserialize(stream);
|
||||
yield* callback(value);
|
||||
buffer.delete(payload.guid);
|
||||
}
|
||||
};
|
||||
return create_listener(endpoint, listener);
|
||||
}
|
||||
NET.listen = listen;
|
||||
})(NET || (NET = {}));
|
||||
export var IPC;
|
||||
(function (IPC) {
|
||||
/** Sends a message with `args` to `channel` */
|
||||
function send(channel, serializer, value) {
|
||||
system.runJob(NET.emit(`ipc:${channel}:send`, serializer, value));
|
||||
}
|
||||
IPC.send = send;
|
||||
/** Sends an `invoke` message through IPC, and expects a result asynchronously. */
|
||||
function invoke(channel, serializer, value, deserializer) {
|
||||
system.runJob(NET.emit(`ipc:${channel}:invoke`, serializer, value));
|
||||
return new Promise(resolve => {
|
||||
const terminate = NET.listen(`ipc:${channel}:handle`, deserializer, function* (value) {
|
||||
resolve(value);
|
||||
terminate();
|
||||
});
|
||||
});
|
||||
}
|
||||
IPC.invoke = invoke;
|
||||
/** Listens to `channel`. When a new message arrives, `listener` will be called with `listener(args)`. */
|
||||
function on(channel, deserializer, listener) {
|
||||
return NET.listen(`ipc:${channel}:send`, deserializer, function* (value) {
|
||||
listener(value);
|
||||
});
|
||||
}
|
||||
IPC.on = on;
|
||||
/** Listens to `channel` once. When a new message arrives, `listener` will be called with `listener(args)`, and then removed. */
|
||||
function once(channel, deserializer, listener) {
|
||||
const terminate = NET.listen(`ipc:${channel}:send`, deserializer, function* (value) {
|
||||
listener(value);
|
||||
terminate();
|
||||
});
|
||||
return terminate;
|
||||
}
|
||||
IPC.once = once;
|
||||
/** Adds a handler for an `invoke` IPC. This handler will be called whenever `invoke(channel, ...args)` is called */
|
||||
function handle(channel, deserializer, serializer, listener) {
|
||||
return NET.listen(`ipc:${channel}:invoke`, deserializer, function* (value) {
|
||||
const result = listener(value);
|
||||
yield* NET.emit(`ipc:${channel}:handle`, serializer, result);
|
||||
});
|
||||
}
|
||||
IPC.handle = handle;
|
||||
})(IPC || (IPC = {}));
|
||||
export default IPC;
|
||||
Reference in New Issue
Block a user