Skip to content

Commit

Permalink
fix: set last read message id before sending read event
Browse files Browse the repository at this point in the history
  • Loading branch information
szuperaz committed Sep 24, 2023
1 parent 52be19d commit 57cd950
Show file tree
Hide file tree
Showing 5 changed files with 85 additions and 80 deletions.
37 changes: 37 additions & 0 deletions projects/stream-chat-angular/src/lib/channel.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
ChannelResponse,
ChannelSort,
Event,
FormatMessageResponse,
SendMessageAPIResponse,
UserResponse,
} from 'stream-chat';
Expand Down Expand Up @@ -343,6 +344,7 @@ describe('ChannelService', () => {
expect(pinnedMessagesSpy).toHaveBeenCalledWith([]);
expect(typingUsersSpy).toHaveBeenCalledWith([]);
expect(typingUsersInThreadSpy).toHaveBeenCalledWith([]);
expect(service.activeChannelLastReadMessageId).toBeUndefined();

messagesSpy.calls.reset();
(activeChannel as MockChannel).handleEvent('message.new', mockMessage());
Expand Down Expand Up @@ -1996,4 +1998,39 @@ describe('ChannelService', () => {

expect(spy).not.toHaveBeenCalled();
});

it('should set last read message id', () => {
const activeChannel = generateMockChannels()[0];
activeChannel.id = 'next-active-channel';
activeChannel.state.read[user.id] = {
last_read: new Date(),
last_read_message_id: 'last-read-message-id',
unread_messages: 5,
user: user,
};

service.setAsActiveChannel(activeChannel);

expect(service.activeChannelLastReadMessageId).toBe('last-read-message-id');
});

it(`should set last read message id to undefined if it's the last message`, () => {
const activeChannel = generateMockChannels()[0];
activeChannel.id = 'next-active-channel';
activeChannel.state.read[user.id] = {
last_read: new Date(),
last_read_message_id: 'last-read-message-id',
unread_messages: 5,
user: user,
};
activeChannel.state.latestMessages = [
{
id: 'last-read-message-id',
} as any as FormatMessageResponse<DefaultStreamChatGenerics>,
];

service.setAsActiveChannel(activeChannel);

expect(service.activeChannelLastReadMessageId).toBe(undefined);
});
});
34 changes: 28 additions & 6 deletions projects/stream-chat-angular/src/lib/channel.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,10 @@ export class ChannelService<
* Emits a map that contains the date of the latest message sent by the current user by channels (this is used to detect if slow mode countdown should be started)
*/
latestMessageDateByUserByChannels$: Observable<{ [key: string]: Date }>;
/**
* The last read message id of the active channel, it's only set once, when a new active channel is set. It's used by the message list to jump to the latest read message
*/
activeChannelLastReadMessageId?: string;
/**
* Custom event handler to call if a new message received from a channel that is not being watched, provide an event handler if you want to override the [default channel list ordering](./ChannelService.mdx/#channels)
*/
Expand Down Expand Up @@ -392,7 +396,10 @@ export class ChannelService<
*/
set shouldMarkActiveChannelAsRead(shouldMarkActiveChannelAsRead: boolean) {
if (!this._shouldMarkActiveChannelAsRead && shouldMarkActiveChannelAsRead) {
this.activeChannelSubject.getValue()?.markRead();
const activeChannel = this.activeChannelSubject.getValue();
if (activeChannel && this.canSendReadEvents) {
void activeChannel.markRead();
}
}
this._shouldMarkActiveChannelAsRead = shouldMarkActiveChannelAsRead;
}
Expand All @@ -409,6 +416,16 @@ export class ChannelService<
return;
}
this.stopWatchForActiveChannelEvents(prevActiveChannel);
this.activeChannelLastReadMessageId =
channel.state.read[
this.chatClientService.chatClient.user?.id || ''
]?.last_read_message_id;
if (
channel.state.latestMessages[channel.state.latestMessages.length - 1]
.id === this.activeChannelLastReadMessageId
) {
this.activeChannelLastReadMessageId = undefined;
}
this.watchForActiveChannelEvents(channel);
this.addChannel(channel);
this.activeChannelSubject.next(channel);
Expand All @@ -434,6 +451,7 @@ export class ChannelService<
this.activeChannelPinnedMessagesSubject.next([]);
this.usersTypingInChannelSubject.next([]);
this.usersTypingInThreadSubject.next([]);
this.activeChannelLastReadMessageId = undefined;
}

/**
Expand Down Expand Up @@ -1130,8 +1148,8 @@ export class ChannelService<
...channel.state.messages,
]);
this.activeChannel$.pipe(first()).subscribe((c) => {
if (this.canSendReadEvents && this.shouldMarkActiveChannelAsRead) {
void c?.markRead();
if (c) {
this.markRead(c);
}
});
this.updateLatestMessages(event);
Expand Down Expand Up @@ -1687,9 +1705,7 @@ export class ChannelService<
);
}
});
if (this.canSendReadEvents && this.shouldMarkActiveChannelAsRead) {
void channel.markRead();
}
this.markRead(channel);
this.activeChannelMessagesSubject.next([...channel.state.messages]);
this.activeChannelPinnedMessagesSubject.next([
...channel.state.pinnedMessages,
Expand All @@ -1700,4 +1716,10 @@ export class ChannelService<
this.usersTypingInChannelSubject.next([]);
this.usersTypingInThreadSubject.next([]);
}

private markRead(channel: Channel<T>) {
if (this.canSendReadEvents && this.shouldMarkActiveChannelAsRead) {
void channel.markRead();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -912,13 +912,8 @@ describe('MessageListComponent', () => {
const channel = generateMockChannels()[0];
const messages = generateMockMessages();
channel.id = 'test-channel';
channel.state.read[mockCurrentUser().id] = {
last_read: new Date(),
last_read_message_id: messages[messages.length - 1].id,
unread_messages: 5,
user: mockCurrentUser(),
};

channelServiceMock.activeChannelLastReadMessageId =
messages[messages.length - 1].id;
channelServiceMock.activeChannel$.next(channel);
channelServiceMock.activeChannelMessages$.next(messages);

Expand Down Expand Up @@ -1060,13 +1055,8 @@ describe('MessageListComponent', () => {
const channel = generateMockChannels()[0];
const messages = generateMockMessages();
channel.id = 'test-channel';
channel.state.read[mockCurrentUser().id] = {
last_read: new Date(),
last_read_message_id: messages[messages.length - 2].id,
unread_messages: 5,
user: mockCurrentUser(),
};

channelServiceMock.activeChannelLastReadMessageId =
messages[messages.length - 2].id;
channelServiceMock.activeChannel$.next(channel);
channelServiceMock.activeChannelMessages$.next(messages);

Expand All @@ -1085,13 +1075,8 @@ describe('MessageListComponent', () => {
);
messages[messages.length - 1].user!.id = 'not' + mockCurrentUser().id;
channel.id = 'test-channel';
channel.state.read[mockCurrentUser().id] = {
last_read: new Date(),
last_read_message_id: messages[messages.length - 2].id,
unread_messages: 1,
user: mockCurrentUser(),
};

channelServiceMock.activeChannelLastReadMessageId =
messages[messages.length - 2].id;
channelServiceMock.activeChannel$.next(channel);
channelServiceMock.activeChannelMessages$.next(messages);
fixture.detectChanges();
Expand All @@ -1111,13 +1096,8 @@ describe('MessageListComponent', () => {
);
messages[messages.length - 1].user!.id = 'not' + mockCurrentUser().id;
channel.id = 'test-channel';
channel.state.read[mockCurrentUser().id] = {
last_read: new Date(),
last_read_message_id: messages[messages.length - 2].id,
unread_messages: 1,
user: mockCurrentUser(),
};

channelServiceMock.activeChannelLastReadMessageId =
messages[messages.length - 2].id;
channelServiceMock.activeChannel$.next(channel);
channelServiceMock.activeChannelMessages$.next(messages);
fixture.detectChanges();
Expand All @@ -1134,13 +1114,8 @@ describe('MessageListComponent', () => {
const messages = generateMockMessages();
messages[messages.length - 1].user!.id = 'not' + mockCurrentUser().id;
channel.id = 'test-channel';
channel.state.read[mockCurrentUser().id] = {
last_read: new Date(),
last_read_message_id: messages[messages.length - 2].id,
unread_messages: 1,
user: mockCurrentUser(),
};

channelServiceMock.activeChannelLastReadMessageId =
messages[messages.length - 2].id;
channelServiceMock.activeChannel$.next(channel);
channelServiceMock.activeChannelMessages$.next(messages);
fixture.detectChanges();
Expand All @@ -1156,13 +1131,8 @@ describe('MessageListComponent', () => {
const messages = generateMockMessages();
messages[messages.length - 1].user!.id = 'not' + mockCurrentUser().id;
channel.id = 'test-channel';
channel.state.read[mockCurrentUser().id] = {
last_read: new Date(),
last_read_message_id: messages[messages.length - 2].id,
unread_messages: 1,
user: mockCurrentUser(),
};

channelServiceMock.activeChannelLastReadMessageId =
messages[messages.length - 2].id;
channelServiceMock.activeChannel$.next(channel);
channelServiceMock.activeChannelMessages$.next(messages);
fixture.detectChanges();
Expand All @@ -1177,13 +1147,8 @@ describe('MessageListComponent', () => {
const messages = generateMockMessages();
messages[messages.length - 1].user!.id = 'not' + mockCurrentUser().id;
channel.id = 'test-channel';
channel.state.read[mockCurrentUser().id] = {
last_read: new Date(),
last_read_message_id: messages[messages.length - 2].id,
unread_messages: 1,
user: mockCurrentUser(),
};

channelServiceMock.activeChannelLastReadMessageId =
messages[messages.length - 2].id;
channelServiceMock.activeChannel$.next(channel);
channelServiceMock.activeChannelMessages$.next(messages);
fixture.detectChanges();
Expand All @@ -1205,13 +1170,8 @@ describe('MessageListComponent', () => {
);
messages[messages.length - 1].user!.id = 'not' + mockCurrentUser().id;
channel.id = 'test-channel';
channel.state.read[mockCurrentUser().id] = {
last_read: new Date(),
last_read_message_id: messages[messages.length - 2].id,
unread_messages: 1,
user: mockCurrentUser(),
};

channelServiceMock.activeChannelLastReadMessageId =
messages[messages.length - 2].id;
channelServiceMock.activeChannel$.next(channel);
channelServiceMock.activeChannelMessages$.next(messages);
fixture.detectChanges();
Expand All @@ -1232,13 +1192,8 @@ describe('MessageListComponent', () => {
);
messages[messages.length - 1].user!.id = 'not' + mockCurrentUser().id;
channel.id = 'test-channel';
channel.state.read[mockCurrentUser().id] = {
last_read: new Date(),
last_read_message_id: messages[messages.length - 2].id,
unread_messages: 1,
user: mockCurrentUser(),
};

channelServiceMock.activeChannelLastReadMessageId =
messages[messages.length - 2].id;
channelServiceMock.activeChannel$.next(channel);
channelServiceMock.activeChannelMessages$.next(messages);
fixture.detectChanges();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -147,17 +147,7 @@ export class MessageListComponent
this.mode === 'main'
) {
this.lastReadMessageId =
channel?.state.read[
this.chatClientService.chatClient.user?.id || ''
]?.last_read_message_id;
if (
channel &&
channel.state.latestMessages[
channel.state.latestMessages.length - 1
].id === this.lastReadMessageId
) {
this.lastReadMessageId = undefined;
}
this.channelService.activeChannelLastReadMessageId;
if (this.lastReadMessageId) {
this.isJumpingToLatestUnreadMessage = true;
void this.channelService.jumpToMessage(this.lastReadMessageId);
Expand Down
1 change: 1 addition & 0 deletions projects/stream-chat-angular/src/lib/mocks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,7 @@ export type MockChannelService = {
usersTypingInThread$: BehaviorSubject<UserResponse[]>;
jumpToMessage$: BehaviorSubject<{ id?: string; parentId?: string }>;
channelQueryState$: BehaviorSubject<ChannelQueryState | undefined>;
activeChannelLastReadMessageId?: string;
loadMoreMessages: (d: 'older' | 'newer') => void;
loadMoreChannels: () => void;
setAsActiveChannel: (c: Channel) => void;
Expand Down

0 comments on commit 57cd950

Please sign in to comment.