Skip to content

Commit 57cd950

Browse files
committed
fix: set last read message id before sending read event
1 parent 52be19d commit 57cd950

File tree

5 files changed

+85
-80
lines changed

5 files changed

+85
-80
lines changed

projects/stream-chat-angular/src/lib/channel.service.spec.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
ChannelResponse,
99
ChannelSort,
1010
Event,
11+
FormatMessageResponse,
1112
SendMessageAPIResponse,
1213
UserResponse,
1314
} from 'stream-chat';
@@ -343,6 +344,7 @@ describe('ChannelService', () => {
343344
expect(pinnedMessagesSpy).toHaveBeenCalledWith([]);
344345
expect(typingUsersSpy).toHaveBeenCalledWith([]);
345346
expect(typingUsersInThreadSpy).toHaveBeenCalledWith([]);
347+
expect(service.activeChannelLastReadMessageId).toBeUndefined();
346348

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

19971999
expect(spy).not.toHaveBeenCalled();
19982000
});
2001+
2002+
it('should set last read message id', () => {
2003+
const activeChannel = generateMockChannels()[0];
2004+
activeChannel.id = 'next-active-channel';
2005+
activeChannel.state.read[user.id] = {
2006+
last_read: new Date(),
2007+
last_read_message_id: 'last-read-message-id',
2008+
unread_messages: 5,
2009+
user: user,
2010+
};
2011+
2012+
service.setAsActiveChannel(activeChannel);
2013+
2014+
expect(service.activeChannelLastReadMessageId).toBe('last-read-message-id');
2015+
});
2016+
2017+
it(`should set last read message id to undefined if it's the last message`, () => {
2018+
const activeChannel = generateMockChannels()[0];
2019+
activeChannel.id = 'next-active-channel';
2020+
activeChannel.state.read[user.id] = {
2021+
last_read: new Date(),
2022+
last_read_message_id: 'last-read-message-id',
2023+
unread_messages: 5,
2024+
user: user,
2025+
};
2026+
activeChannel.state.latestMessages = [
2027+
{
2028+
id: 'last-read-message-id',
2029+
} as any as FormatMessageResponse<DefaultStreamChatGenerics>,
2030+
];
2031+
2032+
service.setAsActiveChannel(activeChannel);
2033+
2034+
expect(service.activeChannelLastReadMessageId).toBe(undefined);
2035+
});
19992036
});

projects/stream-chat-angular/src/lib/channel.service.ts

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,10 @@ export class ChannelService<
128128
* 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)
129129
*/
130130
latestMessageDateByUserByChannels$: Observable<{ [key: string]: Date }>;
131+
/**
132+
* 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
133+
*/
134+
activeChannelLastReadMessageId?: string;
131135
/**
132136
* 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)
133137
*/
@@ -392,7 +396,10 @@ export class ChannelService<
392396
*/
393397
set shouldMarkActiveChannelAsRead(shouldMarkActiveChannelAsRead: boolean) {
394398
if (!this._shouldMarkActiveChannelAsRead && shouldMarkActiveChannelAsRead) {
395-
this.activeChannelSubject.getValue()?.markRead();
399+
const activeChannel = this.activeChannelSubject.getValue();
400+
if (activeChannel && this.canSendReadEvents) {
401+
void activeChannel.markRead();
402+
}
396403
}
397404
this._shouldMarkActiveChannelAsRead = shouldMarkActiveChannelAsRead;
398405
}
@@ -409,6 +416,16 @@ export class ChannelService<
409416
return;
410417
}
411418
this.stopWatchForActiveChannelEvents(prevActiveChannel);
419+
this.activeChannelLastReadMessageId =
420+
channel.state.read[
421+
this.chatClientService.chatClient.user?.id || ''
422+
]?.last_read_message_id;
423+
if (
424+
channel.state.latestMessages[channel.state.latestMessages.length - 1]
425+
.id === this.activeChannelLastReadMessageId
426+
) {
427+
this.activeChannelLastReadMessageId = undefined;
428+
}
412429
this.watchForActiveChannelEvents(channel);
413430
this.addChannel(channel);
414431
this.activeChannelSubject.next(channel);
@@ -434,6 +451,7 @@ export class ChannelService<
434451
this.activeChannelPinnedMessagesSubject.next([]);
435452
this.usersTypingInChannelSubject.next([]);
436453
this.usersTypingInThreadSubject.next([]);
454+
this.activeChannelLastReadMessageId = undefined;
437455
}
438456

439457
/**
@@ -1130,8 +1148,8 @@ export class ChannelService<
11301148
...channel.state.messages,
11311149
]);
11321150
this.activeChannel$.pipe(first()).subscribe((c) => {
1133-
if (this.canSendReadEvents && this.shouldMarkActiveChannelAsRead) {
1134-
void c?.markRead();
1151+
if (c) {
1152+
this.markRead(c);
11351153
}
11361154
});
11371155
this.updateLatestMessages(event);
@@ -1687,9 +1705,7 @@ export class ChannelService<
16871705
);
16881706
}
16891707
});
1690-
if (this.canSendReadEvents && this.shouldMarkActiveChannelAsRead) {
1691-
void channel.markRead();
1692-
}
1708+
this.markRead(channel);
16931709
this.activeChannelMessagesSubject.next([...channel.state.messages]);
16941710
this.activeChannelPinnedMessagesSubject.next([
16951711
...channel.state.pinnedMessages,
@@ -1700,4 +1716,10 @@ export class ChannelService<
17001716
this.usersTypingInChannelSubject.next([]);
17011717
this.usersTypingInThreadSubject.next([]);
17021718
}
1719+
1720+
private markRead(channel: Channel<T>) {
1721+
if (this.canSendReadEvents && this.shouldMarkActiveChannelAsRead) {
1722+
void channel.markRead();
1723+
}
1724+
}
17031725
}

projects/stream-chat-angular/src/lib/message-list/message-list.component.spec.ts

Lines changed: 18 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -912,13 +912,8 @@ describe('MessageListComponent', () => {
912912
const channel = generateMockChannels()[0];
913913
const messages = generateMockMessages();
914914
channel.id = 'test-channel';
915-
channel.state.read[mockCurrentUser().id] = {
916-
last_read: new Date(),
917-
last_read_message_id: messages[messages.length - 1].id,
918-
unread_messages: 5,
919-
user: mockCurrentUser(),
920-
};
921-
915+
channelServiceMock.activeChannelLastReadMessageId =
916+
messages[messages.length - 1].id;
922917
channelServiceMock.activeChannel$.next(channel);
923918
channelServiceMock.activeChannelMessages$.next(messages);
924919

@@ -1060,13 +1055,8 @@ describe('MessageListComponent', () => {
10601055
const channel = generateMockChannels()[0];
10611056
const messages = generateMockMessages();
10621057
channel.id = 'test-channel';
1063-
channel.state.read[mockCurrentUser().id] = {
1064-
last_read: new Date(),
1065-
last_read_message_id: messages[messages.length - 2].id,
1066-
unread_messages: 5,
1067-
user: mockCurrentUser(),
1068-
};
1069-
1058+
channelServiceMock.activeChannelLastReadMessageId =
1059+
messages[messages.length - 2].id;
10701060
channelServiceMock.activeChannel$.next(channel);
10711061
channelServiceMock.activeChannelMessages$.next(messages);
10721062

@@ -1085,13 +1075,8 @@ describe('MessageListComponent', () => {
10851075
);
10861076
messages[messages.length - 1].user!.id = 'not' + mockCurrentUser().id;
10871077
channel.id = 'test-channel';
1088-
channel.state.read[mockCurrentUser().id] = {
1089-
last_read: new Date(),
1090-
last_read_message_id: messages[messages.length - 2].id,
1091-
unread_messages: 1,
1092-
user: mockCurrentUser(),
1093-
};
1094-
1078+
channelServiceMock.activeChannelLastReadMessageId =
1079+
messages[messages.length - 2].id;
10951080
channelServiceMock.activeChannel$.next(channel);
10961081
channelServiceMock.activeChannelMessages$.next(messages);
10971082
fixture.detectChanges();
@@ -1111,13 +1096,8 @@ describe('MessageListComponent', () => {
11111096
);
11121097
messages[messages.length - 1].user!.id = 'not' + mockCurrentUser().id;
11131098
channel.id = 'test-channel';
1114-
channel.state.read[mockCurrentUser().id] = {
1115-
last_read: new Date(),
1116-
last_read_message_id: messages[messages.length - 2].id,
1117-
unread_messages: 1,
1118-
user: mockCurrentUser(),
1119-
};
1120-
1099+
channelServiceMock.activeChannelLastReadMessageId =
1100+
messages[messages.length - 2].id;
11211101
channelServiceMock.activeChannel$.next(channel);
11221102
channelServiceMock.activeChannelMessages$.next(messages);
11231103
fixture.detectChanges();
@@ -1134,13 +1114,8 @@ describe('MessageListComponent', () => {
11341114
const messages = generateMockMessages();
11351115
messages[messages.length - 1].user!.id = 'not' + mockCurrentUser().id;
11361116
channel.id = 'test-channel';
1137-
channel.state.read[mockCurrentUser().id] = {
1138-
last_read: new Date(),
1139-
last_read_message_id: messages[messages.length - 2].id,
1140-
unread_messages: 1,
1141-
user: mockCurrentUser(),
1142-
};
1143-
1117+
channelServiceMock.activeChannelLastReadMessageId =
1118+
messages[messages.length - 2].id;
11441119
channelServiceMock.activeChannel$.next(channel);
11451120
channelServiceMock.activeChannelMessages$.next(messages);
11461121
fixture.detectChanges();
@@ -1156,13 +1131,8 @@ describe('MessageListComponent', () => {
11561131
const messages = generateMockMessages();
11571132
messages[messages.length - 1].user!.id = 'not' + mockCurrentUser().id;
11581133
channel.id = 'test-channel';
1159-
channel.state.read[mockCurrentUser().id] = {
1160-
last_read: new Date(),
1161-
last_read_message_id: messages[messages.length - 2].id,
1162-
unread_messages: 1,
1163-
user: mockCurrentUser(),
1164-
};
1165-
1134+
channelServiceMock.activeChannelLastReadMessageId =
1135+
messages[messages.length - 2].id;
11661136
channelServiceMock.activeChannel$.next(channel);
11671137
channelServiceMock.activeChannelMessages$.next(messages);
11681138
fixture.detectChanges();
@@ -1177,13 +1147,8 @@ describe('MessageListComponent', () => {
11771147
const messages = generateMockMessages();
11781148
messages[messages.length - 1].user!.id = 'not' + mockCurrentUser().id;
11791149
channel.id = 'test-channel';
1180-
channel.state.read[mockCurrentUser().id] = {
1181-
last_read: new Date(),
1182-
last_read_message_id: messages[messages.length - 2].id,
1183-
unread_messages: 1,
1184-
user: mockCurrentUser(),
1185-
};
1186-
1150+
channelServiceMock.activeChannelLastReadMessageId =
1151+
messages[messages.length - 2].id;
11871152
channelServiceMock.activeChannel$.next(channel);
11881153
channelServiceMock.activeChannelMessages$.next(messages);
11891154
fixture.detectChanges();
@@ -1205,13 +1170,8 @@ describe('MessageListComponent', () => {
12051170
);
12061171
messages[messages.length - 1].user!.id = 'not' + mockCurrentUser().id;
12071172
channel.id = 'test-channel';
1208-
channel.state.read[mockCurrentUser().id] = {
1209-
last_read: new Date(),
1210-
last_read_message_id: messages[messages.length - 2].id,
1211-
unread_messages: 1,
1212-
user: mockCurrentUser(),
1213-
};
1214-
1173+
channelServiceMock.activeChannelLastReadMessageId =
1174+
messages[messages.length - 2].id;
12151175
channelServiceMock.activeChannel$.next(channel);
12161176
channelServiceMock.activeChannelMessages$.next(messages);
12171177
fixture.detectChanges();
@@ -1232,13 +1192,8 @@ describe('MessageListComponent', () => {
12321192
);
12331193
messages[messages.length - 1].user!.id = 'not' + mockCurrentUser().id;
12341194
channel.id = 'test-channel';
1235-
channel.state.read[mockCurrentUser().id] = {
1236-
last_read: new Date(),
1237-
last_read_message_id: messages[messages.length - 2].id,
1238-
unread_messages: 1,
1239-
user: mockCurrentUser(),
1240-
};
1241-
1195+
channelServiceMock.activeChannelLastReadMessageId =
1196+
messages[messages.length - 2].id;
12421197
channelServiceMock.activeChannel$.next(channel);
12431198
channelServiceMock.activeChannelMessages$.next(messages);
12441199
fixture.detectChanges();

projects/stream-chat-angular/src/lib/message-list/message-list.component.ts

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -147,17 +147,7 @@ export class MessageListComponent
147147
this.mode === 'main'
148148
) {
149149
this.lastReadMessageId =
150-
channel?.state.read[
151-
this.chatClientService.chatClient.user?.id || ''
152-
]?.last_read_message_id;
153-
if (
154-
channel &&
155-
channel.state.latestMessages[
156-
channel.state.latestMessages.length - 1
157-
].id === this.lastReadMessageId
158-
) {
159-
this.lastReadMessageId = undefined;
160-
}
150+
this.channelService.activeChannelLastReadMessageId;
161151
if (this.lastReadMessageId) {
162152
this.isJumpingToLatestUnreadMessage = true;
163153
void this.channelService.jumpToMessage(this.lastReadMessageId);

projects/stream-chat-angular/src/lib/mocks/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,7 @@ export type MockChannelService = {
213213
usersTypingInThread$: BehaviorSubject<UserResponse[]>;
214214
jumpToMessage$: BehaviorSubject<{ id?: string; parentId?: string }>;
215215
channelQueryState$: BehaviorSubject<ChannelQueryState | undefined>;
216+
activeChannelLastReadMessageId?: string;
216217
loadMoreMessages: (d: 'older' | 'newer') => void;
217218
loadMoreChannels: () => void;
218219
setAsActiveChannel: (c: Channel) => void;

0 commit comments

Comments
 (0)