Skip to content
This repository has been archived by the owner on Sep 20, 2020. It is now read-only.

Commit

Permalink
Fix update plugin schema (#60)
Browse files Browse the repository at this point in the history
* IRAD-1040:Update effective schema in collab server

* Fix: avoid error in update schema method in non collaborative mode.

* Update Collab server with the effective schema.

Co-authored-by: seybi.ea <[email protected]>
Co-authored-by: Ashfaq Shamsudeen <[email protected]>
  • Loading branch information
3 people authored Sep 3, 2020
1 parent cf71973 commit 44c4de0
Show file tree
Hide file tree
Showing 10 changed files with 161 additions and 43 deletions.
5 changes: 5 additions & 0 deletions flow-typed/flatted.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// @flow

declare module 'flatted' {
declare module.exports: any;
}
2 changes: 1 addition & 1 deletion licit/client/index.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

34 changes: 29 additions & 5 deletions licit/server/collab/instance.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@

const { readFileSync, writeFile } = require("fs")

import EditorSchema from "../../../src/EditorSchema"
// [FS] IRAD-1040 2020-09-02
import { Schema } from 'prosemirror-model';

let _editorSchema: Schema = null;

const MAX_STEP_HISTORY = 10000

Expand All @@ -19,10 +22,11 @@ export class Instance {
waiting = [];
collecting: any;
// end
constructor(id: any, doc: any) {
constructor(id: any, doc: any, effectiveSchema: Schema) {
this.id = id
this.doc = doc || EditorSchema.node("doc", null, [EditorSchema.node("paragraph", null, [
EditorSchema.text(" ")
// [FS] IRAD-1040 2020-09-02
this.doc = doc || _editorSchema.node("doc", null, [_editorSchema.node("paragraph", null, [
_editorSchema.text(" ")
])])
// The version number of the document instance.
this.version = 0
Expand Down Expand Up @@ -135,14 +139,16 @@ if (process.argv.indexOf("--fresh") == -1) {

if (json) {
for (let prop in json)
newInstance(prop, EditorSchema.nodeFromJSON(json[prop].doc))
// [FS] IRAD-1040 2020-09-02
newInstance(prop, _editorSchema.nodeFromJSON(json[prop].doc))
}

let saveTimeout = null, saveEvery = 1e4
function scheduleSave() {
if (saveTimeout != null) return
saveTimeout = setTimeout(doSave, saveEvery)
}

function doSave() {
saveTimeout = null
let out = {}
Expand All @@ -151,6 +157,24 @@ function doSave() {
writeFile(saveFile, JSON.stringify(out), () => { null })
}

// [FS] IRAD-1040 2020-09-02
function updateDocs() {
for (var prop in instances) {
instances[prop].doc = _editorSchema.nodeFromJSON(instances[prop].doc.toJSON());
}
}

export function setEditorSchema(effectiveSchema: Schema) {
_editorSchema = effectiveSchema;
updateDocs();
}

export function initEditorSchema(effectiveSchema: Schema) {
if(null == _editorSchema) {
_editorSchema = effectiveSchema;
}
}

export function getInstance(id: any, ip: any) {
let inst = instances[id] || newInstance(id)
if (ip) inst.registerUser(ip)
Expand Down
79 changes: 66 additions & 13 deletions licit/server/collab/server.js
Original file line number Diff line number Diff line change
@@ -1,28 +1,38 @@
// @flow

import { Step } from "prosemirror-transform"
import { Schema } from 'prosemirror-model'

import Router from "./route"
import EditorSchema from "../../../src/EditorSchema"
import { getInstance, instanceInfo } from "./instance"
import { getInstance, instanceInfo, setEditorSchema, initEditorSchema } from "./instance"
// [FS] IRAD-899 2020-03-13
// This is for Capcomode document attribute. Shared Step, so that capcomode can be dealt collaboratively.
import SetDocAttrStep from "../../../src/SetDocAttrStep";
// [FS] IRAD-1040 2020-09-02
import * as Flatted from 'flatted';

const router = new Router();

// [FS] IRAD-1040 2020-09-02
let effectiveSchema = EditorSchema;
let lastUpdatedSchema = null;

function handleCollabRequest(req: any, resp: any) {
// [FS] IRAD-1040 2020-09-02
initEditorSchema(effectiveSchema);
const headers = {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': ';POST, GET, PUT, DELETE, OPTIONS',
'Access-Control-Allow-Credential': false,
'Access-Control-Max-Age': 86400, // 24hrs
'Access-Control-Allow-Headers':
'X-Requested-With, X-HTTP-Method-Override, Content-Type, Accept'
};
if (!router.resolve(req, resp)) {
const method = req.method.toUpperCase();
if (method === 'OPTIONS') {
const headers = {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': ';POST, GET, PUT, DELETE, OPTIONS',
'Access-Control-Allow-Credential': false,
'Access-Control-Max-Age': 86400, // 24hrs
'Access-Control-Allow-Headers':
'X-Requested-With, X-HTTP-Method-Override, Content-Type, Accept'
};

resp.writeHead(200, headers);
resp.end();
} else {
Expand Down Expand Up @@ -84,9 +94,23 @@ function readStreamAsJSON(stream, callback) {
stream.on("error", e => callback(e))
}

// : (stream.Readable, Function)
// Invoke a callback with a stream's data.
function readStreamAsFlatted(stream, callback) {
let data = ""
stream.on("data", chunk => data += chunk)
stream.on("end", () => {
let result, error
try { result = Flatted.parse(data) }
catch (e) { error = e }
callback(error, result)
})
stream.on("error", e => callback(e))
}

// : (string, Array, Function)
// Register a server route.
function handle(method, url, f) {
function handle(method, url, f, readFlatted = false) {
router.add(method, url, (req, resp, ...args) => {
function finish() {
let output
Expand All @@ -99,11 +123,13 @@ function handle(method, url, f) {
if (output) output.resp(resp)
}

if (method == "PUT" || method == "POST")
readStreamAsJSON(req, (err, val) => {
if (method == "PUT" || method == "POST") {
const readMethod = readFlatted ? readStreamAsFlatted : readStreamAsJSON;
readMethod(req, (err, val) => {
if (err) new Output(500, err.toString()).resp(resp)
else { args.unshift(val); finish() }
})
}
else
finish()
})
Expand Down Expand Up @@ -209,10 +235,37 @@ function reqIP(request) {
// The event submission endpoint, which a client sends an event to.
handle("POST", ["docs", null, "events"], (data, id, req) => {
let version = nonNegInteger(data.version)
let steps = data.steps.map(s => Step.fromJSON(EditorSchema, s))
let steps = data.steps.map(s => Step.fromJSON(effectiveSchema, s))
let result = getInstance(id, reqIP(req)).addEvents(version, steps, data.clientID)
if (!result)
return new Output(409, "Version not current")
else
return Output.json(result)
})

// [FS] IRAD-1040 2020-09-02
// set the effective schema from client to work the plugins collaboratively
handle("POST", ["docs", null, "schema"], (data, id, req) => {
const updatedSchema = Flatted.stringify(data);
// Do a string comparison to see if they are same or not.
// if same, don't update
if(lastUpdatedSchema !== updatedSchema) {
lastUpdatedSchema = updatedSchema;
const spec = data['spec'];
updateSpec(spec, 'nodes');
updateSpec(spec, 'marks');
effectiveSchema = new Schema({ nodes: effectiveSchema.spec.nodes, marks: effectiveSchema.spec.marks });
setEditorSchema(effectiveSchema);
}
return Output.json({ result: 'success' });
}, true)

function updateSpec(spec, attrName) {
// clear current array
effectiveSchema.spec[attrName].content.splice(0, effectiveSchema.spec[attrName].content.length);
const collection = spec[attrName]['content'];
// update current array with the latest info
for (var i = 0; i < collection.length; i += 2) {
effectiveSchema.spec[attrName] = effectiveSchema.spec[attrName].update(collection[i], collection[i+1]);
}
}
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@
"cors": "^2.8.5",
"exports-loader": "1.1.0",
"express": "^4.17.1",
"flatted": "^3.0.4",
"flow-typed": "^3.2.0",
"formidable": "^1.2.2",
"invariant": "2.2.4",
Expand Down
9 changes: 8 additions & 1 deletion src/client/CollabConnector.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import { Transform } from 'prosemirror-transform';
import { EditorState } from 'prosemirror-state';
import { Schema } from 'prosemirror-model';
import SimpleConnector from './SimpleConnector';
import type { SetStateCall } from './SimpleConnector';
import EditorConnection from './EditorConnection';
Expand Down Expand Up @@ -29,7 +30,7 @@ class CollabConnector extends SimpleConnector {
this._docID = docID;

// [FS][11-MAR-2020]
// Modified the scripts to ensure not to always replace 3001 with 3002 to run both servers together,
// Modified the scripts to ensure not to always replace 3001 with 3002 to run both servers together,
// instead used running hostname and configured port.
const url = window.location.protocol + '\/\/' +
window.location.hostname + ':3002/docs/' +
Expand Down Expand Up @@ -57,6 +58,12 @@ class CollabConnector extends SimpleConnector {
this._connection.dispatch({ type: 'transaction', transaction });
});
};

// FS IRAD-1040 2020-09-02
// Send the modified schema to server
updateSchema = (schema: Schema) => {
this._connection.updateSchema(schema);
};
}

export default CollabConnector;
55 changes: 35 additions & 20 deletions src/client/EditorConnection.js
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
// @flow

import nullthrows from 'nullthrows';
import {
collab,
getVersion,
receiveTransaction,
sendableSteps,
} from 'prosemirror-collab';
import {EditorState} from 'prosemirror-state';
import {Step} from 'prosemirror-transform';
import {EditorView} from 'prosemirror-view';

import { EditorState } from 'prosemirror-state';
import { Step } from 'prosemirror-transform';
import { EditorView } from 'prosemirror-view';
import EditorPlugins from '../EditorPlugins';
import EditorSchema from '../EditorSchema';
import uuid from '../uuid';
import {GET, POST} from './http';
import throttle from './throttle';
import { GET, POST } from './http';
// [FS] IRAD-1040 2020-09-02
import { Schema } from 'prosemirror-model';
import { stringify } from 'flatted';

function badVersion(err: Object) {
return err.status == 400 && /invalid version/i.test(String(err));
Expand Down Expand Up @@ -168,25 +168,25 @@ class EditorConnection {
if (err.status == 410 || badVersion(err)) {
// Too far behind. Revert to server state
this.report.failure(err);
this.dispatch({type: 'restart'});
this.dispatch({ type: 'restart' });
} else if (err) {
this.dispatch({type: 'recover', error: err});
this.dispatch({ type: 'recover', error: err });
}
}
);
}

sendable(editState: EditorState): ?{steps: Array<Step>} {
sendable(editState: EditorState): ?{ steps: Array<Step> } {
const steps = sendableSteps(editState);
if (steps) {
return {steps};
return { steps };
}
return null;
}

// Send the given steps to the server
send(editState: EditorState, sendable: Object) {
const {steps} = sendable;
const { steps } = sendable;
const json = JSON.stringify({
version: getVersion(editState),
steps: steps ? steps.steps.map(s => s.toJSON()) : [],
Expand All @@ -198,10 +198,10 @@ class EditorConnection {
this.backOff = 0;
const tr = steps
? receiveTransaction(
this.state.edit,
steps.steps,
repeat(steps.clientID, steps.steps.length)
)
this.state.edit,
steps.steps,
repeat(steps.clientID, steps.steps.length)
)
: this.state.edit.tr;

this.dispatch({
Expand All @@ -215,17 +215,32 @@ class EditorConnection {
// The client's document conflicts with the server's version.
// Poll for changes and then try again.
this.backOff = 0;
this.dispatch({type: 'poll'});
this.dispatch({ type: 'poll' });
} else if (badVersion(err)) {
this.report.failure(err);
this.dispatch({type: 'restart'});
this.dispatch({ type: 'restart' });
} else {
this.dispatch({type: 'recover', error: err});
this.dispatch({ type: 'recover', error: err });
}
}
);
}

// [FS] IRAD-1040 2020-09-02
// Send the modified schema to server
updateSchema(schema: Schema) {
// to avoid cyclic reference error, use flatted string.
const schemaFlatted = stringify(schema);
this.run(POST(this.url + '/schema/', schemaFlatted, 'text/plain')).then(
data => {
console.log('schema updated');
},
err => {
this.report.failure(err);
}
);
}

// Try to recover from an error
recover(err: Error): void {
const newBackOff = this.backOff ? Math.min(this.backOff * 2, 6e4) : 200;
Expand All @@ -235,7 +250,7 @@ class EditorConnection {
this.backOff = newBackOff;
setTimeout(() => {
if (this.state.comm == 'recover') {
this.dispatch({type: 'poll'});
this.dispatch({ type: 'poll' });
}
}, this.backOff);
}
Expand Down
5 changes: 5 additions & 0 deletions src/client/Licit.js
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,11 @@ class Licit extends React.Component<any, any> {
embedded,
runtime
};
// FS IRAD-1040 2020-26-08
// Get the modified schema from editorstate and send it to collab server
if (this._connector.updateSchema) {
this._connector.updateSchema(this.state.editorState.schema);
}
}

setContent = (content: any = {}): void => {
Expand Down
Loading

0 comments on commit 44c4de0

Please sign in to comment.