commit 502b52933722f499dc2286b1c0ee34e73ce5c11d
parent a8dd2af60f4bd1e12858d6d5d1e817548583b372
Author: Andreas Gruhler <andreas.gruhler@adfinis.com>
Date: Fri, 20 Sep 2024 16:00:11 +0200
feat: add simple websocket listener
Diffstat:
3 files changed, 97 insertions(+), 16 deletions(-)
diff --git a/package-lock.json b/package-lock.json
@@ -17,7 +17,8 @@
"react-cookie": "^7.2.0",
"react-dom": "^18.3.1",
"react-router-dom": "^6.25.1",
- "react-select": "^5.8.0"
+ "react-select": "^5.8.0",
+ "ws": "^8.18.0"
},
"devDependencies": {
"@types/react": "^18.3.3",
@@ -4607,11 +4608,10 @@
}
},
"node_modules/vite": {
- "version": "4.5.3",
- "resolved": "https://registry.npmjs.org/vite/-/vite-4.5.3.tgz",
- "integrity": "sha512-kQL23kMeX92v3ph7IauVkXkikdDRsYMGTVl5KY2E9OY4ONLvkHf04MDTbnfo6NKxZiDLWzVpP5oTa8hQD8U3dg==",
+ "version": "4.5.5",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-4.5.5.tgz",
+ "integrity": "sha512-ifW3Lb2sMdX+WU91s3R0FyQlAyLxOzCSCP37ujw0+r5POeHPwe6udWVIElKQq8gk3t7b8rkmvqC6IHBpCff4GQ==",
"dev": true,
- "license": "MIT",
"dependencies": {
"esbuild": "^0.18.10",
"postcss": "^8.4.27",
@@ -4796,7 +4796,6 @@
"version": "8.18.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz",
"integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==",
- "license": "MIT",
"engines": {
"node": ">=10.0.0"
},
diff --git a/package.json b/package.json
@@ -20,7 +20,8 @@
"react-cookie": "^7.2.0",
"react-dom": "^18.3.1",
"react-router-dom": "^6.25.1",
- "react-select": "^5.8.0"
+ "react-select": "^5.8.0",
+ "ws": "^8.18.0"
},
"devDependencies": {
"@types/react": "^18.3.3",
diff --git a/src/api/server.cjs b/src/api/server.cjs
@@ -1,3 +1,4 @@
+const ws = require('ws');
const http = require('node:http');
const url = require('url');
const jwt = require('jsonwebtoken');
@@ -38,7 +39,7 @@ if (jwt_secret === undefined) {
process.exit()
}
-// configure mail transport
+// Configure mail transport
// https://nodemailer.com/smtp
const transport = nodemailer.createTransport({
host: process.env.SMTP_HOST,
@@ -50,6 +51,14 @@ const transport = nodemailer.createTransport({
},
});
+// Define API paths and allowed request methods
+const paths = [
+ '/v1/healthz',
+ '/v1/auth/verify',
+ '/v1/echo',
+ '/v1/auth/requestMagicLink',
+]
+
console.log("Backend API:", api_uri);
console.log("Backend API port:", api_port);
console.log("Redirect URI:", redirect_uri);
@@ -60,14 +69,17 @@ console.log("SMTP starttls:", process.env.SMTP_STARTTLS);
console.log("* * * * * * *", "Uncle roger ready 🍚", "* * * * * * *");
// Create a simple http API server
-// https://nodejs.org/en/learn/modules/anatomy-of-an-http-transaction
-const server = http.createServer(async (req, res) => {
+// - https://nodejs.org/en/learn/modules/anatomy-of-an-http-transaction
+// - https://nodejs.org/api/http.html#httpcreateserveroptions-requestlistener
+const server = http.createServer();
+
+// Listen on request events
+server.on('request', async (req, res) => {
const url = new URL(`${api_uri}:${api_port}${req.url}`);
const search_params = url.searchParams;
console.log('> Path', url.pathname);
console.log(' Method', req.method);
- console.log(' Status code', req.client._httpMessage.statusCode);
// set headers
res.setHeader('Content-Type', 'application/json');
@@ -118,6 +130,14 @@ const server = http.createServer(async (req, res) => {
} catch (error) {
serverError(res, error);
}
+ } else {
+ const pathExists = paths.find((i) => i === url.pathname);
+ if (pathExists) {
+ // wrong method for this path
+ notAllowed(res, req.method);
+ } else {
+ notFound(res, url.pathname);
+ }
}
// cors pre-flight request uses options method
} else if (req.method === 'OPTIONS') {
@@ -183,18 +203,33 @@ const server = http.createServer(async (req, res) => {
}
})
} else {
- notFound(res);
+ const pathExists = paths.find((i) => i === url.pathname);
+ if (pathExists) {
+ // wrong method for this path
+ notAllowed(res, req.method);
+ } else {
+ notFound(res, url.pathname);
+ }
}
} else {
- notFound(res);
+ notAllowed(res, req.method);
}
});
-function notFound(res) {
+function notFound(res, path) {
+ console.log('x Error: 404 not found');
res.statusCode = 404;
res.end(JSON.stringify({
message: 'no tea cup 🍵 use fingaa 👇 haaiiyaa!',
- error: '404 not found',
+ error: `404 path ${path} not found`,
+ }));
+}
+function notAllowed(res, method) {
+ console.log('x Error: 403 method', method, 'not allowed');
+ res.statusCode = 403;
+ res.end(JSON.stringify({
+ message: 'why english tea 🫖 cup to measure rice? Use fingaa 👇 haaiiyaa!',
+ error: `403 method ${method} not allowed`,
}));
}
function serverError(res, err) {
@@ -270,5 +305,51 @@ async function getUser(email) {
}
}
-// listen and serve
+// Create websocket listeners
+// https://github.com/websockets/ws?tab=readme-ov-file#multiple-servers-sharing-a-single-https-server
+const wss1 = new ws.WebSocketServer({ noServer: true});
+
+// Keep track of connected websocket clients
+// https://javascript.info/websocket
+const clients = new Set();
+
+// Listen for websocket connections
+wss1.on('connection', function connection(sock) {
+ sock.on('message', function message(data) {
+ console.log(' Uncle roger hears: %s', data);
+ });
+
+ sock.on('error', console.error);
+
+ // Client closes the connection
+ sock.on('close', function(event) {
+ console.log(" Close event:", event.code);
+ console.log(" Close reason:", event.reason);
+ clients.delete(ws);
+ });
+
+ clients.add(sock);
+ console.log(`~ Received a new websocket connection`);
+ console.log(` ${sock} connected`);
+
+ sock.send('🎵 Streaming leaderboard live data..');
+ sock.close(1000, '< Websocket rice party done 👋 uncle roger disowns niece/nephew');
+});
+
+// Listen to upgrade event
+server.on('upgrade', function upgrade(req, sock, head) {
+ const url = new URL(`${api_uri}:${api_port}${req.url}`);
+
+ console.log('> WS path', url.pathname);
+
+ if (url.pathname === '/v1/leaderboard') {
+ wss1.handleUpgrade(req, sock, head, function done(sock) {
+ wss1.emit('connection', sock, req);
+ });
+ } else {
+ sock.destroy();
+ }
+});
+
+// Listen and serve
server.listen(api_port);