: initial commit

This commit is contained in:
StarAppeal
2024-02-20 03:47:50 +01:00
parent 21731c0db0
commit dd082eb922
11 changed files with 1949 additions and 0 deletions
+2
View File
@@ -1,3 +1,5 @@
.idea/
# Logs
logs
*.log
+1725
View File
File diff suppressed because it is too large Load Diff
+27
View File
@@ -0,0 +1,27 @@
{
"name": "matrix-backend",
"version": "1.0.0",
"main": "index.js",
"scripts": {
"start": "tsc && node dist/index.js",
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"description": "",
"dependencies": {
"@types/express": "^4.17.21",
"@types/jsonwebtoken": "^9.0.5",
"@types/node": "^20.11.19",
"@types/ws": "^8.5.10",
"dotenv": "^16.4.4",
"express": "^4.18.2",
"jsonwebtoken": "^9.0.2",
"typescript": "^5.3.3",
"ws": "^8.16.0"
},
"devDependencies": {
"prettier": "^3.2.5"
}
}
+11
View File
@@ -0,0 +1,11 @@
import express from "express";
import { ExtendedWebSocketServer } from "./websocket";
import { RestAPI } from "./rest";
const app = express();
const server = app.listen(3000, () => {
console.log("Server is running on port 3000");
});
const webSocketServer = new ExtendedWebSocketServer(server);
const restAPI = new RestAPI(app, webSocketServer);
+4
View File
@@ -0,0 +1,4 @@
export interface DecodedToken {
uuid: string;
name: string;
}
@@ -0,0 +1,6 @@
import { IncomingMessage } from "node:http";
import { DecodedToken } from "./decodedToken";
export interface ExtendedIncomingMessage extends IncomingMessage {
payload: DecodedToken;
}
+7
View File
@@ -0,0 +1,7 @@
import { WebSocket } from "ws";
import { DecodedToken } from "./decodedToken";
export interface ExtendedWebSocket extends WebSocket {
payload: DecodedToken;
isAlive: boolean;
}
+31
View File
@@ -0,0 +1,31 @@
import express, { Application, Request, Response } from "express";
import { ExtendedWebSocketServer } from "./websocket";
export class RestAPI {
constructor(
private app: Application,
private webSocketServer: ExtendedWebSocketServer,
) {
this.app.use(express.json());
this.setupRoutes();
}
private setupRoutes() {
this.app.post("/broadcast", (req: Request, res: Response) => {
const message: string = req.body.message;
this.webSocketServer.broadcast(message);
res.status(200).send("Broadcast erfolgreich.");
});
this.app.post("/send-message", (req, res) => {
const message = req.body.message;
const user = req.body.user;
this.webSocketServer.sendMessageToUser(user, message);
res.status(200).send("OK");
});
}
}
+37
View File
@@ -0,0 +1,37 @@
import "dotenv/config";
import { IncomingMessage } from "node:http";
import jwt from "jsonwebtoken";
import { DecodedToken } from "../interfaces/decodedToken";
import { ExtendedIncomingMessage } from "../interfaces/extendedIncomingMessage";
export function verifyClient(
request: IncomingMessage,
callback: (res: boolean, code?: number, message?: string) => void,
) {
const token = request.headers["authorization"];
if (!token) {
reject(request, callback);
} else {
jwt.verify(token, process.env.SECRET_KEY as string, (err, decoded) => {
if (err) {
reject(request, callback);
} else {
(request as ExtendedIncomingMessage).payload = decoded as DecodedToken;
callback(true);
}
});
}
}
const reject = (
request: IncomingMessage,
callback: (res: boolean, code?: number, message?: string) => void,
) => {
console.log(
"Connection refused",
`${request.socket.remoteAddress}:${request.socket.remotePort}`,
);
callback(false, 401, "Unauthorized");
};
+85
View File
@@ -0,0 +1,85 @@
import { Server } from "http";
import { WebSocket, Server as WebSocketServer } from "ws";
import { verifyClient } from "./utils/verifyClient";
import { ExtendedIncomingMessage } from "./interfaces/extendedIncomingMessage";
import { ExtendedWebSocket } from "./interfaces/extendedWebsocket";
import { DecodedToken } from "./interfaces/decodedToken";
export class ExtendedWebSocketServer {
private wss: WebSocketServer;
constructor(server: Server) {
this.wss = new WebSocketServer({
server,
verifyClient: (info, callback) => verifyClient(info.req, callback),
});
this.setupWebSocket();
const interval = setInterval(() => {
this.wss.clients.forEach(
(ws: WebSocket & { isAlive?: boolean; payload?: DecodedToken }) => {
console.log(ws.payload?.name + ": isAlive: " + ws.isAlive);
if (!ws.isAlive) return ws.terminate();
ws.send("keepalive");
ws.isAlive = false;
ws.ping();
},
);
}, 30000);
this.wss.on("close", function close() {
clearInterval(interval);
});
}
private setupWebSocket() {
this.wss.on(
"connection",
(ws: ExtendedWebSocket, request: ExtendedIncomingMessage) => {
ws.payload = request.payload;
ws.isAlive = true;
console.log("WebSocket client connected");
ws.on("error", console.error);
ws.on("pong", () => {
ws.isAlive = true;
});
ws.on("message", (data) => {
const message = data.toString();
console.log("Received message:", message);
const json: { username: string; message: string } =
JSON.parse(message);
this.sendMessageToUser(json.username, json.message);
});
},
);
}
public broadcast(message: string) {
this.wss.clients.forEach((client) => {
if (client.readyState === WebSocket.OPEN) {
client.send(message);
}
});
}
public sendMessageToUser(userName: string, message: string) {
this.wss.clients.forEach(
(client: WebSocket & { payload?: DecodedToken }) => {
if (
client.payload?.name === userName &&
client.readyState === WebSocket.OPEN
) {
client.send(message, { binary: false });
}
},
);
}
}
+14
View File
@@ -0,0 +1,14 @@
{
"compilerOptions": {
"target": "es2016",
"module": "commonjs",
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"skipLibCheck": true,
"outDir": "./dist"
},
"include": [
"src/**/*.ts"
]
}