diff --git a/packages/backend/src/AutomergeCollaboration.ts b/packages/backend/src/AutomergeCollaboration.ts index a7e4ceb..2fe970e 100644 --- a/packages/backend/src/AutomergeCollaboration.ts +++ b/packages/backend/src/AutomergeCollaboration.ts @@ -254,8 +254,6 @@ export default class AutomergeCollaboration { opCount: collabActions.length } - console.log(JSON.stringify(data.payload, null, 2)) - this.onTrace(metaData, () => { switch (data.type) { case 'operation': diff --git a/packages/client/src/client.spec.ts b/packages/client/src/client.spec.ts index cae3ae1..53d04cf 100644 --- a/packages/client/src/client.spec.ts +++ b/packages/client/src/client.spec.ts @@ -4,9 +4,16 @@ import fs from 'fs' import isEqual from 'lodash/isEqual' import { createEditor, Editor, Element, Node, Transforms } from 'slate' 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 { AutomergeOptions, SocketIOPluginOptions } from './interfaces' +import { + AutomergeEditor, + AutomergeOptions, + SocketIOPluginOptions, + WithSocketIOEditor +} from './interfaces' import { getTarget } from '@hiveteams/collab-bridge/src/path' import getActiveConnections from '@hiveteams/collab-backend/src/utils/getActiveConnections' @@ -41,12 +48,8 @@ const server = createServer(function(req, res) { res.end() }) -const defaultSlateJson = [ - { - type: 'paragraph', - children: [{ text: 'hello world' }, { text: 'goodbye world' }] - } -] +const defaultSlateJson = [{ type: 'paragraph', children: [{ text: '' }] }] +let operationTraces: IAutomergeMetaData[] = [] const collabBackend = new AutomergeCollaboration({ entry: server, defaultValue: defaultSlateJson, @@ -58,9 +61,7 @@ const collabBackend = new AutomergeCollaboration({ return defaultSlateJson }, onTrace(metaData, computationFn) { - if (metaData.opCount && metaData.opCount > 100) { - } - console.log('metaData', metaData) + operationTraces.push(metaData) computationFn() } }) @@ -72,7 +73,12 @@ describe('automerge editor client tests', () => { server.listen(5000, () => done()) }) + let collabEditors: (Editor & WithSocketIOEditor & AutomergeEditor)[] = [] afterEach(done => { + operationTraces = [] + collabEditors.forEach(editor => editor.destroy()) + collabEditors = [] + waitForCondition(() => !collabBackend.backend.getDocument(docId)).then(done) }) @@ -98,6 +104,7 @@ describe('automerge editor client tests', () => { }) editor.connect() + collabEditors.push(editor) await promise return editor } @@ -117,8 +124,6 @@ describe('automerge editor client tests', () => { const serverDoc = toJS(collabBackend.backend.getDocument(docId)) return serverDoc.children.length === 2 }) - - editor.destroy() }) it('should sync updates across two clients', async () => { @@ -131,9 +136,6 @@ describe('automerge editor client tests', () => { const serverDoc = toJS(collabBackend.backend.getDocument(docId)) return serverDoc.children.length === 2 && editor2.children.length === 2 }) - - editor1.destroy() - editor2.destroy() }) 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') - - editor1.destroy() - editor2.destroy() }) it('should work with concurrent edits', async () => { @@ -182,9 +181,6 @@ describe('automerge editor client tests', () => { }) expect(isEqual(editor1.children, editor2.children)).toBeTruthy() - - editor1.destroy() - editor2.destroy() }) 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() - - editor1.destroy() - editor2.destroy() }) it('should not throw deep nested tree error', () => { @@ -248,8 +241,6 @@ describe('automerge editor client tests', () => { expect(editor.children.length).toEqual(2) expect(Node.string(editor.children[0])).toEqual('new') expect(Node.string(editor.children[1])).toEqual('nodes') - - editor.destroy() }) 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) }) - it.only('should work with concurrent insert text operations', async () => { - const editor1 = await createCollabEditor() + it('should reconnect with no opCount', async () => { + const editor1 = await createCollabEditor({ resetOnReconnect: true }) console.log('----\neditor1 disconnect\n-----') await waitForCondition(() => { return getActiveConnections(collabBackend.backend, docId) === 1 @@ -303,14 +294,17 @@ describe('automerge editor client tests', () => { () => 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)) - console.log('destroying last editor') - editor1.destroy() - - await waitForCondition(() => { - return getActiveConnections(collabBackend.backend, docId) === 0 - }) + // Expect that reconnecting with resetOnReconnect option set to true + // does not result in any operations being sent from the client to the server + expect( + operationTraces.some( + trace => trace.opCount !== undefined && trace.opCount > 0 + ) + ).toBeFalsy }) afterAll(() => { diff --git a/packages/client/src/interfaces.ts b/packages/client/src/interfaces.ts index 6a37942..a936057 100644 --- a/packages/client/src/interfaces.ts +++ b/packages/client/src/interfaces.ts @@ -38,6 +38,7 @@ export interface SocketIOPluginOptions { onConnect?: () => void onDisconnect?: () => void onError?: (msg: string | Error, data: any) => void + resetOnReconnect?: boolean } export interface WithSocketIOEditor { diff --git a/packages/client/src/withSocketIO.ts b/packages/client/src/withSocketIO.ts index 4dad0fc..c899d7c 100644 --- a/packages/client/src/withSocketIO.ts +++ b/packages/client/src/withSocketIO.ts @@ -17,7 +17,14 @@ const withSocketIO = ( slateEditor: T, options: SocketIOPluginOptions & AutomergeOptions ) => { - const { onConnect, onDisconnect, connectOpts, url } = options + const { + onConnect, + onDisconnect, + connectOpts, + url, + docId, + resetOnReconnect + } = options const editor = slateEditor as T & WithSocketIOEditor & AutomergeEditor let socket: SocketIOClient.Socket @@ -31,6 +38,17 @@ const withSocketIO = ( // On socket io connect, open a new automerge connection socket.on('connect', () => { 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() onConnect && onConnect() })