Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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"
}
Original file line number Diff line number Diff line change
@@ -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"
}
9 changes: 5 additions & 4 deletions packages/chat-stateful-client/src/EventSubscriber.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<void> => {
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' });
}
Expand All @@ -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);
}
};

Expand Down
40 changes: 40 additions & 0 deletions packages/chat-stateful-client/src/StatefulChatClient.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down
Loading