It works
This commit is contained in:
parent
4480165793
commit
1798cdbc5f
|
@ -1,3 +1,7 @@
|
|||
# Custom
|
||||
testing/
|
||||
build/
|
||||
|
||||
# ---> Node
|
||||
# Logs
|
||||
logs
|
||||
|
|
2
LICENSE
2
LICENSE
|
@ -1,6 +1,6 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2024 tgpholly
|
||||
Copyright (c) 2024 Holly Stubbs (tgpholly)
|
||||
|
||||
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:
|
||||
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"remoteAddress": "ws://localhost:18472",
|
||||
"localPort": 25566,
|
||||
"authKey": "default"
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"port": 18472,
|
||||
"localHost": "localhost",
|
||||
"localPort": 25565,
|
||||
"authKey": "default"
|
||||
}
|
|
@ -0,0 +1,197 @@
|
|||
import { WebSocket, WebSocketServer } from "ws";
|
||||
import { Socket, createServer } from "net";
|
||||
import { Packet } from "./src/enum/Packet";
|
||||
import { Endian, createReader, createWriter } from "bufferstuff";
|
||||
import { existsSync, readFileSync } from "fs";
|
||||
import IServerConfig from "./src/interface/IServerConfig";
|
||||
import { AuthState } from "./src/enum/AuthState";
|
||||
import IClientConfig from "./src/interface/IClientConfig";
|
||||
|
||||
const keepAliveMessage = createWriter(Endian.LE, 1).writeUByte(Packet.KeepAlive).toBuffer();
|
||||
|
||||
if (process.argv[2] === "server") {
|
||||
if (!existsSync("./config/server-config.json")) {
|
||||
console.error("server-config.json is missing!");
|
||||
process.exit(1);
|
||||
}
|
||||
const config:IServerConfig = JSON.parse(readFileSync("./config/server-config.json").toString());
|
||||
const server = new WebSocketServer({ port: config.port }, () => console.log(`Server started at ${config.port}`));
|
||||
|
||||
server.on("connection", (socket) => {
|
||||
console.log("Connection");
|
||||
let queuedMessages = new Array<Buffer>();
|
||||
let connectedToServer = false;
|
||||
let connectingToServer = false;
|
||||
let authed = false;
|
||||
let client:Socket | null = null;
|
||||
const clientKeepAlive = setInterval(() => {
|
||||
socket.send(keepAliveMessage);
|
||||
}, 5000);
|
||||
|
||||
socket.on("message", (data, isBinary) => {
|
||||
if (!isBinary) {
|
||||
return;
|
||||
}
|
||||
|
||||
// NOTE: The types declarations for ws are really messed up >:(
|
||||
// @ts-ignore
|
||||
const packetData = createReader(Endian.LE, data);
|
||||
const packetId = packetData.readUByte();
|
||||
let tempReadLength = 0;
|
||||
|
||||
switch (packetId) {
|
||||
case Packet.KeepAlive: break; // We don't really care, it's just so CF doesn't drop the connection.
|
||||
case Packet.Auth:
|
||||
try {
|
||||
if (packetData.readShortString() !== config.authKey) {
|
||||
socket.send(createWriter(Endian.LE, 2).writeUByte(Packet.Auth).writeUByte(AuthState.Bad).toBuffer());
|
||||
socket.close();
|
||||
return;
|
||||
}
|
||||
socket.send(createWriter(Endian.LE, 2).writeUByte(Packet.Auth).writeUByte(AuthState.Good).toBuffer());
|
||||
authed = true;
|
||||
|
||||
client = new Socket();
|
||||
|
||||
client.on("connect", () => {
|
||||
connectedToServer = true;
|
||||
if (queuedMessages.length > 0) {
|
||||
for (const message of queuedMessages) {
|
||||
console.log("Sent", message);
|
||||
client?.write(message);
|
||||
}
|
||||
queuedMessages.length = 0;
|
||||
}
|
||||
});
|
||||
|
||||
client.on("data", (chunk) => {
|
||||
socket.send(createWriter(Endian.LE, 5)
|
||||
.writeUByte(Packet.Data)
|
||||
.writeUInt(chunk.length)
|
||||
.writeBuffer(chunk)
|
||||
.toBuffer());
|
||||
});
|
||||
|
||||
function clientCloseOrError() {
|
||||
socket.close();
|
||||
}
|
||||
client.on("close", clientCloseOrError);
|
||||
client.on("error", clientCloseOrError);
|
||||
|
||||
connectingToServer = true;
|
||||
client.connect({
|
||||
host: config.localHost,
|
||||
port: config.localPort
|
||||
});
|
||||
} catch (e) {
|
||||
client?.end();
|
||||
client = null;
|
||||
socket.close();
|
||||
}
|
||||
break;
|
||||
case Packet.Data:
|
||||
if (!authed) {
|
||||
return;
|
||||
}
|
||||
|
||||
tempReadLength = packetData.readUInt();
|
||||
if (connectedToServer) {
|
||||
client?.write(packetData.readBuffer(tempReadLength));
|
||||
} else {
|
||||
queuedMessages.push(packetData.readBuffer(tempReadLength));
|
||||
}
|
||||
//console.log("[SERVER] Data:", data, " Length:", tempReadLength);
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
function closeOrError() {
|
||||
clearInterval(clientKeepAlive);
|
||||
client?.end();
|
||||
}
|
||||
socket.on("close", closeOrError);
|
||||
socket.on("error", closeOrError);
|
||||
});
|
||||
} else if (process.argv[2] === "client") {
|
||||
if (!existsSync("./config/client-config.json")) {
|
||||
console.error("client-config.json is missing!");
|
||||
process.exit(1);
|
||||
}
|
||||
const config:IClientConfig = JSON.parse(readFileSync("./config/client-config.json").toString());
|
||||
const server = createServer((socket) => {
|
||||
let authed = false;
|
||||
let queuedMessages = new Array<Buffer>();
|
||||
let txBytes = 0;
|
||||
let rxBytes = 0;
|
||||
const txrxInterval = setInterval(() => {
|
||||
console.log(`TX: ${(txBytes / 1024).toFixed(2)}KB/s | RX: ${(rxBytes / 1024).toFixed(2)}KB/s`);
|
||||
txBytes = rxBytes = 0;
|
||||
}, 1000);
|
||||
|
||||
const client = new WebSocket(config.remoteAddress);
|
||||
client.on("open", () => {
|
||||
// Send off auth as soon as we connect
|
||||
client.send(createWriter(Endian.LE, 2 + config.authKey.length).writeUByte(Packet.Auth).writeShortString(config.authKey).toBuffer());
|
||||
});
|
||||
|
||||
client.on("message", (data) => {
|
||||
// @ts-ignore
|
||||
const packetData = createReader(Endian.LE, data);
|
||||
const packetId = packetData.readUByte();
|
||||
let tempReadLength = 0;
|
||||
let bufferData:Buffer;
|
||||
|
||||
switch (packetId) {
|
||||
case Packet.KeepAlive:
|
||||
client.send(keepAliveMessage);
|
||||
break;
|
||||
|
||||
case Packet.Auth:
|
||||
if (authed) {
|
||||
return;
|
||||
}
|
||||
|
||||
authed = packetData.readUByte() === AuthState.Good;
|
||||
if (authed && queuedMessages.length > 0) {
|
||||
for (const message of queuedMessages) {
|
||||
client.send(message);
|
||||
}
|
||||
queuedMessages.length = 0;
|
||||
}
|
||||
break;
|
||||
case Packet.Data:
|
||||
tempReadLength = packetData.readUInt();
|
||||
bufferData = packetData.readBuffer(tempReadLength);
|
||||
rxBytes += bufferData.length;
|
||||
socket.write(bufferData);
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
function clientCloseOrError() {
|
||||
socket.end();
|
||||
clearInterval(txrxInterval);
|
||||
}
|
||||
client.on("close", clientCloseOrError);
|
||||
client.on("error", clientCloseOrError);
|
||||
|
||||
socket.on("data", (chunk) => {
|
||||
const bufferData = createWriter(Endian.LE, 5).writeUByte(Packet.Data).writeUInt(chunk.length).writeBuffer(chunk).toBuffer();
|
||||
txBytes += chunk.length;
|
||||
if (authed) {
|
||||
client.send(bufferData);
|
||||
} else {
|
||||
queuedMessages.push(bufferData);
|
||||
}
|
||||
});
|
||||
|
||||
function serverCloseOrError() {
|
||||
client.close();
|
||||
clearInterval(txrxInterval);
|
||||
}
|
||||
socket.on("close", serverCloseOrError);
|
||||
socket.on("error", serverCloseOrError);
|
||||
});
|
||||
|
||||
server.listen(config.localPort, () => console.log(`Local server listening at ${config.localPort}`));
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,37 @@
|
|||
{
|
||||
"name": "tcp-ws-proxy",
|
||||
"version": "1.0.0",
|
||||
"description": "A TCP -> WS -> TCP proxy",
|
||||
"main": "build/index.js",
|
||||
"scripts": {
|
||||
"dev:updateCheck": "check-outdated",
|
||||
"dev:run": "nodemon --watch './**/*.ts' --watch './**/*.json' --exec ts-node index.ts",
|
||||
"build": "npm-run-all build:*",
|
||||
"build:build": "ncc build index.ts -o build",
|
||||
"build:mangle": "ts-node ./tooling/mangle.ts",
|
||||
"build:cleanup": "ts-node ./tooling/cleanup.ts",
|
||||
"_clean": "tsc --build --clean"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://git.eusv.net/tgpholly/tcp-ws-proxy.git"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@types/node": "^20.11.5",
|
||||
"@types/ws": "^8.5.10",
|
||||
"@vercel/ncc": "^0.38.1",
|
||||
"check-outdated": "^2.12.0",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"terser": "^5.27.0",
|
||||
"ts-node": "^10.9.2",
|
||||
"typescript": "^5.3.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"bufferstuff": "^1.5.0",
|
||||
"dyetty": "^1.0.1",
|
||||
"ws": "^8.16.0"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
export enum AuthState {
|
||||
Bad,
|
||||
Good
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
export enum Packet {
|
||||
KeepAlive,
|
||||
Auth,
|
||||
Data
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
export default interface IClientConfig {
|
||||
remoteAddress: string,
|
||||
localPort: number,
|
||||
authKey: string
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
export default interface IServerConfig {
|
||||
port: number,
|
||||
localHost: string,
|
||||
localPort: number,
|
||||
authKey: string
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
import { readdirSync, rmSync, readFileSync } from "fs";
|
||||
|
||||
const libFiles = readdirSync("./build");
|
||||
|
||||
const mangled = readFileSync("./build/.MANGLED").toString() === "false";
|
||||
|
||||
for (const file of libFiles) {
|
||||
if (!file.startsWith(mangled ? "index.min.js" : "index.js")) {
|
||||
rmSync(`./build/${file}`, { recursive: true });
|
||||
}
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
import { readFileSync, writeFileSync } from "fs";
|
||||
import { minify } from "terser";
|
||||
|
||||
const DISABLE = false;
|
||||
writeFileSync("./build/.MANGLED", `${DISABLE}`);
|
||||
|
||||
if (DISABLE) {
|
||||
//writeFileSync("./build/index.js", readFileSync("./build/index.js"));
|
||||
console.warn("[WARNING] mangle.ts is disabled!");
|
||||
} else {
|
||||
(async () => {
|
||||
const mangled = await minify(readFileSync("./build/index.js").toString(), {
|
||||
mangle: true,
|
||||
toplevel: true,
|
||||
});
|
||||
writeFileSync("./build/index.min.js", `${mangled.code}`);
|
||||
})();
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"module": "CommonJS",
|
||||
"moduleResolution": "node",
|
||||
"target": "ES2020",
|
||||
"esModuleInterop": true,
|
||||
"resolveJsonModule": true,
|
||||
"rootDir": "./",
|
||||
"outDir": "./build/",
|
||||
"strict": true,
|
||||
"declaration": true
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue