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/utils.ts
CHANGED
|
@@ -1,23 +1,12 @@
|
|
|
1
1
|
import FormData from 'form-data';
|
|
2
2
|
import type {
|
|
3
|
-
AscDesc,
|
|
4
|
-
ChannelFilters,
|
|
5
|
-
ChannelQueryOptions,
|
|
6
|
-
ChannelSort,
|
|
7
|
-
ChannelSortBase,
|
|
8
3
|
LocalMessage,
|
|
9
|
-
|
|
10
|
-
Logger,
|
|
11
|
-
Message,
|
|
12
|
-
MessagePaginationOptions,
|
|
4
|
+
MessageRequest,
|
|
13
5
|
MessageResponse,
|
|
14
|
-
MessageResponseBase,
|
|
15
|
-
MessageSet,
|
|
16
6
|
OwnUserBase,
|
|
17
7
|
OwnUserResponse,
|
|
18
|
-
PromoteChannelParams,
|
|
19
|
-
QueryChannelAPIResponse,
|
|
20
8
|
ReactionGroupResponse,
|
|
9
|
+
ReactionResponse,
|
|
21
10
|
UpdatedMessage,
|
|
22
11
|
UserResponse,
|
|
23
12
|
} from './types';
|
|
@@ -25,18 +14,22 @@ import type { StreamChat } from './client';
|
|
|
25
14
|
import type { Channel } from './channel';
|
|
26
15
|
import type { AxiosRequestConfig } from 'axios';
|
|
27
16
|
import { LOCAL_MESSAGE_FIELDS, RESERVED_UPDATED_MESSAGE_FIELDS } from './constants';
|
|
17
|
+
import { chatLoggerSystem } from './logger';
|
|
18
|
+
|
|
19
|
+
const logger = chatLoggerSystem.getLogger('utils');
|
|
28
20
|
|
|
29
21
|
/**
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
* @param {Promise<T>} promise The promise you want to run and log
|
|
34
|
-
* @param {string} name A descriptive name of what the promise does for log output
|
|
22
|
+
* Logs the execution of a promise. Use this when you want to run the promise and handle errors by
|
|
23
|
+
* logging a warning.
|
|
35
24
|
*
|
|
25
|
+
* @param promise - The promise you want to run and log.
|
|
26
|
+
* @param name - A descriptive name of what the promise does for log output.
|
|
36
27
|
*/
|
|
37
28
|
export function logChatPromiseExecution<T>(promise: Promise<T>, name: string) {
|
|
38
29
|
promise.then().catch((error) => {
|
|
39
|
-
|
|
30
|
+
logger
|
|
31
|
+
.withExtraTags('logChatPromiseExecution')
|
|
32
|
+
.error(`Failed to execute "${name}".`, { error });
|
|
40
33
|
});
|
|
41
34
|
}
|
|
42
35
|
|
|
@@ -153,29 +146,11 @@ export function addFileToFormData(
|
|
|
153
146
|
|
|
154
147
|
return data;
|
|
155
148
|
}
|
|
156
|
-
export function normalizeQuerySort<T extends Record<string, AscDesc | undefined>>(
|
|
157
|
-
sort: T | T[],
|
|
158
|
-
) {
|
|
159
|
-
const sortFields: Array<{ direction: AscDesc; field: keyof T }> = [];
|
|
160
|
-
const sortArr = Array.isArray(sort) ? sort : [sort];
|
|
161
|
-
for (const item of sortArr) {
|
|
162
|
-
const entries = Object.entries(item) as [keyof T, AscDesc][];
|
|
163
|
-
if (entries.length > 1) {
|
|
164
|
-
console.warn(
|
|
165
|
-
"client._buildSort() - multiple fields in a single sort object detected. Object's field order is not guaranteed",
|
|
166
|
-
);
|
|
167
|
-
}
|
|
168
|
-
for (const [field, direction] of entries) {
|
|
169
|
-
sortFields.push({ field, direction });
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
return sortFields;
|
|
173
|
-
}
|
|
174
149
|
|
|
175
150
|
/**
|
|
176
151
|
* retryInterval - A retry interval which increases acc to number of failures
|
|
177
152
|
*
|
|
178
|
-
* @
|
|
153
|
+
* @returns Duration to wait in milliseconds
|
|
179
154
|
*/
|
|
180
155
|
export function retryInterval(numberOfFailures: number) {
|
|
181
156
|
// try to reconnect in 0.25-25 seconds (random to spread out the load from failures)
|
|
@@ -270,9 +245,9 @@ export function isOnline() {
|
|
|
270
245
|
: undefined;
|
|
271
246
|
|
|
272
247
|
if (!nav) {
|
|
273
|
-
|
|
274
|
-
'isOnline
|
|
275
|
-
|
|
248
|
+
logger
|
|
249
|
+
.withExtraTags('isOnline')
|
|
250
|
+
.warn('Could not access window.navigator; assuming the browser is online.');
|
|
276
251
|
return true;
|
|
277
252
|
}
|
|
278
253
|
|
|
@@ -319,102 +294,204 @@ export const axiosParamsSerializer: AxiosRequestConfig['paramsSerializer'] = (pa
|
|
|
319
294
|
};
|
|
320
295
|
|
|
321
296
|
/**
|
|
322
|
-
* Takes the message object,
|
|
323
|
-
* and
|
|
297
|
+
* Takes the message object, adds SDK-specific fields (status, error),
|
|
298
|
+
* and returns a new LocalMessage object.
|
|
324
299
|
*
|
|
325
|
-
* @param
|
|
300
|
+
* @param message - message object
|
|
326
301
|
*/
|
|
327
|
-
export function formatMessage(
|
|
328
|
-
message: MessageResponse | MessageResponseBase | LocalMessage,
|
|
329
|
-
): LocalMessage {
|
|
302
|
+
export function formatMessage(message: MessageResponse | LocalMessage): LocalMessage {
|
|
330
303
|
const toLocalMessageBase = (
|
|
331
|
-
msg: MessageResponse |
|
|
332
|
-
):
|
|
304
|
+
msg: MessageResponse | LocalMessage | null | undefined,
|
|
305
|
+
): LocalMessage | null => {
|
|
333
306
|
if (!msg) return null;
|
|
334
307
|
return {
|
|
335
308
|
...msg,
|
|
336
309
|
created_at: msg.created_at ? new Date(msg.created_at) : new Date(),
|
|
337
|
-
deleted_at: msg.deleted_at ? new Date(msg.deleted_at) :
|
|
338
|
-
pinned_at: msg.pinned_at ? new Date(msg.pinned_at) :
|
|
310
|
+
deleted_at: msg.deleted_at ? new Date(msg.deleted_at) : undefined,
|
|
311
|
+
pinned_at: msg.pinned_at ? new Date(msg.pinned_at) : undefined,
|
|
339
312
|
reaction_groups: maybeGetReactionGroupsFallback(
|
|
340
313
|
msg.reaction_groups,
|
|
341
314
|
msg.reaction_counts,
|
|
342
315
|
msg.reaction_scores,
|
|
343
316
|
),
|
|
344
|
-
status: msg.status || 'received',
|
|
317
|
+
status: (msg as LocalMessage).status || 'received',
|
|
345
318
|
updated_at: msg.updated_at ? new Date(msg.updated_at) : new Date(),
|
|
346
319
|
};
|
|
347
320
|
};
|
|
348
321
|
|
|
349
322
|
return {
|
|
350
323
|
...toLocalMessageBase(message),
|
|
351
|
-
error: (message as LocalMessage).error ??
|
|
352
|
-
quoted_message:
|
|
324
|
+
error: (message as LocalMessage).error ?? undefined,
|
|
325
|
+
quoted_message:
|
|
326
|
+
toLocalMessageBase((message as MessageResponse).quoted_message) ?? undefined,
|
|
353
327
|
} as LocalMessage;
|
|
354
328
|
}
|
|
355
329
|
|
|
356
330
|
/**
|
|
357
|
-
*
|
|
331
|
+
* Computes the current user's `own_reactions` after applying a single reaction change, off a
|
|
332
|
+
* `current` base. A WS reaction/edit event carries `own_reactions: []` (or stale), so consumers
|
|
333
|
+
* must recompute from the copy they already hold rather than trusting the event — this is the
|
|
334
|
+
* shared core used by both the message paginator (`reflectReaction`) and the thread's parent
|
|
335
|
+
* message (which is not held in any paginator, so it needs the same logic directly).
|
|
358
336
|
*
|
|
359
|
-
*
|
|
360
|
-
*
|
|
337
|
+
* - `removed`: drop the current user's reaction of this type.
|
|
338
|
+
* - `enforceUnique`: replace any existing own reaction with the incoming one (used by
|
|
339
|
+
* `reaction.updated`, where a user's reaction supersedes their previous one).
|
|
340
|
+
* - otherwise: add the reaction, de-duped by type.
|
|
361
341
|
*
|
|
362
|
-
*
|
|
342
|
+
* A reaction by another user never changes the current user's `own_reactions`.
|
|
363
343
|
*/
|
|
364
|
-
export function
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
344
|
+
export function computeOwnReactions({
|
|
345
|
+
current,
|
|
346
|
+
enforceUnique = false,
|
|
347
|
+
reaction,
|
|
348
|
+
removed = false,
|
|
349
|
+
userId,
|
|
350
|
+
}: {
|
|
351
|
+
current: ReactionResponse[];
|
|
352
|
+
reaction: ReactionResponse;
|
|
353
|
+
enforceUnique?: boolean;
|
|
354
|
+
removed?: boolean;
|
|
355
|
+
userId?: string;
|
|
356
|
+
}): ReactionResponse[] {
|
|
357
|
+
const withoutType = current.filter(
|
|
358
|
+
(r) => r.user_id !== reaction.user_id || r.type !== reaction.type,
|
|
359
|
+
);
|
|
360
|
+
if (removed) return withoutType;
|
|
361
|
+
if (userId !== reaction.user_id) return withoutType;
|
|
362
|
+
return enforceUnique ? [reaction] : [...withoutType, reaction];
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* Returns a copy of `message` with `reaction` folded into its `reaction_groups` /
|
|
367
|
+
* `latest_reactions`. Does not touch `own_reactions` — the reaction entry points
|
|
368
|
+
* ({@link MessageIntervalPaginator.reflectReaction} / `Thread.applyParentReactionLocally`) own that
|
|
369
|
+
* via {@link computeOwnReactions}. Shared so both the paginator and the (paginator-less) thread
|
|
370
|
+
* parent compute counts identically.
|
|
371
|
+
*/
|
|
372
|
+
export function messageWithReactionAdded(
|
|
373
|
+
message: LocalMessage,
|
|
374
|
+
reaction: ReactionResponse,
|
|
375
|
+
enforceUnique: boolean,
|
|
376
|
+
): LocalMessage {
|
|
377
|
+
const score = reaction.score ?? 1;
|
|
378
|
+
const reactionGroups: Record<string, ReactionGroupResponse> = {
|
|
379
|
+
...(message.reaction_groups ?? {}),
|
|
377
380
|
};
|
|
378
381
|
|
|
382
|
+
// When enforcing uniqueness, first back the current user's existing reactions out of the groups
|
|
383
|
+
if (enforceUnique) {
|
|
384
|
+
for (const ownReaction of message.own_reactions ?? []) {
|
|
385
|
+
const group = reactionGroups[ownReaction.type];
|
|
386
|
+
if (!group) continue;
|
|
387
|
+
const next = {
|
|
388
|
+
...group,
|
|
389
|
+
count: group.count - 1,
|
|
390
|
+
sum_scores: group.sum_scores - (ownReaction.score ?? 1),
|
|
391
|
+
};
|
|
392
|
+
if (next.count < 1) delete reactionGroups[ownReaction.type];
|
|
393
|
+
else reactionGroups[ownReaction.type] = next;
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
const existingGroup = reactionGroups[reaction.type];
|
|
398
|
+
reactionGroups[reaction.type] = existingGroup
|
|
399
|
+
? {
|
|
400
|
+
...existingGroup,
|
|
401
|
+
count: existingGroup.count + 1,
|
|
402
|
+
last_reaction_at: reaction.created_at,
|
|
403
|
+
sum_scores: existingGroup.sum_scores + score,
|
|
404
|
+
}
|
|
405
|
+
: {
|
|
406
|
+
count: 1,
|
|
407
|
+
first_reaction_at: reaction.created_at,
|
|
408
|
+
last_reaction_at: reaction.created_at,
|
|
409
|
+
latest_reactions_by: [],
|
|
410
|
+
sum_scores: score,
|
|
411
|
+
};
|
|
412
|
+
|
|
413
|
+
const latestReactions = enforceUnique
|
|
414
|
+
? [
|
|
415
|
+
...(message.latest_reactions ?? []).filter((r) => r.user_id !== reaction.user_id),
|
|
416
|
+
reaction,
|
|
417
|
+
]
|
|
418
|
+
: [...(message.latest_reactions ?? []), reaction];
|
|
419
|
+
|
|
379
420
|
return {
|
|
380
|
-
...
|
|
381
|
-
|
|
382
|
-
|
|
421
|
+
...message,
|
|
422
|
+
latest_reactions: latestReactions,
|
|
423
|
+
reaction_groups: reactionGroups,
|
|
424
|
+
};
|
|
383
425
|
}
|
|
384
426
|
|
|
385
|
-
|
|
386
|
-
|
|
427
|
+
/**
|
|
428
|
+
* Returns a copy of `message` with the current user's reaction of `reaction.type` backed out of its
|
|
429
|
+
* `reaction_groups` / `latest_reactions`. Does not touch `own_reactions`.
|
|
430
|
+
*/
|
|
431
|
+
export function messageWithReactionRemoved(
|
|
432
|
+
message: LocalMessage,
|
|
433
|
+
reaction: ReactionResponse,
|
|
434
|
+
): LocalMessage {
|
|
435
|
+
const reactionGroups: Record<string, ReactionGroupResponse> = {
|
|
436
|
+
...(message.reaction_groups ?? {}),
|
|
437
|
+
};
|
|
438
|
+
const reactionToRemove = message.own_reactions?.find((r) => r.type === reaction.type);
|
|
439
|
+
|
|
440
|
+
if (reactionToRemove && reactionGroups[reactionToRemove.type]) {
|
|
441
|
+
const group = reactionGroups[reactionToRemove.type];
|
|
442
|
+
const next = {
|
|
443
|
+
...group,
|
|
444
|
+
count: group.count - 1,
|
|
445
|
+
sum_scores: group.sum_scores - (reactionToRemove.score ?? 1),
|
|
446
|
+
};
|
|
447
|
+
if (next.count < 1) delete reactionGroups[reactionToRemove.type];
|
|
448
|
+
else reactionGroups[reactionToRemove.type] = next;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
return {
|
|
452
|
+
...message,
|
|
453
|
+
latest_reactions: message.latest_reactions?.filter(
|
|
454
|
+
(r) => !(r.user_id === reaction.user_id && r.type === reaction.type),
|
|
455
|
+
),
|
|
456
|
+
reaction_groups: reactionGroups,
|
|
457
|
+
};
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
export const localMessageToNewMessagePayload = (
|
|
461
|
+
localMessage: LocalMessage,
|
|
462
|
+
): MessageRequest => {
|
|
387
463
|
const {
|
|
388
464
|
// Remove all timestamp fields and client-specific fields.
|
|
389
465
|
// Field pinned_at can therefore be earlier than created_at as new message payload can hold it.
|
|
390
|
-
created_at,
|
|
391
|
-
updated_at,
|
|
392
|
-
deleted_at,
|
|
466
|
+
created_at: _created_at,
|
|
467
|
+
updated_at: _updated_at,
|
|
468
|
+
deleted_at: _deleted_at,
|
|
393
469
|
// Client-specific fields
|
|
394
|
-
error,
|
|
395
|
-
status,
|
|
470
|
+
error: _error,
|
|
471
|
+
status: _status,
|
|
396
472
|
// Reaction related fields
|
|
397
|
-
latest_reactions,
|
|
398
|
-
own_reactions,
|
|
399
|
-
reaction_counts,
|
|
400
|
-
reaction_scores,
|
|
401
|
-
reply_count,
|
|
402
|
-
//
|
|
403
|
-
command,
|
|
404
|
-
html,
|
|
405
|
-
i18n,
|
|
406
|
-
mentioned_groups,
|
|
407
|
-
quoted_message,
|
|
473
|
+
latest_reactions: _latest_reactions,
|
|
474
|
+
own_reactions: _own_reactions,
|
|
475
|
+
reaction_counts: _reaction_counts,
|
|
476
|
+
reaction_scores: _reaction_scores,
|
|
477
|
+
reply_count: _reply_count,
|
|
478
|
+
// MessageRequest text related fields that shouldn't be in update
|
|
479
|
+
command: _command,
|
|
480
|
+
html: _html,
|
|
481
|
+
i18n: _i18n,
|
|
482
|
+
mentioned_groups: _mentioned_groups,
|
|
483
|
+
quoted_message: _quoted_message,
|
|
408
484
|
mentioned_users,
|
|
409
|
-
//
|
|
485
|
+
// MessageRequest content related fields
|
|
410
486
|
...messageFields
|
|
411
487
|
} = localMessage;
|
|
412
488
|
|
|
489
|
+
// `messageFields` still carries LocalMessage-only fields (cid, deleted_reply_count, mentioned_*,
|
|
490
|
+
// pinned, shadowed, …) that the stricter OpenAPI `MessageRequest` omits; the server ignores them.
|
|
413
491
|
return {
|
|
414
492
|
...messageFields,
|
|
415
|
-
pinned_at: messageFields.pinned_at?.toISOString(),
|
|
416
493
|
mentioned_users: mentioned_users?.map((user) => user.id),
|
|
417
|
-
};
|
|
494
|
+
} as MessageRequest;
|
|
418
495
|
};
|
|
419
496
|
|
|
420
497
|
export const toUpdatedMessagePayload = (
|
|
@@ -445,7 +522,7 @@ export const toDeletedMessage = ({
|
|
|
445
522
|
deletedAt,
|
|
446
523
|
hardDelete = false,
|
|
447
524
|
}: {
|
|
448
|
-
message: LocalMessage
|
|
525
|
+
message: LocalMessage;
|
|
449
526
|
deletedAt: LocalMessage['deleted_at'];
|
|
450
527
|
hardDelete: boolean;
|
|
451
528
|
}) => {
|
|
@@ -453,7 +530,7 @@ export const toDeletedMessage = ({
|
|
|
453
530
|
/**
|
|
454
531
|
* In case of hard delete, we need to strip down all text, html, attachments and all the custom properties on message
|
|
455
532
|
* The hard-deleted message is kept in the UI until the messages are re-queried
|
|
456
|
-
* FIXME: we are returning an object that does not match LocalMessage
|
|
533
|
+
* FIXME: we are returning an object that does not match LocalMessage
|
|
457
534
|
*/
|
|
458
535
|
return {
|
|
459
536
|
attachments: [],
|
|
@@ -482,39 +559,6 @@ export const toDeletedMessage = ({
|
|
|
482
559
|
}
|
|
483
560
|
};
|
|
484
561
|
|
|
485
|
-
export const deleteUserMessages = ({
|
|
486
|
-
messages,
|
|
487
|
-
user,
|
|
488
|
-
hardDelete = false,
|
|
489
|
-
deletedAt,
|
|
490
|
-
}: {
|
|
491
|
-
messages: Array<LocalMessage>;
|
|
492
|
-
user: UserResponse;
|
|
493
|
-
hardDelete: boolean;
|
|
494
|
-
deletedAt: LocalMessage['deleted_at'];
|
|
495
|
-
}) => {
|
|
496
|
-
for (let i = 0; i < messages.length; i++) {
|
|
497
|
-
const message = messages[i];
|
|
498
|
-
if (message.user?.id === user.id) {
|
|
499
|
-
messages[i] =
|
|
500
|
-
message.type === 'deleted'
|
|
501
|
-
? message
|
|
502
|
-
: (toDeletedMessage({ message, hardDelete, deletedAt }) as LocalMessage);
|
|
503
|
-
}
|
|
504
|
-
|
|
505
|
-
if (messages[i].quoted_message && message.quoted_message?.user?.id === user.id) {
|
|
506
|
-
messages[i].quoted_message =
|
|
507
|
-
message.quoted_message.type === 'deleted'
|
|
508
|
-
? message.quoted_message
|
|
509
|
-
: (toDeletedMessage({
|
|
510
|
-
message: messages[i].quoted_message as LocalMessageBase,
|
|
511
|
-
hardDelete,
|
|
512
|
-
deletedAt,
|
|
513
|
-
}) as LocalMessage);
|
|
514
|
-
}
|
|
515
|
-
}
|
|
516
|
-
};
|
|
517
|
-
|
|
518
562
|
export const findIndexInSortedArray = <T, L>({
|
|
519
563
|
needle,
|
|
520
564
|
sortedArray,
|
|
@@ -604,77 +648,11 @@ export const findIndexInSortedArray = <T, L>({
|
|
|
604
648
|
return left;
|
|
605
649
|
};
|
|
606
650
|
|
|
607
|
-
export function addToMessageList<T extends LocalMessage>(
|
|
608
|
-
messages: readonly T[],
|
|
609
|
-
newMessage: T,
|
|
610
|
-
timestampChanged = false,
|
|
611
|
-
sortBy: 'pinned_at' | 'created_at' = 'created_at',
|
|
612
|
-
addIfDoesNotExist = true,
|
|
613
|
-
) {
|
|
614
|
-
const addMessageToList = addIfDoesNotExist || timestampChanged;
|
|
615
|
-
let newMessages = [...messages];
|
|
616
|
-
|
|
617
|
-
// if created_at has changed, message should be filtered and re-inserted in correct order
|
|
618
|
-
// slow op but usually this only happens for a message inserted to state before actual response with correct timestamp
|
|
619
|
-
if (timestampChanged) {
|
|
620
|
-
newMessages = newMessages.filter(
|
|
621
|
-
(message) => !(message.id && newMessage.id === message.id),
|
|
622
|
-
);
|
|
623
|
-
}
|
|
624
|
-
|
|
625
|
-
// for empty list just concat and return unless it's an update or deletion
|
|
626
|
-
if (newMessages.length === 0 && addMessageToList) {
|
|
627
|
-
return newMessages.concat(newMessage);
|
|
628
|
-
} else if (newMessages.length === 0) {
|
|
629
|
-
return newMessages;
|
|
630
|
-
}
|
|
631
|
-
|
|
632
|
-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
633
|
-
const messageTime = newMessage[sortBy]!.getTime();
|
|
634
|
-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
635
|
-
const messageIsNewest = newMessages.at(-1)![sortBy]!.getTime() < messageTime;
|
|
636
|
-
|
|
637
|
-
// if message is newer than last item in the list concat and return unless it's an update or deletion
|
|
638
|
-
if (messageIsNewest && addMessageToList) {
|
|
639
|
-
return newMessages.concat(newMessage);
|
|
640
|
-
} else if (messageIsNewest) {
|
|
641
|
-
return newMessages;
|
|
642
|
-
}
|
|
643
|
-
|
|
644
|
-
// find the closest index to push the new message
|
|
645
|
-
const insertionIndex = findIndexInSortedArray({
|
|
646
|
-
needle: newMessage,
|
|
647
|
-
sortedArray: newMessages,
|
|
648
|
-
sortDirection: 'ascending',
|
|
649
|
-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
650
|
-
selectValueToCompare: (m) => m[sortBy]!.getTime(),
|
|
651
|
-
selectKey: (m) => m.id,
|
|
652
|
-
});
|
|
653
|
-
|
|
654
|
-
// message already exists and not filtered with timestampChanged, update and return
|
|
655
|
-
if (
|
|
656
|
-
!timestampChanged &&
|
|
657
|
-
newMessage.id &&
|
|
658
|
-
newMessages[insertionIndex] &&
|
|
659
|
-
newMessage.id === newMessages[insertionIndex].id
|
|
660
|
-
) {
|
|
661
|
-
newMessages[insertionIndex] = newMessage;
|
|
662
|
-
return newMessages;
|
|
663
|
-
}
|
|
664
|
-
|
|
665
|
-
// do not add updated or deleted messages to the list if they already exist or come with a timestamp change
|
|
666
|
-
if (addMessageToList) {
|
|
667
|
-
newMessages.splice(insertionIndex, 0, newMessage);
|
|
668
|
-
}
|
|
669
|
-
|
|
670
|
-
return newMessages;
|
|
671
|
-
}
|
|
672
|
-
|
|
673
651
|
function maybeGetReactionGroupsFallback(
|
|
674
652
|
groups: { [key: string]: ReactionGroupResponse } | null | undefined,
|
|
675
653
|
counts: { [key: string]: number } | null | undefined,
|
|
676
654
|
scores: { [key: string]: number } | null | undefined,
|
|
677
|
-
): { [key: string]: ReactionGroupResponse } |
|
|
655
|
+
): { [key: string]: ReactionGroupResponse } | undefined {
|
|
678
656
|
if (groups) {
|
|
679
657
|
return groups;
|
|
680
658
|
}
|
|
@@ -683,19 +661,20 @@ function maybeGetReactionGroupsFallback(
|
|
|
683
661
|
const fallback: { [key: string]: ReactionGroupResponse } = {};
|
|
684
662
|
|
|
685
663
|
for (const type of Object.keys(counts)) {
|
|
664
|
+
// Best-effort fallback derived from counts/scores; the richer OpenAPI `ReactionGroupResponse`
|
|
665
|
+
// fields (first/last_reaction_at, latest_reactions_by) are not available here.
|
|
686
666
|
fallback[type] = {
|
|
687
667
|
count: counts[type],
|
|
688
668
|
sum_scores: scores[type],
|
|
689
|
-
};
|
|
669
|
+
} as ReactionGroupResponse;
|
|
690
670
|
}
|
|
691
671
|
|
|
692
672
|
return fallback;
|
|
693
673
|
}
|
|
694
674
|
|
|
695
|
-
return
|
|
675
|
+
return undefined;
|
|
696
676
|
}
|
|
697
677
|
|
|
698
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
699
678
|
export interface DebouncedFunc<T extends (...args: any[]) => any> {
|
|
700
679
|
/**
|
|
701
680
|
* Call the original function, but applying the debounce rules.
|
|
@@ -724,7 +703,7 @@ export interface DebouncedFunc<T extends (...args: any[]) => any> {
|
|
|
724
703
|
}
|
|
725
704
|
|
|
726
705
|
// works exactly the same as lodash.debounce
|
|
727
|
-
|
|
706
|
+
|
|
728
707
|
export const debounce = <T extends (...args: any[]) => any>(
|
|
729
708
|
fn: T,
|
|
730
709
|
timeout = 0,
|
|
@@ -771,39 +750,14 @@ export const debounce = <T extends (...args: any[]) => any>(
|
|
|
771
750
|
return debouncedFn;
|
|
772
751
|
};
|
|
773
752
|
|
|
774
|
-
//
|
|
775
|
-
//
|
|
776
|
-
export
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
let storedArgs: Parameters<T> | null = null;
|
|
783
|
-
|
|
784
|
-
return (...args: Parameters<T>) => {
|
|
785
|
-
if (runningTimeout) {
|
|
786
|
-
if (trailing) storedArgs = args;
|
|
787
|
-
return;
|
|
788
|
-
}
|
|
789
|
-
|
|
790
|
-
if (leading) fn(...args);
|
|
791
|
-
|
|
792
|
-
const timeoutHandler = () => {
|
|
793
|
-
if (storedArgs) {
|
|
794
|
-
fn(...storedArgs);
|
|
795
|
-
storedArgs = null;
|
|
796
|
-
runningTimeout = setTimeout(timeoutHandler, timeout);
|
|
797
|
-
|
|
798
|
-
return;
|
|
799
|
-
}
|
|
800
|
-
|
|
801
|
-
runningTimeout = null;
|
|
802
|
-
};
|
|
803
|
-
|
|
804
|
-
runningTimeout = setTimeout(timeoutHandler, timeout);
|
|
805
|
-
};
|
|
806
|
-
};
|
|
753
|
+
// The single throttle implementation lives in ./utils/throttling/throttle; re-exported here so
|
|
754
|
+
// `import { throttle } from './utils'` keeps working (lodash.throttle-style leading/trailing).
|
|
755
|
+
export { throttle } from './utils/throttling/throttle';
|
|
756
|
+
export type {
|
|
757
|
+
Throttled,
|
|
758
|
+
ThrottleOptions,
|
|
759
|
+
ThrottledCallback,
|
|
760
|
+
} from './utils/throttling/throttle';
|
|
807
761
|
|
|
808
762
|
const get = <T>(obj: T, path: string): unknown =>
|
|
809
763
|
path.split('.').reduce<unknown>((acc, key) => {
|
|
@@ -830,342 +784,13 @@ export const uniqBy = <T>(
|
|
|
830
784
|
});
|
|
831
785
|
};
|
|
832
786
|
|
|
833
|
-
type MessagePaginationUpdatedParams = {
|
|
834
|
-
parentSet: MessageSet;
|
|
835
|
-
requestedPageSize: number;
|
|
836
|
-
returnedPage: MessageResponse[];
|
|
837
|
-
filteredReturnedPage: MessageResponse[];
|
|
838
|
-
logger?: Logger;
|
|
839
|
-
messagePaginationOptions?: MessagePaginationOptions;
|
|
840
|
-
};
|
|
841
|
-
|
|
842
|
-
export function binarySearchByDateEqualOrNearestGreater(
|
|
843
|
-
array: {
|
|
844
|
-
created_at?: string;
|
|
845
|
-
}[],
|
|
846
|
-
targetDate: Date,
|
|
847
|
-
): number {
|
|
848
|
-
let left = 0;
|
|
849
|
-
let right = array.length - 1;
|
|
850
|
-
|
|
851
|
-
while (left <= right) {
|
|
852
|
-
const mid = Math.floor((left + right) / 2);
|
|
853
|
-
const midCreatedAt = array[mid].created_at;
|
|
854
|
-
if (!midCreatedAt) {
|
|
855
|
-
left += 1;
|
|
856
|
-
continue;
|
|
857
|
-
}
|
|
858
|
-
const midDate = new Date(midCreatedAt);
|
|
859
|
-
|
|
860
|
-
if (midDate.getTime() === targetDate.getTime()) {
|
|
861
|
-
return mid;
|
|
862
|
-
} else if (midDate.getTime() < targetDate.getTime()) {
|
|
863
|
-
left = mid + 1;
|
|
864
|
-
} else {
|
|
865
|
-
right = mid - 1;
|
|
866
|
-
}
|
|
867
|
-
}
|
|
868
|
-
|
|
869
|
-
return left;
|
|
870
|
-
}
|
|
871
|
-
|
|
872
|
-
const messagePaginationCreatedAtAround = ({
|
|
873
|
-
parentSet,
|
|
874
|
-
requestedPageSize,
|
|
875
|
-
returnedPage,
|
|
876
|
-
filteredReturnedPage,
|
|
877
|
-
messagePaginationOptions,
|
|
878
|
-
}: MessagePaginationUpdatedParams) => {
|
|
879
|
-
const newPagination = { ...parentSet.pagination };
|
|
880
|
-
if (!messagePaginationOptions?.created_at_around) return newPagination;
|
|
881
|
-
let hasPrev;
|
|
882
|
-
let hasNext;
|
|
883
|
-
let updateHasPrev;
|
|
884
|
-
let updateHasNext;
|
|
885
|
-
const createdAtAroundDate = new Date(messagePaginationOptions.created_at_around);
|
|
886
|
-
const [firstPageMsg, lastPageMsg] = [returnedPage[0], returnedPage.slice(-1)[0]];
|
|
887
|
-
|
|
888
|
-
// expect ASC order (from oldest to newest)
|
|
889
|
-
const wholePageHasNewerMessages =
|
|
890
|
-
!!firstPageMsg?.created_at && new Date(firstPageMsg.created_at) > createdAtAroundDate;
|
|
891
|
-
const wholePageHasOlderMessages =
|
|
892
|
-
!!lastPageMsg?.created_at && new Date(lastPageMsg.created_at) < createdAtAroundDate;
|
|
893
|
-
|
|
894
|
-
const requestedPageSizeNotMet =
|
|
895
|
-
requestedPageSize > parentSet.messages.length &&
|
|
896
|
-
requestedPageSize > returnedPage.length;
|
|
897
|
-
const noMoreMessages =
|
|
898
|
-
(requestedPageSize > parentSet.messages.length ||
|
|
899
|
-
parentSet.messages.length >= returnedPage.length) &&
|
|
900
|
-
requestedPageSize > returnedPage.length;
|
|
901
|
-
|
|
902
|
-
if (wholePageHasNewerMessages) {
|
|
903
|
-
hasPrev = false;
|
|
904
|
-
updateHasPrev = true;
|
|
905
|
-
if (requestedPageSizeNotMet) {
|
|
906
|
-
hasNext = false;
|
|
907
|
-
updateHasNext = true;
|
|
908
|
-
}
|
|
909
|
-
} else if (wholePageHasOlderMessages) {
|
|
910
|
-
hasNext = false;
|
|
911
|
-
updateHasNext = true;
|
|
912
|
-
if (requestedPageSizeNotMet) {
|
|
913
|
-
hasPrev = false;
|
|
914
|
-
updateHasPrev = true;
|
|
915
|
-
}
|
|
916
|
-
} else if (noMoreMessages) {
|
|
917
|
-
hasNext = hasPrev = false;
|
|
918
|
-
updateHasPrev = updateHasNext = true;
|
|
919
|
-
} else {
|
|
920
|
-
const [firstFilteredPageMsg, lastFilteredPageMsg] = [
|
|
921
|
-
filteredReturnedPage[0],
|
|
922
|
-
filteredReturnedPage.slice(-1)[0],
|
|
923
|
-
];
|
|
924
|
-
const [firstPageMsgIsFirstInSet, lastPageMsgIsLastInSet] = [
|
|
925
|
-
firstFilteredPageMsg?.id && firstFilteredPageMsg.id === parentSet.messages[0]?.id,
|
|
926
|
-
lastFilteredPageMsg?.id &&
|
|
927
|
-
lastFilteredPageMsg.id === parentSet.messages.slice(-1)[0]?.id,
|
|
928
|
-
];
|
|
929
|
-
updateHasPrev = firstPageMsgIsFirstInSet;
|
|
930
|
-
updateHasNext = lastPageMsgIsLastInSet;
|
|
931
|
-
const midPointByCount = Math.floor(returnedPage.length / 2);
|
|
932
|
-
const midPointByCreationDate = binarySearchByDateEqualOrNearestGreater(
|
|
933
|
-
returnedPage,
|
|
934
|
-
createdAtAroundDate,
|
|
935
|
-
);
|
|
936
|
-
|
|
937
|
-
if (midPointByCreationDate !== -1) {
|
|
938
|
-
hasPrev = midPointByCount <= midPointByCreationDate;
|
|
939
|
-
hasNext = midPointByCount >= midPointByCreationDate;
|
|
940
|
-
}
|
|
941
|
-
}
|
|
942
|
-
|
|
943
|
-
if (updateHasPrev && typeof hasPrev !== 'undefined') newPagination.hasPrev = hasPrev;
|
|
944
|
-
if (updateHasNext && typeof hasNext !== 'undefined') newPagination.hasNext = hasNext;
|
|
945
|
-
|
|
946
|
-
return newPagination;
|
|
947
|
-
};
|
|
948
|
-
|
|
949
|
-
const messagePaginationIdAround = ({
|
|
950
|
-
parentSet,
|
|
951
|
-
requestedPageSize,
|
|
952
|
-
returnedPage,
|
|
953
|
-
filteredReturnedPage,
|
|
954
|
-
messagePaginationOptions,
|
|
955
|
-
}: MessagePaginationUpdatedParams) => {
|
|
956
|
-
const newPagination = { ...parentSet.pagination };
|
|
957
|
-
const { id_around } = messagePaginationOptions || {};
|
|
958
|
-
if (!id_around) return newPagination;
|
|
959
|
-
let hasPrev;
|
|
960
|
-
let hasNext;
|
|
961
|
-
|
|
962
|
-
const [firstFilteredPageMsg, lastFilteredPageMsg] = [
|
|
963
|
-
filteredReturnedPage[0],
|
|
964
|
-
filteredReturnedPage.slice(-1)[0],
|
|
965
|
-
];
|
|
966
|
-
const [firstPageMsgIsFirstInSet, lastPageMsgIsLastInSet] = [
|
|
967
|
-
firstFilteredPageMsg?.id === parentSet.messages[0]?.id,
|
|
968
|
-
lastFilteredPageMsg?.id === parentSet.messages.slice(-1)[0]?.id,
|
|
969
|
-
];
|
|
970
|
-
let updateHasPrev = firstPageMsgIsFirstInSet;
|
|
971
|
-
let updateHasNext = lastPageMsgIsLastInSet;
|
|
972
|
-
|
|
973
|
-
const midPoint = Math.floor(returnedPage.length / 2);
|
|
974
|
-
const noMoreMessages =
|
|
975
|
-
(requestedPageSize > parentSet.messages.length ||
|
|
976
|
-
parentSet.messages.length >= returnedPage.length) &&
|
|
977
|
-
requestedPageSize > returnedPage.length;
|
|
978
|
-
|
|
979
|
-
if (noMoreMessages) {
|
|
980
|
-
hasNext = hasPrev = false;
|
|
981
|
-
updateHasPrev = updateHasNext = true;
|
|
982
|
-
} else if (!returnedPage[midPoint]) {
|
|
983
|
-
return newPagination;
|
|
984
|
-
} else if (returnedPage[midPoint].id === id_around) {
|
|
985
|
-
hasPrev = hasNext = true;
|
|
986
|
-
} else {
|
|
987
|
-
let targetMsg;
|
|
988
|
-
const halves = [returnedPage.slice(0, midPoint), returnedPage.slice(midPoint)];
|
|
989
|
-
hasPrev = hasNext = true;
|
|
990
|
-
for (let i = 0; i < halves.length; i++) {
|
|
991
|
-
targetMsg = halves[i].find((message) => message.id === id_around);
|
|
992
|
-
if (targetMsg && i === 0) {
|
|
993
|
-
hasPrev = false;
|
|
994
|
-
}
|
|
995
|
-
if (targetMsg && i === 1) {
|
|
996
|
-
hasNext = false;
|
|
997
|
-
}
|
|
998
|
-
}
|
|
999
|
-
}
|
|
1000
|
-
|
|
1001
|
-
if (updateHasPrev && typeof hasPrev !== 'undefined') newPagination.hasPrev = hasPrev;
|
|
1002
|
-
if (updateHasNext && typeof hasNext !== 'undefined') newPagination.hasNext = hasNext;
|
|
1003
|
-
|
|
1004
|
-
return newPagination;
|
|
1005
|
-
};
|
|
1006
|
-
|
|
1007
|
-
const messagePaginationLinear = ({
|
|
1008
|
-
parentSet,
|
|
1009
|
-
requestedPageSize,
|
|
1010
|
-
returnedPage,
|
|
1011
|
-
filteredReturnedPage,
|
|
1012
|
-
messagePaginationOptions,
|
|
1013
|
-
}: MessagePaginationUpdatedParams) => {
|
|
1014
|
-
const newPagination = { ...parentSet.pagination };
|
|
1015
|
-
|
|
1016
|
-
let hasPrev;
|
|
1017
|
-
let hasNext;
|
|
1018
|
-
|
|
1019
|
-
const [firstFilteredPageMsg, lastFilteredPageMsg] = [
|
|
1020
|
-
filteredReturnedPage[0],
|
|
1021
|
-
filteredReturnedPage.slice(-1)[0],
|
|
1022
|
-
];
|
|
1023
|
-
const [firstPageMsgIsFirstInSet, lastPageMsgIsLastInSet] = [
|
|
1024
|
-
firstFilteredPageMsg?.id && firstFilteredPageMsg.id === parentSet.messages[0]?.id,
|
|
1025
|
-
lastFilteredPageMsg?.id &&
|
|
1026
|
-
lastFilteredPageMsg.id === parentSet.messages.slice(-1)[0]?.id,
|
|
1027
|
-
];
|
|
1028
|
-
|
|
1029
|
-
const queriedNextMessages =
|
|
1030
|
-
messagePaginationOptions &&
|
|
1031
|
-
(messagePaginationOptions.created_at_after_or_equal ||
|
|
1032
|
-
messagePaginationOptions.created_at_after ||
|
|
1033
|
-
messagePaginationOptions.id_gt ||
|
|
1034
|
-
messagePaginationOptions.id_gte);
|
|
1035
|
-
|
|
1036
|
-
const queriedPrevMessages =
|
|
1037
|
-
typeof messagePaginationOptions === 'undefined'
|
|
1038
|
-
? true
|
|
1039
|
-
: messagePaginationOptions.created_at_before_or_equal ||
|
|
1040
|
-
messagePaginationOptions.created_at_before ||
|
|
1041
|
-
messagePaginationOptions.id_lt ||
|
|
1042
|
-
messagePaginationOptions.id_lte ||
|
|
1043
|
-
messagePaginationOptions.offset;
|
|
1044
|
-
|
|
1045
|
-
const containsUnrecognizedOptionsOnly =
|
|
1046
|
-
!queriedNextMessages &&
|
|
1047
|
-
!queriedPrevMessages &&
|
|
1048
|
-
!messagePaginationOptions?.id_around &&
|
|
1049
|
-
!messagePaginationOptions?.created_at_around;
|
|
1050
|
-
|
|
1051
|
-
const hasMore = returnedPage.length >= requestedPageSize;
|
|
1052
|
-
|
|
1053
|
-
if (typeof queriedPrevMessages !== 'undefined' || containsUnrecognizedOptionsOnly) {
|
|
1054
|
-
hasPrev = hasMore;
|
|
1055
|
-
}
|
|
1056
|
-
if (typeof queriedNextMessages !== 'undefined') {
|
|
1057
|
-
hasNext = hasMore;
|
|
1058
|
-
}
|
|
1059
|
-
const returnedPageIsEmpty = returnedPage.length === 0;
|
|
1060
|
-
|
|
1061
|
-
if ((firstPageMsgIsFirstInSet || returnedPageIsEmpty) && typeof hasPrev !== 'undefined')
|
|
1062
|
-
newPagination.hasPrev = hasPrev;
|
|
1063
|
-
if ((lastPageMsgIsLastInSet || returnedPageIsEmpty) && typeof hasNext !== 'undefined')
|
|
1064
|
-
newPagination.hasNext = hasNext;
|
|
1065
|
-
|
|
1066
|
-
return newPagination;
|
|
1067
|
-
};
|
|
1068
|
-
|
|
1069
|
-
export const messageSetPagination = (params: MessagePaginationUpdatedParams) => {
|
|
1070
|
-
if (
|
|
1071
|
-
params.parentSet.messages.length +
|
|
1072
|
-
(params.returnedPage.length - params.filteredReturnedPage.length) <
|
|
1073
|
-
params.returnedPage.length
|
|
1074
|
-
) {
|
|
1075
|
-
params.logger?.(
|
|
1076
|
-
'error',
|
|
1077
|
-
'Corrupted message set state: parent set size < returned page size',
|
|
1078
|
-
);
|
|
1079
|
-
return params.parentSet.pagination;
|
|
1080
|
-
}
|
|
1081
|
-
|
|
1082
|
-
if (params.messagePaginationOptions?.created_at_around) {
|
|
1083
|
-
return messagePaginationCreatedAtAround(params);
|
|
1084
|
-
} else if (params.messagePaginationOptions?.id_around) {
|
|
1085
|
-
return messagePaginationIdAround(params);
|
|
1086
|
-
} else {
|
|
1087
|
-
return messagePaginationLinear(params);
|
|
1088
|
-
}
|
|
1089
|
-
};
|
|
1090
|
-
|
|
1091
|
-
/**
|
|
1092
|
-
* A utility object used to prevent duplicate invocation of channel.watch() to be triggered when
|
|
1093
|
-
* 'notification.message_new' and 'notification.added_to_channel' events arrive at the same time.
|
|
1094
|
-
*/
|
|
1095
|
-
const WATCH_QUERY_IN_PROGRESS_FOR_CHANNEL: Record<
|
|
1096
|
-
string,
|
|
1097
|
-
Promise<QueryChannelAPIResponse> | undefined
|
|
1098
|
-
> = {};
|
|
1099
|
-
|
|
1100
|
-
type GetChannelParams = {
|
|
1101
|
-
client: StreamChat;
|
|
1102
|
-
channel?: Channel;
|
|
1103
|
-
id?: string;
|
|
1104
|
-
members?: string[];
|
|
1105
|
-
options?: ChannelQueryOptions;
|
|
1106
|
-
type?: string;
|
|
1107
|
-
};
|
|
1108
|
-
/**
|
|
1109
|
-
* Calls channel.watch() if it was not already recently called. Waits for watch promise to resolve even if it was invoked previously.
|
|
1110
|
-
* If the channel is not passed as a property, it will get it either by its channel.cid or by its members list and do the same.
|
|
1111
|
-
* @param client
|
|
1112
|
-
* @param members
|
|
1113
|
-
* @param options
|
|
1114
|
-
* @param type
|
|
1115
|
-
* @param id
|
|
1116
|
-
* @param channel
|
|
1117
|
-
*/
|
|
1118
|
-
export const getAndWatchChannel = async ({
|
|
1119
|
-
channel,
|
|
1120
|
-
client,
|
|
1121
|
-
id,
|
|
1122
|
-
members,
|
|
1123
|
-
options,
|
|
1124
|
-
type,
|
|
1125
|
-
}: GetChannelParams) => {
|
|
1126
|
-
if (!channel && !type) {
|
|
1127
|
-
throw new Error('Channel or channel type have to be provided to query a channel.');
|
|
1128
|
-
}
|
|
1129
|
-
|
|
1130
|
-
// unfortunately typescript is not able to infer that if (!channel && !type) === false, then channel or type has to be truthy
|
|
1131
|
-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
1132
|
-
const channelToWatch = channel || client.channel(type!, id, { members });
|
|
1133
|
-
|
|
1134
|
-
// need to keep as with call to channel.watch the id can be changed from undefined to an actual ID generated server-side
|
|
1135
|
-
const originalCid = channelToWatch.id
|
|
1136
|
-
? channelToWatch.cid
|
|
1137
|
-
: members && members.length
|
|
1138
|
-
? generateChannelTempCid(channelToWatch.type, members)
|
|
1139
|
-
: undefined;
|
|
1140
|
-
|
|
1141
|
-
if (!originalCid) {
|
|
1142
|
-
throw new Error(
|
|
1143
|
-
'Channel ID or channel members array have to be provided to query a channel.',
|
|
1144
|
-
);
|
|
1145
|
-
}
|
|
1146
|
-
|
|
1147
|
-
const queryPromise = WATCH_QUERY_IN_PROGRESS_FOR_CHANNEL[originalCid];
|
|
1148
|
-
|
|
1149
|
-
if (queryPromise) {
|
|
1150
|
-
await queryPromise;
|
|
1151
|
-
} else {
|
|
1152
|
-
try {
|
|
1153
|
-
WATCH_QUERY_IN_PROGRESS_FOR_CHANNEL[originalCid] = channelToWatch.watch(options);
|
|
1154
|
-
await WATCH_QUERY_IN_PROGRESS_FOR_CHANNEL[originalCid];
|
|
1155
|
-
} finally {
|
|
1156
|
-
delete WATCH_QUERY_IN_PROGRESS_FOR_CHANNEL[originalCid];
|
|
1157
|
-
}
|
|
1158
|
-
}
|
|
1159
|
-
|
|
1160
|
-
return channelToWatch;
|
|
1161
|
-
};
|
|
1162
|
-
|
|
1163
787
|
/**
|
|
1164
788
|
* Generates a temporary channel.cid for channels created without ID, as they need to be referenced
|
|
1165
789
|
* by an identifier until the back-end generates the final ID. The cid is generated by its member IDs
|
|
1166
790
|
* which are sorted and can be recreated the same every time given the same arguments.
|
|
1167
|
-
*
|
|
1168
|
-
* @param
|
|
791
|
+
*
|
|
792
|
+
* @param channelType - The channel type.
|
|
793
|
+
* @param members - The member ids used to build the temporary cid.
|
|
1169
794
|
*/
|
|
1170
795
|
export const generateChannelTempCid = (channelType: string, members: string[]) => {
|
|
1171
796
|
if (!members) return;
|
|
@@ -1174,186 +799,6 @@ export const generateChannelTempCid = (channelType: string, members: string[]) =
|
|
|
1174
799
|
return `${channelType}:!members-${membersStr}`;
|
|
1175
800
|
};
|
|
1176
801
|
|
|
1177
|
-
/**
|
|
1178
|
-
* Checks if a channel is pinned or not. Will return true only if channel.state.membership.pinned_at exists.
|
|
1179
|
-
* @param channel
|
|
1180
|
-
*/
|
|
1181
|
-
export const isChannelPinned = (channel: Channel) => {
|
|
1182
|
-
if (!channel) return false;
|
|
1183
|
-
|
|
1184
|
-
const member = channel.state.membership;
|
|
1185
|
-
|
|
1186
|
-
return !!member?.pinned_at;
|
|
1187
|
-
};
|
|
1188
|
-
|
|
1189
|
-
/**
|
|
1190
|
-
* Checks if a channel is archived or not. Will return true only if channel.state.membership.archived_at exists.
|
|
1191
|
-
* @param channel
|
|
1192
|
-
*/
|
|
1193
|
-
export const isChannelArchived = (channel: Channel) => {
|
|
1194
|
-
if (!channel) return false;
|
|
1195
|
-
|
|
1196
|
-
const member = channel.state.membership;
|
|
1197
|
-
|
|
1198
|
-
return !!member?.archived_at;
|
|
1199
|
-
};
|
|
1200
|
-
|
|
1201
|
-
/**
|
|
1202
|
-
* A utility that tells us whether we should consider archived channels or not based
|
|
1203
|
-
* on filters. Will return true only if filters.archived exists and is a boolean value.
|
|
1204
|
-
* @param filters
|
|
1205
|
-
*/
|
|
1206
|
-
export const shouldConsiderArchivedChannels = (filters: ChannelFilters) => {
|
|
1207
|
-
if (!filters) return false;
|
|
1208
|
-
|
|
1209
|
-
return typeof filters.archived === 'boolean';
|
|
1210
|
-
};
|
|
1211
|
-
|
|
1212
|
-
/**
|
|
1213
|
-
* Extracts the value of the sort parameter at a given index, for a targeted key. Can
|
|
1214
|
-
* handle both array and object versions of sort. Will return null if the index/key
|
|
1215
|
-
* combination does not exist.
|
|
1216
|
-
* @param atIndex - the index at which we'll examine the sort value, if it's an array one
|
|
1217
|
-
* @param sort - the sort value - both array and object notations are accepted
|
|
1218
|
-
* @param targetKey - the target key which needs to exist for the sort at a certain index
|
|
1219
|
-
*/
|
|
1220
|
-
export const extractSortValue = ({
|
|
1221
|
-
atIndex,
|
|
1222
|
-
sort,
|
|
1223
|
-
targetKey,
|
|
1224
|
-
}: {
|
|
1225
|
-
atIndex: number;
|
|
1226
|
-
targetKey: keyof ChannelSortBase;
|
|
1227
|
-
sort?: ChannelSort;
|
|
1228
|
-
}) => {
|
|
1229
|
-
if (!sort) return null;
|
|
1230
|
-
let option: null | ChannelSortBase = null;
|
|
1231
|
-
|
|
1232
|
-
if (Array.isArray(sort)) {
|
|
1233
|
-
option = sort[atIndex] ?? null;
|
|
1234
|
-
} else {
|
|
1235
|
-
let index = 0;
|
|
1236
|
-
for (const key in sort) {
|
|
1237
|
-
if (index !== atIndex) {
|
|
1238
|
-
index++;
|
|
1239
|
-
continue;
|
|
1240
|
-
}
|
|
1241
|
-
|
|
1242
|
-
if (key !== targetKey) {
|
|
1243
|
-
return null;
|
|
1244
|
-
}
|
|
1245
|
-
|
|
1246
|
-
option = sort;
|
|
1247
|
-
|
|
1248
|
-
break;
|
|
1249
|
-
}
|
|
1250
|
-
}
|
|
1251
|
-
|
|
1252
|
-
return option?.[targetKey] ?? null;
|
|
1253
|
-
};
|
|
1254
|
-
|
|
1255
|
-
/**
|
|
1256
|
-
* Returns true only if `{ pinned_at: -1 }` or `{ pinned_at: 1 }` option is first within the `sort` array.
|
|
1257
|
-
*/
|
|
1258
|
-
export const shouldConsiderPinnedChannels = (sort: ChannelSort) => {
|
|
1259
|
-
const value = findPinnedAtSortOrder({ sort });
|
|
1260
|
-
|
|
1261
|
-
if (typeof value !== 'number') return false;
|
|
1262
|
-
|
|
1263
|
-
return Math.abs(value) === 1;
|
|
1264
|
-
};
|
|
1265
|
-
|
|
1266
|
-
/**
|
|
1267
|
-
* Checks whether the sort value of type object contains a pinned_at value or if
|
|
1268
|
-
* an array sort value type has the first value be an object containing pinned_at.
|
|
1269
|
-
* @param sort
|
|
1270
|
-
*/
|
|
1271
|
-
export const findPinnedAtSortOrder = ({ sort }: { sort: ChannelSort }) =>
|
|
1272
|
-
extractSortValue({
|
|
1273
|
-
atIndex: 0,
|
|
1274
|
-
sort,
|
|
1275
|
-
targetKey: 'pinned_at',
|
|
1276
|
-
});
|
|
1277
|
-
|
|
1278
|
-
/**
|
|
1279
|
-
* Finds the index of the last consecutively pinned channel, starting from the start of the
|
|
1280
|
-
* array. Will not consider any pinned channels after the contiguous subsequence at the
|
|
1281
|
-
* start of the array.
|
|
1282
|
-
* @param channels
|
|
1283
|
-
*/
|
|
1284
|
-
export const findLastPinnedChannelIndex = ({ channels }: { channels: Channel[] }) => {
|
|
1285
|
-
let lastPinnedChannelIndex: number | null = null;
|
|
1286
|
-
|
|
1287
|
-
for (const channel of channels) {
|
|
1288
|
-
if (!isChannelPinned(channel)) break;
|
|
1289
|
-
|
|
1290
|
-
if (typeof lastPinnedChannelIndex === 'number') {
|
|
1291
|
-
lastPinnedChannelIndex++;
|
|
1292
|
-
} else {
|
|
1293
|
-
lastPinnedChannelIndex = 0;
|
|
1294
|
-
}
|
|
1295
|
-
}
|
|
1296
|
-
|
|
1297
|
-
return lastPinnedChannelIndex;
|
|
1298
|
-
};
|
|
1299
|
-
|
|
1300
|
-
/**
|
|
1301
|
-
* A utility used to move a channel towards the beginning of a list of channels (promote it to a higher position). It
|
|
1302
|
-
* considers pinned channels in the process if needed and makes sure to only update the list reference if the list
|
|
1303
|
-
* should actually change. It will try to move the channel as high as it can within the list.
|
|
1304
|
-
* @param channels - the list of channels we want to modify
|
|
1305
|
-
* @param channelToMove - the channel we want to promote
|
|
1306
|
-
* @param channelToMoveIndexWithinChannels - optionally, the index of the channel we want to move if we know it (will skip a manual check)
|
|
1307
|
-
* @param sort - the sort value used to check for pinned channels
|
|
1308
|
-
*/
|
|
1309
|
-
export const promoteChannel = ({
|
|
1310
|
-
channels,
|
|
1311
|
-
channelToMove,
|
|
1312
|
-
channelToMoveIndexWithinChannels,
|
|
1313
|
-
sort,
|
|
1314
|
-
}: PromoteChannelParams) => {
|
|
1315
|
-
// get index of channel to move up
|
|
1316
|
-
const targetChannelIndex =
|
|
1317
|
-
channelToMoveIndexWithinChannels ??
|
|
1318
|
-
channels.findIndex((channel) => channel.cid === channelToMove.cid);
|
|
1319
|
-
|
|
1320
|
-
const targetChannelExistsWithinList = targetChannelIndex >= 0;
|
|
1321
|
-
const targetChannelAlreadyAtTheTop = targetChannelIndex === 0;
|
|
1322
|
-
|
|
1323
|
-
// pinned channels should not move within the list based on recent activity, channels which
|
|
1324
|
-
// receive messages and are not pinned should move upwards but only under the last pinned channel
|
|
1325
|
-
// in the list
|
|
1326
|
-
const considerPinnedChannels = shouldConsiderPinnedChannels(sort);
|
|
1327
|
-
const isTargetChannelPinned = isChannelPinned(channelToMove);
|
|
1328
|
-
|
|
1329
|
-
if (targetChannelAlreadyAtTheTop || (considerPinnedChannels && isTargetChannelPinned)) {
|
|
1330
|
-
return channels;
|
|
1331
|
-
}
|
|
1332
|
-
|
|
1333
|
-
const newChannels = [...channels];
|
|
1334
|
-
|
|
1335
|
-
// target channel index is known, remove it from the list
|
|
1336
|
-
if (targetChannelExistsWithinList) {
|
|
1337
|
-
newChannels.splice(targetChannelIndex, 1);
|
|
1338
|
-
}
|
|
1339
|
-
|
|
1340
|
-
// as position of pinned channels has to stay unchanged, we need to
|
|
1341
|
-
// find last pinned channel in the list to move the target channel after
|
|
1342
|
-
let lastPinnedChannelIndex: number | null = null;
|
|
1343
|
-
if (considerPinnedChannels) {
|
|
1344
|
-
lastPinnedChannelIndex = findLastPinnedChannelIndex({ channels: newChannels });
|
|
1345
|
-
}
|
|
1346
|
-
|
|
1347
|
-
// re-insert it at the new place (to specific index if pinned channels are considered)
|
|
1348
|
-
newChannels.splice(
|
|
1349
|
-
typeof lastPinnedChannelIndex === 'number' ? lastPinnedChannelIndex + 1 : 0,
|
|
1350
|
-
0,
|
|
1351
|
-
channelToMove,
|
|
1352
|
-
);
|
|
1353
|
-
|
|
1354
|
-
return newChannels;
|
|
1355
|
-
};
|
|
1356
|
-
|
|
1357
802
|
export const isDate = (value: unknown): value is Date => !!(value as Date).getTime;
|
|
1358
803
|
|
|
1359
804
|
export const isLocalMessage = (message: unknown): message is LocalMessage =>
|
|
@@ -1367,9 +812,11 @@ export const runDetached = <T>(
|
|
|
1367
812
|
onErrorCallback?: (error: Error) => void | Promise<void>;
|
|
1368
813
|
},
|
|
1369
814
|
) => {
|
|
1370
|
-
const { context, onSuccessCallback
|
|
815
|
+
const { context, onSuccessCallback, onErrorCallback } = options ?? {};
|
|
1371
816
|
const defaultOnError = (error: Error) => {
|
|
1372
|
-
|
|
817
|
+
logger
|
|
818
|
+
.withExtraTags('runDetached')
|
|
819
|
+
.error(`An error occurred in context "${context}".`, { error });
|
|
1373
820
|
};
|
|
1374
821
|
const onError = onErrorCallback ?? defaultOnError;
|
|
1375
822
|
|
|
@@ -1383,11 +830,18 @@ export const runDetached = <T>(
|
|
|
1383
830
|
};
|
|
1384
831
|
|
|
1385
832
|
export const isBlockedMessage = (message: LocalMessage) =>
|
|
1386
|
-
message.type === 'error' &&
|
|
1387
|
-
(message.moderation_details?.action === 'MESSAGE_RESPONSE_ACTION_REMOVE' ||
|
|
1388
|
-
message.moderation?.action === 'remove');
|
|
833
|
+
message.type === 'error' && message.moderation?.action === 'remove';
|
|
1389
834
|
|
|
1390
835
|
export const isBouncedMessage = (message: LocalMessage) =>
|
|
1391
|
-
message.type === 'error' &&
|
|
1392
|
-
|
|
1393
|
-
|
|
836
|
+
message.type === 'error' && message?.moderation?.action === 'bounce';
|
|
837
|
+
|
|
838
|
+
export const getEnv = (envKey: keyof NodeJS.ProcessEnv) => {
|
|
839
|
+
if (
|
|
840
|
+
typeof process !== 'undefined' &&
|
|
841
|
+
(Object.hasOwn(process, 'env') || 'env' in process)
|
|
842
|
+
) {
|
|
843
|
+
return process.env[envKey];
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
return undefined;
|
|
847
|
+
};
|