From 1d5d7a12f0bf38cef608832d4328d03a816b52ea Mon Sep 17 00:00:00 2001 From: RichardDorian Date: Wed, 30 Mar 2022 16:24:31 +0200 Subject: [PATCH] feat: :sparkles: add `ReadOnlyProxy` --- .github/workflow/build.yml | 44 ++++++++-------- .npmignore | 2 +- README.md | 48 +++++++++++++++++- example/readOnlyProxy.js | 15 ++++++ package.json | 14 ++--- src/ReadOnlyProxy.ts | 101 +++++++++++++++++++++++++++++++++++++ src/index.ts | 3 ++ 7 files changed, 195 insertions(+), 32 deletions(-) create mode 100644 example/readOnlyProxy.js create mode 100644 src/ReadOnlyProxy.ts create mode 100644 src/index.ts diff --git a/.github/workflow/build.yml b/.github/workflow/build.yml index c0dd8d5..b46bfd0 100644 --- a/.github/workflow/build.yml +++ b/.github/workflow/build.yml @@ -1,25 +1,23 @@ -# name: Build +name: Build -# on: -# push: -# branches: [main] -# pull_request: -# branches: [main] +on: + push: + branches: [main] + pull_request: + branches: [main] -# jobs: -# build: -# runs-on: ubuntu-latest -# strategy: -# matrix: -# node-version: [16.x] -# steps: -# - uses: actions/checkout@v2 -# - name: Node.js -# uses: actions/setup-node@v2 -# with: -# node-version: ${{ matrix.node-version }} -# - run: npm install -# - name: Build code -# run: npm run build -# - name: Test code -# run: npm run test +jobs: + build: + runs-on: ubuntu-latest + strategy: + matrix: + node-version: [16.x] + steps: + - uses: actions/checkout@v2 + - name: Node.js + uses: actions/setup-node@v2 + with: + node-version: ${{ matrix.node-version }} + - run: npm install + - name: Build code + run: npm run build diff --git a/.npmignore b/.npmignore index 752fccb..f58ee56 100644 --- a/.npmignore +++ b/.npmignore @@ -1,6 +1,6 @@ .github src -test +example .deepsource.toml .prettierrc tsconfig.json \ No newline at end of file diff --git a/README.md b/README.md index 9e588dd..69cac9b 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,47 @@ -# MinecraftJS Template +# Proxies -Fill with your own content \ No newline at end of file +Create proxies for Minecraft + +# Documentation + +## Installation + +Install the package: + +```bash +$ npm install @minecraft-js/proxies +``` + +And then import it in your JavaScript/TypeScript file + +```ts +const Proxies = require('@minecraft-js/proxies'); // CommonJS + +import * as Proxies from '@minecraft-js/proxies'; // ES6 +``` + +## Read only proxies +Read only proxies are proxies where you can only read packets. +Create a read only proxy like so + +```js +const proxy = new Proxies.ReadOnlyProxy({ + serverHostname: 'localhost', // Target server hostname + port: 8080, // Proxy port +}); +``` + +And then you can use the `ReadOnlyProxy#on` method to listen for different events: +- `raw_listening` - When the TCP server is listening +- `raw_connect` - When a client opens a connection +- `raw_disconnect` - When a client closes a connection +- `raw_close` - When the TCP server is closed +- `error` - When an error occurs +- `outgoing_data` - When data is sent to the target server +- `incoming_data` - When data is received from the target server + +# Using TypeScript? +If you are using TypeScript or you want type checking make sure to install the `typed-emitter` package! +```bash +$ npm install typed-emitter +``` \ No newline at end of file diff --git a/example/readOnlyProxy.js b/example/readOnlyProxy.js new file mode 100644 index 0000000..3161877 --- /dev/null +++ b/example/readOnlyProxy.js @@ -0,0 +1,15 @@ +const { ReadOnlyProxy } = require('../dist'); + +const proxy = new ReadOnlyProxy({ + debug: true, + serverHostname: 'localhost', + port: 8080, +}); + +proxy.on('incoming_data', (data) => { + console.log('[INCOMING]', data); +}); + +proxy.on('outgoing_data', (data) => { + console.log('[OUTGOING]', data); +}); diff --git a/package.json b/package.json index fec3b45..0b7c800 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { - "name": "template", + "name": "@minecraft-js/proxies", "version": "1.0.0", - "description": "todo: change", + "description": "Create proxies for Minecraft", "main": "dist/index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1", @@ -9,19 +9,21 @@ }, "repository": { "type": "git", - "url": "git+https://github.com/MinecraftJS/template.git" + "url": "git+https://github.com/MinecraftJS/Proxies.git" }, "keywords": [ "minecraftjs" ], - "author": "todo: change", + "author": "RichardDorian", "license": "MIT", "bugs": { - "url": "https://github.com/MinecraftJS/template/issues" + "url": "https://github.com/MinecraftJS/Proxies/issues" }, - "homepage": "https://github.com/MinecraftJS/template#readme", + "homepage": "https://github.com/MinecraftJS/Proxies#readme", "devDependencies": { + "@types/node": "^17.0.23", "prettier": "^2.6.1", + "typed-emitter": "^2.1.0", "typescript": "^4.6.3" } } diff --git a/src/ReadOnlyProxy.ts b/src/ReadOnlyProxy.ts new file mode 100644 index 0000000..0046f42 --- /dev/null +++ b/src/ReadOnlyProxy.ts @@ -0,0 +1,101 @@ +import { EventEmitter } from 'node:events'; +import TypedEmitter from 'typed-emitter'; +import { Server, Socket, createServer } from 'node:net'; + +export default class ReadOnlyProxy extends (EventEmitter as new () => TypedEmitter) { + private readonly options: ReadOnlyProxyOptions; + private readonly sockets: ReadOnlyProxySocketMap[]; + private readonly server: Server; + + /** + * Create a new read only proxy. Read only proxies are proxies where you can only read packets + * @param options The options for the proxy + */ + public constructor(options: ReadOnlyProxyOptions = {}) { + super(); + this.options = options; + + this.sockets = []; + + this.server = createServer(); + this.server.listen(options.port ?? 25565, options.hostname); + + this.server.once('listening', () => { + this.emit('raw_listening', this.server); + this.debug('Listening addr=', this.server.address()); + }); + + this.server.on('connection', (socket: Socket) => { + this.emit('raw_connect', socket); + this.debug('New connection from', socket.remoteAddress); + + const internalSocket = new Socket(); + this.sockets.push({ + socket: socket, + internal: internalSocket, + }); + + // Handle errors + socket.on('close', (hadError) => { + this.emit('raw_disconnect', socket, hadError); + internalSocket.end(); + }); + internalSocket.on('close', () => socket.end()); + + // Connect the internalSocket to the target server + internalSocket.connect( + options.serverPort ?? 25565, + options.serverHostname + ); + + // Forward data + socket.on('data', (data) => { + internalSocket.write(data); + this.emit('outgoing_data', data, socket, internalSocket); + }); + internalSocket.on('data', (data) => { + socket.write(data); + this.emit('incoming_data', data, socket, internalSocket); + }); + }); + + this.server.on('close', () => this.emit('raw_close')); + this.server.on('error', (error) => this.emit('error', 'server', error)); + } + + private debug(...args) { + if (this.options.debug) console.log('[DEBUG]', ...args); + } +} + +type ErrorOrigin = 'server' | 'internal'; + +type ReadOnlyProxyEvents = { + raw_listening: (server: Server) => void; + raw_connect: (socket: Socket) => void; + raw_disconnect: (socket: Socket, hadError: boolean) => void; + raw_close: () => void; + error: (origin: ErrorOrigin, error: Error) => void; + outgoing_data: (data: Buffer, socket: Socket, internal: Socket) => void; + incoming_data: (data: Buffer, socket: Socket, internal: Socket) => void; +}; + +type ReadOnlyProxySocketMap = { + /** The real socket (the minecraft client, the user), the one connected to the proxy */ + socket: Socket; + /** The proxy socket, the one connected to the target server */ + internal: Socket; +}; + +interface ReadOnlyProxyOptions { + /** Port to listen on, defaults to `25565` */ + port?: number; + /** Hostname to bind to */ + hostname?: string; + /** Whether or not to enable debugging */ + debug?: boolean; + /** Target server port, defaults to `25565` */ + serverPort?: number; + /** Target server hostname */ + serverHostname?: string; +} diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..86d7936 --- /dev/null +++ b/src/index.ts @@ -0,0 +1,3 @@ +import ReadOnlyProxy from './ReadOnlyProxy'; + +export { ReadOnlyProxy };