Refactored the Socket connection and callbacks to work properly
This commit is contained in:
parent
4068822489
commit
dbf6763a99
|
@ -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}`,
|
||||
});
|
||||
|
|
|
@ -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 (
|
||||
|
|
|
@ -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);
|
||||
};
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
|
@ -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];
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue