From 66232da6f87f43b202e1178ed869464c17c0e976 Mon Sep 17 00:00:00 2001 From: janderedev Date: Sat, 22 Jan 2022 22:37:59 +0100 Subject: [PATCH] websocket thingy --- api/package.json | 4 +- api/src/index.ts | 17 ++++-- api/src/middlewares/log.ts | 7 +++ api/src/routes/internal/ws.ts | 53 ++++++++++++++++++ api/src/routes/root.ts | 6 ++ api/src/server.ts | 5 ++ api/yarn.lock | 12 ++++ bot/src/bot/modules/api_communication.ts | 70 ++++++++++++++++++++++++ bot/src/index.ts | 1 + package.json | 6 ++ yarn.lock | 20 +++++++ 11 files changed, 195 insertions(+), 6 deletions(-) create mode 100644 api/src/middlewares/log.ts create mode 100644 api/src/routes/internal/ws.ts create mode 100644 api/src/routes/root.ts create mode 100644 api/src/server.ts create mode 100644 bot/src/bot/modules/api_communication.ts create mode 100644 package.json create mode 100644 yarn.lock diff --git a/api/package.json b/api/package.json index 67d2a82..81330b0 100644 --- a/api/package.json +++ b/api/package.json @@ -14,8 +14,10 @@ "license": "ISC", "dependencies": { "@types/express": "^4.17.13", + "@types/ws": "^8.2.2", "dotenv": "^14.2.0", "express": "^4.17.2", - "log75": "^2.2.0" + "log75": "^2.2.0", + "ws": "^8.4.2" } } diff --git a/api/src/index.ts b/api/src/index.ts index e2209a9..b7d64f5 100644 --- a/api/src/index.ts +++ b/api/src/index.ts @@ -1,5 +1,5 @@ import { config } from 'dotenv'; -import Express, { Request, Response } from "express"; +import Express from "express"; import Log75, { LogLevel } from 'log75'; 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 app = Express(); -app.get('/', (req: Request, res: Response) => { - res.send({ msg: "yo" }); -}); +export { logger, app, PORT } -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'); diff --git a/api/src/middlewares/log.ts b/api/src/middlewares/log.ts new file mode 100644 index 0000000..a8f6d30 --- /dev/null +++ b/api/src/middlewares/log.ts @@ -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(); +}); diff --git a/api/src/routes/internal/ws.ts b/api/src/routes/internal/ws.ts new file mode 100644 index 0000000..e6f1eea --- /dev/null +++ b/api/src/routes/internal/ws.ts @@ -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(); + } +}); diff --git a/api/src/routes/root.ts b/api/src/routes/root.ts new file mode 100644 index 0000000..40abb62 --- /dev/null +++ b/api/src/routes/root.ts @@ -0,0 +1,6 @@ +import { app } from '..'; +import { Request, Response } from 'express'; + +app.get('/', (req: Request, res: Response) => { + res.send({ msg: "yo" }); +}); \ No newline at end of file diff --git a/api/src/server.ts b/api/src/server.ts new file mode 100644 index 0000000..5f2e17e --- /dev/null +++ b/api/src/server.ts @@ -0,0 +1,5 @@ +import { app, logger, PORT } from "."; + +const server = app.listen(PORT, () => logger.info(`Listening on port ${PORT}`)); + +export default server; diff --git a/api/yarn.lock b/api/yarn.lock index 3df63ec..a28426a 100644 --- a/api/yarn.lock +++ b/api/yarn.lock @@ -64,6 +64,13 @@ "@types/mime" "^1" "@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: version "1.3.7" resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.7.tgz#531bc726517a3b2b41f850021c6cc15eaab507cd" @@ -429,3 +436,8 @@ vary@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" 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== diff --git a/bot/src/bot/modules/api_communication.ts b/bot/src/bot/modules/api_communication.ts new file mode 100644 index 0000000..6b737c6 --- /dev/null +++ b/bot/src/bot/modules/api_communication.ts @@ -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 } diff --git a/bot/src/index.ts b/bot/src/index.ts index 9bb6623..c0e0d6f 100644 --- a/bot/src/index.ts +++ b/bot/src/index.ts @@ -25,4 +25,5 @@ export { client } import('./bot/modules/event_handler'); import('./bot/modules/tempbans'); import('./bot/modules/user_scan'); + import('./bot/modules/api_communication'); })(); diff --git a/package.json b/package.json new file mode 100644 index 0000000..56d04d4 --- /dev/null +++ b/package.json @@ -0,0 +1,6 @@ +{ + "dependencies": { + "@types/ws": "^8.2.2", + "ws": "^8.4.2" + } +} diff --git a/yarn.lock b/yarn.lock new file mode 100644 index 0000000..55d13e0 --- /dev/null +++ b/yarn.lock @@ -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==