websocket thingy

This commit is contained in:
janderedev 2022-01-22 22:37:59 +01:00
parent 09c4d462ed
commit 66232da6f8
Signed by: Lea
GPG key ID: 5D5E18ACB990F57A
11 changed files with 195 additions and 6 deletions

View file

@ -14,8 +14,10 @@
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"@types/express": "^4.17.13", "@types/express": "^4.17.13",
"@types/ws": "^8.2.2",
"dotenv": "^14.2.0", "dotenv": "^14.2.0",
"express": "^4.17.2", "express": "^4.17.2",
"log75": "^2.2.0" "log75": "^2.2.0",
"ws": "^8.4.2"
} }
} }

View file

@ -1,5 +1,5 @@
import { config } from 'dotenv'; import { config } from 'dotenv';
import Express, { Request, Response } from "express"; import Express from "express";
import Log75, { LogLevel } from 'log75'; import Log75, { LogLevel } from 'log75';
config(); config();
@ -10,8 +10,15 @@ const DEBUG = process.env.NODE_ENV != 'production';
const logger: Log75 = new (Log75 as any).default(DEBUG ? LogLevel.Debug : LogLevel.Standard); const logger: Log75 = new (Log75 as any).default(DEBUG ? LogLevel.Debug : LogLevel.Standard);
const app = Express(); const app = Express();
app.get('/', (req: Request, res: Response) => { export { logger, app, PORT }
res.send({ msg: "yo" });
});
app.listen(PORT, () => logger.info(`Listening on port ${PORT}`)); (async () => {
await Promise.all([
import('./middlewares/log'),
import('./routes/internal/ws'),
import('./routes/root'),
]);
logger.done('All routes and middlewares loaded');
})();
import('./server');

View file

@ -0,0 +1,7 @@
import { Request, Response } from "express";
import { app, logger } from "..";
app.use('*', (req: Request, res: Response, next: () => void) => {
logger.debug(`${req.method} ${req.path}`);
next();
});

View file

@ -0,0 +1,53 @@
/**
* Provides a WebSocket the bot can connect to.
* (IPC on crack)
*/
import { WebSocketServer, WebSocket } from 'ws';
import { logger } from "../..";
import server from '../../server';
if (!process.env.BOT_API_TOKEN) {
logger.error(`$BOT_API_TOKEN is not set. This token is `
+ `required for the bot to communicate with the API.`);
process.exit(1);
}
const { BOT_API_TOKEN } = process.env;
const wsServer = new WebSocketServer({ noServer: true });
const sockets: WebSocket[] = [];
wsServer.on('connection', (sock) => {
sockets.push(sock);
sock.once('close', () => {
logger.debug('WS closed');
const i = sockets.findIndex(s => s == sock);
sockets.splice(i, 1);
});
sock.on('message', (msg) => {
logger.debug(`[WS] [<] ${msg.toString()}`);
sock.send(JSON.stringify({ "h": JSON.parse(msg.toString()) }));
});
});
server.on('upgrade', (req, socket, head) => {
logger.debug(`WS Upgrade ${req.url}`);
switch(req.url) {
case '/internal/ws':
if (req.headers['authorization'] !== BOT_API_TOKEN) {
logger.debug('WS unauthorized');
head.write(JSON.stringify({ error: 'Not authenticated' }, null, 4));
socket.end();
} else {
wsServer.handleUpgrade(req, socket, head, (sock) => {
wsServer.emit('connection', sock, req);
});
}
break;
default:
head.write(JSON.stringify({ error: 'Cannot open WebSocket on this endpoint' }, null, 4));
socket.end();
}
});

6
api/src/routes/root.ts Normal file
View file

@ -0,0 +1,6 @@
import { app } from '..';
import { Request, Response } from 'express';
app.get('/', (req: Request, res: Response) => {
res.send({ msg: "yo" });
});

5
api/src/server.ts Normal file
View file

@ -0,0 +1,5 @@
import { app, logger, PORT } from ".";
const server = app.listen(PORT, () => logger.info(`Listening on port ${PORT}`));
export default server;

View file

@ -64,6 +64,13 @@
"@types/mime" "^1" "@types/mime" "^1"
"@types/node" "*" "@types/node" "*"
"@types/ws@^8.2.2":
version "8.2.2"
resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.2.2.tgz#7c5be4decb19500ae6b3d563043cd407bf366c21"
integrity sha512-NOn5eIcgWLOo6qW8AcuLZ7G8PycXu0xTxxkS6Q18VWFxgPUSOwV0pBj2a/4viNZVu25i7RIB7GttdkAIUUXOOg==
dependencies:
"@types/node" "*"
accepts@~1.3.7: accepts@~1.3.7:
version "1.3.7" version "1.3.7"
resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.7.tgz#531bc726517a3b2b41f850021c6cc15eaab507cd" resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.7.tgz#531bc726517a3b2b41f850021c6cc15eaab507cd"
@ -429,3 +436,8 @@ vary@~1.1.2:
version "1.1.2" version "1.1.2"
resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc"
integrity sha1-IpnwLG3tMNSllhsLn3RSShj2NPw= integrity sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=
ws@^8.4.2:
version "8.4.2"
resolved "https://registry.yarnpkg.com/ws/-/ws-8.4.2.tgz#18e749868d8439f2268368829042894b6907aa0b"
integrity sha512-Kbk4Nxyq7/ZWqr/tarI9yIt/+iNNFOjBXEWgTb4ydaNHBNGgvf2QHbS9fdfsndfjFlFwEd4Al+mw83YkaD10ZA==

View file

@ -0,0 +1,70 @@
/**
* This handles communication with the API server.
*/
import ws from "ws";
import logger from "../logger";
import { EventEmitter } from "events";
const wsEvents = new EventEmitter();
const { API_WS_URL, API_WS_TOKEN } = process.env;
const wsQueue: { [key: string]: string }[] = [];
let client: ws|undefined = undefined;
if (!API_WS_URL || !API_WS_TOKEN)
logger.info("$API_WS_URL or $API_WS_TOKEN not found.");
else {
logger.info(`$API_WS_URL and $API_WS_TOKEN set; Connecting to ${API_WS_URL}`);
connect();
}
function connect() {
if (client && client.readyState == ws.OPEN) client.close();
client = new ws(API_WS_URL!, { headers: { authorization: API_WS_TOKEN! } });
client.once("open", () => {
logger.debug("WS connected");
if (wsQueue.length > 0) {
logger.debug(`Attempting to send ${wsQueue.length} queued WS messages`);
while (wsQueue.length > 0) {
if (client?.readyState != ws.OPEN) break;
const data = JSON.stringify(wsQueue.shift());
logger.debug(`[WS] [FROM QUEUE] [>] ${data}`);
client.send(data);
}
}
});
client.once("close", () => {
client = undefined;
logger.warn(`WS closed, reconnecting in 3 seconds`);
setTimeout(connect, 3000);
});
client.once('error', (err) => {
client = undefined;
logger.warn(`WS: ${err}`);
});
client.on('message', (msg) => {
logger.debug(`[WS] [<] ${msg.toString('utf8')}`);
try {
wsEvents.emit('message', JSON.parse(msg.toString('utf8')));
} catch(e) { console.error(e) }
});
}
function wsSend(data: { [key: string]: string }) {
if (client && client.readyState == client.OPEN) {
logger.debug(`[WS] [>] ${JSON.stringify(data)}`);
client.send(JSON.stringify(data));
} else {
logger.debug(`[WS] [QUEUED] [>] ${JSON.stringify(data)}`);
wsQueue.push(data);
}
}
setInterval(() => wsSend({ "among": "us" }), 1000);
export { wsEvents, wsSend }

View file

@ -25,4 +25,5 @@ export { client }
import('./bot/modules/event_handler'); import('./bot/modules/event_handler');
import('./bot/modules/tempbans'); import('./bot/modules/tempbans');
import('./bot/modules/user_scan'); import('./bot/modules/user_scan');
import('./bot/modules/api_communication');
})(); })();

6
package.json Normal file
View file

@ -0,0 +1,6 @@
{
"dependencies": {
"@types/ws": "^8.2.2",
"ws": "^8.4.2"
}
}

20
yarn.lock Normal file
View file

@ -0,0 +1,20 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
"@types/node@*":
version "17.0.10"
resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.10.tgz#616f16e9d3a2a3d618136b1be244315d95bd7cab"
integrity sha512-S/3xB4KzyFxYGCppyDt68yzBU9ysL88lSdIah4D6cptdcltc4NCPCAMc0+PCpg/lLIyC7IPvj2Z52OJWeIUkog==
"@types/ws@^8.2.2":
version "8.2.2"
resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.2.2.tgz#7c5be4decb19500ae6b3d563043cd407bf366c21"
integrity sha512-NOn5eIcgWLOo6qW8AcuLZ7G8PycXu0xTxxkS6Q18VWFxgPUSOwV0pBj2a/4viNZVu25i7RIB7GttdkAIUUXOOg==
dependencies:
"@types/node" "*"
ws@^8.4.2:
version "8.4.2"
resolved "https://registry.yarnpkg.com/ws/-/ws-8.4.2.tgz#18e749868d8439f2268368829042894b6907aa0b"
integrity sha512-Kbk4Nxyq7/ZWqr/tarI9yIt/+iNNFOjBXEWgTb4ydaNHBNGgvf2QHbS9fdfsndfjFlFwEd4Al+mw83YkaD10ZA==