diff --git a/change-beta/@azure-communication-react-f327bae5-5527-4e73-b4a6-fd6b43b44ff5.json b/change-beta/@azure-communication-react-f327bae5-5527-4e73-b4a6-fd6b43b44ff5.json new file mode 100644 index 00000000000..182f7363e8b --- /dev/null +++ b/change-beta/@azure-communication-react-f327bae5-5527-4e73-b4a6-fd6b43b44ff5.json @@ -0,0 +1,9 @@ +{ + "type": "patch", + "area": "fix", + "workstream": "Chat system messages", + "comment": "Fix participant system messages being hidden when the client clock differs from event timestamps", + "packageName": "@azure/communication-react", + "email": "miguelgamis@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/change/@azure-communication-react-f327bae5-5527-4e73-b4a6-fd6b43b44ff5.json b/change/@azure-communication-react-f327bae5-5527-4e73-b4a6-fd6b43b44ff5.json new file mode 100644 index 00000000000..182f7363e8b --- /dev/null +++ b/change/@azure-communication-react-f327bae5-5527-4e73-b4a6-fd6b43b44ff5.json @@ -0,0 +1,9 @@ +{ + "type": "patch", + "area": "fix", + "workstream": "Chat system messages", + "comment": "Fix participant system messages being hidden when the client clock differs from event timestamps", + "packageName": "@azure/communication-react", + "email": "miguelgamis@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/packages/chat-stateful-client/src/EventSubscriber.ts b/packages/chat-stateful-client/src/EventSubscriber.ts index d62764be97a..0af6595b7a3 100644 --- a/packages/chat-stateful-client/src/EventSubscriber.ts +++ b/packages/chat-stateful-client/src/EventSubscriber.ts @@ -108,18 +108,19 @@ export class EventSubscriber { this.chatContext.createThreadIfNotExist(event.threadId); this.chatContext.setParticipants(event.threadId, participantsToAdd); }); - this.fetchLastParticipantMessage(event.threadId, 'participantAdded'); + this.fetchLastParticipantMessage(event.threadId, 'participantAdded', event.addedOn); }; // This is a temporary fix that no participant message is received for onChatMessageReceived event, which should be handled by JS SDK. // Without the temporary fix, there are missing 'participant joined' and 'participant left' system messages in the chat thread. private fetchLastParticipantMessage = async ( threadId: string, - actionType: 'participantAdded' | 'participantRemoved' + actionType: 'participantAdded' | 'participantRemoved', + actionTimestamp: Date ): Promise => { for await (const message of this.chatClient .getChatThreadClient(threadId) - .listMessages({ startTime: new Date(Date.now() - maxSyncTimeInMs) })) { + .listMessages({ startTime: new Date(actionTimestamp.getTime() - maxSyncTimeInMs) })) { if (message.type === actionType) { this.chatContext.setChatMessage(threadId, { ...message, status: 'delivered' }); } @@ -137,7 +138,7 @@ export class EventSubscriber { const currentUserId = toFlatCommunicationIdentifier(this.chatContext.getState().userId); const wasCurrentUserRemoved = participantIds.find((id) => toFlatCommunicationIdentifier(id) === currentUserId); if (!wasCurrentUserRemoved) { - this.fetchLastParticipantMessage(event.threadId, 'participantRemoved'); + this.fetchLastParticipantMessage(event.threadId, 'participantRemoved', event.removedOn); } }; diff --git a/packages/chat-stateful-client/src/StatefulChatClient.test.ts b/packages/chat-stateful-client/src/StatefulChatClient.test.ts index aec5af14a8f..236453567c6 100644 --- a/packages/chat-stateful-client/src/StatefulChatClient.test.ts +++ b/packages/chat-stateful-client/src/StatefulChatClient.test.ts @@ -268,6 +268,46 @@ describe('declarative chatClient subscribe to event properly after startRealtime expect(Object.keys(client.getState().threads[threadId]?.participants ?? {}).length).toBe(1); }); + test('fetches participant system messages relative to the event time when the client clock is skewed', async () => { + const threadId = 'threadId1'; + const chatClient = createMockChatClient(); + const chatThreadClient = chatClient.getChatThreadClient(threadId); + const listMessagesSpy = jest.spyOn(chatThreadClient, 'listMessages'); + chatClient.getChatThreadClient = jest.fn(() => chatThreadClient); + const skewedClient = _createStatefulChatClientWithDeps( + chatClient, + defaultClientArgs + ) as StatefulChatClientWithEventTrigger; + await skewedClient.startRealtimeNotifications(); + + const addedOn = new Date('2020-01-01T00:00:00.000Z'); + const removedOn = new Date('2020-01-01T00:01:00.000Z'); + const participant = mockParticipants[0]; + if (!participant) { + throw new Error('mockParticipants is empty'); + } + + await skewedClient.triggerEvent('participantsAdded', { + threadId, + addedBy: participant, + addedOn, + participantsAdded: mockParticipants, + version: '' + } satisfies ParticipantsAddedEvent); + await skewedClient.triggerEvent('participantsRemoved', { + threadId, + removedBy: participant, + removedOn, + participantsRemoved: [participant], + version: '' + } satisfies ParticipantsRemovedEvent); + + expect(listMessagesSpy).toHaveBeenNthCalledWith(1, { startTime: new Date('2019-12-31T23:59:50.000Z') }); + expect(listMessagesSpy).toHaveBeenNthCalledWith(2, { startTime: new Date('2020-01-01T00:00:50.000Z') }); + + await skewedClient.stopRealtimeNotifications(); + }); + test('set internal store correctly when receive typingIndicator events', async () => { const threadId = 'threadId1';