diff --git a/src/Space.test.ts b/src/Space.test.ts index e0c1de87..0e5e3725 100644 --- a/src/Space.test.ts +++ b/src/Space.test.ts @@ -63,7 +63,9 @@ describe('Space', () => { beforeEach(({ presence }) => { vi.spyOn(presence, 'subscribe').mockImplementation( async (_, listener?: (presenceMessage: Types.PresenceMessage) => void) => { - listener!(createPresenceMessage('enter' /* arbitrarily chosen */)); + listener!( + createPresenceMessage('enter' /* arbitrarily chosen */, { clientId: 'MOCK_CLIENT_ID', connectionId: '1' }), + ); }, ); }); @@ -74,6 +76,64 @@ describe('Space', () => { expect(spy).toHaveBeenNthCalledWith(1, createProfileUpdate({ current: { name: 'Betty' } })); }); + describe.each([ + { + scenario: 'when it receives a presence message from a client ID and connection ID that matches ours', + presenceMessageData: { clientId: 'MOCK_CLIENT_ID', connectionId: '1' }, + shouldComplete: true, + }, + { + scenario: 'when it receives a presence message from a client ID that isn’t ours', + presenceMessageData: { clientId: 'OTHER_MOCK_CLIENT_ID', connectionId: '1' }, + shouldComplete: false, + }, + { + scenario: 'when it receives a presence message from a connection ID that isn’t ours', + presenceMessageData: { clientId: 'MOCK_CLIENT_ID', connectionId: '2' }, + shouldComplete: false, + }, + ])('$scenario', ({ presenceMessageData, shouldComplete }) => { + it(shouldComplete ? 'completes' : 'does not complete', async ({ space, presence }) => { + const unsubscribeSpy = vi.spyOn(presence, 'unsubscribe'); + + vi.spyOn(presence, 'subscribe').mockImplementation( + async (_, listener?: (presenceMessage: Types.PresenceMessage) => void) => { + listener!(createPresenceMessage('enter' /* arbitrarily chosen */, presenceMessageData)); + }, + ); + + space.enter(); + + // Note: There’s no nice way (i.e. without timeouts) to actually check that space.enter() didn’t complete, so we use "did it remove its presence listener?" as a proxy for "did it complete?" + if (shouldComplete) { + expect(unsubscribeSpy).toHaveBeenCalled(); + } else { + expect(unsubscribeSpy).not.toHaveBeenCalled(); + } + }); + }); + + it('doesn’t complete as a result of a presence message from a client ID that is not ours', async ({ + space, + presence, + }) => { + const unsubscribeSpy = vi.spyOn(presence, 'unsubscribe'); + + vi.spyOn(presence, 'subscribe').mockImplementation( + async (_, listener?: (presenceMessage: Types.PresenceMessage) => void) => { + listener!( + createPresenceMessage('enter' /* arbitrarily chosen */, { + clientId: 'OTHER_MOCK_CLIENT_ID', + connectionId: '1', + }), + ); + }, + ); + + space.enter(); + expect(unsubscribeSpy).not.toHaveBeenCalled(); + }); + it('returns current space members', async ({ presenceMap, space }) => { presenceMap.set('1', createPresenceMessage('enter')); presenceMap.set('2', createPresenceMessage('update', { clientId: '2', connectionId: '2' })); diff --git a/src/Space.ts b/src/Space.ts index f6fb22cf..7761b381 100644 --- a/src/Space.ts +++ b/src/Space.ts @@ -244,7 +244,16 @@ class Space extends EventEmitter { return new Promise((resolve) => { const presence = this.channel.presence; - const presenceListener = async () => { + const presenceListener = async (presenceMessage: Types.PresenceMessage) => { + if ( + !( + presenceMessage.clientId == this.client.auth.clientId && + presenceMessage.connectionId == this.client.connection.id + ) + ) { + return; + } + presence.unsubscribe(presenceListener); const presenceMessages = await presence.get();