Move client and server to their own files

This commit is contained in:
Holly Stubbs 2024-01-19 10:20:23 +00:00
parent 8198358d1d
commit 690420eefc
4 changed files with 230 additions and 197 deletions

206
index.ts
View File

@ -1,202 +1,14 @@
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();
import Client from "./src/Client";
import Server from "./src/Server";
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);
});
new Server();
} else if (process.argv[2] === "client") {
if (!existsSync("./config/client-config.json")) {
console.error("client-config.json is missing!");
process.exit(1);
new Client();
} else {
if (process.argv[2] == null || process.argv[2].trim() == "") {
console.log(`You must pick an option. Valid options:\n - client\n - server`);
} else {
console.log(`${process.argv[2]} is not a valid option. Valid options:\n\t - client\n\t - server`);
}
const config:IClientConfig = JSON.parse(readFileSync("./config/client-config.json").toString());
let CLIENT_ID = 0;
const server = createServer((socket) => {
let authed = false;
let queuedMessages = new Array<Buffer>();
let txBytes = 0;
let rxBytes = 0;
const thisClientId = CLIENT_ID++;
console.log(`[LOCAL CLIENT ${thisClientId}] New Connection`);
const txrxInterval = setInterval(() => {
console.log(`[LOCAL CLIENT ${thisClientId}] 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);
console.log(`[LOCAL CLIENT ${thisClientId}] Remote server terminated the connection.`);
}
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);
console.log(`[LOCAL CLIENT ${thisClientId}] Disconnected`);
}
socket.on("close", serverCloseOrError);
socket.on("error", serverCloseOrError);
});
server.listen(config.localPort, () => console.log(`Local server listening at ${config.localPort}`));
}

99
src/Client.ts Normal file
View File

@ -0,0 +1,99 @@
import { AuthState } from "./enum/AuthState";
import { Endian, createReader, createWriter } from "bufferstuff";
import { existsSync, readFileSync } from "fs";
import { createServer } from "net";
import { Packet } from "./enum/Packet";
import { WebSocket } from "ws";
import IClientConfig from "./interface/IClientConfig";
import Constants from "./Constants";
export default class Client {
constructor() {
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());
let CLIENT_ID = 0;
const server = createServer((socket) => {
let authed = false;
let queuedMessages = new Array<Buffer>();
let txBytes = 0;
let rxBytes = 0;
const thisClientId = CLIENT_ID++;
console.log(`[LOCAL CLIENT ${thisClientId}] New Connection`);
const txrxInterval = setInterval(() => {
console.log(`[LOCAL CLIENT ${thisClientId}] 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(Constants.KEEPALIVE_PACKET);
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);
console.log(`[LOCAL CLIENT ${thisClientId}] Remote server terminated the connection.`);
}
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);
console.log(`[LOCAL CLIENT ${thisClientId}] Disconnected`);
}
socket.on("close", serverCloseOrError);
socket.on("error", serverCloseOrError);
});
server.listen(config.localPort, () => console.log(`Local server listening at ${config.localPort}`));
}
}

7
src/Constants.ts Normal file
View File

@ -0,0 +1,7 @@
import { Endian, createWriter } from "bufferstuff";
import { Packet } from "./enum/Packet";
export default abstract class Constants {
public static DEBUG_MODE = true;
public static KEEPALIVE_PACKET = createWriter(Endian.LE, 1).writeUByte(Packet.KeepAlive).toBuffer();
}

115
src/Server.ts Normal file
View File

@ -0,0 +1,115 @@
import { AuthState } from "./enum/AuthState";
import { Endian, createReader, createWriter } from "bufferstuff";
import { existsSync, readFileSync } from "fs";
import { Socket } from "net";
import { Packet } from "./enum/Packet";
import { WebSocketServer } from "ws";
import IServerConfig from "./interface/IServerConfig";
import Constants from "./Constants";
export default class Server {
constructor() {
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(Constants.KEEPALIVE_PACKET);
}, 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);
});
}
}