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.ts
CHANGED
|
@@ -1,101 +1,163 @@
|
|
|
1
1
|
import type { AxiosRequestConfig } from 'axios';
|
|
2
2
|
import { ChannelState } from './channel_state';
|
|
3
3
|
import { CooldownTimer } from './CooldownTimer';
|
|
4
|
+
import { isEphemeral } from './errors';
|
|
5
|
+
import { applyReactionLocally } from './entityStore';
|
|
4
6
|
import { MessageComposer } from './messageComposer';
|
|
5
7
|
import { MessageReceiptsTracker } from './messageDelivery';
|
|
8
|
+
import type { ReadStoreReconcileMeta } from './messageDelivery';
|
|
9
|
+
import { MessagePaginator, PinnedMessagePaginator } from './pagination/paginators';
|
|
10
|
+
import { MessageOperations } from './messageOperations';
|
|
6
11
|
import {
|
|
7
12
|
channelHasReadEvents,
|
|
13
|
+
formatMessage,
|
|
8
14
|
generateChannelTempCid,
|
|
15
|
+
localMessageToNewMessagePayload,
|
|
9
16
|
logChatPromiseExecution,
|
|
10
|
-
messageSetPagination,
|
|
11
|
-
normalizeQuerySort,
|
|
12
17
|
} from './utils';
|
|
13
18
|
import type { StreamChat } from './client';
|
|
19
|
+
import { chatLoggerSystem } from './logger';
|
|
14
20
|
import { DEFAULT_QUERY_CHANNEL_MESSAGE_LIST_PAGE_SIZE } from './constants';
|
|
15
21
|
import type {
|
|
16
22
|
AIState,
|
|
17
23
|
APIResponse,
|
|
18
|
-
AscDesc,
|
|
19
24
|
BanUserOptions,
|
|
20
|
-
ChannelAPIResponse,
|
|
21
25
|
ChannelData,
|
|
22
|
-
|
|
23
|
-
ChannelMemberAPIResponse,
|
|
26
|
+
ChannelGetOrCreateRequest,
|
|
24
27
|
ChannelMemberResponse,
|
|
25
|
-
ChannelPushPreference,
|
|
26
|
-
ChannelQueryOptions,
|
|
27
28
|
ChannelResponse,
|
|
29
|
+
ChannelStateResponseFields,
|
|
28
30
|
ChannelUpdateOptions,
|
|
29
31
|
CreateDraftResponse,
|
|
30
|
-
|
|
31
|
-
DraftMessagePayload,
|
|
32
|
+
DeleteMessageOptions,
|
|
32
33
|
Event,
|
|
33
34
|
EventAPIResponse,
|
|
34
35
|
EventHandler,
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
GetMultipleMessagesAPIResponse,
|
|
38
|
-
GetReactionsAPIResponse,
|
|
36
|
+
EventPayload,
|
|
37
|
+
EventType,
|
|
39
38
|
GetRepliesAPIResponse,
|
|
40
|
-
|
|
39
|
+
GetRepliesRequest,
|
|
41
40
|
LocalMessage,
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
MemberFilters,
|
|
45
|
-
MemberSort,
|
|
46
|
-
Message,
|
|
47
|
-
MessageFilters,
|
|
48
|
-
MessageOptions,
|
|
41
|
+
MarkReadRequest,
|
|
42
|
+
MarkUnreadRequest,
|
|
49
43
|
MessagePaginationOptions,
|
|
44
|
+
MessageRequest,
|
|
50
45
|
MessageResponse,
|
|
51
46
|
MessageSetType,
|
|
52
|
-
MuteChannelAPIResponse,
|
|
53
|
-
NewMemberPayload,
|
|
54
|
-
PartialUpdateChannel,
|
|
55
|
-
PartialUpdateChannelAPIResponse,
|
|
56
|
-
PartialUpdateMember,
|
|
57
|
-
PartialUpdateMemberAPIResponse,
|
|
58
47
|
PinnedMessagePaginationOptions,
|
|
59
48
|
PinnedMessagesSort,
|
|
60
|
-
|
|
61
|
-
QueryChannelAPIResponse,
|
|
62
|
-
QueryMembersOptions,
|
|
63
|
-
Reaction,
|
|
49
|
+
QueryMembersPayload,
|
|
64
50
|
ReactionAPIResponse,
|
|
65
|
-
|
|
66
|
-
SearchMessageSortBase,
|
|
67
|
-
SearchOptions,
|
|
51
|
+
ReactionRequest,
|
|
68
52
|
SearchPayload,
|
|
69
|
-
SendMessageAPIResponse,
|
|
70
53
|
SendMessageOptions,
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
TruncateChannelAPIResponse,
|
|
74
|
-
TruncateOptions,
|
|
54
|
+
SendReactionRequest,
|
|
55
|
+
SharedLocation,
|
|
75
56
|
UnBanUserOptions,
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
57
|
+
UpdateChannelPartialRequest,
|
|
58
|
+
UpdateLiveLocationRequest,
|
|
59
|
+
UpdateMessageOptions,
|
|
79
60
|
UserResponse,
|
|
80
61
|
} from './types';
|
|
81
|
-
import type {
|
|
82
|
-
import
|
|
62
|
+
import type { RoleName } from './permissions';
|
|
63
|
+
import { StateStore } from './store';
|
|
64
|
+
import type {
|
|
65
|
+
ChannelMemberRequest as Gen_ChannelMemberRequest,
|
|
66
|
+
ChannelPushPreferencesResponse as Gen_ChannelPushPreferencesResponse,
|
|
67
|
+
ChannelStopWatchingRequest as Gen_ChannelStopWatchingRequest,
|
|
68
|
+
CreateDraftRequest as Gen_CreateDraftRequest,
|
|
69
|
+
HideChannelRequest as Gen_HideChannelRequest,
|
|
70
|
+
MuteChannelRequest as Gen_MuteChannelRequest,
|
|
71
|
+
SendMessageRequest as Gen_SendMessageRequest,
|
|
72
|
+
ShowChannelRequest as Gen_ShowChannelRequest,
|
|
73
|
+
UnmuteChannelRequest as Gen_UnmuteChannelRequest,
|
|
74
|
+
UpdateChannelRequest as Gen_UpdateChannelRequest,
|
|
75
|
+
WSEvent,
|
|
76
|
+
} from './gen/models';
|
|
77
|
+
import type { ChatApi } from './gen/chat/ChatApi';
|
|
78
|
+
import { ChannelApi } from './gen/chat/ChannelApi';
|
|
79
|
+
|
|
80
|
+
const logger = chatLoggerSystem.getLogger('channel');
|
|
81
|
+
const offlineDbLogger = chatLoggerSystem.getLogger('offline-db');
|
|
82
|
+
|
|
83
|
+
// todo: move to dedicated file
|
|
84
|
+
export type SendMessageWithStateUpdateParams = {
|
|
85
|
+
localMessage: LocalMessage;
|
|
86
|
+
message?: MessageRequest;
|
|
87
|
+
options?: SendMessageOptions;
|
|
88
|
+
/**
|
|
89
|
+
* Per-call override for the send/retry request (advanced).
|
|
90
|
+
* If set, it takes precedence over channel instance configuration handlers.
|
|
91
|
+
*/
|
|
92
|
+
sendMessageRequestFn?: CustomSendMessageRequestFn;
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
export type RetrySendMessageWithLocalUpdateParams = Omit<
|
|
96
|
+
SendMessageWithStateUpdateParams,
|
|
97
|
+
'message'
|
|
98
|
+
>;
|
|
99
|
+
|
|
100
|
+
export type UpdateMessageWithStateUpdateParams = {
|
|
101
|
+
localMessage: LocalMessage;
|
|
102
|
+
options?: UpdateMessageOptions;
|
|
103
|
+
/**
|
|
104
|
+
* Per-call override for the update request (advanced).
|
|
105
|
+
* If set, it takes precedence over channel instance configuration handlers.
|
|
106
|
+
*/
|
|
107
|
+
updateMessageRequestFn?: CustomUpdateMessageRequestFn;
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
export type DeleteMessageWithStateUpdateParams = {
|
|
111
|
+
localMessage: LocalMessage;
|
|
112
|
+
options?: DeleteMessageOptions;
|
|
113
|
+
/**
|
|
114
|
+
* Per-call override for the delete request (advanced).
|
|
115
|
+
* If set, it takes precedence over channel instance configuration handlers.
|
|
116
|
+
*/
|
|
117
|
+
deleteMessageRequestFn?: CustomDeleteMessageRequestFn;
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
// Custom request function types for configuration
|
|
121
|
+
export type CustomSendMessageRequestFn = (
|
|
122
|
+
params: Omit<SendMessageWithStateUpdateParams, 'sendMessageRequestFn'>,
|
|
123
|
+
) => Promise<{ message: MessageResponse }>;
|
|
124
|
+
|
|
125
|
+
export type CustomUpdateMessageRequestFn = (
|
|
126
|
+
params: Omit<UpdateMessageWithStateUpdateParams, 'updateMessageRequestFn'>,
|
|
127
|
+
) => Promise<{ message: MessageResponse }>;
|
|
128
|
+
|
|
129
|
+
export type CustomDeleteMessageRequestFn = (
|
|
130
|
+
params: Omit<DeleteMessageWithStateUpdateParams, 'deleteMessageRequestFn'>,
|
|
131
|
+
) => Promise<{ message: MessageResponse }>;
|
|
132
|
+
|
|
133
|
+
export type CustomMarkReadRequestFn = (params: {
|
|
134
|
+
channel: Channel;
|
|
135
|
+
options?: MarkReadRequest;
|
|
136
|
+
}) => Promise<EventAPIResponse | null>;
|
|
137
|
+
|
|
138
|
+
export type ChannelInstanceConfig = {
|
|
139
|
+
requestHandlers?: {
|
|
140
|
+
deleteMessageRequest?: CustomDeleteMessageRequestFn;
|
|
141
|
+
markReadRequest?: CustomMarkReadRequestFn;
|
|
142
|
+
sendMessageRequest?: CustomSendMessageRequestFn;
|
|
143
|
+
retrySendMessageRequest?: CustomSendMessageRequestFn;
|
|
144
|
+
updateMessageRequest?: CustomUpdateMessageRequestFn;
|
|
145
|
+
};
|
|
146
|
+
};
|
|
83
147
|
|
|
84
148
|
/**
|
|
85
|
-
*
|
|
149
|
+
* The Channel class manages its own state.
|
|
86
150
|
*/
|
|
87
|
-
export class Channel {
|
|
151
|
+
export class Channel extends ChannelApi {
|
|
88
152
|
_client: StreamChat;
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
data: Partial<ChannelData & ChannelResponse> | undefined;
|
|
92
|
-
_data: Partial<ChannelData & ChannelResponse>;
|
|
153
|
+
data: Partial<ChannelResponse> | undefined;
|
|
154
|
+
_data: ChannelData;
|
|
93
155
|
cid: string;
|
|
94
156
|
/** */
|
|
95
|
-
listeners:
|
|
157
|
+
listeners: Map<EventType, Set<EventHandler>>;
|
|
96
158
|
state: ChannelState;
|
|
97
159
|
/**
|
|
98
|
-
* This boolean is a vague indication of
|
|
160
|
+
* This boolean is a vague indication of whether the channel exists on chat backend.
|
|
99
161
|
*
|
|
100
162
|
* If the value is true, then that means the channel has been initialized by either calling
|
|
101
163
|
* channel.create() or channel.query() or channel.watch().
|
|
@@ -105,7 +167,7 @@ export class Channel {
|
|
|
105
167
|
*/
|
|
106
168
|
initialized: boolean;
|
|
107
169
|
/**
|
|
108
|
-
* Indicates
|
|
170
|
+
* Indicates whether channel has been initialized by manually populating the state with some messages, members etc.
|
|
109
171
|
* Static state indicates that channel exists on backend, but is not being watched yet.
|
|
110
172
|
*/
|
|
111
173
|
offlineMode: boolean;
|
|
@@ -113,20 +175,23 @@ export class Channel {
|
|
|
113
175
|
lastTypingEvent: Date | null;
|
|
114
176
|
isTyping: boolean;
|
|
115
177
|
disconnected: boolean;
|
|
116
|
-
push_preferences?:
|
|
178
|
+
push_preferences?: Gen_ChannelPushPreferencesResponse;
|
|
179
|
+
public readonly configState = new StateStore<ChannelInstanceConfig>({});
|
|
117
180
|
public readonly messageComposer: MessageComposer;
|
|
118
181
|
public readonly messageReceiptsTracker: MessageReceiptsTracker;
|
|
182
|
+
public readonly messagePaginator: MessagePaginator;
|
|
183
|
+
public readonly pinnedMessagesPaginator: PinnedMessagePaginator;
|
|
184
|
+
public readonly messageOperations: MessageOperations;
|
|
119
185
|
public readonly cooldownTimer: CooldownTimer;
|
|
120
186
|
|
|
121
187
|
/**
|
|
122
|
-
*
|
|
188
|
+
* Creates a `Channel` instance bound to the given chat client.
|
|
123
189
|
*
|
|
124
|
-
* @param
|
|
125
|
-
* @param
|
|
126
|
-
* @param
|
|
127
|
-
* @param
|
|
128
|
-
*
|
|
129
|
-
* @return {Channel} Returns a new uninitialized channel
|
|
190
|
+
* @param client - The chat client.
|
|
191
|
+
* @param type - The type of channel.
|
|
192
|
+
* @param id - The ID of the chat (optional).
|
|
193
|
+
* @param data - Any additional custom params.
|
|
194
|
+
* @returns A new uninitialized channel.
|
|
130
195
|
*/
|
|
131
196
|
constructor(
|
|
132
197
|
client: StreamChat,
|
|
@@ -144,15 +209,15 @@ export class Channel {
|
|
|
144
209
|
throw new Error(`Invalid chat id ${id}, letters, numbers and "!-_" are allowed`);
|
|
145
210
|
}
|
|
146
211
|
|
|
212
|
+
super(client, type, id);
|
|
213
|
+
|
|
147
214
|
this._client = client;
|
|
148
|
-
this.type = type;
|
|
149
|
-
this.id = id;
|
|
150
215
|
// used by the frontend, gets updated:
|
|
151
|
-
this.data = data
|
|
216
|
+
this.data = data as Partial<ChannelResponse>;
|
|
152
217
|
// this._data is used for the requests...
|
|
153
218
|
this._data = { ...data };
|
|
154
219
|
this.cid = `${type}:${id}`;
|
|
155
|
-
this.listeners =
|
|
220
|
+
this.listeners = new Map();
|
|
156
221
|
// perhaps the state variable should be private
|
|
157
222
|
this.state = new ChannelState(this);
|
|
158
223
|
this.initialized = false;
|
|
@@ -166,20 +231,87 @@ export class Channel {
|
|
|
166
231
|
compositionContext: this,
|
|
167
232
|
});
|
|
168
233
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
234
|
+
// Created before MessageReceiptsTracker and CooldownTimer: both read the message paginator
|
|
235
|
+
// (receipts resolve read cursors via findItemByTimestamp; CooldownTimer.refresh reads the
|
|
236
|
+
// latest window at construction).
|
|
237
|
+
this.messagePaginator = new MessagePaginator({ channel: this });
|
|
238
|
+
this.pinnedMessagesPaginator = new PinnedMessagePaginator({ channel: this });
|
|
239
|
+
|
|
240
|
+
this.messageReceiptsTracker = new MessageReceiptsTracker({ channel: this });
|
|
241
|
+
this.messageReceiptsTracker.registerSubscriptions();
|
|
175
242
|
|
|
176
243
|
this.cooldownTimer = new CooldownTimer({ channel: this });
|
|
244
|
+
|
|
245
|
+
this.messageOperations = new MessageOperations({
|
|
246
|
+
ingest: (m) => {
|
|
247
|
+
this.messagePaginator.ingestItem(m);
|
|
248
|
+
this.getClient().messageStore.flushSubscribers(m.id);
|
|
249
|
+
},
|
|
250
|
+
get: (id) => this.messagePaginator.getItem(id),
|
|
251
|
+
handlers: () => {
|
|
252
|
+
const { requestHandlers } = this.configState.getLatestValue();
|
|
253
|
+
const deleteMessageRequest = requestHandlers?.deleteMessageRequest;
|
|
254
|
+
const sendMessageRequest = requestHandlers?.sendMessageRequest;
|
|
255
|
+
const retrySendMessageRequest = requestHandlers?.retrySendMessageRequest;
|
|
256
|
+
const updateMessageRequest = requestHandlers?.updateMessageRequest;
|
|
257
|
+
return {
|
|
258
|
+
delete: deleteMessageRequest
|
|
259
|
+
? (p) =>
|
|
260
|
+
deleteMessageRequest({
|
|
261
|
+
localMessage: p.localMessage,
|
|
262
|
+
options: p.options,
|
|
263
|
+
})
|
|
264
|
+
: undefined,
|
|
265
|
+
send: sendMessageRequest
|
|
266
|
+
? (p) =>
|
|
267
|
+
sendMessageRequest({
|
|
268
|
+
localMessage: p.localMessage,
|
|
269
|
+
message: p.message,
|
|
270
|
+
options: p.options,
|
|
271
|
+
})
|
|
272
|
+
: undefined,
|
|
273
|
+
retry: retrySendMessageRequest
|
|
274
|
+
? (p) =>
|
|
275
|
+
retrySendMessageRequest({
|
|
276
|
+
localMessage: p.localMessage,
|
|
277
|
+
message: p.message,
|
|
278
|
+
options: p.options,
|
|
279
|
+
})
|
|
280
|
+
: undefined,
|
|
281
|
+
update: updateMessageRequest
|
|
282
|
+
? (p) =>
|
|
283
|
+
updateMessageRequest({
|
|
284
|
+
localMessage: p.localMessage,
|
|
285
|
+
options: p.options,
|
|
286
|
+
})
|
|
287
|
+
: undefined,
|
|
288
|
+
};
|
|
289
|
+
},
|
|
290
|
+
defaults: {
|
|
291
|
+
delete: async (id, o) => {
|
|
292
|
+
const result = await this.getClient().deleteMessage({ id, ...o });
|
|
293
|
+
return { message: result.message };
|
|
294
|
+
},
|
|
295
|
+
send: async (m, o) => {
|
|
296
|
+
const result = await this.sendMessage({ message: m, ...o });
|
|
297
|
+
return { message: result.message };
|
|
298
|
+
},
|
|
299
|
+
update: async (m, o) => {
|
|
300
|
+
const result = await this.getClient().updateMessage({
|
|
301
|
+
id: m.id,
|
|
302
|
+
message: localMessageToNewMessagePayload(m),
|
|
303
|
+
...o,
|
|
304
|
+
});
|
|
305
|
+
return { message: result.message };
|
|
306
|
+
},
|
|
307
|
+
},
|
|
308
|
+
});
|
|
177
309
|
}
|
|
178
310
|
|
|
179
311
|
/**
|
|
180
|
-
*
|
|
312
|
+
* Returns the chat client for this channel. Throws if `client.disconnect()` was called.
|
|
181
313
|
*
|
|
182
|
-
* @
|
|
314
|
+
* @returns The chat client.
|
|
183
315
|
*/
|
|
184
316
|
getClient(): StreamChat {
|
|
185
317
|
if (this.disconnected === true) {
|
|
@@ -189,73 +321,186 @@ export class Channel {
|
|
|
189
321
|
}
|
|
190
322
|
|
|
191
323
|
/**
|
|
192
|
-
*
|
|
324
|
+
* Returns the config for this channel ID (CID).
|
|
193
325
|
*
|
|
194
|
-
* @
|
|
326
|
+
* @returns The channel config.
|
|
195
327
|
*/
|
|
196
328
|
getConfig() {
|
|
197
329
|
const client = this.getClient();
|
|
198
330
|
return client.configs[this.cid];
|
|
199
331
|
}
|
|
200
332
|
|
|
333
|
+
_sendMessage(request: Gen_SendMessageRequest) {
|
|
334
|
+
return super.sendMessage(request);
|
|
335
|
+
}
|
|
336
|
+
|
|
201
337
|
/**
|
|
202
|
-
*
|
|
338
|
+
* Sends a message to this channel.
|
|
203
339
|
*
|
|
204
|
-
* @param
|
|
205
|
-
*
|
|
206
|
-
* @
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
340
|
+
* @param request - The send message request payload, including the message body and optional flags
|
|
341
|
+
* such as `skip_enrich_url`, `skip_push`, and `keep_channel_hidden`.
|
|
342
|
+
* @returns The server response.
|
|
343
|
+
*/
|
|
344
|
+
override async sendMessage(request: Gen_SendMessageRequest) {
|
|
345
|
+
try {
|
|
346
|
+
const offlineDb = this.getClient().offlineDb;
|
|
347
|
+
const messageId = request.message?.id;
|
|
348
|
+
if (offlineDb && messageId) {
|
|
349
|
+
return await offlineDb.queueTask<Awaited<ReturnType<ChatApi['sendMessage']>>>({
|
|
350
|
+
task: {
|
|
351
|
+
channelId: this.id as string,
|
|
352
|
+
channelType: this.type,
|
|
353
|
+
messageId,
|
|
354
|
+
payload: [request],
|
|
355
|
+
type: 'send-message',
|
|
356
|
+
},
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
} catch (error) {
|
|
360
|
+
offlineDbLogger
|
|
361
|
+
.withExtraTags('sendMessage', this.cid)
|
|
362
|
+
.error('Sending the message failed.', { error });
|
|
363
|
+
}
|
|
364
|
+
return await this._sendMessage(request);
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* Sends a message with optimistic local state update.
|
|
213
369
|
*/
|
|
214
|
-
async
|
|
215
|
-
|
|
216
|
-
|
|
370
|
+
async sendMessageWithLocalUpdate(
|
|
371
|
+
params: SendMessageWithStateUpdateParams,
|
|
372
|
+
): Promise<void> {
|
|
373
|
+
await this.messageOperations.send(
|
|
217
374
|
{
|
|
218
|
-
|
|
219
|
-
|
|
375
|
+
localMessage: params.localMessage,
|
|
376
|
+
message: params.message,
|
|
377
|
+
options: params.options,
|
|
378
|
+
},
|
|
379
|
+
params.sendMessageRequestFn,
|
|
380
|
+
);
|
|
381
|
+
if (this.messageComposer.config.text.publishTypingEvents) await this.stopTyping();
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
/**
|
|
385
|
+
* Retry sending a failed message.
|
|
386
|
+
*/
|
|
387
|
+
async retrySendMessageWithLocalUpdate(
|
|
388
|
+
params: Omit<SendMessageWithStateUpdateParams, 'message'>,
|
|
389
|
+
) {
|
|
390
|
+
await this.messageOperations.retry(
|
|
391
|
+
{
|
|
392
|
+
localMessage: { ...params.localMessage, type: 'regular' },
|
|
393
|
+
options: params.options,
|
|
220
394
|
},
|
|
395
|
+
params.sendMessageRequestFn,
|
|
221
396
|
);
|
|
222
397
|
}
|
|
223
398
|
|
|
224
|
-
|
|
399
|
+
/**
|
|
400
|
+
* Updates a message with optimistic local state update.
|
|
401
|
+
*/
|
|
402
|
+
async updateMessageWithLocalUpdate(params: UpdateMessageWithStateUpdateParams) {
|
|
403
|
+
await this.messageOperations.update(
|
|
404
|
+
{
|
|
405
|
+
localMessage: params.localMessage,
|
|
406
|
+
options: params.options,
|
|
407
|
+
},
|
|
408
|
+
params.updateMessageRequestFn,
|
|
409
|
+
);
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
/**
|
|
413
|
+
* Deletes a message with local state update.
|
|
414
|
+
*/
|
|
415
|
+
async deleteMessageWithLocalUpdate(params: DeleteMessageWithStateUpdateParams) {
|
|
416
|
+
await this.messageOperations.delete(
|
|
417
|
+
{
|
|
418
|
+
localMessage: params.localMessage,
|
|
419
|
+
options: params.options,
|
|
420
|
+
},
|
|
421
|
+
params.deleteMessageRequestFn,
|
|
422
|
+
);
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
/**
|
|
426
|
+
* Adds a reaction with an optimistic local state update: the reaction is applied to the cached
|
|
427
|
+
* message immediately ({@link applyReactionLocally}), then the request is
|
|
428
|
+
* fired via {@link Channel.sendReaction} (which owns the offline-DB write + queue). The
|
|
429
|
+
* server-authoritative counts reconcile on the response; the message is rolled back on failure.
|
|
430
|
+
*/
|
|
431
|
+
async addReactionWithLocalUpdate({
|
|
432
|
+
messageId,
|
|
433
|
+
reaction,
|
|
434
|
+
options,
|
|
435
|
+
}: {
|
|
436
|
+
messageId: string;
|
|
437
|
+
reaction: ReactionRequest;
|
|
438
|
+
options?: Pick<SendReactionRequest, 'enforce_unique' | 'skip_push'>;
|
|
439
|
+
}) {
|
|
440
|
+
const client = this.getClient();
|
|
441
|
+
const undo = applyReactionLocally(client, {
|
|
442
|
+
enforceUnique: options?.enforce_unique ?? false,
|
|
443
|
+
messageId,
|
|
444
|
+
reaction,
|
|
445
|
+
});
|
|
446
|
+
|
|
225
447
|
try {
|
|
226
|
-
const
|
|
227
|
-
if
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
task: {
|
|
232
|
-
channelId: this.id as string,
|
|
233
|
-
channelType: this.type,
|
|
234
|
-
messageId,
|
|
235
|
-
payload: [message, options],
|
|
236
|
-
type: 'send-message',
|
|
237
|
-
},
|
|
238
|
-
});
|
|
239
|
-
}
|
|
448
|
+
const response = await this.sendReaction({ id: messageId, reaction, ...options });
|
|
449
|
+
// reconcile the server copy only if we still hold it — a bare upsert of an unheld id would
|
|
450
|
+
// orphan it (the store's refcount GC only reclaims held ids).
|
|
451
|
+
if (response?.message && client.messageStore.has(response.message.id)) {
|
|
452
|
+
client.messageStore.upsert(formatMessage(response.message));
|
|
240
453
|
}
|
|
241
454
|
} catch (error) {
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
455
|
+
if (undo && (!client.offlineDb || !isEphemeral(error as Error))) {
|
|
456
|
+
undo();
|
|
457
|
+
}
|
|
458
|
+
throw error;
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
/**
|
|
463
|
+
* Removes the current user's reaction with an optimistic local state update, mirroring
|
|
464
|
+
* {@link Channel.addReactionWithLocalUpdate}.
|
|
465
|
+
*/
|
|
466
|
+
async deleteReactionWithLocalUpdate({
|
|
467
|
+
messageId,
|
|
468
|
+
type,
|
|
469
|
+
}: {
|
|
470
|
+
messageId: string;
|
|
471
|
+
type: string;
|
|
472
|
+
}) {
|
|
473
|
+
const client = this.getClient();
|
|
474
|
+
const undo = applyReactionLocally(client, {
|
|
475
|
+
messageId,
|
|
476
|
+
reaction: { type },
|
|
477
|
+
removed: true,
|
|
478
|
+
});
|
|
479
|
+
|
|
480
|
+
try {
|
|
481
|
+
const response = await this.deleteReaction({ id: messageId, type });
|
|
482
|
+
// reconcile the server copy only if we still hold it — a bare upsert of an unheld id would
|
|
483
|
+
// orphan it (the store's refcount GC only reclaims held ids).
|
|
484
|
+
if (response?.message && client.messageStore.has(response.message.id)) {
|
|
485
|
+
client.messageStore.upsert(formatMessage(response.message));
|
|
486
|
+
}
|
|
487
|
+
} catch (error) {
|
|
488
|
+
if (undo && (!client.offlineDb || !isEphemeral(error as Error))) {
|
|
489
|
+
undo();
|
|
490
|
+
}
|
|
491
|
+
throw error;
|
|
246
492
|
}
|
|
247
|
-
return await this._sendMessage(message, options);
|
|
248
493
|
}
|
|
249
494
|
|
|
250
495
|
/**
|
|
251
496
|
* Upload a file to this channel’s file endpoint (multipart). Forwards to the client’s `sendFile` implementation.
|
|
252
497
|
*
|
|
253
|
-
* @param uri File source: URL string, `File`, `Buffer`, or readable stream (Node).
|
|
254
|
-
* @param name File name sent in the multipart body.
|
|
255
|
-
* @param contentType MIME type; defaults are applied when omitted.
|
|
256
|
-
* @param user
|
|
257
|
-
* @param axiosRequestConfig
|
|
258
|
-
* @
|
|
498
|
+
* @param uri - File source: URL string, `File`, `Buffer`, or readable stream (Node).
|
|
499
|
+
* @param name - File name sent in the multipart body (optional).
|
|
500
|
+
* @param contentType - MIME type; defaults are applied when omitted (optional).
|
|
501
|
+
* @param user - User payload appended to the form as JSON (optional).
|
|
502
|
+
* @param axiosRequestConfig - Axios per-request config, merged after upload defaults, e.g. `onUploadProgress`, `signal` from `AbortController` (optional).
|
|
503
|
+
* @returns A promise resolving to `{ file: string, ... }` with the CDN URL.
|
|
259
504
|
*/
|
|
260
505
|
sendFile(
|
|
261
506
|
uri: string | NodeJS.ReadableStream | Buffer | File,
|
|
@@ -264,7 +509,7 @@ export class Channel {
|
|
|
264
509
|
user?: UserResponse,
|
|
265
510
|
axiosRequestConfig?: AxiosRequestConfig,
|
|
266
511
|
) {
|
|
267
|
-
return this.getClient().sendFile(
|
|
512
|
+
return this.getClient().api.sendFile(
|
|
268
513
|
`${this._channelURL()}/file`,
|
|
269
514
|
uri,
|
|
270
515
|
name,
|
|
@@ -275,14 +520,14 @@ export class Channel {
|
|
|
275
520
|
}
|
|
276
521
|
|
|
277
522
|
/**
|
|
278
|
-
* Upload an image to this channel
|
|
523
|
+
* Upload an image to this channel's image endpoint (multipart). Uses the same transport as `sendFile`.
|
|
279
524
|
*
|
|
280
|
-
* @param uri Image source: URL string, `File`, or readable stream (Node). For `Buffer` uploads, use `sendFile` toward the channel file endpoint instead.
|
|
281
|
-
* @param name File name sent in the multipart body.
|
|
282
|
-
* @param contentType MIME type.
|
|
283
|
-
* @param user
|
|
284
|
-
* @param axiosRequestConfig
|
|
285
|
-
* @
|
|
525
|
+
* @param uri - Image source: URL string, `File`, or readable stream (Node). For `Buffer` uploads, use `sendFile` toward the channel file endpoint instead.
|
|
526
|
+
* @param name - File name sent in the multipart body (optional).
|
|
527
|
+
* @param contentType - MIME type (optional).
|
|
528
|
+
* @param user - User payload appended to the form as JSON (optional).
|
|
529
|
+
* @param axiosRequestConfig - Axios per-request config, merged after upload defaults, e.g. `onUploadProgress`, `signal` (optional).
|
|
530
|
+
* @returns A promise resolving to `{ file: string, ... }` with the CDN URL.
|
|
286
531
|
*/
|
|
287
532
|
sendImage(
|
|
288
533
|
uri: string | NodeJS.ReadableStream | File,
|
|
@@ -291,7 +536,7 @@ export class Channel {
|
|
|
291
536
|
user?: UserResponse,
|
|
292
537
|
axiosRequestConfig?: AxiosRequestConfig,
|
|
293
538
|
) {
|
|
294
|
-
return this.getClient().sendFile(
|
|
539
|
+
return this.getClient().api.sendFile(
|
|
295
540
|
`${this._channelURL()}/image`,
|
|
296
541
|
uri,
|
|
297
542
|
name,
|
|
@@ -302,427 +547,223 @@ export class Channel {
|
|
|
302
547
|
}
|
|
303
548
|
|
|
304
549
|
deleteFile(url: string) {
|
|
305
|
-
return this.
|
|
550
|
+
return this.deleteChannelFile({ url });
|
|
306
551
|
}
|
|
307
552
|
|
|
308
553
|
deleteImage(url: string) {
|
|
309
|
-
return this.
|
|
554
|
+
return this.deleteChannelImage({ url });
|
|
310
555
|
}
|
|
311
556
|
|
|
312
557
|
/**
|
|
313
|
-
*
|
|
314
|
-
*
|
|
315
|
-
* @param {Event} event for example {type: 'message.read'}
|
|
558
|
+
* Sends an event on this channel.
|
|
316
559
|
*
|
|
317
|
-
* @
|
|
560
|
+
* @param event - For example `{ type: 'message.read' }`.
|
|
561
|
+
* @returns The server response.
|
|
318
562
|
*/
|
|
319
|
-
async sendEvent(event: Event) {
|
|
563
|
+
override async sendEvent(request: { event: Event }) {
|
|
320
564
|
this._checkInitialized();
|
|
321
|
-
return await
|
|
322
|
-
event,
|
|
323
|
-
});
|
|
565
|
+
return await super.sendEvent(request);
|
|
324
566
|
}
|
|
325
567
|
|
|
326
568
|
/**
|
|
327
|
-
*
|
|
328
|
-
*
|
|
329
|
-
* @param {MessageFilters | string} query search query or object MongoDB style filters
|
|
330
|
-
* @param {{client_id?: string; connection_id?: string; query?: string; message_filter_conditions?: MessageFilters}} options Option object, {user_id: 'tommaso'}
|
|
569
|
+
* Queries messages.
|
|
331
570
|
*
|
|
332
|
-
* @
|
|
571
|
+
* @param request - The search request payload (optional). The inner `payload` accepts
|
|
572
|
+
* MongoDB-style filters and additional options such as `user_id`.
|
|
573
|
+
* @returns The search messages response.
|
|
333
574
|
*/
|
|
334
|
-
async search(
|
|
335
|
-
|
|
336
|
-
options: SearchOptions & {
|
|
337
|
-
client_id?: string;
|
|
338
|
-
connection_id?: string;
|
|
339
|
-
message_filter_conditions?: MessageFilters;
|
|
340
|
-
message_options?: MessageOptions;
|
|
341
|
-
query?: string;
|
|
342
|
-
} = {},
|
|
343
|
-
) {
|
|
344
|
-
if (options.offset && options.next) {
|
|
345
|
-
throw Error(`Cannot specify offset with next`);
|
|
346
|
-
}
|
|
347
|
-
// Return a list of channels
|
|
348
|
-
const payload: SearchPayload = {
|
|
349
|
-
filter_conditions: { cid: this.cid } as ChannelFilters,
|
|
350
|
-
...options,
|
|
351
|
-
sort: options.sort
|
|
352
|
-
? normalizeQuerySort<SearchMessageSortBase>(options.sort)
|
|
353
|
-
: undefined,
|
|
354
|
-
};
|
|
355
|
-
if (typeof query === 'string') {
|
|
356
|
-
payload.query = query;
|
|
357
|
-
} else if (typeof query === 'object') {
|
|
358
|
-
payload.message_filter_conditions = query;
|
|
359
|
-
} else {
|
|
360
|
-
throw Error(`Invalid type ${typeof query} for query parameter`);
|
|
361
|
-
}
|
|
362
|
-
// Make sure we wait for the connect promise if there is a pending one
|
|
363
|
-
await this.getClient().wsPromise;
|
|
364
|
-
|
|
365
|
-
return await this.getClient().get<SearchAPIResponse>(
|
|
366
|
-
this.getClient().baseURL + '/search',
|
|
367
|
-
{
|
|
368
|
-
payload,
|
|
369
|
-
},
|
|
370
|
-
);
|
|
575
|
+
async search(request?: { payload?: SearchPayload }) {
|
|
576
|
+
return await this.getClient().search(request);
|
|
371
577
|
}
|
|
372
578
|
|
|
373
579
|
/**
|
|
374
|
-
*
|
|
375
|
-
*
|
|
376
|
-
* @param {MemberFilters} filterConditions object MongoDB style filters
|
|
377
|
-
* @param {MemberSort} [sort] Sort options, for instance [{created_at: -1}].
|
|
378
|
-
* When using multiple fields, make sure you use array of objects to guarantee field order, for instance [{name: -1}, {created_at: 1}]
|
|
379
|
-
* @param {{ limit?: number; offset?: number }} [options] Option object, {limit: 10, offset:10}
|
|
580
|
+
* Queries members.
|
|
380
581
|
*
|
|
381
|
-
* @
|
|
582
|
+
* @param request - The query members request payload (optional). The inner `payload` accepts
|
|
583
|
+
* MongoDB-style filters, sort directions (e.g. `[{ field: 'created_at', direction: -1 }]`),
|
|
584
|
+
* and pagination options (`limit`, `offset`).
|
|
585
|
+
* @returns The query members response.
|
|
382
586
|
*/
|
|
383
|
-
async queryMembers(
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
let members: string[] | ChannelMemberResponse[] | undefined;
|
|
587
|
+
async queryMembers(request?: { payload?: Partial<QueryMembersPayload> }) {
|
|
588
|
+
const payload = {
|
|
589
|
+
type: this.type,
|
|
590
|
+
// TODO: these should be probably optional in the OAPI spec
|
|
591
|
+
// filter_conditions: ...
|
|
592
|
+
} as QueryMembersPayload;
|
|
593
|
+
|
|
391
594
|
if (this.id) {
|
|
392
|
-
id = this.id;
|
|
393
|
-
} else if (
|
|
394
|
-
members = this.data.members
|
|
595
|
+
payload.id = this.id;
|
|
596
|
+
} else if (Array.isArray(this.data?.members)) {
|
|
597
|
+
payload.members = this.data.members.map((m) => ({
|
|
598
|
+
...m,
|
|
599
|
+
// TODO: this should not be needed Gen_QueryMembersResponse should not come with user_id as optinal
|
|
600
|
+
user_id: (m.user_id ?? m.user?.id) as string,
|
|
601
|
+
}));
|
|
395
602
|
}
|
|
396
603
|
// Return a list of members
|
|
397
|
-
return await this.getClient().
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
payload
|
|
401
|
-
type,
|
|
402
|
-
id,
|
|
403
|
-
members,
|
|
404
|
-
sort: normalizeQuerySort(sort),
|
|
405
|
-
filter_conditions: filterConditions,
|
|
406
|
-
...options,
|
|
407
|
-
},
|
|
604
|
+
return await this.getClient().queryMembers({
|
|
605
|
+
payload: {
|
|
606
|
+
...payload,
|
|
607
|
+
...request?.payload,
|
|
408
608
|
},
|
|
409
|
-
);
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
/**
|
|
413
|
-
* updateMemberPartial - Partial update a member
|
|
414
|
-
*
|
|
415
|
-
* @param {PartialUpdateMember} updates
|
|
416
|
-
* @param {{ user_id?: string }} [options] Option object, {user_id: 'jane'} to optionally specify the user id
|
|
417
|
-
|
|
418
|
-
* @return {Promise<ChannelMemberResponse>} Updated member
|
|
419
|
-
*/
|
|
420
|
-
async updateMemberPartial(updates: PartialUpdateMember, options?: { userId?: string }) {
|
|
421
|
-
const url = new URL(`${this._channelURL()}/member`);
|
|
422
|
-
|
|
423
|
-
if (options?.userId) {
|
|
424
|
-
url.searchParams.append('user_id', options.userId);
|
|
425
|
-
}
|
|
426
|
-
|
|
427
|
-
return await this.getClient().patch<PartialUpdateMemberAPIResponse>(
|
|
428
|
-
url.toString(),
|
|
429
|
-
updates,
|
|
430
|
-
);
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
/**
|
|
434
|
-
* @deprecated Use `updateMemberPartial` instead
|
|
435
|
-
* partialUpdateMember - Partial update a member
|
|
436
|
-
*
|
|
437
|
-
* @param {string} user_id member user id
|
|
438
|
-
* @param {PartialUpdateMember} updates
|
|
439
|
-
*
|
|
440
|
-
* @return {Promise<ChannelMemberResponse>} Updated member
|
|
441
|
-
*/
|
|
442
|
-
async partialUpdateMember(user_id: string, updates: PartialUpdateMember) {
|
|
443
|
-
if (!user_id) {
|
|
444
|
-
throw Error('Please specify the user id');
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
return await this.getClient().patch<PartialUpdateMemberAPIResponse>(
|
|
448
|
-
this._channelURL() + `/member/${encodeURIComponent(user_id)}`,
|
|
449
|
-
updates,
|
|
450
|
-
);
|
|
609
|
+
});
|
|
451
610
|
}
|
|
452
611
|
|
|
453
612
|
/**
|
|
454
|
-
*
|
|
613
|
+
* Sends a reaction to a message. If offline support is enabled, it will make sure
|
|
455
614
|
* that sending the reaction is queued up if it fails due to bad internet conditions and executed
|
|
456
615
|
* later.
|
|
457
616
|
*
|
|
458
|
-
* @param
|
|
459
|
-
*
|
|
460
|
-
*
|
|
461
|
-
*
|
|
462
|
-
* @return {Promise<ReactionAPIResponse>} The Server Response
|
|
617
|
+
* @param request - The send-reaction request payload, including the target message ID, the
|
|
618
|
+
* reaction object (e.g. `{ type: 'love' }`), and optional flags such as `enforce_unique` and
|
|
619
|
+
* `skip_push`.
|
|
620
|
+
* @returns The server response.
|
|
463
621
|
*/
|
|
464
|
-
async sendReaction(
|
|
465
|
-
|
|
466
|
-
reaction: Reaction,
|
|
467
|
-
options?: SendReactionOptions,
|
|
468
|
-
) {
|
|
469
|
-
if (!messageID) {
|
|
470
|
-
throw Error(`Message id is missing`);
|
|
471
|
-
}
|
|
472
|
-
if (!reaction || Object.keys(reaction).length === 0) {
|
|
473
|
-
throw Error(`Reaction object is missing`);
|
|
474
|
-
}
|
|
622
|
+
async sendReaction(request: Parameters<ChatApi['sendReaction']>[0]) {
|
|
623
|
+
const { id: messageId } = request;
|
|
475
624
|
|
|
476
625
|
try {
|
|
477
626
|
const offlineDb = this.getClient().offlineDb;
|
|
478
627
|
if (offlineDb) {
|
|
628
|
+
// The optimistic reaction row is written by the local-update layer
|
|
629
|
+
// (`applyReactionLocally`); here we only queue the request for replay.
|
|
479
630
|
return await offlineDb.queueTask<ReactionAPIResponse>({
|
|
480
631
|
task: {
|
|
481
632
|
channelId: this.id as string,
|
|
482
633
|
channelType: this.type,
|
|
483
|
-
messageId
|
|
484
|
-
payload: [
|
|
634
|
+
messageId,
|
|
635
|
+
payload: [request],
|
|
485
636
|
type: 'send-reaction',
|
|
486
637
|
},
|
|
487
638
|
});
|
|
488
639
|
}
|
|
489
640
|
} catch (error) {
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
error,
|
|
493
|
-
});
|
|
641
|
+
offlineDbLogger
|
|
642
|
+
.withExtraTags('sendReaction', this.cid)
|
|
643
|
+
.error('Sending the reaction failed.', { error });
|
|
494
644
|
}
|
|
495
645
|
|
|
496
|
-
return this._sendReaction(
|
|
646
|
+
return this._sendReaction(request);
|
|
497
647
|
}
|
|
498
648
|
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
*
|
|
502
|
-
* @param {string} messageID the message id
|
|
503
|
-
* @param {Reaction} reaction the reaction object for instance {type: 'love'}
|
|
504
|
-
* @param {{ enforce_unique?: boolean, skip_push?: boolean }} [options] Option object, {enforce_unique: true, skip_push: true} to override any existing reaction or skip sending push notifications
|
|
505
|
-
*
|
|
506
|
-
* @return {Promise<ReactionAPIResponse>} The Server Response
|
|
507
|
-
*/
|
|
508
|
-
async _sendReaction(
|
|
509
|
-
messageID: string,
|
|
510
|
-
reaction: Reaction,
|
|
511
|
-
options?: SendReactionOptions,
|
|
512
|
-
) {
|
|
513
|
-
if (!messageID) {
|
|
514
|
-
throw Error(`Message id is missing`);
|
|
515
|
-
}
|
|
516
|
-
if (!reaction || Object.keys(reaction).length === 0) {
|
|
517
|
-
throw Error(`Reaction object is missing`);
|
|
518
|
-
}
|
|
519
|
-
|
|
520
|
-
return await this.getClient().post<ReactionAPIResponse>(
|
|
521
|
-
this.getClient().baseURL + `/messages/${encodeURIComponent(messageID)}/reaction`,
|
|
522
|
-
{
|
|
523
|
-
reaction,
|
|
524
|
-
...options,
|
|
525
|
-
},
|
|
526
|
-
);
|
|
649
|
+
_sendReaction(request: Parameters<ChatApi['sendReaction']>[0]) {
|
|
650
|
+
return this.getClient().sendReaction(request);
|
|
527
651
|
}
|
|
528
652
|
|
|
529
|
-
async deleteReaction(
|
|
653
|
+
async deleteReaction(request: Parameters<ChatApi['deleteReaction']>[0]) {
|
|
530
654
|
this._checkInitialized();
|
|
531
|
-
if (!reactionType || !messageID) {
|
|
532
|
-
throw Error(
|
|
533
|
-
'Deleting a reaction requires specifying both the message and reaction type',
|
|
534
|
-
);
|
|
535
|
-
}
|
|
536
655
|
|
|
537
656
|
try {
|
|
538
657
|
const offlineDb = this.getClient().offlineDb;
|
|
539
658
|
if (offlineDb) {
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
created_at: '',
|
|
543
|
-
updated_at: '',
|
|
544
|
-
message_id: messageID,
|
|
545
|
-
type: reactionType,
|
|
546
|
-
user_id: (this.getClient().userID as string) ?? user_id,
|
|
547
|
-
};
|
|
548
|
-
|
|
549
|
-
if (message) {
|
|
550
|
-
await offlineDb.deleteReaction({
|
|
551
|
-
message,
|
|
552
|
-
reaction,
|
|
553
|
-
});
|
|
554
|
-
}
|
|
555
|
-
|
|
659
|
+
// The optimistic reaction-row removal is handled by the local-update layer
|
|
660
|
+
// (`applyReactionLocally`); here we only queue the request for replay.
|
|
556
661
|
return await offlineDb.queueTask<ReactionAPIResponse>({
|
|
557
662
|
task: {
|
|
558
663
|
channelId: this.id as string,
|
|
559
664
|
channelType: this.type,
|
|
560
|
-
messageId:
|
|
561
|
-
payload: [
|
|
665
|
+
messageId: request.id,
|
|
666
|
+
payload: [request],
|
|
562
667
|
type: 'delete-reaction',
|
|
563
668
|
},
|
|
564
669
|
});
|
|
565
670
|
}
|
|
566
671
|
} catch (error) {
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
error,
|
|
570
|
-
});
|
|
672
|
+
offlineDbLogger
|
|
673
|
+
.withExtraTags('deleteReaction', this.cid)
|
|
674
|
+
.error('Deleting the reaction failed.', { error });
|
|
571
675
|
}
|
|
572
676
|
|
|
573
|
-
return await this._deleteReaction(
|
|
677
|
+
return await this._deleteReaction(request);
|
|
574
678
|
}
|
|
575
679
|
|
|
576
680
|
/**
|
|
577
|
-
*
|
|
578
|
-
*
|
|
579
|
-
* @param {string} messageID the id of the message from which te remove the reaction
|
|
580
|
-
* @param {string} reactionType the type of reaction that should be removed
|
|
581
|
-
* @param {string} [user_id] the id of the user (used only for server side request) default null
|
|
681
|
+
* Deletes a reaction by user and type.
|
|
582
682
|
*
|
|
583
|
-
* @
|
|
683
|
+
* @param request - The delete reaction request payload identifying the target message and reaction type.
|
|
684
|
+
* @returns The server response.
|
|
584
685
|
*/
|
|
585
|
-
async _deleteReaction(
|
|
586
|
-
this.
|
|
587
|
-
if (!reactionType || !messageID) {
|
|
588
|
-
throw Error(
|
|
589
|
-
'Deleting a reaction requires specifying both the message and reaction type',
|
|
590
|
-
);
|
|
591
|
-
}
|
|
592
|
-
|
|
593
|
-
const url =
|
|
594
|
-
this.getClient().baseURL +
|
|
595
|
-
`/messages/${encodeURIComponent(messageID)}/reaction/${encodeURIComponent(
|
|
596
|
-
reactionType,
|
|
597
|
-
)}`;
|
|
598
|
-
//provided when server side request
|
|
599
|
-
if (user_id) {
|
|
600
|
-
return await this.getClient().delete<ReactionAPIResponse>(url, { user_id });
|
|
601
|
-
}
|
|
602
|
-
|
|
603
|
-
return await this.getClient().delete<ReactionAPIResponse>(url, {});
|
|
686
|
+
async _deleteReaction(request: Parameters<ChatApi['deleteReaction']>[0]) {
|
|
687
|
+
return await this.getClient().deleteReaction(request);
|
|
604
688
|
}
|
|
605
689
|
|
|
606
690
|
/**
|
|
607
|
-
*
|
|
691
|
+
* Edit the channel using the inherited `update()` from `ChannelApi`. Caches the
|
|
692
|
+
* server-returned channel onto `this.data`.
|
|
608
693
|
*
|
|
609
|
-
* @param
|
|
610
|
-
* @
|
|
611
|
-
* @param {ChannelUpdateOptions} [options] Option object, configuration to control the behavior while updating
|
|
612
|
-
* @return {Promise<UpdateChannelAPIResponse>} The server response
|
|
694
|
+
* @param request - Channel update payload, e.g. `{ data: { name: 'foo' }, message }` (optional).
|
|
695
|
+
* @returns The server response.
|
|
613
696
|
*/
|
|
614
|
-
async update(
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
// TODO: this needs to be typed better
|
|
621
|
-
const reserved: Exclude<
|
|
622
|
-
keyof (ChannelResponse & ChannelData),
|
|
623
|
-
keyof CustomChannelData
|
|
624
|
-
>[] = [
|
|
625
|
-
'config',
|
|
626
|
-
'cid',
|
|
627
|
-
'created_by',
|
|
628
|
-
'id',
|
|
629
|
-
'member_count',
|
|
630
|
-
'type',
|
|
631
|
-
'created_at',
|
|
632
|
-
'updated_at',
|
|
633
|
-
'last_message_at',
|
|
634
|
-
'own_capabilities',
|
|
635
|
-
];
|
|
636
|
-
|
|
637
|
-
reserved.forEach((key) => {
|
|
638
|
-
delete channelData[key];
|
|
639
|
-
});
|
|
640
|
-
|
|
641
|
-
return await this._update({
|
|
642
|
-
message: updateMessage,
|
|
643
|
-
data: channelData,
|
|
644
|
-
...options,
|
|
645
|
-
});
|
|
697
|
+
override async update(request?: Gen_UpdateChannelRequest) {
|
|
698
|
+
const previousData = this.data;
|
|
699
|
+
const data = await super.update(request);
|
|
700
|
+
this.data = data.channel;
|
|
701
|
+
this._syncStateFromChannelData(this.data, previousData);
|
|
702
|
+
return data;
|
|
646
703
|
}
|
|
647
704
|
|
|
648
705
|
/**
|
|
649
|
-
*
|
|
706
|
+
* Partial update of channel properties.
|
|
650
707
|
*
|
|
651
|
-
* @param
|
|
652
|
-
*
|
|
653
|
-
* @return {Promise<PartialUpdateChannelAPIResponse>}
|
|
708
|
+
* @param update - The partial update request.
|
|
709
|
+
* @returns The server response.
|
|
654
710
|
*/
|
|
655
|
-
async updatePartial(update:
|
|
656
|
-
const data = await this.
|
|
657
|
-
this._channelURL(),
|
|
658
|
-
update,
|
|
659
|
-
);
|
|
711
|
+
async updatePartial(update: UpdateChannelPartialRequest) {
|
|
712
|
+
const data = await this.updateChannelPartial(update);
|
|
660
713
|
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
714
|
+
if (!this.getClient()._cacheEnabled) return data;
|
|
715
|
+
|
|
716
|
+
const channel = data.channel;
|
|
717
|
+
const currentCapabilities = this.data?.own_capabilities ?? [];
|
|
718
|
+
const newCapabilities = channel?.own_capabilities;
|
|
719
|
+
|
|
720
|
+
const capabilitiesChanged =
|
|
721
|
+
newCapabilities &&
|
|
722
|
+
[...currentCapabilities].sort().join() !== [...newCapabilities].sort().join();
|
|
723
|
+
|
|
724
|
+
const previousData = this.data;
|
|
725
|
+
this.data = channel;
|
|
726
|
+
this._syncStateFromChannelData(this.data, previousData);
|
|
671
727
|
// If the capabiltities are changed, we trigger the `capabilities.changed` event.
|
|
672
|
-
if (
|
|
728
|
+
if (capabilitiesChanged) {
|
|
673
729
|
this.getClient().dispatchEvent({
|
|
674
730
|
type: 'capabilities.changed',
|
|
675
731
|
cid: this.cid,
|
|
676
|
-
own_capabilities:
|
|
732
|
+
own_capabilities: newCapabilities,
|
|
677
733
|
});
|
|
678
734
|
}
|
|
735
|
+
|
|
679
736
|
return data;
|
|
680
737
|
}
|
|
681
738
|
|
|
682
739
|
/**
|
|
683
|
-
*
|
|
740
|
+
* Enables slow mode.
|
|
684
741
|
*
|
|
685
|
-
* @param
|
|
686
|
-
* @
|
|
742
|
+
* @param coolDownInterval - The cooldown interval in seconds.
|
|
743
|
+
* @returns The server response.
|
|
687
744
|
*/
|
|
688
745
|
async enableSlowMode(coolDownInterval: number) {
|
|
689
|
-
|
|
690
|
-
this._channelURL(),
|
|
691
|
-
{
|
|
692
|
-
cooldown: coolDownInterval,
|
|
693
|
-
},
|
|
694
|
-
);
|
|
695
|
-
this.data = data.channel;
|
|
696
|
-
return data;
|
|
746
|
+
return await this.update({ cooldown: coolDownInterval });
|
|
697
747
|
}
|
|
698
748
|
|
|
699
749
|
/**
|
|
700
|
-
*
|
|
750
|
+
* Disables slow mode.
|
|
701
751
|
*
|
|
702
|
-
* @
|
|
752
|
+
* @returns The server response.
|
|
703
753
|
*/
|
|
704
754
|
async disableSlowMode() {
|
|
705
|
-
|
|
706
|
-
this._channelURL(),
|
|
707
|
-
{
|
|
708
|
-
cooldown: 0,
|
|
709
|
-
},
|
|
710
|
-
);
|
|
711
|
-
this.data = data.channel;
|
|
712
|
-
return data;
|
|
755
|
+
return await this.update({ cooldown: 0 });
|
|
713
756
|
}
|
|
714
757
|
|
|
715
|
-
public async sendSharedLocation(
|
|
716
|
-
location: StaticLocationPayload | LiveLocationPayload,
|
|
717
|
-
userId?: string,
|
|
718
|
-
) {
|
|
758
|
+
public async sendSharedLocation(location: SharedLocation & { message_id?: string }) {
|
|
719
759
|
const result = await this.sendMessage({
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
760
|
+
message: {
|
|
761
|
+
id: location.message_id,
|
|
762
|
+
shared_location: location,
|
|
763
|
+
},
|
|
723
764
|
});
|
|
724
765
|
|
|
725
|
-
if (
|
|
766
|
+
if (location.end_at) {
|
|
726
767
|
this.getClient().dispatchEvent({
|
|
727
768
|
message: result.message,
|
|
728
769
|
type: 'live_location_sharing.started',
|
|
@@ -732,10 +773,10 @@ export class Channel {
|
|
|
732
773
|
return result;
|
|
733
774
|
}
|
|
734
775
|
|
|
735
|
-
public async stopLiveLocationSharing(payload:
|
|
736
|
-
const location = await this.getClient().
|
|
776
|
+
public async stopLiveLocationSharing(payload: UpdateLiveLocationRequest) {
|
|
777
|
+
const location = await this.getClient().updateLiveLocation({
|
|
737
778
|
...payload,
|
|
738
|
-
end_at: new Date()
|
|
779
|
+
end_at: new Date(),
|
|
739
780
|
});
|
|
740
781
|
this.getClient().dispatchEvent({
|
|
741
782
|
live_location: location,
|
|
@@ -744,359 +785,284 @@ export class Channel {
|
|
|
744
785
|
}
|
|
745
786
|
|
|
746
787
|
/**
|
|
747
|
-
*
|
|
788
|
+
* Accepts an invitation to the channel.
|
|
748
789
|
*
|
|
749
|
-
* @param
|
|
750
|
-
*
|
|
751
|
-
* @return {Promise<DeleteChannelAPIResponse>} The server response
|
|
790
|
+
* @param options - The object to update the custom properties of this channel with (optional, defaults to `{}`).
|
|
791
|
+
* @returns The server response.
|
|
752
792
|
*/
|
|
753
|
-
async
|
|
754
|
-
return await this.
|
|
755
|
-
...options,
|
|
756
|
-
});
|
|
793
|
+
async acceptInvite(options: ChannelUpdateOptions = {}) {
|
|
794
|
+
return await this.update({ accept_invite: true, ...options });
|
|
757
795
|
}
|
|
758
796
|
|
|
759
797
|
/**
|
|
760
|
-
*
|
|
761
|
-
* @param {TruncateOptions} [options] Defines truncation options
|
|
762
|
-
* @return {Promise<TruncateChannelAPIResponse>} The server response
|
|
763
|
-
*/
|
|
764
|
-
async truncate(options: TruncateOptions = {}) {
|
|
765
|
-
return await this.getClient().post<TruncateChannelAPIResponse>(
|
|
766
|
-
this._channelURL() + '/truncate',
|
|
767
|
-
options,
|
|
768
|
-
);
|
|
769
|
-
}
|
|
770
|
-
|
|
771
|
-
/**
|
|
772
|
-
* acceptInvite - accept invitation to the channel
|
|
773
|
-
*
|
|
774
|
-
* @param {UpdateChannelOptions} [options] The object to update the custom properties of this channel with
|
|
798
|
+
* Rejects an invitation to the channel.
|
|
775
799
|
*
|
|
776
|
-
* @
|
|
800
|
+
* @param options - The object to update the custom properties of this channel with (optional, defaults to `{}`).
|
|
801
|
+
* @returns The server response.
|
|
777
802
|
*/
|
|
778
|
-
async
|
|
779
|
-
return await this.
|
|
803
|
+
async rejectInvite(options: ChannelUpdateOptions = {}) {
|
|
804
|
+
return await this.update({ reject_invite: true, ...options });
|
|
780
805
|
}
|
|
781
806
|
|
|
782
807
|
/**
|
|
783
|
-
*
|
|
808
|
+
* Adds members to the channel.
|
|
784
809
|
*
|
|
785
|
-
* @param
|
|
786
|
-
*
|
|
787
|
-
* @
|
|
788
|
-
|
|
789
|
-
async rejectInvite(options: UpdateChannelOptions = {}) {
|
|
790
|
-
return await this._update({ reject_invite: true, ...options });
|
|
791
|
-
}
|
|
792
|
-
|
|
793
|
-
/**
|
|
794
|
-
* addMembers - add members to the channel
|
|
795
|
-
*
|
|
796
|
-
* @param {string[] | Array<NewMemberPayload>} members An array of members to add to the channel
|
|
797
|
-
* @param {Message} [message] Optional message object for channel members notification
|
|
798
|
-
* @param {ChannelUpdateOptions} [options] Option object, configuration to control the behavior while updating
|
|
799
|
-
* @return {Promise<UpdateChannelAPIResponse>} The server response
|
|
810
|
+
* @param members - An array of members to add to the channel.
|
|
811
|
+
* @param message - Message object for channel members notification (optional).
|
|
812
|
+
* @param options - Configuration to control the behavior while updating (optional, defaults to `{}`).
|
|
813
|
+
* @returns The server response.
|
|
800
814
|
*/
|
|
801
815
|
async addMembers(
|
|
802
|
-
members: string[] | Array<
|
|
803
|
-
message?:
|
|
816
|
+
members: string[] | Array<Gen_ChannelMemberRequest>,
|
|
817
|
+
message?: MessageRequest,
|
|
804
818
|
options: ChannelUpdateOptions = {},
|
|
805
819
|
) {
|
|
806
|
-
return await this.
|
|
820
|
+
return await this.update({
|
|
821
|
+
add_members: members.map((member) =>
|
|
822
|
+
typeof member === 'string' ? { user_id: member } : member,
|
|
823
|
+
),
|
|
824
|
+
message,
|
|
825
|
+
...options,
|
|
826
|
+
});
|
|
807
827
|
}
|
|
808
828
|
|
|
809
829
|
/**
|
|
810
|
-
*
|
|
830
|
+
* Adds filter tags to the channel.
|
|
811
831
|
*
|
|
812
|
-
* @param
|
|
813
|
-
* @param
|
|
814
|
-
* @param
|
|
815
|
-
* @
|
|
832
|
+
* @param tags - An array of tags to add to the channel.
|
|
833
|
+
* @param message - Message object for channel members notification (optional).
|
|
834
|
+
* @param options - Configuration to control the behavior while updating (optional, defaults to `{}`).
|
|
835
|
+
* @returns The server response.
|
|
816
836
|
*/
|
|
817
837
|
async addFilterTags(
|
|
818
838
|
tags: string[],
|
|
819
|
-
message?:
|
|
839
|
+
message?: MessageRequest,
|
|
820
840
|
options: ChannelUpdateOptions = {},
|
|
821
841
|
) {
|
|
822
|
-
return await this.
|
|
842
|
+
return await this.update({ add_filter_tags: tags, message, ...options });
|
|
823
843
|
}
|
|
824
844
|
|
|
825
845
|
/**
|
|
826
|
-
*
|
|
846
|
+
* Removes filter tags from the channel.
|
|
827
847
|
*
|
|
828
|
-
* @param
|
|
829
|
-
* @param
|
|
830
|
-
* @param
|
|
831
|
-
* @
|
|
848
|
+
* @param tags - An array of tags to remove from the channel.
|
|
849
|
+
* @param message - Message object for channel members notification (optional).
|
|
850
|
+
* @param options - Configuration to control the behavior while updating (optional, defaults to `{}`).
|
|
851
|
+
* @returns The server response.
|
|
832
852
|
*/
|
|
833
853
|
async removeFilterTags(
|
|
834
854
|
tags: string[],
|
|
835
|
-
message?:
|
|
855
|
+
message?: MessageRequest,
|
|
836
856
|
options: ChannelUpdateOptions = {},
|
|
837
857
|
) {
|
|
838
|
-
return await this.
|
|
858
|
+
return await this.update({ remove_filter_tags: tags, message, ...options });
|
|
839
859
|
}
|
|
840
860
|
|
|
841
861
|
/**
|
|
842
|
-
*
|
|
862
|
+
* Adds moderators to the channel.
|
|
843
863
|
*
|
|
844
|
-
* @param
|
|
845
|
-
* @param
|
|
846
|
-
* @param
|
|
847
|
-
* @
|
|
864
|
+
* @param members - An array of member identifiers.
|
|
865
|
+
* @param message - Message object for channel members notification (optional).
|
|
866
|
+
* @param options - Configuration to control the behavior while updating (optional, defaults to `{}`).
|
|
867
|
+
* @returns The server response.
|
|
848
868
|
*/
|
|
849
869
|
async addModerators(
|
|
850
870
|
members: string[],
|
|
851
|
-
message?:
|
|
871
|
+
message?: MessageRequest,
|
|
852
872
|
options: ChannelUpdateOptions = {},
|
|
853
873
|
) {
|
|
854
|
-
return await this.
|
|
874
|
+
return await this.update({ add_moderators: members, message, ...options });
|
|
855
875
|
}
|
|
856
876
|
|
|
857
877
|
/**
|
|
858
|
-
*
|
|
878
|
+
* Sets member roles in a channel.
|
|
859
879
|
*
|
|
860
|
-
* @param
|
|
861
|
-
* @param
|
|
862
|
-
* @param
|
|
863
|
-
* @
|
|
880
|
+
* @param roles - List of role assignments.
|
|
881
|
+
* @param message - Message object for channel members notification (optional).
|
|
882
|
+
* @param options - Configuration to control the behavior while updating (optional, defaults to `{}`).
|
|
883
|
+
* @returns The server response.
|
|
864
884
|
*/
|
|
865
885
|
async assignRoles(
|
|
866
|
-
roles: { channel_role:
|
|
867
|
-
message?:
|
|
886
|
+
roles: { channel_role: RoleName; user_id: string }[],
|
|
887
|
+
message?: MessageRequest,
|
|
868
888
|
options: ChannelUpdateOptions = {},
|
|
869
889
|
) {
|
|
870
|
-
return await this.
|
|
890
|
+
return await this.update({ assign_roles: roles, message, ...options });
|
|
871
891
|
}
|
|
872
892
|
|
|
873
893
|
/**
|
|
874
|
-
*
|
|
894
|
+
* Invite members to the channel.
|
|
875
895
|
*
|
|
876
|
-
* @param
|
|
877
|
-
* @param
|
|
878
|
-
* @param
|
|
879
|
-
* @
|
|
896
|
+
* @param members - An array of members to invite to the channel.
|
|
897
|
+
* @param message - Message object for channel members notification (optional).
|
|
898
|
+
* @param options - Configuration to control the behavior while updating (optional, defaults to `{}`).
|
|
899
|
+
* @returns The server response.
|
|
880
900
|
*/
|
|
881
901
|
async inviteMembers(
|
|
882
|
-
members: string[] | Required<Omit<
|
|
883
|
-
message?:
|
|
902
|
+
members: string[] | Required<Omit<Gen_ChannelMemberRequest, 'channel_role'>>[],
|
|
903
|
+
message?: MessageRequest,
|
|
884
904
|
options: ChannelUpdateOptions = {},
|
|
885
905
|
) {
|
|
886
|
-
return await this.
|
|
906
|
+
return await this.update({
|
|
907
|
+
invites: members.map((member) =>
|
|
908
|
+
typeof member === 'string' ? { user_id: member } : member,
|
|
909
|
+
),
|
|
910
|
+
message,
|
|
911
|
+
...options,
|
|
912
|
+
});
|
|
887
913
|
}
|
|
888
914
|
|
|
889
915
|
/**
|
|
890
|
-
*
|
|
916
|
+
* Removes members from the channel.
|
|
891
917
|
*
|
|
892
|
-
* @param
|
|
893
|
-
* @param
|
|
894
|
-
* @param
|
|
895
|
-
* @
|
|
918
|
+
* @param members - An array of member identifiers.
|
|
919
|
+
* @param message - Message object for channel members notification (optional).
|
|
920
|
+
* @param options - Configuration to control the behavior while updating (optional, defaults to `{}`).
|
|
921
|
+
* @returns The server response.
|
|
896
922
|
*/
|
|
897
923
|
async removeMembers(
|
|
898
924
|
members: string[],
|
|
899
|
-
message?:
|
|
925
|
+
message?: MessageRequest,
|
|
900
926
|
options: ChannelUpdateOptions = {},
|
|
901
927
|
) {
|
|
902
|
-
return await this.
|
|
928
|
+
return await this.update({ remove_members: members, message, ...options });
|
|
903
929
|
}
|
|
904
930
|
|
|
905
931
|
/**
|
|
906
|
-
*
|
|
932
|
+
* Removes the moderator role from channel members.
|
|
907
933
|
*
|
|
908
|
-
* @param
|
|
909
|
-
* @param
|
|
910
|
-
* @param
|
|
911
|
-
* @
|
|
934
|
+
* @param members - An array of member identifiers.
|
|
935
|
+
* @param message - Message object for channel members notification (optional).
|
|
936
|
+
* @param options - Configuration to control the behavior while updating (optional, defaults to `{}`).
|
|
937
|
+
* @returns The server response.
|
|
912
938
|
*/
|
|
913
939
|
async demoteModerators(
|
|
914
940
|
members: string[],
|
|
915
|
-
message?:
|
|
941
|
+
message?: MessageRequest,
|
|
916
942
|
options: ChannelUpdateOptions = {},
|
|
917
943
|
) {
|
|
918
|
-
return await this.
|
|
944
|
+
return await this.update({ demote_moderators: members, message, ...options });
|
|
919
945
|
}
|
|
920
946
|
|
|
921
947
|
/**
|
|
922
|
-
*
|
|
923
|
-
* @param payload Object Update Channel payload
|
|
924
|
-
* @return {Promise<UpdateChannelAPIResponse>} The server response
|
|
925
|
-
* TODO: introduce new type instead of Object in the next major update
|
|
926
|
-
*/
|
|
927
|
-
async _update(payload: object) {
|
|
928
|
-
const data = await this.getClient().post<UpdateChannelAPIResponse>(
|
|
929
|
-
this._channelURL(),
|
|
930
|
-
payload,
|
|
931
|
-
);
|
|
932
|
-
this.data = data.channel;
|
|
933
|
-
return data;
|
|
934
|
-
}
|
|
935
|
-
|
|
936
|
-
/**
|
|
937
|
-
* mute - mutes the current channel
|
|
938
|
-
* @param {{ user_id?: string, expiration?: string }} opts expiration in minutes or user_id
|
|
939
|
-
* @return {Promise<MuteChannelAPIResponse>} The server response
|
|
948
|
+
* Mutes the current channel.
|
|
940
949
|
*
|
|
941
|
-
* example
|
|
942
|
-
*
|
|
950
|
+
* @example
|
|
951
|
+
* // with expiration
|
|
952
|
+
* await channel.mute({ expiration: moment.duration(2, 'weeks') });
|
|
943
953
|
*
|
|
944
|
-
* example
|
|
945
|
-
*
|
|
954
|
+
* @example
|
|
955
|
+
* // server side
|
|
956
|
+
* await channel.mute({ user_id: userId });
|
|
946
957
|
*
|
|
958
|
+
* @param options - Mute options (optional, defaults to `{}`).
|
|
959
|
+
* @param options.expiration - Expiration in minutes (optional).
|
|
960
|
+
* @returns The server response.
|
|
947
961
|
*/
|
|
948
|
-
async mute(
|
|
949
|
-
return await this.getClient().
|
|
950
|
-
this.
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
...opts,
|
|
954
|
-
},
|
|
955
|
-
);
|
|
962
|
+
async mute(options?: Gen_MuteChannelRequest) {
|
|
963
|
+
return await this.getClient().muteChannel({
|
|
964
|
+
channel_cids: [this.cid],
|
|
965
|
+
...options,
|
|
966
|
+
});
|
|
956
967
|
}
|
|
957
968
|
|
|
958
969
|
/**
|
|
959
|
-
*
|
|
960
|
-
* @param {{ user_id?: string}} opts user_id
|
|
961
|
-
* @return {Promise<APIResponse>} The server response
|
|
970
|
+
* Unmutes the current channel.
|
|
962
971
|
*
|
|
963
|
-
* example
|
|
964
|
-
*
|
|
972
|
+
* @example
|
|
973
|
+
* // server side
|
|
974
|
+
* await channel.unmute({ user_id: userId });
|
|
975
|
+
*
|
|
976
|
+
* @param options - Unmute options (optional, defaults to `{}`).
|
|
977
|
+
* @param options.user_id - User ID (optional).
|
|
978
|
+
* @returns The server response.
|
|
965
979
|
*/
|
|
966
|
-
async unmute(
|
|
967
|
-
return await this.getClient().
|
|
968
|
-
this.
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
...opts,
|
|
972
|
-
},
|
|
973
|
-
);
|
|
980
|
+
async unmute(options?: Gen_UnmuteChannelRequest) {
|
|
981
|
+
return await this.getClient().unmuteChannel({
|
|
982
|
+
channel_cids: [this.cid],
|
|
983
|
+
...options,
|
|
984
|
+
});
|
|
974
985
|
}
|
|
975
986
|
|
|
976
987
|
/**
|
|
977
|
-
*
|
|
978
|
-
* @param {{ user_id?: string }} opts user_id if called server side
|
|
979
|
-
* @return {Promise<ChannelMemberResponse>} The server response
|
|
980
|
-
*
|
|
981
|
-
* example:
|
|
982
|
-
* await channel.archives();
|
|
988
|
+
* Archives the current channel.
|
|
983
989
|
*
|
|
984
|
-
* example
|
|
985
|
-
* await channel.archive(
|
|
990
|
+
* @example
|
|
991
|
+
* await channel.archive();
|
|
986
992
|
*
|
|
993
|
+
* @returns The server response.
|
|
987
994
|
*/
|
|
988
|
-
async archive(
|
|
989
|
-
|
|
990
|
-
const uid = opts.user_id || cli.userID;
|
|
991
|
-
if (!uid) {
|
|
992
|
-
throw Error('A user_id is required for archiving a channel');
|
|
993
|
-
}
|
|
994
|
-
const resp = await this.partialUpdateMember(uid, { set: { archived: true } });
|
|
995
|
-
return resp.channel_member;
|
|
995
|
+
async archive() {
|
|
996
|
+
return await this.updateMemberPartial({ set: { archived: true } });
|
|
996
997
|
}
|
|
997
998
|
|
|
998
999
|
/**
|
|
999
|
-
*
|
|
1000
|
-
* @param {{ user_id?: string }} opts user_id if called server side
|
|
1001
|
-
* @return {Promise<ChannelMemberResponse>} The server response
|
|
1000
|
+
* Unarchives the current channel.
|
|
1002
1001
|
*
|
|
1003
|
-
* example
|
|
1002
|
+
* @example
|
|
1004
1003
|
* await channel.unarchive();
|
|
1005
1004
|
*
|
|
1006
|
-
*
|
|
1007
|
-
* await channel.unarchive({user_id: userId});
|
|
1008
|
-
*
|
|
1005
|
+
* @returns The server response.
|
|
1009
1006
|
*/
|
|
1010
|
-
async unarchive(
|
|
1011
|
-
|
|
1012
|
-
const uid = opts.user_id || cli.userID;
|
|
1013
|
-
if (!uid) {
|
|
1014
|
-
throw Error('A user_id is required for unarchiving a channel');
|
|
1015
|
-
}
|
|
1016
|
-
const resp = await this.partialUpdateMember(uid, { set: { archived: false } });
|
|
1017
|
-
return resp.channel_member;
|
|
1007
|
+
async unarchive() {
|
|
1008
|
+
return await this.updateMemberPartial({ set: { archived: false } });
|
|
1018
1009
|
}
|
|
1019
1010
|
|
|
1020
1011
|
/**
|
|
1021
|
-
*
|
|
1022
|
-
* @param {{ user_id?: string }} opts user_id if called server side
|
|
1023
|
-
* @return {Promise<ChannelMemberResponse>} The server response
|
|
1012
|
+
* Pins the current channel.
|
|
1024
1013
|
*
|
|
1025
|
-
* example
|
|
1014
|
+
* @example
|
|
1026
1015
|
* await channel.pin();
|
|
1027
1016
|
*
|
|
1028
|
-
*
|
|
1029
|
-
* await channel.pin({user_id: userId});
|
|
1030
|
-
*
|
|
1017
|
+
* @returns The server response.
|
|
1031
1018
|
*/
|
|
1032
|
-
async pin(
|
|
1033
|
-
|
|
1034
|
-
const uid = opts.user_id || cli.userID;
|
|
1035
|
-
if (!uid) {
|
|
1036
|
-
throw new Error('A user_id is required for pinning a channel');
|
|
1037
|
-
}
|
|
1038
|
-
const resp = await this.partialUpdateMember(uid, { set: { pinned: true } });
|
|
1039
|
-
return resp.channel_member;
|
|
1019
|
+
async pin() {
|
|
1020
|
+
return await this.updateMemberPartial({ set: { pinned: true } });
|
|
1040
1021
|
}
|
|
1041
1022
|
|
|
1042
1023
|
/**
|
|
1043
|
-
*
|
|
1044
|
-
* @param {{ user_id?: string }} opts user_id if called server side
|
|
1045
|
-
* @return {Promise<ChannelMemberResponse>} The server response
|
|
1024
|
+
* Unpins the current channel.
|
|
1046
1025
|
*
|
|
1047
|
-
* example
|
|
1026
|
+
* @example
|
|
1048
1027
|
* await channel.unpin();
|
|
1049
1028
|
*
|
|
1050
|
-
*
|
|
1051
|
-
* await channel.unpin({user_id: userId});
|
|
1052
|
-
*
|
|
1029
|
+
* @returns The server response.
|
|
1053
1030
|
*/
|
|
1054
|
-
async unpin(
|
|
1055
|
-
|
|
1056
|
-
const uid = opts.user_id || cli.userID;
|
|
1057
|
-
if (!uid) {
|
|
1058
|
-
throw new Error('A user_id is required for unpinning a channel');
|
|
1059
|
-
}
|
|
1060
|
-
const resp = await this.partialUpdateMember(uid, { set: { pinned: false } });
|
|
1061
|
-
return resp.channel_member;
|
|
1031
|
+
async unpin() {
|
|
1032
|
+
return await this.updateMemberPartial({ set: { pinned: false } });
|
|
1062
1033
|
}
|
|
1063
1034
|
|
|
1064
1035
|
/**
|
|
1065
|
-
*
|
|
1066
|
-
*
|
|
1036
|
+
* Returns the mute status for the current channel.
|
|
1037
|
+
*
|
|
1038
|
+
* @returns An object of the form `{ muted: true | false, createdAt: Date | null, expiresAt: Date | null }`.
|
|
1067
1039
|
*/
|
|
1068
|
-
muteStatus()
|
|
1069
|
-
createdAt: Date | null;
|
|
1070
|
-
expiresAt: Date | null;
|
|
1071
|
-
muted: boolean;
|
|
1072
|
-
} {
|
|
1040
|
+
muteStatus() {
|
|
1073
1041
|
this._checkInitialized();
|
|
1074
1042
|
return this.getClient()._muteStatus(this.cid);
|
|
1075
1043
|
}
|
|
1076
1044
|
|
|
1077
|
-
sendAction(
|
|
1045
|
+
sendAction(messageId: string, formData: Record<string, string>) {
|
|
1078
1046
|
this._checkInitialized();
|
|
1079
|
-
if (!
|
|
1080
|
-
throw Error(`Message
|
|
1047
|
+
if (!messageId) {
|
|
1048
|
+
throw Error(`Message ID is missing`);
|
|
1081
1049
|
}
|
|
1082
|
-
return this.getClient().
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
form_data: formData,
|
|
1087
|
-
id: this.id,
|
|
1088
|
-
type: this.type,
|
|
1089
|
-
},
|
|
1090
|
-
);
|
|
1050
|
+
return this.getClient().runMessageAction({
|
|
1051
|
+
id: messageId,
|
|
1052
|
+
form_data: formData,
|
|
1053
|
+
});
|
|
1091
1054
|
}
|
|
1092
1055
|
|
|
1093
1056
|
/**
|
|
1094
|
-
*
|
|
1095
|
-
* Call this on every keystroke
|
|
1057
|
+
* First of the `typing.start` and `typing.stop` events based on the user's keystrokes.
|
|
1058
|
+
* Call this on every keystroke.
|
|
1059
|
+
*
|
|
1096
1060
|
* @see {@link https://getstream.io/chat/docs/typing_indicators/?language=js|Docs}
|
|
1097
|
-
*
|
|
1061
|
+
*
|
|
1062
|
+
* @param parentId - Set this field to `message.id` to indicate that the typing event is happening in a thread (optional).
|
|
1063
|
+
* @param options - Optional override carrying a `user_id` (optional).
|
|
1098
1064
|
*/
|
|
1099
|
-
async keystroke(
|
|
1065
|
+
async keystroke(parentId?: string, options?: { user_id: string }) {
|
|
1100
1066
|
if (!this._isTypingIndicatorsEnabled()) {
|
|
1101
1067
|
return;
|
|
1102
1068
|
}
|
|
@@ -1108,10 +1074,14 @@ export class Channel {
|
|
|
1108
1074
|
if (diff === null || diff > 2000) {
|
|
1109
1075
|
this.lastTypingEvent = new Date();
|
|
1110
1076
|
await this.sendEvent({
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1077
|
+
event: {
|
|
1078
|
+
type: 'typing.start',
|
|
1079
|
+
parent_id: parentId,
|
|
1080
|
+
...(options || {}),
|
|
1081
|
+
created_at: new Date(),
|
|
1082
|
+
custom: {},
|
|
1083
|
+
},
|
|
1084
|
+
});
|
|
1115
1085
|
}
|
|
1116
1086
|
}
|
|
1117
1087
|
|
|
@@ -1120,8 +1090,9 @@ export class Channel {
|
|
|
1120
1090
|
* Typically used by the server connected to the AI service to notify clients of state changes.
|
|
1121
1091
|
*
|
|
1122
1092
|
* @param messageId - The ID of the message associated with the AI state.
|
|
1123
|
-
* @param state - The new state of the AI process
|
|
1124
|
-
* @param options -
|
|
1093
|
+
* @param state - The new state of the AI process, e.g. thinking, generating.
|
|
1094
|
+
* @param options - Parameters such as `ai_message` to include additional details in the event (optional, defaults to `{}`).
|
|
1095
|
+
* @param options.ai_message - Additional message detail to include in the event (optional).
|
|
1125
1096
|
*/
|
|
1126
1097
|
async updateAIState(
|
|
1127
1098
|
messageId: string,
|
|
@@ -1129,11 +1100,15 @@ export class Channel {
|
|
|
1129
1100
|
options: { ai_message?: string } = {},
|
|
1130
1101
|
) {
|
|
1131
1102
|
await this.sendEvent({
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1103
|
+
event: {
|
|
1104
|
+
...options,
|
|
1105
|
+
type: 'ai_indicator.update',
|
|
1106
|
+
message_id: messageId,
|
|
1107
|
+
ai_state: state,
|
|
1108
|
+
created_at: new Date(),
|
|
1109
|
+
custom: {},
|
|
1110
|
+
},
|
|
1111
|
+
});
|
|
1137
1112
|
}
|
|
1138
1113
|
|
|
1139
1114
|
/**
|
|
@@ -1142,8 +1117,12 @@ export class Channel {
|
|
|
1142
1117
|
*/
|
|
1143
1118
|
async clearAIIndicator() {
|
|
1144
1119
|
await this.sendEvent({
|
|
1145
|
-
|
|
1146
|
-
|
|
1120
|
+
event: {
|
|
1121
|
+
type: 'ai_indicator.clear',
|
|
1122
|
+
created_at: new Date(),
|
|
1123
|
+
custom: {},
|
|
1124
|
+
},
|
|
1125
|
+
});
|
|
1147
1126
|
}
|
|
1148
1127
|
|
|
1149
1128
|
/**
|
|
@@ -1152,26 +1131,37 @@ export class Channel {
|
|
|
1152
1131
|
*/
|
|
1153
1132
|
async stopAIResponse() {
|
|
1154
1133
|
await this.sendEvent({
|
|
1155
|
-
|
|
1156
|
-
|
|
1134
|
+
event: {
|
|
1135
|
+
type: 'ai_indicator.stop',
|
|
1136
|
+
created_at: new Date(),
|
|
1137
|
+
custom: {},
|
|
1138
|
+
},
|
|
1139
|
+
});
|
|
1157
1140
|
}
|
|
1158
1141
|
|
|
1159
1142
|
/**
|
|
1160
|
-
*
|
|
1143
|
+
* Sets last typing to null and sends the `typing.stop` event.
|
|
1144
|
+
*
|
|
1161
1145
|
* @see {@link https://getstream.io/chat/docs/typing_indicators/?language=js|Docs}
|
|
1162
|
-
*
|
|
1146
|
+
*
|
|
1147
|
+
* @param parentId - Set this field to `message.id` to indicate that the typing event is happening in a thread (optional).
|
|
1148
|
+
* @param options - Optional override carrying a `user_id` (optional).
|
|
1163
1149
|
*/
|
|
1164
|
-
async stopTyping(
|
|
1150
|
+
async stopTyping(parentId?: string, options?: { user_id: string }) {
|
|
1165
1151
|
if (!this._isTypingIndicatorsEnabled()) {
|
|
1166
1152
|
return;
|
|
1167
1153
|
}
|
|
1168
1154
|
this.lastTypingEvent = null;
|
|
1169
1155
|
this.isTyping = false;
|
|
1170
1156
|
await this.sendEvent({
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1157
|
+
event: {
|
|
1158
|
+
type: 'typing.stop',
|
|
1159
|
+
parent_id: parentId,
|
|
1160
|
+
...(options || {}),
|
|
1161
|
+
created_at: new Date(),
|
|
1162
|
+
custom: {},
|
|
1163
|
+
},
|
|
1164
|
+
});
|
|
1175
1165
|
}
|
|
1176
1166
|
|
|
1177
1167
|
_isTypingIndicatorsEnabled(): boolean {
|
|
@@ -1182,74 +1172,53 @@ export class Channel {
|
|
|
1182
1172
|
}
|
|
1183
1173
|
|
|
1184
1174
|
/**
|
|
1185
|
-
*
|
|
1175
|
+
* Run this user's mark-read reporter for this channel. Delegates to
|
|
1176
|
+
* `MessageDeliveryReporter`, which batches the underlying `markRead` request
|
|
1177
|
+
* with the user's read receipts state.
|
|
1186
1178
|
*
|
|
1187
|
-
*
|
|
1188
|
-
*/
|
|
1189
|
-
lastMessage(): LocalMessage | undefined {
|
|
1190
|
-
// get last 5 messages, sort, return the latest
|
|
1191
|
-
// get a slice of the last 5
|
|
1192
|
-
let min = this.state.latestMessages.length - 5;
|
|
1193
|
-
if (min < 0) {
|
|
1194
|
-
min = 0;
|
|
1195
|
-
}
|
|
1196
|
-
const max = this.state.latestMessages.length + 1;
|
|
1197
|
-
const messageSlice = this.state.latestMessages.slice(min, max);
|
|
1198
|
-
|
|
1199
|
-
// sort by pk desc
|
|
1200
|
-
messageSlice.sort((a, b) => b.created_at.getTime() - a.created_at.getTime());
|
|
1201
|
-
|
|
1202
|
-
return messageSlice[0];
|
|
1203
|
-
}
|
|
1204
|
-
|
|
1205
|
-
/**
|
|
1206
|
-
* 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.
|
|
1179
|
+
* Use the inherited `markRead()` from `ChannelApi` for a direct, unbatched call.
|
|
1207
1180
|
*
|
|
1208
|
-
* @param {
|
|
1209
|
-
* @return {Promise<EventAPIResponse | null>} Description
|
|
1181
|
+
* @param data - Mark read options (optional, defaults to `{}`).
|
|
1210
1182
|
*/
|
|
1211
|
-
async
|
|
1183
|
+
async markReadViaReporter(data: MarkReadRequest = {}) {
|
|
1212
1184
|
return await this.getClient().messageDeliveryReporter.markRead(this, data);
|
|
1213
1185
|
}
|
|
1214
1186
|
|
|
1215
1187
|
/**
|
|
1216
|
-
*
|
|
1188
|
+
* Override of the inherited `markRead()` from `ChannelApi` that requires the
|
|
1189
|
+
* channel to be initialized and respects the `read_events` channel config.
|
|
1217
1190
|
*
|
|
1218
|
-
* @param {
|
|
1219
|
-
* @
|
|
1191
|
+
* @param data - Mark read options (optional, defaults to `{}`).
|
|
1192
|
+
* @returns The server response, or `null` if the request was skipped.
|
|
1220
1193
|
*/
|
|
1221
|
-
async
|
|
1194
|
+
override async markRead(data?: MarkReadRequest) {
|
|
1222
1195
|
this._checkInitialized();
|
|
1223
1196
|
|
|
1224
|
-
if (!this.getConfig()?.read_events
|
|
1225
|
-
|
|
1197
|
+
if (!this.getConfig()?.read_events) {
|
|
1198
|
+
throw new Error('Read events are disabled for this application');
|
|
1226
1199
|
}
|
|
1227
1200
|
|
|
1228
|
-
return await
|
|
1229
|
-
...data,
|
|
1230
|
-
});
|
|
1201
|
+
return await super.markRead(data);
|
|
1231
1202
|
}
|
|
1232
1203
|
|
|
1233
1204
|
/**
|
|
1234
|
-
*
|
|
1205
|
+
* Marks the channel as unread from `messageId`. Only works when the `read_events` setting is enabled.
|
|
1235
1206
|
*
|
|
1236
|
-
* @param
|
|
1237
|
-
* @
|
|
1207
|
+
* @param data - Mark unread options.
|
|
1208
|
+
* @returns An API response, or `null` if the request was skipped.
|
|
1238
1209
|
*/
|
|
1239
|
-
async markUnread(data
|
|
1210
|
+
override async markUnread(data?: MarkUnreadRequest) {
|
|
1240
1211
|
this._checkInitialized();
|
|
1241
1212
|
|
|
1242
|
-
if (!this.getConfig()?.read_events
|
|
1243
|
-
|
|
1213
|
+
if (!this.getConfig()?.read_events) {
|
|
1214
|
+
throw new Error('Read events are disabled for this application');
|
|
1244
1215
|
}
|
|
1245
1216
|
|
|
1246
|
-
return await
|
|
1247
|
-
...data,
|
|
1248
|
-
});
|
|
1217
|
+
return await super.markUnread(data);
|
|
1249
1218
|
}
|
|
1250
1219
|
|
|
1251
1220
|
/**
|
|
1252
|
-
*
|
|
1221
|
+
* Resets this user's unread count locally, without any backend call. Intended for
|
|
1253
1222
|
* channels that have read events disabled (e.g. livestreams) when the client is created with the
|
|
1254
1223
|
* `isLocalUnreadCountEnabled` option. Dispatches a dedicated, client-only `message.read_locally` event
|
|
1255
1224
|
* that runs through the same `_handleChannelEvent` read logic as a real `message.read` (minus the
|
|
@@ -1257,21 +1226,21 @@ export class Channel {
|
|
|
1257
1226
|
* is enabled, the offline DB persists the reset for read-events-disabled channels, so the local
|
|
1258
1227
|
* count stays consistent across app restarts.
|
|
1259
1228
|
*
|
|
1260
|
-
* @
|
|
1229
|
+
* @returns The dispatched `message.read_locally` event, or `undefined` if there is no connected user.
|
|
1261
1230
|
*/
|
|
1262
1231
|
markReadLocally() {
|
|
1263
1232
|
const client = this.getClient();
|
|
1264
|
-
if (!client.
|
|
1233
|
+
if (!client.userId) return;
|
|
1265
1234
|
|
|
1266
|
-
const event:
|
|
1235
|
+
const event: EventPayload<'message.read_locally'> = {
|
|
1267
1236
|
channel_id: this.id,
|
|
1268
1237
|
channel_type: this.type,
|
|
1269
1238
|
cid: this.cid,
|
|
1270
|
-
created_at: new Date()
|
|
1271
|
-
last_read_message_id: this.
|
|
1239
|
+
created_at: new Date(),
|
|
1240
|
+
last_read_message_id: this.messagePaginator.headmostItem?.id,
|
|
1272
1241
|
team: this.data?.team,
|
|
1273
1242
|
type: 'message.read_locally',
|
|
1274
|
-
user: client.user,
|
|
1243
|
+
user: client.user as UserResponse,
|
|
1275
1244
|
};
|
|
1276
1245
|
client.dispatchEvent(event);
|
|
1277
1246
|
|
|
@@ -1279,7 +1248,7 @@ export class Channel {
|
|
|
1279
1248
|
}
|
|
1280
1249
|
|
|
1281
1250
|
/**
|
|
1282
|
-
*
|
|
1251
|
+
* Cleans the channel state and fires stop typing if needed.
|
|
1283
1252
|
*/
|
|
1284
1253
|
clean() {
|
|
1285
1254
|
if (this.lastKeyStroke) {
|
|
@@ -1294,13 +1263,12 @@ export class Channel {
|
|
|
1294
1263
|
}
|
|
1295
1264
|
|
|
1296
1265
|
/**
|
|
1297
|
-
*
|
|
1298
|
-
*
|
|
1299
|
-
* @param {ChannelQueryOptions} options additional options for the query endpoint
|
|
1266
|
+
* Loads the initial channel state and watches for changes.
|
|
1300
1267
|
*
|
|
1301
|
-
* @
|
|
1268
|
+
* @param options - Additional options for the query endpoint (optional).
|
|
1269
|
+
* @returns The server response.
|
|
1302
1270
|
*/
|
|
1303
|
-
async watch(options?:
|
|
1271
|
+
async watch(options?: ChannelGetOrCreateRequest) {
|
|
1304
1272
|
const defaultOptions = {
|
|
1305
1273
|
state: true,
|
|
1306
1274
|
watch: true,
|
|
@@ -1317,138 +1285,102 @@ export class Channel {
|
|
|
1317
1285
|
const combined = { ...defaultOptions, ...options };
|
|
1318
1286
|
const state = await this.query(combined, 'latest');
|
|
1319
1287
|
this.initialized = true;
|
|
1288
|
+
const previousData = this.data;
|
|
1320
1289
|
this.data = state.channel;
|
|
1290
|
+
this._syncStateFromChannelData(this.data, previousData);
|
|
1321
1291
|
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
channel: this,
|
|
1328
|
-
},
|
|
1329
|
-
);
|
|
1292
|
+
// The message paginator is seeded synchronously inside query() (before read-state hydration),
|
|
1293
|
+
// so a channel opened via watch() alone — a deep-link restore, a search result, a freshly
|
|
1294
|
+
// created DM — already has its latest page loaded here.
|
|
1295
|
+
|
|
1296
|
+
logger.withExtraTags('watch', this.cid).info('Started watching the channel.');
|
|
1330
1297
|
return state;
|
|
1331
1298
|
}
|
|
1332
1299
|
|
|
1333
1300
|
/**
|
|
1334
|
-
*
|
|
1301
|
+
* Stops watching the channel.
|
|
1335
1302
|
*
|
|
1336
|
-
* @
|
|
1303
|
+
* @param request - The stop-watching request payload (optional).
|
|
1304
|
+
* @returns The server response.
|
|
1337
1305
|
*/
|
|
1338
|
-
async stopWatching() {
|
|
1339
|
-
const response = await
|
|
1340
|
-
this._channelURL() + '/stop-watching',
|
|
1341
|
-
{},
|
|
1342
|
-
);
|
|
1306
|
+
override async stopWatching(request?: Gen_ChannelStopWatchingRequest) {
|
|
1307
|
+
const response = await super.stopWatching(request);
|
|
1343
1308
|
|
|
1344
|
-
this.
|
|
1345
|
-
'info',
|
|
1346
|
-
`channel:watch() - stopped watching channel ${this.cid}`,
|
|
1347
|
-
{
|
|
1348
|
-
tags: ['channel'],
|
|
1349
|
-
channel: this,
|
|
1350
|
-
},
|
|
1351
|
-
);
|
|
1309
|
+
logger.withExtraTags('stopWatching', this.cid).info('Stopped watching the channel.');
|
|
1352
1310
|
|
|
1353
1311
|
return response;
|
|
1354
1312
|
}
|
|
1355
1313
|
|
|
1356
1314
|
/**
|
|
1357
|
-
*
|
|
1315
|
+
* List the message replies for a parent message.
|
|
1358
1316
|
*
|
|
1359
|
-
* The recommended way of working with threads is to use the Thread class.
|
|
1317
|
+
* The recommended way of working with threads is to use the `Thread` class.
|
|
1360
1318
|
*
|
|
1361
|
-
* @param
|
|
1362
|
-
*
|
|
1363
|
-
*
|
|
1364
|
-
* @return {Promise<GetRepliesAPIResponse>} A response with a list of messages
|
|
1319
|
+
* @param request - The get-replies request payload, including the parent message ID, pagination
|
|
1320
|
+
* params, and optional sort directions for `created_at`.
|
|
1321
|
+
* @returns A response with a list of messages.
|
|
1365
1322
|
*/
|
|
1366
|
-
async getReplies(
|
|
1367
|
-
|
|
1368
|
-
options: MessagePaginationOptions & { user?: UserResponse; user_id?: string },
|
|
1369
|
-
sort?: { created_at: AscDesc }[],
|
|
1370
|
-
) {
|
|
1371
|
-
const normalizedSort = sort ? normalizeQuerySort(sort) : undefined;
|
|
1372
|
-
const data = await this.getClient().get<GetRepliesAPIResponse>(
|
|
1373
|
-
this.getClient().baseURL + `/messages/${encodeURIComponent(parent_id)}/replies`,
|
|
1374
|
-
{
|
|
1375
|
-
sort: normalizedSort,
|
|
1376
|
-
...options,
|
|
1377
|
-
},
|
|
1378
|
-
);
|
|
1379
|
-
|
|
1380
|
-
// add any messages to our thread state
|
|
1381
|
-
if (data.messages) {
|
|
1382
|
-
this.state.addMessagesSorted(data.messages);
|
|
1383
|
-
}
|
|
1323
|
+
async getReplies(request: GetRepliesRequest) {
|
|
1324
|
+
const data = await this.getClient().getReplies(request);
|
|
1384
1325
|
|
|
1326
|
+
// Thread reply state is owned by the Thread object (Thread.messagePaginator); the returned
|
|
1327
|
+
// replies are consumed there. The channel message list is owned by channel.messagePaginator.
|
|
1385
1328
|
return data;
|
|
1386
1329
|
}
|
|
1387
1330
|
|
|
1331
|
+
// TODO: find out v2 equivalent
|
|
1388
1332
|
/**
|
|
1389
|
-
*
|
|
1390
|
-
*
|
|
1391
|
-
* @param {PinnedMessagePaginationOptions & { user?: UserResponse; user_id?: string }} options Pagination params, ie {limit:10, id_lte: 10}
|
|
1392
|
-
* @param {PinnedMessagesSort} sort defines sorting direction of pinned messages
|
|
1333
|
+
* List pinned messages of the channel.
|
|
1393
1334
|
*
|
|
1394
|
-
* @
|
|
1335
|
+
* @param options - Pagination params, e.g. `{ limit: 10, id_lte: 10 }`.
|
|
1336
|
+
* @param sort - Defines sorting direction of pinned messages (optional, defaults to `[]`).
|
|
1337
|
+
* @returns A response with a list of messages.
|
|
1395
1338
|
*/
|
|
1396
1339
|
async getPinnedMessages(
|
|
1397
|
-
options: PinnedMessagePaginationOptions
|
|
1340
|
+
options: PinnedMessagePaginationOptions,
|
|
1398
1341
|
sort: PinnedMessagesSort = [],
|
|
1399
1342
|
) {
|
|
1400
|
-
return await this.getClient().get<GetRepliesAPIResponse>(
|
|
1343
|
+
return await this.getClient().api.get<GetRepliesAPIResponse>(
|
|
1401
1344
|
this._channelURL() + '/pinned_messages',
|
|
1402
1345
|
{
|
|
1403
1346
|
payload: {
|
|
1404
1347
|
...options,
|
|
1405
|
-
sort
|
|
1348
|
+
sort,
|
|
1406
1349
|
},
|
|
1407
1350
|
},
|
|
1408
1351
|
);
|
|
1409
1352
|
}
|
|
1410
1353
|
|
|
1411
1354
|
/**
|
|
1412
|
-
*
|
|
1413
|
-
*
|
|
1414
|
-
* @param {string} message_id The message id
|
|
1415
|
-
* @param {{ limit?: number; offset?: number }} options The pagination options
|
|
1355
|
+
* List the reactions; supports pagination.
|
|
1416
1356
|
*
|
|
1417
|
-
* @
|
|
1357
|
+
* @param request - The request payload, including the target message ID and
|
|
1358
|
+
* pagination options (`limit`, `offset`).
|
|
1359
|
+
* @returns The server response.
|
|
1418
1360
|
*/
|
|
1419
|
-
getReactions(
|
|
1420
|
-
return this.getClient().
|
|
1421
|
-
this.getClient().baseURL + `/messages/${encodeURIComponent(message_id)}/reactions`,
|
|
1422
|
-
{
|
|
1423
|
-
...options,
|
|
1424
|
-
},
|
|
1425
|
-
);
|
|
1361
|
+
getReactions(request: Parameters<ChatApi['getReactions']>[0]) {
|
|
1362
|
+
return this.getClient().getReactions(request);
|
|
1426
1363
|
}
|
|
1427
1364
|
|
|
1428
1365
|
/**
|
|
1429
|
-
*
|
|
1366
|
+
* Retrieves a list of messages by ID.
|
|
1430
1367
|
*
|
|
1431
|
-
* @param
|
|
1432
|
-
*
|
|
1433
|
-
* @return {Promise<GetMultipleMessagesAPIResponse>} Server response
|
|
1368
|
+
* @param messageIds - The IDs of the messages to retrieve from this channel.
|
|
1369
|
+
* @returns Server response.
|
|
1434
1370
|
*/
|
|
1435
1371
|
getMessagesById(messageIds: string[]) {
|
|
1436
|
-
return this.
|
|
1437
|
-
this._channelURL() + '/messages',
|
|
1438
|
-
{
|
|
1439
|
-
ids: messageIds.join(','),
|
|
1440
|
-
},
|
|
1441
|
-
);
|
|
1372
|
+
return this.getManyMessages({ ids: messageIds });
|
|
1442
1373
|
}
|
|
1443
1374
|
|
|
1444
1375
|
/**
|
|
1445
|
-
*
|
|
1446
|
-
*
|
|
1376
|
+
* Returns the last time the user marked the channel as read. If the user never marked the channel as read, this will return `null`.
|
|
1377
|
+
*
|
|
1378
|
+
* @returns The last-read `Date`, `null` if never read, or `undefined` if the user is unset.
|
|
1447
1379
|
*/
|
|
1448
1380
|
lastRead() {
|
|
1449
|
-
const {
|
|
1450
|
-
if (
|
|
1451
|
-
return this.state.read[
|
|
1381
|
+
const { userId } = this.getClient();
|
|
1382
|
+
if (userId) {
|
|
1383
|
+
return this.state.read[userId] ? this.state.read[userId].last_read : null;
|
|
1452
1384
|
}
|
|
1453
1385
|
}
|
|
1454
1386
|
|
|
@@ -1456,7 +1388,7 @@ export class Channel {
|
|
|
1456
1388
|
if (message.shadowed) return false;
|
|
1457
1389
|
if (message.silent) return false;
|
|
1458
1390
|
if (message.parent_id && !message.show_in_channel) return false;
|
|
1459
|
-
if (message.user?.id === this.getClient().
|
|
1391
|
+
if (message.user?.id === this.getClient().userId) return false;
|
|
1460
1392
|
if (message.user?.id && this.getClient().userMuteStatus(message.user.id))
|
|
1461
1393
|
return false;
|
|
1462
1394
|
|
|
@@ -1478,18 +1410,17 @@ export class Channel {
|
|
|
1478
1410
|
}
|
|
1479
1411
|
|
|
1480
1412
|
/**
|
|
1481
|
-
*
|
|
1482
|
-
*
|
|
1483
|
-
* @param {Date | null} [lastRead] lastRead the time that the user read a message, defaults to current user's read state
|
|
1413
|
+
* Count of unread messages.
|
|
1484
1414
|
*
|
|
1485
|
-
* @
|
|
1415
|
+
* @param lastRead - The time that the user read a message (optional, defaults to the current user's read state).
|
|
1416
|
+
* @returns Unread count.
|
|
1486
1417
|
*/
|
|
1487
1418
|
countUnread(lastRead?: Date | null) {
|
|
1488
1419
|
if (!lastRead) return this.state.unreadCount;
|
|
1489
|
-
// todo: prevent finding the latest message set on each iteration
|
|
1490
1420
|
let count = 0;
|
|
1491
|
-
|
|
1492
|
-
|
|
1421
|
+
const latestMessages = this.messagePaginator.headItems;
|
|
1422
|
+
for (let i = 0; i < latestMessages.length; i += 1) {
|
|
1423
|
+
const message = latestMessages[i];
|
|
1493
1424
|
if (message.created_at > lastRead && this._countMessageAsUnread(message)) {
|
|
1494
1425
|
count++;
|
|
1495
1426
|
}
|
|
@@ -1498,21 +1429,22 @@ export class Channel {
|
|
|
1498
1429
|
}
|
|
1499
1430
|
|
|
1500
1431
|
/**
|
|
1501
|
-
*
|
|
1432
|
+
* Count the number of unread messages mentioning the current user.
|
|
1502
1433
|
*
|
|
1503
|
-
* @
|
|
1434
|
+
* @returns Unread mentions count.
|
|
1504
1435
|
*/
|
|
1505
1436
|
countUnreadMentions() {
|
|
1506
1437
|
const lastRead = this.lastRead();
|
|
1507
|
-
const
|
|
1438
|
+
const userId = this.getClient().userId;
|
|
1508
1439
|
|
|
1509
1440
|
let count = 0;
|
|
1510
|
-
|
|
1511
|
-
|
|
1441
|
+
const latestMessages = this.messagePaginator.headItems;
|
|
1442
|
+
for (let i = 0; i < latestMessages.length; i += 1) {
|
|
1443
|
+
const message = latestMessages[i];
|
|
1512
1444
|
if (
|
|
1513
1445
|
this._countMessageAsUnread(message) &&
|
|
1514
1446
|
(!lastRead || message.created_at > lastRead) &&
|
|
1515
|
-
message.mentioned_users?.some((user) => user.id ===
|
|
1447
|
+
message.mentioned_users?.some((user) => user.id === userId)
|
|
1516
1448
|
) {
|
|
1517
1449
|
count++;
|
|
1518
1450
|
}
|
|
@@ -1521,12 +1453,12 @@ export class Channel {
|
|
|
1521
1453
|
}
|
|
1522
1454
|
|
|
1523
1455
|
/**
|
|
1524
|
-
*
|
|
1525
|
-
*
|
|
1526
|
-
* @return {Promise<QueryChannelAPIResponse>} The Server Response
|
|
1456
|
+
* Creates a new channel.
|
|
1527
1457
|
*
|
|
1458
|
+
* @param options - Channel query options (optional).
|
|
1459
|
+
* @returns The server response.
|
|
1528
1460
|
*/
|
|
1529
|
-
create = async (options?:
|
|
1461
|
+
create = async (options?: ChannelGetOrCreateRequest) => {
|
|
1530
1462
|
const defaultOptions = {
|
|
1531
1463
|
...options,
|
|
1532
1464
|
watch: false,
|
|
@@ -1537,53 +1469,42 @@ export class Channel {
|
|
|
1537
1469
|
};
|
|
1538
1470
|
|
|
1539
1471
|
/**
|
|
1540
|
-
*
|
|
1541
|
-
*
|
|
1542
|
-
* @param {ChannelQueryOptions} options The query options
|
|
1543
|
-
* @param {MessageSetType} messageSetToAddToIfDoesNotExist It's possible to load disjunct sets of a channel's messages into state, use `current` to load the initial channel state or if you want to extend the currently displayed messages, use `latest` if you want to load/extend the latest messages, `new` is used for loading a specific message and it's surroundings
|
|
1472
|
+
* Queries the API to load messages, members, or other channel fields.
|
|
1544
1473
|
*
|
|
1545
|
-
* @
|
|
1474
|
+
* @param options - The query options (optional, defaults to `{}`).
|
|
1475
|
+
* @param messageSetToAddToIfDoesNotExist - It's possible to load disjunct sets of a channel's
|
|
1476
|
+
* messages into state. Use `current` to load the initial channel state or to extend the
|
|
1477
|
+
* currently displayed messages; use `latest` to load/extend the latest messages; `new` is
|
|
1478
|
+
* used for loading a specific message and its surroundings (optional, defaults to `'current'`).
|
|
1479
|
+
* @returns A query response.
|
|
1546
1480
|
*/
|
|
1547
1481
|
async query(
|
|
1548
|
-
options:
|
|
1482
|
+
options: ChannelGetOrCreateRequest = {},
|
|
1549
1483
|
messageSetToAddToIfDoesNotExist: MessageSetType = 'current',
|
|
1550
1484
|
) {
|
|
1551
1485
|
// Make sure we wait for the connect promise if there is a pending one
|
|
1552
1486
|
await this.getClient().wsPromise;
|
|
1553
1487
|
|
|
1554
|
-
const
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
if (this.getClient()._isUsingServerAuth() && typeof createdById !== 'string') {
|
|
1561
|
-
this.getClient().logger(
|
|
1562
|
-
'warn',
|
|
1563
|
-
'Either `created_by` (with `id` property) or `created_by_id` are missing from both `Channel._data` and `options` parameter',
|
|
1564
|
-
);
|
|
1565
|
-
}
|
|
1488
|
+
const queryPayload: ChannelGetOrCreateRequest = {
|
|
1489
|
+
data: this._data,
|
|
1490
|
+
state: true,
|
|
1491
|
+
...options,
|
|
1492
|
+
};
|
|
1566
1493
|
|
|
1567
|
-
|
|
1568
|
-
this.
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1494
|
+
const state = this.id
|
|
1495
|
+
? await this.getOrCreate(queryPayload)
|
|
1496
|
+
: await this.getClient().getOrCreateDistinctChannel({
|
|
1497
|
+
type: this.type,
|
|
1498
|
+
...queryPayload,
|
|
1499
|
+
});
|
|
1573
1500
|
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
{
|
|
1577
|
-
data: this._data,
|
|
1578
|
-
state: true,
|
|
1579
|
-
...options,
|
|
1580
|
-
},
|
|
1581
|
-
);
|
|
1501
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
1502
|
+
const channel = state.channel!;
|
|
1582
1503
|
|
|
1583
1504
|
// update the channel id if it was missing
|
|
1584
1505
|
if (!this.id) {
|
|
1585
|
-
this.id =
|
|
1586
|
-
this.cid =
|
|
1506
|
+
this.id = channel.id;
|
|
1507
|
+
this.cid = channel.cid;
|
|
1587
1508
|
// set the channel as active...
|
|
1588
1509
|
|
|
1589
1510
|
const tempChannelCid = generateChannelTempCid(
|
|
@@ -1605,34 +1526,41 @@ export class Channel {
|
|
|
1605
1526
|
}
|
|
1606
1527
|
}
|
|
1607
1528
|
|
|
1608
|
-
this.getClient()._addChannelConfig(
|
|
1529
|
+
this.getClient()._addChannelConfig(channel);
|
|
1609
1530
|
|
|
1610
1531
|
// the only config param that is necessary to be updated based on server config soon as the config is delivered
|
|
1611
|
-
if (typeof
|
|
1532
|
+
if (typeof channel.config?.shared_locations !== 'undefined') {
|
|
1612
1533
|
this.messageComposer.updateConfig({
|
|
1613
|
-
location: { enabled:
|
|
1534
|
+
location: { enabled: channel.config.shared_locations },
|
|
1614
1535
|
});
|
|
1615
1536
|
}
|
|
1616
1537
|
|
|
1617
|
-
//
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
)
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1538
|
+
// Seed the message paginator with the first (latest) page BEFORE _initializeState, which
|
|
1539
|
+
// hydrates the read state and (via MessageReceiptsTracker) resolves read/delivered cursors
|
|
1540
|
+
// against this paginator. Seeding first guarantees the tracker sees a populated timeline; a
|
|
1541
|
+
// later async seed would run after the reconcile and mislabel delivery status. Only the
|
|
1542
|
+
// latest-page open paths (watch/create) pass 'latest' — the paginator's own pagination queries
|
|
1543
|
+
// use 'current' and must not be reseeded as a first page here.
|
|
1544
|
+
if (messageSetToAddToIfDoesNotExist === 'latest' && Array.isArray(state.messages)) {
|
|
1545
|
+
const requestedPageSize =
|
|
1546
|
+
options?.messages?.limit ?? DEFAULT_QUERY_CHANNEL_MESSAGE_LIST_PAGE_SIZE;
|
|
1547
|
+
// Pass the query's message pagination options through: a channel can be opened AROUND a
|
|
1548
|
+
// message (id_around / created_at_around), in which case the fetched page is a jump window,
|
|
1549
|
+
// not the latest page — the paginator must reconcile it with jump semantics.
|
|
1550
|
+
this.messagePaginator.seedFirstPageSync(
|
|
1551
|
+
state.messages.map(formatMessage),
|
|
1552
|
+
requestedPageSize,
|
|
1553
|
+
options?.messages,
|
|
1554
|
+
);
|
|
1555
|
+
}
|
|
1556
|
+
|
|
1557
|
+
// Seed read/members/pinned/thread-cleanup state; the message list is in the paginator.
|
|
1558
|
+
this._initializeState(state);
|
|
1559
|
+
// The queried page is the latest set unless this was a jump/around query.
|
|
1560
|
+
const isLatestMessageSet =
|
|
1561
|
+
messageSetToAddToIfDoesNotExist === 'latest' &&
|
|
1562
|
+
!options?.messages?.id_around &&
|
|
1563
|
+
!(options?.messages as MessagePaginationOptions | undefined)?.created_at_around;
|
|
1636
1564
|
|
|
1637
1565
|
this.getClient().polls.hydratePollCache(state.messages, true);
|
|
1638
1566
|
this.getClient().reminders.hydrateState(state.messages);
|
|
@@ -1640,7 +1568,7 @@ export class Channel {
|
|
|
1640
1568
|
this.messageComposer.initStateFromChannelResponse(state);
|
|
1641
1569
|
|
|
1642
1570
|
const areCapabilitiesChanged =
|
|
1643
|
-
[...(
|
|
1571
|
+
[...(channel.own_capabilities || [])].sort().join() !==
|
|
1644
1572
|
[
|
|
1645
1573
|
...(this.data && Array.isArray(this.data?.own_capabilities)
|
|
1646
1574
|
? this.data.own_capabilities
|
|
@@ -1648,7 +1576,9 @@ export class Channel {
|
|
|
1648
1576
|
]
|
|
1649
1577
|
.sort()
|
|
1650
1578
|
.join();
|
|
1651
|
-
|
|
1579
|
+
const previousData = this.data;
|
|
1580
|
+
this.data = channel;
|
|
1581
|
+
this._syncStateFromChannelData(this.data, previousData);
|
|
1652
1582
|
this.offlineMode = false;
|
|
1653
1583
|
this.cooldownTimer.refresh();
|
|
1654
1584
|
|
|
@@ -1656,7 +1586,7 @@ export class Channel {
|
|
|
1656
1586
|
this.getClient().dispatchEvent({
|
|
1657
1587
|
type: 'capabilities.changed',
|
|
1658
1588
|
cid: this.cid,
|
|
1659
|
-
own_capabilities:
|
|
1589
|
+
own_capabilities: channel.own_capabilities ?? [],
|
|
1660
1590
|
});
|
|
1661
1591
|
}
|
|
1662
1592
|
|
|
@@ -1664,14 +1594,14 @@ export class Channel {
|
|
|
1664
1594
|
type: 'channels.queried',
|
|
1665
1595
|
queriedChannels: {
|
|
1666
1596
|
channels: [state],
|
|
1667
|
-
isLatestMessageSet
|
|
1597
|
+
isLatestMessageSet,
|
|
1668
1598
|
},
|
|
1669
1599
|
});
|
|
1670
1600
|
this.getClient().offlineDb?.executeQuerySafely(
|
|
1671
1601
|
(db) =>
|
|
1672
1602
|
db.upsertChannels?.({
|
|
1673
1603
|
channels: [state],
|
|
1674
|
-
isLatestMessagesSet:
|
|
1604
|
+
isLatestMessagesSet: isLatestMessageSet,
|
|
1675
1605
|
}),
|
|
1676
1606
|
{ method: 'upsertChannels' },
|
|
1677
1607
|
);
|
|
@@ -1681,15 +1611,15 @@ export class Channel {
|
|
|
1681
1611
|
}
|
|
1682
1612
|
|
|
1683
1613
|
/**
|
|
1684
|
-
*
|
|
1614
|
+
* Bans a user from a channel.
|
|
1685
1615
|
*
|
|
1686
|
-
* @param
|
|
1687
|
-
* @param
|
|
1688
|
-
* @returns
|
|
1616
|
+
* @param targetUserId - The user to ban.
|
|
1617
|
+
* @param options - Ban options.
|
|
1618
|
+
* @returns The server response.
|
|
1689
1619
|
*/
|
|
1690
|
-
async banUser(
|
|
1620
|
+
async banUser(targetUserId: string, options: BanUserOptions) {
|
|
1691
1621
|
this._checkInitialized();
|
|
1692
|
-
return await this.getClient().banUser(
|
|
1622
|
+
return await this.getClient().banUser(targetUserId, {
|
|
1693
1623
|
...options,
|
|
1694
1624
|
type: this.type,
|
|
1695
1625
|
id: this.id,
|
|
@@ -1697,45 +1627,39 @@ export class Channel {
|
|
|
1697
1627
|
}
|
|
1698
1628
|
|
|
1699
1629
|
/**
|
|
1700
|
-
*
|
|
1701
|
-
* If
|
|
1630
|
+
* Hides the channel from `queryChannels` for the user until a message is added.
|
|
1631
|
+
* If `clear_history` is set to `true`, all messages will be removed for the user.
|
|
1702
1632
|
*
|
|
1703
|
-
* @param {
|
|
1704
|
-
*
|
|
1705
|
-
* @returns
|
|
1633
|
+
* @param request - The hide channel request payload (optional). Pass `{ clear_history: true }`
|
|
1634
|
+
* to clear message history for the user.
|
|
1635
|
+
* @returns The server response.
|
|
1706
1636
|
*/
|
|
1707
|
-
async hide(
|
|
1637
|
+
override async hide(request?: Gen_HideChannelRequest) {
|
|
1708
1638
|
this._checkInitialized();
|
|
1709
|
-
|
|
1710
|
-
return await this.getClient().post<APIResponse>(`${this._channelURL()}/hide`, {
|
|
1711
|
-
user_id: userId,
|
|
1712
|
-
clear_history: clearHistory,
|
|
1713
|
-
});
|
|
1639
|
+
return await super.hide(request);
|
|
1714
1640
|
}
|
|
1715
1641
|
|
|
1716
1642
|
/**
|
|
1717
|
-
*
|
|
1643
|
+
* Removes the hidden status for a channel. Ensures the channel is initialized first.
|
|
1718
1644
|
*
|
|
1719
|
-
* @param
|
|
1720
|
-
* @returns
|
|
1645
|
+
* @param request - The show channel request payload (optional).
|
|
1646
|
+
* @returns The server response.
|
|
1721
1647
|
*/
|
|
1722
|
-
async show(
|
|
1648
|
+
override async show(request?: Gen_ShowChannelRequest) {
|
|
1723
1649
|
this._checkInitialized();
|
|
1724
|
-
return await
|
|
1725
|
-
user_id: userId,
|
|
1726
|
-
});
|
|
1650
|
+
return await super.show(request);
|
|
1727
1651
|
}
|
|
1728
1652
|
|
|
1729
1653
|
/**
|
|
1730
|
-
*
|
|
1654
|
+
* Removes the bans for a user on a channel.
|
|
1731
1655
|
*
|
|
1732
|
-
* @param
|
|
1733
|
-
* @param
|
|
1734
|
-
* @returns
|
|
1656
|
+
* @param targetUserId - The user to unban.
|
|
1657
|
+
* @param options - Unban options (optional).
|
|
1658
|
+
* @returns The server response.
|
|
1735
1659
|
*/
|
|
1736
|
-
async unbanUser(
|
|
1660
|
+
async unbanUser(targetUserId: string, options?: UnBanUserOptions) {
|
|
1737
1661
|
this._checkInitialized();
|
|
1738
|
-
return await this.getClient().unbanUser(
|
|
1662
|
+
return await this.getClient().unbanUser(targetUserId, {
|
|
1739
1663
|
...options,
|
|
1740
1664
|
type: this.type,
|
|
1741
1665
|
id: this.id,
|
|
@@ -1743,15 +1667,15 @@ export class Channel {
|
|
|
1743
1667
|
}
|
|
1744
1668
|
|
|
1745
1669
|
/**
|
|
1746
|
-
*
|
|
1670
|
+
* Shadow bans a user from a channel.
|
|
1747
1671
|
*
|
|
1748
|
-
* @param
|
|
1749
|
-
* @param
|
|
1750
|
-
* @returns
|
|
1672
|
+
* @param targetUserId - The user to shadow ban.
|
|
1673
|
+
* @param options - Ban options.
|
|
1674
|
+
* @returns The server response.
|
|
1751
1675
|
*/
|
|
1752
|
-
async shadowBan(
|
|
1676
|
+
async shadowBan(targetUserId: string, options: BanUserOptions) {
|
|
1753
1677
|
this._checkInitialized();
|
|
1754
|
-
return await this.getClient().shadowBan(
|
|
1678
|
+
return await this.getClient().shadowBan(targetUserId, {
|
|
1755
1679
|
...options,
|
|
1756
1680
|
type: this.type,
|
|
1757
1681
|
id: this.id,
|
|
@@ -1759,242 +1683,245 @@ export class Channel {
|
|
|
1759
1683
|
}
|
|
1760
1684
|
|
|
1761
1685
|
/**
|
|
1762
|
-
*
|
|
1686
|
+
* Removes the shadow ban for a user on a channel.
|
|
1763
1687
|
*
|
|
1764
|
-
* @param
|
|
1765
|
-
* @returns
|
|
1688
|
+
* @param targetUserId - The user to remove the shadow ban for.
|
|
1689
|
+
* @returns The server response.
|
|
1766
1690
|
*/
|
|
1767
|
-
async removeShadowBan(
|
|
1691
|
+
async removeShadowBan(targetUserId: string) {
|
|
1768
1692
|
this._checkInitialized();
|
|
1769
|
-
return await this.getClient().removeShadowBan(
|
|
1693
|
+
return await this.getClient().removeShadowBan(targetUserId, {
|
|
1770
1694
|
type: this.type,
|
|
1771
1695
|
id: this.id,
|
|
1772
1696
|
});
|
|
1773
1697
|
}
|
|
1774
1698
|
|
|
1775
1699
|
/**
|
|
1776
|
-
*
|
|
1777
|
-
*
|
|
1778
|
-
* @param
|
|
1779
|
-
*
|
|
1700
|
+
* Casts or cancels one or more votes on a poll.
|
|
1701
|
+
*
|
|
1702
|
+
* @param request - The cast-poll-vote request payload, including the target message ID, poll ID,
|
|
1703
|
+
* and the vote to cast (or an empty payload to cancel).
|
|
1704
|
+
* @returns The poll vote response.
|
|
1780
1705
|
*/
|
|
1781
|
-
async vote(
|
|
1782
|
-
return await this.getClient().castPollVote(
|
|
1706
|
+
async vote(request: Parameters<ChatApi['castPollVote']>[0]) {
|
|
1707
|
+
return await this.getClient().castPollVote(request);
|
|
1783
1708
|
}
|
|
1784
1709
|
|
|
1785
|
-
async removeVote(
|
|
1786
|
-
return await this.getClient().
|
|
1710
|
+
async removeVote(request: Parameters<ChatApi['deletePollVote']>[0]) {
|
|
1711
|
+
return await this.getClient().deletePollVote(request);
|
|
1787
1712
|
}
|
|
1788
1713
|
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
*
|
|
1792
|
-
* @param {DraftMessagePayload} message The draft message to create or update
|
|
1793
|
-
*
|
|
1794
|
-
* @return {Promise<CreateDraftResponse>} Response containing the created draft
|
|
1795
|
-
*/
|
|
1796
|
-
async _createDraft(message: DraftMessagePayload) {
|
|
1797
|
-
return await this.getClient().post<CreateDraftResponse>(
|
|
1798
|
-
this._channelURL() + '/draft',
|
|
1799
|
-
{
|
|
1800
|
-
message,
|
|
1801
|
-
},
|
|
1802
|
-
);
|
|
1714
|
+
async _createDraft(request: Gen_CreateDraftRequest) {
|
|
1715
|
+
return await super.createDraft(request);
|
|
1803
1716
|
}
|
|
1804
1717
|
|
|
1805
1718
|
/**
|
|
1806
|
-
*
|
|
1807
|
-
*
|
|
1808
|
-
* bad internet conditions and executed later.
|
|
1809
|
-
*
|
|
1810
|
-
* @param {DraftMessagePayload} message The draft message to create or update
|
|
1811
|
-
*
|
|
1812
|
-
* @return {Promise<CreateDraftResponse>} Response containing the created draft
|
|
1719
|
+
* Creates or updates a draft message in a channel. If offline support is enabled, the
|
|
1720
|
+
* call is queued so it is replayed on reconnect.
|
|
1813
1721
|
*/
|
|
1814
|
-
async createDraft(
|
|
1722
|
+
override async createDraft(request: Gen_CreateDraftRequest) {
|
|
1815
1723
|
try {
|
|
1816
1724
|
const offlineDb = this.getClient().offlineDb;
|
|
1817
1725
|
if (offlineDb) {
|
|
1818
|
-
return await offlineDb.queueTask<CreateDraftResponse>({
|
|
1726
|
+
return (await offlineDb.queueTask<CreateDraftResponse>({
|
|
1819
1727
|
task: {
|
|
1820
1728
|
channelId: this.id as string,
|
|
1821
1729
|
channelType: this.type,
|
|
1822
|
-
threadId: message
|
|
1823
|
-
payload: [
|
|
1730
|
+
threadId: request.message?.parent_id,
|
|
1731
|
+
payload: [request],
|
|
1824
1732
|
type: 'create-draft',
|
|
1825
1733
|
},
|
|
1826
|
-
})
|
|
1734
|
+
})) as Awaited<ReturnType<ChannelApi['createDraft']>>;
|
|
1827
1735
|
}
|
|
1828
1736
|
} catch (error) {
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
error,
|
|
1832
|
-
});
|
|
1737
|
+
offlineDbLogger
|
|
1738
|
+
.withExtraTags('createDraft', this.cid)
|
|
1739
|
+
.error('Creating the draft in the offline database failed.', { error });
|
|
1833
1740
|
}
|
|
1834
1741
|
|
|
1835
|
-
return this._createDraft(
|
|
1742
|
+
return this._createDraft(request);
|
|
1836
1743
|
}
|
|
1837
1744
|
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
*
|
|
1841
|
-
* @param {Object} options
|
|
1842
|
-
* @param {string} options.parent_id Optional parent message ID for drafts in threads
|
|
1843
|
-
*
|
|
1844
|
-
* @return {Promise<APIResponse>} API response
|
|
1845
|
-
*/
|
|
1846
|
-
async _deleteDraft({ parent_id }: { parent_id?: string } = {}) {
|
|
1847
|
-
return await this.getClient().delete<APIResponse>(this._channelURL() + '/draft', {
|
|
1848
|
-
parent_id,
|
|
1849
|
-
});
|
|
1745
|
+
async _deleteDraft(request?: Parameters<ChannelApi['deleteDraft']>[0]) {
|
|
1746
|
+
return await super.deleteDraft(request);
|
|
1850
1747
|
}
|
|
1851
1748
|
|
|
1852
1749
|
/**
|
|
1853
|
-
*
|
|
1854
|
-
*
|
|
1855
|
-
* bad internet conditions and executed later.
|
|
1856
|
-
*
|
|
1857
|
-
* @param {Object} options
|
|
1858
|
-
* @param {string} options.parent_id Optional parent message ID for drafts in threads
|
|
1859
|
-
*
|
|
1860
|
-
* @return {Promise<APIResponse>} API response
|
|
1750
|
+
* Deletes a draft message from a channel or a thread. If offline support is enabled, the
|
|
1751
|
+
* call is queued so it is replayed on reconnect.
|
|
1861
1752
|
*/
|
|
1862
|
-
async deleteDraft(
|
|
1863
|
-
const { parent_id } = options;
|
|
1753
|
+
override async deleteDraft(request?: Parameters<ChannelApi['deleteDraft']>[0]) {
|
|
1864
1754
|
try {
|
|
1865
1755
|
const offlineDb = this.getClient().offlineDb;
|
|
1866
1756
|
if (offlineDb) {
|
|
1867
|
-
return await offlineDb.queueTask<APIResponse>({
|
|
1757
|
+
return (await offlineDb.queueTask<APIResponse>({
|
|
1868
1758
|
task: {
|
|
1869
1759
|
channelId: this.id as string,
|
|
1870
1760
|
channelType: this.type,
|
|
1871
|
-
threadId: parent_id,
|
|
1872
|
-
payload: [
|
|
1761
|
+
threadId: request?.parent_id,
|
|
1762
|
+
payload: [request],
|
|
1873
1763
|
type: 'delete-draft',
|
|
1874
1764
|
},
|
|
1875
|
-
})
|
|
1765
|
+
})) as Awaited<ReturnType<ChannelApi['deleteDraft']>>;
|
|
1876
1766
|
}
|
|
1877
1767
|
} catch (error) {
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
error,
|
|
1881
|
-
});
|
|
1768
|
+
offlineDbLogger
|
|
1769
|
+
.withExtraTags('deleteDraft', this.cid)
|
|
1770
|
+
.error('Deleting the draft from the offline database failed.', { error });
|
|
1882
1771
|
}
|
|
1883
1772
|
|
|
1884
|
-
return this._deleteDraft(
|
|
1773
|
+
return this._deleteDraft(request);
|
|
1885
1774
|
}
|
|
1886
1775
|
|
|
1887
1776
|
/**
|
|
1888
|
-
*
|
|
1777
|
+
* Listens to events on this channel.
|
|
1889
1778
|
*
|
|
1890
|
-
* @
|
|
1891
|
-
*
|
|
1892
|
-
*
|
|
1893
|
-
*
|
|
1894
|
-
*/
|
|
1895
|
-
async getDraft({ parent_id }: { parent_id?: string } = {}) {
|
|
1896
|
-
return await this.getClient().get<GetDraftResponse>(this._channelURL() + '/draft', {
|
|
1897
|
-
parent_id,
|
|
1898
|
-
});
|
|
1899
|
-
}
|
|
1900
|
-
|
|
1901
|
-
/**
|
|
1902
|
-
* on - Listen to events on this channel.
|
|
1779
|
+
* @example
|
|
1780
|
+
* channel.on('message.new', (event) => {
|
|
1781
|
+
* console.log('my new message', event, channel.state.messages);
|
|
1782
|
+
* });
|
|
1903
1783
|
*
|
|
1904
|
-
*
|
|
1905
|
-
*
|
|
1906
|
-
*
|
|
1784
|
+
* @example
|
|
1785
|
+
* channel.on((event) => {
|
|
1786
|
+
* console.log(event.type);
|
|
1787
|
+
* });
|
|
1907
1788
|
*
|
|
1908
|
-
* @param
|
|
1909
|
-
* @param
|
|
1789
|
+
* @param callbackOrString - The event type to listen for, or the callback when listening to all events.
|
|
1790
|
+
* @param callbackOrNothing - The callback to call when an event type was provided (optional).
|
|
1791
|
+
* @returns An object with an `unsubscribe()` method.
|
|
1910
1792
|
*/
|
|
1911
|
-
on
|
|
1793
|
+
on<T extends EventType | string>(
|
|
1794
|
+
eventType: T,
|
|
1795
|
+
callback: EventHandler<T>,
|
|
1796
|
+
): { unsubscribe: () => void };
|
|
1912
1797
|
on(callback: EventHandler): { unsubscribe: () => void };
|
|
1913
1798
|
on(
|
|
1914
|
-
callbackOrString: EventHandler |
|
|
1799
|
+
callbackOrString: EventHandler | string,
|
|
1915
1800
|
callbackOrNothing?: EventHandler,
|
|
1916
1801
|
): { unsubscribe: () => void } {
|
|
1917
|
-
const key = callbackOrNothing ? (callbackOrString as
|
|
1918
|
-
const callback = callbackOrNothing
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
this.
|
|
1923
|
-
'info',
|
|
1924
|
-
`Attaching listener for ${key} event on channel ${this.cid}`,
|
|
1925
|
-
{
|
|
1926
|
-
tags: ['event', 'channel'],
|
|
1927
|
-
channel: this,
|
|
1928
|
-
},
|
|
1929
|
-
);
|
|
1802
|
+
const key = callbackOrNothing ? (callbackOrString as EventType) : 'all';
|
|
1803
|
+
const callback = callbackOrNothing
|
|
1804
|
+
? callbackOrNothing
|
|
1805
|
+
: (callbackOrString as EventHandler);
|
|
1806
|
+
|
|
1807
|
+
const set = this.listeners.get(key) ?? new Set();
|
|
1930
1808
|
|
|
1931
|
-
|
|
1809
|
+
logger
|
|
1810
|
+
.withExtraTags('on', this.cid)
|
|
1811
|
+
.debug(`Attaching a listener for the "${key}" event.`);
|
|
1812
|
+
set.add(callback);
|
|
1813
|
+
|
|
1814
|
+
if (!this.listeners.has(key)) {
|
|
1815
|
+
this.listeners.set(key, set);
|
|
1816
|
+
}
|
|
1932
1817
|
|
|
1933
1818
|
return {
|
|
1934
1819
|
unsubscribe: () => {
|
|
1935
|
-
|
|
1936
|
-
'
|
|
1937
|
-
`Removing listener for ${key} event
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
);
|
|
1943
|
-
|
|
1944
|
-
this.listeners[key] = this.listeners[key].filter((el) => el !== callback);
|
|
1820
|
+
logger
|
|
1821
|
+
.withExtraTags('on', this.cid)
|
|
1822
|
+
.debug(`Removing the listener for the "${key}" event.`);
|
|
1823
|
+
set.delete(callback);
|
|
1824
|
+
if (!set.size) {
|
|
1825
|
+
this.listeners.delete(key);
|
|
1826
|
+
}
|
|
1945
1827
|
},
|
|
1946
1828
|
};
|
|
1947
1829
|
}
|
|
1948
1830
|
|
|
1949
1831
|
/**
|
|
1950
|
-
*
|
|
1832
|
+
* Removes the event handler.
|
|
1951
1833
|
*
|
|
1834
|
+
* @param callbackOrString - The event type, or the callback when removing an all-events listener.
|
|
1835
|
+
* @param callbackOrNothing - The callback to remove when an event type was provided (optional).
|
|
1952
1836
|
*/
|
|
1953
|
-
off(eventType:
|
|
1837
|
+
off<T extends EventType | string>(eventType: T, callback: EventHandler): void;
|
|
1954
1838
|
off(callback: EventHandler): void;
|
|
1955
|
-
off(
|
|
1956
|
-
callbackOrString
|
|
1957
|
-
callbackOrNothing
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
this.
|
|
1839
|
+
off(callbackOrString: EventHandler | string, callbackOrNothing?: EventHandler): void {
|
|
1840
|
+
const key = callbackOrNothing ? (callbackOrString as EventType) : 'all';
|
|
1841
|
+
const callback = callbackOrNothing
|
|
1842
|
+
? callbackOrNothing
|
|
1843
|
+
: (callbackOrString as EventHandler);
|
|
1844
|
+
|
|
1845
|
+
logger
|
|
1846
|
+
.withExtraTags('off', this.cid)
|
|
1847
|
+
.debug(`Removing the listener for the "${key}" event.`);
|
|
1848
|
+
|
|
1849
|
+
const set = this.listeners.get(key);
|
|
1850
|
+
|
|
1851
|
+
set?.delete(callback);
|
|
1852
|
+
|
|
1853
|
+
if (!set?.size) {
|
|
1854
|
+
this.listeners.delete(key);
|
|
1963
1855
|
}
|
|
1856
|
+
}
|
|
1964
1857
|
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
)
|
|
1973
|
-
|
|
1858
|
+
private _patchReadState(
|
|
1859
|
+
patch: (currentReadState: ChannelState['read']) => ChannelState['read'],
|
|
1860
|
+
reconcileMeta?: ReadStoreReconcileMeta,
|
|
1861
|
+
) {
|
|
1862
|
+
let hasStateChanged = false;
|
|
1863
|
+
this.messageReceiptsTracker.setPendingReadStoreReconcileMeta(reconcileMeta);
|
|
1864
|
+
|
|
1865
|
+
this.state.readStore.next((currentReadStoreState) => {
|
|
1866
|
+
const nextReadState = patch(currentReadStoreState.read);
|
|
1867
|
+
|
|
1868
|
+
if (nextReadState === currentReadStoreState.read) {
|
|
1869
|
+
return currentReadStoreState;
|
|
1870
|
+
}
|
|
1871
|
+
hasStateChanged = true;
|
|
1872
|
+
|
|
1873
|
+
return {
|
|
1874
|
+
...currentReadStoreState,
|
|
1875
|
+
read: nextReadState,
|
|
1876
|
+
};
|
|
1877
|
+
});
|
|
1878
|
+
|
|
1879
|
+
if (!hasStateChanged) {
|
|
1880
|
+
this.messageReceiptsTracker.setPendingReadStoreReconcileMeta(undefined);
|
|
1881
|
+
}
|
|
1882
|
+
}
|
|
1883
|
+
|
|
1884
|
+
private _upsertReadState(
|
|
1885
|
+
userId: string,
|
|
1886
|
+
update: (
|
|
1887
|
+
currentUserReadState: ChannelState['read'][string] | undefined,
|
|
1888
|
+
) => ChannelState['read'][string],
|
|
1889
|
+
reconcileMeta?: ReadStoreReconcileMeta,
|
|
1890
|
+
) {
|
|
1891
|
+
let nextUserReadState: ChannelState['read'][string] | undefined;
|
|
1892
|
+
|
|
1893
|
+
this._patchReadState((currentReadState) => {
|
|
1894
|
+
const currentUserReadState = currentReadState[userId];
|
|
1895
|
+
const updatedUserReadState = update(currentUserReadState);
|
|
1896
|
+
|
|
1897
|
+
nextUserReadState = updatedUserReadState;
|
|
1898
|
+
|
|
1899
|
+
return {
|
|
1900
|
+
...currentReadState,
|
|
1901
|
+
[userId]: updatedUserReadState,
|
|
1902
|
+
};
|
|
1903
|
+
}, reconcileMeta);
|
|
1904
|
+
|
|
1905
|
+
return nextUserReadState;
|
|
1974
1906
|
}
|
|
1975
1907
|
|
|
1976
1908
|
_handleChannelEvent(event: Event) {
|
|
1977
1909
|
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
|
1978
1910
|
const channel = this;
|
|
1979
|
-
|
|
1980
|
-
'
|
|
1981
|
-
`
|
|
1982
|
-
{
|
|
1983
|
-
tags: ['event', 'channel'],
|
|
1984
|
-
channel: this,
|
|
1985
|
-
},
|
|
1986
|
-
);
|
|
1911
|
+
logger
|
|
1912
|
+
.withExtraTags('_handleChannelEvent', this.cid)
|
|
1913
|
+
.debug(`Received an event of type "${event.type}".`, { event });
|
|
1987
1914
|
|
|
1988
1915
|
const channelState = channel.state;
|
|
1989
1916
|
switch (event.type) {
|
|
1990
1917
|
case 'typing.start':
|
|
1991
1918
|
if (event.user?.id) {
|
|
1992
|
-
channelState.
|
|
1919
|
+
channelState.setTypingEvent(event.user.id, event);
|
|
1993
1920
|
}
|
|
1994
1921
|
break;
|
|
1995
1922
|
case 'typing.stop':
|
|
1996
1923
|
if (event.user?.id) {
|
|
1997
|
-
|
|
1924
|
+
channelState.removeTypingEvent(event.user.id);
|
|
1998
1925
|
}
|
|
1999
1926
|
break;
|
|
2000
1927
|
// `message.read_locally` is the client-only event dispatched by `markReadLocally()` when read
|
|
@@ -2004,22 +1931,45 @@ export class Channel {
|
|
|
2004
1931
|
case 'message.read_locally':
|
|
2005
1932
|
case 'message.read':
|
|
2006
1933
|
if (event.user?.id && event.created_at) {
|
|
2007
|
-
const
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
1934
|
+
const eventUser = event.user;
|
|
1935
|
+
const readAtDate = new Date(event.created_at);
|
|
1936
|
+
const toDate = (value?: string | Date) =>
|
|
1937
|
+
value ? (value instanceof Date ? value : new Date(value)) : undefined;
|
|
1938
|
+
const userReadState = this._upsertReadState(
|
|
1939
|
+
eventUser.id,
|
|
1940
|
+
(currentUserReadState) => {
|
|
1941
|
+
const currentDeliveredAt = toDate(currentUserReadState?.last_delivered_at);
|
|
1942
|
+
|
|
1943
|
+
return {
|
|
1944
|
+
// preserve delivery information already known for user
|
|
1945
|
+
...currentUserReadState,
|
|
1946
|
+
...(currentUserReadState?.last_read
|
|
1947
|
+
? { last_read: toDate(currentUserReadState.last_read) }
|
|
1948
|
+
: null),
|
|
1949
|
+
...(currentDeliveredAt
|
|
1950
|
+
? { last_delivered_at: currentDeliveredAt }
|
|
1951
|
+
: null),
|
|
1952
|
+
last_read: readAtDate,
|
|
1953
|
+
last_read_message_id: event.last_read_message_id,
|
|
1954
|
+
last_delivered_at:
|
|
1955
|
+
!currentDeliveredAt || currentDeliveredAt < readAtDate
|
|
1956
|
+
? readAtDate
|
|
1957
|
+
: currentDeliveredAt,
|
|
1958
|
+
last_delivered_message_id:
|
|
1959
|
+
!currentDeliveredAt || currentDeliveredAt < readAtDate
|
|
1960
|
+
? (event.last_read_message_id ??
|
|
1961
|
+
currentUserReadState?.last_delivered_message_id)
|
|
1962
|
+
: currentUserReadState?.last_delivered_message_id,
|
|
1963
|
+
first_unread_message_id: undefined,
|
|
1964
|
+
user: eventUser,
|
|
1965
|
+
unread_messages: 0,
|
|
1966
|
+
};
|
|
1967
|
+
},
|
|
1968
|
+
{ changedUserIds: [eventUser.id] },
|
|
1969
|
+
);
|
|
1970
|
+
void userReadState;
|
|
2022
1971
|
|
|
1972
|
+
const client = this.getClient();
|
|
2023
1973
|
const isOwnEvent = event.user?.id === client.user?.id;
|
|
2024
1974
|
|
|
2025
1975
|
if (isOwnEvent) {
|
|
@@ -2035,21 +1985,40 @@ export class Channel {
|
|
|
2035
1985
|
case 'message.delivered':
|
|
2036
1986
|
// todo: update also on thread
|
|
2037
1987
|
if (event.user?.id && event.created_at) {
|
|
2038
|
-
const
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
1988
|
+
const eventUser = event.user;
|
|
1989
|
+
const createdAt = event.created_at;
|
|
1990
|
+
const toDate = (value?: string | Date) =>
|
|
1991
|
+
value ? (value instanceof Date ? value : new Date(value)) : undefined;
|
|
1992
|
+
const resolvedDeliveredAt = new Date(event.last_delivered_at ?? createdAt);
|
|
1993
|
+
const userReadState = this._upsertReadState(
|
|
1994
|
+
eventUser.id,
|
|
1995
|
+
(currentUserReadState) => {
|
|
1996
|
+
const currentDeliveredAt = toDate(currentUserReadState?.last_delivered_at);
|
|
1997
|
+
const currentReadAt = toDate(currentUserReadState?.last_read);
|
|
1998
|
+
|
|
1999
|
+
return {
|
|
2000
|
+
...currentUserReadState,
|
|
2001
|
+
...(currentReadAt ? { last_read: currentReadAt } : null),
|
|
2002
|
+
...(currentDeliveredAt
|
|
2003
|
+
? { last_delivered_at: currentDeliveredAt }
|
|
2004
|
+
: null),
|
|
2005
|
+
last_delivered_at:
|
|
2006
|
+
currentDeliveredAt && currentDeliveredAt > resolvedDeliveredAt
|
|
2007
|
+
? currentDeliveredAt
|
|
2008
|
+
: resolvedDeliveredAt,
|
|
2009
|
+
last_delivered_message_id:
|
|
2010
|
+
currentDeliveredAt && currentDeliveredAt > resolvedDeliveredAt
|
|
2011
|
+
? currentUserReadState?.last_delivered_message_id
|
|
2012
|
+
: event.last_delivered_message_id,
|
|
2013
|
+
user: eventUser,
|
|
2014
|
+
// delivery events can be received before read events
|
|
2015
|
+
last_read: currentReadAt ?? new Date(createdAt),
|
|
2016
|
+
unread_messages: currentUserReadState?.unread_messages ?? 0,
|
|
2017
|
+
};
|
|
2018
|
+
},
|
|
2019
|
+
{ changedUserIds: [eventUser.id] },
|
|
2020
|
+
);
|
|
2021
|
+
void userReadState;
|
|
2053
2022
|
|
|
2054
2023
|
const client = this.getClient();
|
|
2055
2024
|
const isOwnEvent = event.user?.id === client.user?.id;
|
|
@@ -2075,23 +2044,37 @@ export class Channel {
|
|
|
2075
2044
|
case 'message.deleted':
|
|
2076
2045
|
if (event.message) {
|
|
2077
2046
|
this._extendEventWithOwnReactions(event);
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2047
|
+
const formattedMessage = formatMessage(event.message);
|
|
2048
|
+
const isThreadReply =
|
|
2049
|
+
!!event.message.parent_id && !event.message.show_in_channel;
|
|
2050
|
+
// Thread-only replies are handled by the Thread object; the channel owns the main list.
|
|
2051
|
+
if (!isThreadReply) {
|
|
2052
|
+
if (event.hard_delete) {
|
|
2053
|
+
this.messagePaginator.removeItem({ id: event.message.id });
|
|
2054
|
+
this.pinnedMessagesPaginator.removeItem({ id: event.message.id });
|
|
2055
|
+
} else {
|
|
2056
|
+
this.messagePaginator.ingestItem(formattedMessage);
|
|
2057
|
+
this.pinnedMessagesPaginator.ingestItem(formattedMessage);
|
|
2058
|
+
}
|
|
2085
2059
|
}
|
|
2060
|
+
this.messagePaginator.reflectQuotedMessageUpdate(formattedMessage);
|
|
2061
|
+
this.pinnedMessagesPaginator.reflectQuotedMessageUpdate(formattedMessage);
|
|
2086
2062
|
}
|
|
2087
2063
|
break;
|
|
2088
2064
|
case 'user.messages.deleted':
|
|
2089
2065
|
if (event.user) {
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2066
|
+
const deletedAt = new Date(event.created_at ?? Date.now());
|
|
2067
|
+
const hardDelete = !!event.hard_delete;
|
|
2068
|
+
this.messagePaginator.applyMessageDeletionForUser({
|
|
2069
|
+
userId: event.user.id,
|
|
2070
|
+
hardDelete,
|
|
2071
|
+
deletedAt,
|
|
2072
|
+
});
|
|
2073
|
+
this.pinnedMessagesPaginator.applyMessageDeletionForUser({
|
|
2074
|
+
userId: event.user.id,
|
|
2075
|
+
hardDelete,
|
|
2076
|
+
deletedAt,
|
|
2077
|
+
});
|
|
2095
2078
|
}
|
|
2096
2079
|
break;
|
|
2097
2080
|
case 'message.new':
|
|
@@ -2102,12 +2085,13 @@ export class Channel {
|
|
|
2102
2085
|
const isThreadMessage =
|
|
2103
2086
|
event.message.parent_id && !event.message.show_in_channel;
|
|
2104
2087
|
|
|
2105
|
-
if (
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2088
|
+
if (!isThreadMessage) {
|
|
2089
|
+
// ingestItem advances the paginator's tracked latest message (→ last_message_at). A
|
|
2090
|
+
// message that arrives while the viewer has scrolled to an older window lands in the
|
|
2091
|
+
// head interval, not the active one, so the view is preserved without an isUpToDate flag.
|
|
2092
|
+
this.messagePaginator.ingestItem(formatMessage(event.message));
|
|
2093
|
+
// ingestItem auto-adds when pinned (matchesFilter { pinned: true }).
|
|
2094
|
+
this.pinnedMessagesPaginator.ingestItem(formatMessage(event.message));
|
|
2111
2095
|
}
|
|
2112
2096
|
|
|
2113
2097
|
// do not increase the unread count - the back-end does not increase the count neither in the following cases:
|
|
@@ -2120,23 +2104,54 @@ export class Channel {
|
|
|
2120
2104
|
if (preventUnreadCountUpdate) break;
|
|
2121
2105
|
|
|
2122
2106
|
if (event.user?.id) {
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2107
|
+
const eventUser = event.user;
|
|
2108
|
+
const eventUserId = eventUser.id;
|
|
2109
|
+
const createdAt = new Date(event.created_at ?? Date.now());
|
|
2110
|
+
const eventMessageId = event.message.id;
|
|
2111
|
+
this._patchReadState(
|
|
2112
|
+
(currentReadState) => {
|
|
2113
|
+
const userIds = Object.keys(currentReadState);
|
|
2114
|
+
if (!userIds.length) return currentReadState;
|
|
2115
|
+
|
|
2116
|
+
const nextReadState = { ...currentReadState };
|
|
2117
|
+
|
|
2118
|
+
for (const userId of userIds) {
|
|
2119
|
+
if (userId === eventUserId) {
|
|
2120
|
+
nextReadState[eventUserId] = {
|
|
2121
|
+
last_read: createdAt,
|
|
2122
|
+
user: eventUser,
|
|
2123
|
+
unread_messages: 0,
|
|
2124
|
+
last_delivered_at: createdAt,
|
|
2125
|
+
last_delivered_message_id: eventMessageId,
|
|
2126
|
+
};
|
|
2127
|
+
} else {
|
|
2128
|
+
nextReadState[userId] = {
|
|
2129
|
+
...currentReadState[userId],
|
|
2130
|
+
unread_messages:
|
|
2131
|
+
(currentReadState[userId]?.unread_messages ?? 0) + 1,
|
|
2132
|
+
};
|
|
2133
|
+
}
|
|
2134
|
+
}
|
|
2135
|
+
|
|
2136
|
+
return nextReadState;
|
|
2137
|
+
},
|
|
2138
|
+
{ changedUserIds: Object.keys(channelState.read) },
|
|
2139
|
+
);
|
|
2136
2140
|
}
|
|
2137
2141
|
|
|
2138
|
-
|
|
2142
|
+
// Skip the own-unread bump when the user is actively viewing the latest messages (app
|
|
2143
|
+
// foregrounded + newest message on screen). Without this, a message read in real time
|
|
2144
|
+
// momentarily bumps `unreadCount`/the snapshot — the "N new" separator/banner + the
|
|
2145
|
+
// channel-list badge would flash until the SDK's mark-read resets it. The SDK reports the
|
|
2146
|
+
// viewing state via `messagePaginator.setViewingLive` and marks the message read itself.
|
|
2147
|
+
// Only the OWN unread accounting is gated; the per-user read/receipt tracking above is
|
|
2148
|
+
// intentionally left intact.
|
|
2149
|
+
const isViewingLive = this.messagePaginator.isViewingLive;
|
|
2150
|
+
if (!isViewingLive && this._countMessageAsUnread(event.message)) {
|
|
2139
2151
|
channelState.unreadCount = channelState.unreadCount + 1;
|
|
2152
|
+
this.messagePaginator.setUnreadSnapshot({
|
|
2153
|
+
unreadCount: channelState.unreadCount,
|
|
2154
|
+
});
|
|
2140
2155
|
}
|
|
2141
2156
|
|
|
2142
2157
|
client.syncDeliveredCandidates([this]);
|
|
@@ -2146,44 +2161,38 @@ export class Channel {
|
|
|
2146
2161
|
case 'message.undeleted':
|
|
2147
2162
|
if (event.message) {
|
|
2148
2163
|
this._extendEventWithOwnReactions(event);
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2164
|
+
const formattedMessage = formatMessage(event.message);
|
|
2165
|
+
if (!event.message.parent_id) {
|
|
2166
|
+
this.messagePaginator.ingestItem(formattedMessage);
|
|
2167
|
+
this.messagePaginator.reflectQuotedMessageUpdate(formattedMessage);
|
|
2168
|
+
// ingestItem auto-adds on pin / auto-removes on unpin (matchesFilter { pinned: true }).
|
|
2169
|
+
this.pinnedMessagesPaginator.ingestItem(formattedMessage);
|
|
2170
|
+
this.pinnedMessagesPaginator.reflectQuotedMessageUpdate(formattedMessage);
|
|
2155
2171
|
}
|
|
2156
2172
|
}
|
|
2157
2173
|
break;
|
|
2158
2174
|
case 'channel.truncated':
|
|
2159
2175
|
if (event.channel?.truncated_at) {
|
|
2160
|
-
const
|
|
2161
|
-
|
|
2162
|
-
channelState.
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
});
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
if (truncatedAt > +createdAt)
|
|
2171
|
-
channelState.removePinnedMessage({ id } as MessageResponse);
|
|
2172
|
-
});
|
|
2173
|
-
channelState.unreadCount = this.countUnread(
|
|
2174
|
-
new Date(event.channel.truncated_at),
|
|
2175
|
-
);
|
|
2176
|
+
const truncatedAtDate = new Date(event.channel.truncated_at);
|
|
2177
|
+
|
|
2178
|
+
channelState.unreadCount = this.countUnread(truncatedAtDate);
|
|
2179
|
+
// Partial truncation: keep messages newer than the cutoff. clearStateAndCache would wipe
|
|
2180
|
+
// the whole paginator (readers now source from it), so use the partial truncate. The
|
|
2181
|
+
// channel-wide read/unread context is reset by the truncation, so drop the unread snapshot
|
|
2182
|
+
// too (clearStateAndCache did this for the full-truncate branch).
|
|
2183
|
+
this.messagePaginator.truncate({ truncatedAt: truncatedAtDate });
|
|
2184
|
+
this.messagePaginator.clearUnreadSnapshot();
|
|
2185
|
+
this.pinnedMessagesPaginator.truncate({ truncatedAt: truncatedAtDate });
|
|
2176
2186
|
} else {
|
|
2177
|
-
channelState.clearMessages();
|
|
2178
2187
|
channelState.unreadCount = 0;
|
|
2188
|
+
this.messagePaginator.clearStateAndCache();
|
|
2189
|
+
this.pinnedMessagesPaginator.clearStateAndCache();
|
|
2179
2190
|
}
|
|
2180
2191
|
|
|
2181
2192
|
// system messages don't increment unread counts
|
|
2182
2193
|
if (event.message) {
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
channelState.addPinnedMessage(event.message);
|
|
2186
|
-
}
|
|
2194
|
+
this.messagePaginator.ingestItem(formatMessage(event.message));
|
|
2195
|
+
this.pinnedMessagesPaginator.ingestItem(formatMessage(event.message));
|
|
2187
2196
|
}
|
|
2188
2197
|
|
|
2189
2198
|
break;
|
|
@@ -2208,7 +2217,7 @@ export class Channel {
|
|
|
2208
2217
|
};
|
|
2209
2218
|
}
|
|
2210
2219
|
|
|
2211
|
-
const currentUserId = this.getClient().
|
|
2220
|
+
const currentUserId = this.getClient().userId;
|
|
2212
2221
|
if (
|
|
2213
2222
|
typeof currentUserId === 'string' &&
|
|
2214
2223
|
typeof memberCopy?.user?.id === 'string' &&
|
|
@@ -2233,26 +2242,33 @@ export class Channel {
|
|
|
2233
2242
|
break;
|
|
2234
2243
|
case 'notification.mark_unread': {
|
|
2235
2244
|
const ownMessage = event.user?.id === this.getClient().user?.id;
|
|
2236
|
-
if (!ownMessage || !event.user) break;
|
|
2237
|
-
|
|
2245
|
+
if (!ownMessage || !event.user || !event.last_read_at) break;
|
|
2246
|
+
const eventUser = event.user;
|
|
2247
|
+
const lastReadAt = event.last_read_at;
|
|
2238
2248
|
const unreadCount = event.unread_messages ?? 0;
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
+
this._upsertReadState(
|
|
2250
|
+
eventUser.id,
|
|
2251
|
+
(currentUserReadState) => ({
|
|
2252
|
+
// keep the message delivery info
|
|
2253
|
+
...currentUserReadState,
|
|
2254
|
+
first_unread_message_id: event.first_unread_message_id,
|
|
2255
|
+
last_read: new Date(lastReadAt),
|
|
2256
|
+
last_read_message_id: event.last_read_message_id,
|
|
2257
|
+
user: eventUser,
|
|
2258
|
+
unread_messages: unreadCount,
|
|
2259
|
+
}),
|
|
2260
|
+
{ changedUserIds: [eventUser.id] },
|
|
2261
|
+
);
|
|
2249
2262
|
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
lastReadAt: event.
|
|
2254
|
-
lastReadMessageId: event.last_read_message_id,
|
|
2263
|
+
this.messagePaginator.setUnreadSnapshot({
|
|
2264
|
+
firstUnreadMessageId:
|
|
2265
|
+
channelState.read[event.user.id].first_unread_message_id ?? null,
|
|
2266
|
+
lastReadAt: channelState.read[event.user.id].last_read,
|
|
2267
|
+
lastReadMessageId: channelState.read[event.user.id].last_read_message_id,
|
|
2268
|
+
unreadCount,
|
|
2255
2269
|
});
|
|
2270
|
+
|
|
2271
|
+
channelState.unreadCount = unreadCount;
|
|
2256
2272
|
break;
|
|
2257
2273
|
}
|
|
2258
2274
|
case 'channel.updated':
|
|
@@ -2263,57 +2279,98 @@ export class Channel {
|
|
|
2263
2279
|
if (isFrozenChanged) {
|
|
2264
2280
|
this.query({ state: false, messages: { limit: 0 }, watchers: { limit: 0 } });
|
|
2265
2281
|
}
|
|
2282
|
+
const previousChannelData = channel.data;
|
|
2266
2283
|
const newChannelData = {
|
|
2267
2284
|
...event.channel,
|
|
2268
2285
|
hidden: event.channel?.hidden ?? channel.data?.hidden,
|
|
2286
|
+
member_count: event.channel?.member_count ?? channel.data?.member_count,
|
|
2269
2287
|
own_capabilities:
|
|
2270
2288
|
event.channel?.own_capabilities ?? channel.data?.own_capabilities,
|
|
2271
2289
|
};
|
|
2272
2290
|
channel.data = newChannelData;
|
|
2291
|
+
channel._syncStateFromChannelData(channel.data, previousChannelData);
|
|
2273
2292
|
this.cooldownTimer.refresh();
|
|
2274
2293
|
}
|
|
2275
2294
|
break;
|
|
2276
2295
|
case 'reaction.new':
|
|
2277
2296
|
if (event.message && event.reaction) {
|
|
2278
|
-
const {
|
|
2279
|
-
|
|
2297
|
+
const { reaction } = event;
|
|
2298
|
+
// Reflect main messages AND show_in_channel replies (both live in these paginators);
|
|
2299
|
+
// pure replies are handled by the thread's own reaction subscription.
|
|
2300
|
+
if (!event.message?.parent_id || event.message.show_in_channel) {
|
|
2301
|
+
this.messagePaginator.reflectReaction({ message: event.message, reaction });
|
|
2302
|
+
this.pinnedMessagesPaginator.reflectReaction({
|
|
2303
|
+
message: event.message,
|
|
2304
|
+
reaction,
|
|
2305
|
+
});
|
|
2306
|
+
}
|
|
2280
2307
|
}
|
|
2281
2308
|
break;
|
|
2282
2309
|
case 'reaction.deleted':
|
|
2283
2310
|
if (event.message && event.reaction) {
|
|
2284
|
-
const {
|
|
2285
|
-
|
|
2311
|
+
const { reaction } = event;
|
|
2312
|
+
if (
|
|
2313
|
+
event.message &&
|
|
2314
|
+
(!event.message.parent_id || event.message.show_in_channel)
|
|
2315
|
+
) {
|
|
2316
|
+
this.messagePaginator.reflectReaction({
|
|
2317
|
+
message: event.message,
|
|
2318
|
+
reaction,
|
|
2319
|
+
removed: true,
|
|
2320
|
+
});
|
|
2321
|
+
this.pinnedMessagesPaginator.reflectReaction({
|
|
2322
|
+
message: event.message,
|
|
2323
|
+
reaction,
|
|
2324
|
+
removed: true,
|
|
2325
|
+
});
|
|
2326
|
+
}
|
|
2286
2327
|
}
|
|
2287
2328
|
break;
|
|
2288
2329
|
case 'reaction.updated':
|
|
2289
2330
|
if (event.message && event.reaction) {
|
|
2290
|
-
const {
|
|
2331
|
+
const { reaction } = event;
|
|
2291
2332
|
// assuming reaction.updated is only called if enforce_unique is true
|
|
2292
|
-
event.message
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
2333
|
+
if (!event.message?.parent_id || event.message.show_in_channel) {
|
|
2334
|
+
this.messagePaginator.reflectReaction({
|
|
2335
|
+
enforceUnique: true,
|
|
2336
|
+
message: event.message,
|
|
2337
|
+
reaction,
|
|
2338
|
+
});
|
|
2339
|
+
this.pinnedMessagesPaginator.reflectReaction({
|
|
2340
|
+
enforceUnique: true,
|
|
2341
|
+
message: event.message,
|
|
2342
|
+
reaction,
|
|
2343
|
+
});
|
|
2344
|
+
}
|
|
2297
2345
|
}
|
|
2298
2346
|
break;
|
|
2299
|
-
case 'channel.hidden':
|
|
2347
|
+
case 'channel.hidden': {
|
|
2348
|
+
const previousChannelData = channel.data;
|
|
2300
2349
|
channel.data = {
|
|
2301
|
-
|
|
2302
|
-
|
|
2350
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
2351
|
+
...channel.data!,
|
|
2352
|
+
blocked: event.channel?.blocked ?? false,
|
|
2303
2353
|
hidden: true,
|
|
2304
2354
|
};
|
|
2355
|
+
channel._syncStateFromChannelData(channel.data, previousChannelData);
|
|
2305
2356
|
if (event.clear_history) {
|
|
2306
|
-
|
|
2357
|
+
this.messagePaginator.clearStateAndCache();
|
|
2358
|
+
this.pinnedMessagesPaginator.clearStateAndCache();
|
|
2307
2359
|
}
|
|
2308
2360
|
break;
|
|
2309
|
-
|
|
2361
|
+
}
|
|
2362
|
+
case 'channel.visible': {
|
|
2363
|
+
const previousChannelData = channel.data;
|
|
2310
2364
|
channel.data = {
|
|
2311
|
-
|
|
2312
|
-
|
|
2365
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
2366
|
+
...channel.data!,
|
|
2367
|
+
blocked: event.channel?.blocked ?? false,
|
|
2313
2368
|
hidden: false,
|
|
2314
2369
|
};
|
|
2370
|
+
channel._syncStateFromChannelData(channel.data, previousChannelData);
|
|
2315
2371
|
this.getClient().offlineDb?.handleChannelVisibilityEvent({ event });
|
|
2316
2372
|
break;
|
|
2373
|
+
}
|
|
2317
2374
|
case 'user.banned':
|
|
2318
2375
|
if (!event.user?.id) break;
|
|
2319
2376
|
channelState.members[event.user.id] = {
|
|
@@ -2335,36 +2392,26 @@ export class Channel {
|
|
|
2335
2392
|
default:
|
|
2336
2393
|
}
|
|
2337
2394
|
|
|
2395
|
+
const typedEvent = event as Extract<WSEvent, { watcher_count?: any }>;
|
|
2338
2396
|
// any event can send over the online count
|
|
2339
|
-
if (
|
|
2340
|
-
channel.state.watcher_count =
|
|
2397
|
+
if (typeof typedEvent.watcher_count !== 'undefined') {
|
|
2398
|
+
channel.state.watcher_count = typedEvent.watcher_count;
|
|
2341
2399
|
}
|
|
2342
2400
|
}
|
|
2343
2401
|
|
|
2344
|
-
_callChannelListeners = (event:
|
|
2345
|
-
|
|
2346
|
-
const
|
|
2347
|
-
// gather and call the listeners
|
|
2348
|
-
const listeners = [];
|
|
2349
|
-
if (channel.listeners.all) {
|
|
2350
|
-
listeners.push(...channel.listeners.all);
|
|
2351
|
-
}
|
|
2352
|
-
if (channel.listeners[event.type]) {
|
|
2353
|
-
listeners.push(...channel.listeners[event.type]);
|
|
2354
|
-
}
|
|
2402
|
+
_callChannelListeners = (event: WSEvent) => {
|
|
2403
|
+
const allSet = this.listeners.get('all');
|
|
2404
|
+
const targetSet = this.listeners.get(event.type);
|
|
2355
2405
|
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
listener(event);
|
|
2360
|
-
}
|
|
2361
|
-
}
|
|
2406
|
+
[allSet, targetSet].forEach((set) =>
|
|
2407
|
+
set?.forEach((handleEvent) => handleEvent(event)),
|
|
2408
|
+
);
|
|
2362
2409
|
};
|
|
2363
2410
|
|
|
2364
2411
|
/**
|
|
2365
|
-
*
|
|
2412
|
+
* Returns the channel url.
|
|
2366
2413
|
*
|
|
2367
|
-
* @
|
|
2414
|
+
* @returns The channel url.
|
|
2368
2415
|
*/
|
|
2369
2416
|
_channelURL = () => {
|
|
2370
2417
|
if (!this.id) {
|
|
@@ -2376,21 +2423,22 @@ export class Channel {
|
|
|
2376
2423
|
};
|
|
2377
2424
|
|
|
2378
2425
|
_checkInitialized() {
|
|
2379
|
-
if (
|
|
2380
|
-
!this.initialized &&
|
|
2381
|
-
!this.offlineMode &&
|
|
2382
|
-
!this.getClient()._isUsingServerAuth()
|
|
2383
|
-
) {
|
|
2426
|
+
if (!this.initialized && !this.offlineMode) {
|
|
2384
2427
|
throw Error(
|
|
2385
2428
|
`Channel ${this.cid} hasn't been initialized yet. Make sure to call .watch() and wait for it to resolve`,
|
|
2386
2429
|
);
|
|
2387
2430
|
}
|
|
2388
2431
|
}
|
|
2389
2432
|
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2433
|
+
_syncStateFromChannelData(
|
|
2434
|
+
data: Channel['data'],
|
|
2435
|
+
fallbackData: Channel['data'] = this.data,
|
|
2393
2436
|
) {
|
|
2437
|
+
this.state.syncOwnCapabilitiesFromChannelData(data, fallbackData);
|
|
2438
|
+
this.state.syncMemberCountFromChannelData(data, fallbackData);
|
|
2439
|
+
}
|
|
2440
|
+
|
|
2441
|
+
_initializeState(state: ChannelStateResponseFields) {
|
|
2394
2442
|
const { state: clientState, user, userID } = this.getClient();
|
|
2395
2443
|
|
|
2396
2444
|
// add the members and users
|
|
@@ -2404,24 +2452,22 @@ export class Channel {
|
|
|
2404
2452
|
}
|
|
2405
2453
|
}
|
|
2406
2454
|
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
const messages = state.messages || [];
|
|
2410
|
-
if (!this.state.messages) {
|
|
2411
|
-
this.state.initMessages();
|
|
2455
|
+
if (state.membership) {
|
|
2456
|
+
this.state.membership = state.membership;
|
|
2412
2457
|
}
|
|
2413
|
-
const { messageSet, filteredMessageIds } = this.state.addMessagesSorted(
|
|
2414
|
-
messages,
|
|
2415
|
-
false,
|
|
2416
|
-
true,
|
|
2417
|
-
true,
|
|
2418
|
-
messageSetToAddToIfDoesNotExist,
|
|
2419
|
-
);
|
|
2420
2458
|
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2459
|
+
// Seed the message paginator's `lastMessageAt` aggregate from the server's authoritative
|
|
2460
|
+
// `last_message_at`. The first-page seed (Channel.query / client.hydrateActiveChannels) also
|
|
2461
|
+
// advances it from ingested messages; both feed the same monotonic max, so this additionally
|
|
2462
|
+
// covers the path where the paginator seed is skipped (an already-loaded channel the viewer has
|
|
2463
|
+
// jumped away from, where re-seeding would clobber their window).
|
|
2464
|
+
this.messagePaginator.seedLastMessageAt(state.channel?.last_message_at);
|
|
2465
|
+
|
|
2466
|
+
// Seed the pinned-messages paginator from the same response.
|
|
2467
|
+
this.pinnedMessagesPaginator.seedFirstPageSync(
|
|
2468
|
+
(state.pinned_messages || []).map(formatMessage),
|
|
2469
|
+
this.pinnedMessagesPaginator.pageSize,
|
|
2470
|
+
);
|
|
2425
2471
|
if (state.pending_messages) {
|
|
2426
2472
|
this.state.pending_messages = state.pending_messages;
|
|
2427
2473
|
}
|
|
@@ -2441,11 +2487,12 @@ export class Channel {
|
|
|
2441
2487
|
// initialize read state to last message or current time if the channel is empty
|
|
2442
2488
|
// if the user is a member, this value will be overwritten later on otherwise this ensures
|
|
2443
2489
|
// that everything up to this point is not marked as unread
|
|
2490
|
+
const readUpdates: ChannelState['read'] = {};
|
|
2444
2491
|
if (userID != null) {
|
|
2445
|
-
const last_read = this.
|
|
2492
|
+
const last_read = this.messagePaginator.lastMessageAt || new Date();
|
|
2446
2493
|
if (user) {
|
|
2447
|
-
|
|
2448
|
-
user,
|
|
2494
|
+
readUpdates[user.id] = {
|
|
2495
|
+
user: user as UserResponse,
|
|
2449
2496
|
last_read,
|
|
2450
2497
|
unread_messages: 0,
|
|
2451
2498
|
};
|
|
@@ -2455,7 +2502,7 @@ export class Channel {
|
|
|
2455
2502
|
// apply read state if part of the state
|
|
2456
2503
|
if (state.read) {
|
|
2457
2504
|
for (const read of state.read) {
|
|
2458
|
-
|
|
2505
|
+
readUpdates[read.user.id] = {
|
|
2459
2506
|
last_delivered_at: read.last_delivered_at
|
|
2460
2507
|
? new Date(read.last_delivered_at)
|
|
2461
2508
|
: undefined,
|
|
@@ -2467,24 +2514,41 @@ export class Channel {
|
|
|
2467
2514
|
};
|
|
2468
2515
|
|
|
2469
2516
|
if (read.user.id === user?.id) {
|
|
2470
|
-
this.state.unreadCount =
|
|
2517
|
+
this.state.unreadCount = readUpdates[read.user.id].unread_messages;
|
|
2471
2518
|
}
|
|
2472
2519
|
}
|
|
2473
|
-
|
|
2474
|
-
this.messageReceiptsTracker.ingestInitial(state.read);
|
|
2475
2520
|
}
|
|
2476
2521
|
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
|
|
2480
|
-
|
|
2522
|
+
const entries = Object.entries(readUpdates);
|
|
2523
|
+
if (entries.length) {
|
|
2524
|
+
this._patchReadState(
|
|
2525
|
+
(currentReadState) => {
|
|
2526
|
+
let hasChanges = false;
|
|
2527
|
+
const nextReadState = { ...currentReadState };
|
|
2528
|
+
|
|
2529
|
+
for (const [userId, readState] of entries) {
|
|
2530
|
+
if (nextReadState[userId] === readState) continue;
|
|
2531
|
+
nextReadState[userId] = readState;
|
|
2532
|
+
hasChanges = true;
|
|
2533
|
+
}
|
|
2534
|
+
|
|
2535
|
+
return hasChanges ? nextReadState : currentReadState;
|
|
2536
|
+
},
|
|
2537
|
+
{ changedUserIds: entries.map(([userId]) => userId) },
|
|
2538
|
+
);
|
|
2539
|
+
}
|
|
2481
2540
|
}
|
|
2482
2541
|
|
|
2483
|
-
_extendEventWithOwnReactions(
|
|
2542
|
+
_extendEventWithOwnReactions(
|
|
2543
|
+
event: EventPayload<'message.undeleted' | 'message.updated' | 'message.deleted'>,
|
|
2544
|
+
) {
|
|
2484
2545
|
if (!event.message) {
|
|
2485
2546
|
return;
|
|
2486
2547
|
}
|
|
2487
|
-
|
|
2548
|
+
// The channel message list is owned by the paginator; enrich from it. Thread-only replies are
|
|
2549
|
+
// not in the paginator (getItem returns undefined) — the Thread object preserves their
|
|
2550
|
+
// own_reactions on its own reply store.
|
|
2551
|
+
const message = this.messagePaginator.getItem(event.message.id);
|
|
2488
2552
|
if (message) {
|
|
2489
2553
|
event.message.own_reactions = message.own_reactions;
|
|
2490
2554
|
}
|
|
@@ -2524,17 +2588,15 @@ export class Channel {
|
|
|
2524
2588
|
}
|
|
2525
2589
|
|
|
2526
2590
|
_disconnect() {
|
|
2527
|
-
this.
|
|
2528
|
-
'info',
|
|
2529
|
-
`channel:disconnect() - Disconnecting the channel ${this.cid}`,
|
|
2530
|
-
{
|
|
2531
|
-
tags: ['connection', 'channel'],
|
|
2532
|
-
channel: this,
|
|
2533
|
-
},
|
|
2534
|
-
);
|
|
2591
|
+
logger.withExtraTags('_disconnect', this.cid).info('Disconnecting the channel.');
|
|
2535
2592
|
|
|
2536
2593
|
this.disconnected = true;
|
|
2594
|
+
this.messageReceiptsTracker.unregisterSubscriptions();
|
|
2537
2595
|
this.cooldownTimer.clearTimeout();
|
|
2538
|
-
this
|
|
2596
|
+
// Release the store-backed paginators so the message store no longer pins this removed channel
|
|
2597
|
+
// (and its whole message graph) through its subscriber registry. The channel is being discarded
|
|
2598
|
+
// here (disconnected + deleted from activeChannels, never reused), mirroring Thread teardown.
|
|
2599
|
+
this.messagePaginator.dispose();
|
|
2600
|
+
this.pinnedMessagesPaginator.dispose();
|
|
2539
2601
|
}
|
|
2540
2602
|
}
|