diff --git a/packages/server/src/Hocuspocus.ts b/packages/server/src/Hocuspocus.ts index e71e27545..4f143c5bb 100644 --- a/packages/server/src/Hocuspocus.ts +++ b/packages/server/src/Hocuspocus.ts @@ -274,10 +274,17 @@ export class Hocuspocus { * Get the total number of active connections */ getConnectionsCount(): number { - return Array.from(this.documents.values()).reduce((acc, document) => { - acc += document.getConnectionsCount() - return acc + const uniqueSocketIds = new Set() + const totalDirectConnections = Array.from(this.documents.values()).reduce((acc, document) => { + // Accumulate unique socket IDs + document.getConnections().forEach(({ socketId }) => { + uniqueSocketIds.add(socketId) + }) + // Accumulate direct connections + return acc + document.directConnectionsCount }, 0) + // Return the sum of unique socket IDs and direct connections + return uniqueSocketIds.size + totalDirectConnections } /** diff --git a/tests/server/getConnectionsCount.ts b/tests/server/getConnectionsCount.ts index 10f6a01a7..aa9540097 100644 --- a/tests/server/getConnectionsCount.ts +++ b/tests/server/getConnectionsCount.ts @@ -1,6 +1,6 @@ import test from 'ava' import { retryableAssertion } from '../utils/retryableAssertion.js' -import { newHocuspocus, newHocuspocusProvider } from '../utils/index.js' +import { newHocuspocus, newHocuspocusProvider, newHocuspocusProviderWebsocket } from '../utils/index.js' test('returns 0 connections when there’s no one connected', async t => { await new Promise(async resolve => { @@ -87,3 +87,27 @@ test('adds and removes connections properly', async t => { tt.is(server.getConnectionsCount(), 0) }) }) + +test('multiplexed connections counts properly', async t => { + const server = await newHocuspocus() + const socket = newHocuspocusProviderWebsocket(server) + + const providers = [ + newHocuspocusProvider(server, { name: 'mux-1' }, {}, socket), + newHocuspocusProvider(server, { name: 'mux-2' }, {}, socket), + newHocuspocusProvider(server, { name: 'mux-3' }, {}, socket), + newHocuspocusProvider(server), + newHocuspocusProvider(server), + + ] + + await retryableAssertion(t, tt => { + tt.is(server.getConnectionsCount(), 3) + }) + + providers.forEach(provider => { provider.disconnect(); provider.configuration.websocketProvider.disconnect() }) + + await retryableAssertion(t, tt => { + tt.is(server.getConnectionsCount(), 0) + }) +}) diff --git a/tests/server/onAuthenticate.ts b/tests/server/onAuthenticate.ts index 94f4c79f2..aa02a18a3 100644 --- a/tests/server/onAuthenticate.ts +++ b/tests/server/onAuthenticate.ts @@ -362,7 +362,7 @@ test('onAuthenticate readonly auth only affects 1 doc (when multiplexing)', asyn tt.is(providerOK.status, WebSocketStatus.Connected) tt.is(providerReadOnly.status, WebSocketStatus.Connected) tt.is(server.getDocumentsCount(), 2) - tt.is(server.getConnectionsCount(), 2) + tt.is(server.getConnectionsCount(), 1) tt.is(socket.status, WebSocketStatus.Connected) }) diff --git a/tests/server/onClose.ts b/tests/server/onClose.ts index 70b3d9ae1..3c7c1989d 100644 --- a/tests/server/onClose.ts +++ b/tests/server/onClose.ts @@ -42,7 +42,7 @@ test('server closes connection only after receiving close event from all connect }) await retryableAssertion(t, t2 => { - t2.is(server.getConnectionsCount(), 2) + t2.is(server.getConnectionsCount(), 1) }) socket.shouldConnect = false diff --git a/tests/utils/newHocuspocusProvider.ts b/tests/utils/newHocuspocusProvider.ts index 0e71f250b..ba4b859e1 100644 --- a/tests/utils/newHocuspocusProvider.ts +++ b/tests/utils/newHocuspocusProvider.ts @@ -1,6 +1,6 @@ import { HocuspocusProvider, - HocuspocusProviderConfiguration, HocuspocusProviderWebsocketConfiguration, + HocuspocusProviderConfiguration, HocuspocusProviderWebsocket, HocuspocusProviderWebsocketConfiguration, } from '@hocuspocus/provider' import { Hocuspocus } from '@hocuspocus/server' import { newHocuspocusProviderWebsocket } from './newHocuspocusProviderWebsocket.js' @@ -9,9 +9,10 @@ export const newHocuspocusProvider = ( server: Hocuspocus, options: Partial = {}, websocketOptions: Partial = {}, + websocketProvider?: HocuspocusProviderWebsocket, ): HocuspocusProvider => { return new HocuspocusProvider({ - websocketProvider: newHocuspocusProviderWebsocket(server, websocketOptions), + websocketProvider: websocketProvider ?? newHocuspocusProviderWebsocket(server, websocketOptions), // Just use a generic document name for all tests. name: 'hocuspocus-test', // There is no need to share data with other browser tabs in the testing environment.