: initial commit
This commit is contained in:
@@ -1,3 +1,5 @@
|
||||
.idea/
|
||||
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
|
||||
Generated
+1725
File diff suppressed because it is too large
Load Diff
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
@@ -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;
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
import { WebSocket } from "ws";
|
||||
import { DecodedToken } from "./decodedToken";
|
||||
|
||||
export interface ExtendedWebSocket extends WebSocket {
|
||||
payload: DecodedToken;
|
||||
isAlive: boolean;
|
||||
}
|
||||
+31
@@ -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");
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
};
|
||||
@@ -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 });
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es2016",
|
||||
"module": "commonjs",
|
||||
"esModuleInterop": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"strict": true,
|
||||
"skipLibCheck": true,
|
||||
"outDir": "./dist"
|
||||
},
|
||||
"include": [
|
||||
"src/**/*.ts"
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user