diff --git a/package-lock.json b/package-lock.json index f010ef2..67a130d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,9 @@ "license": "ISC", "dependencies": { "axios": "^1.2.2", - "express": "^4.18.2" + "express": "^4.18.2", + "uuid": "^9.0.0", + "ws": "^8.13.0" }, "devDependencies": { "nodemon": "^2.0.22" @@ -1020,6 +1022,14 @@ "node": ">= 0.4.0" } }, + "node_modules/uuid": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz", + "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==", + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", @@ -1027,6 +1037,26 @@ "engines": { "node": ">= 0.8" } + }, + "node_modules/ws": { + "version": "8.13.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz", + "integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } } }, "dependencies": { @@ -1769,10 +1799,21 @@ "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==" }, + "uuid": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz", + "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==" + }, "vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==" + }, + "ws": { + "version": "8.13.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz", + "integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==", + "requires": {} } } } diff --git a/package.json b/package.json index fdbd8b1..5a82677 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,9 @@ "license": "ISC", "dependencies": { "axios": "^1.2.2", - "express": "^4.18.2" + "express": "^4.18.2", + "uuid": "^9.0.0", + "ws": "^8.13.0" }, "devDependencies": { "nodemon": "^2.0.22" diff --git a/src/example/job.json b/src/example/job.json new file mode 100644 index 0000000..3ab6b91 --- /dev/null +++ b/src/example/job.json @@ -0,0 +1,37 @@ +{ + "remoteIdentifier": "AAAAA", + "callback": "undefined", + "type": "card", + "size": { + "width": 600, + "height": 1000 + }, + "elements": [ + { + "type": "image", + "asset": "https://cdn.discordapp.com/attachments/1083687175998152714/1113486254953222205/rainbow_overlay.png", + "x": 0, + "y": 300, + "width": 600, + "height": 1000 + }, + { + "type": "image", + "asset": "https://cdn.discordapp.com/attachments/1083687175998152714/1113486177002070126/template.png", + "x": 0, + "y": 300, + "width": 600, + "height": 1000 + }, + { + "type": "text", + "text": "Ninomae Ina'nis", + "fontSize": 55, + "x": 0, + "y": 700, + "width": 600, + "height": 300, + "horizontalAlignment": "center" + } + ] +} diff --git a/src/index.js b/src/index.js index 88fb15b..0bc91e0 100644 --- a/src/index.js +++ b/src/index.js @@ -1,11 +1,18 @@ const express = require("express"); var crypto = require('crypto'); const { finished } = require("stream"); +const uuid = require("uuid"); const axios = require("axios"); +const path = require("path") +const { WebSocketServer } = require("ws"); +const { request } = require("http"); + +const PORT_WEB = 6968; +const PORT_WSS = 6980; const app = express(); - -const PORT = 6968; +const wss = new WebSocketServer({ port: PORT_WSS }); +console.log(`Websocket listening on port ${PORT_WSS}`); let jobs = { queued: {}, @@ -13,12 +20,16 @@ let jobs = { finished: {} } +let nodes = {}; + app.use(express.json()); app.get('/', (req, res) => { res.send('Job handling server') }) +app.use('/example', express.static(path.join(__dirname, 'example'))); + app.get('/jobs', (req, res) => { let queued = Object.values(jobs['queued']); let waiting = Object.values(jobs['waiting']); @@ -35,7 +46,14 @@ app.get('/jobs', (req, res) => { ); }); -app.post("/jobs", (req, res) => { +function ResolveJob(result){ + let jobId = result["jobId"]; + console.log(`Completed Job ${jobId}`); + delete jobs['queued'][jobId]; + return result; +} + +app.post("/jobs", async (req, res) => { const data = req.body; let jobId = crypto.createHash('md5').update(JSON.stringify(req.body)).digest('hex'); @@ -47,7 +65,18 @@ app.post("/jobs", (req, res) => { jobs['queued'][jobId] = req.body; jobs['queued'][jobId]['jobId'] = jobId; console.log(`Queued Job ${jobId}`); - res.json({ 'jobId': jobId }); + var client = Array.from(wss.clients)[0]; + if(!client) { + res.status(503).json({ 'message': 'No render nodes available', 'jobId': jobId }); + return; + } + client.send(JSON.stringify({"job": req.body})); + let response = new Promise(function (resolve, reject){ + jobs['queued'][jobId]['promise'] = {resolve: resolve, reject: reject}; + }); + console.log("Sent job to node", client.nodeID); + let result = await response.then(ResolveJob) + res.json({ 'jobId': jobId, "path": result["path"] }); }); app.get("/batch", async (req, res) => { @@ -104,6 +133,31 @@ app.post("/jobs/:jobId/completed", async (req, res) => { res.status(400).send({ 'message': `Job ${jobId} not found` }); }); -app.listen(PORT, () => { - console.log("Job Server running") + +wss.on('connection', function connection(ws) { + var nodeID = uuid.v4(); + ws.nodeID = nodeID; + nodes[nodeID] = ws + console.log("New connection from", nodeID) + ws.on('error', console.error); + + ws.on('message', function message(data) { + let request = JSON.parse(data); + console.log('received: %s', data); + + if(request["register"]){ + ws.send(JSON.stringify({"welcome": { "clientId": nodeID }})); + console.log("Node registered", nodeID); + } + + if(request["result"]) { + let jobResult = request["result"] + jobs['queued'][jobResult["jobId"]]['promise'].resolve(jobResult); + } + }); + +}); + +app.listen(PORT_WEB, () => { + console.log(`Job Server API running on ${PORT_WEB}`); }) \ No newline at end of file