stream-chat 9.50.3 → 10.0.0-rc.2
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 +19798 -14472
- package/dist/cjs/index.browser.js.map +4 -4
- package/dist/cjs/index.node.js +19817 -14482
- package/dist/cjs/index.node.js.map +4 -4
- package/dist/esm/index.mjs +19795 -14469
- package/dist/esm/index.mjs.map +4 -4
- package/dist/types/ChannelManager.d.ts +221 -0
- package/dist/types/EventHandlerPipeline.d.ts +104 -0
- package/dist/types/api-client.d.ts +26 -0
- package/dist/types/campaign.d.ts +0 -44
- package/dist/types/channel.d.ts +454 -479
- package/dist/types/channel_batch_updater.d.ts +0 -93
- package/dist/types/channel_state.d.ts +53 -201
- package/dist/types/client.d.ts +451 -1920
- package/dist/types/client_state.d.ts +4 -4
- package/dist/types/configuration/InstanceConfigurationService.d.ts +26 -0
- package/dist/types/configuration/index.d.ts +1 -0
- package/dist/types/configuration/types.d.ts +53 -0
- package/dist/types/connection.d.ts +49 -41
- package/dist/types/connection_fallback.d.ts +6 -4
- package/dist/types/constants.d.ts +0 -4
- package/dist/types/custom_types.d.ts +2 -0
- package/dist/types/entityStore/EntityStore.d.ts +111 -0
- package/dist/types/entityStore/StoreBackedItemIndex.d.ts +60 -0
- package/dist/types/entityStore/applyReactionLocally.d.ts +26 -0
- package/dist/types/entityStore/index.d.ts +2 -0
- package/dist/types/errors.d.ts +10 -2
- package/dist/types/gen/chat/ChannelApi.d.ts +45 -0
- package/dist/types/gen/chat/ChatApi.d.ts +334 -0
- package/dist/types/gen/model-decoders/decoders.d.ts +3 -0
- package/dist/types/gen/model-decoders/event-decoder-mapping.d.ts +6 -0
- package/dist/types/gen/models/index.d.ts +8836 -0
- package/dist/types/gen/moderation/ModerationApi.d.ts +50 -0
- package/dist/types/gen-imports.d.ts +3 -0
- package/dist/types/index.d.ts +8 -7
- package/dist/types/insights.d.ts +9 -8
- package/dist/types/logger.d.ts +9 -0
- package/dist/types/messageComposer/LocationComposer.d.ts +7 -4
- package/dist/types/messageComposer/attachmentIdentity.d.ts +2 -2
- package/dist/types/messageComposer/attachmentManager.d.ts +8 -4
- package/dist/types/messageComposer/configuration/types.d.ts +11 -11
- package/dist/types/messageComposer/linkPreviewsManager.d.ts +25 -13
- package/dist/types/messageComposer/messageComposer.d.ts +10 -10
- package/dist/types/messageComposer/middleware/messageComposer/types.d.ts +3 -3
- package/dist/types/messageComposer/middleware/pollComposer/types.d.ts +4 -7
- package/dist/types/messageComposer/middleware/textComposer/commandUtils.d.ts +3 -3
- package/dist/types/messageComposer/middleware/textComposer/commands.d.ts +7 -7
- package/dist/types/messageComposer/middleware/textComposer/mentions.d.ts +218 -4
- package/dist/types/messageComposer/middleware/textComposer/types.d.ts +9 -4
- package/dist/types/messageComposer/pollComposer.d.ts +5 -4
- package/dist/types/messageComposer/textComposer.d.ts +8 -3
- package/dist/types/messageComposer/types.d.ts +6 -28
- package/dist/types/messageDelivery/MessageDeliveryReporter.d.ts +40 -25
- package/dist/types/messageDelivery/MessageReceiptsTracker.d.ts +53 -12
- package/dist/types/messageOperations/MessageOperationStatePolicy.d.ts +19 -0
- package/dist/types/messageOperations/MessageOperations.d.ts +17 -0
- package/dist/types/messageOperations/index.d.ts +3 -0
- package/dist/types/messageOperations/types.d.ts +49 -0
- package/dist/types/moderation.d.ts +21 -203
- package/dist/types/notifications/types.d.ts +8 -7
- package/dist/types/offline-support/offline_support_api.d.ts +111 -87
- package/dist/types/offline-support/offline_sync_manager.d.ts +0 -7
- package/dist/types/offline-support/types.d.ts +19 -23
- package/dist/types/offline-support/util.d.ts +4 -2
- package/dist/types/pagination/FilterBuilder.d.ts +1 -1
- package/dist/types/pagination/ItemIndex.d.ts +25 -0
- package/dist/types/pagination/cursorDerivation/createdAtAroundPaginationFlags.d.ts +9 -0
- package/dist/types/pagination/cursorDerivation/idAroundPaginationFlags.d.ts +6 -0
- package/dist/types/pagination/cursorDerivation/index.d.ts +1 -0
- package/dist/types/pagination/cursorDerivation/linearPaginationFlags.d.ts +6 -0
- package/dist/types/pagination/filterCompiler.d.ts +7 -0
- package/dist/types/pagination/index.d.ts +1 -3
- package/dist/types/pagination/paginators/BasePaginator.d.ts +848 -0
- package/dist/types/pagination/paginators/ChannelPaginator.d.ts +212 -0
- package/dist/types/pagination/paginators/MessageIntervalPaginator.d.ts +282 -0
- package/dist/types/pagination/paginators/MessagePaginator.d.ts +208 -0
- package/dist/types/pagination/paginators/PinnedMessagePaginator.d.ts +39 -0
- package/dist/types/pagination/paginators/ReminderPaginator.d.ts +19 -0
- package/dist/types/pagination/paginators/UserGroupPaginator.d.ts +24 -0
- package/dist/types/pagination/paginators/index.d.ts +7 -0
- package/dist/types/pagination/paginators/stateThrottling.d.ts +2 -0
- package/dist/types/pagination/sortCompiler.d.ts +49 -0
- package/dist/types/pagination/types.normalization.d.ts +11 -0
- package/dist/types/pagination/utility.normalization.d.ts +18 -0
- package/dist/types/pagination/utility.queryChannel.d.ts +26 -0
- package/dist/types/pagination/utility.search.d.ts +13 -0
- package/dist/types/permissions.d.ts +1 -1
- package/dist/types/poll.d.ts +29 -32
- package/dist/types/poll_manager.d.ts +2 -2
- package/dist/types/reminders/Reminder.d.ts +2 -5
- package/dist/types/reminders/ReminderManager.d.ts +2 -9
- package/dist/types/search/MessageSearchSource.d.ts +4 -4
- package/dist/types/search/UserSearchSource.d.ts +1 -1
- package/dist/types/search/types.d.ts +3 -3
- package/dist/types/segment.d.ts +0 -34
- package/dist/types/signing.d.ts +105 -65
- package/dist/types/store.d.ts +1 -0
- package/dist/types/thread.d.ts +70 -34
- package/dist/types/thread_manager.d.ts +3 -3
- package/dist/types/token_manager.d.ts +15 -10
- package/dist/types/types.d.ts +197 -3404
- package/dist/types/utils/FixedSizeQueueCache.d.ts +12 -6
- package/dist/types/utils/WithSubscriptions.d.ts +2 -1
- package/dist/types/utils/concurrency.d.ts +4 -4
- package/dist/types/utils/mergeWith/mergeWith.d.ts +3 -3
- package/dist/types/utils/mergeWith/mergeWithCore.d.ts +8 -3
- package/dist/types/utils/retryable.d.ts +34 -0
- package/dist/types/utils/throttling/throttle.d.ts +36 -0
- package/dist/types/utils.d.ts +100 -194
- package/package.json +7 -3
- package/src/ChannelManager.ts +724 -0
- package/src/CooldownTimer.ts +2 -2
- package/src/EventHandlerPipeline.ts +228 -0
- package/src/LiveLocationManager.ts +12 -11
- package/src/api-client.ts +275 -0
- package/src/campaign.ts +1 -77
- package/src/channel.ts +1344 -1282
- package/src/channel_batch_updater.ts +1 -211
- package/src/channel_state.ts +144 -1013
- package/src/client.ts +936 -3995
- package/src/client_state.ts +4 -4
- package/src/configuration/InstanceConfigurationService.ts +73 -0
- package/src/configuration/index.ts +1 -0
- package/src/configuration/types.ts +81 -0
- package/src/connection.ts +145 -94
- package/src/connection_fallback.ts +29 -27
- package/src/constants.ts +1 -5
- package/src/custom_types.ts +4 -1
- package/src/entityStore/EntityStore.ts +208 -0
- package/src/entityStore/StoreBackedItemIndex.ts +115 -0
- package/src/entityStore/applyReactionLocally.ts +113 -0
- package/src/entityStore/index.ts +2 -0
- package/src/errors.ts +14 -2
- package/src/gen/chat/ChannelApi.ts +275 -0
- package/src/gen/chat/ChatApi.ts +2554 -0
- package/src/gen/model-decoders/decoders.ts +2680 -0
- package/src/gen/model-decoders/event-decoder-mapping.ts +198 -0
- package/src/gen/models/index.ts +12673 -0
- package/src/gen/moderation/ModerationApi.ts +607 -0
- package/src/gen-imports.ts +3 -0
- package/src/index.ts +11 -12
- package/src/insights.ts +6 -5
- package/src/logger.ts +28 -0
- package/src/messageComposer/LocationComposer.ts +9 -5
- package/src/messageComposer/MessageComposerEffectHandlers.ts +1 -0
- package/src/messageComposer/attachmentIdentity.ts +16 -9
- package/src/messageComposer/attachmentManager.ts +13 -8
- package/src/messageComposer/configuration/types.ts +11 -11
- package/src/messageComposer/linkPreviewsManager.ts +9 -10
- package/src/messageComposer/messageComposer.ts +57 -53
- package/src/messageComposer/middleware/messageComposer/attachments.ts +1 -2
- package/src/messageComposer/middleware/messageComposer/cleanData.ts +1 -1
- package/src/messageComposer/middleware/messageComposer/compositionValidation.ts +2 -2
- package/src/messageComposer/middleware/messageComposer/messageComposerState.ts +2 -2
- package/src/messageComposer/middleware/messageComposer/sharedLocation.ts +4 -3
- package/src/messageComposer/middleware/messageComposer/textComposer.ts +2 -2
- package/src/messageComposer/middleware/messageComposer/types.ts +3 -4
- package/src/messageComposer/middleware/messageComposer/userDataInjection.ts +9 -5
- package/src/messageComposer/middleware/pollComposer/state.ts +0 -2
- package/src/messageComposer/middleware/pollComposer/types.ts +4 -7
- package/src/messageComposer/middleware/textComposer/TextComposerMiddlewareExecutor.ts +10 -1
- package/src/messageComposer/middleware/textComposer/commandEffects.ts +4 -4
- package/src/messageComposer/middleware/textComposer/commandUtils.ts +3 -6
- package/src/messageComposer/middleware/textComposer/commands.ts +3 -3
- package/src/messageComposer/middleware/textComposer/mentionUtils.ts +1 -1
- package/src/messageComposer/middleware/textComposer/mentions.ts +31 -15
- package/src/messageComposer/middleware/textComposer/types.ts +9 -4
- package/src/messageComposer/pollComposer.ts +6 -8
- package/src/messageComposer/textComposer.ts +27 -2
- package/src/messageComposer/types.ts +6 -28
- package/src/messageDelivery/MessageDeliveryReporter.ts +101 -45
- package/src/messageDelivery/MessageReceiptsTracker.ts +294 -21
- package/src/messageOperations/MessageOperationStatePolicy.ts +77 -0
- package/src/messageOperations/MessageOperations.ts +212 -0
- package/src/messageOperations/index.ts +10 -0
- package/src/messageOperations/types.ts +64 -0
- package/src/moderation.ts +38 -431
- package/src/notifications/types.ts +8 -7
- package/src/offline-support/offline_support_api.ts +200 -133
- package/src/offline-support/offline_sync_manager.ts +23 -44
- package/src/offline-support/types.ts +23 -26
- package/src/offline-support/util.ts +5 -4
- package/src/pagination/FilterBuilder.ts +4 -1
- package/src/pagination/ItemIndex.ts +25 -0
- package/src/pagination/cursorDerivation/createdAtAroundPaginationFlags.ts +73 -0
- package/src/pagination/cursorDerivation/idAroundPaginationFlags.ts +53 -0
- package/src/pagination/cursorDerivation/index.ts +1 -0
- package/src/pagination/cursorDerivation/linearPaginationFlags.ts +86 -0
- package/src/pagination/filterCompiler.ts +196 -0
- package/src/pagination/index.ts +1 -3
- package/src/pagination/paginators/BasePaginator.ts +2859 -0
- package/src/pagination/paginators/ChannelPaginator.ts +729 -0
- package/src/pagination/paginators/MessageIntervalPaginator.ts +1116 -0
- package/src/pagination/paginators/MessagePaginator.ts +508 -0
- package/src/pagination/paginators/PinnedMessagePaginator.ts +108 -0
- package/src/pagination/paginators/ReminderPaginator.ts +107 -0
- package/src/pagination/paginators/UserGroupPaginator.ts +127 -0
- package/src/pagination/paginators/index.ts +7 -0
- package/src/pagination/paginators/stateThrottling.ts +31 -0
- package/src/pagination/sortCompiler.ts +193 -0
- package/src/pagination/types.normalization.ts +14 -0
- package/src/pagination/utility.normalization.ts +106 -0
- package/src/pagination/utility.queryChannel.ts +84 -0
- package/src/pagination/utility.search.ts +80 -0
- package/src/permissions.ts +7 -8
- package/src/poll.ts +101 -110
- package/src/poll_manager.ts +14 -8
- package/src/reminders/Reminder.ts +2 -5
- package/src/reminders/ReminderManager.ts +19 -21
- package/src/search/BaseSearchSource.ts +0 -3
- package/src/search/ChannelMemberSearchSource.ts +7 -1
- package/src/search/ChannelSearchSource.ts +10 -3
- package/src/search/MessageSearchSource.ts +29 -30
- package/src/search/UserSearchSource.ts +12 -8
- package/src/search/types.ts +3 -4
- package/src/segment.ts +1 -95
- package/src/signing.ts +107 -67
- package/src/store.ts +2 -1
- package/src/thread.ts +562 -238
- package/src/thread_manager.ts +37 -16
- package/src/token_manager.ts +21 -10
- package/src/types.ts +364 -4533
- package/src/uploadManager.ts +8 -0
- package/src/utils/FixedSizeQueueCache.ts +12 -6
- package/src/utils/WithSubscriptions.ts +2 -1
- package/src/utils/concurrency.ts +4 -4
- package/src/utils/mergeWith/mergeWith.ts +3 -3
- package/src/utils/mergeWith/mergeWithCore.ts +140 -164
- package/src/utils/mergeWith/mergeWithDiff.ts +3 -3
- package/src/utils/retryable.ts +117 -0
- package/src/utils/throttling/throttle.ts +130 -0
- package/src/utils.ts +208 -754
- package/dist/types/channel_manager.d.ts +0 -122
- package/dist/types/events.d.ts +0 -72
- package/dist/types/pagination/BasePaginator.d.ts +0 -69
- package/dist/types/pagination/ReminderPaginator.d.ts +0 -16
- package/dist/types/pagination/UserGroupPaginator.d.ts +0 -21
- package/src/channel_manager.ts +0 -830
- package/src/events.ts +0 -75
- package/src/pagination/BasePaginator.ts +0 -184
- package/src/pagination/ReminderPaginator.ts +0 -56
- package/src/pagination/UserGroupPaginator.ts +0 -93
package/src/channel_state.ts
CHANGED
|
@@ -4,20 +4,11 @@ import type {
|
|
|
4
4
|
Event,
|
|
5
5
|
LocalMessage,
|
|
6
6
|
MessageResponse,
|
|
7
|
-
MessageResponseBase,
|
|
8
|
-
MessageSet,
|
|
9
|
-
MessageSetType,
|
|
10
7
|
PendingMessageResponse,
|
|
11
|
-
ReactionResponse,
|
|
12
8
|
UserResponse,
|
|
13
9
|
} from './types';
|
|
14
|
-
import {
|
|
15
|
-
|
|
16
|
-
addToMessageList,
|
|
17
|
-
formatMessage,
|
|
18
|
-
isBlockedMessage,
|
|
19
|
-
} from './utils';
|
|
20
|
-
import { DEFAULT_MESSAGE_SET_PAGINATION } from './constants';
|
|
10
|
+
import { formatMessage } from './utils';
|
|
11
|
+
import { StateStore } from './store';
|
|
21
12
|
|
|
22
13
|
type ChannelReadStatus = Record<
|
|
23
14
|
string,
|
|
@@ -32,790 +23,222 @@ type ChannelReadStatus = Record<
|
|
|
32
23
|
}
|
|
33
24
|
>;
|
|
34
25
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
newestMessageA: new Date(a[0]?.created_at ?? 0),
|
|
40
|
-
oldestMessageA: new Date(a.slice(-1)[0]?.created_at ?? 0),
|
|
41
|
-
newestMessageB: new Date(b[0]?.created_at ?? 0),
|
|
42
|
-
oldestMessageB: new Date(b.slice(-1)[0]?.created_at ?? 0),
|
|
43
|
-
});
|
|
26
|
+
export type WatcherState = {
|
|
27
|
+
watcherCount: number;
|
|
28
|
+
watchers: Record<string, UserResponse>;
|
|
29
|
+
};
|
|
44
30
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
messageSetBounds(a, b);
|
|
48
|
-
return newestMessageA >= newestMessageB && oldestMessageB >= oldestMessageA;
|
|
31
|
+
export type TypingUsersState = {
|
|
32
|
+
typing: Record<string, Event>;
|
|
49
33
|
};
|
|
50
34
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
messageSetBounds(a, b);
|
|
54
|
-
return (
|
|
55
|
-
oldestMessageA < oldestMessageB &&
|
|
56
|
-
oldestMessageB < newestMessageA &&
|
|
57
|
-
newestMessageA < newestMessageB
|
|
58
|
-
);
|
|
35
|
+
export type ReadState = {
|
|
36
|
+
read: ChannelReadStatus;
|
|
59
37
|
};
|
|
60
38
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
39
|
+
export type MutedUsersState = {
|
|
40
|
+
mutedUsers: Array<UserResponse>;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export type MembersState = {
|
|
44
|
+
members: Record<string, ChannelMemberResponse>;
|
|
45
|
+
memberCount: number;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
export type OwnCapabilitiesState = {
|
|
49
|
+
ownCapabilities: string[];
|
|
50
|
+
};
|
|
66
51
|
|
|
67
52
|
/**
|
|
68
53
|
* ChannelState - A container class for the channel state.
|
|
69
54
|
*/
|
|
70
55
|
export class ChannelState {
|
|
71
56
|
_channel: Channel;
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
57
|
+
readonly watcherStore: StateStore<WatcherState>;
|
|
58
|
+
readonly typingStore: StateStore<TypingUsersState>;
|
|
59
|
+
readonly readStore: StateStore<ReadState>;
|
|
60
|
+
readonly membersStore: StateStore<MembersState>;
|
|
61
|
+
readonly ownCapabilitiesStore: StateStore<OwnCapabilitiesState>;
|
|
62
|
+
// todo: is this actually used somewhere?
|
|
63
|
+
readonly mutedUsersStore: StateStore<MutedUsersState>;
|
|
76
64
|
pending_messages: Array<PendingMessageResponse>;
|
|
77
|
-
threads: Record<string, Array<ReturnType<ChannelState['formatMessage']>>>;
|
|
78
|
-
mutedUsers: Array<UserResponse>;
|
|
79
|
-
watchers: Record<string, UserResponse>;
|
|
80
|
-
members: Record<string, ChannelMemberResponse>;
|
|
81
65
|
unreadCount: number;
|
|
82
66
|
membership: ChannelMemberResponse;
|
|
83
|
-
last_message_at: Date | null;
|
|
84
|
-
/**
|
|
85
|
-
* Flag which indicates if channel state contain latest/recent messages or no.
|
|
86
|
-
* This flag should be managed by UI sdks using a setter - setIsUpToDate.
|
|
87
|
-
* When false, any new message (received by websocket event - message.new) will not
|
|
88
|
-
* be pushed on to message list.
|
|
89
|
-
*/
|
|
90
|
-
isUpToDate: boolean;
|
|
91
|
-
/**
|
|
92
|
-
* Disjoint lists of messages
|
|
93
|
-
* Users can jump in the message list (with searching) and this can result in disjoint lists of messages
|
|
94
|
-
* The state manages these lists and merges them when lists overlap
|
|
95
|
-
* The messages array contains the currently active set
|
|
96
|
-
*/
|
|
97
|
-
messageSets: MessageSet[] = [];
|
|
98
67
|
|
|
99
68
|
constructor(channel: Channel) {
|
|
100
69
|
this._channel = channel;
|
|
101
|
-
this.
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
this.
|
|
106
|
-
|
|
107
|
-
|
|
70
|
+
this.watcherStore = new StateStore<WatcherState>({
|
|
71
|
+
watcherCount: 0,
|
|
72
|
+
watchers: {},
|
|
73
|
+
});
|
|
74
|
+
this.typingStore = new StateStore<TypingUsersState>({
|
|
75
|
+
typing: {},
|
|
76
|
+
});
|
|
77
|
+
this.readStore = new StateStore<ReadState>({ read: {} });
|
|
108
78
|
// a list of users to hide messages from
|
|
109
|
-
this.
|
|
110
|
-
this.
|
|
111
|
-
this.
|
|
112
|
-
|
|
79
|
+
this.mutedUsersStore = new StateStore<MutedUsersState>({ mutedUsers: [] });
|
|
80
|
+
this.membersStore = new StateStore<MembersState>({ members: {}, memberCount: 0 });
|
|
81
|
+
this.ownCapabilitiesStore = new StateStore<OwnCapabilitiesState>({
|
|
82
|
+
ownCapabilities: [],
|
|
83
|
+
});
|
|
84
|
+
this.syncMemberCountFromChannelData(channel?.data);
|
|
85
|
+
this.syncOwnCapabilitiesFromChannelData(channel?.data);
|
|
86
|
+
this.pending_messages = [];
|
|
87
|
+
this.membership = {} as ChannelMemberResponse;
|
|
113
88
|
this.unreadCount = 0;
|
|
114
|
-
/**
|
|
115
|
-
* Flag which indicates if channel state contain latest/recent messages or no.
|
|
116
|
-
* This flag should be managed by UI sdks using a setter - setIsUpToDate.
|
|
117
|
-
* When false, any new message (received by websocket event - message.new) will not
|
|
118
|
-
* be pushed on to message list.
|
|
119
|
-
*/
|
|
120
|
-
this.isUpToDate = true;
|
|
121
|
-
this.last_message_at =
|
|
122
|
-
channel?.state?.last_message_at != null
|
|
123
|
-
? new Date(channel.state.last_message_at)
|
|
124
|
-
: null;
|
|
125
89
|
}
|
|
126
90
|
|
|
127
|
-
get
|
|
128
|
-
return this.
|
|
91
|
+
get members() {
|
|
92
|
+
return this.membersStore.getLatestValue().members;
|
|
129
93
|
}
|
|
130
94
|
|
|
131
|
-
set
|
|
132
|
-
|
|
133
|
-
this.messageSets[index].messages = messages;
|
|
95
|
+
set members(members: Record<string, ChannelMemberResponse>) {
|
|
96
|
+
this.membersStore.partialNext({ members });
|
|
134
97
|
}
|
|
135
98
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
* The messages array not always contains the latest messages (for example if a user searched for an earlier message, that is in a different message set)
|
|
139
|
-
*/
|
|
140
|
-
get latestMessages() {
|
|
141
|
-
return this.messageSets.find((s) => s.isLatest)?.messages || [];
|
|
99
|
+
get member_count() {
|
|
100
|
+
return this.membersStore.getLatestValue().memberCount;
|
|
142
101
|
}
|
|
143
102
|
|
|
144
|
-
set
|
|
145
|
-
|
|
146
|
-
this.messageSets[index].messages = messages;
|
|
103
|
+
set member_count(memberCount: number) {
|
|
104
|
+
this.membersStore.partialNext({ memberCount });
|
|
147
105
|
}
|
|
148
106
|
|
|
149
|
-
get
|
|
150
|
-
return (
|
|
151
|
-
this.messageSets.find((s) => s.isCurrent)?.pagination ||
|
|
152
|
-
DEFAULT_MESSAGE_SET_PAGINATION
|
|
153
|
-
);
|
|
107
|
+
get read() {
|
|
108
|
+
return this.readStore.getLatestValue().read;
|
|
154
109
|
}
|
|
155
110
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
if (this.messageSets[currentIndex].isLatest) {
|
|
159
|
-
const newMessages = this.messageSets[currentIndex].messages;
|
|
160
|
-
this.messageSets[currentIndex].messages = newMessages.slice(-maxMessages);
|
|
161
|
-
this.messageSets[currentIndex].pagination.hasPrev = true;
|
|
162
|
-
}
|
|
111
|
+
set read(read: ChannelReadStatus) {
|
|
112
|
+
this.readStore.next({ read });
|
|
163
113
|
}
|
|
164
114
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
* @param {boolean} timestampChanged Whether updating a message with changed created_at value.
|
|
170
|
-
* @param {boolean} addIfDoesNotExist Add message if it is not in the list, used to prevent out of order updated messages from being added.
|
|
171
|
-
* @param {MessageSetType} messageSetToAddToIfDoesNotExist Which message set to add to if message is not in the list (only used if addIfDoesNotExist is true)
|
|
172
|
-
*/
|
|
173
|
-
addMessageSorted(
|
|
174
|
-
newMessage: MessageResponse | LocalMessage,
|
|
175
|
-
timestampChanged = false,
|
|
176
|
-
addIfDoesNotExist = true,
|
|
177
|
-
messageSetToAddToIfDoesNotExist: MessageSetType = 'latest',
|
|
178
|
-
) {
|
|
179
|
-
return this.addMessagesSorted(
|
|
180
|
-
[newMessage],
|
|
181
|
-
timestampChanged,
|
|
182
|
-
false,
|
|
183
|
-
addIfDoesNotExist,
|
|
184
|
-
messageSetToAddToIfDoesNotExist,
|
|
115
|
+
get typing() {
|
|
116
|
+
return (
|
|
117
|
+
this._channel?.messageComposer?.textComposer.typing ??
|
|
118
|
+
this.typingStore.getLatestValue().typing
|
|
185
119
|
);
|
|
186
120
|
}
|
|
187
121
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
* and sets the status to `received` if missing; returns a new message object.
|
|
191
|
-
*
|
|
192
|
-
* @param {MessageResponse} message `MessageResponse` object
|
|
193
|
-
*/
|
|
194
|
-
formatMessage = (message: MessageResponse | MessageResponseBase | LocalMessage) =>
|
|
195
|
-
formatMessage(message);
|
|
196
|
-
|
|
197
|
-
/**
|
|
198
|
-
* addMessagesSorted - Add the list of messages to state and resorts the messages
|
|
199
|
-
*
|
|
200
|
-
* @param {Array<MessageResponse>} newMessages A list of messages
|
|
201
|
-
* @param {boolean} timestampChanged Whether updating messages with changed created_at value.
|
|
202
|
-
* @param {boolean} initializing Whether channel is being initialized.
|
|
203
|
-
* @param {boolean} addIfDoesNotExist Add message if it is not in the list, used to prevent out of order updated messages from being added.
|
|
204
|
-
* @param {MessageSetType} messageSetToAddToIfDoesNotExist Which message set to add to if messages are not in the list (only used if addIfDoesNotExist is true)
|
|
205
|
-
*
|
|
206
|
-
*/
|
|
207
|
-
addMessagesSorted(
|
|
208
|
-
newMessages: (MessageResponse | LocalMessage)[],
|
|
209
|
-
timestampChanged = false,
|
|
210
|
-
initializing = false,
|
|
211
|
-
addIfDoesNotExist = true,
|
|
212
|
-
messageSetToAddToIfDoesNotExist: MessageSetType = 'current',
|
|
213
|
-
) {
|
|
214
|
-
const { messagesToAdd, targetMessageSetIndex } = this.findTargetMessageSet(
|
|
215
|
-
newMessages,
|
|
216
|
-
addIfDoesNotExist,
|
|
217
|
-
messageSetToAddToIfDoesNotExist,
|
|
218
|
-
);
|
|
219
|
-
|
|
220
|
-
const filteredMessageIds: string[] = [];
|
|
221
|
-
|
|
222
|
-
for (let i = 0; i < messagesToAdd.length; i += 1) {
|
|
223
|
-
const isFromShadowBannedUser = messagesToAdd[i].shadowed;
|
|
224
|
-
if (isFromShadowBannedUser && addIfDoesNotExist) {
|
|
225
|
-
filteredMessageIds.push(messagesToAdd[i].id);
|
|
226
|
-
continue;
|
|
227
|
-
}
|
|
228
|
-
// If message is already formatted we can skip the tasks below
|
|
229
|
-
// This will be true for messages that are already present at the state -> this happens when we perform merging of message sets
|
|
230
|
-
// This will be also true for message previews used by some SDKs
|
|
231
|
-
const isMessageFormatted = messagesToAdd[i].created_at instanceof Date;
|
|
232
|
-
let message: ReturnType<ChannelState['formatMessage']>;
|
|
233
|
-
if (isMessageFormatted) {
|
|
234
|
-
message = messagesToAdd[i] as ReturnType<ChannelState['formatMessage']>;
|
|
235
|
-
} else {
|
|
236
|
-
message = this.formatMessage(messagesToAdd[i]);
|
|
237
|
-
|
|
238
|
-
if (message.user && this._channel?.cid) {
|
|
239
|
-
/**
|
|
240
|
-
* Store the reference to user for this channel, so that when we have to
|
|
241
|
-
* handle updates to user, we can use the reference map, to determine which
|
|
242
|
-
* channels need to be updated with updated user object.
|
|
243
|
-
*/
|
|
244
|
-
this._channel
|
|
245
|
-
.getClient()
|
|
246
|
-
.state.updateUserReference(message.user, this._channel.cid);
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
if (
|
|
250
|
-
initializing &&
|
|
251
|
-
message.id &&
|
|
252
|
-
this.threads[message.id] &&
|
|
253
|
-
!this._channel.getClient().preventThreadCleanup
|
|
254
|
-
) {
|
|
255
|
-
// If we are initializing the state of channel (e.g., in case of connection recovery),
|
|
256
|
-
// then in that case we remove thread related to this message from threads object.
|
|
257
|
-
// This way we can ensure that we don't have any stale data in thread object
|
|
258
|
-
// and consumer can refetch the replies.
|
|
259
|
-
delete this.threads[message.id];
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
const shouldSkipLastMessageAtUpdate =
|
|
263
|
-
this._channel.getConfig()?.skip_last_msg_update_for_system_msgs &&
|
|
264
|
-
message.type === 'system';
|
|
265
|
-
|
|
266
|
-
if (
|
|
267
|
-
!shouldSkipLastMessageAtUpdate &&
|
|
268
|
-
(!this.last_message_at ||
|
|
269
|
-
message.created_at.getTime() > this.last_message_at.getTime())
|
|
270
|
-
) {
|
|
271
|
-
this.last_message_at = new Date(message.created_at.getTime());
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
// update or append the messages...
|
|
276
|
-
const parentID = message.parent_id;
|
|
277
|
-
|
|
278
|
-
// add to the given message set
|
|
279
|
-
if ((!parentID || message.show_in_channel) && targetMessageSetIndex !== -1) {
|
|
280
|
-
this.messageSets[targetMessageSetIndex].messages = this._addToMessageList(
|
|
281
|
-
this.messageSets[targetMessageSetIndex].messages,
|
|
282
|
-
message,
|
|
283
|
-
timestampChanged,
|
|
284
|
-
'created_at',
|
|
285
|
-
addIfDoesNotExist,
|
|
286
|
-
);
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
/**
|
|
290
|
-
* Add message to thread if applicable and the message
|
|
291
|
-
* was added when querying for replies, or the thread already exits.
|
|
292
|
-
* This is to prevent the thread state from getting out of sync if
|
|
293
|
-
* a thread message is shown in channel but older than the newest thread
|
|
294
|
-
* message. This situation can result in a thread state where a random
|
|
295
|
-
* message is "oldest" message, and newer messages are therefore not loaded.
|
|
296
|
-
* This can also occur if an old thread message is updated.
|
|
297
|
-
*/
|
|
298
|
-
if (parentID && !initializing) {
|
|
299
|
-
const thread = this.threads[parentID] || [];
|
|
300
|
-
this.threads[parentID] = this._addToMessageList(
|
|
301
|
-
thread,
|
|
302
|
-
message,
|
|
303
|
-
timestampChanged,
|
|
304
|
-
'created_at',
|
|
305
|
-
addIfDoesNotExist,
|
|
306
|
-
);
|
|
307
|
-
}
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
return {
|
|
311
|
-
messageSet: this.messageSets[targetMessageSetIndex],
|
|
312
|
-
filteredMessageIds,
|
|
313
|
-
};
|
|
314
|
-
}
|
|
122
|
+
set typing(typing: Record<string, Event>) {
|
|
123
|
+
this.typingStore.next({ typing });
|
|
315
124
|
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
*
|
|
319
|
-
* @param {Array<MessageResponse>} pinnedMessages A list of pinned messages
|
|
320
|
-
*
|
|
321
|
-
*/
|
|
322
|
-
addPinnedMessages(pinnedMessages: MessageResponse[]) {
|
|
323
|
-
for (let i = 0; i < pinnedMessages.length; i += 1) {
|
|
324
|
-
this.addPinnedMessage(pinnedMessages[i]);
|
|
125
|
+
if (this._channel?.messageComposer) {
|
|
126
|
+
this._channel.messageComposer.textComposer.setTyping(typing);
|
|
325
127
|
}
|
|
326
128
|
}
|
|
327
129
|
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
* @param {MessageResponse} pinnedMessage message to update
|
|
332
|
-
*
|
|
333
|
-
*/
|
|
334
|
-
addPinnedMessage(pinnedMessage: MessageResponse) {
|
|
335
|
-
this.pinnedMessages = this._addToMessageList(
|
|
336
|
-
this.pinnedMessages,
|
|
337
|
-
this.formatMessage(pinnedMessage),
|
|
338
|
-
false,
|
|
339
|
-
'pinned_at',
|
|
340
|
-
);
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
/**
|
|
344
|
-
* removePinnedMessage - removes pinned message from pinnedMessages
|
|
345
|
-
*
|
|
346
|
-
* @param {MessageResponse} message message to remove
|
|
347
|
-
*
|
|
348
|
-
*/
|
|
349
|
-
removePinnedMessage(message: MessageResponse) {
|
|
350
|
-
const { result } = this.removeMessageFromArray(this.pinnedMessages, message);
|
|
351
|
-
this.pinnedMessages = result;
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
addReaction(
|
|
355
|
-
reaction: ReactionResponse,
|
|
356
|
-
message?: MessageResponse,
|
|
357
|
-
enforce_unique?: boolean,
|
|
130
|
+
syncMemberCountFromChannelData(
|
|
131
|
+
data: Channel['data'],
|
|
132
|
+
fallbackData: Channel['data'] = this._channel?.data,
|
|
358
133
|
) {
|
|
359
|
-
const
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
}
|
|
134
|
+
const fallbackMemberCount =
|
|
135
|
+
typeof fallbackData?.member_count === 'number'
|
|
136
|
+
? fallbackData.member_count
|
|
137
|
+
: this.membersStore.getLatestValue().memberCount;
|
|
364
138
|
|
|
365
|
-
if (!
|
|
139
|
+
if (!data || typeof data !== 'object') {
|
|
140
|
+
this.membersStore.partialNext({ memberCount: fallbackMemberCount ?? 0 });
|
|
366
141
|
return;
|
|
367
142
|
}
|
|
368
143
|
|
|
369
|
-
const
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
this._updateMessage(updateData, (msg) => {
|
|
378
|
-
if (messageWithReaction) {
|
|
379
|
-
const updatedMessage = { ...messageWithReaction };
|
|
380
|
-
// This part will remove own_reactions from what is essentially
|
|
381
|
-
// a copy of event.message; we do not want to return that as someone
|
|
382
|
-
// else reaction would remove our own_reactions needlessly. This
|
|
383
|
-
// only happens when we are not the sender of the reaction. We need
|
|
384
|
-
// the variable itself so that the event can be properly enriched
|
|
385
|
-
// later on.
|
|
386
|
-
messageWithReaction.own_reactions = this._addOwnReactionToMessage(
|
|
387
|
-
msg.own_reactions,
|
|
388
|
-
reaction,
|
|
389
|
-
enforce_unique,
|
|
390
|
-
);
|
|
391
|
-
// Whenever we are the ones sending the reaction, the helper enriches
|
|
392
|
-
// own_reactions as normal so we can use that, otherwise we fallback
|
|
393
|
-
// to whatever state we had.
|
|
394
|
-
updatedMessage.own_reactions =
|
|
395
|
-
this._channel.getClient().userID === reaction.user_id
|
|
396
|
-
? messageWithReaction.own_reactions
|
|
397
|
-
: msg.own_reactions;
|
|
398
|
-
return this.formatMessage(updatedMessage);
|
|
399
|
-
}
|
|
144
|
+
const dataDescriptor = Object.getOwnPropertyDescriptor(data, 'member_count');
|
|
145
|
+
let memberCount =
|
|
146
|
+
typeof data.member_count === 'number'
|
|
147
|
+
? data.member_count
|
|
148
|
+
: typeof fallbackMemberCount === 'number'
|
|
149
|
+
? fallbackMemberCount
|
|
150
|
+
: undefined;
|
|
400
151
|
|
|
401
|
-
|
|
402
|
-
return this._addReactionToState(messageFromState, reaction, enforce_unique);
|
|
403
|
-
}
|
|
152
|
+
this.membersStore.partialNext({ memberCount: memberCount ?? 0 });
|
|
404
153
|
|
|
405
|
-
|
|
154
|
+
Object.defineProperty(data, 'member_count', {
|
|
155
|
+
configurable: true,
|
|
156
|
+
enumerable: dataDescriptor?.enumerable ?? false,
|
|
157
|
+
get: () => memberCount,
|
|
158
|
+
set: (nextMemberCount: number | undefined) => {
|
|
159
|
+
memberCount = typeof nextMemberCount === 'number' ? nextMemberCount : undefined;
|
|
160
|
+
this.membersStore.partialNext({ memberCount: memberCount ?? 0 });
|
|
161
|
+
},
|
|
406
162
|
});
|
|
407
|
-
return messageWithReaction ?? messageFromState;
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
_addReactionToState(
|
|
411
|
-
messageFromState: LocalMessage,
|
|
412
|
-
reaction: ReactionResponse,
|
|
413
|
-
enforce_unique?: boolean,
|
|
414
|
-
) {
|
|
415
|
-
if (!messageFromState.reaction_groups) {
|
|
416
|
-
messageFromState.reaction_groups = {};
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
// 1. Firstly, get rid of all of our own reactions from the reaction_groups
|
|
420
|
-
// if enforce_unique is enabled.
|
|
421
|
-
if (enforce_unique) {
|
|
422
|
-
for (const ownReaction of messageFromState.own_reactions ?? []) {
|
|
423
|
-
const oldOwnReactionTypeData = messageFromState.reaction_groups[ownReaction.type];
|
|
424
|
-
messageFromState.reaction_groups[ownReaction.type] = {
|
|
425
|
-
...oldOwnReactionTypeData,
|
|
426
|
-
count: oldOwnReactionTypeData.count - 1,
|
|
427
|
-
sum_scores: oldOwnReactionTypeData.sum_scores - (ownReaction.score ?? 1),
|
|
428
|
-
};
|
|
429
|
-
// If there are no reactions left in this group, simply remove it.
|
|
430
|
-
if (messageFromState.reaction_groups[ownReaction.type].count < 1) {
|
|
431
|
-
delete messageFromState.reaction_groups[ownReaction.type];
|
|
432
|
-
}
|
|
433
|
-
}
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
const newReactionGroups = messageFromState.reaction_groups;
|
|
437
|
-
const oldReactionTypeData = newReactionGroups[reaction.type];
|
|
438
|
-
const score = reaction.score ?? 1;
|
|
439
|
-
|
|
440
|
-
// 2. Next, update the reaction_groups with the new reaction.
|
|
441
|
-
messageFromState.reaction_groups[reaction.type] = oldReactionTypeData
|
|
442
|
-
? {
|
|
443
|
-
...oldReactionTypeData,
|
|
444
|
-
count: oldReactionTypeData.count + 1,
|
|
445
|
-
sum_scores: oldReactionTypeData.sum_scores + score,
|
|
446
|
-
last_reaction_at: reaction.created_at,
|
|
447
|
-
}
|
|
448
|
-
: {
|
|
449
|
-
count: 1,
|
|
450
|
-
first_reaction_at: reaction.created_at,
|
|
451
|
-
last_reaction_at: reaction.created_at,
|
|
452
|
-
sum_scores: score,
|
|
453
|
-
};
|
|
454
|
-
|
|
455
|
-
// 3. Update the own_reactions with the new reaction.
|
|
456
|
-
messageFromState.own_reactions = this._addOwnReactionToMessage(
|
|
457
|
-
messageFromState.own_reactions,
|
|
458
|
-
reaction,
|
|
459
|
-
enforce_unique,
|
|
460
|
-
);
|
|
461
|
-
|
|
462
|
-
// 4. Finally, update the latest_reactions with the new reaction,
|
|
463
|
-
// while respecting enforce_unique.
|
|
464
|
-
const userId = this._channel.getClient().userID;
|
|
465
|
-
messageFromState.latest_reactions = enforce_unique
|
|
466
|
-
? [
|
|
467
|
-
...(messageFromState.latest_reactions || []).filter(
|
|
468
|
-
(r) => r.user_id !== userId,
|
|
469
|
-
),
|
|
470
|
-
reaction,
|
|
471
|
-
]
|
|
472
|
-
: [...(messageFromState.latest_reactions || []), reaction];
|
|
473
|
-
|
|
474
|
-
return messageFromState;
|
|
475
163
|
}
|
|
476
164
|
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
enforce_unique?: boolean,
|
|
165
|
+
syncOwnCapabilitiesFromChannelData(
|
|
166
|
+
data: Channel['data'],
|
|
167
|
+
fallbackData: Channel['data'] = this._channel?.data,
|
|
481
168
|
) {
|
|
482
|
-
if (
|
|
483
|
-
|
|
484
|
-
} else {
|
|
485
|
-
ownReactions = this._removeOwnReactionFromMessage(ownReactions, reaction);
|
|
486
|
-
}
|
|
487
|
-
|
|
488
|
-
ownReactions = ownReactions || [];
|
|
489
|
-
if (this._channel.getClient().userID === reaction.user_id) {
|
|
490
|
-
ownReactions.push(reaction);
|
|
491
|
-
}
|
|
492
|
-
|
|
493
|
-
return ownReactions;
|
|
494
|
-
}
|
|
495
|
-
|
|
496
|
-
_removeOwnReactionFromMessage(
|
|
497
|
-
ownReactions: ReactionResponse[] | null | undefined,
|
|
498
|
-
reaction: ReactionResponse,
|
|
499
|
-
) {
|
|
500
|
-
if (ownReactions) {
|
|
501
|
-
return ownReactions.filter(
|
|
502
|
-
(item) => item.user_id !== reaction.user_id || item.type !== reaction.type,
|
|
503
|
-
);
|
|
504
|
-
}
|
|
505
|
-
return ownReactions;
|
|
506
|
-
}
|
|
507
|
-
|
|
508
|
-
removeReaction(reaction: ReactionResponse, message?: MessageResponse) {
|
|
509
|
-
const messageWithRemovedReaction = message;
|
|
510
|
-
let messageFromState: LocalMessage | undefined;
|
|
511
|
-
if (!messageWithRemovedReaction) {
|
|
512
|
-
messageFromState = this.findMessage(reaction.message_id);
|
|
513
|
-
}
|
|
514
|
-
|
|
515
|
-
if (!messageWithRemovedReaction && !messageFromState) {
|
|
169
|
+
if (!data || typeof data !== 'object') {
|
|
170
|
+
this.ownCapabilitiesStore.next({ ownCapabilities: [] });
|
|
516
171
|
return;
|
|
517
172
|
}
|
|
518
173
|
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
};
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
174
|
+
let ownCapabilities: string[] | undefined = Array.isArray(data.own_capabilities)
|
|
175
|
+
? [...data.own_capabilities]
|
|
176
|
+
: Array.isArray(fallbackData?.own_capabilities)
|
|
177
|
+
? [...fallbackData.own_capabilities]
|
|
178
|
+
: undefined;
|
|
179
|
+
|
|
180
|
+
this.ownCapabilitiesStore.next({ ownCapabilities: ownCapabilities ?? [] });
|
|
181
|
+
|
|
182
|
+
// Keep the reactive getter/setter so backward-compatible assignments still sync to
|
|
183
|
+
// the store, but return `undefined` until capabilities are actually known. Forcing
|
|
184
|
+
// `[]` on an unloaded channel would make read-events–gated logic (e.g. unread
|
|
185
|
+
// counting, regression #1732) treat "not yet loaded" as "explicitly no capabilities".
|
|
186
|
+
Object.defineProperty(data, 'own_capabilities', {
|
|
187
|
+
configurable: true,
|
|
188
|
+
enumerable: true,
|
|
189
|
+
get: () => ownCapabilities,
|
|
190
|
+
set: (nextOwnCapabilities: string[] | undefined) => {
|
|
191
|
+
ownCapabilities = Array.isArray(nextOwnCapabilities)
|
|
192
|
+
? [...nextOwnCapabilities]
|
|
193
|
+
: undefined;
|
|
194
|
+
this.ownCapabilitiesStore.next({ ownCapabilities: ownCapabilities ?? [] });
|
|
195
|
+
},
|
|
540
196
|
});
|
|
541
|
-
return messageWithRemovedReaction;
|
|
542
197
|
}
|
|
543
198
|
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
(r) => r.type === reaction.type,
|
|
547
|
-
);
|
|
548
|
-
if (reactionToRemove && messageFromState.reaction_groups?.[reactionToRemove.type]) {
|
|
549
|
-
const newReactionGroup = messageFromState.reaction_groups[reactionToRemove.type];
|
|
550
|
-
messageFromState.reaction_groups[reactionToRemove.type] = {
|
|
551
|
-
...newReactionGroup,
|
|
552
|
-
count: newReactionGroup.count - 1,
|
|
553
|
-
sum_scores: newReactionGroup.sum_scores - (reactionToRemove.score ?? 1),
|
|
554
|
-
};
|
|
555
|
-
// If there are no reactions left in this group, simply remove it.
|
|
556
|
-
if (messageFromState.reaction_groups[reactionToRemove.type].count < 1) {
|
|
557
|
-
delete messageFromState.reaction_groups[reactionToRemove.type];
|
|
558
|
-
}
|
|
559
|
-
}
|
|
560
|
-
messageFromState.own_reactions = messageFromState.own_reactions?.filter(
|
|
561
|
-
(r) => r.type !== reaction.type,
|
|
562
|
-
);
|
|
563
|
-
const userId = this._channel.getClient().userID;
|
|
564
|
-
messageFromState.latest_reactions = messageFromState.latest_reactions?.filter(
|
|
565
|
-
(r) => !(r.user_id === userId && r.type === reaction.type),
|
|
566
|
-
);
|
|
567
|
-
return messageFromState;
|
|
199
|
+
setTypingEvent(userID: string, event: Event) {
|
|
200
|
+
this.typing = { ...this.typing, [userID]: event };
|
|
568
201
|
}
|
|
569
202
|
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
remove,
|
|
573
|
-
}: {
|
|
574
|
-
message: MessageResponse;
|
|
575
|
-
remove?: boolean;
|
|
576
|
-
}) {
|
|
577
|
-
const parseMessage = (m: ReturnType<ChannelState['formatMessage']>) =>
|
|
578
|
-
({
|
|
579
|
-
...m,
|
|
580
|
-
created_at: m.created_at.toISOString(),
|
|
581
|
-
pinned_at: m.pinned_at?.toISOString(),
|
|
582
|
-
updated_at: m.updated_at?.toISOString(),
|
|
583
|
-
}) as unknown as MessageResponse;
|
|
203
|
+
removeTypingEvent(userID: string) {
|
|
204
|
+
if (!this.typing[userID]) return;
|
|
584
205
|
|
|
585
|
-
const
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
acc.push({
|
|
589
|
-
...parseMessage(msg),
|
|
590
|
-
quoted_message: remove ? { ...message, attachments: [] } : message,
|
|
591
|
-
});
|
|
592
|
-
}
|
|
593
|
-
return acc;
|
|
594
|
-
}, []);
|
|
595
|
-
this.addMessagesSorted(updatedMessages, true);
|
|
596
|
-
};
|
|
597
|
-
|
|
598
|
-
if (!message.parent_id) {
|
|
599
|
-
this.messageSets.forEach((set) => update(set.messages));
|
|
600
|
-
} else if (message.parent_id && this.threads[message.parent_id]) {
|
|
601
|
-
// prevent going through all the threads even though it is possible to quote a message from another thread
|
|
602
|
-
update(this.threads[message.parent_id]);
|
|
603
|
-
}
|
|
206
|
+
const typing = { ...this.typing };
|
|
207
|
+
delete typing[userID];
|
|
208
|
+
this.typing = typing;
|
|
604
209
|
}
|
|
605
210
|
|
|
606
|
-
|
|
607
|
-
this.
|
|
211
|
+
get mutedUsers() {
|
|
212
|
+
return this.mutedUsersStore.getLatestValue().mutedUsers;
|
|
608
213
|
}
|
|
609
214
|
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
* @param message
|
|
613
|
-
* @param updateFunc
|
|
614
|
-
*/
|
|
615
|
-
_updateMessage(
|
|
616
|
-
message: {
|
|
617
|
-
id?: string;
|
|
618
|
-
parent_id?: string;
|
|
619
|
-
pinned?: boolean;
|
|
620
|
-
show_in_channel?: boolean;
|
|
621
|
-
},
|
|
622
|
-
updateFunc: (
|
|
623
|
-
msg: ReturnType<ChannelState['formatMessage']>,
|
|
624
|
-
) => ReturnType<ChannelState['formatMessage']>,
|
|
625
|
-
) {
|
|
626
|
-
const { parent_id, show_in_channel, pinned } = message;
|
|
627
|
-
|
|
628
|
-
if (parent_id && this.threads[parent_id]) {
|
|
629
|
-
const thread = this.threads[parent_id];
|
|
630
|
-
const msgIndex = thread.findIndex((msg) => msg.id === message.id);
|
|
631
|
-
if (msgIndex !== -1) {
|
|
632
|
-
thread[msgIndex] = updateFunc(thread[msgIndex]);
|
|
633
|
-
this.threads[parent_id] = thread;
|
|
634
|
-
}
|
|
635
|
-
}
|
|
636
|
-
|
|
637
|
-
if ((!show_in_channel && !parent_id) || show_in_channel) {
|
|
638
|
-
const messageSetIndex = this.findMessageSetIndex(message);
|
|
639
|
-
if (messageSetIndex !== -1) {
|
|
640
|
-
const msgIndex = this.messageSets[messageSetIndex].messages.findIndex(
|
|
641
|
-
(msg) => msg.id === message.id,
|
|
642
|
-
);
|
|
643
|
-
if (msgIndex !== -1) {
|
|
644
|
-
const upMsg = updateFunc(this.messageSets[messageSetIndex].messages[msgIndex]);
|
|
645
|
-
this.messageSets[messageSetIndex].messages[msgIndex] = upMsg;
|
|
646
|
-
}
|
|
647
|
-
}
|
|
648
|
-
}
|
|
649
|
-
|
|
650
|
-
if (pinned) {
|
|
651
|
-
const msgIndex = this.pinnedMessages.findIndex((msg) => msg.id === message.id);
|
|
652
|
-
if (msgIndex !== -1) {
|
|
653
|
-
this.pinnedMessages[msgIndex] = updateFunc(this.pinnedMessages[msgIndex]);
|
|
654
|
-
}
|
|
655
|
-
}
|
|
215
|
+
set mutedUsers(mutedUsers: Array<UserResponse>) {
|
|
216
|
+
this.mutedUsersStore.next({ mutedUsers });
|
|
656
217
|
}
|
|
657
218
|
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
*
|
|
661
|
-
* @param isUpToDate Flag which indicates if channel state contain latest/recent messages or no.
|
|
662
|
-
* This flag should be managed by UI sdks using a setter - setIsUpToDate.
|
|
663
|
-
* When false, any new message (received by websocket event - message.new) will not
|
|
664
|
-
* be pushed on to message list.
|
|
665
|
-
*/
|
|
666
|
-
setIsUpToDate = (isUpToDate: boolean) => {
|
|
667
|
-
this.isUpToDate = isUpToDate;
|
|
668
|
-
};
|
|
669
|
-
|
|
670
|
-
/**
|
|
671
|
-
* _addToMessageList - Adds a message to a list of messages, tries to update first, appends if message isn't found
|
|
672
|
-
*
|
|
673
|
-
* @param {Array<ReturnType<ChannelState['formatMessage']>>} messages A list of messages
|
|
674
|
-
* @param message
|
|
675
|
-
* @param {boolean} timestampChanged Whether updating a message with changed created_at value.
|
|
676
|
-
* @param {string} sortBy field name to use to sort the messages by
|
|
677
|
-
* @param {boolean} addIfDoesNotExist Add message if it is not in the list, used to prevent out of order updated messages from being added.
|
|
678
|
-
*/
|
|
679
|
-
_addToMessageList(
|
|
680
|
-
messages: Array<ReturnType<ChannelState['formatMessage']>>,
|
|
681
|
-
message: ReturnType<ChannelState['formatMessage']>,
|
|
682
|
-
timestampChanged = false,
|
|
683
|
-
sortBy: 'pinned_at' | 'created_at' = 'created_at',
|
|
684
|
-
addIfDoesNotExist = true,
|
|
685
|
-
) {
|
|
686
|
-
return addToMessageList(
|
|
687
|
-
messages,
|
|
688
|
-
message,
|
|
689
|
-
timestampChanged,
|
|
690
|
-
sortBy,
|
|
691
|
-
addIfDoesNotExist,
|
|
692
|
-
);
|
|
219
|
+
get watchers() {
|
|
220
|
+
return this.watcherStore.getLatestValue().watchers;
|
|
693
221
|
}
|
|
694
222
|
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
*
|
|
698
|
-
* @param {{ id: string; parent_id?: string }} messageToRemove Object of the message to remove. Needs to have at id specified.
|
|
699
|
-
*
|
|
700
|
-
* @return {boolean} Returns if the message was removed
|
|
701
|
-
*/
|
|
702
|
-
removeMessage(messageToRemove: {
|
|
703
|
-
id: string;
|
|
704
|
-
messageSetIndex?: number;
|
|
705
|
-
parent_id?: string;
|
|
706
|
-
}) {
|
|
707
|
-
let isRemoved = false;
|
|
708
|
-
if (messageToRemove.parent_id && this.threads[messageToRemove.parent_id]) {
|
|
709
|
-
const { removed, result: threadMessages } = this.removeMessageFromArray(
|
|
710
|
-
this.threads[messageToRemove.parent_id],
|
|
711
|
-
messageToRemove,
|
|
712
|
-
);
|
|
713
|
-
|
|
714
|
-
this.threads[messageToRemove.parent_id] = threadMessages;
|
|
715
|
-
isRemoved = removed;
|
|
716
|
-
} else {
|
|
717
|
-
const messageSetIndex =
|
|
718
|
-
messageToRemove.messageSetIndex ?? this.findMessageSetIndex(messageToRemove);
|
|
719
|
-
if (messageSetIndex !== -1) {
|
|
720
|
-
const { removed, result: messages } = this.removeMessageFromArray(
|
|
721
|
-
this.messageSets[messageSetIndex].messages,
|
|
722
|
-
messageToRemove,
|
|
723
|
-
);
|
|
724
|
-
this.messageSets[messageSetIndex].messages = messages;
|
|
725
|
-
isRemoved = removed;
|
|
726
|
-
}
|
|
727
|
-
}
|
|
728
|
-
|
|
729
|
-
return isRemoved;
|
|
223
|
+
set watchers(watchers: Record<string, UserResponse>) {
|
|
224
|
+
this.watcherStore.partialNext({ watchers });
|
|
730
225
|
}
|
|
731
226
|
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
) => {
|
|
736
|
-
const result = msgArray.filter(
|
|
737
|
-
(message) => !(!!message.id && !!msg.id && message.id === msg.id),
|
|
738
|
-
);
|
|
739
|
-
|
|
740
|
-
return { removed: result.length < msgArray.length, result };
|
|
741
|
-
};
|
|
742
|
-
|
|
743
|
-
/**
|
|
744
|
-
* Updates the message.user property with updated user object, for messages.
|
|
745
|
-
*
|
|
746
|
-
* @param {UserResponse} user
|
|
747
|
-
*/
|
|
748
|
-
updateUserMessages = (user: UserResponse) => {
|
|
749
|
-
const _updateUserMessages = (
|
|
750
|
-
messages: Array<ReturnType<ChannelState['formatMessage']>>,
|
|
751
|
-
user: UserResponse,
|
|
752
|
-
) => {
|
|
753
|
-
for (let i = 0; i < messages.length; i++) {
|
|
754
|
-
const m = messages[i];
|
|
755
|
-
if (m.user?.id === user.id) {
|
|
756
|
-
messages[i] = { ...m, user };
|
|
757
|
-
}
|
|
758
|
-
}
|
|
759
|
-
};
|
|
760
|
-
|
|
761
|
-
this.messageSets.forEach((set) => _updateUserMessages(set.messages, user));
|
|
762
|
-
|
|
763
|
-
for (const parentId in this.threads) {
|
|
764
|
-
_updateUserMessages(this.threads[parentId], user);
|
|
765
|
-
}
|
|
766
|
-
|
|
767
|
-
_updateUserMessages(this.pinnedMessages, user);
|
|
768
|
-
};
|
|
769
|
-
|
|
770
|
-
/**
|
|
771
|
-
* Marks the messages as deleted, from deleted user.
|
|
772
|
-
*
|
|
773
|
-
* @param {UserResponse} user
|
|
774
|
-
* @param {boolean} hardDelete
|
|
775
|
-
*/
|
|
776
|
-
deleteUserMessages = (
|
|
777
|
-
user: UserResponse,
|
|
778
|
-
hardDelete = false,
|
|
779
|
-
deletedAt?: LocalMessage['deleted_at'],
|
|
780
|
-
) => {
|
|
781
|
-
this.messageSets.forEach(({ messages }) =>
|
|
782
|
-
_deleteUserMessages({ messages, user, hardDelete, deletedAt: deletedAt ?? null }),
|
|
783
|
-
);
|
|
784
|
-
|
|
785
|
-
for (const parentId in this.threads) {
|
|
786
|
-
_deleteUserMessages({
|
|
787
|
-
messages: this.threads[parentId],
|
|
788
|
-
user,
|
|
789
|
-
hardDelete,
|
|
790
|
-
deletedAt: deletedAt ?? null,
|
|
791
|
-
});
|
|
792
|
-
}
|
|
227
|
+
get watcher_count() {
|
|
228
|
+
return this.watcherStore.getLatestValue().watcherCount;
|
|
229
|
+
}
|
|
793
230
|
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
hardDelete,
|
|
798
|
-
deletedAt: deletedAt ?? null,
|
|
799
|
-
});
|
|
800
|
-
};
|
|
231
|
+
set watcher_count(watcherCount: number) {
|
|
232
|
+
this.watcherStore.partialNext({ watcherCount });
|
|
233
|
+
}
|
|
801
234
|
|
|
802
235
|
/**
|
|
803
|
-
*
|
|
236
|
+
* Takes the message object, adds SDK-specific fields (status, error),
|
|
237
|
+
* and returns a new message object.
|
|
804
238
|
*
|
|
239
|
+
* @param message - `MessageResponse` object
|
|
805
240
|
*/
|
|
806
|
-
|
|
807
|
-
const filteredMessages = this.latestMessages.filter(
|
|
808
|
-
(message) => message.type !== 'error',
|
|
809
|
-
);
|
|
810
|
-
|
|
811
|
-
const blockedMessages = this.latestMessages.filter(isBlockedMessage);
|
|
812
|
-
// We need to hard delete the blocked messages from the offline database.
|
|
813
|
-
for (const message of blockedMessages) {
|
|
814
|
-
this._channel.getClient().offlineDb?.hardDeleteMessage({ id: message.id });
|
|
815
|
-
}
|
|
816
|
-
|
|
817
|
-
this.latestMessages = filteredMessages;
|
|
818
|
-
}
|
|
241
|
+
formatMessage = (message: MessageResponse | LocalMessage) => formatMessage(message);
|
|
819
242
|
|
|
820
243
|
/**
|
|
821
244
|
* clean - Remove stale data such as users that stayed in typing state for more than 5 seconds
|
|
@@ -829,7 +252,7 @@ export class ChannelState {
|
|
|
829
252
|
? new Date(lastEvent.received_at)
|
|
830
253
|
: lastEvent.received_at || new Date();
|
|
831
254
|
if (now.getTime() - receivedAt.getTime() > 7000) {
|
|
832
|
-
|
|
255
|
+
this.removeTypingEvent(userID);
|
|
833
256
|
this._channel.getClient().dispatchEvent({
|
|
834
257
|
cid: this._channel.cid,
|
|
835
258
|
type: 'typing.stop',
|
|
@@ -838,296 +261,4 @@ export class ChannelState {
|
|
|
838
261
|
}
|
|
839
262
|
}
|
|
840
263
|
}
|
|
841
|
-
|
|
842
|
-
clearMessages() {
|
|
843
|
-
this.initMessages();
|
|
844
|
-
this.pinnedMessages = [];
|
|
845
|
-
}
|
|
846
|
-
|
|
847
|
-
initMessages() {
|
|
848
|
-
this.messageSets = [
|
|
849
|
-
{
|
|
850
|
-
messages: [],
|
|
851
|
-
isLatest: true,
|
|
852
|
-
isCurrent: true,
|
|
853
|
-
pagination: { ...DEFAULT_MESSAGE_SET_PAGINATION },
|
|
854
|
-
},
|
|
855
|
-
];
|
|
856
|
-
}
|
|
857
|
-
|
|
858
|
-
/**
|
|
859
|
-
* loadMessageIntoState - Loads a given message (and messages around it) into the state
|
|
860
|
-
*
|
|
861
|
-
* @param {string} messageId The id of the message, or 'latest' to indicate switching to the latest messages
|
|
862
|
-
* @param {string} parentMessageId The id of the parent message, if we want load a thread reply
|
|
863
|
-
* @param {number} limit The page size if the message has to be queried from the server
|
|
864
|
-
*/
|
|
865
|
-
async loadMessageIntoState(
|
|
866
|
-
messageId: string | 'latest',
|
|
867
|
-
parentMessageId?: string,
|
|
868
|
-
limit = 25,
|
|
869
|
-
) {
|
|
870
|
-
let messageSetIndex: number;
|
|
871
|
-
let switchedToMessageSet = false;
|
|
872
|
-
let loadedMessageThread = false;
|
|
873
|
-
const messageIdToFind = parentMessageId || messageId;
|
|
874
|
-
if (messageId === 'latest') {
|
|
875
|
-
if (this.messages === this.latestMessages) {
|
|
876
|
-
return;
|
|
877
|
-
}
|
|
878
|
-
messageSetIndex = this.messageSets.findIndex((s) => s.isLatest);
|
|
879
|
-
} else {
|
|
880
|
-
messageSetIndex = this.findMessageSetIndex({ id: messageIdToFind });
|
|
881
|
-
}
|
|
882
|
-
if (messageSetIndex !== -1) {
|
|
883
|
-
this.switchToMessageSet(messageSetIndex);
|
|
884
|
-
switchedToMessageSet = true;
|
|
885
|
-
}
|
|
886
|
-
loadedMessageThread =
|
|
887
|
-
!parentMessageId ||
|
|
888
|
-
!!this.threads[parentMessageId]?.find((m) => m.id === messageId);
|
|
889
|
-
if (switchedToMessageSet && loadedMessageThread) {
|
|
890
|
-
return;
|
|
891
|
-
}
|
|
892
|
-
if (!switchedToMessageSet) {
|
|
893
|
-
await this._channel.query(
|
|
894
|
-
{ messages: { id_around: messageIdToFind, limit } },
|
|
895
|
-
'new',
|
|
896
|
-
);
|
|
897
|
-
}
|
|
898
|
-
if (!loadedMessageThread && parentMessageId) {
|
|
899
|
-
await this._channel.getReplies(parentMessageId, { id_around: messageId, limit });
|
|
900
|
-
}
|
|
901
|
-
messageSetIndex = this.findMessageSetIndex({ id: messageIdToFind });
|
|
902
|
-
if (messageSetIndex !== -1) {
|
|
903
|
-
this.switchToMessageSet(messageSetIndex);
|
|
904
|
-
}
|
|
905
|
-
}
|
|
906
|
-
|
|
907
|
-
/**
|
|
908
|
-
* findMessage - Finds a message inside the state
|
|
909
|
-
*
|
|
910
|
-
* @param {string} messageId The id of the message
|
|
911
|
-
* @param {string} parentMessageId The id of the parent message, if we want load a thread reply
|
|
912
|
-
*
|
|
913
|
-
* @return {ReturnType<ChannelState['formatMessage']>} Returns the message, or undefined if the message wasn't found
|
|
914
|
-
*/
|
|
915
|
-
findMessage(messageId: string, parentMessageId?: string) {
|
|
916
|
-
if (parentMessageId) {
|
|
917
|
-
const messages = this.threads[parentMessageId];
|
|
918
|
-
if (!messages) {
|
|
919
|
-
return undefined;
|
|
920
|
-
}
|
|
921
|
-
return messages.find((m) => m.id === messageId);
|
|
922
|
-
}
|
|
923
|
-
|
|
924
|
-
const messageSetIndex = this.findMessageSetIndex({ id: messageId });
|
|
925
|
-
if (messageSetIndex === -1) {
|
|
926
|
-
return undefined;
|
|
927
|
-
}
|
|
928
|
-
return this.messageSets[messageSetIndex].messages.find((m) => m.id === messageId);
|
|
929
|
-
}
|
|
930
|
-
|
|
931
|
-
findMessageByTimestamp(
|
|
932
|
-
timestampMs: number,
|
|
933
|
-
parentMessageId?: string,
|
|
934
|
-
exactTsMatch: boolean = false,
|
|
935
|
-
): LocalMessage | null {
|
|
936
|
-
if (
|
|
937
|
-
(parentMessageId && !this.threads[parentMessageId]) ||
|
|
938
|
-
this.messageSets.length === 0
|
|
939
|
-
)
|
|
940
|
-
return null;
|
|
941
|
-
const setIndex = this.findMessageSetByOldestTimestamp(timestampMs);
|
|
942
|
-
const targetMsgSet = this.messageSets[setIndex]?.messages;
|
|
943
|
-
if (!targetMsgSet?.length) return null;
|
|
944
|
-
const firstMsgTimestamp = targetMsgSet[0].created_at.getTime();
|
|
945
|
-
const lastMsgTimestamp = targetMsgSet.slice(-1)[0].created_at.getTime();
|
|
946
|
-
const isOutOfBound =
|
|
947
|
-
timestampMs < firstMsgTimestamp || lastMsgTimestamp < timestampMs;
|
|
948
|
-
if (isOutOfBound && exactTsMatch) return null;
|
|
949
|
-
|
|
950
|
-
let msgIndex = 0,
|
|
951
|
-
hi = targetMsgSet.length - 1;
|
|
952
|
-
while (msgIndex < hi) {
|
|
953
|
-
const mid = (msgIndex + hi) >>> 1;
|
|
954
|
-
if (timestampMs <= targetMsgSet[mid].created_at.getTime()) hi = mid;
|
|
955
|
-
else msgIndex = mid + 1;
|
|
956
|
-
}
|
|
957
|
-
|
|
958
|
-
const foundMessage = targetMsgSet[msgIndex];
|
|
959
|
-
return !exactTsMatch
|
|
960
|
-
? foundMessage
|
|
961
|
-
: foundMessage.created_at.getTime() === timestampMs
|
|
962
|
-
? foundMessage
|
|
963
|
-
: null;
|
|
964
|
-
}
|
|
965
|
-
|
|
966
|
-
private switchToMessageSet(index: number) {
|
|
967
|
-
const currentMessages = this.messageSets.find((s) => s.isCurrent);
|
|
968
|
-
if (!currentMessages) {
|
|
969
|
-
return;
|
|
970
|
-
}
|
|
971
|
-
currentMessages.isCurrent = false;
|
|
972
|
-
this.messageSets[index].isCurrent = true;
|
|
973
|
-
}
|
|
974
|
-
|
|
975
|
-
private areMessageSetsOverlap(
|
|
976
|
-
messages1: Array<{ id: string }>,
|
|
977
|
-
messages2: Array<{ id: string }>,
|
|
978
|
-
) {
|
|
979
|
-
return messages1.some((m1) => messages2.find((m2) => m1.id === m2.id));
|
|
980
|
-
}
|
|
981
|
-
|
|
982
|
-
private findMessageSetIndex(message: { id?: string }) {
|
|
983
|
-
return this.messageSets.findIndex(
|
|
984
|
-
(set) => !!set.messages.find((m) => m.id === message.id),
|
|
985
|
-
);
|
|
986
|
-
}
|
|
987
|
-
|
|
988
|
-
/**
|
|
989
|
-
* Identifies the set index into which a message set would pertain if its first item's creation date corresponded to oldestTimestampMs.
|
|
990
|
-
* @param oldestTimestampMs
|
|
991
|
-
*/
|
|
992
|
-
private findMessageSetByOldestTimestamp = (oldestTimestampMs: number): number => {
|
|
993
|
-
let lo = 0,
|
|
994
|
-
hi = this.messageSets.length;
|
|
995
|
-
while (lo < hi) {
|
|
996
|
-
const mid = (lo + hi) >>> 1;
|
|
997
|
-
const msgSet = this.messageSets[mid];
|
|
998
|
-
// should not happen
|
|
999
|
-
if (msgSet.messages.length === 0) return -1;
|
|
1000
|
-
|
|
1001
|
-
const oldestMessageTimestampInSet = msgSet.messages[0].created_at.getTime();
|
|
1002
|
-
if (oldestMessageTimestampInSet <= oldestTimestampMs) hi = mid;
|
|
1003
|
-
else lo = mid + 1;
|
|
1004
|
-
}
|
|
1005
|
-
return lo;
|
|
1006
|
-
};
|
|
1007
|
-
|
|
1008
|
-
private findTargetMessageSet(
|
|
1009
|
-
newMessages: (MessageResponse | LocalMessage)[],
|
|
1010
|
-
addIfDoesNotExist = true,
|
|
1011
|
-
messageSetToAddToIfDoesNotExist: MessageSetType = 'current',
|
|
1012
|
-
) {
|
|
1013
|
-
let messagesToAdd: (MessageResponse | LocalMessage)[] = newMessages;
|
|
1014
|
-
let targetMessageSetIndex!: number;
|
|
1015
|
-
if (newMessages.length === 0)
|
|
1016
|
-
return { targetMessageSetIndex: 0, messagesToAdd: newMessages };
|
|
1017
|
-
if (addIfDoesNotExist) {
|
|
1018
|
-
const overlappingMessageSetIndicesByMsgIds = this.messageSets
|
|
1019
|
-
.map((_, i) => i)
|
|
1020
|
-
.filter((i) =>
|
|
1021
|
-
this.areMessageSetsOverlap(this.messageSets[i].messages, newMessages),
|
|
1022
|
-
);
|
|
1023
|
-
const overlappingMessageSetIndicesByTimestamp = this.messageSets
|
|
1024
|
-
.map((_, i) => i)
|
|
1025
|
-
.filter((i) =>
|
|
1026
|
-
messageSetsOverlapByTimestamp(
|
|
1027
|
-
this.messageSets[i].messages,
|
|
1028
|
-
newMessages.map(formatMessage),
|
|
1029
|
-
),
|
|
1030
|
-
);
|
|
1031
|
-
switch (messageSetToAddToIfDoesNotExist) {
|
|
1032
|
-
case 'new':
|
|
1033
|
-
if (overlappingMessageSetIndicesByMsgIds.length > 0) {
|
|
1034
|
-
targetMessageSetIndex = overlappingMessageSetIndicesByMsgIds[0];
|
|
1035
|
-
} else if (overlappingMessageSetIndicesByTimestamp.length > 0) {
|
|
1036
|
-
targetMessageSetIndex = overlappingMessageSetIndicesByTimestamp[0];
|
|
1037
|
-
// No new message set is created if newMessages only contains thread replies
|
|
1038
|
-
} else if (newMessages.some((m) => !m.parent_id)) {
|
|
1039
|
-
// find the index to insert the set
|
|
1040
|
-
const setIngestIndex = this.findMessageSetByOldestTimestamp(
|
|
1041
|
-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
1042
|
-
new Date(newMessages[0].created_at!).getTime(),
|
|
1043
|
-
);
|
|
1044
|
-
if (setIngestIndex === -1) {
|
|
1045
|
-
this.messageSets.push({
|
|
1046
|
-
messages: [],
|
|
1047
|
-
isCurrent: false,
|
|
1048
|
-
isLatest: false,
|
|
1049
|
-
pagination: { ...DEFAULT_MESSAGE_SET_PAGINATION },
|
|
1050
|
-
});
|
|
1051
|
-
targetMessageSetIndex = this.messageSets.length - 1;
|
|
1052
|
-
} else {
|
|
1053
|
-
const isLatest = setIngestIndex === 0;
|
|
1054
|
-
this.messageSets.splice(setIngestIndex, 0, {
|
|
1055
|
-
messages: [],
|
|
1056
|
-
isCurrent: false,
|
|
1057
|
-
isLatest,
|
|
1058
|
-
pagination: { ...DEFAULT_MESSAGE_SET_PAGINATION }, // fixme: it is problematic decide about pagination without having data
|
|
1059
|
-
});
|
|
1060
|
-
if (isLatest) {
|
|
1061
|
-
this.messageSets.slice(1).forEach((set) => {
|
|
1062
|
-
set.isLatest = false;
|
|
1063
|
-
});
|
|
1064
|
-
}
|
|
1065
|
-
targetMessageSetIndex = setIngestIndex;
|
|
1066
|
-
}
|
|
1067
|
-
}
|
|
1068
|
-
break;
|
|
1069
|
-
case 'current':
|
|
1070
|
-
// determine if there is another set to which it would match taken into consideration the timestamp
|
|
1071
|
-
if (overlappingMessageSetIndicesByTimestamp.length > 0) {
|
|
1072
|
-
targetMessageSetIndex = overlappingMessageSetIndicesByTimestamp[0];
|
|
1073
|
-
} else {
|
|
1074
|
-
targetMessageSetIndex = this.messageSets.findIndex((s) => s.isCurrent);
|
|
1075
|
-
}
|
|
1076
|
-
break;
|
|
1077
|
-
case 'latest':
|
|
1078
|
-
// determine if there is another set to which it would match taken into consideration the timestamp
|
|
1079
|
-
if (overlappingMessageSetIndicesByTimestamp.length > 0) {
|
|
1080
|
-
targetMessageSetIndex = overlappingMessageSetIndicesByTimestamp[0];
|
|
1081
|
-
} else {
|
|
1082
|
-
targetMessageSetIndex = this.messageSets.findIndex((s) => s.isLatest);
|
|
1083
|
-
}
|
|
1084
|
-
break;
|
|
1085
|
-
default:
|
|
1086
|
-
targetMessageSetIndex = -1;
|
|
1087
|
-
}
|
|
1088
|
-
// when merging the target set will be the first one from the overlapping message sets
|
|
1089
|
-
const mergeTargetMessageSetIndex = overlappingMessageSetIndicesByMsgIds.splice(
|
|
1090
|
-
0,
|
|
1091
|
-
1,
|
|
1092
|
-
)[0];
|
|
1093
|
-
const mergeSourceMessageSetIndices = [...overlappingMessageSetIndicesByMsgIds];
|
|
1094
|
-
if (
|
|
1095
|
-
mergeTargetMessageSetIndex !== undefined &&
|
|
1096
|
-
mergeTargetMessageSetIndex !== targetMessageSetIndex
|
|
1097
|
-
) {
|
|
1098
|
-
mergeSourceMessageSetIndices.push(targetMessageSetIndex);
|
|
1099
|
-
}
|
|
1100
|
-
// merge message sets
|
|
1101
|
-
if (mergeSourceMessageSetIndices.length > 0) {
|
|
1102
|
-
const target = this.messageSets[mergeTargetMessageSetIndex];
|
|
1103
|
-
const sources = this.messageSets.filter(
|
|
1104
|
-
(_, i) => mergeSourceMessageSetIndices.indexOf(i) !== -1,
|
|
1105
|
-
);
|
|
1106
|
-
sources.forEach((messageSet) => {
|
|
1107
|
-
target.isLatest = target.isLatest || messageSet.isLatest;
|
|
1108
|
-
target.isCurrent = target.isCurrent || messageSet.isCurrent;
|
|
1109
|
-
target.pagination.hasPrev =
|
|
1110
|
-
messageSet.messages[0].created_at < target.messages[0].created_at
|
|
1111
|
-
? messageSet.pagination.hasPrev
|
|
1112
|
-
: target.pagination.hasPrev;
|
|
1113
|
-
target.pagination.hasNext =
|
|
1114
|
-
target.messages.slice(-1)[0].created_at <
|
|
1115
|
-
messageSet.messages.slice(-1)[0].created_at
|
|
1116
|
-
? messageSet.pagination.hasNext
|
|
1117
|
-
: target.pagination.hasNext;
|
|
1118
|
-
messagesToAdd = [...messagesToAdd, ...messageSet.messages];
|
|
1119
|
-
});
|
|
1120
|
-
sources.forEach((s) => this.messageSets.splice(this.messageSets.indexOf(s), 1));
|
|
1121
|
-
const overlappingMessageSetIndex = this.messageSets.findIndex((s) =>
|
|
1122
|
-
this.areMessageSetsOverlap(s.messages, newMessages),
|
|
1123
|
-
);
|
|
1124
|
-
targetMessageSetIndex = overlappingMessageSetIndex;
|
|
1125
|
-
}
|
|
1126
|
-
} else {
|
|
1127
|
-
// assumes that all new messages belong to the same set
|
|
1128
|
-
targetMessageSetIndex = this.findMessageSetIndex(newMessages[0]);
|
|
1129
|
-
}
|
|
1130
|
-
|
|
1131
|
-
return { targetMessageSetIndex, messagesToAdd };
|
|
1132
|
-
}
|
|
1133
264
|
}
|