From 70ef611c36e403e3b0bdbda984d30bf60ab631be Mon Sep 17 00:00:00 2001 From: Jan Thurau Date: Thu, 13 Jun 2024 20:22:54 +0200 Subject: [PATCH 1/2] fixes #832 --- packages/server/src/DirectConnection.ts | 4 +- packages/server/src/Hocuspocus.ts | 53 +++++++++++++------------ 2 files changed, 30 insertions(+), 27 deletions(-) diff --git a/packages/server/src/DirectConnection.ts b/packages/server/src/DirectConnection.ts index a4cebef24..7ecad0a7d 100644 --- a/packages/server/src/DirectConnection.ts +++ b/packages/server/src/DirectConnection.ts @@ -42,7 +42,7 @@ export class DirectConnection implements DirectConnectionInterface { requestParameters: new URLSearchParams(), socketId: 'server', transactionOrigin, - }) + }, true) } async disconnect() { @@ -59,7 +59,7 @@ export class DirectConnection implements DirectConnectionInterface { requestHeaders: {}, requestParameters: new URLSearchParams(), socketId: 'server', - }) + }, true) this.document = null } diff --git a/packages/server/src/Hocuspocus.ts b/packages/server/src/Hocuspocus.ts index a9d3f78a5..14877ba5c 100644 --- a/packages/server/src/Hocuspocus.ts +++ b/packages/server/src/Hocuspocus.ts @@ -378,7 +378,7 @@ export class Hocuspocus { * "connection" is not necessarily type "Connection", it's the Yjs "origin" (which is "Connection" if * the update is incoming from the provider, but can be anything if the updates is originated from an extension. */ - private handleDocumentUpdate(document: Document, connection: Connection | undefined, update: Uint8Array, request?: IncomingMessage): void { + private async handleDocumentUpdate(document: Document, connection: Connection | undefined, update: Uint8Array, request?: IncomingMessage) { const hookPayload: onChangePayload | onStoreDocumentPayload = { instance: this, clientsCount: document.getConnectionsCount(), @@ -405,12 +405,7 @@ export class Hocuspocus { return } - this.debouncer.debounce( - `onStoreDocument-${document.name}`, - () => this.storeDocumentHooks(document, hookPayload), - this.configuration.debounce, - this.configuration.maxDebounce, - ) + await this.storeDocumentHooks(document, hookPayload) } /** @@ -521,26 +516,34 @@ export class Hocuspocus { return document } - storeDocumentHooks(document: Document, hookPayload: onStoreDocumentPayload) { - return this.hooks('onStoreDocument', hookPayload) - .then(() => { - this.hooks('afterStoreDocument', hookPayload).then(() => { - // Remove document from memory. - - if (document.getConnectionsCount() > 0) { - return - } + storeDocumentHooks(document: Document, hookPayload: onStoreDocumentPayload, immediately?: boolean) { + return this.debouncer.debounce( + `onStoreDocument-${document.name}`, + () => { + return this.hooks('onStoreDocument', hookPayload) + .then(() => { + this.hooks('afterStoreDocument', hookPayload).then(() => { + // Remove document from memory. + + if (document.getConnectionsCount() > 0) { + return + } + + this.unloadDocument(document) + }) + }) + .catch(error => { + console.error('Caught error during storeDocumentHooks', error) - this.unloadDocument(document) - }) - }) - .catch(error => { - console.error('Caught error during storeDocumentHooks', error) + if (error?.message) { + throw error + } + }) + }, + immediately ? 0 : this.configuration.debounce, + this.configuration.maxDebounce, + ) - if (error?.message) { - throw error - } - }) } /** From e0a6ea85753235c0766c9bf79d6b75e3298ce9a6 Mon Sep 17 00:00:00 2001 From: Jan Thurau Date: Thu, 13 Jun 2024 20:43:25 +0200 Subject: [PATCH 2/2] adds reproduction test --- tests/server/openDirectConnection.ts | 46 ++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/tests/server/openDirectConnection.ts b/tests/server/openDirectConnection.ts index 00bdd072b..aac0d8e60 100644 --- a/tests/server/openDirectConnection.ts +++ b/tests/server/openDirectConnection.ts @@ -218,3 +218,49 @@ test('direct connection transact awaits until onStoreDocument has finished, even resolve('done') }) }) + +test('creating a websocket connection after transact but before debounce interval doesnt create different docs', async t => { + let onStoreDocumentFinished = false + let disconnected = false + + await new Promise(async resolve => { + const server = await newHocuspocus({ + onStoreDocument: async () => { + onStoreDocumentFinished = false + await sleep(200) + onStoreDocumentFinished = true + }, + async afterUnloadDocument(data) { + console.log('called') + if (disconnected) { + t.fail('must not be called') + } + }, + }) + + const direct = await server.openDirectConnection('hocuspocus-test') + t.is(server.getDocumentsCount(), 1) + t.is(server.getConnectionsCount(), 1) + + t.is(onStoreDocumentFinished, false) + await direct.transact(document => { + document.transact(() => { + document.getArray('test').insert(0, ['value']) + }, 'testOrigin') + }) + t.is(onStoreDocumentFinished, true) + + await direct.disconnect() + disconnected = true + + t.is(server.getConnectionsCount(), 0) + t.is(server.getDocumentsCount(), 0) + t.is(onStoreDocumentFinished, true) + + const provider = newHocuspocusProvider(server) + + await sleep(server.configuration.debounce * 2) + + resolve('done') + }) +})