Skip to content

Commit

Permalink
feat: handle notification.mark_unread (#1208)
Browse files Browse the repository at this point in the history
  • Loading branch information
MartinCupela authored Jan 12, 2024
1 parent 31e63df commit 4d73838
Show file tree
Hide file tree
Showing 5 changed files with 101 additions and 7 deletions.
16 changes: 15 additions & 1 deletion src/channel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1241,7 +1241,6 @@ export class Channel<StreamChatGenerics extends ExtendableGenerics = DefaultGene
case 'message.read':
if (event.user?.id && event.created_at) {
channelState.read[event.user.id] = {
// because in client.ts the handleEvent call that flows to this sets this `event.received_at = new Date();`
last_read: new Date(event.created_at),
last_read_message_id: event.last_read_message_id,
user: event.user,
Expand Down Expand Up @@ -1360,6 +1359,21 @@ export class Channel<StreamChatGenerics extends ExtendableGenerics = DefaultGene
delete channelState.members[event.user.id];
}
break;
case 'notification.mark_unread': {
const ownMessage = event.user?.id === this.getClient().user?.id;
if (!(ownMessage && event.user)) break;

channelState.read[event.user.id] = {
first_unread_message_id: event.first_unread_message_id,
last_read: new Date(event.last_read_at as string),
last_read_message_id: event.last_read_message_id,
user: event.user,
unread_messages: event.unread_messages ?? 0,
};

channelState.unreadCount = event.unread_messages ?? 0;
break;
}
case 'channel.updated':
if (event.channel) {
const isFrozenChanged = event.channel?.frozen !== undefined && event.channel.frozen !== channel.data?.frozen;
Expand Down
8 changes: 7 additions & 1 deletion src/channel_state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,13 @@ import {

type ChannelReadStatus<StreamChatGenerics extends ExtendableGenerics = DefaultGenerics> = Record<
string,
{ last_read: Date; unread_messages: number; user: UserResponse<StreamChatGenerics>; last_read_message_id?: string }
{
last_read: Date;
unread_messages: number;
user: UserResponse<StreamChatGenerics>;
first_unread_message_id?: string;
last_read_message_id?: string;
}
>;

/**
Expand Down
1 change: 1 addition & 0 deletions src/events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export const EVENT_MAP = {
'notification.invite_rejected': true,
'notification.invited': true,
'notification.mark_read': true,
'notification.mark_unread': true,
'notification.message_new': true,
'notification.mutes_updated': true,
'notification.removed_from_channel': true,
Expand Down
10 changes: 10 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1055,8 +1055,13 @@ export type Event<StreamChatGenerics extends ExtendableGenerics = DefaultGeneric
cid?: string;
clear_history?: boolean;
connection_id?: string;
// event creation timestamp, format Date ISO string
created_at?: string;
// id of the message that was marked as unread - all the following messages are considered unread. (notification.mark_unread)
first_unread_message_id?: string;
hard_delete?: boolean;
// creation date of a message with last_read_message_id, formatted as Date ISO string
last_read_at?: string;
last_read_message_id?: string;
mark_messages_deleted?: boolean;
me?: OwnUserResponse<StreamChatGenerics>;
Expand All @@ -1072,9 +1077,14 @@ export type Event<StreamChatGenerics extends ExtendableGenerics = DefaultGeneric
reaction?: ReactionResponse<StreamChatGenerics>;
received_at?: string | Date;
team?: string;
// @deprecated number of all unread messages across all current user's unread channels, equals unread_count
total_unread_count?: number;
// number of all current user's channels with at least one unread message including the channel in this event
unread_channels?: number;
// number of all unread messages across all current user's unread channels
unread_count?: number;
// number of unread messages in the channel from this event (notification.mark_unread)
unread_messages?: number;
user?: UserResponse<StreamChatGenerics>;
user_id?: string;
watcher_count?: number;
Expand Down
73 changes: 68 additions & 5 deletions test/unit/channel.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,7 @@ import { getOrCreateChannelApi } from './test-utils/getOrCreateChannelApi';
import sinon from 'sinon';
import { mockChannelQueryResponse } from './test-utils/mockChannelQueryResponse';

import { StreamChat } from '../../src/client';
import { ChannelState } from '../../src';
import exp from 'constants';
import { ChannelState, StreamChat } from '../../src';

const expect = chai.expect;

Expand Down Expand Up @@ -353,6 +351,72 @@ describe('Channel _handleChannelEvent', function () {
expect(channel.state.messages.find((msg) => msg.id === quotingMessage.id).quoted_message.deleted_at).to.be.ok;
});

describe('notification.mark_unread', () => {
let initialCountUnread;
let initialReadState;
let notificationMarkUnreadEvent;
beforeEach(() => {
initialCountUnread = 0;
initialReadState = {
last_read: new Date().toISOString(),
last_read_message_id: '6',
user,
unread_messages: initialCountUnread,
};
notificationMarkUnreadEvent = {
type: 'notification.mark_unread',
created_at: new Date().toISOString(),
cid: channel.cid,
channel_id: channel.id,
channel_type: channel.type,
channel: null,
user,
first_unread_message_id: '2',
last_read_at: new Date(new Date(initialReadState.last_read).getTime() - 1000).toISOString(),
last_read_message_id: '1',
unread_messages: 5,
unread_count: 6,
total_unread_count: 6,
unread_channels: 2,
};
});

it('should update channel read state produced for current user', () => {
channel.state.unreadCount = initialCountUnread;
channel.state.read[user.id] = initialReadState;
const event = notificationMarkUnreadEvent;

channel._handleChannelEvent(event);

expect(channel.state.unreadCount).to.be.equal(event.unread_messages);
expect(new Date(channel.state.read[user.id].last_read).getTime()).to.be.equal(
new Date(event.last_read_at).getTime(),
);
expect(channel.state.read[user.id].last_read_message_id).to.be.equal(event.last_read_message_id);
expect(channel.state.read[user.id].unread_messages).to.be.equal(event.unread_messages);
});

it('should not update channel read state produced for another user or user is missing', () => {
channel.state.unreadCount = initialCountUnread;
channel.state.read[user.id] = initialReadState;
const { user: excludedUser, ...eventMissingUser } = notificationMarkUnreadEvent;
const eventWithAnotherUser = { ...notificationMarkUnreadEvent, user: { id: 'another-user' } };

[eventWithAnotherUser, eventMissingUser].forEach((event) => {
channel._handleChannelEvent(event);

expect(channel.state.unreadCount).to.be.equal(initialCountUnread);
expect(new Date(channel.state.read[user.id].last_read).getTime()).to.be.equal(
new Date(initialReadState.last_read).getTime(),
);
expect(channel.state.read[user.id].last_read_message_id).to.be.equal(
initialReadState.last_read_message_id,
);
expect(channel.state.read[user.id].unread_messages).to.be.equal(initialReadState.unread_messages);
});
});
});

it('should include unread_messages for message events from another user', () => {
channel.state.read['id'] = {
unread_messages: 2,
Expand Down Expand Up @@ -501,8 +565,7 @@ describe('Channel _handleChannelEvent', function () {

it(`should make sure that state reload doesn't wipe out existing data`, async () => {
const mock = sinon.mock(client);
const response = mockChannelQueryResponse;
mock.expects('post').returns(Promise.resolve(response));
mock.expects('post').returns(Promise.resolve(mockChannelQueryResponse));

channel.state.members = {
user: { id: 'user' },
Expand Down

0 comments on commit 4d73838

Please sign in to comment.