feat: add ability to reset docSet for client on reconnect (resetOnReconnect)

This commit is contained in:
Eric Maciel 2021-03-04 15:37:10 -05:00
parent 581d68b142
commit d0a7930484
4 changed files with 49 additions and 38 deletions

View File

@ -254,8 +254,6 @@ export default class AutomergeCollaboration {
opCount: collabActions.length opCount: collabActions.length
} }
console.log(JSON.stringify(data.payload, null, 2))
this.onTrace(metaData, () => { this.onTrace(metaData, () => {
switch (data.type) { switch (data.type) {
case 'operation': case 'operation':

View File

@ -4,9 +4,16 @@ import fs from 'fs'
import isEqual from 'lodash/isEqual' import isEqual from 'lodash/isEqual'
import { createEditor, Editor, Element, Node, Transforms } from 'slate' import { createEditor, Editor, Element, Node, Transforms } from 'slate'
import { createDoc, SyncDoc, toJS, toSlateOp } from '@hiveteams/collab-bridge' import { createDoc, SyncDoc, toJS, toSlateOp } from '@hiveteams/collab-bridge'
import AutomergeCollaboration from '@hiveteams/collab-backend/lib/AutomergeCollaboration' import AutomergeCollaboration, {
IAutomergeMetaData
} from '@hiveteams/collab-backend/lib/AutomergeCollaboration'
import withIOCollaboration from './withIOCollaboration' import withIOCollaboration from './withIOCollaboration'
import { AutomergeOptions, SocketIOPluginOptions } from './interfaces' import {
AutomergeEditor,
AutomergeOptions,
SocketIOPluginOptions,
WithSocketIOEditor
} from './interfaces'
import { getTarget } from '@hiveteams/collab-bridge/src/path' import { getTarget } from '@hiveteams/collab-bridge/src/path'
import getActiveConnections from '@hiveteams/collab-backend/src/utils/getActiveConnections' import getActiveConnections from '@hiveteams/collab-backend/src/utils/getActiveConnections'
@ -41,12 +48,8 @@ const server = createServer(function(req, res) {
res.end() res.end()
}) })
const defaultSlateJson = [ const defaultSlateJson = [{ type: 'paragraph', children: [{ text: '' }] }]
{ let operationTraces: IAutomergeMetaData[] = []
type: 'paragraph',
children: [{ text: 'hello world' }, { text: 'goodbye world' }]
}
]
const collabBackend = new AutomergeCollaboration({ const collabBackend = new AutomergeCollaboration({
entry: server, entry: server,
defaultValue: defaultSlateJson, defaultValue: defaultSlateJson,
@ -58,9 +61,7 @@ const collabBackend = new AutomergeCollaboration({
return defaultSlateJson return defaultSlateJson
}, },
onTrace(metaData, computationFn) { onTrace(metaData, computationFn) {
if (metaData.opCount && metaData.opCount > 100) { operationTraces.push(metaData)
}
console.log('metaData', metaData)
computationFn() computationFn()
} }
}) })
@ -72,7 +73,12 @@ describe('automerge editor client tests', () => {
server.listen(5000, () => done()) server.listen(5000, () => done())
}) })
let collabEditors: (Editor & WithSocketIOEditor & AutomergeEditor)[] = []
afterEach(done => { afterEach(done => {
operationTraces = []
collabEditors.forEach(editor => editor.destroy())
collabEditors = []
waitForCondition(() => !collabBackend.backend.getDocument(docId)).then(done) waitForCondition(() => !collabBackend.backend.getDocument(docId)).then(done)
}) })
@ -98,6 +104,7 @@ describe('automerge editor client tests', () => {
}) })
editor.connect() editor.connect()
collabEditors.push(editor)
await promise await promise
return editor return editor
} }
@ -117,8 +124,6 @@ describe('automerge editor client tests', () => {
const serverDoc = toJS(collabBackend.backend.getDocument(docId)) const serverDoc = toJS(collabBackend.backend.getDocument(docId))
return serverDoc.children.length === 2 return serverDoc.children.length === 2
}) })
editor.destroy()
}) })
it('should sync updates across two clients', async () => { it('should sync updates across two clients', async () => {
@ -131,9 +136,6 @@ describe('automerge editor client tests', () => {
const serverDoc = toJS(collabBackend.backend.getDocument(docId)) const serverDoc = toJS(collabBackend.backend.getDocument(docId))
return serverDoc.children.length === 2 && editor2.children.length === 2 return serverDoc.children.length === 2 && editor2.children.length === 2
}) })
editor1.destroy()
editor2.destroy()
}) })
it('should sync offline changes on reconnect', async () => { it('should sync offline changes on reconnect', async () => {
@ -159,9 +161,6 @@ describe('automerge editor client tests', () => {
}) })
expect(Node.string(editor2.children[2])).toEqual('offline') expect(Node.string(editor2.children[2])).toEqual('offline')
editor1.destroy()
editor2.destroy()
}) })
it('should work with concurrent edits', async () => { it('should work with concurrent edits', async () => {
@ -182,9 +181,6 @@ describe('automerge editor client tests', () => {
}) })
expect(isEqual(editor1.children, editor2.children)).toBeTruthy() expect(isEqual(editor1.children, editor2.children)).toBeTruthy()
editor1.destroy()
editor2.destroy()
}) })
it('should work with concurrent insert text operations', async () => { it('should work with concurrent insert text operations', async () => {
@ -208,9 +204,6 @@ describe('automerge editor client tests', () => {
}) })
expect(isEqual(editor1.children, editor2.children)).toBeTruthy() expect(isEqual(editor1.children, editor2.children)).toBeTruthy()
editor1.destroy()
editor2.destroy()
}) })
it('should not throw deep nested tree error', () => { it('should not throw deep nested tree error', () => {
@ -248,8 +241,6 @@ describe('automerge editor client tests', () => {
expect(editor.children.length).toEqual(2) expect(editor.children.length).toEqual(2)
expect(Node.string(editor.children[0])).toEqual('new') expect(Node.string(editor.children[0])).toEqual('new')
expect(Node.string(editor.children[1])).toEqual('nodes') expect(Node.string(editor.children[1])).toEqual('nodes')
editor.destroy()
}) })
it('set node for children with missing value should not throw error', () => { it('set node for children with missing value should not throw error', () => {
@ -283,8 +274,8 @@ describe('automerge editor client tests', () => {
expect(target).toEqual(null) expect(target).toEqual(null)
}) })
it.only('should work with concurrent insert text operations', async () => { it('should reconnect with no opCount', async () => {
const editor1 = await createCollabEditor() const editor1 = await createCollabEditor({ resetOnReconnect: true })
console.log('----\neditor1 disconnect\n-----') console.log('----\neditor1 disconnect\n-----')
await waitForCondition(() => { await waitForCondition(() => {
return getActiveConnections(collabBackend.backend, docId) === 1 return getActiveConnections(collabBackend.backend, docId) === 1
@ -303,14 +294,17 @@ describe('automerge editor client tests', () => {
() => getActiveConnections(collabBackend.backend, docId) === 1 () => getActiveConnections(collabBackend.backend, docId) === 1
) )
// Wait for a few seconds to allow the client and server to synchronize their
// document states
await new Promise(res => setTimeout(res, 3000)) await new Promise(res => setTimeout(res, 3000))
console.log('destroying last editor') // Expect that reconnecting with resetOnReconnect option set to true
editor1.destroy() // does not result in any operations being sent from the client to the server
expect(
await waitForCondition(() => { operationTraces.some(
return getActiveConnections(collabBackend.backend, docId) === 0 trace => trace.opCount !== undefined && trace.opCount > 0
}) )
).toBeFalsy
}) })
afterAll(() => { afterAll(() => {

View File

@ -38,6 +38,7 @@ export interface SocketIOPluginOptions {
onConnect?: () => void onConnect?: () => void
onDisconnect?: () => void onDisconnect?: () => void
onError?: (msg: string | Error, data: any) => void onError?: (msg: string | Error, data: any) => void
resetOnReconnect?: boolean
} }
export interface WithSocketIOEditor { export interface WithSocketIOEditor {

View File

@ -17,7 +17,14 @@ const withSocketIO = <T extends AutomergeEditor>(
slateEditor: T, slateEditor: T,
options: SocketIOPluginOptions & AutomergeOptions options: SocketIOPluginOptions & AutomergeOptions
) => { ) => {
const { onConnect, onDisconnect, connectOpts, url } = options const {
onConnect,
onDisconnect,
connectOpts,
url,
docId,
resetOnReconnect
} = options
const editor = slateEditor as T & WithSocketIOEditor & AutomergeEditor const editor = slateEditor as T & WithSocketIOEditor & AutomergeEditor
let socket: SocketIOClient.Socket let socket: SocketIOClient.Socket
@ -31,6 +38,17 @@ const withSocketIO = <T extends AutomergeEditor>(
// On socket io connect, open a new automerge connection // On socket io connect, open a new automerge connection
socket.on('connect', () => { socket.on('connect', () => {
editor.clientId = socket.id editor.clientId = socket.id
// If the resetOnReconnect option is true we should close our connection
// and remove our document from the docSet if the user has already received
// a document from our collab server
if (resetOnReconnect && editor.docSet.getDoc(docId)) {
if (editor.connection) {
editor.connection.close()
}
editor.docSet.removeDoc(docId)
}
editor.openConnection() editor.openConnection()
onConnect && onConnect() onConnect && onConnect()
}) })