Skip to content

Commit dee1c69

Browse files
committed
Add yjs on server and frontend implementation
1 parent fd6728a commit dee1c69

File tree

3 files changed

+114
-0
lines changed

3 files changed

+114
-0
lines changed

collab-service/app/server.js

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,13 @@ import http from "http";
33
import index from "./index.js";
44
import { Server } from "socket.io";
55
import { addMessageToChat } from "./model/repository.js";
6+
import ywsUtils from "y-websocket/bin/utils";
7+
import { WebSocketServer } from "ws";
8+
const setupWSConnection = ywsUtils.setupWSConnection;
69

710
const PORT = process.env.PORT || 3002;
811
const server = http.createServer(index);
12+
const docs = ywsUtils.docs;
913

1014
const io = new Server(server, {
1115
cors: {
@@ -16,6 +20,27 @@ const io = new Server(server, {
1620
},
1721
});
1822

23+
const yjsWs = new WebSocketServer({ noServer: true });
24+
yjsWs.on("connection", (conn, req) => {
25+
setupWSConnection(conn, req, {
26+
gc: req.url.slice(1) !== "ws/prosemirror-versions",
27+
});
28+
});
29+
30+
setInterval(() => {
31+
let conns = 0;
32+
docs.forEach((doc) => {
33+
conns += doc.conns.size;
34+
});
35+
const stats = {
36+
conns,
37+
docs: docs.size,
38+
websocket: `ws://localhost:${PORT}`,
39+
http: `http://localhost:${PORT}`,
40+
};
41+
console.log(`${new Date().toISOString()} Stats: ${JSON.stringify(stats)}`);
42+
}, 10000);
43+
1944
io.on("connection", (socket) => {
2045
console.log("User connected to Socket.IO");
2146

@@ -39,6 +64,19 @@ io.on("connection", (socket) => {
3964
});
4065
});
4166

67+
server.on("upgrade", (request, socket, head) => {
68+
const pathname = new URL(request.url, `http://${request.headers.host}`)
69+
.pathname;
70+
71+
if (pathname.startsWith("/yjs")) {
72+
yjsWs.handleUpgrade(request, socket, head, (ws) => {
73+
yjsWs.emit("connection", ws, request);
74+
});
75+
} else {
76+
socket.destroy();
77+
}
78+
});
79+
4280
connectToMongo()
4381
.then(() => {
4482
server.listen(PORT, () => {
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import dynamic from "next/dynamic";
2+
3+
export const MonacoEditor = dynamic(
4+
() => import("@/components/collab/monaco-editor"),
5+
{
6+
ssr: false,
7+
}
8+
);
9+
10+
export default function CollabRoom({
11+
params,
12+
}: {
13+
params: { room_id: string };
14+
}) {
15+
return <MonacoEditor roomId={params.room_id} />;
16+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
"use client";
2+
import * as Y from "yjs";
3+
import { WebsocketProvider } from "y-websocket";
4+
import { MonacoBinding } from "y-monaco";
5+
import { editor as MonacoEditor } from "monaco-types";
6+
7+
import React, { useEffect, useMemo, useState } from "react";
8+
import Editor from "@monaco-editor/react";
9+
10+
export default function MonacoEdit({ roomId }: { roomId: string }) {
11+
const ydoc = useMemo(() => new Y.Doc(), []);
12+
const [editor, setEditor] =
13+
useState<MonacoEditor.IStandaloneCodeEditor | null>(null);
14+
const [provider, setProvider] = useState<WebsocketProvider | null>(null);
15+
const [binding, setBinding] = useState<MonacoBinding | null>(null);
16+
17+
// this effect manages the lifetime of the Yjs document and the provider
18+
useEffect(() => {
19+
const provider = new WebsocketProvider(
20+
"ws://localhost:3002/yjs",
21+
roomId,
22+
ydoc
23+
);
24+
console.log(provider);
25+
setProvider(provider);
26+
return () => {
27+
provider?.destroy();
28+
ydoc.destroy();
29+
};
30+
}, [ydoc, roomId]);
31+
32+
// this effect manages the lifetime of the editor binding
33+
useEffect(() => {
34+
if (provider == null || editor == null) {
35+
console.log(provider, editor);
36+
return;
37+
}
38+
const monacoBinding = new MonacoBinding(
39+
ydoc.getText(),
40+
editor.getModel()!,
41+
new Set([editor]),
42+
provider?.awareness
43+
);
44+
setBinding(monacoBinding);
45+
return () => {
46+
monacoBinding.destroy();
47+
};
48+
}, [ydoc, provider, editor]);
49+
50+
return (
51+
<Editor
52+
height="90vh"
53+
defaultValue="// some comment"
54+
defaultLanguage="javascript"
55+
onMount={(editor) => {
56+
setEditor(editor);
57+
}}
58+
/>
59+
);
60+
}

0 commit comments

Comments
 (0)