diff --git a/infrastructure/excalidraw-backend/build.py b/infrastructure/excalidraw-backend/build.py new file mode 100644 index 0000000..ec5bcff --- /dev/null +++ b/infrastructure/excalidraw-backend/build.py @@ -0,0 +1,57 @@ +from os import environ +from datetime import datetime +from pybuilder.core import task, init +from ddadevops import * + +name = "excalidraw-backend" +MODULE = "excalidraw-backend" +PROJECT_ROOT_PATH = "../.." +version = "1.4.2-SNAPSHOT" + + +@init +def initialize(project): + image_tag = version + if "dev" in image_tag: + image_tag += datetime.now().strftime("%Y-%m-%d-%H-%M-%S") + + input = { + "name": name, + "module": MODULE, + "stage": "notused", + "project_root_path": PROJECT_ROOT_PATH, + "build_types": ["IMAGE"], + "mixin_types": [], + "image_naming": "NAME_ONLY", + "image_tag": f"{image_tag}", + } + + project.build_depends_on("ddadevops>=4.7.0") + + build = DevopsImageBuild(project, input) + build.initialize_build_dir() + + +@task +def image(project): + build = get_devops_build(project) + build.image() + + +@task +def drun(project): + build = get_devops_build(project) + build.drun() + + +@task +def test(project): + build = get_devops_build(project) + build.test() + + +@task +def publish(project): + build = get_devops_build(project) + build.dockerhub_login() + build.dockerhub_publish() diff --git a/infrastructure/excalidraw-backend/image/Dockerfile b/infrastructure/excalidraw-backend/image/Dockerfile new file mode 100644 index 0000000..a11916d --- /dev/null +++ b/infrastructure/excalidraw-backend/image/Dockerfile @@ -0,0 +1,14 @@ +# Taken from: https://github.com/jitsi/excalidraw-backend +FROM node:16.17-slim + +WORKDIR /excalidraw-backend + + +COPY resources/package.json resources/package-lock.json resources/tsconfig.json resources/src ./ +RUN npm install +RUN npm run build + +EXPOSE 80 +EXPOSE 9090 + +CMD ["npm", "start"] \ No newline at end of file diff --git a/infrastructure/excalidraw-backend/image/resources/package.json b/infrastructure/excalidraw-backend/image/resources/package.json new file mode 100644 index 0000000..1a02523 --- /dev/null +++ b/infrastructure/excalidraw-backend/image/resources/package.json @@ -0,0 +1,52 @@ +{ + "name": "excalidraw-backend", + "version": "1.0.0", + "main": "src/index.js", + "description": "Excalidraw backend", + "repository": { + "type": "git", + "url": "https://github.com/jitsi/excalidraw-backend" + }, + "private": true, + "engines": { + "node": ">=14.0.0", + "npm": ">=7.0.0" + }, + "dependencies": { + "@types/debug": "4.1.5", + "@types/express": "4.17.11", + "@types/node": "14.14.31", + "@types/socket.io": "2.1.4", + "cross-env": "^7.0.3", + "debug": "4.3.1", + "dotenv": "^10.0.0", + "express": "4.17.1", + "socket.io": "^2.5.0", + "socket.io-prometheus-metrics": "^1.0.6", + "ts-node-dev": "^1.1.8", + "typescript": "4.2.3" + }, + "license": "MIT", + "scripts": { + "build": "tsc", + "lint": "eslint .", + "lint-fix": "eslint . --fix", + "start": "tsc && node dist/index.js", + "start:local": "tsc && DEBUG='engine,app,socket.io:client,server' node dist/index.js", + "start:dev": "cross-env NODE_ENV=development ts-node-dev --respawn --transpile-only src/index.ts" + }, + "devDependencies": { + "@jitsi/eslint-config": "^4.1.0", + "@types/dotenv": "^8.2.0", + "@typescript-eslint/eslint-plugin": "5.30.5", + "@typescript-eslint/parser": "5.30.4", + "eslint": "8.1.0", + "eslint-plugin-import": "2.25.2", + "eslint-plugin-jsdoc": "37.0.3", + "eslint-plugin-typescript-sort-keys": "^2.1.0" + }, + "optionalDependencies": { + "bufferutil": "^4.0.6", + "utf-8-validate": "^5.0.9" + } +} diff --git a/infrastructure/excalidraw-backend/image/resources/src/.eslintrc.js b/infrastructure/excalidraw-backend/image/resources/src/.eslintrc.js new file mode 100644 index 0000000..8cc173e --- /dev/null +++ b/infrastructure/excalidraw-backend/image/resources/src/.eslintrc.js @@ -0,0 +1,7 @@ +module.exports = { + extends: [ + '@jitsi/eslint-config', + '@jitsi/eslint-config/jsdoc', + '@jitsi/eslint-config/typescript', + ], +}; diff --git a/infrastructure/excalidraw-backend/image/resources/src/index.ts b/infrastructure/excalidraw-backend/image/resources/src/index.ts new file mode 100644 index 0000000..9f05c40 --- /dev/null +++ b/infrastructure/excalidraw-backend/image/resources/src/index.ts @@ -0,0 +1,105 @@ +import debug from 'debug'; +import dotenv from 'dotenv'; +import express from 'express'; +import http from 'http'; +import socketIO from 'socket.io'; +import * as prometheus from 'socket.io-prometheus-metrics'; + +const serverDebug = debug('server'); + +dotenv.config( + process.env.NODE_ENV === 'development' + ? { path: '.env.development' } + : { path: '.env.production' } +); + +const app = express(); +const port = process.env.PORT || 80; // default port to listen + +app.get('/', (req, res) => { + res.send('Excalidraw backend is up :)'); +}); + +const server = http.createServer(app); + +server.listen(port, () => { + serverDebug(`listening on port: ${port}`); +}); + +const io = socketIO(server, { + handlePreflightRequest: (req, res) => { + const headers = { + 'Access-Control-Allow-Headers': 'Content-Type, Authorization', + 'Access-Control-Allow-Origin': req.header?.origin ?? 'https://meet.jit.si', + 'Access-Control-Allow-Credentials': true + }; + + res.writeHead(200, headers); + res.end(); + }, + maxHttpBufferSize: 10e6, + pingTimeout: 10000 +}); + +// listens on host:9090/metrics +prometheus.metrics(io, { + collectDefaultMetrics: true +}); + +io.on('connection', socket => { + serverDebug(`connection established! ${socket.conn.request.url}`); + io.to(`${socket.id}`).emit('init-room'); + socket.on('join-room', roomID => { + serverDebug(`${socket.id} has joined ${roomID} for url ${socket.conn.request.url}`); + socket.join(roomID); + if (io.sockets.adapter.rooms[roomID].length <= 1) { + io.to(`${socket.id}`).emit('first-in-room'); + } else { + socket.broadcast.to(roomID).emit('new-user', socket.id); + } + io.in(roomID).emit( + 'room-user-change', + Object.keys(io.sockets.adapter.rooms[roomID].sockets) + ); + }); + + socket.on( + 'server-broadcast', + (roomID: string, encryptedData: ArrayBuffer, iv: Uint8Array) => { + socket.broadcast.to(roomID).emit('client-broadcast', encryptedData, iv); + } + ); + + socket.on( + 'server-volatile-broadcast', + (roomID: string, encryptedData: ArrayBuffer, iv: Uint8Array) => { + socket.volatile.broadcast + .to(roomID) + .emit('client-broadcast', encryptedData, iv); + } + ); + + socket.on('disconnecting', () => { + const rooms = io.sockets.adapter.rooms; + + for (const roomID of Object.keys(socket.rooms)) { + const clients = Object.keys(rooms[roomID].sockets).filter(id => id !== socket.id); + + if (roomID !== socket.id) { + socket.to(roomID).emit('user has left', socket.id); + } + + if (clients.length > 0) { + socket.broadcast.to(roomID).emit('room-user-change', clients); + } + } + }); + + socket.on('disconnect', (reason, details) => { + serverDebug( + `${socket.id} was disconnected from url ${socket.conn.request.url} for the following reason: ${reason} + ${JSON.stringify(details)}` + ); + socket.removeAllListeners(); + }); +}); diff --git a/infrastructure/excalidraw-backend/image/resources/tsconfig.json b/infrastructure/excalidraw-backend/image/resources/tsconfig.json new file mode 100644 index 0000000..14aa52f --- /dev/null +++ b/infrastructure/excalidraw-backend/image/resources/tsconfig.json @@ -0,0 +1,17 @@ +{ + "compilerOptions": { + "allowSyntheticDefaultImports": true, + "target": "es5", + "lib": ["dom", "dom.iterable", "esnext"], + "allowJs": true, + "skipLibCheck": true, + "esModuleInterop": true, + "strict": true, + "moduleResolution": "Node", + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "isolatedModules": true, + "outDir": "dist" + } + } + \ No newline at end of file