It works
This commit is contained in:
parent
4480165793
commit
1798cdbc5f
14 changed files with 1982 additions and 1 deletions
4
.gitignore
vendored
4
.gitignore
vendored
|
@ -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:
|
||||
|
||||
|
|
5
config/client-config.json
Normal file
5
config/client-config.json
Normal file
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"remoteAddress": "ws://localhost:18472",
|
||||
"localPort": 25566,
|
||||
"authKey": "default"
|
||||
}
|
6
config/server-config.json
Normal file
6
config/server-config.json
Normal file
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"port": 18472,
|
||||
"localHost": "localhost",
|
||||
"localPort": 25565,
|
||||
"authKey": "default"
|
||||
}
|
197
index.ts
Normal file
197
index.ts
Normal file
|
@ -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}`));
|
||||
}
|
1670
package-lock.json
generated
Normal file
1670
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
37
package.json
Normal file
37
package.json
Normal file
|
@ -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"
|
||||
}
|
||||
}
|
4
src/enum/AuthState.ts
Normal file
4
src/enum/AuthState.ts
Normal file
|
@ -0,0 +1,4 @@
|
|||
export enum AuthState {
|
||||
Bad,
|
||||
Good
|
||||
}
|
5
src/enum/Packet.ts
Normal file
5
src/enum/Packet.ts
Normal file
|
@ -0,0 +1,5 @@
|
|||
export enum Packet {
|
||||
KeepAlive,
|
||||
Auth,
|
||||
Data
|
||||
}
|
5
src/interface/IClientConfig.ts
Normal file
5
src/interface/IClientConfig.ts
Normal file
|
@ -0,0 +1,5 @@
|
|||
export default interface IClientConfig {
|
||||
remoteAddress: string,
|
||||
localPort: number,
|
||||
authKey: string
|
||||
}
|
6
src/interface/IServerConfig.ts
Normal file
6
src/interface/IServerConfig.ts
Normal file
|
@ -0,0 +1,6 @@
|
|||
export default interface IServerConfig {
|
||||
port: number,
|
||||
localHost: string,
|
||||
localPort: number,
|
||||
authKey: string
|
||||
}
|
11
tooling/cleanup.ts
Normal file
11
tooling/cleanup.ts
Normal file
|
@ -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 });
|
||||
}
|
||||
}
|
18
tooling/mangle.ts
Normal file
18
tooling/mangle.ts
Normal file
|
@ -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}`);
|
||||
})();
|
||||
}
|
13
tsconfig.json
Normal file
13
tsconfig.json
Normal file
|
@ -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 a new issue