mirror of
https://github.com/DinheroDevelopmentGroup/modular-minecraft-proxy.git
synced 2024-11-23 07:38:18 -05:00
refactor
I actually wrote a package manager but then my shit got fucked and I lost motivation to do it again but now I have motivation again (thanks modern medicine)
This commit is contained in:
parent
1a58d1c126
commit
cc9c274325
30 changed files with 1569 additions and 2604 deletions
|
@ -1,36 +0,0 @@
|
||||||
{
|
|
||||||
"env": {
|
|
||||||
"es2021": true,
|
|
||||||
"node": true
|
|
||||||
},
|
|
||||||
"extends": [
|
|
||||||
"standard-with-typescript",
|
|
||||||
"plugin:require-extensions/recommended"
|
|
||||||
],
|
|
||||||
"plugins": [
|
|
||||||
"require-extensions"
|
|
||||||
],
|
|
||||||
"parserOptions": {
|
|
||||||
"ecmaVersion": "latest",
|
|
||||||
"sourceType": "module",
|
|
||||||
"project": "tsconfig.json"
|
|
||||||
},
|
|
||||||
"rules": {
|
|
||||||
"import/order": [
|
|
||||||
"error",
|
|
||||||
{
|
|
||||||
"groups": [
|
|
||||||
"type",
|
|
||||||
"index",
|
|
||||||
"sibling",
|
|
||||||
"parent",
|
|
||||||
"internal",
|
|
||||||
"builtin",
|
|
||||||
"external",
|
|
||||||
"object"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"no-case-declarations": "off"
|
|
||||||
}
|
|
||||||
}
|
|
3
.prettierrc
Normal file
3
.prettierrc
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
"singleQuote": true
|
||||||
|
}
|
8
.swcrc
8
.swcrc
|
@ -14,13 +14,9 @@
|
||||||
"target": "esnext",
|
"target": "esnext",
|
||||||
"loose": true
|
"loose": true
|
||||||
},
|
},
|
||||||
"exclude": [
|
"exclude": ["\\.js$", "\\.d\\.ts$", "node_modules"],
|
||||||
"\\.js$",
|
|
||||||
"\\.d\\.ts$",
|
|
||||||
"node_modules"
|
|
||||||
],
|
|
||||||
"module": {
|
"module": {
|
||||||
"type": "es6"
|
"type": "es6"
|
||||||
},
|
},
|
||||||
"minify": false
|
"minify": false
|
||||||
}
|
}
|
||||||
|
|
5
.vscode/settings.json
vendored
5
.vscode/settings.json
vendored
|
@ -1,5 +0,0 @@
|
||||||
{
|
|
||||||
"files.exclude": {
|
|
||||||
"src/module/*": false
|
|
||||||
}
|
|
||||||
}
|
|
21
LICENSE
21
LICENSE
|
@ -1,21 +0,0 @@
|
||||||
MIT License
|
|
||||||
|
|
||||||
Copyright (c) 2024 Dinhero21
|
|
||||||
|
|
||||||
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.
|
|
|
@ -1,4 +1,5 @@
|
||||||
# MMP
|
# MMP
|
||||||
|
|
||||||
- Modular Minecraft Proxy
|
- Modular Minecraft Proxy
|
||||||
|
|
||||||
# How does this differ from other proxies (such as SMP)?
|
# How does this differ from other proxies (such as SMP)?
|
||||||
|
@ -13,7 +14,7 @@ No more cleint, sever or posiotin!
|
||||||
|
|
||||||
For every client that connects a new thread is created.
|
For every client that connects a new thread is created.
|
||||||
|
|
||||||
While also offering minimal performance gains (as there probably isn't (and shouldn't) be ever more then one client) makes it so that program instances client-based instead of being shared between all clients.
|
While also offering minimal performance gains (as there probably isn't (and shouldn't) be ever more then one client) makes it so that program instances client-based instead of being shared between all clients.
|
||||||
|
|
||||||
This makes a Singleton-like architecture possible without needing to worry about multiple instances of the same plugin and explicit declaration of dependencies.
|
This makes a Singleton-like architecture possible without needing to worry about multiple instances of the same plugin and explicit declaration of dependencies.
|
||||||
|
|
||||||
|
|
6
TODO.md
Normal file
6
TODO.md
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
- [x] TypeScript
|
||||||
|
- [x] Client
|
||||||
|
- [x] Server
|
||||||
|
- [x] Packet Forwarding
|
||||||
|
- [x] Module Loading
|
||||||
|
- [ ] Module Manager
|
46
eslint.config.mjs
Normal file
46
eslint.config.mjs
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
// @ts-check
|
||||||
|
|
||||||
|
import eslint from '@eslint/js';
|
||||||
|
import prettier from 'eslint-config-prettier';
|
||||||
|
import simpleImportSort from 'eslint-plugin-simple-import-sort';
|
||||||
|
import globals from 'globals';
|
||||||
|
import tseslint from 'typescript-eslint';
|
||||||
|
|
||||||
|
export default tseslint.config(
|
||||||
|
{ languageOptions: { globals: globals.browser } },
|
||||||
|
eslint.configs.recommended,
|
||||||
|
...tseslint.configs.recommended,
|
||||||
|
{
|
||||||
|
plugins: {
|
||||||
|
'simple-import-sort': simpleImportSort,
|
||||||
|
},
|
||||||
|
rules: {
|
||||||
|
'simple-import-sort/exports': 'off',
|
||||||
|
'simple-import-sort/imports': 'error',
|
||||||
|
'no-case-declarations': 'off',
|
||||||
|
'@typescript-eslint/no-explicit-any': 'off',
|
||||||
|
'no-constant-condition': ['error', { checkLoops: false }],
|
||||||
|
'@typescript-eslint/no-unused-vars': [
|
||||||
|
'error',
|
||||||
|
{
|
||||||
|
args: 'all',
|
||||||
|
argsIgnorePattern: '^_',
|
||||||
|
caughtErrors: 'all',
|
||||||
|
caughtErrorsIgnorePattern: '^_',
|
||||||
|
destructuredArrayIgnorePattern: '^_',
|
||||||
|
varsIgnorePattern: '^_',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'@typescript-eslint/no-floating-promises': 'error',
|
||||||
|
'@typescript-eslint/no-misused-promises': 'error',
|
||||||
|
'object-shorthand': 'error',
|
||||||
|
'@typescript-eslint/consistent-type-imports': 'error',
|
||||||
|
},
|
||||||
|
languageOptions: {
|
||||||
|
parserOptions: {
|
||||||
|
project: './tsconfig.json',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
prettier,
|
||||||
|
);
|
3176
package-lock.json
generated
3176
package-lock.json
generated
File diff suppressed because it is too large
Load diff
43
package.json
43
package.json
|
@ -1,47 +1,44 @@
|
||||||
{
|
{
|
||||||
"name": "modular-minecraft-proxy",
|
"name": "modular-minecraft-proxy",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"description": "Lego Minecraft",
|
"description": "Modular Minecraft Proxy",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"type": "module",
|
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "echo \"Error: no test specified\" && exit 1",
|
"test": "echo \"Error: no test specified\" && exit 1",
|
||||||
"build": "scripty",
|
"build": "scripty",
|
||||||
"watch:build": "scripty",
|
"watch:build": "scripty",
|
||||||
"copy": "scripty",
|
"lint": "scripty",
|
||||||
"watch:copy": "scripty",
|
|
||||||
"run": "scripty",
|
"run": "scripty",
|
||||||
"watch:run": "scripty",
|
"watch:run": "scripty",
|
||||||
"watch": "scripty"
|
"watch": "scripty"
|
||||||
},
|
},
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
"author": "",
|
"author": "Dinhero Development Group",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"type": "module",
|
||||||
"config": {
|
"config": {
|
||||||
"scripty": {
|
"scripty": {
|
||||||
"parallel": true,
|
"parallel": true,
|
||||||
"path": "script"
|
"path": "script"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
|
||||||
"@swc/cli": "^0.1.63",
|
|
||||||
"@swc/core": "^1.3.102",
|
|
||||||
"@types/node": "^20.16.2",
|
|
||||||
"@typescript-eslint/eslint-plugin": "^6.17.0",
|
|
||||||
"@typescript-eslint/parser": "^6.17.0",
|
|
||||||
"eslint": "^8.56.0",
|
|
||||||
"eslint-config-standard-with-typescript": "^43.0.0",
|
|
||||||
"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": {
|
"dependencies": {
|
||||||
|
"minecraft-protocol": "^1.47.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@eslint/js": "^9.9.1",
|
||||||
|
"@swc/cli": "^0.4.0",
|
||||||
|
"@swc/core": "^1.7.22",
|
||||||
"@swc/helpers": "^0.5.12",
|
"@swc/helpers": "^0.5.12",
|
||||||
"chalk": "^5.3.0",
|
"@types/node": "^22.5.1",
|
||||||
"minecraft-protocol": "^1.26.5",
|
"eslint": "^9.9.1",
|
||||||
"scripty": "^2.1.1"
|
"eslint-config-prettier": "^9.1.0",
|
||||||
|
"eslint-plugin-simple-import-sort": "^12.1.1",
|
||||||
|
"globals": "^15.9.0",
|
||||||
|
"nodemon": "^3.1.4",
|
||||||
|
"prettier": "^3.3.3",
|
||||||
|
"scripty": "^2.1.1",
|
||||||
|
"typescript": "^5.5.4",
|
||||||
|
"typescript-eslint": "^8.3.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
swc src -d dist $@
|
# --delete-dir-on-start breaks nodemon
|
||||||
|
swc src -d dist --strip-leading-paths $@
|
||||||
|
|
|
@ -1,11 +0,0 @@
|
||||||
#!/usr/bin/env bash
|
|
||||||
|
|
||||||
SOURCE="$1"
|
|
||||||
|
|
||||||
: ${SOURCE:=src}
|
|
||||||
|
|
||||||
TARGET="$2"
|
|
||||||
|
|
||||||
: ${TARGET:=dist}
|
|
||||||
|
|
||||||
rsync -rav --exclude="*.ts" $SOURCE/ $TARGET/
|
|
4
script/lint.sh
Executable file
4
script/lint.sh
Executable file
|
@ -0,0 +1,4 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
eslint --fix src
|
||||||
|
prettier --write src
|
|
@ -1,3 +1,3 @@
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
node dist/index.js
|
node dist/index.js $@
|
||||||
|
|
|
@ -1,3 +0,0 @@
|
||||||
./script/copy.sh
|
|
||||||
|
|
||||||
nodemon --watch src --exec "./script/copy.sh $@"
|
|
22
src/index.ts
22
src/index.ts
|
@ -1,19 +1,19 @@
|
||||||
import Instance from './instance/index.js'
|
import { createServer } from 'minecraft-protocol';
|
||||||
import { SERVER_OPTIONS } from './settings.js'
|
|
||||||
import { createServer } from 'minecraft-protocol'
|
import Instance from './instance/index.js';
|
||||||
|
import { SERVER_OPTIONS } from './settings.js';
|
||||||
|
|
||||||
// See: https://nodejs.org/api/worker_threads.html#considerations-when-transferring-typedarrays-and-buffers
|
// See: https://nodejs.org/api/worker_threads.html#considerations-when-transferring-typedarrays-and-buffers
|
||||||
Object.assign(Uint8Array.prototype, Buffer.prototype)
|
Object.assign(Uint8Array.prototype, Buffer.prototype);
|
||||||
|
|
||||||
export const server = createServer({
|
export const server = createServer({
|
||||||
'online-mode': false,
|
'online-mode': false,
|
||||||
...SERVER_OPTIONS,
|
...SERVER_OPTIONS,
|
||||||
keepAlive: false
|
keepAlive: false,
|
||||||
})
|
});
|
||||||
|
|
||||||
server.on('login', client => {
|
server.on('login', (client) => {
|
||||||
console.info(`${client.username} has connected!`)
|
console.info(`${client.username} has connected!`);
|
||||||
|
|
||||||
// eslint-disable-next-line no-new
|
new Instance(client);
|
||||||
new Instance(client)
|
});
|
||||||
})
|
|
||||||
|
|
|
@ -1,140 +1,147 @@
|
||||||
import { type Message } from '../worker/parent.js'
|
import chalk from 'chalk';
|
||||||
import { importModulesGenerator } from '../util/import-modules.js'
|
import { createClient, type ServerClient } from 'minecraft-protocol';
|
||||||
import { Channel } from '../util/channel.js'
|
import { dirname, resolve } from 'path';
|
||||||
import { TARGET_OPTIONS } from '../settings.js'
|
import { fileURLToPath } from 'url';
|
||||||
import { dirname, resolve } from 'path'
|
import { Worker } from 'worker_threads';
|
||||||
import { fileURLToPath } from 'url'
|
|
||||||
import { Worker } from 'worker_threads'
|
import { TARGET_OPTIONS } from '../settings.js';
|
||||||
import { createClient, type ServerClient } from 'minecraft-protocol'
|
import { Channel } from '../util/channel.js';
|
||||||
import chalk from 'chalk'
|
import { importModulesGenerator } from '../util/import-modules.js';
|
||||||
|
import { type Message } from '../worker/parent.js';
|
||||||
|
|
||||||
if (!('require' in globalThis)) {
|
if (!('require' in globalThis)) {
|
||||||
globalThis.__filename = fileURLToPath(import.meta.url)
|
globalThis.__filename = fileURLToPath(import.meta.url);
|
||||||
globalThis.__dirname = dirname(__filename)
|
globalThis.__dirname = dirname(__filename);
|
||||||
}
|
}
|
||||||
|
|
||||||
const WORKER_PATH = resolve(__dirname, '../worker')
|
const WORKER_PATH = resolve(__dirname, '../worker');
|
||||||
const MODULE_DIR_PATH = resolve(__dirname, '../module')
|
const MODULE_DIR_PATH = resolve(__dirname, '../module');
|
||||||
|
|
||||||
export class Instance {
|
export class Instance {
|
||||||
public readonly client
|
public readonly client;
|
||||||
public readonly server
|
public readonly server;
|
||||||
public readonly worker
|
public readonly worker;
|
||||||
|
|
||||||
constructor (client: ServerClient) {
|
constructor(client: ServerClient) {
|
||||||
this.client = client
|
this.client = client;
|
||||||
|
|
||||||
const target = createClient({
|
const target = createClient({
|
||||||
auth: 'offline',
|
auth: 'offline',
|
||||||
username: client.username,
|
username: client.username,
|
||||||
...TARGET_OPTIONS,
|
...TARGET_OPTIONS,
|
||||||
keepAlive: false
|
keepAlive: false,
|
||||||
})
|
});
|
||||||
|
|
||||||
this.server = target
|
this.server = target;
|
||||||
|
|
||||||
target.on('error', error => {
|
target.on('error', (error) => {
|
||||||
console.error('Target error:', error)
|
console.error('Target error:', error);
|
||||||
})
|
});
|
||||||
|
|
||||||
console.info('Initializing worker... (local context)')
|
console.info('Initializing worker... (local context)');
|
||||||
|
|
||||||
const start = performance.now()
|
const start = performance.now();
|
||||||
|
|
||||||
const worker = new Worker(WORKER_PATH)
|
const worker = new Worker(WORKER_PATH);
|
||||||
this.worker = worker
|
this.worker = worker;
|
||||||
|
|
||||||
worker.on('online', () => {
|
worker.on('online', () => {
|
||||||
const end = performance.now()
|
const end = performance.now();
|
||||||
|
|
||||||
const delta = end - start
|
const delta = end - start;
|
||||||
|
|
||||||
console.info(`Worker online! took ${delta.toFixed(2)}ms`)
|
console.info(`Worker online! took ${delta.toFixed(2)}ms`);
|
||||||
})
|
});
|
||||||
|
|
||||||
worker.on('error', error => {
|
worker.on('error', (error) => {
|
||||||
console.error('Worker error:', error)
|
console.error('Worker error:', error);
|
||||||
})
|
});
|
||||||
|
|
||||||
void this._importModules()
|
void this._importModules();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _importModules (): Promise<void> {
|
private async _importModules(): Promise<void> {
|
||||||
console.group('Loading modules... (global)')
|
console.group('Loading modules... (global)');
|
||||||
|
|
||||||
const start = performance.now()
|
const start = performance.now();
|
||||||
|
|
||||||
let moduleStart = NaN
|
let moduleStart = NaN;
|
||||||
|
|
||||||
for await (const module of importModulesGenerator(
|
for await (const module of importModulesGenerator(
|
||||||
MODULE_DIR_PATH,
|
MODULE_DIR_PATH,
|
||||||
'global.js',
|
'global.js',
|
||||||
{
|
{
|
||||||
pre (entry) {
|
pre(entry) {
|
||||||
const now = performance.now()
|
const now = performance.now();
|
||||||
moduleStart = now
|
moduleStart = now;
|
||||||
|
|
||||||
const module = entry.name
|
const module = entry.name;
|
||||||
console.group(`Loading ${module}...`)
|
console.group(`Loading ${module}...`);
|
||||||
},
|
},
|
||||||
post (entry) {
|
post(_entry) {
|
||||||
const now = performance.now()
|
const now = performance.now();
|
||||||
const delta = now - moduleStart
|
const delta = now - moduleStart;
|
||||||
|
|
||||||
console.groupEnd()
|
console.groupEnd();
|
||||||
console.info(`took ${delta.toPrecision(2)}ms`)
|
console.info(`took ${delta.toPrecision(2)}ms`);
|
||||||
},
|
},
|
||||||
error (error, entry) {
|
error(error, entry) {
|
||||||
const module = entry.name
|
const module = entry.name;
|
||||||
|
|
||||||
error.stack += `\n while loading module ${JSON.stringify(module)} (local)`
|
error.stack += `\n while loading module ${JSON.stringify(
|
||||||
|
module,
|
||||||
|
)} (local)`;
|
||||||
|
|
||||||
console.error(chalk.red(error.stack))
|
console.error(chalk.red(error.stack));
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
)) {
|
)) {
|
||||||
if (module === null) throw new Error('Expected module not to be null')
|
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 (typeof module !== 'object')
|
||||||
|
throw new Error('Expected module to be an object');
|
||||||
|
|
||||||
if (!('default' in module)) throw new Error('Expected default export')
|
if (!('default' in module)) throw new Error('Expected default export');
|
||||||
|
|
||||||
const f = module.default
|
const f = module.default;
|
||||||
|
|
||||||
if (typeof f !== 'function') throw new Error('Expected default export to be a function')
|
if (typeof f !== 'function')
|
||||||
|
throw new Error('Expected default export to be a function');
|
||||||
|
|
||||||
await (f as (instance: Instance) => Promise<void>)(this)
|
await (f as (instance: Instance) => Promise<void>)(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
const end = performance.now()
|
const end = performance.now();
|
||||||
|
|
||||||
const delta = end - start
|
const delta = end - start;
|
||||||
|
|
||||||
console.groupEnd()
|
console.groupEnd();
|
||||||
console.info(`took ${delta.toFixed(2)}ms`)
|
console.info(`took ${delta.toFixed(2)}ms`);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected postMessage (channel: string, data: any): void {
|
protected postMessage(channel: string, data: any): void {
|
||||||
this.worker.postMessage({
|
this.worker.postMessage({
|
||||||
channel,
|
channel,
|
||||||
data
|
data,
|
||||||
} satisfies Message)
|
} satisfies Message);
|
||||||
}
|
}
|
||||||
|
|
||||||
public createChannel<TSend, TReceive = TSend> (id: string): Channel<TSend, TReceive> {
|
public createChannel<TSend, TReceive = TSend>(
|
||||||
const channel = new Channel<TSend, TReceive>(id)
|
id: string,
|
||||||
|
): Channel<TSend, TReceive> {
|
||||||
|
const channel = new Channel<TSend, TReceive>(id);
|
||||||
|
|
||||||
channel._subscribe(data => {
|
channel._subscribe((data) => {
|
||||||
this.postMessage(id, data)
|
this.postMessage(id, data);
|
||||||
})
|
});
|
||||||
|
|
||||||
this.worker.on('message', (message: Message<TReceive>) => {
|
this.worker.on('message', (message: Message<TReceive>) => {
|
||||||
if (message.channel !== id) return
|
if (message.channel !== id) return;
|
||||||
|
|
||||||
channel._write(message.data)
|
channel._write(message.data);
|
||||||
})
|
});
|
||||||
|
|
||||||
return channel
|
return channel;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Instance
|
export default Instance;
|
||||||
|
|
|
@ -1,122 +1,125 @@
|
||||||
import type Instance from '../../instance/index.js'
|
import { states } from 'minecraft-protocol';
|
||||||
import { type Message } from './shared.js'
|
|
||||||
import { type RawPacket } from '../../util/packet.js'
|
import type Instance from '../../instance/index.js';
|
||||||
import { states } from 'minecraft-protocol'
|
import { type RawPacket } from '../../util/packet.js';
|
||||||
|
import { type Message } from './shared.js';
|
||||||
|
|
||||||
export default async function (instance: Instance): Promise<void> {
|
export default async function (instance: Instance): Promise<void> {
|
||||||
const clientQueue: RawPacket[] = []
|
const clientQueue: RawPacket[] = [];
|
||||||
const serverQueue: RawPacket[] = []
|
const serverQueue: RawPacket[] = [];
|
||||||
|
|
||||||
const channel = instance.createChannel<Message>('proxy')
|
const channel = instance.createChannel<Message>('proxy');
|
||||||
|
|
||||||
const client = instance.client
|
const client = instance.client;
|
||||||
const server = instance.server
|
const server = instance.server;
|
||||||
const worker = instance.worker
|
const worker = instance.worker;
|
||||||
|
|
||||||
client.on('end', reason => {
|
client.on('end', (reason) => {
|
||||||
console.info('Client disconnected:', reason)
|
console.info('Client disconnected:', reason);
|
||||||
|
|
||||||
server.end(reason)
|
server.end(reason);
|
||||||
|
|
||||||
void worker.terminate()
|
void worker.terminate();
|
||||||
})
|
});
|
||||||
|
|
||||||
server.on('end', reason => {
|
server.on('end', (reason) => {
|
||||||
console.info('Target disconnected:', reason)
|
console.info('Server disconnected:', reason);
|
||||||
|
|
||||||
client.end(reason)
|
client.end(reason);
|
||||||
|
|
||||||
void worker.terminate()
|
void worker.terminate();
|
||||||
})
|
});
|
||||||
|
|
||||||
client.on('error', error => {
|
client.on('error', (error) => {
|
||||||
console.error('Client error:', error)
|
console.error('Client error:', error);
|
||||||
})
|
});
|
||||||
|
|
||||||
server.on('error', error => {
|
server.on('error', (error) => {
|
||||||
console.error('Target error:', error)
|
console.error('Server error:', error);
|
||||||
})
|
});
|
||||||
|
|
||||||
client.on('packet', (data, meta) => {
|
client.on('packet', (data, meta) => {
|
||||||
if (meta.state !== states.PLAY) return
|
// if (meta.state !== states.PLAY) return;
|
||||||
|
|
||||||
channel.write({
|
channel.write({
|
||||||
side: 'client',
|
direction: 'downstream',
|
||||||
packet: {
|
packet: {
|
||||||
name: meta.name,
|
...meta,
|
||||||
data
|
data,
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
|
|
||||||
server.on('packet', (data, meta) => {
|
server.on('packet', (data, meta) => {
|
||||||
if (meta.state !== states.PLAY) return
|
// if (meta.state !== states.PLAY) return;
|
||||||
|
|
||||||
channel.write({
|
channel.write({
|
||||||
side: 'server',
|
direction: 'upstream',
|
||||||
packet: {
|
packet: {
|
||||||
name: meta.name,
|
...meta,
|
||||||
data
|
data,
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
|
|
||||||
client.on('state', state => {
|
// Flush packet queue on play state
|
||||||
if (state !== states.PLAY) return
|
|
||||||
|
|
||||||
const queue = clientQueue
|
client.on('state', (state) => {
|
||||||
|
if (state !== states.PLAY) return;
|
||||||
|
|
||||||
for (const packet of queue) client.write(packet.name, packet.data)
|
const queue = clientQueue;
|
||||||
|
|
||||||
queue.length = 0
|
for (const packet of queue) client.write(packet.name, packet.data);
|
||||||
})
|
|
||||||
|
|
||||||
server.on('state', state => {
|
queue.length = 0;
|
||||||
if (state !== states.PLAY) return
|
});
|
||||||
|
|
||||||
const queue = serverQueue
|
server.on('state', (state) => {
|
||||||
|
if (state !== states.PLAY) return;
|
||||||
|
|
||||||
for (const packet of queue) server.write(packet.name, packet.data)
|
const queue = serverQueue;
|
||||||
|
|
||||||
queue.length = 0
|
for (const packet of queue) server.write(packet.name, packet.data);
|
||||||
})
|
|
||||||
|
|
||||||
channel.subscribe(({ side, packet }) => {
|
queue.length = 0;
|
||||||
|
});
|
||||||
|
|
||||||
|
channel.subscribe(({ direction: side, packet }) => {
|
||||||
switch (side) {
|
switch (side) {
|
||||||
case 'client':
|
case 'downstream':
|
||||||
writeClientPacket(packet)
|
writeClientPacket(packet);
|
||||||
break
|
break;
|
||||||
case 'server':
|
case 'upstream':
|
||||||
writeServerPacket(packet)
|
writeServerPacket(packet);
|
||||||
break
|
break;
|
||||||
default:
|
default:
|
||||||
throw new Error(`Invalid side: ${side as any}`)
|
throw new Error(`Invalid side: ${side as any}`);
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
|
|
||||||
function writeClientPacket (packet: RawPacket): void {
|
function writeClientPacket(packet: RawPacket): void {
|
||||||
|
// wait until play state
|
||||||
if (client.state !== states.PLAY) {
|
if (client.state !== states.PLAY) {
|
||||||
clientQueue.push(packet)
|
clientQueue.push(packet);
|
||||||
|
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
client.write(
|
if (packet.state !== states.PLAY) return;
|
||||||
packet.name,
|
|
||||||
packet.data
|
client.write(packet.name, packet.data);
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function writeServerPacket (packet: RawPacket): void {
|
function writeServerPacket(packet: RawPacket): void {
|
||||||
|
// wait until play state
|
||||||
if (server.state !== states.PLAY) {
|
if (server.state !== states.PLAY) {
|
||||||
serverQueue.push(packet)
|
serverQueue.push(packet);
|
||||||
|
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
server.write(
|
if (packet.state !== states.PLAY) return;
|
||||||
packet.name,
|
|
||||||
packet.data
|
server.write(packet.name, packet.data);
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,60 +1,75 @@
|
||||||
import { type Side, type Message } from './shared.js'
|
import type { States } from 'minecraft-protocol';
|
||||||
import { PublicEventHandler } from '../../util/events.js'
|
import { states } from 'minecraft-protocol';
|
||||||
import { createChannel } from '../../worker/parent.js'
|
|
||||||
import { Packet, type RawPacket } from '../../util/packet.js'
|
|
||||||
import { type AsyncVoid } from '../../util/types.js'
|
|
||||||
|
|
||||||
export type PacketEventMap = Record<string, (packet: Packet) => AsyncVoid>
|
import { PublicEventHandler } from '../../util/events.js';
|
||||||
|
import { Packet, type RawPacket } from '../../util/packet.js';
|
||||||
|
import { type AsyncVoid } from '../../util/types.js';
|
||||||
|
import { createChannel } from '../../worker/parent.js';
|
||||||
|
import { type Direction as Direction, type Message } from './shared.js';
|
||||||
|
|
||||||
|
export type PacketEventMap = Record<string, (packet: Packet) => AsyncVoid>;
|
||||||
|
|
||||||
// ? Should I export the channel
|
// ? Should I export the channel
|
||||||
export const channel = createChannel<Message>('proxy')
|
export const channel = createChannel<Message>('proxy');
|
||||||
|
|
||||||
function write (side: Side, packet: RawPacket): void {
|
function write(direction: Direction, packet: RawPacket): void {
|
||||||
channel.write({
|
channel.write({
|
||||||
side,
|
direction,
|
||||||
packet
|
packet,
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export const proxy = {
|
export const proxy = {
|
||||||
client: new PublicEventHandler<PacketEventMap>(),
|
client: new PublicEventHandler<PacketEventMap>(),
|
||||||
server: new PublicEventHandler<PacketEventMap>(),
|
server: new PublicEventHandler<PacketEventMap>(),
|
||||||
|
|
||||||
writeClient (name: string, data: unknown): void {
|
writeDownstream(
|
||||||
write('client', { name, data })
|
name: string,
|
||||||
|
data: unknown,
|
||||||
|
state: States = states.PLAY,
|
||||||
|
): void {
|
||||||
|
write('downstream', { name, data, state });
|
||||||
},
|
},
|
||||||
writeServer (name: string, data: unknown): void {
|
writeUpstream(
|
||||||
write('server', { name, data })
|
name: string,
|
||||||
}
|
data: unknown,
|
||||||
} as const
|
state: States = states.PLAY,
|
||||||
|
): void {
|
||||||
|
write('upstream', { name, data, state });
|
||||||
|
},
|
||||||
|
} as const;
|
||||||
|
|
||||||
channel.subscribe(({ side, packet: raw }: Message) => {
|
channel.subscribe(({ direction, packet: raw }: Message) => {
|
||||||
void (async () => {
|
void (async () => {
|
||||||
const emitter = proxy[side]
|
const sourceHandler = {
|
||||||
|
downstream: proxy.server,
|
||||||
|
upstream: proxy.client,
|
||||||
|
}[direction];
|
||||||
|
|
||||||
const packet = new Packet(raw.name, raw.data)
|
const packet = new Packet(raw.name, raw.data);
|
||||||
|
|
||||||
await emitter.emit('packet', packet)
|
await sourceHandler.emit('packet', packet);
|
||||||
await emitter.emit(packet.name, packet)
|
await sourceHandler.emit(packet.name, packet);
|
||||||
|
|
||||||
if (packet.canceled) return
|
if (packet.canceled) return;
|
||||||
|
|
||||||
switch (side) {
|
switch (direction) {
|
||||||
case 'client':
|
case 'downstream':
|
||||||
side = 'server'
|
direction = 'upstream';
|
||||||
break
|
break;
|
||||||
case 'server':
|
case 'upstream':
|
||||||
side = 'client'
|
direction = 'downstream';
|
||||||
break
|
break;
|
||||||
default:
|
default:
|
||||||
throw new Error(`Invalid side: ${side as any}`)
|
throw new Error(`Invalid direction: ${direction as any}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Forward packet
|
||||||
channel.write({
|
channel.write({
|
||||||
side,
|
direction,
|
||||||
packet
|
packet,
|
||||||
})
|
});
|
||||||
})()
|
})();
|
||||||
})
|
});
|
||||||
|
|
||||||
export default proxy
|
export default proxy;
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import { type RawPacket } from '../../util/packet.js'
|
import { type RawPacket } from '../../util/packet.js';
|
||||||
|
|
||||||
export type Side = 'client' | 'server'
|
export type Direction = 'upstream' | 'downstream';
|
||||||
|
|
||||||
export interface Message {
|
export interface Message {
|
||||||
side: Side
|
direction: Direction;
|
||||||
packet: RawPacket
|
packet: RawPacket;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,21 +1,24 @@
|
||||||
import { type ClientOptions, type ServerOptions } from 'minecraft-protocol'
|
import { type ClientOptions, type ServerOptions } from 'minecraft-protocol';
|
||||||
|
|
||||||
export const VERSION = '1.19.4'
|
export const VERSION = '1.19.4';
|
||||||
|
|
||||||
|
export const TARGET_HOST = process.env.HOST;
|
||||||
|
export const TARGET_PORT = parseInt(process.env.PORT ?? '25565');
|
||||||
|
|
||||||
interface TargetOptions extends Omit<ClientOptions, 'username'> {
|
interface TargetOptions extends Omit<ClientOptions, 'username'> {
|
||||||
username?: ClientOptions['username']
|
username?: ClientOptions['username'];
|
||||||
}
|
}
|
||||||
|
|
||||||
export const TARGET_OPTIONS: TargetOptions = {
|
export const TARGET_OPTIONS: TargetOptions = {
|
||||||
host: 'kaboom.pw',
|
host: TARGET_HOST,
|
||||||
port: 25565,
|
port: TARGET_PORT,
|
||||||
// username: 'RealDinhero21',
|
// username: 'RealDinhero21',
|
||||||
// auth: 'microsoft',
|
// auth: 'microsoft',
|
||||||
version: VERSION
|
version: VERSION,
|
||||||
}
|
};
|
||||||
|
|
||||||
export const SERVER_OPTIONS: ServerOptions = {
|
export const SERVER_OPTIONS: ServerOptions = {
|
||||||
host: '127.0.0.1',
|
host: '127.0.0.1',
|
||||||
port: 25565,
|
port: 25565,
|
||||||
version: VERSION
|
version: VERSION,
|
||||||
}
|
};
|
||||||
|
|
|
@ -1,43 +1,48 @@
|
||||||
export type Listener<T> = (data: T) => void
|
// ==============================================================
|
||||||
|
// # This looks more and more like rx with each day that passes #
|
||||||
|
// ==============================================================
|
||||||
|
// https://rxjs.dev/
|
||||||
|
|
||||||
|
export type Listener<T> = (data: T) => void;
|
||||||
|
|
||||||
// _x -> x
|
// _x -> x
|
||||||
// x -> _x
|
// x -> _x
|
||||||
export class Channel<TSend, TReceive = TSend> {
|
export class Channel<TSend, TReceive = TSend> {
|
||||||
public readonly id
|
public readonly id;
|
||||||
|
|
||||||
constructor (id: string) {
|
constructor(id: string) {
|
||||||
this.id = id
|
this.id = id;
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly listeners = new Set<Listener<TReceive>>()
|
private readonly listeners = new Set<Listener<TReceive>>();
|
||||||
|
|
||||||
public subscribe (listener: Listener<TReceive>): void {
|
public subscribe(listener: Listener<TReceive>): void {
|
||||||
this.listeners.add(listener)
|
this.listeners.add(listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
public unsubscribe (listener: Listener<TReceive>): void {
|
public unsubscribe(listener: Listener<TReceive>): void {
|
||||||
this.listeners.delete(listener)
|
this.listeners.delete(listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
public write (data: TSend): void {
|
public write(data: TSend): void {
|
||||||
for (const listener of this._listeners) {
|
for (const listener of this._listeners) {
|
||||||
listener(data)
|
listener(data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly _listeners = new Set<Listener<TSend>>()
|
private readonly _listeners = new Set<Listener<TSend>>();
|
||||||
|
|
||||||
public _subscribe (listener: Listener<TSend>): void {
|
public _subscribe(listener: Listener<TSend>): void {
|
||||||
this._listeners.add(listener)
|
this._listeners.add(listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
public _unsubscribe (listener: Listener<TSend>): void {
|
public _unsubscribe(listener: Listener<TSend>): void {
|
||||||
this._listeners.delete(listener)
|
this._listeners.delete(listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
public _write (data: TReceive): void {
|
public _write(data: TReceive): void {
|
||||||
for (const listener of this.listeners) {
|
for (const listener of this.listeners) {
|
||||||
listener(data)
|
listener(data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,59 +1,73 @@
|
||||||
import { type AsyncVoid } from './types.js'
|
// ==============================================================
|
||||||
|
// # This looks more and more like rx with each day that passes #
|
||||||
|
// ==============================================================
|
||||||
|
// https://rxjs.dev/
|
||||||
|
|
||||||
export type EventMap<T> = { [K in keyof T]: (...args: any[]) => AsyncVoid }
|
import { type AsyncVoid } from './types.js';
|
||||||
|
|
||||||
|
export type EventMap<T> = { [K in keyof T]: (...args: any[]) => AsyncVoid };
|
||||||
|
|
||||||
export class EventHandler<T extends EventMap<T>> {
|
export class EventHandler<T extends EventMap<T>> {
|
||||||
protected map = new Map<keyof T, Set<T[keyof 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> {
|
protected async _emit<E extends keyof T>(
|
||||||
const map = this.map
|
name: E,
|
||||||
|
...data: Parameters<T[E]>
|
||||||
|
): Promise<void> {
|
||||||
|
const map = this.map;
|
||||||
|
|
||||||
const set = map.get(name)
|
const set = map.get(name);
|
||||||
|
|
||||||
if (set === undefined) return
|
if (set === undefined) return;
|
||||||
|
|
||||||
for (const listener of set) await listener(...data)
|
for (const listener of set) await listener(...data);
|
||||||
}
|
}
|
||||||
|
|
||||||
public on<E extends keyof T> (name: E, callback: T[E]): void {
|
public on<E extends keyof T>(name: E, callback: T[E]): void {
|
||||||
const map = this.map
|
const map = this.map;
|
||||||
|
|
||||||
const set = map.get(name) ?? new Set()
|
const set = map.get(name) ?? new Set();
|
||||||
map.set(name, set)
|
map.set(name, set);
|
||||||
|
|
||||||
set.add(callback)
|
set.add(callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
public once<E extends keyof T>(name: E, callback: T[E]): void {
|
public once<E extends keyof T>(name: E, callback: T[E]): void {
|
||||||
const original = callback
|
const original = callback;
|
||||||
callback = function (this: ThisParameterType<T[E]>, ...args: Parameters<T[E]>): ReturnType<T[E]> {
|
callback = function (
|
||||||
const output = original.apply(this, args)
|
this: ThisParameterType<T[E]>,
|
||||||
|
...args: Parameters<T[E]>
|
||||||
|
): ReturnType<T[E]> {
|
||||||
|
const output = original.apply(this, args);
|
||||||
|
|
||||||
return output as ReturnType<T[E]>
|
return output as ReturnType<T[E]>;
|
||||||
} as unknown as T[E]
|
} as unknown as T[E];
|
||||||
|
|
||||||
this.on(name, callback)
|
this.on(name, callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
public off<E extends keyof T> (name: E, callback: T[E]): void {
|
public off<E extends keyof T>(name: E, callback: T[E]): void {
|
||||||
const map = this.map
|
const map = this.map;
|
||||||
|
|
||||||
const set = map.get(name)
|
const set = map.get(name);
|
||||||
|
|
||||||
if (set === undefined) return
|
if (set === undefined) return;
|
||||||
|
|
||||||
set.delete(callback)
|
set.delete(callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
public clear (): void {
|
public clear(): void {
|
||||||
const map = this.map
|
const map = this.map;
|
||||||
|
|
||||||
map.clear()
|
map.clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class PublicEventHandler<T extends EventMap<T>> extends EventHandler<T> {
|
export class PublicEventHandler<T extends EventMap<T>> extends EventHandler<T> {
|
||||||
public async emit<E extends keyof T> (name: E, ...data: Parameters<T[E]>): Promise<void> {
|
public async emit<E extends keyof T>(
|
||||||
await this._emit(name, ...data)
|
name: E,
|
||||||
|
...data: Parameters<T[E]>
|
||||||
|
): Promise<void> {
|
||||||
|
await this._emit(name, ...data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,22 +1,22 @@
|
||||||
import fs, { access, constants } from 'fs/promises'
|
import fs, { access, constants } from 'fs/promises';
|
||||||
import { resolve } from 'path'
|
import { resolve } from 'path';
|
||||||
|
|
||||||
export async function * getAllFiles (dir: string): AsyncIterable<string> {
|
export async function* getAllFiles(dir: string): AsyncIterable<string> {
|
||||||
const entries = await fs.readdir(dir, { withFileTypes: true })
|
const entries = await fs.readdir(dir, { withFileTypes: true });
|
||||||
|
|
||||||
for (const entry of entries) {
|
for (const entry of entries) {
|
||||||
const res = resolve(dir, entry.name)
|
const res = resolve(dir, entry.name);
|
||||||
|
|
||||||
if (entry.isDirectory()) {
|
if (entry.isDirectory()) {
|
||||||
yield * getAllFiles(res)
|
yield* getAllFiles(res);
|
||||||
} else {
|
} else {
|
||||||
yield res
|
yield res;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function exists (path: string): Promise<boolean> {
|
export async function exists(path: string): Promise<boolean> {
|
||||||
return await access(path, constants.F_OK)
|
return await access(path, constants.F_OK)
|
||||||
.then(() => true)
|
.then(() => true)
|
||||||
.catch(() => false)
|
.catch(() => false);
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,47 +4,60 @@
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
|
|
||||||
import { exists } from './file.js'
|
import { type Dirent } from 'fs';
|
||||||
import { type AsyncVoid } from './types.js'
|
import { readdir } from 'fs/promises';
|
||||||
import { type Dirent } from 'fs'
|
import { resolve } from 'path';
|
||||||
import { readdir } from 'fs/promises'
|
|
||||||
import { resolve } from 'path'
|
import { exists } from './file.js';
|
||||||
|
import { type AsyncVoid } from './types.js';
|
||||||
|
|
||||||
export interface Callbacks {
|
export interface Callbacks {
|
||||||
pre?: (entry: Dirent) => AsyncVoid
|
pre?: (entry: Dirent) => AsyncVoid;
|
||||||
post?: (entry: Dirent) => AsyncVoid
|
post?: (entry: Dirent) => AsyncVoid;
|
||||||
error?: (error: Error, entry: Dirent) => AsyncVoid
|
error?: (error: Error, entry: Dirent) => AsyncVoid;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function * importModulesGenerator (directory: string, index: string, callbacks?: Callbacks): AsyncGenerator<unknown> {
|
// TODO: listr2?
|
||||||
|
// https://listr2.kilic.dev/
|
||||||
|
export async function* importModulesGenerator(
|
||||||
|
directory: string,
|
||||||
|
index: string,
|
||||||
|
callbacks?: Callbacks,
|
||||||
|
): AsyncGenerator<unknown> {
|
||||||
for (const entry of await readdir(directory, { withFileTypes: true })) {
|
for (const entry of await readdir(directory, { withFileTypes: true })) {
|
||||||
const path = resolve(entry.path, entry.name, index)
|
const path = resolve(entry.path, entry.name, index);
|
||||||
|
|
||||||
if (!entry.isDirectory()) console.warn(`Expected ${entry.name} to be a directory (located at ${entry.path})`)
|
if (!entry.isDirectory())
|
||||||
|
console.warn(
|
||||||
|
`Expected ${entry.name} to be a directory (located at ${entry.path})`,
|
||||||
|
);
|
||||||
|
|
||||||
if (!await exists(path)) continue
|
if (!(await exists(path))) continue;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const preCallback = callbacks?.pre
|
const preCallback = callbacks?.pre;
|
||||||
|
|
||||||
if (preCallback !== undefined) await preCallback(entry)
|
if (preCallback !== undefined) await preCallback(entry);
|
||||||
|
|
||||||
yield await import(path)
|
yield await import(path);
|
||||||
|
|
||||||
const postCallback = callbacks?.post
|
const postCallback = callbacks?.post;
|
||||||
|
|
||||||
if (postCallback !== undefined) await postCallback(entry)
|
if (postCallback !== undefined) await postCallback(entry);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const errorCallback = callbacks?.error
|
const errorCallback = callbacks?.error;
|
||||||
|
|
||||||
if (errorCallback === undefined) throw error
|
if (errorCallback === undefined) throw error;
|
||||||
|
|
||||||
await errorCallback(error as Error, entry)
|
await errorCallback(error as Error, entry);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function importModules (directory: string, index: string, callbacks?: Callbacks): Promise<void> {
|
export async function importModules(
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
directory: string,
|
||||||
|
index: string,
|
||||||
|
callbacks?: Callbacks,
|
||||||
|
): Promise<void> {
|
||||||
for await (const _ of importModulesGenerator(directory, index, callbacks));
|
for await (const _ of importModulesGenerator(directory, index, callbacks));
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,16 +1,19 @@
|
||||||
export interface RawPacket {
|
import type { PacketMeta, States } from 'minecraft-protocol';
|
||||||
name: string
|
import { states } from 'minecraft-protocol';
|
||||||
data: unknown
|
|
||||||
|
export interface RawPacket extends PacketMeta {
|
||||||
|
data: unknown;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Packet<Data = unknown> {
|
export class Packet<Data = unknown> {
|
||||||
public name
|
public name;
|
||||||
public data
|
public state: States = states.PLAY;
|
||||||
|
public data;
|
||||||
|
|
||||||
public canceled: boolean = false
|
public canceled: boolean = false;
|
||||||
|
|
||||||
constructor (name: string, data: Data) {
|
constructor(name: string, data: Data) {
|
||||||
this.name = name
|
this.name = name;
|
||||||
this.data = data
|
this.data = data;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
export type AsyncVoid = void | Promise<void>
|
export type AsyncVoid = void | Promise<void>;
|
||||||
|
|
|
@ -1,52 +1,51 @@
|
||||||
import { importModules } from '../util/import-modules.js'
|
import chalk from 'chalk';
|
||||||
import { dirname, resolve } from 'path'
|
import { dirname, resolve } from 'path';
|
||||||
import { fileURLToPath } from 'url'
|
import { fileURLToPath } from 'url';
|
||||||
import chalk from 'chalk'
|
|
||||||
|
import { importModules } from '../util/import-modules.js';
|
||||||
|
|
||||||
if (!('require' in globalThis)) {
|
if (!('require' in globalThis)) {
|
||||||
globalThis.__filename = fileURLToPath(import.meta.url)
|
globalThis.__filename = fileURLToPath(import.meta.url);
|
||||||
globalThis.__dirname = dirname(__filename)
|
globalThis.__dirname = dirname(__filename);
|
||||||
}
|
}
|
||||||
|
|
||||||
const MODULE_DIR_PATH = resolve(__dirname, '../module')
|
const MODULE_DIR_PATH = resolve(__dirname, '../module');
|
||||||
|
|
||||||
console.group('Loading modules... (local)')
|
console.group('Loading modules... (local)');
|
||||||
|
|
||||||
const start = performance.now()
|
const start = performance.now();
|
||||||
|
|
||||||
let moduleStart = NaN
|
let moduleStart = NaN;
|
||||||
|
|
||||||
await importModules(
|
await importModules(MODULE_DIR_PATH, 'local.js', {
|
||||||
MODULE_DIR_PATH,
|
pre(entry) {
|
||||||
'local.js',
|
const module = entry.name;
|
||||||
{
|
console.group(`Loading ${module}...`);
|
||||||
pre (entry) {
|
|
||||||
const module = entry.name
|
|
||||||
console.group(`Loading ${module}...`)
|
|
||||||
|
|
||||||
const now = performance.now()
|
const now = performance.now();
|
||||||
moduleStart = now
|
moduleStart = now;
|
||||||
},
|
},
|
||||||
post (entry) {
|
post(_entry) {
|
||||||
const now = performance.now()
|
const now = performance.now();
|
||||||
const delta = now - moduleStart
|
const delta = now - moduleStart;
|
||||||
|
|
||||||
console.groupEnd()
|
console.groupEnd();
|
||||||
console.info(`took ${delta.toFixed(2)}ms`)
|
console.info(`took ${delta.toFixed(2)}ms`);
|
||||||
},
|
},
|
||||||
error (error, entry) {
|
error(error, entry) {
|
||||||
const module = entry.name
|
const module = entry.name;
|
||||||
|
|
||||||
error.stack += `\n while loading module ${JSON.stringify(module)} (local)`
|
error.stack += `\n while loading module ${JSON.stringify(
|
||||||
|
module,
|
||||||
|
)} (local)`;
|
||||||
|
|
||||||
console.error(chalk.red(error.stack))
|
console.error(chalk.red(error.stack));
|
||||||
}
|
},
|
||||||
}
|
});
|
||||||
)
|
|
||||||
|
|
||||||
const end = performance.now()
|
const end = performance.now();
|
||||||
|
|
||||||
const delta = end - start
|
const delta = end - start;
|
||||||
|
|
||||||
console.groupEnd()
|
console.groupEnd();
|
||||||
console.info(`took ${delta.toFixed(2)}ms`)
|
console.info(`took ${delta.toFixed(2)}ms`);
|
||||||
|
|
|
@ -1,34 +1,37 @@
|
||||||
import { Channel } from '../util/channel.js'
|
import { parentPort } from 'worker_threads';
|
||||||
import { parentPort } from 'worker_threads'
|
|
||||||
|
|
||||||
if (parentPort === null) throw new Error('Must run in worker thread')
|
import { Channel } from '../util/channel.js';
|
||||||
|
|
||||||
const port = parentPort
|
if (parentPort === null) throw new Error('Must run in worker thread');
|
||||||
|
|
||||||
|
const port = parentPort;
|
||||||
|
|
||||||
export interface Message<T = any> {
|
export interface Message<T = any> {
|
||||||
channel: string
|
channel: string;
|
||||||
data: T
|
data: T;
|
||||||
}
|
}
|
||||||
|
|
||||||
function postMessage (channel: string, data: any): void {
|
function postMessage(channel: string, data: any): void {
|
||||||
port.postMessage({
|
port.postMessage({
|
||||||
channel,
|
channel,
|
||||||
data
|
data,
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createChannel<TSend, TReceive = TSend> (id: string): Channel<TSend, TReceive> {
|
export function createChannel<TSend, TReceive = TSend>(
|
||||||
const channel = new Channel<TSend, TReceive>(id)
|
id: string,
|
||||||
|
): Channel<TSend, TReceive> {
|
||||||
|
const channel = new Channel<TSend, TReceive>(id);
|
||||||
|
|
||||||
channel._subscribe(message => {
|
channel._subscribe((message) => {
|
||||||
postMessage(id, message)
|
postMessage(id, message);
|
||||||
})
|
});
|
||||||
|
|
||||||
port.on('message', (message: Message<TReceive>) => {
|
port.on('message', (message: Message<TReceive>) => {
|
||||||
if (message.channel !== id) return
|
if (message.channel !== id) return;
|
||||||
|
|
||||||
channel._write(message.data)
|
channel._write(message.data);
|
||||||
})
|
});
|
||||||
|
|
||||||
return channel
|
return channel;
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
|
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
|
||||||
|
|
||||||
/* Language and Environment */
|
/* Language and Environment */
|
||||||
"target": "esnext", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
|
"target": "esnext" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */,
|
||||||
// "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
|
// "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
|
||||||
// "jsx": "preserve", /* Specify what JSX code is generated. */
|
// "jsx": "preserve", /* Specify what JSX code is generated. */
|
||||||
// "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */
|
// "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */
|
||||||
|
@ -25,9 +25,9 @@
|
||||||
// "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */
|
// "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */
|
||||||
|
|
||||||
/* Modules */
|
/* Modules */
|
||||||
"module": "nodenext", /* Specify what module code is generated. */
|
"module": "nodenext" /* Specify what module code is generated. */,
|
||||||
// "rootDir": "./", /* Specify the root folder within your source files. */
|
// "rootDir": "./", /* Specify the root folder within your source files. */
|
||||||
"moduleResolution": "nodenext", /* Specify how TypeScript looks up a file from a given module specifier. */
|
"moduleResolution": "nodenext" /* Specify how TypeScript looks up a file from a given module specifier. */,
|
||||||
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
|
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
|
||||||
// "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
|
// "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
|
||||||
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
|
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
|
||||||
|
@ -77,12 +77,12 @@
|
||||||
// "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
|
// "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
|
||||||
// "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */
|
// "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */
|
||||||
// "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
|
// "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
|
||||||
"esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */
|
"esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */,
|
||||||
// "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
|
// "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
|
||||||
"forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
|
"forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */,
|
||||||
|
|
||||||
/* Type Checking */
|
/* Type Checking */
|
||||||
"strict": true, /* Enable all strict type-checking options. */
|
"strict": true /* Enable all strict type-checking options. */,
|
||||||
// "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */
|
// "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */
|
||||||
// "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */
|
// "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */
|
||||||
// "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
|
// "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
|
||||||
|
@ -104,12 +104,8 @@
|
||||||
|
|
||||||
/* Completeness */
|
/* Completeness */
|
||||||
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
|
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
|
||||||
"skipLibCheck": true /* Skip type checking all .d.ts files. */
|
"skipLibCheck": true /* Skip type checking all .d.ts files. */
|
||||||
},
|
},
|
||||||
"include": [
|
"include": ["src/**/*"],
|
||||||
"src/**/*"
|
"exclude": ["node_modules"]
|
||||||
],
|
|
||||||
"exclude": [
|
|
||||||
"node_modules"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue