stream-chat 9.21.0 → 9.22.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/index.browser.js +661 -38
- package/dist/cjs/index.browser.js.map +4 -4
- package/dist/cjs/index.node.js +656 -37
- package/dist/cjs/index.node.js.map +4 -4
- package/dist/esm/index.mjs +664 -41
- package/dist/esm/index.mjs.map +4 -4
- package/dist/types/channel.d.ts +11 -2
- package/dist/types/channel_state.d.ts +6 -0
- package/dist/types/client.d.ts +17 -5
- package/dist/types/events.d.ts +4 -4
- package/dist/types/index.d.ts +1 -0
- package/dist/types/messageDelivery/MessageDeliveryReporter.d.ts +74 -0
- package/dist/types/messageDelivery/MessageReceiptsTracker.d.ts +121 -0
- package/dist/types/messageDelivery/index.d.ts +2 -0
- package/dist/types/types.d.ts +11 -4
- package/dist/types/utils.d.ts +2 -1
- package/package.json +1 -1
- package/src/channel.ts +73 -7
- package/src/channel_state.ts +147 -14
- package/src/client.ts +60 -14
- package/src/events.ts +4 -6
- package/src/index.ts +1 -0
- package/src/messageDelivery/MessageDeliveryReporter.ts +259 -0
- package/src/messageDelivery/MessageReceiptsTracker.ts +417 -0
- package/src/messageDelivery/index.ts +2 -0
- package/src/thread.ts +1 -1
- package/src/types.ts +13 -4
- package/src/utils.ts +2 -1
package/dist/types/channel.d.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { ChannelState } from './channel_state';
|
|
2
|
+
import { MessageComposer } from './messageComposer';
|
|
3
|
+
import { MessageReceiptsTracker } from './messageDelivery';
|
|
2
4
|
import type { StreamChat } from './client';
|
|
3
5
|
import type { AIState, APIResponse, AscDesc, BanUserOptions, ChannelAPIResponse, ChannelData, ChannelMemberAPIResponse, ChannelMemberResponse, ChannelQueryOptions, ChannelResponse, ChannelUpdateOptions, CreateDraftResponse, DeleteChannelAPIResponse, DraftMessagePayload, Event, EventAPIResponse, EventHandler, EventTypes, GetDraftResponse, GetMultipleMessagesAPIResponse, GetReactionsAPIResponse, GetRepliesAPIResponse, LiveLocationPayload, LocalMessage, MarkReadOptions, MarkUnreadOptions, MemberFilters, MemberSort, Message, MessageFilters, MessageOptions, MessagePaginationOptions, MessageResponse, MessageSetType, MuteChannelAPIResponse, NewMemberPayload, PartialUpdateChannel, PartialUpdateChannelAPIResponse, PartialUpdateMember, PartialUpdateMemberAPIResponse, PinnedMessagePaginationOptions, PinnedMessagesSort, PollVoteData, PushPreference, QueryChannelAPIResponse, QueryMembersOptions, Reaction, ReactionAPIResponse, SearchAPIResponse, SearchOptions, SendMessageAPIResponse, SendMessageOptions, SendReactionOptions, StaticLocationPayload, TruncateChannelAPIResponse, TruncateOptions, UpdateChannelAPIResponse, UpdateChannelOptions, UpdateLocationPayload, UserResponse } from './types';
|
|
4
6
|
import type { Role } from './permissions';
|
|
5
|
-
import { MessageComposer } from './messageComposer';
|
|
6
7
|
/**
|
|
7
8
|
* Channel - The Channel class manages it's own state.
|
|
8
9
|
*/
|
|
@@ -39,6 +40,7 @@ export declare class Channel {
|
|
|
39
40
|
disconnected: boolean;
|
|
40
41
|
push_preferences?: PushPreference;
|
|
41
42
|
readonly messageComposer: MessageComposer;
|
|
43
|
+
readonly messageReceiptsTracker: MessageReceiptsTracker;
|
|
42
44
|
/**
|
|
43
45
|
* constructor - Create a channel
|
|
44
46
|
*
|
|
@@ -440,12 +442,19 @@ export declare class Channel {
|
|
|
440
442
|
*/
|
|
441
443
|
lastMessage(): LocalMessage | undefined;
|
|
442
444
|
/**
|
|
443
|
-
* markRead - Send the mark read event for this user, only works if the `read_events` setting is enabled
|
|
445
|
+
* markRead - Send the mark read event for this user, only works if the `read_events` setting is enabled. Syncs the message delivery report candidates local state.
|
|
444
446
|
*
|
|
445
447
|
* @param {MarkReadOptions} data
|
|
446
448
|
* @return {Promise<EventAPIResponse | null>} Description
|
|
447
449
|
*/
|
|
448
450
|
markRead(data?: MarkReadOptions): Promise<EventAPIResponse | null>;
|
|
451
|
+
/**
|
|
452
|
+
* markReadRequest - Send the mark read event for this user, only works if the `read_events` setting is enabled
|
|
453
|
+
*
|
|
454
|
+
* @param {MarkReadOptions} data
|
|
455
|
+
* @return {Promise<EventAPIResponse | null>} Description
|
|
456
|
+
*/
|
|
457
|
+
markAsReadRequest(data?: MarkReadOptions): Promise<EventAPIResponse | null>;
|
|
449
458
|
/**
|
|
450
459
|
* markUnread - Mark the channel as unread from messageID, only works if the `read_events` setting is enabled
|
|
451
460
|
*
|
|
@@ -206,9 +206,15 @@ export declare class ChannelState {
|
|
|
206
206
|
* @return {ReturnType<ChannelState['formatMessage']>} Returns the message, or undefined if the message wasn't found
|
|
207
207
|
*/
|
|
208
208
|
findMessage(messageId: string, parentMessageId?: string): LocalMessage | undefined;
|
|
209
|
+
findMessageByTimestamp(timestampMs: number, parentMessageId?: string, exactTsMatch?: boolean): LocalMessage | null;
|
|
209
210
|
private switchToMessageSet;
|
|
210
211
|
private areMessageSetsOverlap;
|
|
211
212
|
private findMessageSetIndex;
|
|
213
|
+
/**
|
|
214
|
+
* Identifies the set index into which a message set would pertain if its first item's creation date corresponded to oldestTimestampMs.
|
|
215
|
+
* @param oldestTimestampMs
|
|
216
|
+
*/
|
|
217
|
+
private findMessageSetByOldestTimestamp;
|
|
212
218
|
private findTargetMessageSet;
|
|
213
219
|
}
|
|
214
220
|
export {};
|
package/dist/types/client.d.ts
CHANGED
|
@@ -7,7 +7,7 @@ import { TokenManager } from './token_manager';
|
|
|
7
7
|
import { WSConnectionFallback } from './connection_fallback';
|
|
8
8
|
import { Campaign } from './campaign';
|
|
9
9
|
import { Segment } from './segment';
|
|
10
|
-
import type { ActiveLiveLocationsAPIResponse, APIErrorResponse, APIResponse, AppSettings, AppSettingsAPIResponse, BannedUsersFilters, BannedUsersPaginationOptions, BannedUsersResponse, BannedUsersSort, BanUserOptions, BaseDeviceFields, BlockList, BlockListResponse, BlockUserAPIResponse, CampaignData, CampaignFilters, CampaignQueryOptions, CampaignResponse, CampaignSort, CastVoteAPIResponse, ChannelAPIResponse, ChannelData, ChannelFilters, ChannelMute, ChannelOptions, ChannelResponse, ChannelSort, ChannelStateOptions, CheckPushResponse, CheckSNSResponse, CheckSQSResponse, Configs, ConnectAPIResponse, CreateChannelOptions, CreateChannelResponse, CreateCommandOptions, CreateCommandResponse, CreateImportOptions, CreateImportResponse, CreateImportURLResponse, CreatePollAPIResponse, CreatePollData, CreatePollOptionAPIResponse, CreateReminderOptions, CustomPermissionOptions, DeactivateUsersOptions, DeleteCommandResponse, DeleteUserOptions, Device, DeviceIdentifier, DraftFilters, DraftSort, EndpointName, Event, EventAPIResponse, EventHandler, ExportChannelOptions, ExportChannelRequest, ExportChannelResponse, ExportChannelStatusResponse, ExportUsersRequest, ExportUsersResponse, FlagMessageResponse, FlagReportsFilters, FlagReportsPaginationOptions, FlagReportsResponse, FlagsFilters, FlagsPaginationOptions, FlagsResponse, FlagUserResponse, GetBlockedUsersAPIResponse, GetCampaignOptions, GetChannelTypeResponse, GetCommandResponse, GetHookEventsResponse, GetImportResponse, GetMessageOptions, GetPollAPIResponse, GetRateLimitsResponse, GetThreadAPIResponse, GetThreadOptions, GetUnreadCountAPIResponse, GetUnreadCountBatchAPIResponse, ListChannelResponse, ListCommandsResponse, ListImportsPaginationOptions, ListImportsResponse, LocalMessage, Logger, MarkChannelsReadOptions, MarkDeliveredOptions, MessageFilters, MessageFlagsFilters, MessageFlagsPaginationOptions, MessageFlagsResponse, MessageResponse, Mute, MuteUserOptions, MuteUserResponse, OGAttachment, OwnUserResponse, Pager, PartialMessageUpdate, PartialPollUpdate, PartialThreadUpdate, PartialUserUpdate, PermissionAPIResponse, PermissionsAPIResponse, PollAnswersAPIResponse, PollData, PollOptionData, PollSort, PollVote, PollVoteData, PollVotesAPIResponse, Product, PushPreference, PushProvider, PushProviderConfig, PushProviderID, PushProviderListResponse, PushProviderUpsertResponse, QueryDraftsResponse, QueryMessageHistoryFilters, QueryMessageHistoryOptions, QueryMessageHistoryResponse, QueryMessageHistorySort, QueryPollsFilters, QueryPollsOptions, QueryPollsResponse, QueryReactionsAPIResponse, QueryReactionsOptions, QueryRemindersOptions, QueryRemindersResponse, QuerySegmentsOptions, QuerySegmentTargetsFilter, QueryThreadsOptions, QueryVotesFilters, QueryVotesOptions, ReactionFilters, ReactionResponse, ReactionSort, ReactivateUserOptions, ReactivateUsersOptions, ReminderAPIResponse, ReviewFlagReportOptions, ReviewFlagReportResponse, SdkIdentifier, SearchAPIResponse, SearchOptions, SegmentData, SegmentResponse, SegmentTargetsResponse, SegmentType, SendFileAPIResponse, SharedLocationResponse, SortParam, StreamChatOptions, SyncOptions, SyncResponse, TaskResponse, TaskStatus, TestPushDataInput, TestSNSDataInput, TestSQSDataInput, TokenOrProvider, TranslateResponse, UnBanUserOptions, UpdateChannelTypeRequest, UpdateChannelTypeResponse, UpdateCommandOptions, UpdateCommandResponse, UpdateLocationPayload, UpdateMessageAPIResponse, UpdateMessageOptions, UpdatePollAPIResponse, UpdateReminderOptions, UpdateSegmentData, UpsertPushPreferencesResponse, UserCustomEvent, UserFilters, UserOptions, UserResponse, UserSort, VoteSort } from './types';
|
|
10
|
+
import type { ActiveLiveLocationsAPIResponse, APIErrorResponse, APIResponse, AppSettings, AppSettingsAPIResponse, BannedUsersFilters, BannedUsersPaginationOptions, BannedUsersResponse, BannedUsersSort, BanUserOptions, BaseDeviceFields, BlockList, BlockListResponse, BlockUserAPIResponse, CampaignData, CampaignFilters, CampaignQueryOptions, CampaignResponse, CampaignSort, CastVoteAPIResponse, ChannelAPIResponse, ChannelData, ChannelFilters, ChannelMute, ChannelOptions, ChannelResponse, ChannelSort, ChannelStateOptions, CheckPushResponse, CheckSNSResponse, CheckSQSResponse, Configs, ConnectAPIResponse, CreateChannelOptions, CreateChannelResponse, CreateCommandOptions, CreateCommandResponse, CreateImportOptions, CreateImportResponse, CreateImportURLResponse, CreatePollAPIResponse, CreatePollData, CreatePollOptionAPIResponse, CreateReminderOptions, CustomPermissionOptions, DeactivateUsersOptions, DeleteCommandResponse, DeleteMessageOptions, DeleteUserOptions, Device, DeviceIdentifier, DraftFilters, DraftSort, EndpointName, Event, EventAPIResponse, EventHandler, ExportChannelOptions, ExportChannelRequest, ExportChannelResponse, ExportChannelStatusResponse, ExportUsersRequest, ExportUsersResponse, FlagMessageResponse, FlagReportsFilters, FlagReportsPaginationOptions, FlagReportsResponse, FlagsFilters, FlagsPaginationOptions, FlagsResponse, FlagUserResponse, GetBlockedUsersAPIResponse, GetCampaignOptions, GetChannelTypeResponse, GetCommandResponse, GetHookEventsResponse, GetImportResponse, GetMessageOptions, GetPollAPIResponse, GetRateLimitsResponse, GetThreadAPIResponse, GetThreadOptions, GetUnreadCountAPIResponse, GetUnreadCountBatchAPIResponse, ListChannelResponse, ListCommandsResponse, ListImportsPaginationOptions, ListImportsResponse, LocalMessage, Logger, MarkChannelsReadOptions, MarkDeliveredOptions, MessageFilters, MessageFlagsFilters, MessageFlagsPaginationOptions, MessageFlagsResponse, MessageResponse, Mute, MuteUserOptions, MuteUserResponse, OGAttachment, OwnUserResponse, Pager, PartialMessageUpdate, PartialPollUpdate, PartialThreadUpdate, PartialUserUpdate, PermissionAPIResponse, PermissionsAPIResponse, PollAnswersAPIResponse, PollData, PollOptionData, PollSort, PollVote, PollVoteData, PollVotesAPIResponse, Product, PushPreference, PushProvider, PushProviderConfig, PushProviderID, PushProviderListResponse, PushProviderUpsertResponse, QueryDraftsResponse, QueryMessageHistoryFilters, QueryMessageHistoryOptions, QueryMessageHistoryResponse, QueryMessageHistorySort, QueryPollsFilters, QueryPollsOptions, QueryPollsResponse, QueryReactionsAPIResponse, QueryReactionsOptions, QueryRemindersOptions, QueryRemindersResponse, QuerySegmentsOptions, QuerySegmentTargetsFilter, QueryThreadsOptions, QueryVotesFilters, QueryVotesOptions, ReactionFilters, ReactionResponse, ReactionSort, ReactivateUserOptions, ReactivateUsersOptions, ReminderAPIResponse, ReviewFlagReportOptions, ReviewFlagReportResponse, SdkIdentifier, SearchAPIResponse, SearchOptions, SegmentData, SegmentResponse, SegmentTargetsResponse, SegmentType, SendFileAPIResponse, SharedLocationResponse, SortParam, StreamChatOptions, SyncOptions, SyncResponse, TaskResponse, TaskStatus, TestPushDataInput, TestSNSDataInput, TestSQSDataInput, TokenOrProvider, TranslateResponse, UnBanUserOptions, UpdateChannelTypeRequest, UpdateChannelTypeResponse, UpdateCommandOptions, UpdateCommandResponse, UpdateLocationPayload, UpdateMessageAPIResponse, UpdateMessageOptions, UpdatePollAPIResponse, UpdateReminderOptions, UpdateSegmentData, UpsertPushPreferencesResponse, UserCustomEvent, UserFilters, UserOptions, UserResponse, UserSort, VoteSort } from './types';
|
|
11
11
|
import { ErrorFromResponse } from './types';
|
|
12
12
|
import { InsightMetrics } from './insights';
|
|
13
13
|
import { Thread } from './thread';
|
|
@@ -16,6 +16,7 @@ import { ThreadManager } from './thread_manager';
|
|
|
16
16
|
import { PollManager } from './poll_manager';
|
|
17
17
|
import type { ChannelManagerEventHandlerOverrides, ChannelManagerOptions, QueryChannelsRequestType } from './channel_manager';
|
|
18
18
|
import { ChannelManager } from './channel_manager';
|
|
19
|
+
import { MessageDeliveryReporter } from './messageDelivery';
|
|
19
20
|
import { NotificationManager } from './notifications';
|
|
20
21
|
import { ReminderManager } from './reminders';
|
|
21
22
|
import { StateStore } from './store';
|
|
@@ -38,6 +39,7 @@ export type MessageComposerSetupState = {
|
|
|
38
39
|
};
|
|
39
40
|
export declare class StreamChat {
|
|
40
41
|
private static _instance?;
|
|
42
|
+
messageDeliveryReporter: MessageDeliveryReporter;
|
|
41
43
|
_user?: OwnUserResponse | UserResponse;
|
|
42
44
|
appSettingsPromise?: Promise<AppSettingsAPIResponse>;
|
|
43
45
|
activeChannels: {
|
|
@@ -1064,6 +1066,7 @@ export declare class StreamChat {
|
|
|
1064
1066
|
status?: string;
|
|
1065
1067
|
thread_participants?: UserResponse[];
|
|
1066
1068
|
updated_at?: string;
|
|
1069
|
+
deleted_for_me?: boolean;
|
|
1067
1070
|
} & {
|
|
1068
1071
|
quoted_message?: import("./types").MessageResponseBase;
|
|
1069
1072
|
}>;
|
|
@@ -1140,10 +1143,17 @@ export declare class StreamChat {
|
|
|
1140
1143
|
partialUpdateMessage(id: string, partialMessageObject: PartialMessageUpdate, partialUserOrUserId?: string | {
|
|
1141
1144
|
id: string;
|
|
1142
1145
|
}, options?: UpdateMessageOptions): Promise<UpdateMessageAPIResponse>;
|
|
1143
|
-
|
|
1146
|
+
/**
|
|
1147
|
+
* deleteMessage - Delete a message
|
|
1148
|
+
*
|
|
1149
|
+
* @param {string} messageID The id of the message to delete
|
|
1150
|
+
* @param {boolean | DeleteMessageOptions | undefined} [optionsOrHardDelete]
|
|
1151
|
+
* @return {Promise<APIResponse & { message: MessageResponse }>} The API response
|
|
1152
|
+
*/
|
|
1153
|
+
deleteMessage(messageID: string, optionsOrHardDelete?: DeleteMessageOptions | boolean): Promise<APIResponse & {
|
|
1144
1154
|
message: MessageResponse;
|
|
1145
1155
|
}>;
|
|
1146
|
-
_deleteMessage(messageID: string,
|
|
1156
|
+
_deleteMessage(messageID: string, optionsOrHardDelete?: DeleteMessageOptions | boolean): Promise<APIResponse & {
|
|
1147
1157
|
message: MessageResponse;
|
|
1148
1158
|
}>;
|
|
1149
1159
|
/**
|
|
@@ -1730,6 +1740,7 @@ export declare class StreamChat {
|
|
|
1730
1740
|
status?: string;
|
|
1731
1741
|
thread_participants?: UserResponse[];
|
|
1732
1742
|
updated_at?: string;
|
|
1743
|
+
deleted_for_me?: boolean;
|
|
1733
1744
|
} & {
|
|
1734
1745
|
quoted_message?: import("./types").MessageResponseBase;
|
|
1735
1746
|
}>;
|
|
@@ -1971,11 +1982,12 @@ export declare class StreamChat {
|
|
|
1971
1982
|
*/
|
|
1972
1983
|
deleteImage(url: string): Promise<APIResponse>;
|
|
1973
1984
|
/**
|
|
1974
|
-
* Send the mark delivered event for this user
|
|
1985
|
+
* Send the mark delivered event for this user
|
|
1975
1986
|
*
|
|
1976
1987
|
* @param {MarkDeliveredOptions} data
|
|
1977
1988
|
* @return {Promise<EventAPIResponse | void>} Description
|
|
1978
1989
|
*/
|
|
1979
|
-
markChannelsDelivered(data
|
|
1990
|
+
markChannelsDelivered(data: MarkDeliveredOptions): Promise<EventAPIResponse | undefined>;
|
|
1991
|
+
syncDeliveredCandidates(collections: Channel[]): void;
|
|
1980
1992
|
}
|
|
1981
1993
|
export {};
|
package/dist/types/events.d.ts
CHANGED
|
@@ -31,6 +31,7 @@ export declare const EVENT_MAP: {
|
|
|
31
31
|
'notification.mark_unread': boolean;
|
|
32
32
|
'notification.message_new': boolean;
|
|
33
33
|
'notification.mutes_updated': boolean;
|
|
34
|
+
'notification.reminder_due': boolean;
|
|
34
35
|
'notification.removed_from_channel': boolean;
|
|
35
36
|
'notification.thread_message_new': boolean;
|
|
36
37
|
'poll.closed': boolean;
|
|
@@ -41,6 +42,9 @@ export declare const EVENT_MAP: {
|
|
|
41
42
|
'reaction.deleted': boolean;
|
|
42
43
|
'reaction.new': boolean;
|
|
43
44
|
'reaction.updated': boolean;
|
|
45
|
+
'reminder.created': boolean;
|
|
46
|
+
'reminder.deleted': boolean;
|
|
47
|
+
'reminder.updated': boolean;
|
|
44
48
|
'thread.updated': boolean;
|
|
45
49
|
'typing.start': boolean;
|
|
46
50
|
'typing.stop': boolean;
|
|
@@ -64,8 +68,4 @@ export declare const EVENT_MAP: {
|
|
|
64
68
|
'capabilities.changed': boolean;
|
|
65
69
|
'live_location_sharing.started': boolean;
|
|
66
70
|
'live_location_sharing.stopped': boolean;
|
|
67
|
-
'reminder.created': boolean;
|
|
68
|
-
'reminder.updated': boolean;
|
|
69
|
-
'reminder.deleted': boolean;
|
|
70
|
-
'notification.reminder_due': boolean;
|
|
71
71
|
};
|
package/dist/types/index.d.ts
CHANGED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import type { StreamChat } from '../client';
|
|
2
|
+
import { Channel } from '../channel';
|
|
3
|
+
import { Thread } from '../thread';
|
|
4
|
+
import type { EventAPIResponse, MarkDeliveredOptions, MarkReadOptions } from '../types';
|
|
5
|
+
type MessageId = string;
|
|
6
|
+
type ChannelThreadCompositeId = string;
|
|
7
|
+
export type AnnounceDeliveryOptions = Omit<MarkDeliveredOptions, 'latest_delivered_messages'>;
|
|
8
|
+
export type MessageDeliveryReporterOptions = {
|
|
9
|
+
client: StreamChat;
|
|
10
|
+
};
|
|
11
|
+
export declare class MessageDeliveryReporter {
|
|
12
|
+
protected client: StreamChat;
|
|
13
|
+
protected deliveryReportCandidates: Map<ChannelThreadCompositeId, MessageId>;
|
|
14
|
+
protected nextDeliveryReportCandidates: Map<ChannelThreadCompositeId, MessageId>;
|
|
15
|
+
protected markDeliveredRequestPromise: Promise<EventAPIResponse | void> | null;
|
|
16
|
+
protected markDeliveredTimeout: ReturnType<typeof setTimeout> | null;
|
|
17
|
+
constructor({ client }: MessageDeliveryReporterOptions);
|
|
18
|
+
private get markDeliveredRequestInFlight();
|
|
19
|
+
private get hasTimer();
|
|
20
|
+
private get hasDeliveryCandidates();
|
|
21
|
+
/**
|
|
22
|
+
* Build latest_delivered_messages payload from an arbitrary buffer (deliveryReportCandidates / nextDeliveryReportCandidates)
|
|
23
|
+
*/
|
|
24
|
+
private confirmationsFrom;
|
|
25
|
+
private confirmationsFromDeliveryReportCandidates;
|
|
26
|
+
/**
|
|
27
|
+
* Generate candidate key for storing in the candidates buffer
|
|
28
|
+
* @param collection
|
|
29
|
+
* @private
|
|
30
|
+
*/
|
|
31
|
+
private candidateKeyFor;
|
|
32
|
+
/**
|
|
33
|
+
* Retrieve the reference to the latest message in the state that is nor read neither reported as delivered
|
|
34
|
+
* @param collection
|
|
35
|
+
*/
|
|
36
|
+
private getNextDeliveryReportCandidate;
|
|
37
|
+
/**
|
|
38
|
+
* Updates the delivery candidates buffer with the latest delivery candidates
|
|
39
|
+
* @param collection
|
|
40
|
+
*/
|
|
41
|
+
private trackDeliveredCandidate;
|
|
42
|
+
/**
|
|
43
|
+
* Removes candidate from the delivery report buffer
|
|
44
|
+
* @param collection
|
|
45
|
+
* @private
|
|
46
|
+
*/
|
|
47
|
+
private removeCandidateFor;
|
|
48
|
+
/**
|
|
49
|
+
* Records the latest message delivered for Channel or Thread instances and schedules the next report
|
|
50
|
+
* if not already scheduled and candidates exist.
|
|
51
|
+
* Should be used for WS handling (message.new) as well as for ingesting HTTP channel query results.
|
|
52
|
+
* @param collections
|
|
53
|
+
*/
|
|
54
|
+
syncDeliveredCandidates(collections: (Channel | Thread)[]): void;
|
|
55
|
+
/**
|
|
56
|
+
* Fires delivery announcement request followed by immediate delivery candidate buffer reset.
|
|
57
|
+
* @param options
|
|
58
|
+
*/
|
|
59
|
+
announceDelivery: (options?: AnnounceDeliveryOptions) => void;
|
|
60
|
+
announceDeliveryBuffered: (options?: AnnounceDeliveryOptions) => void;
|
|
61
|
+
/**
|
|
62
|
+
* Delegates the mark-read call to the Channel or Thread instance
|
|
63
|
+
* @param collection
|
|
64
|
+
* @param options
|
|
65
|
+
*/
|
|
66
|
+
markRead: (collection: Channel | Thread, options?: MarkReadOptions) => Promise<EventAPIResponse | null>;
|
|
67
|
+
/**
|
|
68
|
+
* Throttles the MessageDeliveryReporter.markRead call
|
|
69
|
+
* @param collection
|
|
70
|
+
* @param options
|
|
71
|
+
*/
|
|
72
|
+
throttledMarkRead: (collection: Channel | Thread, options?: MarkReadOptions | undefined) => void;
|
|
73
|
+
}
|
|
74
|
+
export {};
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import type { ReadResponse, UserResponse } from '../types';
|
|
2
|
+
type MessageId = string;
|
|
3
|
+
export type MsgRef = {
|
|
4
|
+
timestampMs: number;
|
|
5
|
+
msgId: MessageId;
|
|
6
|
+
};
|
|
7
|
+
export type OwnMessageReceiptsTrackerMessageLocator = (timestampMs: number) => MsgRef | null;
|
|
8
|
+
export type UserProgress = {
|
|
9
|
+
user: UserResponse;
|
|
10
|
+
lastReadRef: MsgRef;
|
|
11
|
+
lastDeliveredRef: MsgRef;
|
|
12
|
+
};
|
|
13
|
+
export type OwnMessageReceiptsTrackerOptions = {
|
|
14
|
+
locateMessage: OwnMessageReceiptsTrackerMessageLocator;
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* MessageReceiptsTracker
|
|
18
|
+
* --------------------------------
|
|
19
|
+
* Tracks **other participants’** delivery/read progress toward **own (outgoing) messages**
|
|
20
|
+
* within a **single timeline** (one channel/thread).
|
|
21
|
+
*
|
|
22
|
+
* How it works
|
|
23
|
+
* ------------
|
|
24
|
+
* - Each user has a compact progress record:
|
|
25
|
+
* - `lastReadRef`: latest message they have **read**
|
|
26
|
+
* - `lastDeliveredRef`: latest message they have **received** (always `>= lastReadRef`)
|
|
27
|
+
* - Internally keeps two arrays sorted **ascending by timestamp**:
|
|
28
|
+
* - `readSorted` (by `lastReadRef`)
|
|
29
|
+
* - `deliveredSorted` (by `lastDeliveredRef`)
|
|
30
|
+
* - Queries like “who read message M?” become a **binary search + suffix slice**.
|
|
31
|
+
*
|
|
32
|
+
* Construction
|
|
33
|
+
* ------------
|
|
34
|
+
* `new MessageReceiptsTracker({locateMessage})`
|
|
35
|
+
* - `locateMessage(timestamp) => MsgRef | null` must resolve a message ref representation - `{ timestamp, msgId }`.
|
|
36
|
+
* - If `locateMessage` returns `null`, the event is ignored (message unknown locally).
|
|
37
|
+
*
|
|
38
|
+
* Event ingestion
|
|
39
|
+
* ---------------
|
|
40
|
+
* - `ingestInitial(rows: ReadResponse[])`: Builds initial state from server snapshot.
|
|
41
|
+
* If a user’s `last_read` is ahead of `last_delivered_at`, the tracker enforces
|
|
42
|
+
* the invariant `lastDeliveredRef >= lastReadRef`.
|
|
43
|
+
* - `onMessageRead(user, readAtISO)`:
|
|
44
|
+
* Advances the user’s read; also bumps delivered to match if needed.
|
|
45
|
+
* - `onMessageDelivered(user, deliveredAtISO)`:
|
|
46
|
+
* Advances the user’s delivered to `max(currentRead, deliveredAt)`.
|
|
47
|
+
*
|
|
48
|
+
* Queries
|
|
49
|
+
* -------
|
|
50
|
+
* - `readersForMessage(msgRef) : UserResponse[]` → users with `lastReadRef >= msgRef`
|
|
51
|
+
* - `deliveredForMessage(msgRef) : UserResponse[]` → users with `lastDeliveredRef >= msgRef`
|
|
52
|
+
* - `deliveredNotReadForMessage(msgRef): UserResponse[]` → delivered but `lastReadRef < msgRef`
|
|
53
|
+
* - `usersWhoseLastReadIs : UserResponse[]` → users for whom `msgRef` is their *last read* (exact match)
|
|
54
|
+
* - `usersWhoseLastDeliveredIs : UserResponse[]` → users for whom `msgRef` is their *last delivered* (exact match)
|
|
55
|
+
* - `groupUsersByLastReadMessage : Record<MsgId, UserResponse[]> → mapping of messages to their readers
|
|
56
|
+
* - `groupUsersByLastDeliveredMessage : Record<MsgId, UserResponse[]> → mapping of messages to their receivers
|
|
57
|
+
* - `hasUserRead(msgRef, userId) : boolean`
|
|
58
|
+
* - `hasUserDelivered(msgRef, userId) : boolean`
|
|
59
|
+
*
|
|
60
|
+
* Complexity
|
|
61
|
+
* ----------
|
|
62
|
+
* - Update on read/delivered: **O(log U)** (binary search + one splice) per event, where U is count of users stored by tracker.
|
|
63
|
+
* - Query lists: **O(log U + K)** where `K` is the number of returned users (suffix length).
|
|
64
|
+
* - Memory: **O(U)** - tracker’s memory grows linearly with the number of users in the channel/thread and does not depend on the number of messages.
|
|
65
|
+
*
|
|
66
|
+
* Scope & notes
|
|
67
|
+
* -------------
|
|
68
|
+
* - One tracker instance is **scoped to a single timeline**. Instantiate per channel/thread.
|
|
69
|
+
* - Ordering is by **ascending timestamp**; ties are kept stable by inserting at the end of the
|
|
70
|
+
* equal-timestamp plateau (upper-bound insertion), preserving intuitive arrival order.
|
|
71
|
+
* - This tracker models **others’ progress toward own messages**;
|
|
72
|
+
*/
|
|
73
|
+
export declare class MessageReceiptsTracker {
|
|
74
|
+
private byUser;
|
|
75
|
+
private readSorted;
|
|
76
|
+
private deliveredSorted;
|
|
77
|
+
private locateMessage;
|
|
78
|
+
constructor({ locateMessage }: OwnMessageReceiptsTrackerOptions);
|
|
79
|
+
/** Build initial state from server snapshots (single pass + sort). */
|
|
80
|
+
ingestInitial(responses: ReadResponse[]): void;
|
|
81
|
+
/** message.delivered — user device confirmed delivery up to and including messageId. */
|
|
82
|
+
onMessageDelivered({ user, deliveredAt, lastDeliveredMessageId, }: {
|
|
83
|
+
user: UserResponse;
|
|
84
|
+
deliveredAt: string;
|
|
85
|
+
lastDeliveredMessageId?: string;
|
|
86
|
+
}): void;
|
|
87
|
+
/** message.read — user read up to and including messageId. */
|
|
88
|
+
onMessageRead({ user, readAt, lastReadMessageId, }: {
|
|
89
|
+
user: UserResponse;
|
|
90
|
+
readAt: string;
|
|
91
|
+
lastReadMessageId?: string;
|
|
92
|
+
}): void;
|
|
93
|
+
/** notification.mark_unread — user marked messages unread starting at `first_unread_message_id`.
|
|
94
|
+
* Sets lastReadRef to the event’s last_read_* values. Delivery never moves backward.
|
|
95
|
+
* The event is sent only to the user that triggered the action (own user), so we will never adjust read ref
|
|
96
|
+
* for other users - we will not see changes in the UI for other users. However, this implementation does not
|
|
97
|
+
* take into consideration this fact and is ready to handle the mark-unread event for any user.
|
|
98
|
+
*/
|
|
99
|
+
onNotificationMarkUnread({ user, lastReadAt, lastReadMessageId, }: {
|
|
100
|
+
user: UserResponse;
|
|
101
|
+
lastReadAt?: string;
|
|
102
|
+
lastReadMessageId?: string;
|
|
103
|
+
}): void;
|
|
104
|
+
/** All users who READ this message. */
|
|
105
|
+
readersForMessage(msgRef: MsgRef): UserResponse[];
|
|
106
|
+
/** All users who have it DELIVERED (includes readers). */
|
|
107
|
+
deliveredForMessage(msgRef: MsgRef): UserResponse[];
|
|
108
|
+
/** Users who delivered but have NOT read. */
|
|
109
|
+
deliveredNotReadForMessage(msgRef: MsgRef): UserResponse[];
|
|
110
|
+
/** Users for whom `msgRef` is their *last read* (exact match). */
|
|
111
|
+
usersWhoseLastReadIs(msgRef: MsgRef): UserResponse[];
|
|
112
|
+
/** Users for whom `msgRef` is their *last delivered* (exact match). */
|
|
113
|
+
usersWhoseLastDeliveredIs(msgRef: MsgRef): UserResponse[];
|
|
114
|
+
hasUserRead(msgRef: MsgRef, userId: string): boolean;
|
|
115
|
+
hasUserDelivered(msgRef: MsgRef, userId: string): boolean;
|
|
116
|
+
getUserProgress(userId: string): UserProgress | null;
|
|
117
|
+
groupUsersByLastReadMessage(): Record<MessageId, UserResponse[]>;
|
|
118
|
+
groupUsersByLastDeliveredMessage(): Record<MessageId, UserResponse[]>;
|
|
119
|
+
private ensureUser;
|
|
120
|
+
}
|
|
121
|
+
export {};
|
package/dist/types/types.d.ts
CHANGED
|
@@ -605,6 +605,7 @@ export type MessageResponseBase = MessageBase & {
|
|
|
605
605
|
status?: string;
|
|
606
606
|
thread_participants?: UserResponse[];
|
|
607
607
|
updated_at?: string;
|
|
608
|
+
deleted_for_me?: boolean;
|
|
608
609
|
};
|
|
609
610
|
export type ReactionGroupResponse = {
|
|
610
611
|
count: number;
|
|
@@ -821,7 +822,7 @@ export type BanUserOptions = UnBanUserOptions & {
|
|
|
821
822
|
ip_ban?: boolean;
|
|
822
823
|
reason?: string;
|
|
823
824
|
timeout?: number;
|
|
824
|
-
delete_messages?:
|
|
825
|
+
delete_messages?: MessageDeletionStrategy;
|
|
825
826
|
};
|
|
826
827
|
export type ChannelOptions = {
|
|
827
828
|
limit?: number;
|
|
@@ -1271,13 +1272,14 @@ export type Event = CustomEventData & {
|
|
|
1271
1272
|
ai_state?: AIState;
|
|
1272
1273
|
channel?: ChannelResponse;
|
|
1273
1274
|
channel_custom?: CustomChannelData;
|
|
1274
|
-
channel_member_count?: number;
|
|
1275
1275
|
channel_id?: string;
|
|
1276
|
+
channel_member_count?: number;
|
|
1276
1277
|
channel_type?: string;
|
|
1277
1278
|
cid?: string;
|
|
1278
1279
|
clear_history?: boolean;
|
|
1279
1280
|
connection_id?: string;
|
|
1280
1281
|
created_at?: string;
|
|
1282
|
+
deleted_for_me?: boolean;
|
|
1281
1283
|
draft?: DraftResponse;
|
|
1282
1284
|
first_unread_message_id?: string;
|
|
1283
1285
|
hard_delete?: boolean;
|
|
@@ -2627,13 +2629,18 @@ export type CustomCheckFlag = {
|
|
|
2627
2629
|
labels?: string[];
|
|
2628
2630
|
reason?: string;
|
|
2629
2631
|
};
|
|
2630
|
-
export type
|
|
2632
|
+
export type MessageDeletionStrategy = 'soft' | 'hard';
|
|
2633
|
+
export type DeleteMessagesOptions = MessageDeletionStrategy;
|
|
2634
|
+
export type DeleteMessageOptions = {
|
|
2635
|
+
deleteForMe?: boolean;
|
|
2636
|
+
hardDelete?: boolean;
|
|
2637
|
+
};
|
|
2631
2638
|
export type SubmitActionOptions = {
|
|
2632
2639
|
ban?: {
|
|
2633
2640
|
channel_ban_only?: boolean;
|
|
2634
2641
|
reason?: string;
|
|
2635
2642
|
timeout?: number;
|
|
2636
|
-
delete_messages?:
|
|
2643
|
+
delete_messages?: MessageDeletionStrategy;
|
|
2637
2644
|
};
|
|
2638
2645
|
delete_message?: {
|
|
2639
2646
|
hard_delete?: boolean;
|
package/dist/types/utils.d.ts
CHANGED
|
@@ -136,6 +136,7 @@ export declare const toDeletedMessage: ({ message, deletedAt, hardDelete, }: {
|
|
|
136
136
|
shadowed?: boolean | undefined;
|
|
137
137
|
shared_location?: import("./types").SharedLocationResponse | undefined;
|
|
138
138
|
thread_participants?: UserResponse[] | undefined;
|
|
139
|
+
deleted_for_me?: boolean | undefined;
|
|
139
140
|
created_at: Date;
|
|
140
141
|
pinned_at: Date | null;
|
|
141
142
|
status: string;
|
|
@@ -212,7 +213,7 @@ export declare const debounce: <T extends (...args: any[]) => any>(fn: T, timeou
|
|
|
212
213
|
leading?: boolean;
|
|
213
214
|
trailing?: boolean;
|
|
214
215
|
}) => DebouncedFunc<T>;
|
|
215
|
-
export declare const throttle: <T extends (...args:
|
|
216
|
+
export declare const throttle: <T extends (...args: any[]) => any>(fn: T, timeout?: number, { leading, trailing }?: {
|
|
216
217
|
leading?: boolean;
|
|
217
218
|
trailing?: boolean;
|
|
218
219
|
}) => (...args: Parameters<T>) => void;
|
package/package.json
CHANGED
package/src/channel.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { ChannelState } from './channel_state';
|
|
2
|
+
import { MessageComposer } from './messageComposer';
|
|
3
|
+
import { MessageReceiptsTracker } from './messageDelivery';
|
|
2
4
|
import {
|
|
3
5
|
generateChannelTempCid,
|
|
4
6
|
logChatPromiseExecution,
|
|
@@ -74,7 +76,6 @@ import type {
|
|
|
74
76
|
} from './types';
|
|
75
77
|
import type { Role } from './permissions';
|
|
76
78
|
import type { CustomChannelData } from './custom_types';
|
|
77
|
-
import { MessageComposer } from './messageComposer';
|
|
78
79
|
|
|
79
80
|
/**
|
|
80
81
|
* Channel - The Channel class manages it's own state.
|
|
@@ -110,6 +111,7 @@ export class Channel {
|
|
|
110
111
|
disconnected: boolean;
|
|
111
112
|
push_preferences?: PushPreference;
|
|
112
113
|
public readonly messageComposer: MessageComposer;
|
|
114
|
+
public readonly messageReceiptsTracker: MessageReceiptsTracker;
|
|
113
115
|
|
|
114
116
|
/**
|
|
115
117
|
* constructor - Create a channel
|
|
@@ -158,6 +160,13 @@ export class Channel {
|
|
|
158
160
|
client: this._client,
|
|
159
161
|
compositionContext: this,
|
|
160
162
|
});
|
|
163
|
+
|
|
164
|
+
this.messageReceiptsTracker = new MessageReceiptsTracker({
|
|
165
|
+
locateMessage: (timestampMs) => {
|
|
166
|
+
const msg = this.state.findMessageByTimestamp(timestampMs);
|
|
167
|
+
return msg && { timestampMs, msgId: msg.id };
|
|
168
|
+
},
|
|
169
|
+
});
|
|
161
170
|
}
|
|
162
171
|
|
|
163
172
|
/**
|
|
@@ -1131,16 +1140,26 @@ export class Channel {
|
|
|
1131
1140
|
}
|
|
1132
1141
|
|
|
1133
1142
|
/**
|
|
1134
|
-
* markRead - Send the mark read event for this user, only works if the `read_events` setting is enabled
|
|
1143
|
+
* markRead - Send the mark read event for this user, only works if the `read_events` setting is enabled. Syncs the message delivery report candidates local state.
|
|
1135
1144
|
*
|
|
1136
1145
|
* @param {MarkReadOptions} data
|
|
1137
1146
|
* @return {Promise<EventAPIResponse | null>} Description
|
|
1138
1147
|
*/
|
|
1139
1148
|
async markRead(data: MarkReadOptions = {}) {
|
|
1149
|
+
return await this.getClient().messageDeliveryReporter.markRead(this, data);
|
|
1150
|
+
}
|
|
1151
|
+
|
|
1152
|
+
/**
|
|
1153
|
+
* markReadRequest - Send the mark read event for this user, only works if the `read_events` setting is enabled
|
|
1154
|
+
*
|
|
1155
|
+
* @param {MarkReadOptions} data
|
|
1156
|
+
* @return {Promise<EventAPIResponse | null>} Description
|
|
1157
|
+
*/
|
|
1158
|
+
async markAsReadRequest(data: MarkReadOptions = {}) {
|
|
1140
1159
|
this._checkInitialized();
|
|
1141
1160
|
|
|
1142
1161
|
if (!this.getConfig()?.read_events && !this.getClient()._isUsingServerAuth()) {
|
|
1143
|
-
return
|
|
1162
|
+
return null;
|
|
1144
1163
|
}
|
|
1145
1164
|
|
|
1146
1165
|
return await this.getClient().post<EventAPIResponse>(this._channelURL() + '/read', {
|
|
@@ -1554,6 +1573,7 @@ export class Channel {
|
|
|
1554
1573
|
{ method: 'upsertChannels' },
|
|
1555
1574
|
);
|
|
1556
1575
|
|
|
1576
|
+
this.getClient().syncDeliveredCandidates([this]);
|
|
1557
1577
|
return state;
|
|
1558
1578
|
}
|
|
1559
1579
|
|
|
@@ -1874,18 +1894,50 @@ export class Channel {
|
|
|
1874
1894
|
break;
|
|
1875
1895
|
case 'message.read':
|
|
1876
1896
|
if (event.user?.id && event.created_at) {
|
|
1897
|
+
const previousReadState = channelState.read[event.user.id];
|
|
1877
1898
|
channelState.read[event.user.id] = {
|
|
1899
|
+
// in case we already have delivery information
|
|
1900
|
+
...previousReadState,
|
|
1878
1901
|
last_read: new Date(event.created_at),
|
|
1879
1902
|
last_read_message_id: event.last_read_message_id,
|
|
1880
1903
|
user: event.user,
|
|
1881
1904
|
unread_messages: 0,
|
|
1882
1905
|
};
|
|
1906
|
+
this.messageReceiptsTracker.onMessageRead({
|
|
1907
|
+
user: event.user,
|
|
1908
|
+
readAt: event.created_at,
|
|
1909
|
+
lastReadMessageId: event.last_read_message_id,
|
|
1910
|
+
});
|
|
1911
|
+
const client = this.getClient();
|
|
1883
1912
|
|
|
1884
|
-
|
|
1913
|
+
const isOwnEvent = event.user?.id === client.user?.id;
|
|
1914
|
+
|
|
1915
|
+
if (isOwnEvent) {
|
|
1885
1916
|
channelState.unreadCount = 0;
|
|
1917
|
+
client.syncDeliveredCandidates([this]);
|
|
1886
1918
|
}
|
|
1887
1919
|
}
|
|
1888
1920
|
break;
|
|
1921
|
+
case 'message.delivered':
|
|
1922
|
+
// todo: update also on thread
|
|
1923
|
+
if (event.user?.id && event.created_at) {
|
|
1924
|
+
const previousReadState = channelState.read[event.user.id];
|
|
1925
|
+
channelState.read[event.user.id] = {
|
|
1926
|
+
...previousReadState,
|
|
1927
|
+
last_delivered_at: event.last_delivered_at
|
|
1928
|
+
? new Date(event.last_delivered_at)
|
|
1929
|
+
: undefined,
|
|
1930
|
+
last_delivered_message_id: event.last_delivered_message_id,
|
|
1931
|
+
user: event.user,
|
|
1932
|
+
};
|
|
1933
|
+
|
|
1934
|
+
this.messageReceiptsTracker.onMessageDelivered({
|
|
1935
|
+
user: event.user,
|
|
1936
|
+
deliveredAt: event.created_at,
|
|
1937
|
+
lastDeliveredMessageId: event.last_delivered_message_id,
|
|
1938
|
+
});
|
|
1939
|
+
}
|
|
1940
|
+
break;
|
|
1889
1941
|
case 'user.watching.start':
|
|
1890
1942
|
case 'user.updated':
|
|
1891
1943
|
if (event.user?.id) {
|
|
@@ -1921,8 +1973,9 @@ export class Channel {
|
|
|
1921
1973
|
break;
|
|
1922
1974
|
case 'message.new':
|
|
1923
1975
|
if (event.message) {
|
|
1976
|
+
const client = this.getClient();
|
|
1924
1977
|
/* if message belongs to current user, always assume timestamp is changed to filter it out and add again to avoid duplication */
|
|
1925
|
-
const ownMessage = event.user?.id ===
|
|
1978
|
+
const ownMessage = event.user?.id === client.user?.id;
|
|
1926
1979
|
const isThreadMessage =
|
|
1927
1980
|
event.message.parent_id && !event.message.show_in_channel;
|
|
1928
1981
|
|
|
@@ -1947,6 +2000,8 @@ export class Channel {
|
|
|
1947
2000
|
last_read: new Date(event.created_at as string),
|
|
1948
2001
|
user: event.user,
|
|
1949
2002
|
unread_messages: 0,
|
|
2003
|
+
last_delivered_at: new Date(event.created_at as string),
|
|
2004
|
+
last_delivered_message_id: event.message.id,
|
|
1950
2005
|
};
|
|
1951
2006
|
} else {
|
|
1952
2007
|
channelState.read[userId].unread_messages += 1;
|
|
@@ -1957,6 +2012,8 @@ export class Channel {
|
|
|
1957
2012
|
if (this._countMessageAsUnread(event.message)) {
|
|
1958
2013
|
channelState.unreadCount = channelState.unreadCount + 1;
|
|
1959
2014
|
}
|
|
2015
|
+
|
|
2016
|
+
client.syncDeliveredCandidates([this]);
|
|
1960
2017
|
}
|
|
1961
2018
|
break;
|
|
1962
2019
|
case 'message.updated':
|
|
@@ -2057,11 +2114,13 @@ export class Channel {
|
|
|
2057
2114
|
break;
|
|
2058
2115
|
case 'notification.mark_unread': {
|
|
2059
2116
|
const ownMessage = event.user?.id === this.getClient().user?.id;
|
|
2060
|
-
if (!
|
|
2117
|
+
if (!ownMessage || !event.user) break;
|
|
2061
2118
|
|
|
2062
2119
|
const unreadCount = event.unread_messages ?? 0;
|
|
2063
|
-
|
|
2120
|
+
const currentState = channelState.read[event.user.id];
|
|
2064
2121
|
channelState.read[event.user.id] = {
|
|
2122
|
+
// keep the message delivery info
|
|
2123
|
+
...currentState,
|
|
2065
2124
|
first_unread_message_id: event.first_unread_message_id,
|
|
2066
2125
|
last_read: new Date(event.last_read_at as string),
|
|
2067
2126
|
last_read_message_id: event.last_read_message_id,
|
|
@@ -2070,6 +2129,11 @@ export class Channel {
|
|
|
2070
2129
|
};
|
|
2071
2130
|
|
|
2072
2131
|
channelState.unreadCount = unreadCount;
|
|
2132
|
+
this.messageReceiptsTracker.onNotificationMarkUnread({
|
|
2133
|
+
user: event.user,
|
|
2134
|
+
lastReadAt: event.last_read_at,
|
|
2135
|
+
lastReadMessageId: event.last_read_message_id,
|
|
2136
|
+
});
|
|
2073
2137
|
break;
|
|
2074
2138
|
}
|
|
2075
2139
|
case 'channel.updated':
|
|
@@ -2286,6 +2350,8 @@ export class Channel {
|
|
|
2286
2350
|
this.state.unreadCount = this.state.read[read.user.id].unread_messages;
|
|
2287
2351
|
}
|
|
2288
2352
|
}
|
|
2353
|
+
|
|
2354
|
+
this.messageReceiptsTracker.ingestInitial(state.read);
|
|
2289
2355
|
}
|
|
2290
2356
|
|
|
2291
2357
|
return {
|