Working websocket structure
This commit is contained in:
@@ -19,6 +19,15 @@ export function UPGRADE(client: WebSocket, server: WebSocketServer) {
|
|||||||
client.once("message", (message) => {
|
client.once("message", (message) => {
|
||||||
runId = message.toString();
|
runId = message.toString();
|
||||||
|
|
||||||
|
const sendText = async () => {
|
||||||
|
const text = await subscriber.get(`liveRunText:${runId}`);
|
||||||
|
client.send(JSON.stringify({ moves: [], text }));
|
||||||
|
};
|
||||||
|
|
||||||
|
sendText();
|
||||||
|
|
||||||
|
client.on("message", sendText);
|
||||||
|
|
||||||
subscriber.subscribe(`liveRunMoves:${runId}`, (err, count) => {
|
subscriber.subscribe(`liveRunMoves:${runId}`, (err, count) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
console.error("Failed to subscribe", err);
|
console.error("Failed to subscribe", err);
|
||||||
@@ -29,8 +38,9 @@ export function UPGRADE(client: WebSocket, server: WebSocketServer) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
subscriber.on("message", (channel, message) => {
|
subscriber.on("message", (channel, message) => {
|
||||||
|
const moves = JSON.parse(message);
|
||||||
if (client.readyState === WebSocket.OPEN) {
|
if (client.readyState === WebSocket.OPEN) {
|
||||||
client.send(message);
|
client.send(JSON.stringify({ moves, text: null }));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ function getLanguageExtension(lang: Language) {
|
|||||||
export default function Editor({ run }: { run: LiveRun | null | undefined }) {
|
export default function Editor({ run }: { run: LiveRun | null | undefined }) {
|
||||||
const [editorView, setEditorView] = useState<EditorView>();
|
const [editorView, setEditorView] = useState<EditorView>();
|
||||||
const scheduledTimeouts = useRef<NodeJS.Timeout[]>([]);
|
const scheduledTimeouts = useRef<NodeJS.Timeout[]>([]);
|
||||||
|
const [text, setText] = useState("");
|
||||||
|
|
||||||
// WebSocket connection
|
// WebSocket connection
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -54,17 +55,30 @@ export default function Editor({ run }: { run: LiveRun | null | undefined }) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
ws.onmessage = (m) => {
|
ws.onmessage = (m) => {
|
||||||
const data: LiveRunMove[] = JSON.parse(m.data);
|
const data: { moves: LiveRunMove[]; text: string | null } = JSON.parse(
|
||||||
|
m.data,
|
||||||
|
);
|
||||||
|
|
||||||
data.forEach((move) => {
|
if (data.text) {
|
||||||
|
setText(data.text);
|
||||||
|
}
|
||||||
|
|
||||||
|
let cancelled = false;
|
||||||
|
|
||||||
|
data.moves.forEach((move) => {
|
||||||
const timeout = setTimeout(() => {
|
const timeout = setTimeout(() => {
|
||||||
if (!editorView) return;
|
if (!editorView || cancelled) return;
|
||||||
|
|
||||||
|
try {
|
||||||
editorView.dispatch({
|
editorView.dispatch({
|
||||||
selection: { anchor: move.cursor },
|
selection: { anchor: move.cursor },
|
||||||
scrollIntoView: true,
|
scrollIntoView: true,
|
||||||
changes: move.changes,
|
changes: move.changes,
|
||||||
});
|
});
|
||||||
|
} catch {
|
||||||
|
cancelled = true;
|
||||||
|
ws.send("getText");
|
||||||
|
}
|
||||||
}, move.latency);
|
}, move.latency);
|
||||||
|
|
||||||
scheduledTimeouts.current.push(timeout);
|
scheduledTimeouts.current.push(timeout);
|
||||||
@@ -78,7 +92,7 @@ export default function Editor({ run }: { run: LiveRun | null | undefined }) {
|
|||||||
<CodeMirror
|
<CodeMirror
|
||||||
readOnly
|
readOnly
|
||||||
className="h-full w-full"
|
className="h-full w-full"
|
||||||
value={``}
|
value={text}
|
||||||
extensions={[
|
extensions={[
|
||||||
getLanguageExtension("javascript"),
|
getLanguageExtension("javascript"),
|
||||||
EditorState.transactionFilter.of((tr) => {
|
EditorState.transactionFilter.of((tr) => {
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import { LiveRunMove } from "@/modules/live-run/types";
|
||||||
|
|
||||||
|
export function applyMoves(text: string, moves: LiveRunMove[]) {
|
||||||
|
let result = text;
|
||||||
|
|
||||||
|
for (const move of moves) {
|
||||||
|
if (!move.changes) continue;
|
||||||
|
|
||||||
|
result =
|
||||||
|
result.slice(0, move.changes.from) +
|
||||||
|
move.changes.insert +
|
||||||
|
result.slice(move.changes.to);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
@@ -5,6 +5,7 @@ import { LiveRun, LiveRunMove } from "./types";
|
|||||||
import { SupabaseClient } from "@supabase/supabase-js";
|
import { SupabaseClient } from "@supabase/supabase-js";
|
||||||
import { redis } from "@/lib/redis";
|
import { redis } from "@/lib/redis";
|
||||||
import Redis from "ioredis";
|
import Redis from "ioredis";
|
||||||
|
import { applyMoves } from "@/lib/move";
|
||||||
|
|
||||||
/** -------------------- Live Runs in Redis -------------------- */
|
/** -------------------- Live Runs in Redis -------------------- */
|
||||||
|
|
||||||
@@ -50,14 +51,25 @@ export async function removeLiveRun(id: string) {
|
|||||||
* Add moves to a live run (ephemeral)
|
* Add moves to a live run (ephemeral)
|
||||||
*/
|
*/
|
||||||
export async function addLiveRunMoves(runId: string, moves: LiveRunMove[]) {
|
export async function addLiveRunMoves(runId: string, moves: LiveRunMove[]) {
|
||||||
const runExists = await redis.exists(`liveRun:${runId}`);
|
const redis = new Redis();
|
||||||
|
|
||||||
|
// Make sure the live run exists
|
||||||
|
const runExists = await redis.exists(`liveRun:${runId}`);
|
||||||
if (!runExists) throw "Invalid live run";
|
if (!runExists) throw "Invalid live run";
|
||||||
|
|
||||||
const publisher = new Redis();
|
// Publish the new moves to subscribers
|
||||||
|
await redis.publish(`liveRunMoves:${runId}`, JSON.stringify(moves));
|
||||||
|
|
||||||
// Send a message
|
// Get the last text (from Redis)
|
||||||
await publisher.publish(`liveRunMoves:${runId}`, JSON.stringify(moves));
|
const lastText = (await redis.get(`liveRunText:${runId}`)) || "";
|
||||||
|
|
||||||
|
// Apply the new moves to get the updated text
|
||||||
|
const updatedText = applyMoves(lastText, moves);
|
||||||
|
|
||||||
|
// Store the updated text back in Redis
|
||||||
|
await redis.set(`liveRunText:${runId}`, updatedText);
|
||||||
|
|
||||||
|
return updatedText;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** -------------------- Submit Run -------------------- */
|
/** -------------------- Submit Run -------------------- */
|
||||||
|
|||||||
Reference in New Issue
Block a user