Trouble Running Y-websocket-server with react

Hi @dmonad, thanks for the yjs library. I’m trying to build a collaborative editor with yjs-websocket (codemirror binding) and create-react-app with node.js in the back-end. Whenever I try to run, the sync stops after sometime. Please have a look:
ezgif.com-gif-maker (7)

The code is quite simple

This is my front-end

import React, { useEffect, useRef, useState } from "react";
import * as Y from "yjs";
import { WebsocketProvider } from "y-websocket";
import { CodemirrorBinding } from "y-codemirror";
import { Controlled as CodeMirror } from "react-codemirror2";
import { useParams } from "react-router-dom";


const CodeMirrorEditor = ({ userName }) => {
  const [codeMirrorText, setCodeMirrorText] = useState("");
  const codeMirrorRef = useRef();
  const { roomId } = useParams();
  const handleChange = (value) => {
    setCodeMirrorText(value);
  };
  useEffect(() => {
    if (!codeMirrorRef.current) return;
    const ydoc = new Y.Doc({
      meta: {
        cellId: roomId,
      },
    });
    const wsProvider = new WebsocketProvider(
      "ws://localhost:1234",
      roomId,
      ydoc
    );
    const yText = ydoc.getText(`codemirror`);
    wsProvider.awareness.setLocalStateField("user", {
      name: userName,
      color: "#ffaabb",
    });
    const _codemirrorBinding = new CodemirrorBinding(
      yText,
      codeMirrorRef.current,
      wsProvider.awareness
    );
    return () => {
      _codemirrorBinding.destroy();
      wsProvider.destroy();
    };
  }, [roomId, userName]);

  return (
        <CodeMirror
          editorDidMount={(editor) => {
            codeMirrorRef.current = editor;
          }}
          value={codeMirrorText}
          onBeforeChange={(_editor, _data, value) => {
            handleChange(value);
          }}
        />
  );
};

export default CodeMirrorEditor;

Backend:

import pkg from "ws";
import { setupWSConnection, setPersistence } from "y-websocket/bin/utils.js";
import * as Y from "yjs";
import express from "express";

const { Server: WsServer } = pkg;

const app = express();
app.use(express.json());

const notes = {
  1: `# Welcome to Live Collab`,
};

const port = process.env.PORT || 1234;
const wss = new WsServer({ noServer: true });

setPersistence({
  bindState: async (id, doc) => {
    const foundNote = notes.hasOwnProperty(id);
    const foundSourceCode = foundNote ? notes[id] : "";
    const yText = doc.getText("codemirror");
    yText.insert(0, foundSourceCode);
    const ecodedState = Y.encodeStateAsUpdate(doc);
    doc.on("update", (update) => {
      Y.applyUpdate(doc, update);
    });
    return Y.applyUpdate(doc, ecodedState);
  },
  writeState: (_identifier, _doc) => {
    delete notes[_identifier];
    return new Promise((resolve) => {
      resolve();
    });
  },
});

wss.on("connection", setupWSConnection);

const server = app.listen(port, () => {
  console.log(`Server listening at http://localhost:${port}`);
});

server.on("upgrade", (request, socket, head) => {
  const handleAuth = (ws) => {
    wss.emit("connection", ws, request);
  };
  wss.handleUpgrade(request, socket, head, handleAuth);
});

app.post("/:id", (req, res) => {
  const { id } = req.params;
  notes[id] = req.body.content;
});

Please have a look when you get time. Is there anything that I’ve done wrong? Please let me know. Thanks

Hi @ram,

I don’t want to take away the fun of debugging this on your own. There is a lot going on in the code you sent me. First, you could start to reproduce the issue with a default y-websocket server. Next, you could start fresh adding one feature at a time. There are some things that I don’t understand (and are likely the result of you copying some code from somewhere else). E.g. the handleChange function overwrites the content of the codemirror editor, although this should only be done by the CodeMirrorBinding. Furthermore, the content of the editor is also overwritten by the value property in the CodeMirror component.

My guess is that there is some weird interaction going on and the CodeMirror component eventually replaces the CodeMirror doc with something different. CodeMirror binding listens to a specific CodeMirror-doc and doesn’t notice when somebody else replaces the document. This is by design to allow many doc instances to be rendered with a single editor (e.g. for tab management).

My recommendation: Start with something simple, gradually improve, and find out how exactly all these components interact with each other (also some of these interactions seem unnecessary).

1 Like