From dfe4fa9c300e85a5afd2d6b1d01e3ed7608b16b4 Mon Sep 17 00:00:00 2001 From: Lewis Marshall Date: Thu, 24 Aug 2023 10:51:06 +0100 Subject: [PATCH] locks: Release all locks when a member leaves Signed-off-by: Lewis Marshall --- src/Locks.test.ts | 44 ++++++++++++++++++++++++++++++++++++++++++++ src/Locks.ts | 6 +++--- 2 files changed, 47 insertions(+), 3 deletions(-) diff --git a/src/Locks.test.ts b/src/Locks.test.ts index 97d147ec..7b33e2df 100644 --- a/src/Locks.test.ts +++ b/src/Locks.test.ts @@ -116,6 +116,7 @@ describe('Locks (mockClient)', () => { const emitSpy = vi.spyOn(space.locks, 'emit'); const msg = Realtime.PresenceMessage.fromValues({ + action: 'update', connectionId: member.connectionId, extras: { locks: [ @@ -179,6 +180,7 @@ describe('Locks (mockClient)', () => { // process a PENDING request for the other member, which should // transition to LOCKED let msg = Realtime.PresenceMessage.fromValues({ + action: 'update', connectionId: otherConnId, extras: { locks: [ @@ -198,6 +200,7 @@ describe('Locks (mockClient)', () => { // result matches what is expected const emitSpy = vi.spyOn(space.locks, 'emit'); msg = Realtime.PresenceMessage.fromValues({ + action: 'update', connectionId: client.connection.id, extras: { locks: [ @@ -233,6 +236,7 @@ describe('Locks (mockClient)', () => { const member = (await space.members.getSelf())!; let msg = Realtime.PresenceMessage.fromValues({ + action: 'update', connectionId: member.connectionId, extras: { locks: [ @@ -249,6 +253,7 @@ describe('Locks (mockClient)', () => { const emitSpy = vi.spyOn(space.locks, 'emit'); msg = Realtime.PresenceMessage.fromValues({ + action: 'update', connectionId: member.connectionId, extras: undefined, }); @@ -258,6 +263,45 @@ describe('Locks (mockClient)', () => { expect(lock).not.toBeDefined(); expect(emitSpy).toHaveBeenCalledWith('update', lockEvent(member, 'unlocked')); }); + + it('sets all locks to UNLOCKED when a member leaves', async ({ space }) => { + await space.enter(); + const member = (await space.members.getSelf())!; + + let msg = Realtime.PresenceMessage.fromValues({ + action: 'update', + connectionId: member.connectionId, + extras: { + locks: [ + { + id: 'lock1', + status: 'pending', + timestamp: Date.now(), + }, + { + id: 'lock2', + status: 'pending', + timestamp: Date.now(), + }, + ], + }, + }); + await space.locks.processPresenceMessage(msg); + let lock1 = space.locks.get('lock1'); + expect(lock1).toBeDefined(); + expect(lock1!.member).toEqual(member); + let lock2 = space.locks.get('lock2'); + expect(lock2).toBeDefined(); + expect(lock2!.member).toEqual(member); + + msg.action = 'leave'; + await space.locks.processPresenceMessage(msg); + + lock1 = space.locks.get('lock1'); + expect(lock1).not.toBeDefined(); + lock2 = space.locks.get('lock2'); + expect(lock2).not.toBeDefined(); + }); }); describe('release', () => { diff --git a/src/Locks.ts b/src/Locks.ts index 81fb354f..c4637ad7 100644 --- a/src/Locks.ts +++ b/src/Locks.ts @@ -131,9 +131,9 @@ export default class Locks extends EventEmitter { const member = await this.space.members.getByConnectionId(message.connectionId); if (!member) return; - if (!Array.isArray(message?.extras?.locks)) { - // there are no locks in presence, so release any existing locks for the - // member + if (message.action === 'leave' || !Array.isArray(message?.extras?.locks)) { + // the member has left, or they have no locks in presence, so release any + // existing locks for that member for (const locks of this.locks.values()) { const lock = locks.get(member.connectionId); if (lock) {