diff --git a/package.json b/package.json index 54180fe..b7440eb 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "dependencies": { "cors": "^2.8.5", "express": "^4.18.1", + "socket.io": "^4.7.5", "uuid": "^8.3.2" } } diff --git a/src/index.js b/src/index.js index 1dd18d7..178f683 100644 --- a/src/index.js +++ b/src/index.js @@ -4,14 +4,43 @@ const {supportedLanguages} = require("./run-code/instructions"); const express = require("express"); const bodyParser = require("body-parser"); const app = express(); -const port = process.env.PORT || 3000; +const port = process.env.PORT || 8000; const cors = require("cors"); const {info} = require("./run-code/info"); +const socketIO = require("socket.io"); +const { runCodeViaSocket } = require("./run-code/use-socket"); + +const server = require("node:http").createServer(app); app.use(bodyParser.json()); app.use(bodyParser.urlencoded({extended: true})); app.use(cors()); +const io = socketIO(server, { + cors: { + origin: "*" + } +}); + +io.on('connection', (socket) => { + console.log('a user connected') + socket.on("code", async ({code, language, input = ""}) => { + try { + await runCodeViaSocket(socket, {code, language, input}) + } catch (err) { + console.log(err) + const timeStamp = Date.now() + + socket.emit("error", { + timeStamp, + status: err?.status || 500, + ...err + }) + socket.disconnect() + } + }) +}); + const sendResponse = (res, statusCode, body) => { const timeStamp = Date.now() @@ -44,4 +73,4 @@ app.get('/list', async (req, res) => { sendResponse(res, 200, {supportedLanguages: body}) }) -app.listen(port); +server.listen(port); diff --git a/src/run-code/use-socket.js b/src/run-code/use-socket.js new file mode 100644 index 0000000..cebe32c --- /dev/null +++ b/src/run-code/use-socket.js @@ -0,0 +1,119 @@ +const {commandMap, supportedLanguages} = require("./instructions") +const {createCodeFile} = require("../file-system/createCodeFile") +const {removeCodeFile} = require("../file-system/removeCodeFile") +const {info} = require("./info") + +const {spawn} = require("child_process"); + +async function runCodeViaSocket(socket, {language = "", code = "", input = ""}) { + if (!socket) { + throw { + status: 500, + error: "Could not establish a socket connection." + } + } + + const timeout = 120; + + if (code === "") + throw { + status: 400, + error: "No Code found to execute." + } + + if (!supportedLanguages.includes(language)) + throw { + status: 400, + error: `Please enter a valid language. Check documentation for more details: https://github.com/Jaagrav/CodeX-API#readme. The languages currently supported are: ${supportedLanguages.join(', ')}.` + } + + const {jobID} = await createCodeFile(language, code); + const {compileCodeCommand, compilationArgs, executeCodeCommand, executionArgs, outputExt} = commandMap(jobID, language); + + if (compileCodeCommand) { + await new Promise((resolve, reject) => { + const compileCode = spawn(compileCodeCommand, compilationArgs || []) + compileCode.stderr.on('data', (error) => { + reject({ + status: 200, + output: '', + error: error.toString(), + language + }) + }); + compileCode.on('exit', () => { + resolve() + }) + }) + } + + const result = await new Promise((resolve, reject) => { + const executeCode = spawn(executeCodeCommand, executionArgs || []); + let output = "", error = ""; + + const timer = setTimeout(async () => { + executeCode.kill("SIGHUP"); + + await removeCodeFile(jobID, language, outputExt); + + reject({ + status: 408, + error: `CodeX API Timed Out. Your code took too long to execute, over ${timeout} seconds. Make sure you are sending input as payload if your code expects an input.` + }) + }, timeout * 1000); + + if (input !== "") { + input.split('\n').forEach((line) => { + executeCode.stdin.write(`${line}\n`); + }); + // Not ending stream to allow incoming data + // executeCode.stdin.end(); + } + + // Allow more data through socket emit + socket.on("input", (input) => { + executeCode.stdin.write(`${input}\n`) + }) + + executeCode.stdin.on('error', (err) => { + + console.log('stdin err', err); + }); + + executeCode.stdout.on('data', (data) => { + output += data.toString(); + socket.emit('output', { + output: data.toString(), + executionStatus: true + }) + }); + + executeCode.stderr.on('data', (data) => { + error += data.toString(); + socket.emit('error', { + error: data.toString(), + executionStatus: true + }) + }); + + executeCode.on('exit', (err) => { + clearTimeout(timer); + socket.emit('exit', { + output, + error, + executionStatus: false + }) + resolve({output, error}); + }); + }) + + await removeCodeFile(jobID, language, outputExt); + + return { + ...result, + language, + info: await info(language) + } +} + +module.exports = {runCodeViaSocket} \ No newline at end of file