From ef726bc7895fb46a3731bfae9f0136b47fb812fb Mon Sep 17 00:00:00 2001 From: clfreville2 Date: Mon, 27 May 2024 08:46:59 +0200 Subject: [PATCH] Add a live edit mode --- dprint.json | 2 +- package.json | 3 ++ src/server.ts | 87 ++++++++++++++++++++++++++++++++++++++++++++++----- 3 files changed, 83 insertions(+), 9 deletions(-) diff --git a/dprint.json b/dprint.json index 26cfbaa..feb14b8 100644 --- a/dprint.json +++ b/dprint.json @@ -1,6 +1,6 @@ { "typescript": { - "quoteStyle": "preferSingle" + "quoteStyle": "preferDouble" }, "includes": ["src/**/*.{ts,tsx,js,jsx,cjs,mjs,json}"], "excludes": [ diff --git a/package.json b/package.json index 85171a3..44ed1b8 100644 --- a/package.json +++ b/package.json @@ -12,8 +12,11 @@ "typescript": "^5.3.3" }, "dependencies": { + "@codemirror/collab": "^6.1.1", + "@codemirror/state": "^6.4.1", "@fastify/cors": "^9.0.0", "@fastify/type-provider-typebox": "^4.0.0", + "@fastify/websocket": "^10.0.1", "@sinclair/typebox": "^0.32.9", "fastify": "^4.27.0", "nanoid": "^5.0.4", diff --git a/src/server.ts b/src/server.ts index f8606ac..8690712 100644 --- a/src/server.ts +++ b/src/server.ts @@ -1,14 +1,14 @@ import cors from "@fastify/cors"; +import websocket, { WebSocket } from '@fastify/websocket'; import { Type, TypeBoxTypeProvider } from "@fastify/type-provider-typebox"; -import Fastify, { FastifyReply, FastifyRequest } from "fastify"; +import Fastify, { FastifyReply } from "fastify"; import { nanoid } from "nanoid"; -import { allocateBuffer, IMAGES } from "runner"; +import { allocateBuffer, getRunner } from "runner"; import { Pull, Push } from "zeromq"; - +import { ChangeSet, Text } from "@codemirror/state"; +import { Update, rebaseUpdates } from "@codemirror/collab"; import * as db from "./database"; -console.log(IMAGES.logo); - const sender = new Push(); await sender.bind(`tcp://127.0.0.1:5557`); const receiver = new Pull(); @@ -17,14 +17,85 @@ await receiver.bind(`tcp://127.0.0.1:5558`); const clients: Record = {}; const generateId = () => nanoid(32); +let updates: Update[] = []; +let doc = Text.of(['']); +const liveClients: WebSocket[] = []; + +function send(socket: WebSocket, requestId: number, payload: unknown) { + const response = { + _request: requestId, + payload + }; + socket.send(JSON.stringify(response)); +} + const fastify = Fastify({ logger: true, }).withTypeProvider(); await fastify.register(cors, { - origin: process.env.ALLOW_ORIGIN || "*", + origin: process.env.ALLOW_ORIGIN || '*', +}); +fastify.register(websocket); +fastify.get("/live", { websocket: true }, (socket, req) => { + liveClients.push(socket); + socket.on("message", message => { + const data = JSON.parse(message.toString()); + const requestId = data._request; + if (data.type === "pullUpdates") { + send(socket, requestId, updates.slice(data.version)) + } else if (data.type === "pushUpdates") { + let received = data.updates.map((json: any) => ({ + clientID: json.clientID, + changes: ChangeSet.fromJSON(json.changes) + })) + if (data.version != updates.length) { + received = rebaseUpdates(received, updates.slice(data.version)) + } + for (let update of received) { + updates.push(update) + doc = update.changes.apply(doc) + } + send(socket, requestId, received.map((update: any) => ({ + clientID: update.clientID, + changes: update.changes.toJSON() + }))); + } else if (data.type == "getDocument") { + send(socket, requestId, {version: updates.length, doc: doc.toString()}) + } + }) +}) + +fastify.post("/run", { + schema: { + body: Type.Object({ + code: Type.String(), + language: Type.String(), + }), + }, +}, (req, reply) => { + const { code, language } = req.body; + const runner = getRunner(language); + if (runner === null) { + return reply.status(422).send({ error: "Invalid language" }); + } + const jobId = generateId(); + const buffer = allocateBuffer(jobId, code, runner); + reply.raw.writeHead(200, { + "Content-Type": "text/event-stream", + Connection: "keep-alive", + "Cache-Control": "no-cache", + "Access-Control-Allow-Origin": process.env.ALLOW_ORIGIN || "*", + }); + reply.raw.on("close", () => { + delete clients[jobId]; + }); + sender.send(buffer).then(() => { + reply.raw.write("event: connected\n"); + reply.raw.write(`data: ${jobId}\n`); + reply.raw.write("id: 0\n\n"); + }); + clients[jobId] = reply; }); - -/* Database */ /* Création du répertoire de la base de données s'il n'existe pas */ db.createDbDirectory();