Refactored the Socket connection and callbacks to work properly

This commit is contained in:
inaseem 2021-11-02 15:34:10 +05:30
parent 4068822489
commit dbf6763a99
5 changed files with 233 additions and 185 deletions

View File

@ -9,6 +9,7 @@ import React from "react";
import { createMessageConnection } from "vscode-jsonrpc";
import RijuMessageReader from "../services/RijuMessageReader";
import RijuMessageWriter from "../services/RijuMessageWriter";
import { SocketManager } from "../services/WS";
import { EventEmitter } from "../utils/EventEmitter";
let clientDisposable = null;
@ -20,11 +21,11 @@ const RijuEditor = (props) => {
const monacoRef = React.useRef();
React.useEffect(() => {
EventEmitter.subscribe("lspStarted", (data) => {
const { message, socket } = data;
initLSP(socket, message, monacoRef.current, editorRef.current);
const token1 = EventEmitter.subscribe("lspStarted", (data) => {
const { message } = data;
initLSP(message, monacoRef.current, editorRef.current);
});
EventEmitter.subscribe("lspStopped", () => {
const token2 = EventEmitter.subscribe("lspStopped", () => {
if (clientDisposable) {
clientDisposable.dispose();
clientDisposable = null;
@ -34,9 +35,11 @@ const RijuEditor = (props) => {
servicesDisposable = null;
}
});
() => EventEmitter.unsubcribe(token1, token2);
}, []);
const initLSP = (socket, message, monaco, editor) => {
const initLSP = (message, monaco, editor) => {
const socket = SocketManager.socket;
const services = MonacoServices.create(editor, {
rootUri: `file://${message.root}`,
});

View File

@ -19,11 +19,11 @@ function RijuTerminal() {
term.write("Connecting to server...");
window.addEventListener("resize", () => fitAddon.fit());
EventEmitter.subscribe("resize", () => {
const token1 = EventEmitter.subscribe("resize", () => {
const event = new Event("resize");
window.dispatchEvent(event);
});
EventEmitter.subscribe("terminal", (payload) => {
const token2 = EventEmitter.subscribe("terminal", (payload) => {
if (!payload) return;
const { type, data } = payload;
switch (type) {
@ -41,6 +41,8 @@ function RijuTerminal() {
term.onData((data) => {
EventEmitter.dispatch("send", { event: "terminalInput", input: data });
});
() => EventEmitter.unsubcribe(token1, token2);
}, []);
return (

View File

@ -24,6 +24,7 @@ import React, { useEffect, useRef, useState } from "react";
import Layouts from "../../components/Layouts";
import langs from "../../assets/langs.json";
import { EventEmitter } from "../../utils/EventEmitter";
import { SocketManager } from "../../services/WS";
ansi.rgb = {
green: "#00FD61",
};
@ -42,7 +43,7 @@ const CodeRunner = (props) => {
const router = useRouter();
const { langConfig } = props;
const editorRef = useRef(null);
const [config, setConfig] = useState(langConfig);
const [config] = useState(langConfig);
const [mounted, setMounted] = useState(false);
const [isRunning, setRunning] = useState(false);
const [isFormatting, setFormatting] = useState(false);
@ -56,179 +57,162 @@ const CodeRunner = (props) => {
EventEmitter.dispatch("terminal", { type, data });
}
function connect() {
serviceLogBuffers = {};
serviceLogLines = {};
setStatus("connecting");
const socket = new WebSocket(
// (document.location.protocol === "http:" ? "ws://" : "wss://") +
"wss://" +
"riju.codes" +
`/api/v1/ws?lang=${encodeURIComponent(config.id)}`
);
socket.addEventListener("open", () => {
console.log("Successfully connected to server playground");
setStatus("connected");
});
EventEmitter.subscribe("send", (payload) => {
if (DEBUG) {
console.log("SEND:", payload);
}
if (socket) {
socket.send(JSON.stringify(payload));
}
});
socket.addEventListener("message", async (event) => {
let message;
try {
message = JSON.parse(event.data);
} catch (err) {
console.error("Malformed message from server:", event.data);
const handleWsOpen = (event) => {
setStatus("connected");
};
const handleWsMessage = (event) => {
let message;
try {
message = JSON.parse(event.data);
} catch (err) {
console.error("Malformed message from server:", event.data);
return;
}
if (
DEBUG &&
message &&
message.event !== "lspOutput" &&
message.event !== "serviceLog"
) {
console.log("RECEIVE:", message);
}
if (message && message.event && message.event !== "error") {
// retryDelayMs = initialRetryDelayMs;
}
switch (message && message.event) {
case "terminalClear":
// term.reset();
sendToTerminal("terminalClear");
return;
}
if (
DEBUG &&
message &&
message.event !== "lspOutput" &&
message.event !== "serviceLog"
) {
console.log("RECEIVE:", message);
}
if (message && message.event && message.event !== "error") {
// retryDelayMs = initialRetryDelayMs;
}
switch (message && message.event) {
case "terminalClear":
// term.reset();
sendToTerminal("terminalClear");
return;
case "terminalOutput":
if (typeof message.output !== "string") {
console.error("Unexpected message from server:", message);
return;
}
sendToTerminal("terminalOutput", ansi.white(message.output));
setRunning(false);
return;
case "formattedCode":
setFormatting(false);
if (
typeof message.code !== "string" ||
typeof message.originalCode !== "string"
) {
console.error("Unexpected message from server:", message);
return;
}
if (editorRef.current?.getValue() === message.originalCode) {
editorRef.current?.setValue(message.code);
}
return;
case "lspStopped":
setIsLspRequested(false);
setLspStarted(false);
EventEmitter.dispatch("lspStopped");
break;
case "lspStarted":
setLspStarted(true);
setIsLspRequested(false);
if (typeof message.root !== "string") {
console.error("Unexpected message from server:", message);
return;
}
EventEmitter.dispatch("lspStarted", { message, socket });
return;
case "lspOutput":
// Should be handled by RijuMessageReader
return;
case "serviceLog":
if (
typeof message.service !== "string" ||
typeof message.output !== "string"
) {
console.error("Unexpected message from server:", message);
return;
}
let buffer = serviceLogBuffers[message.service] || "";
let lines = serviceLogLines[message.service] || [];
buffer += message.output;
while (buffer.includes("\n")) {
const idx = buffer.indexOf("\n");
const line = buffer.slice(0, idx);
buffer = buffer.slice(idx + 1);
lines.push(line);
if (DEBUG) {
console.log(`${message.service.toUpperCase()} || ${line}`);
}
}
serviceLogBuffers[message.service] = buffer;
serviceLogLines[message.service] = lines;
return;
case "serviceFailed":
if (
typeof message.service !== "string" ||
typeof message.error !== "string"
) {
console.error("Unexpected message from server:", message);
return;
}
switch (message.service) {
case "formatter":
setFormatting(false);
// showError({
// message: "Could not prettify code!",
// data: serviceLogLines["formatter"].join("\n"),
// });
break;
case "lsp":
setLspStarted(false);
setIsLspRequested(false);
EventEmitter.dispatch("lspStopped");
break;
case "terminal":
sendToTerminal(
"terminalOutput",
ansi.red(`\r\n[${message.error}]`)
);
break;
}
return;
case "langConfig":
console.log("Lang Config", message);
// We could use this message instead of hardcoding the
// language config into the HTML page returned from the
// server, but for now we just ignore it.
return;
default:
case "terminalOutput":
if (typeof message.output !== "string") {
console.error("Unexpected message from server:", message);
}
});
socket.addEventListener("close", (event) => {
if (event.wasClean) {
console.log("Connection closed cleanly");
} else {
console.error("Connection died");
}
EventEmitter.dispatch("lspStopped");
setRunning(false);
setLspStarted(false);
setIsLspRequested(false);
setStatus("idle");
});
return;
}
sendToTerminal("terminalOutput", ansi.white(message.output));
setRunning(false);
return;
case "formattedCode":
setFormatting(false);
if (
typeof message.code !== "string" ||
typeof message.originalCode !== "string"
) {
console.error("Unexpected message from server:", message);
return;
}
if (editorRef.current?.getValue() === message.originalCode) {
editorRef.current?.setValue(message.code);
}
return;
case "lspStopped":
setIsLspRequested(false);
setLspStarted(false);
EventEmitter.dispatch("lspStopped");
break;
case "lspStarted":
setLspStarted(true);
setIsLspRequested(false);
if (typeof message.root !== "string") {
console.error("Unexpected message from server:", message);
return;
}
return socket;
}
EventEmitter.dispatch("lspStarted", { message });
return;
case "lspOutput":
// Should be handled by RijuMessageReader
return;
case "serviceLog":
if (
typeof message.service !== "string" ||
typeof message.output !== "string"
) {
console.error("Unexpected message from server:", message);
return;
}
let buffer = serviceLogBuffers[message.service] || "";
let lines = serviceLogLines[message.service] || [];
buffer += message.output;
while (buffer.includes("\n")) {
const idx = buffer.indexOf("\n");
const line = buffer.slice(0, idx);
buffer = buffer.slice(idx + 1);
lines.push(line);
if (DEBUG) {
console.log(`${message.service.toUpperCase()} || ${line}`);
}
}
serviceLogBuffers[message.service] = buffer;
serviceLogLines[message.service] = lines;
return;
case "serviceFailed":
if (
typeof message.service !== "string" ||
typeof message.error !== "string"
) {
console.error("Unexpected message from server:", message);
return;
}
switch (message.service) {
case "formatter":
setFormatting(false);
// showError({
// message: "Could not prettify code!",
// data: serviceLogLines["formatter"].join("\n"),
// });
break;
case "lsp":
setLspStarted(false);
setIsLspRequested(false);
EventEmitter.dispatch("lspStopped");
break;
case "terminal":
sendToTerminal(
"terminalOutput",
ansi.red(`\r\n[${message.error}]`)
);
break;
}
return;
case "langConfig":
console.log("Lang Config", message);
// We could use this message instead of hardcoding the
// language config into the HTML page returned from the
// server, but for now we just ignore it.
return;
default:
console.error("Unexpected message from server:", message);
}
};
const handleWsClose = (event) => {
if (event.wasClean) {
console.log("Connection closed cleanly");
} else {
console.error("Connection died");
}
EventEmitter.dispatch("lspStopped");
setRunning(false);
setLspStarted(false);
setIsLspRequested(false);
setStatus("idle");
};
useEffect(() => {
if (!config || !mounted) return;
const socket = connect();
return () => socket && socket.close();
serviceLogBuffers = {};
serviceLogLines = {};
setStatus("connecting");
SocketManager.connect(config, handleWsOpen, handleWsMessage, handleWsClose);
return () => SocketManager.disconnect();
}, [config, mounted]);
function showValue() {
setRunning(true);
EventEmitter.dispatch("send", {
SocketManager.send({
event: "runCode",
code: editorRef.current.getValue(),
});
@ -238,7 +222,7 @@ const CodeRunner = (props) => {
setFormatting(true);
serviceLogBuffers["formatter"] = "";
serviceLogLines["formatter"] = [];
EventEmitter.dispatch("send", {
SocketManager.send({
event: "formatCode",
code: editorRef.current.getValue(),
});
@ -261,21 +245,28 @@ const CodeRunner = (props) => {
const handleLspClick = () => {
setIsLspRequested(true);
if (isLspStarted) {
EventEmitter.dispatch("send", {
SocketManager.send({
event: "lspStop",
});
} else {
EventEmitter.dispatch("send", {
SocketManager.send({
event: "lspStart",
});
}
};
const handleChange = () => {
if (status != "idle") {
if (SocketManager.isConnected) {
return;
} else {
connect();
if (!SocketManager.isConnected) {
SocketManager.connect(
config,
handleWsOpen,
handleWsMessage,
handleWsClose
);
}
}
};
@ -285,7 +276,6 @@ const CodeRunner = (props) => {
const es = document.querySelectorAll(".split .panel");
for (const e of es) {
e.removeAttribute("style");
e.removeAttribute("style");
}
setSplitType(value);
};

46
frontend/services/WS.js Normal file
View File

@ -0,0 +1,46 @@
const isFn = (callback) => {
return callback && typeof callback == "function";
};
const createSocket = (url) => {
const socket = new WebSocket(url);
return socket;
};
export const SocketManager = {
socket: null,
isConnected: false,
connect: function (config, onOpen, onMessage, onClose) {
let url =
"wss://" +
"riju.codes" +
`/api/v1/ws?lang=${encodeURIComponent(config.id)}`;
this.socket = createSocket(url);
this.socket.addEventListener("open", () => {
console.log("Successfully connected to server playground");
this.isConnected = true;
if (isFn(onOpen)) onOpen();
});
this.socket.addEventListener("message", async (event) => {
if (isFn(onMessage)) onMessage(event);
});
this.socket.addEventListener("close", (event) => {
if (isFn(onClose)) onClose(event);
this.isConnected = false;
});
},
disconnect: function () {
if (this.socket) {
if (this.socket.readyState == WebSocket.OPEN) {
this.socket.close();
}
}
},
send: function (data) {
if (this.socket) {
if (this.socket.readyState == WebSocket.OPEN) {
this.socket.send(JSON.stringify(data));
}
}
},
};

View File

@ -1,18 +1,25 @@
export const EventEmitter = {
events: {},
lastUid: -1,
dispatch: function (event, data) {
if (!this.events[event]) return;
this.events[event].forEach((callback) => callback(data));
for (let token of Object.keys(this.events[event])) {
const callback = this.events[event][token];
if (callback && typeof callback == "function") callback(data);
}
},
subscribe: function (event, callback) {
if (!this.events[event]) this.events[event] = [];
this.events[event].push(callback);
if (!callback) return;
if (!this.events[event]) this.events[event] = {};
const token = "uID_" + String(++this.lastUid);
this.events[event][token] = callback;
},
isSubscribed: function (event) {
if (!Array.isArray(this.events[event])) return false;
else {
if (this.events[event].length == 0) return false;
else return true;
unsubcribe: function (...tokens) {
for (let k of Object.keys(events)) {
let toks = Object.keys(events[k]);
for (let tok of toks) {
if (tokens.includes(tok)) delete events[k][tok];
}
}
},
};