Collab server implementation, "window is undefined" on server

Hey everyone,

I’m trying to put together a basic collab server. I’ve been looking over the collab guide and trying to apply the same concepts to data over the wire to the server.

In the example, an authority is created and has a doc passed into its constructor. I can’t wrap my head around how to apply that same idea when communicating with a server. My idea was to create a new ProseMirror() in the authority on the server, and apply steps as they come in from clients to the server’s ProseMirror instance’s doc to keep the doc’s state in memory. It looks like you can’t do that because the ProseMirror class does something with the window object, which causes a throw on the server since there’s no window in the node environment.

So what’s the proper way to pass the doc into the authority when the authority lives on the server so we can apply steps to it with step.apply(docInAuthrityOnServer)?

You don’t need a whole editor on the server, and as you found out, you can’t create one, since it only works with a DOM. You can create a document, and apply changes to that, if you want. Or just use a ‘dumb’ authority implementation as in the example, and accumulate and broadcast steps without doing anything with them.

Okay that makes sense. I’d like to keep a doc in memory on the server so that I can periodically store a snapshot of its state to db. I’m trying to just create a new empty Doc on the authority, but I’m not doing it correctly, could you point me in the right direction?

I’ve got a class Authority that looks like this

import { Step } from 'prosemirror/dist/transform';
import { defaultSchema, Doc } from 'prosemirror/dist/model/defaultschema';

class Authority {
  constructor(socket) {
    this.doc = new Doc();
    this.steps = [];
    this.stepClientIds = [];
    this.socket = socket;
  
    // listen for clients to submit steps over socket
    this.socket.on('clientSubmitSteps', this.receieveSteps.bind(this));
  }
  
 receieveSteps({ clientId, steps, version }) {
    // convert steps from js object into Step instance
    const stepObjects = steps.map((step) => {
      return Steps.fromJSON(defaultSchema, step);
    });

    // apply steps
    stepObjects.forEach((step) => {
      this.doc = step.apply(this.doc).doc;
      this.steps.push(step);
      this.stepClientIds.push(clientId);
    });
    
    // notify listening clients new steps arrived
    this.socket.emit('authorityNewSteps');
  
  }
}

When a step comes in from a client, at the line this.doc = step.apply(this.doc).doc it throws

TypeError: Object [object Object] has no method 'replace'
at Function.fromReplace(prosemirror/dist/transform/step.js:160:34
at ReplaceStep.apply(prosemirror/dist/transform/replace_step.js:52:31)

new Doc does not create a document, but rather an instance of the document node type (there’s different classes for node types, which are like type tags, and actual nodes). You’ll want defaultSchema.node("doc", null, [defaultSchema.node("paragraph")]) to create a minimal valid document.

awesome, thank you! I was able to get a collab demo working in only a few days, you’re doing great work with ProseMirror :+1: