mirror of
https://github.com/DinheroDevelopmentGroup/modular-minecraft-proxy.git
synced 2025-02-17 00:21:37 -05:00
MVP / PoC
This commit is contained in:
parent
ff3c30a482
commit
8a58ee0ae5
16 changed files with 627 additions and 101 deletions
|
@ -15,5 +15,22 @@
|
|||
"sourceType": "module",
|
||||
"project": "tsconfig.json"
|
||||
},
|
||||
"rules": {}
|
||||
"rules": {
|
||||
"import/order": [
|
||||
"error",
|
||||
{
|
||||
"groups": [
|
||||
"type",
|
||||
"index",
|
||||
"sibling",
|
||||
"parent",
|
||||
"internal",
|
||||
"builtin",
|
||||
"external",
|
||||
"object"
|
||||
]
|
||||
}
|
||||
],
|
||||
"no-case-declarations": "off"
|
||||
}
|
||||
}
|
13
package-lock.json
generated
13
package-lock.json
generated
|
@ -22,6 +22,7 @@
|
|||
"eslint-plugin-import": "^2.29.1",
|
||||
"eslint-plugin-n": "^16.6.1",
|
||||
"eslint-plugin-promise": "^6.1.1",
|
||||
"eslint-plugin-require-extensions": "^0.1.3",
|
||||
"nodemon": "^3.0.2",
|
||||
"typescript": "^5.3.3"
|
||||
}
|
||||
|
@ -2039,6 +2040,18 @@
|
|||
"eslint": "^7.0.0 || ^8.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-plugin-require-extensions": {
|
||||
"version": "0.1.3",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-require-extensions/-/eslint-plugin-require-extensions-0.1.3.tgz",
|
||||
"integrity": "sha512-T3c1PZ9PIdI3hjV8LdunfYI8gj017UQjzAnCrxuo3wAjneDbTPHdE3oNWInOjMA+z/aBkUtlW5vC0YepYMZIug==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"eslint": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-scope": {
|
||||
"version": "7.2.2",
|
||||
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz",
|
||||
|
|
|
@ -32,10 +32,11 @@
|
|||
"eslint-plugin-import": "^2.29.1",
|
||||
"eslint-plugin-n": "^16.6.1",
|
||||
"eslint-plugin-promise": "^6.1.1",
|
||||
"eslint-plugin-require-extensions": "^0.1.3",
|
||||
"nodemon": "^3.0.2",
|
||||
"typescript": "^5.3.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"minecraft-protocol": "^1.26.5"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
107
src/index.ts
107
src/index.ts
|
@ -1,112 +1,21 @@
|
|||
import { createClient, createServer, states } from 'minecraft-protocol'
|
||||
import Instance from './instance/index.js'
|
||||
import { createServer } from 'minecraft-protocol'
|
||||
|
||||
interface RawPacket {
|
||||
name: string
|
||||
data: unknown
|
||||
}
|
||||
// See: https://nodejs.org/api/worker_threads.html#considerations-when-transferring-typedarrays-and-buffers
|
||||
Object.assign(Uint8Array.prototype, Buffer.prototype)
|
||||
|
||||
const VERSION = '1.19.4'
|
||||
|
||||
const SERVER = {
|
||||
export const SERVER_OPTIONS = {
|
||||
host: '127.0.0.1',
|
||||
port: 25565,
|
||||
keepAlive: false,
|
||||
version: VERSION
|
||||
}
|
||||
|
||||
const TARGET = {
|
||||
host: 'kaboom.pw',
|
||||
port: 25565,
|
||||
keepAlive: false,
|
||||
username: 'Player137',
|
||||
version: VERSION
|
||||
}
|
||||
|
||||
const server = createServer(SERVER)
|
||||
export const server = createServer(SERVER_OPTIONS)
|
||||
|
||||
server.on('login', client => {
|
||||
const target = createClient(TARGET)
|
||||
|
||||
client.on('end', reason => {
|
||||
console.info('Client disconnected:', reason)
|
||||
|
||||
target.end(reason)
|
||||
})
|
||||
|
||||
target.on('end', reason => {
|
||||
console.info('Target disconnected:', reason)
|
||||
|
||||
client.end(reason)
|
||||
})
|
||||
|
||||
client.on('error', error => {
|
||||
console.error('Client error:', error)
|
||||
})
|
||||
|
||||
target.on('error', error => {
|
||||
console.error('Target error:', error)
|
||||
})
|
||||
|
||||
client.on('packet', (data, meta) => {
|
||||
if (meta.state !== states.PLAY) return
|
||||
|
||||
sendTargetPacket({
|
||||
name: meta.name,
|
||||
data
|
||||
})
|
||||
})
|
||||
|
||||
target.on('packet', (data, meta) => {
|
||||
if (meta.state !== states.PLAY) return
|
||||
|
||||
sendClientPacket({
|
||||
name: meta.name,
|
||||
data
|
||||
})
|
||||
})
|
||||
|
||||
const CLIENT_QUEUE: RawPacket[] = []
|
||||
const TARGET_QUEUE: RawPacket[] = []
|
||||
|
||||
client.on('state', state => {
|
||||
if (state !== states.PLAY) return
|
||||
|
||||
for (const packet of CLIENT_QUEUE) client.write(packet.name, packet.data)
|
||||
|
||||
CLIENT_QUEUE.length = 0
|
||||
})
|
||||
|
||||
target.on('state', state => {
|
||||
if (state !== states.PLAY) return
|
||||
|
||||
for (const packet of TARGET_QUEUE) target.write(packet.name, packet.data)
|
||||
|
||||
TARGET_QUEUE.length = 0
|
||||
})
|
||||
|
||||
function sendClientPacket (packet: RawPacket): void {
|
||||
if (client.state !== states.PLAY) {
|
||||
CLIENT_QUEUE.push(packet)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
client.write(
|
||||
packet.name,
|
||||
packet.data
|
||||
)
|
||||
}
|
||||
|
||||
function sendTargetPacket (packet: RawPacket): void {
|
||||
if (target.state !== states.PLAY) {
|
||||
TARGET_QUEUE.push(packet)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
target.write(
|
||||
packet.name,
|
||||
packet.data
|
||||
)
|
||||
}
|
||||
// eslint-disable-next-line no-new
|
||||
new Instance(client)
|
||||
})
|
||||
|
|
91
src/instance/index.ts
Normal file
91
src/instance/index.ts
Normal file
|
@ -0,0 +1,91 @@
|
|||
import { type Message } from '../worker/parent.js'
|
||||
import { importModulesGenerator } from '../util/import-modules.js'
|
||||
import { Channel } from '../util/channel.js'
|
||||
import { dirname, resolve } from 'path'
|
||||
import { fileURLToPath } from 'url'
|
||||
import { Worker } from 'worker_threads'
|
||||
import { createClient, type ServerClient } from 'minecraft-protocol'
|
||||
|
||||
if (!('require' in globalThis)) {
|
||||
globalThis.__filename = fileURLToPath(import.meta.url)
|
||||
globalThis.__dirname = dirname(__filename)
|
||||
}
|
||||
|
||||
const WORKER_PATH = resolve(__dirname, '../worker')
|
||||
const MODULE_DIR_PATH = resolve(__dirname, '../module')
|
||||
|
||||
const VERSION = '1.19.4'
|
||||
|
||||
export const TARGET_OPTIONS = {
|
||||
host: 'grandma-does.tech',
|
||||
port: 25565,
|
||||
keepAlive: false,
|
||||
username: 'Player137',
|
||||
version: VERSION
|
||||
}
|
||||
|
||||
export class Instance {
|
||||
public readonly client
|
||||
public readonly server
|
||||
public readonly worker
|
||||
|
||||
constructor (client: ServerClient) {
|
||||
this.client = client
|
||||
|
||||
const target = createClient(TARGET_OPTIONS)
|
||||
this.server = target
|
||||
|
||||
target.on('error', error => {
|
||||
console.error('Target error:', error)
|
||||
})
|
||||
|
||||
const worker = new Worker(WORKER_PATH)
|
||||
this.worker = worker
|
||||
|
||||
worker.on('error', error => {
|
||||
console.error('Worker error:', error)
|
||||
})
|
||||
|
||||
void this._importModules()
|
||||
}
|
||||
|
||||
private async _importModules (): Promise<void> {
|
||||
for await (const module of importModulesGenerator(MODULE_DIR_PATH, 'global.js')) {
|
||||
if (module === null) throw new Error('Expected module not to be null')
|
||||
if (typeof module !== 'object') throw new Error('Expected module to be an object')
|
||||
|
||||
if (!('default' in module)) throw new Error('Expected default export')
|
||||
|
||||
const f = module.default
|
||||
|
||||
if (typeof f !== 'function') throw new Error('Expected default export to be a function')
|
||||
|
||||
await (f as (instance: Instance) => Promise<void>)(this)
|
||||
}
|
||||
}
|
||||
|
||||
protected postMessage (channel: string, data: any): void {
|
||||
this.worker.postMessage({
|
||||
channel,
|
||||
data
|
||||
} satisfies Message)
|
||||
}
|
||||
|
||||
public createChannel<T> (id: string): Channel<T> {
|
||||
const channel = new Channel<T>(id)
|
||||
|
||||
channel._subscribe(data => {
|
||||
this.postMessage(id, data)
|
||||
})
|
||||
|
||||
this.worker.on('message', (message: Message<T>) => {
|
||||
if (message.channel !== id) return
|
||||
|
||||
channel._write(message.data)
|
||||
})
|
||||
|
||||
return channel
|
||||
}
|
||||
}
|
||||
|
||||
export default Instance
|
122
src/module/proxy/global.ts
Normal file
122
src/module/proxy/global.ts
Normal file
|
@ -0,0 +1,122 @@
|
|||
import type Instance from '../../instance/index.js'
|
||||
import { type Message } from './shared.js'
|
||||
import { type RawPacket } from '../../util/packet.js'
|
||||
import { states } from 'minecraft-protocol'
|
||||
|
||||
export default async function (instance: Instance): Promise<void> {
|
||||
const clientQueue: RawPacket[] = []
|
||||
const serverQueue: RawPacket[] = []
|
||||
|
||||
const channel = instance.createChannel<Message>('proxy')
|
||||
|
||||
const client = instance.client
|
||||
const server = instance.server
|
||||
const worker = instance.worker
|
||||
|
||||
client.on('end', reason => {
|
||||
console.info('Client disconnected:', reason)
|
||||
|
||||
server.end(reason)
|
||||
|
||||
void worker.terminate()
|
||||
})
|
||||
|
||||
server.on('end', reason => {
|
||||
console.info('Target disconnected:', reason)
|
||||
|
||||
client.end(reason)
|
||||
|
||||
void worker.terminate()
|
||||
})
|
||||
|
||||
client.on('error', error => {
|
||||
console.error('Client error:', error)
|
||||
})
|
||||
|
||||
server.on('error', error => {
|
||||
console.error('Target error:', error)
|
||||
})
|
||||
|
||||
client.on('packet', (data, meta) => {
|
||||
if (meta.state !== states.PLAY) return
|
||||
|
||||
channel.write({
|
||||
side: 'client',
|
||||
packet: {
|
||||
name: meta.name,
|
||||
data
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
server.on('packet', (data, meta) => {
|
||||
if (meta.state !== states.PLAY) return
|
||||
|
||||
channel.write({
|
||||
side: 'server',
|
||||
packet: {
|
||||
name: meta.name,
|
||||
data
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
client.on('state', state => {
|
||||
if (state !== states.PLAY) return
|
||||
|
||||
const queue = clientQueue
|
||||
|
||||
for (const packet of queue) client.write(packet.name, packet.data)
|
||||
|
||||
queue.length = 0
|
||||
})
|
||||
|
||||
server.on('state', state => {
|
||||
if (state !== states.PLAY) return
|
||||
|
||||
const queue = serverQueue
|
||||
|
||||
for (const packet of queue) server.write(packet.name, packet.data)
|
||||
|
||||
queue.length = 0
|
||||
})
|
||||
|
||||
channel.subscribe(({ side, packet }) => {
|
||||
switch (side) {
|
||||
case 'client':
|
||||
writeClientPacket(packet)
|
||||
break
|
||||
case 'server':
|
||||
writeServerPacket(packet)
|
||||
break
|
||||
default:
|
||||
throw new Error(`Invalid side: ${side as any}`)
|
||||
}
|
||||
})
|
||||
|
||||
function writeClientPacket (packet: RawPacket): void {
|
||||
if (client.state !== states.PLAY) {
|
||||
clientQueue.push(packet)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
client.write(
|
||||
packet.name,
|
||||
packet.data
|
||||
)
|
||||
}
|
||||
|
||||
function writeServerPacket (packet: RawPacket): void {
|
||||
if (server.state !== states.PLAY) {
|
||||
serverQueue.push(packet)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
server.write(
|
||||
packet.name,
|
||||
packet.data
|
||||
)
|
||||
}
|
||||
}
|
68
src/module/proxy/local.ts
Normal file
68
src/module/proxy/local.ts
Normal file
|
@ -0,0 +1,68 @@
|
|||
import { type Side, type Message } from './shared.js'
|
||||
import { type AsyncVoid, EventHandler } from '../../util/events.js'
|
||||
import { createChannel } from '../../worker/parent.js'
|
||||
import { Packet, type RawPacket } from '../../util/packet.js'
|
||||
|
||||
export type EventMap = Record<string, (packet: Packet) => AsyncVoid>
|
||||
|
||||
export class EventEmitter extends EventHandler<EventMap> {
|
||||
public async emit (name: string, packet: Packet): Promise<void> {
|
||||
await this._emit(name, packet)
|
||||
}
|
||||
}
|
||||
|
||||
// ? Should I export the channel
|
||||
export const channel = createChannel<Message>('proxy')
|
||||
|
||||
export class Proxy {
|
||||
public readonly client = new EventEmitter()
|
||||
public readonly server = new EventEmitter()
|
||||
|
||||
constructor () {
|
||||
channel.subscribe(({ side, packet: raw }: Message) => {
|
||||
void (async () => {
|
||||
const emitter = this[side]
|
||||
|
||||
const packet = new Packet(raw.name, raw.data)
|
||||
|
||||
await emitter.emit('packet', packet)
|
||||
await emitter.emit(packet.name, packet)
|
||||
|
||||
if (packet.canceled) return
|
||||
|
||||
switch (side) {
|
||||
case 'client':
|
||||
side = 'server'
|
||||
break
|
||||
case 'server':
|
||||
side = 'client'
|
||||
break
|
||||
default:
|
||||
throw new Error(`Invalid side: ${side as any}`)
|
||||
}
|
||||
|
||||
channel.write({
|
||||
side,
|
||||
packet
|
||||
})
|
||||
})()
|
||||
})
|
||||
}
|
||||
|
||||
protected write (side: Side, packet: RawPacket): void {
|
||||
channel.write({
|
||||
side,
|
||||
packet
|
||||
})
|
||||
}
|
||||
|
||||
public writeClient (name: string, data: unknown): void {
|
||||
this.write('client', { name, data })
|
||||
}
|
||||
|
||||
public writeServer (name: string, data: unknown): void {
|
||||
this.write('server', { name, data })
|
||||
}
|
||||
}
|
||||
|
||||
export default new Proxy()
|
8
src/module/proxy/shared.ts
Normal file
8
src/module/proxy/shared.ts
Normal file
|
@ -0,0 +1,8 @@
|
|||
import { type RawPacket } from '../../util/packet.js'
|
||||
|
||||
export type Side = 'client' | 'server'
|
||||
|
||||
export interface Message {
|
||||
side: Side
|
||||
packet: RawPacket
|
||||
}
|
41
src/util/channel.ts
Normal file
41
src/util/channel.ts
Normal file
|
@ -0,0 +1,41 @@
|
|||
// _x -> x
|
||||
// x -> _x
|
||||
export class Channel<T> {
|
||||
public readonly id
|
||||
|
||||
constructor (id: string) {
|
||||
this.id = id
|
||||
}
|
||||
|
||||
private readonly listeners = new Set<(data: T) => void>()
|
||||
|
||||
public subscribe (listener: (data: T) => void): void {
|
||||
this.listeners.add(listener)
|
||||
}
|
||||
|
||||
public unsubscribe (listener: (data: T) => void): void {
|
||||
this.listeners.delete(listener)
|
||||
}
|
||||
|
||||
public write (data: T): void {
|
||||
for (const listener of this._listeners) {
|
||||
listener(data)
|
||||
}
|
||||
}
|
||||
|
||||
private readonly _listeners = new Set<(data: T) => void>()
|
||||
|
||||
public _subscribe (listener: (data: T) => void): void {
|
||||
this._listeners.add(listener)
|
||||
}
|
||||
|
||||
public _unsubscribe (listener: (data: T) => void): void {
|
||||
this._listeners.delete(listener)
|
||||
}
|
||||
|
||||
public _write (data: T): void {
|
||||
for (const listener of this.listeners) {
|
||||
listener(data)
|
||||
}
|
||||
}
|
||||
}
|
42
src/util/events.ts
Normal file
42
src/util/events.ts
Normal file
|
@ -0,0 +1,42 @@
|
|||
export type AsyncVoid = void | Promise<void>
|
||||
|
||||
export type EventMap<T> = { [K in keyof T]: (...args: any[]) => AsyncVoid }
|
||||
|
||||
export class EventHandler<T extends EventMap<T>> {
|
||||
protected map = new Map<keyof T, Set<T[keyof T]>>()
|
||||
|
||||
protected async _emit<E extends keyof T> (name: E, ...data: Parameters<T[E]>): Promise<void> {
|
||||
const map = this.map
|
||||
|
||||
const set = map.get(name)
|
||||
|
||||
if (set === undefined) return
|
||||
|
||||
for (const listener of set) await listener(...data)
|
||||
}
|
||||
|
||||
public on<E extends keyof T> (name: E, callback: T[E]): void {
|
||||
const map = this.map
|
||||
|
||||
const set = map.get(name) ?? new Set()
|
||||
map.set(name, set)
|
||||
|
||||
set.add(callback)
|
||||
}
|
||||
|
||||
public off<E extends keyof T> (name: E, callback: T[E]): void {
|
||||
const map = this.map
|
||||
|
||||
const set = map.get(name)
|
||||
|
||||
if (set === undefined) return
|
||||
|
||||
set.delete(callback)
|
||||
}
|
||||
|
||||
public clear (): void {
|
||||
const map = this.map
|
||||
|
||||
map.clear()
|
||||
}
|
||||
}
|
22
src/util/file.ts
Normal file
22
src/util/file.ts
Normal file
|
@ -0,0 +1,22 @@
|
|||
import fs, { access, constants } from 'fs/promises'
|
||||
import { resolve } from 'path'
|
||||
|
||||
export async function * getAllFiles (dir: string): AsyncIterable<string> {
|
||||
const entries = await fs.readdir(dir, { withFileTypes: true })
|
||||
|
||||
for (const entry of entries) {
|
||||
const res = resolve(dir, entry.name)
|
||||
|
||||
if (entry.isDirectory()) {
|
||||
yield * getAllFiles(res)
|
||||
} else {
|
||||
yield res
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export async function exists (path: string): Promise<boolean> {
|
||||
return await access(path, constants.F_OK)
|
||||
.then(() => true)
|
||||
.catch(() => false)
|
||||
}
|
26
src/util/import-modules.ts
Normal file
26
src/util/import-modules.ts
Normal file
|
@ -0,0 +1,26 @@
|
|||
// export async function * importStarGenerator (directory: string): AsyncGenerator<unknown> {
|
||||
// for await (const file of getAllFiles(directory)) {
|
||||
// yield await import(file)
|
||||
// }
|
||||
// }
|
||||
|
||||
import { exists } from './file.js'
|
||||
import { readdir } from 'fs/promises'
|
||||
import { resolve } from 'path'
|
||||
|
||||
export async function * importModulesGenerator (directory: string, index: string): AsyncGenerator<unknown> {
|
||||
for (const entry of await readdir(directory, { withFileTypes: true })) {
|
||||
const path = resolve(entry.path, entry.name, index)
|
||||
|
||||
if (!entry.isDirectory()) throw new Error(`Expected ${path} to be a directory`)
|
||||
|
||||
if (!await exists(path)) continue
|
||||
|
||||
yield await import(path)
|
||||
}
|
||||
}
|
||||
|
||||
export async function importModules (directory: string, index: string): Promise<void> {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
for await (const _ of importModulesGenerator(directory, index));
|
||||
}
|
16
src/util/packet.ts
Normal file
16
src/util/packet.ts
Normal file
|
@ -0,0 +1,16 @@
|
|||
export interface RawPacket {
|
||||
name: string
|
||||
data: unknown
|
||||
}
|
||||
|
||||
export class Packet<T = unknown> {
|
||||
public name
|
||||
public data
|
||||
|
||||
public canceled: boolean = false
|
||||
|
||||
constructor (name: string, data: T) {
|
||||
this.name = name
|
||||
this.data = data
|
||||
}
|
||||
}
|
12
src/worker/index.ts
Normal file
12
src/worker/index.ts
Normal file
|
@ -0,0 +1,12 @@
|
|||
import { importModules } from '../util/import-modules.js'
|
||||
import { dirname, resolve } from 'path'
|
||||
import { fileURLToPath } from 'url'
|
||||
|
||||
if (!('require' in globalThis)) {
|
||||
globalThis.__filename = fileURLToPath(import.meta.url)
|
||||
globalThis.__dirname = dirname(__filename)
|
||||
}
|
||||
|
||||
const MODULE_DIR_PATH = resolve(__dirname, '../module')
|
||||
|
||||
await importModules(MODULE_DIR_PATH, 'local.js')
|
104
src/worker/module/chat.ts
Normal file
104
src/worker/module/chat.ts
Normal file
|
@ -0,0 +1,104 @@
|
|||
// import { type UUID } from 'crypto'
|
||||
// import { type AsyncVoid, EventHandler } from '../../util/events.js'
|
||||
// import { type Packet } from '../util/packet.js'
|
||||
// import proxy from './proxy.js'
|
||||
|
||||
// export type i64 = [number, number]
|
||||
|
||||
// export interface ClientChatPacketData {
|
||||
// timestamp: i64
|
||||
// salt: i64
|
||||
// acknowledged: Uint8Array
|
||||
// }
|
||||
|
||||
// export type Signature = Uint8Array
|
||||
|
||||
// export interface ClientChatMessagePacketData extends ClientChatPacketData {
|
||||
// message: string
|
||||
// signature: Signature | undefined
|
||||
// offset: number
|
||||
// }
|
||||
|
||||
// export interface ClientChatCommandPacketData extends ClientChatPacketData {
|
||||
// command: string
|
||||
// argumentSignatures: Signature[]
|
||||
// messageCount: number
|
||||
// }
|
||||
|
||||
// export interface ProfileLessChatPacketData {
|
||||
// message: string
|
||||
// type: number
|
||||
// name: string
|
||||
// // TODO: find out what target is supposed to be
|
||||
// target: undefined
|
||||
// }
|
||||
|
||||
// export interface PlayerChatPacketData {
|
||||
// senderUuid: UUID
|
||||
// index: number
|
||||
// signature: Signature | undefined
|
||||
// plainMessage: string
|
||||
// timestamp: i64
|
||||
// salt: i64
|
||||
// // TODO: find out what previousMessages is supposed to be
|
||||
// previousMessages: never[]
|
||||
// unsignedChatContent: string
|
||||
// filterType: number
|
||||
// // TODO: Find out what filterTypeMask is supposed to be
|
||||
// filterTypeMask: undefined
|
||||
// type: number
|
||||
// networkName: string
|
||||
// // TODO: find out what networkTargetName is supposed to be
|
||||
// networkTargetName: undefined
|
||||
// }
|
||||
|
||||
// export interface SystemChatPacketData {
|
||||
// content: string
|
||||
// isActionBar: boolean
|
||||
// }
|
||||
|
||||
// export interface ChatEventMap {
|
||||
// 'client.command': (packet: Packet<ClientChatCommandPacketData>) => AsyncVoid
|
||||
// 'client.message': (packet: Packet<ClientChatMessagePacketData>) => AsyncVoid
|
||||
|
||||
// 'server.profiless': (packet: Packet<ProfileLessChatPacketData>) => AsyncVoid
|
||||
// 'server.player': (packet: Packet<PlayerChatPacketData>) => AsyncVoid
|
||||
// 'server.system': (packet: Packet<SystemChatPacketData>) => AsyncVoid
|
||||
// }
|
||||
|
||||
// export class Chat extends EventHandler<ChatEventMap> {
|
||||
// constructor () {
|
||||
// super()
|
||||
|
||||
// proxy.client.on('chat_command', async packet => {
|
||||
// await this._emit('client.command', packet as Packet<ClientChatCommandPacketData>)
|
||||
// })
|
||||
|
||||
// proxy.client.on('chat_message', async packet => {
|
||||
// await this._emit('client.message', packet as Packet<ClientChatMessagePacketData>)
|
||||
// })
|
||||
|
||||
// proxy.server.on('profileless_chat', async packet => {
|
||||
// await this._emit('server.profiless', packet as Packet<ProfileLessChatPacketData>)
|
||||
// })
|
||||
|
||||
// proxy.server.on('player_chat', async packet => {
|
||||
// await this._emit('server.player', packet as Packet<PlayerChatPacketData>)
|
||||
// })
|
||||
|
||||
// proxy.server.on('system_chat', async packet => {
|
||||
// await this._emit('server.system', packet as Packet<SystemChatPacketData>)
|
||||
// })
|
||||
// }
|
||||
|
||||
// // // TODO: Fix
|
||||
// // public writeClientCommand (command: string): void {
|
||||
// // proxy.writeClient('chat_command', { command })
|
||||
// // }
|
||||
|
||||
// // public writeClientMessage (message: string): void {
|
||||
// // proxy.writeClient('chat_message', { message })
|
||||
// // }
|
||||
// }
|
||||
|
||||
// export default new Chat()
|
34
src/worker/parent.ts
Normal file
34
src/worker/parent.ts
Normal file
|
@ -0,0 +1,34 @@
|
|||
import { Channel } from '../util/channel.js'
|
||||
import { parentPort } from 'worker_threads'
|
||||
|
||||
if (parentPort === null) throw new Error('Must run in worker thread')
|
||||
|
||||
const port = parentPort
|
||||
|
||||
export interface Message<T = any> {
|
||||
channel: string
|
||||
data: T
|
||||
}
|
||||
|
||||
function postMessage (channel: string, data: any): void {
|
||||
port.postMessage({
|
||||
channel,
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
export function createChannel<T> (id: string): Channel<T> {
|
||||
const channel = new Channel<T>(id)
|
||||
|
||||
channel._subscribe(message => {
|
||||
postMessage(id, message)
|
||||
})
|
||||
|
||||
port.on('message', (message: Message<T>) => {
|
||||
if (message.channel !== id) return
|
||||
|
||||
channel._write(message.data)
|
||||
})
|
||||
|
||||
return channel
|
||||
}
|
Loading…
Reference in a new issue