stream-chat
Version:
JS SDK for the Stream Chat API
1,336 lines (1,328 loc) • 596 kB
JavaScript
"use strict";
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/index.ts
var index_exports = {};
__export(index_exports, {
AbstractOfflineDB: () => AbstractOfflineDB,
Allow: () => Allow,
AllowAll: () => AllowAll,
AnyResource: () => AnyResource,
AnyRole: () => AnyRole,
AttachmentManager: () => AttachmentManager,
AttachmentPostUploadMiddlewareExecutor: () => AttachmentPostUploadMiddlewareExecutor,
AttachmentPreUploadMiddlewareExecutor: () => AttachmentPreUploadMiddlewareExecutor,
BasePaginator: () => BasePaginator,
BaseSearchSource: () => BaseSearchSource,
BaseSearchSourceSync: () => BaseSearchSourceSync,
BuiltinPermissions: () => BuiltinPermissions,
BuiltinRoles: () => BuiltinRoles,
Campaign: () => Campaign,
Channel: () => Channel,
ChannelManager: () => ChannelManager,
ChannelSearchSource: () => ChannelSearchSource,
ChannelState: () => ChannelState,
CheckSignature: () => CheckSignature,
ClientState: () => ClientState,
CommandSearchSource: () => CommandSearchSource,
CustomDataManager: () => CustomDataManager,
DEFAULT_ATTACHMENT_MANAGER_CONFIG: () => DEFAULT_ATTACHMENT_MANAGER_CONFIG,
DEFAULT_CHANNEL_MANAGER_OPTIONS: () => DEFAULT_CHANNEL_MANAGER_OPTIONS,
DEFAULT_CHANNEL_MANAGER_PAGINATION_OPTIONS: () => DEFAULT_CHANNEL_MANAGER_PAGINATION_OPTIONS,
DEFAULT_COMPOSER_CONFIG: () => DEFAULT_COMPOSER_CONFIG,
DEFAULT_LINK_PREVIEW_MANAGER_CONFIG: () => DEFAULT_LINK_PREVIEW_MANAGER_CONFIG,
DEFAULT_LOCATION_COMPOSER_CONFIG: () => DEFAULT_LOCATION_COMPOSER_CONFIG,
DEFAULT_PAGINATION_OPTIONS: () => DEFAULT_PAGINATION_OPTIONS,
DEFAULT_REMINDER_MANAGER_CONFIG: () => DEFAULT_REMINDER_MANAGER_CONFIG,
DEFAULT_STOP_REFRESH_BOUNDARY_MS: () => DEFAULT_STOP_REFRESH_BOUNDARY_MS,
DEFAULT_TEXT_COMPOSER_CONFIG: () => DEFAULT_TEXT_COMPOSER_CONFIG,
Deny: () => Deny,
DenyAll: () => DenyAll,
DevToken: () => DevToken,
EVENT_MAP: () => EVENT_MAP,
ErrorFromResponse: () => ErrorFromResponse,
FilterBuilder: () => FilterBuilder,
FixedSizeQueueCache: () => FixedSizeQueueCache,
InsightMetrics: () => InsightMetrics,
JWTServerToken: () => JWTServerToken,
JWTUserToken: () => JWTUserToken,
LinkPreviewStatus: () => LinkPreviewStatus,
LinkPreviewsManager: () => LinkPreviewsManager,
LiveLocationManager: () => LiveLocationManager,
LocationComposer: () => LocationComposer,
MAX_POLL_OPTIONS: () => MAX_POLL_OPTIONS,
MODERATION_ENTITY_TYPES: () => MODERATION_ENTITY_TYPES,
MaxPriority: () => MaxPriority,
MentionsSearchSource: () => MentionsSearchSource,
MergedStateStore: () => MergedStateStore,
MessageComposer: () => MessageComposer,
MessageComposerMiddlewareExecutor: () => MessageComposerMiddlewareExecutor,
MessageDeliveryReporter: () => MessageDeliveryReporter,
MessageDraftComposerMiddlewareExecutor: () => MessageDraftComposerMiddlewareExecutor,
MessageReceiptsTracker: () => MessageReceiptsTracker,
MessageSearchSource: () => MessageSearchSource,
MiddlewareExecutor: () => MiddlewareExecutor,
MinPriority: () => MinPriority,
Moderation: () => Moderation,
NotificationManager: () => NotificationManager,
OfflineDBSyncManager: () => OfflineDBSyncManager,
OfflineError: () => OfflineError,
Permission: () => Permission,
Poll: () => Poll,
PollComposer: () => PollComposer,
PollComposerCompositionMiddlewareExecutor: () => PollComposerCompositionMiddlewareExecutor,
PollComposerStateMiddlewareExecutor: () => PollComposerStateMiddlewareExecutor,
PollManager: () => PollManager,
Product: () => Product,
Reminder: () => Reminder,
ReminderManager: () => ReminderManager,
ReminderPaginator: () => ReminderPaginator,
ReminderTimer: () => ReminderTimer,
SearchController: () => SearchController,
Segment: () => Segment,
StableWSConnection: () => StableWSConnection,
StateStore: () => StateStore,
StreamChat: () => StreamChat,
THREAD_MANAGER_INITIAL_STATE: () => THREAD_MANAGER_INITIAL_STATE,
TextComposer: () => TextComposer,
TextComposerMiddlewareExecutor: () => TextComposerMiddlewareExecutor,
Thread: () => Thread,
ThreadManager: () => ThreadManager,
TokenManager: () => TokenManager,
UPDATE_LIVE_LOCATION_REQUEST_MIN_THROTTLE_TIMEOUT: () => UPDATE_LIVE_LOCATION_REQUEST_MIN_THROTTLE_TIMEOUT,
UserFromToken: () => UserFromToken,
UserSearchSource: () => UserSearchSource,
VALID_MAX_VOTES_VALUE_REGEX: () => VALID_MAX_VOTES_VALUE_REGEX,
VotingVisibility: () => VotingVisibility,
accentsMap: () => accentsMap,
buildWsFatalInsight: () => buildWsFatalInsight,
buildWsSuccessAfterFailureInsight: () => buildWsSuccessAfterFailureInsight,
calculateLevenshtein: () => calculateLevenshtein,
channelManagerEventToHandlerMapping: () => channelManagerEventToHandlerMapping,
chatCodes: () => chatCodes,
createActiveCommandGuardMiddleware: () => createActiveCommandGuardMiddleware,
createAttachmentsCompositionMiddleware: () => createAttachmentsCompositionMiddleware,
createBlockedAttachmentUploadNotificationMiddleware: () => createBlockedAttachmentUploadNotificationMiddleware,
createCommandInjectionMiddleware: () => createCommandInjectionMiddleware,
createCommandStringExtractionMiddleware: () => createCommandStringExtractionMiddleware,
createCommandsMiddleware: () => createCommandsMiddleware,
createCompositionDataCleanupMiddleware: () => createCompositionDataCleanupMiddleware,
createCompositionValidationMiddleware: () => createCompositionValidationMiddleware,
createCustomDataCompositionMiddleware: () => createCustomDataCompositionMiddleware,
createDraftAttachmentsCompositionMiddleware: () => createDraftAttachmentsCompositionMiddleware,
createDraftCommandInjectionMiddleware: () => createDraftCommandInjectionMiddleware,
createDraftCompositionValidationMiddleware: () => createDraftCompositionValidationMiddleware,
createDraftCustomDataCompositionMiddleware: () => createDraftCustomDataCompositionMiddleware,
createDraftLinkPreviewsCompositionMiddleware: () => createDraftLinkPreviewsCompositionMiddleware,
createDraftMessageComposerStateCompositionMiddleware: () => createDraftMessageComposerStateCompositionMiddleware,
createDraftTextComposerCompositionMiddleware: () => createDraftTextComposerCompositionMiddleware,
createFileFromBlobs: () => createFileFromBlobs,
createLinkPreviewsCompositionMiddleware: () => createLinkPreviewsCompositionMiddleware,
createMentionsMiddleware: () => createMentionsMiddleware,
createMessageComposerStateCompositionMiddleware: () => createMessageComposerStateCompositionMiddleware,
createPollComposerStateMiddleware: () => createPollComposerStateMiddleware,
createPostUploadAttachmentEnrichmentMiddleware: () => createPostUploadAttachmentEnrichmentMiddleware,
createSharedLocationCompositionMiddleware: () => createSharedLocationCompositionMiddleware,
createTextComposerCompositionMiddleware: () => createTextComposerCompositionMiddleware,
createTextComposerPreValidationMiddleware: () => createTextComposerPreValidationMiddleware,
createUploadConfigCheckMiddleware: () => createUploadConfigCheckMiddleware,
createUploadErrorHandlerMiddleware: () => createUploadErrorHandlerMiddleware,
decodeBase64: () => decodeBase64,
defaultPollFieldBlurEventValidators: () => defaultPollFieldBlurEventValidators,
defaultPollFieldChangeEventValidators: () => defaultPollFieldChangeEventValidators,
encodeBase64: () => encodeBase64,
ensureIsLocalAttachment: () => ensureIsLocalAttachment,
escapeRegExp: () => escapeRegExp,
extractPollData: () => extractPollData,
extractPollEnrichedData: () => extractPollEnrichedData,
formatMessage: () => formatMessage,
generateFileName: () => generateFileName,
getAttachmentTypeFromMimeType: () => getAttachmentTypeFromMimeType,
getCompleteCommandInString: () => getCompleteCommandInString,
getExtensionFromMimeType: () => getExtensionFromMimeType,
getTokenizedSuggestionDisplayName: () => getTokenizedSuggestionDisplayName,
getTriggerCharWithToken: () => getTriggerCharWithToken,
insertItemWithTrigger: () => insertItemWithTrigger,
isAudioAttachment: () => isAudioAttachment,
isBlobButNotFile: () => isBlobButNotFile,
isFile: () => isFile,
isFileAttachment: () => isFileAttachment,
isFileList: () => isFileList,
isFileReference: () => isFileReference,
isImageAttachment: () => isImageAttachment,
isImageFile: () => isImageFile,
isLocalAttachment: () => isLocalAttachment,
isLocalAudioAttachment: () => isLocalAudioAttachment,
isLocalFileAttachment: () => isLocalFileAttachment,
isLocalImageAttachment: () => isLocalImageAttachment,
isLocalUploadAttachment: () => isLocalUploadAttachment,
isLocalVideoAttachment: () => isLocalVideoAttachment,
isLocalVoiceRecordingAttachment: () => isLocalVoiceRecordingAttachment,
isOwnUser: () => isOwnUser,
isPatch: () => isPatch,
isScrapedContent: () => isScrapedContent,
isSharedLocationResponse: () => isSharedLocationResponse,
isTargetedOptionTextUpdate: () => isTargetedOptionTextUpdate,
isUploadedAttachment: () => isUploadedAttachment,
isVideoAttachment: () => isVideoAttachment,
isVoiceRecordingAttachment: () => isVoiceRecordingAttachment,
isVoteAnswer: () => isVoteAnswer,
localMessageToNewMessagePayload: () => localMessageToNewMessagePayload,
logChatPromiseExecution: () => logChatPromiseExecution,
mapPollStateToResponse: () => mapPollStateToResponse,
pollCompositionStateProcessors: () => pollCompositionStateProcessors,
pollStateChangeValidators: () => pollStateChangeValidators,
postInsights: () => postInsights,
promoteChannel: () => promoteChannel,
readFileAsArrayBuffer: () => readFileAsArrayBuffer,
removeDiacritics: () => removeDiacritics,
replaceWordWithEntity: () => replaceWordWithEntity,
textIsEmpty: () => textIsEmpty,
timeLeftMs: () => timeLeftMs
});
module.exports = __toCommonJS(index_exports);
// src/base64.ts
var import_base64_js = require("base64-js");
function isString(arrayOrString) {
return typeof arrayOrString === "string";
}
function isMapStringCallback(arrayOrString, callback) {
return !!callback && isString(arrayOrString);
}
function map(arrayOrString, callback) {
const res = [];
if (isString(arrayOrString) && isMapStringCallback(arrayOrString, callback)) {
for (let k = 0, len = arrayOrString.length; k < len; k++) {
if (arrayOrString.charAt(k)) {
const kValue = arrayOrString.charAt(k);
const mappedValue = callback(kValue, k, arrayOrString);
res[k] = mappedValue;
}
}
} else if (!isString(arrayOrString) && !isMapStringCallback(arrayOrString, callback)) {
for (let k = 0, len = arrayOrString.length; k < len; k++) {
if (k in arrayOrString) {
const kValue = arrayOrString[k];
const mappedValue = callback(kValue, k, arrayOrString);
res[k] = mappedValue;
}
}
}
return res;
}
var encodeBase64 = (data) => (0, import_base64_js.fromByteArray)(new Uint8Array(map(data, (char) => char.charCodeAt(0))));
var decodeBase64 = (s) => {
const e = {}, w = String.fromCharCode, L = s.length;
let i, b = 0, c, x, l = 0, a, r = "";
const A = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
for (i = 0; i < 64; i++) {
e[A.charAt(i)] = i;
}
for (x = 0; x < L; x++) {
c = e[s.charAt(x)];
b = (b << 6) + c;
l += 6;
while (l >= 8) {
((a = b >>> (l -= 8) & 255) || x < L - 2) && (r += w(a));
}
}
return r;
};
// src/campaign.ts
var Campaign = class {
constructor(client, id, data) {
this.client = client;
this.id = id;
this.data = data;
}
async create() {
const body = {
id: this.id,
message_template: this.data?.message_template,
segment_ids: this.data?.segment_ids,
sender_id: this.data?.sender_id,
sender_mode: this.data?.sender_mode,
sender_visibility: this.data?.sender_visibility,
channel_template: this.data?.channel_template,
create_channels: this.data?.create_channels,
show_channels: this.data?.show_channels,
description: this.data?.description,
name: this.data?.name,
skip_push: this.data?.skip_push,
skip_webhook: this.data?.skip_webhook,
user_ids: this.data?.user_ids
};
const result = await this.client.createCampaign(body);
this.id = result.campaign.id;
this.data = result.campaign;
return result;
}
verifyCampaignId() {
if (!this.id) {
throw new Error(
"Campaign id is missing. Either create the campaign using campaign.create() or set the id during instantiation - const campaign = client.campaign(id)"
);
}
}
async start(options) {
this.verifyCampaignId();
return await this.client.startCampaign(this.id, options);
}
update(data) {
this.verifyCampaignId();
return this.client.updateCampaign(this.id, data);
}
async delete() {
this.verifyCampaignId();
return await this.client.deleteCampaign(this.id);
}
stop() {
this.verifyCampaignId();
return this.client.stopCampaign(this.id);
}
get(options) {
this.verifyCampaignId();
return this.client.getCampaign(this.id, options);
}
};
// src/client.ts
var import_axios3 = __toESM(require("axios"));
var import_https = __toESM(require("https"));
// src/utils.ts
var import_form_data = __toESM(require("form-data"));
// src/constants.ts
var DEFAULT_QUERY_CHANNELS_MESSAGE_LIST_PAGE_SIZE = 25;
var DEFAULT_QUERY_CHANNEL_MESSAGE_LIST_PAGE_SIZE = 100;
var DEFAULT_MESSAGE_SET_PAGINATION = Object.freeze({
hasNext: false,
hasPrev: false
});
var DEFAULT_UPLOAD_SIZE_LIMIT_BYTES = 100 * 1024 * 1024;
var API_MAX_FILES_ALLOWED_PER_MESSAGE = 10;
var MAX_CHANNEL_MEMBER_COUNT_IN_CHANNEL_QUERY = 100;
var RESERVED_UPDATED_MESSAGE_FIELDS = Object.freeze({
// Dates should not be converted back to ISO strings as JS looses precision on them (milliseconds)
created_at: true,
deleted_at: true,
pinned_at: true,
updated_at: true,
command: true,
// Back-end enriches these fields
mentioned_users: true,
quoted_message: true,
// Client-specific fields
latest_reactions: true,
own_reactions: true,
reaction_counts: true,
reply_count: true,
// Message text related fields that shouldn't be in update
i18n: true,
type: true,
html: true,
__html: true,
user: true
});
var LOCAL_MESSAGE_FIELDS = Object.freeze({ error: true });
var DEFAULT_QUERY_CHANNELS_RETRY_COUNT = 3;
var DEFAULT_QUERY_CHANNELS_MS_BETWEEN_RETRIES = 1e3;
// src/utils.ts
function logChatPromiseExecution(promise, name) {
promise.then().catch((error) => {
console.warn(`failed to do ${name}, ran into error: `, error);
});
}
var sleep = (m) => new Promise((r) => setTimeout(r, m));
function isFunction(value) {
return typeof value === "function" || value instanceof Function || Object.prototype.toString.call(value) === "[object Function]";
}
var chatCodes = {
TOKEN_EXPIRED: 40,
WS_CLOSED_SUCCESS: 1e3
};
function isReadableStream(obj) {
return obj !== null && typeof obj === "object" && (obj.readable || typeof obj._read === "function");
}
function isBuffer(obj) {
return obj != null && obj.constructor != null && // @ts-expect-error expected
typeof obj.constructor.isBuffer === "function" && // @ts-expect-error expected
obj.constructor.isBuffer(obj);
}
function isFileWebAPI(uri) {
return typeof window !== "undefined" && "File" in window && uri instanceof File;
}
function isOwnUser(user) {
return user?.total_unread_count !== void 0;
}
function isBlobWebAPI(uri) {
return typeof window !== "undefined" && "Blob" in window && uri instanceof Blob;
}
function isOwnUserBaseProperty(property) {
const ownUserBaseProperties = {
channel_mutes: true,
devices: true,
mutes: true,
total_unread_count: true,
unread_channels: true,
unread_count: true,
unread_threads: true,
invisible: true,
privacy_settings: true,
roles: true,
push_preferences: true,
total_unread_count_by_team: true
};
return ownUserBaseProperties[property];
}
function addFileToFormData(uri, name, contentType) {
const data = new import_form_data.default();
if (isReadableStream(uri) || isBuffer(uri) || isFileWebAPI(uri) || isBlobWebAPI(uri)) {
if (name) data.append("file", uri, name);
else data.append("file", uri);
} else {
data.append("file", {
uri,
name: name || uri.split("/").reverse()[0],
contentType: contentType || void 0,
type: contentType || void 0
});
}
return data;
}
function normalizeQuerySort(sort) {
const sortFields = [];
const sortArr = Array.isArray(sort) ? sort : [sort];
for (const item of sortArr) {
const entries = Object.entries(item);
if (entries.length > 1) {
console.warn(
"client._buildSort() - multiple fields in a single sort object detected. Object's field order is not guaranteed"
);
}
for (const [field, direction] of entries) {
sortFields.push({ field, direction });
}
}
return sortFields;
}
function retryInterval(numberOfFailures) {
const max = Math.min(500 + numberOfFailures * 2e3, 25e3);
const min = Math.min(Math.max(250, (numberOfFailures - 1) * 2e3), 25e3);
return Math.floor(Math.random() * (max - min) + min);
}
function randomId() {
return generateUUIDv4();
}
function hex(bytes) {
let s = "";
for (let i = 0; i < bytes.length; i++) {
s += bytes[i].toString(16).padStart(2, "0");
}
return s;
}
function generateUUIDv4() {
const bytes = getRandomBytes(16);
bytes[6] = bytes[6] & 15 | 64;
bytes[8] = bytes[8] & 191 | 128;
return hex(bytes.subarray(0, 4)) + "-" + hex(bytes.subarray(4, 6)) + "-" + hex(bytes.subarray(6, 8)) + "-" + hex(bytes.subarray(8, 10)) + "-" + hex(bytes.subarray(10, 16));
}
function getRandomValuesWithMathRandom(bytes) {
const max = Math.pow(2, 8 * bytes.byteLength / bytes.length);
for (let i = 0; i < bytes.length; i++) {
bytes[i] = Math.random() * max;
}
}
var getRandomValues = (() => {
if (typeof crypto !== "undefined" && typeof crypto?.getRandomValues !== "undefined") {
return crypto.getRandomValues.bind(crypto);
} else if (typeof msCrypto !== "undefined") {
return msCrypto.getRandomValues.bind(msCrypto);
} else {
return getRandomValuesWithMathRandom;
}
})();
function getRandomBytes(length) {
const bytes = new Uint8Array(length);
getRandomValues(bytes);
return bytes;
}
function convertErrorToJson(err) {
const jsonObj = {};
if (!err) return jsonObj;
try {
Object.getOwnPropertyNames(err).forEach((key) => {
jsonObj[key] = Object.getOwnPropertyDescriptor(err, key);
});
} catch (_) {
return {
error: "failed to serialize the error"
};
}
return jsonObj;
}
function isOnline() {
const nav = typeof navigator !== "undefined" ? navigator : typeof window !== "undefined" && window.navigator ? window.navigator : void 0;
if (!nav) {
console.warn(
"isOnline failed to access window.navigator and assume browser is online"
);
return true;
}
if (typeof nav.onLine !== "boolean") {
return true;
}
return nav.onLine;
}
function addConnectionEventListeners(cb) {
if (typeof window !== "undefined" && window.addEventListener) {
window.addEventListener("offline", cb);
window.addEventListener("online", cb);
}
}
function removeConnectionEventListeners(cb) {
if (typeof window !== "undefined" && window.removeEventListener) {
window.removeEventListener("offline", cb);
window.removeEventListener("online", cb);
}
}
var axiosParamsSerializer = (params) => {
const newParams = [];
for (const k in params) {
if (params[k] === void 0) continue;
if (Array.isArray(params[k]) || typeof params[k] === "object") {
newParams.push(`${k}=${encodeURIComponent(JSON.stringify(params[k]))}`);
} else {
newParams.push(`${k}=${encodeURIComponent(params[k])}`);
}
}
return newParams.join("&");
};
function formatMessage(message) {
const toLocalMessageBase = (msg) => {
if (!msg) return null;
return {
...msg,
created_at: msg.created_at ? new Date(msg.created_at) : /* @__PURE__ */ new Date(),
deleted_at: msg.deleted_at ? new Date(msg.deleted_at) : null,
pinned_at: msg.pinned_at ? new Date(msg.pinned_at) : null,
reaction_groups: maybeGetReactionGroupsFallback(
msg.reaction_groups,
msg.reaction_counts,
msg.reaction_scores
),
status: msg.status || "received",
updated_at: msg.updated_at ? new Date(msg.updated_at) : /* @__PURE__ */ new Date()
};
};
return {
...toLocalMessageBase(message),
error: message.error ?? null,
quoted_message: toLocalMessageBase(message.quoted_message)
};
}
function unformatMessage(message) {
const toMessageResponseBase = (msg) => {
if (!msg) return null;
const newDateString = (/* @__PURE__ */ new Date()).toISOString();
return {
...msg,
created_at: message.created_at ? message.created_at.toISOString() : newDateString,
deleted_at: message.deleted_at ? message.deleted_at.toISOString() : void 0,
pinned_at: message.pinned_at ? message.pinned_at.toISOString() : void 0,
updated_at: message.updated_at ? message.updated_at.toISOString() : newDateString
};
};
return {
...toMessageResponseBase(message),
quoted_message: toMessageResponseBase(message.quoted_message)
};
}
var localMessageToNewMessagePayload = (localMessage) => {
const {
// Remove all timestamp fields and client-specific fields.
// Field pinned_at can therefore be earlier than created_at as new message payload can hold it.
created_at,
updated_at,
deleted_at,
// Client-specific fields
error,
status,
// Reaction related fields
latest_reactions,
own_reactions,
reaction_counts,
reaction_scores,
reply_count,
// Message text related fields that shouldn't be in update
command,
html,
i18n,
quoted_message,
mentioned_users,
// Message content related fields
...messageFields
} = localMessage;
return {
...messageFields,
pinned_at: messageFields.pinned_at?.toISOString(),
mentioned_users: mentioned_users?.map((user) => user.id)
};
};
var toUpdatedMessagePayload = (message) => {
const reservedKeys = {
...RESERVED_UPDATED_MESSAGE_FIELDS,
...LOCAL_MESSAGE_FIELDS
};
const messageFields = Object.fromEntries(
Object.entries(message).filter(
([key]) => !reservedKeys[key]
)
);
return {
...messageFields,
pinned: !!message.pinned_at,
mentioned_users: message.mentioned_users?.map(
(user) => typeof user === "string" ? user : user.id
)
};
};
var toDeletedMessage = ({
message,
deletedAt,
hardDelete = false
}) => {
if (hardDelete) {
return {
attachments: [],
cid: message.cid,
created_at: message.created_at,
deleted_at: deletedAt,
id: message.id,
latest_reactions: [],
mentioned_users: [],
own_reactions: [],
parent_id: message.parent_id,
reply_count: message.reply_count,
status: message.status,
thread_participants: message.thread_participants,
type: "deleted",
updated_at: message.updated_at,
user: message.user
};
} else {
return {
...message,
attachments: [],
type: "deleted",
deleted_at: deletedAt
};
}
};
var deleteUserMessages = ({
messages,
user,
hardDelete = false,
deletedAt
}) => {
for (let i = 0; i < messages.length; i++) {
const message = messages[i];
if (message.user?.id === user.id) {
messages[i] = message.type === "deleted" ? message : toDeletedMessage({ message, hardDelete, deletedAt });
}
if (message.quoted_message?.user?.id === user.id) {
messages[i].quoted_message = message.quoted_message.type === "deleted" ? message.quoted_message : toDeletedMessage({
message: messages[i].quoted_message,
hardDelete,
deletedAt
});
}
}
};
var findIndexInSortedArray = ({
needle,
sortedArray,
selectKey,
selectValueToCompare = (e) => e,
sortDirection = "ascending"
}) => {
if (!sortedArray.length) return 0;
let left = 0;
let right = sortedArray.length - 1;
let middle = 0;
const recalculateMiddle = () => {
middle = Math.round((left + right) / 2);
};
const comparableNeedle = selectValueToCompare(needle);
while (left <= right) {
recalculateMiddle();
const comparableMiddle = selectValueToCompare(sortedArray[middle]);
if (sortDirection === "ascending" && comparableNeedle < comparableMiddle || sortDirection === "descending" && comparableNeedle >= comparableMiddle) {
right = middle - 1;
} else {
left = middle + 1;
}
}
if (selectKey) {
const needleKey = selectKey(needle);
const step = sortDirection === "ascending" ? -1 : 1;
for (let i = left + step; 0 <= i && i < sortedArray.length && selectValueToCompare(sortedArray[i]) === comparableNeedle; i += step) {
if (selectKey(sortedArray[i]) === needleKey) {
return i;
}
}
}
return left;
};
function addToMessageList(messages, newMessage, timestampChanged = false, sortBy = "created_at", addIfDoesNotExist = true) {
const addMessageToList = addIfDoesNotExist || timestampChanged;
let newMessages = [...messages];
if (timestampChanged) {
newMessages = newMessages.filter(
(message) => !(message.id && newMessage.id === message.id)
);
}
if (newMessages.length === 0 && addMessageToList) {
return newMessages.concat(newMessage);
} else if (newMessages.length === 0) {
return newMessages;
}
const messageTime = newMessage[sortBy].getTime();
const messageIsNewest = newMessages.at(-1)[sortBy].getTime() < messageTime;
if (messageIsNewest && addMessageToList) {
return newMessages.concat(newMessage);
} else if (messageIsNewest) {
return newMessages;
}
const insertionIndex = findIndexInSortedArray({
needle: newMessage,
sortedArray: newMessages,
sortDirection: "ascending",
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
selectValueToCompare: (m) => m[sortBy].getTime(),
selectKey: (m) => m.id
});
if (!timestampChanged && newMessage.id && newMessages[insertionIndex] && newMessage.id === newMessages[insertionIndex].id) {
newMessages[insertionIndex] = newMessage;
return newMessages;
}
if (addMessageToList) {
newMessages.splice(insertionIndex, 0, newMessage);
}
return newMessages;
}
function maybeGetReactionGroupsFallback(groups, counts, scores) {
if (groups) {
return groups;
}
if (counts && scores) {
const fallback = {};
for (const type of Object.keys(counts)) {
fallback[type] = {
count: counts[type],
sum_scores: scores[type]
};
}
return fallback;
}
return null;
}
var debounce = (fn, timeout = 0, { leading = false, trailing = true } = {}) => {
let runningTimeout = null;
let argsForTrailingExecution = null;
let lastResult;
const debouncedFn = (...args) => {
if (runningTimeout) {
clearTimeout(runningTimeout);
} else if (leading) {
lastResult = fn(...args);
}
if (trailing) argsForTrailingExecution = args;
const timeoutHandler = () => {
if (argsForTrailingExecution) {
lastResult = fn(...argsForTrailingExecution);
argsForTrailingExecution = null;
}
runningTimeout = null;
};
runningTimeout = setTimeout(timeoutHandler, timeout);
return lastResult;
};
debouncedFn.cancel = () => {
if (runningTimeout) clearTimeout(runningTimeout);
};
debouncedFn.flush = () => {
if (runningTimeout) {
clearTimeout(runningTimeout);
runningTimeout = null;
if (argsForTrailingExecution) {
lastResult = fn(...argsForTrailingExecution);
}
}
return lastResult;
};
return debouncedFn;
};
var throttle = (fn, timeout = 200, { leading = true, trailing = false } = {}) => {
let runningTimeout = null;
let storedArgs = null;
return (...args) => {
if (runningTimeout) {
if (trailing) storedArgs = args;
return;
}
if (leading) fn(...args);
const timeoutHandler = () => {
if (storedArgs) {
fn(...storedArgs);
storedArgs = null;
runningTimeout = setTimeout(timeoutHandler, timeout);
return;
}
runningTimeout = null;
};
runningTimeout = setTimeout(timeoutHandler, timeout);
};
};
var get = (obj, path) => path.split(".").reduce((acc, key) => {
if (acc && typeof acc === "object" && key in acc) {
return acc[key];
}
return void 0;
}, obj);
var uniqBy = (array, iteratee) => {
if (!Array.isArray(array)) return [];
const seen = /* @__PURE__ */ new Set();
return array.filter((item) => {
const key = typeof iteratee === "function" ? iteratee(item) : get(item, iteratee);
if (seen.has(key)) return false;
seen.add(key);
return true;
});
};
function binarySearchByDateEqualOrNearestGreater(array, targetDate) {
let left = 0;
let right = array.length - 1;
while (left <= right) {
const mid = Math.floor((left + right) / 2);
const midCreatedAt = array[mid].created_at;
if (!midCreatedAt) {
left += 1;
continue;
}
const midDate = new Date(midCreatedAt);
if (midDate.getTime() === targetDate.getTime()) {
return mid;
} else if (midDate.getTime() < targetDate.getTime()) {
left = mid + 1;
} else {
right = mid - 1;
}
}
return left;
}
var messagePaginationCreatedAtAround = ({
parentSet,
requestedPageSize,
returnedPage,
messagePaginationOptions
}) => {
const newPagination = { ...parentSet.pagination };
if (!messagePaginationOptions?.created_at_around) return newPagination;
let hasPrev;
let hasNext;
let updateHasPrev;
let updateHasNext;
const createdAtAroundDate = new Date(messagePaginationOptions.created_at_around);
const [firstPageMsg, lastPageMsg] = [returnedPage[0], returnedPage.slice(-1)[0]];
const wholePageHasNewerMessages = !!firstPageMsg?.created_at && new Date(firstPageMsg.created_at) > createdAtAroundDate;
const wholePageHasOlderMessages = !!lastPageMsg?.created_at && new Date(lastPageMsg.created_at) < createdAtAroundDate;
const requestedPageSizeNotMet = requestedPageSize > parentSet.messages.length && requestedPageSize > returnedPage.length;
const noMoreMessages = (requestedPageSize > parentSet.messages.length || parentSet.messages.length >= returnedPage.length) && requestedPageSize > returnedPage.length;
if (wholePageHasNewerMessages) {
hasPrev = false;
updateHasPrev = true;
if (requestedPageSizeNotMet) {
hasNext = false;
updateHasNext = true;
}
} else if (wholePageHasOlderMessages) {
hasNext = false;
updateHasNext = true;
if (requestedPageSizeNotMet) {
hasPrev = false;
updateHasPrev = true;
}
} else if (noMoreMessages) {
hasNext = hasPrev = false;
updateHasPrev = updateHasNext = true;
} else {
const [firstPageMsgIsFirstInSet, lastPageMsgIsLastInSet] = [
firstPageMsg?.id && firstPageMsg.id === parentSet.messages[0]?.id,
lastPageMsg?.id && lastPageMsg.id === parentSet.messages.slice(-1)[0]?.id
];
updateHasPrev = firstPageMsgIsFirstInSet;
updateHasNext = lastPageMsgIsLastInSet;
const midPointByCount = Math.floor(returnedPage.length / 2);
const midPointByCreationDate = binarySearchByDateEqualOrNearestGreater(
returnedPage,
createdAtAroundDate
);
if (midPointByCreationDate !== -1) {
hasPrev = midPointByCount <= midPointByCreationDate;
hasNext = midPointByCount >= midPointByCreationDate;
}
}
if (updateHasPrev && typeof hasPrev !== "undefined") newPagination.hasPrev = hasPrev;
if (updateHasNext && typeof hasNext !== "undefined") newPagination.hasNext = hasNext;
return newPagination;
};
var messagePaginationIdAround = ({
parentSet,
requestedPageSize,
returnedPage,
messagePaginationOptions
}) => {
const newPagination = { ...parentSet.pagination };
const { id_around } = messagePaginationOptions || {};
if (!id_around) return newPagination;
let hasPrev;
let hasNext;
const [firstPageMsg, lastPageMsg] = [returnedPage[0], returnedPage.slice(-1)[0]];
const [firstPageMsgIsFirstInSet, lastPageMsgIsLastInSet] = [
firstPageMsg?.id === parentSet.messages[0]?.id,
lastPageMsg?.id === parentSet.messages.slice(-1)[0]?.id
];
let updateHasPrev = firstPageMsgIsFirstInSet;
let updateHasNext = lastPageMsgIsLastInSet;
const midPoint = Math.floor(returnedPage.length / 2);
const noMoreMessages = (requestedPageSize > parentSet.messages.length || parentSet.messages.length >= returnedPage.length) && requestedPageSize > returnedPage.length;
if (noMoreMessages) {
hasNext = hasPrev = false;
updateHasPrev = updateHasNext = true;
} else if (!returnedPage[midPoint]) {
return newPagination;
} else if (returnedPage[midPoint].id === id_around) {
hasPrev = hasNext = true;
} else {
let targetMsg;
const halves = [returnedPage.slice(0, midPoint), returnedPage.slice(midPoint)];
hasPrev = hasNext = true;
for (let i = 0; i < halves.length; i++) {
targetMsg = halves[i].find((message) => message.id === id_around);
if (targetMsg && i === 0) {
hasPrev = false;
}
if (targetMsg && i === 1) {
hasNext = false;
}
}
}
if (updateHasPrev && typeof hasPrev !== "undefined") newPagination.hasPrev = hasPrev;
if (updateHasNext && typeof hasNext !== "undefined") newPagination.hasNext = hasNext;
return newPagination;
};
var messagePaginationLinear = ({
parentSet,
requestedPageSize,
returnedPage,
messagePaginationOptions
}) => {
const newPagination = { ...parentSet.pagination };
let hasPrev;
let hasNext;
const [firstPageMsg, lastPageMsg] = [returnedPage[0], returnedPage.slice(-1)[0]];
const [firstPageMsgIsFirstInSet, lastPageMsgIsLastInSet] = [
firstPageMsg?.id && firstPageMsg.id === parentSet.messages[0]?.id,
lastPageMsg?.id && lastPageMsg.id === parentSet.messages.slice(-1)[0]?.id
];
const queriedNextMessages = messagePaginationOptions && (messagePaginationOptions.created_at_after_or_equal || messagePaginationOptions.created_at_after || messagePaginationOptions.id_gt || messagePaginationOptions.id_gte);
const queriedPrevMessages = typeof messagePaginationOptions === "undefined" ? true : messagePaginationOptions.created_at_before_or_equal || messagePaginationOptions.created_at_before || messagePaginationOptions.id_lt || messagePaginationOptions.id_lte || messagePaginationOptions.offset;
const containsUnrecognizedOptionsOnly = !queriedNextMessages && !queriedPrevMessages && !messagePaginationOptions?.id_around && !messagePaginationOptions?.created_at_around;
const hasMore = returnedPage.length >= requestedPageSize;
if (typeof queriedPrevMessages !== "undefined" || containsUnrecognizedOptionsOnly) {
hasPrev = hasMore;
}
if (typeof queriedNextMessages !== "undefined") {
hasNext = hasMore;
}
const returnedPageIsEmpty = returnedPage.length === 0;
if ((firstPageMsgIsFirstInSet || returnedPageIsEmpty) && typeof hasPrev !== "undefined")
newPagination.hasPrev = hasPrev;
if ((lastPageMsgIsLastInSet || returnedPageIsEmpty) && typeof hasNext !== "undefined")
newPagination.hasNext = hasNext;
return newPagination;
};
var messageSetPagination = (params) => {
const messagesFilteredLocally = params.returnedPage.filter(({ shadowed }) => shadowed);
if (params.parentSet.messages.length + messagesFilteredLocally.length < params.returnedPage.length) {
params.logger?.(
"error",
"Corrupted message set state: parent set size < returned page size"
);
return params.parentSet.pagination;
}
if (params.messagePaginationOptions?.created_at_around) {
return messagePaginationCreatedAtAround(params);
} else if (params.messagePaginationOptions?.id_around) {
return messagePaginationIdAround(params);
} else {
return messagePaginationLinear(params);
}
};
var WATCH_QUERY_IN_PROGRESS_FOR_CHANNEL = {};
var getAndWatchChannel = async ({
channel,
client,
id,
members,
options,
type
}) => {
if (!channel && !type) {
throw new Error("Channel or channel type have to be provided to query a channel.");
}
const channelToWatch = channel || client.channel(type, id, { members });
const originalCid = channelToWatch.id ? channelToWatch.cid : members && members.length ? generateChannelTempCid(channelToWatch.type, members) : void 0;
if (!originalCid) {
throw new Error(
"Channel ID or channel members array have to be provided to query a channel."
);
}
const queryPromise = WATCH_QUERY_IN_PROGRESS_FOR_CHANNEL[originalCid];
if (queryPromise) {
await queryPromise;
} else {
try {
WATCH_QUERY_IN_PROGRESS_FOR_CHANNEL[originalCid] = channelToWatch.watch(options);
await WATCH_QUERY_IN_PROGRESS_FOR_CHANNEL[originalCid];
} finally {
delete WATCH_QUERY_IN_PROGRESS_FOR_CHANNEL[originalCid];
}
}
return channelToWatch;
};
var generateChannelTempCid = (channelType, members) => {
if (!members) return;
const membersStr = [...members].sort().join(",");
if (!membersStr) return;
return `${channelType}:!members-${membersStr}`;
};
var isChannelPinned = (channel) => {
if (!channel) return false;
const member = channel.state.membership;
return !!member?.pinned_at;
};
var isChannelArchived = (channel) => {
if (!channel) return false;
const member = channel.state.membership;
return !!member?.archived_at;
};
var shouldConsiderArchivedChannels = (filters) => {
if (!filters) return false;
return typeof filters.archived === "boolean";
};
var extractSortValue = ({
atIndex,
sort,
targetKey
}) => {
if (!sort) return null;
let option = null;
if (Array.isArray(sort)) {
option = sort[atIndex] ?? null;
} else {
let index = 0;
for (const key in sort) {
if (index !== atIndex) {
index++;
continue;
}
if (key !== targetKey) {
return null;
}
option = sort;
break;
}
}
return option?.[targetKey] ?? null;
};
var shouldConsiderPinnedChannels = (sort) => {
const value = findPinnedAtSortOrder({ sort });
if (typeof value !== "number") return false;
return Math.abs(value) === 1;
};
var findPinnedAtSortOrder = ({ sort }) => extractSortValue({
atIndex: 0,
sort,
targetKey: "pinned_at"
});
var findLastPinnedChannelIndex = ({ channels }) => {
let lastPinnedChannelIndex = null;
for (const channel of channels) {
if (!isChannelPinned(channel)) break;
if (typeof lastPinnedChannelIndex === "number") {
lastPinnedChannelIndex++;
} else {
lastPinnedChannelIndex = 0;
}
}
return lastPinnedChannelIndex;
};
var promoteChannel = ({
channels,
channelToMove,
channelToMoveIndexWithinChannels,
sort
}) => {
const targetChannelIndex = channelToMoveIndexWithinChannels ?? channels.findIndex((channel) => channel.cid === channelToMove.cid);
const targetChannelExistsWithinList = targetChannelIndex >= 0;
const targetChannelAlreadyAtTheTop = targetChannelIndex === 0;
const considerPinnedChannels = shouldConsiderPinnedChannels(sort);
const isTargetChannelPinned = isChannelPinned(channelToMove);
if (targetChannelAlreadyAtTheTop || considerPinnedChannels && isTargetChannelPinned) {
return channels;
}
const newChannels = [...channels];
if (targetChannelExistsWithinList) {
newChannels.splice(targetChannelIndex, 1);
}
let lastPinnedChannelIndex = null;
if (considerPinnedChannels) {
lastPinnedChannelIndex = findLastPinnedChannelIndex({ channels: newChannels });
}
newChannels.splice(
typeof lastPinnedChannelIndex === "number" ? lastPinnedChannelIndex + 1 : 0,
0,
channelToMove
);
return newChannels;
};
var isDate = (value) => !!value.getTime;
var isLocalMessage = (message) => isDate(message.created_at);
var runDetached = (callback, options) => {
const { context, onSuccessCallback = () => void 0, onErrorCallback } = options ?? {};
const defaultOnError = (error) => {
console.log(`An error has occurred in context ${context}: ${error}`);
};
const onError = onErrorCallback ?? defaultOnError;
let promise = callback;
if (onSuccessCallback) {
promise = promise.then(onSuccessCallback);
}
promise.catch(onError);
};
var isBlockedMessage = (message) => message.type === "error" && (message.moderation_details?.action === "MESSAGE_RESPONSE_ACTION_REMOVE" || message.moderation?.action === "remove");
// src/channel_state.ts
var messageSetBounds = (a, b) => ({
newestMessageA: new Date(a[0]?.created_at ?? 0),
oldestMessageA: new Date(a.slice(-1)[0]?.created_at ?? 0),
newestMessageB: new Date(b[0]?.created_at ?? 0),
oldestMessageB: new Date(b.slice(-1)[0]?.created_at ?? 0)
});
var aContainsOrEqualsB = (a, b) => {
const { newestMessageA, newestMessageB, oldestMessageA, oldestMessageB } = messageSetBounds(a, b);
return newestMessageA >= newestMessageB && oldestMessageB >= oldestMessageA;
};
var aOverlapsB = (a, b) => {
const { newestMessageA, newestMessageB, oldestMessageA, oldestMessageB } = messageSetBounds(a, b);
return oldestMessageA < oldestMessageB && oldestMessageB < newestMessageA && newestMessageA < newestMessageB;
};
var messageSetsOverlapByTimestamp = (a, b) => aContainsOrEqualsB(a, b) || aContainsOrEqualsB(b, a) || aOverlapsB(a, b) || aOverlapsB(b, a);
var ChannelState = class {
constructor(channel) {
/**
* Disjoint lists of messages
* Users can jump in the message list (with searching) and this can result in disjoint lists of messages
* The state manages these lists and merges them when lists overlap
* The messages array contains the currently active set
*/
this.messageSets = [];
/**
* Takes the message object, parses the dates, sets `__html`
* and sets the status to `received` if missing; returns a new message object.
*
* @param {MessageResponse} message `MessageResponse` object
*/
this.formatMessage = (message) => formatMessage(message);
/**
* Setter for isUpToDate.
*
* @param isUpToDate Flag which indicates if channel state contain latest/recent messages or no.
* This flag should be managed by UI sdks using a setter - setIsUpToDate.
* When false, any new message (received by websocket event - message.new) will not
* be pushed on to message list.
*/
this.setIsUpToDate = (isUpToDate) => {
this.isUpToDate = isUpToDate;
};
this.removeMessageFromArray = (msgArray, msg) => {
const result = msgArray.filter(
(message) => !(!!message.id && !!msg.id && message.id === msg.id)
);
return { removed: result.length < msgArray.length, result };
};
/**
* Updates the message.user property with updated user object, for messages.
*
* @param {UserResponse} user
*/
this.updateUserMessages = (user) => {
const _updateUserMessages = (messages, user2) => {
for (let i = 0; i < messages.length; i++) {
const m = messages[i];
if (m.user?.id === user2.id) {
messages[i] = { ...m, user: user2 };
}
}
};
this.messageSets.forEach((set) => _updateUserMessages(set.messages, user));
for (const parentId in this.threads) {
_updateUserMessages(this.threads[parentId], user);
}
_updateUserMessages(this.pinnedMessages, user);
};
/**
* Marks the messages as deleted, from deleted user.
*
* @param {UserResponse} user
* @param {boolean} hardDelete
*/
this.deleteUserMessages = (user, hardDelete = false, deletedAt) => {
this.messageSets.forEach(
({ messages }) => deleteUserMessages({ messages, user, hardDelete, deletedAt: deletedAt ?? null })
);
for (const parentId in this.threads) {
deleteUserMessages({
messages: this.threads[parentId],
user,
hardDelete,
deletedAt: deletedAt ?? null
});
}
deleteUserMessages({
messages: this.pinnedMessages,
user,
hardDelete,
deletedAt: deletedAt ?? null
});
};
/**
* Identifies the set index into which a message set would pertain if its first item's creation date corresponded to oldestTimestampMs.
* @param oldestTimestampMs
*/
this.findMessageSetByOldestTimestamp = (oldestTimestampMs) => {
let lo = 0, hi = this.messageSets.length;
while (lo < hi) {
const mid = lo + hi >>> 1;
const msgSet = this.messageSets[mid];
if (msgSet.messages.length === 0) return -1;
const oldestMessageTimestampInSet = msgSet.messages[0].created_at.getTime();
if (oldestMessageTimestampInSet <= oldestTimestampMs) hi = mid;
else lo = mid + 1;
}
return lo;
};
this._channel = channel;
this.watcher_count = 0;
this.typing = {};
this.read = {};
this.initMessages();
this.pinnedMessages = [];
this.pending_messages = [];
this.threads = {};
this.mutedUsers = [];
this.watchers = {};
this.members = {};
this.membership = {};
this.unreadCount = 0;
this.isUpToDate = true;
this.last_message_at = channel?.state?.last_message_at != null ? new Date(channel.state.last_message_at) : null;
}
get messages() {
return this.messageSets.find((s) => s.isCurrent)?.messages || [];
}
set messages(messages) {
const index = this.messageSets.findIndex((s) => s.isCurrent);
this.messageSets[index].messages = messages;
}
/**
* The list of latest messages
* The messages array not always contains the latest messages (for example if a user searched for an earlier message, that is in a different message set)
*/
get latestMessages() {
return this.messageSets.find((s) => s.isLatest)?.messages || [];
}
set latestMessages(messages) {
const index = this.messageSets.findIndex((s) => s.isLatest);
this.messageSets[index].messages = messages;
}
get messagePagination() {
return this.messageSets.find((s) => s.isCurrent)?.pagination || DEFAULT_MESSAGE_SET_PAGINATION;
}
pruneOldest(maxMessages) {
const currentIndex = this.messageSets.findIndex((s) => s.isCurrent);
if (this.messageSets[currentIndex].isLatest) {
const newMessages = this.messageSets[currentIndex].messages;
this.messageSets[currentIndex].messages = newMessages.slice(-maxMessages);
this.messageSets[currentIndex].pagination.hasPrev = true;
}
}
/**
* addMessageSorted - Add a message to the state
*
* @param {MessageResponse} newMessage A new message
* @param {boolean} timestampChanged Whether updating a message with changed created_at value.
* @param {boolean} addIfDoesNotExist Add message if it is not in the list, used to prevent out of order updated messages from being added.
* @param {MessageSetType} messageSetToAddToIfDoesNotExist Which message set to add to if message is not in the list (only used if addIfDoesNotExist is true)
*/
addMessageSorted(newMessage, timestampChanged = false, addIfDoesNotExist = true, messageSetToAddToIfDoesNotExist = "latest") {
return this.addMessagesSorted(
[newMessage],
timestampChanged,
false,
addIfDoesNotExist,
messageSetToAddToIfDoesNotExist
);
}
/**
* addMessagesSorted - Add the list of messages to state and resorts the messages
*
* @param {Array<MessageResponse>} newMessages A list of messages
* @param {boolean} timestampChanged Whether updating messages with changed created_at value.
* @param {boolean} initializing Whether channel is being initialized.
* @param {boolean} addIfDoesNotExist Add message if it is not in the list, used to prevent out of order updated messages from being added.
* @param {MessageSetType} messageSetToAddToIfDoesNotExist Which message set to add to if messages are not in the list (only used if addIfDoesNotExist is true)
*
*/
addMessagesSorted(newMessages, timestampChanged = false, initializing = false, addIfDoesNotExist = true, messageSetToAddToIfDoesNotExist = "current") {
const { messagesToAdd, targetMessageSetIndex } = this.findTargetMessageSet(
newMessages,
addIfDoesNotExist,
messageSetToAddToIfDoesNotExist
);
for (let i = 0; i < messagesToAdd.length; i += 1) {
const isFromShadowBannedUser = messagesTo