stream-chat
Version:
JS SDK for the Stream Chat API
4 lines • 1.28 MB
Source Map (JSON)
{
"version": 3,
"sources": ["../../src/base64.ts", "../../src/campaign.ts", "../../src/client.ts", "../../src/utils.ts", "../../src/constants.ts", "../../src/channel_state.ts", "../../src/messageComposer/attachmentIdentity.ts", "../../src/messageComposer/fileUtils.ts", "../../src/messageComposer/middleware/attachmentManager/postUpload/attachmentEnrichment.ts", "../../src/utils/concurrency.ts", "../../src/middleware.ts", "../../src/messageComposer/middleware/attachmentManager/postUpload/uploadErrorHandler.ts", "../../src/messageComposer/middleware/attachmentManager/postUpload/AttachmentPostUploadMiddlewareExecutor.ts", "../../src/messageComposer/middleware/attachmentManager/preUpload/serverUploadConfigCheck.ts", "../../src/messageComposer/middleware/attachmentManager/preUpload/blockedUploadNotification.ts", "../../src/messageComposer/middleware/attachmentManager/preUpload/AttachmentPreUploadMiddlewareExecutor.ts", "../../src/store.ts", "../../src/utils/mergeWith/mergeWithCore.ts", "../../src/utils/mergeWith/mergeWith.ts", "../../src/utils/mergeWith/mergeWithDiff.ts", "../../src/messageComposer/attachmentManager.ts", "../../src/messageComposer/configuration/configuration.ts", "../../src/messageComposer/CustomDataManager.ts", "../../src/messageComposer/linkPreviewsManager.ts", "../../src/messageComposer/LocationComposer.ts", "../../src/messageComposer/middleware/pollComposer/state.ts", "../../src/messageComposer/middleware/pollComposer/composition.ts", "../../src/messageComposer/middleware/pollComposer/PollComposerMiddlewareExecutor.ts", "../../src/types.ts", "../../src/messageComposer/pollComposer.ts", "../../src/messageComposer/middleware/messageComposer/attachments.ts", "../../src/messageComposer/middleware/messageComposer/cleanData.ts", "../../src/messageComposer/middleware/messageComposer/customData.ts", "../../src/messageComposer/middleware/messageComposer/compositionValidation.ts", "../../src/messageComposer/middleware/messageComposer/linkPreviews.ts", "../../src/messageComposer/middleware/messageComposer/textComposer.ts", "../../src/messageComposer/middleware/messageComposer/messageComposerState.ts", "../../src/messageComposer/middleware/messageComposer/userDataInjection.ts", "../../src/messageComposer/middleware/messageComposer/pollOnly.ts", "../../src/messageComposer/middleware/messageComposer/sharedLocation.ts", "../../src/messageComposer/middleware/messageComposer/MessageComposerMiddlewareExecutor.ts", "../../src/messageComposer/middleware/messageComposer/commandInjection.ts", "../../src/messageComposer/middleware/textComposer/activeCommandGuard.ts", "../../src/errors.ts", "../../src/search/BaseSearchSource.ts", "../../src/search/SearchController.ts", "../../src/pagination/BasePaginator.ts", "../../src/pagination/FilterBuilder.ts", "../../src/pagination/ReminderPaginator.ts", "../../src/search/UserSearchSource.ts", "../../src/search/ChannelSearchSource.ts", "../../src/search/MessageSearchSource.ts", "../../src/messageComposer/middleware/textComposer/textMiddlewareUtils.ts", "../../src/messageComposer/middleware/textComposer/commands.ts", "../../src/messageComposer/middleware/textComposer/commandStringExtraction.ts", "../../src/messageComposer/middleware/textComposer/mentions.ts", "../../src/messageComposer/middleware/textComposer/validation.ts", "../../src/messageComposer/middleware/textComposer/TextComposerMiddlewareExecutor.ts", "../../src/messageComposer/textComposer.ts", "../../src/utils/WithSubscriptions.ts", "../../src/thread.ts", "../../src/messageComposer/messageComposer.ts", "../../src/messageDelivery/MessageDeliveryReporter.ts", "../../src/messageDelivery/MessageReceiptsTracker.ts", "../../src/channel.ts", "../../src/client_state.ts", "../../src/connection.ts", "../../src/insights.ts", "../../src/signing.ts", "../../src/token_manager.ts", "../../src/connection_fallback.ts", "../../src/segment.ts", "../../src/moderation.ts", "../../src/thread_manager.ts", "../../src/poll.ts", "../../src/poll_manager.ts", "../../src/channel_manager.ts", "../../src/notifications/configuration.ts", "../../src/notifications/NotificationManager.ts", "../../src/reminders/ReminderTimer.ts", "../../src/reminders/Reminder.ts", "../../src/reminders/ReminderManager.ts", "../../src/events.ts", "../../src/permissions.ts", "../../src/offline-support/types.ts", "../../src/offline-support/offline_sync_manager.ts", "../../src/offline-support/offline_support_api.ts", "../../src/LiveLocationManager.ts", "../../src/utils/FixedSizeQueueCache.ts"],
"sourcesContent": ["import { fromByteArray } from 'base64-js';\n\nfunction isString<T>(arrayOrString: string | T[]): arrayOrString is string {\n return typeof (arrayOrString as string) === 'string';\n}\n\ntype MapGenericCallback<T, U> = (value: T, index: number, array: T[]) => U;\ntype MapStringCallback<U> = (value: string, index: number, string: string) => U;\n\nfunction isMapStringCallback<T, U>(\n arrayOrString: string | T[],\n callback: MapGenericCallback<T, U> | MapStringCallback<U>,\n): callback is MapStringCallback<U> {\n return !!callback && isString(arrayOrString);\n}\n\n// source - https://github.com/beatgammit/base64-js/blob/master/test/convert.js#L72\nfunction map<T, U>(array: T[], callback: MapGenericCallback<T, U>): U[];\nfunction map<U>(string: string, callback: MapStringCallback<U>): U[];\nfunction map<T, U>(\n arrayOrString: string | T[],\n callback: MapGenericCallback<T, U> | MapStringCallback<U>,\n): U[] {\n const res = [];\n\n if (isString(arrayOrString) && isMapStringCallback(arrayOrString, callback)) {\n for (let k = 0, len = arrayOrString.length; k < len; k++) {\n if (arrayOrString.charAt(k)) {\n const kValue = arrayOrString.charAt(k);\n const mappedValue = callback(kValue, k, arrayOrString);\n res[k] = mappedValue;\n }\n }\n } else if (!isString(arrayOrString) && !isMapStringCallback(arrayOrString, callback)) {\n for (let k = 0, len = arrayOrString.length; k < len; k++) {\n if (k in arrayOrString) {\n const kValue = arrayOrString[k];\n const mappedValue = callback(kValue, k, arrayOrString);\n res[k] = mappedValue;\n }\n }\n }\n\n return res;\n}\n\nexport const encodeBase64 = (data: string): string =>\n fromByteArray(new Uint8Array(map(data, (char) => char.charCodeAt(0))));\n\n// base-64 decoder throws exception if encoded string is not padded by '=' to make string length\n// in multiples of 4. So gonna use our own method for this purpose to keep backwards compatibility\n// https://github.com/beatgammit/base64-js/blob/master/index.js#L26\nexport const decodeBase64 = (s: string): string => {\n const e = {} as { [key: string]: number },\n w = String.fromCharCode,\n L = s.length;\n let i,\n b = 0,\n c,\n x,\n l = 0,\n a,\n r = '';\n const A = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';\n for (i = 0; i < 64; i++) {\n e[A.charAt(i)] = i;\n }\n for (x = 0; x < L; x++) {\n c = e[s.charAt(x)];\n b = (b << 6) + c;\n l += 6;\n while (l >= 8) {\n // eslint-disable-next-line @typescript-eslint/no-unused-expressions\n ((a = (b >>> (l -= 8)) & 0xff) || x < L - 2) && (r += w(a));\n }\n }\n return r;\n};\n", "import type { StreamChat } from './client';\nimport type { CampaignData, GetCampaignOptions } from './types';\n\nexport class Campaign {\n id: string | null;\n data?: CampaignData;\n client: StreamChat;\n\n constructor(client: StreamChat, id: string | null, data?: CampaignData) {\n this.client = client;\n this.id = id;\n this.data = data;\n }\n\n async create() {\n const body = {\n id: this.id,\n message_template: this.data?.message_template,\n segment_ids: this.data?.segment_ids,\n sender_id: this.data?.sender_id,\n sender_mode: this.data?.sender_mode,\n sender_visibility: this.data?.sender_visibility,\n channel_template: this.data?.channel_template,\n create_channels: this.data?.create_channels,\n show_channels: this.data?.show_channels,\n description: this.data?.description,\n name: this.data?.name,\n skip_push: this.data?.skip_push,\n skip_webhook: this.data?.skip_webhook,\n user_ids: this.data?.user_ids,\n };\n\n const result = await this.client.createCampaign(body);\n\n this.id = result.campaign.id;\n this.data = result.campaign;\n return result;\n }\n\n verifyCampaignId() {\n if (!this.id) {\n throw new Error(\n 'Campaign id is missing. Either create the campaign using campaign.create() or set the id during instantiation - const campaign = client.campaign(id)',\n );\n }\n }\n\n async start(options?: { scheduledFor?: string; stopAt?: string }) {\n this.verifyCampaignId();\n\n return await this.client.startCampaign(this.id as string, options);\n }\n\n update(data: Partial<CampaignData>) {\n this.verifyCampaignId();\n\n return this.client.updateCampaign(this.id as string, data);\n }\n\n async delete() {\n this.verifyCampaignId();\n\n return await this.client.deleteCampaign(this.id as string);\n }\n\n stop() {\n this.verifyCampaignId();\n\n return this.client.stopCampaign(this.id as string);\n }\n\n get(options?: GetCampaignOptions) {\n this.verifyCampaignId();\n\n return this.client.getCampaign(this.id as string, options);\n }\n}\n", "/* eslint no-unused-vars: \"off\" */\n/* global process */\n\nimport type { AxiosError, AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios';\nimport axios from 'axios';\nimport https from 'https';\nimport type WebSocket from 'isomorphic-ws';\n\nimport { Channel } from './channel';\nimport { ClientState } from './client_state';\nimport { StableWSConnection } from './connection';\nimport { CheckSignature, DevToken, JWTUserToken } from './signing';\nimport { TokenManager } from './token_manager';\nimport { WSConnectionFallback } from './connection_fallback';\nimport { Campaign } from './campaign';\nimport { Segment } from './segment';\nimport { isErrorResponse, isWSFailure } from './errors';\nimport {\n addFileToFormData,\n axiosParamsSerializer,\n chatCodes,\n generateChannelTempCid,\n isFunction,\n isOnline,\n isOwnUserBaseProperty,\n messageSetPagination,\n normalizeQuerySort,\n randomId,\n retryInterval,\n sleep,\n toUpdatedMessagePayload,\n} from './utils';\n\nimport type {\n ActiveLiveLocationsAPIResponse,\n APIErrorResponse,\n APIResponse,\n AppSettings,\n AppSettingsAPIResponse,\n BannedUsersFilters,\n BannedUsersPaginationOptions,\n BannedUsersResponse,\n BannedUsersSort,\n BanUserOptions,\n BaseDeviceFields,\n BlockList,\n BlockListResponse,\n BlockUserAPIResponse,\n CampaignData,\n CampaignFilters,\n CampaignQueryOptions,\n CampaignResponse,\n CampaignSort,\n CastVoteAPIResponse,\n ChannelAPIResponse,\n ChannelData,\n ChannelFilters,\n ChannelMute,\n ChannelOptions,\n ChannelResponse,\n ChannelSort,\n ChannelStateOptions,\n CheckPushResponse,\n CheckSNSResponse,\n CheckSQSResponse,\n Configs,\n ConnectAPIResponse,\n CreateChannelOptions,\n CreateChannelResponse,\n CreateCommandOptions,\n CreateCommandResponse,\n CreateImportOptions,\n CreateImportResponse,\n CreateImportURLResponse,\n CreatePollAPIResponse,\n CreatePollData,\n CreatePollOptionAPIResponse,\n CreateReminderOptions,\n CustomPermissionOptions,\n DeactivateUsersOptions,\n DeleteChannelsResponse,\n DeleteCommandResponse,\n DeleteMessageOptions,\n DeleteUserOptions,\n Device,\n DeviceIdentifier,\n DraftFilters,\n DraftSort,\n EndpointName,\n Event,\n EventAPIResponse,\n EventHandler,\n ExportChannelOptions,\n ExportChannelRequest,\n ExportChannelResponse,\n ExportChannelStatusResponse,\n ExportUsersRequest,\n ExportUsersResponse,\n FlagMessageResponse,\n FlagReportsFilters,\n FlagReportsPaginationOptions,\n FlagReportsResponse,\n FlagsFilters,\n FlagsPaginationOptions,\n FlagsResponse,\n FlagUserResponse,\n GetBlockedUsersAPIResponse,\n GetCampaignOptions,\n GetChannelTypeResponse,\n GetCommandResponse,\n GetHookEventsResponse,\n GetImportResponse,\n GetMessageAPIResponse,\n GetMessageOptions,\n GetPollAPIResponse,\n GetPollOptionAPIResponse,\n GetRateLimitsResponse,\n GetThreadAPIResponse,\n GetThreadOptions,\n GetUnreadCountAPIResponse,\n GetUnreadCountBatchAPIResponse,\n ListChannelResponse,\n ListCommandsResponse,\n ListImportsPaginationOptions,\n ListImportsResponse,\n LocalMessage,\n Logger,\n MarkChannelsReadOptions,\n MarkDeliveredOptions,\n MessageFilters,\n MessageFlagsFilters,\n MessageFlagsPaginationOptions,\n MessageFlagsResponse,\n MessageResponse,\n Mute,\n MuteUserOptions,\n MuteUserResponse,\n NewMemberPayload,\n OGAttachment,\n OwnUserResponse,\n Pager,\n PartialMessageUpdate,\n PartialPollUpdate,\n PartialThreadUpdate,\n PartialUserUpdate,\n PermissionAPIResponse,\n PermissionsAPIResponse,\n PollAnswersAPIResponse,\n PollData,\n PollOptionData,\n PollSort,\n PollVote,\n PollVoteData,\n PollVotesAPIResponse,\n Product,\n PushPreference,\n PushProvider,\n PushProviderConfig,\n PushProviderID,\n PushProviderListResponse,\n PushProviderUpsertResponse,\n QueryChannelsAPIResponse,\n QueryDraftsResponse,\n QueryMessageHistoryFilters,\n QueryMessageHistoryOptions,\n QueryMessageHistoryResponse,\n QueryMessageHistorySort,\n QueryPollsFilters,\n QueryPollsOptions,\n QueryPollsResponse,\n QueryReactionsAPIResponse,\n QueryReactionsOptions,\n QueryRemindersOptions,\n QueryRemindersResponse,\n QuerySegmentsOptions,\n QuerySegmentTargetsFilter,\n QueryThreadsAPIResponse,\n QueryThreadsOptions,\n QueryVotesFilters,\n QueryVotesOptions,\n ReactionFilters,\n ReactionResponse,\n ReactionSort,\n ReactivateUserOptions,\n ReactivateUsersOptions,\n ReminderAPIResponse,\n ReviewFlagReportOptions,\n ReviewFlagReportResponse,\n SdkIdentifier,\n SearchAPIResponse,\n SearchMessageSortBase,\n SearchOptions,\n SearchPayload,\n SegmentData,\n SegmentResponse,\n SegmentTargetsResponse,\n SegmentType,\n SendFileAPIResponse,\n SharedLocationResponse,\n SortParam,\n StreamChatOptions,\n SyncOptions,\n SyncResponse,\n TaskResponse,\n TaskStatus,\n TestPushDataInput,\n TestSNSDataInput,\n TestSQSDataInput,\n TokenOrProvider,\n TranslateResponse,\n UnBanUserOptions,\n UpdateChannelTypeRequest,\n UpdateChannelTypeResponse,\n UpdateCommandOptions,\n UpdateCommandResponse,\n UpdateLocationPayload,\n UpdateMessageAPIResponse,\n UpdateMessageOptions,\n UpdatePollAPIResponse,\n UpdatePollOptionAPIResponse,\n UpdateReminderOptions,\n UpdateSegmentData,\n UpdateUsersAPIResponse,\n UpsertPushPreferencesResponse,\n UserCustomEvent,\n UserFilters,\n UserOptions,\n UserResponse,\n UserSort,\n VoteSort,\n} from './types';\nimport { ErrorFromResponse } from './types';\nimport { InsightMetrics, postInsights } from './insights';\nimport { Thread } from './thread';\nimport { Moderation } from './moderation';\nimport { ThreadManager } from './thread_manager';\nimport { DEFAULT_QUERY_CHANNELS_MESSAGE_LIST_PAGE_SIZE } from './constants';\nimport { PollManager } from './poll_manager';\nimport type {\n ChannelManagerEventHandlerOverrides,\n ChannelManagerOptions,\n QueryChannelsRequestType,\n} from './channel_manager';\nimport { ChannelManager } from './channel_manager';\nimport { MessageDeliveryReporter } from './messageDelivery';\nimport { NotificationManager } from './notifications';\nimport { ReminderManager } from './reminders';\nimport { StateStore } from './store';\nimport type { MessageComposer } from './messageComposer';\nimport type { AbstractOfflineDB } from './offline-support';\n\nfunction isString(x: unknown): x is string {\n return typeof x === 'string' || x instanceof String;\n}\n\ntype MessageComposerTearDownFunction = () => void;\n\ntype MessageComposerSetupFunction = ({\n composer,\n}: {\n composer: MessageComposer;\n}) => void | MessageComposerTearDownFunction;\n\nexport type MessageComposerSetupState = {\n /**\n * Each `MessageComposer` runs this function each time its signature changes or\n * whenever you run `MessageComposer.registerSubscriptions`. Function returned\n * from `applyModifications` will be used as a cleanup function - it will be stored\n * and ran before new modification is applied. Cleaning up only the\n * modified parts is the general way to go but if your setup gets a bit\n * complicated, feel free to restore the whole composer with `MessageComposer.restore`.\n */\n setupFunction: MessageComposerSetupFunction | null;\n};\n\nexport class StreamChat {\n private static _instance?: unknown | StreamChat; // type is undefined|StreamChat, unknown is due to TS limitations with statics\n messageDeliveryReporter: MessageDeliveryReporter;\n _user?: OwnUserResponse | UserResponse;\n appSettingsPromise?: Promise<AppSettingsAPIResponse>;\n activeChannels: {\n [key: string]: Channel;\n };\n threads: ThreadManager;\n polls: PollManager;\n offlineDb?: AbstractOfflineDB;\n notifications: NotificationManager;\n reminders: ReminderManager;\n anonymous: boolean;\n persistUserOnConnectionFailure?: boolean;\n axiosInstance: AxiosInstance;\n baseURL?: string;\n browser: boolean;\n cleaningIntervalRef?: NodeJS.Timeout;\n clientID?: string;\n configs: Configs;\n key: string;\n listeners: Record<string, Array<(event: Event) => void>>;\n logger: Logger;\n /**\n * When network is recovered, we re-query the active channels on client. But in single query, you can recover\n * only 30 channels. So its not guaranteed that all the channels in activeChannels object have updated state.\n * Thus in UI sdks, state recovery is managed by components themselves, they don't rely on js client for this.\n *\n * `recoverStateOnReconnect` parameter can be used in such cases, to disable state recovery within js client.\n * When false, user/consumer of this client will need to make sure all the channels present on UI by\n * manually calling queryChannels endpoint.\n */\n recoverStateOnReconnect?: boolean;\n moderation: Moderation;\n mutedChannels: ChannelMute[];\n mutedUsers: Mute[];\n node: boolean;\n options: StreamChatOptions;\n secret?: string;\n setUserPromise: ConnectAPIResponse | null;\n state: ClientState;\n tokenManager: TokenManager;\n user?: OwnUserResponse | UserResponse;\n userAgent?: string;\n userID?: string;\n wsBaseURL?: string;\n wsConnection: StableWSConnection | null;\n wsFallback?: WSConnectionFallback;\n wsPromise: ConnectAPIResponse | null;\n consecutiveFailures: number;\n insightMetrics: InsightMetrics;\n defaultWSTimeoutWithFallback: number;\n defaultWSTimeout: number;\n sdkIdentifier?: SdkIdentifier;\n deviceIdentifier?: DeviceIdentifier;\n private nextRequestAbortController: AbortController | null = null;\n /**\n * @private\n */\n _messageComposerSetupState = new StateStore<MessageComposerSetupState>({\n setupFunction: null,\n });\n\n /**\n * Initialize a client\n *\n * **Only use constructor for advanced usages. It is strongly advised to use `StreamChat.getInstance()` instead of `new StreamChat()` to reduce integration issues due to multiple WebSocket connections**\n * @param {string} key - the api key\n * @param {string} [secret] - the api secret\n * @param {StreamChatOptions} [options] - additional options, here you can pass custom options to axios instance\n * @param {boolean} [options.browser] - enforce the client to be in browser mode\n * @param {boolean} [options.warmUp] - default to false, if true, client will open a connection as soon as possible to speed up following requests\n * @param {Logger} [options.Logger] - custom logger\n * @param {number} [options.timeout] - default to 3000\n * @param {httpsAgent} [options.httpsAgent] - custom httpsAgent, in node it's default to https.agent()\n * @example <caption>initialize the client in user mode</caption>\n * new StreamChat('api_key')\n * @example <caption>initialize the client in user mode with options</caption>\n * new StreamChat('api_key', { warmUp:true, timeout:5000 })\n * @example <caption>secret is optional and only used in server side mode</caption>\n * new StreamChat('api_key', \"secret\", { httpsAgent: customAgent })\n */\n constructor(key: string, options?: StreamChatOptions);\n constructor(key: string, secret?: string, options?: StreamChatOptions);\n constructor(\n key: string,\n secretOrOptions?: StreamChatOptions | string,\n options?: StreamChatOptions,\n ) {\n // set the key\n this.key = key;\n this.listeners = {};\n this.state = new ClientState({ client: this });\n // a list of channels to hide ws events from\n this.mutedChannels = [];\n this.mutedUsers = [];\n\n this.moderation = new Moderation(this);\n\n this.notifications = options?.notifications ?? new NotificationManager();\n\n // set the secret\n if (secretOrOptions && isString(secretOrOptions)) {\n this.secret = secretOrOptions;\n }\n\n // set the options... and figure out defaults...\n const inputOptions = options\n ? options\n : secretOrOptions && !isString(secretOrOptions)\n ? secretOrOptions\n : {};\n\n this.browser =\n typeof inputOptions.browser !== 'undefined'\n ? inputOptions.browser\n : typeof window !== 'undefined';\n this.node = !this.browser;\n\n this.options = {\n timeout: 3000,\n withCredentials: false, // making sure cookies are not sent\n warmUp: false,\n recoverStateOnReconnect: true,\n disableCache: false,\n wsUrlParams: new URLSearchParams({}),\n ...inputOptions,\n };\n\n if (this.node && !this.options.httpsAgent) {\n this.options.httpsAgent = new https.Agent({\n keepAlive: true,\n keepAliveMsecs: 3000,\n });\n }\n\n this.axiosInstance = axios.create(this.options);\n\n this.setBaseURL(this.options.baseURL || 'https://chat.stream-io-api.com');\n\n if (\n typeof process !== 'undefined' &&\n 'env' in process &&\n process.env.STREAM_LOCAL_TEST_RUN\n ) {\n this.setBaseURL('http://localhost:3030');\n }\n\n if (\n typeof process !== 'undefined' &&\n 'env' in process &&\n process.env.STREAM_LOCAL_TEST_HOST\n ) {\n this.setBaseURL('http://' + process.env.STREAM_LOCAL_TEST_HOST);\n }\n\n // WS connection is initialized when setUser is called\n this.wsConnection = null;\n this.wsPromise = null;\n this.setUserPromise = null;\n // keeps a reference to all the channels that are in use\n this.activeChannels = {};\n\n // mapping between channel groups and configs\n this.configs = {};\n this.anonymous = false;\n this.persistUserOnConnectionFailure = this.options?.persistUserOnConnectionFailure;\n\n // If its a server-side client, then lets initialize the tokenManager, since token will be\n // generated from secret.\n this.tokenManager = new TokenManager(this.secret);\n this.consecutiveFailures = 0;\n this.insightMetrics = new InsightMetrics();\n\n this.defaultWSTimeoutWithFallback = 6 * 1000;\n this.defaultWSTimeout = 15 * 1000;\n\n this.axiosInstance.defaults.paramsSerializer = axiosParamsSerializer;\n\n /**\n * logger function should accept 3 parameters:\n * @param logLevel string\n * @param message string\n * @param extraData object\n *\n * e.g.,\n * const client = new StreamChat('api_key', {}, {\n * logger = (logLevel, message, extraData) => {\n * console.log(message);\n * }\n * })\n *\n * extraData contains tags array attached to log message. Tags can have one/many of following values:\n * 1. api\n * 2. api_request\n * 3. api_response\n * 4. client\n * 5. channel\n * 6. connection\n * 7. event\n *\n * It may also contains some extra data, some examples have been mentioned below:\n * 1. {\n * tags: ['api', 'api_request', 'client'],\n * url: string,\n * payload: object,\n * config: object\n * }\n * 2. {\n * tags: ['api', 'api_response', 'client'],\n * url: string,\n * response: object\n * }\n * 3. {\n * tags: ['api', 'api_response', 'client'],\n * url: string,\n * error: object\n * }\n * 4. {\n * tags: ['event', 'client'],\n * event: object\n * }\n * 5. {\n * tags: ['channel'],\n * channel: object\n * }\n */\n this.logger = isFunction(inputOptions.logger) ? inputOptions.logger : () => null;\n this.recoverStateOnReconnect = this.options.recoverStateOnReconnect;\n this.threads = new ThreadManager({ client: this });\n this.polls = new PollManager({ client: this });\n this.reminders = new ReminderManager({ client: this });\n this.messageDeliveryReporter = new MessageDeliveryReporter({ client: this });\n }\n\n /**\n * Get a client instance\n *\n * This function always returns the same Client instance to avoid issues raised by multiple Client and WS connections\n *\n * **After the first call, the client configuration will not change if the key or options parameters change**\n *\n * @param {string} key - the api key\n * @param {string} [secret] - the api secret\n * @param {StreamChatOptions} [options] - additional options, here you can pass custom options to axios instance\n * @param {boolean} [options.browser] - enforce the client to be in browser mode\n * @param {boolean} [options.warmUp] - default to false, if true, client will open a connection as soon as possible to speed up following requests\n * @param {Logger} [options.Logger] - custom logger\n * @param {number} [options.timeout] - default to 3000\n * @param {httpsAgent} [options.httpsAgent] - custom httpsAgent, in node it's default to https.agent()\n * @example <caption>initialize the client in user mode</caption>\n * StreamChat.getInstance('api_key')\n * @example <caption>initialize the client in user mode with options</caption>\n * StreamChat.getInstance('api_key', { timeout:5000 })\n * @example <caption>secret is optional and only used in server side mode</caption>\n * StreamChat.getInstance('api_key', \"secret\", { httpsAgent: customAgent })\n */\n public static getInstance(key: string, options?: StreamChatOptions): StreamChat;\n public static getInstance(\n key: string,\n secret?: string,\n options?: StreamChatOptions,\n ): StreamChat;\n public static getInstance(\n key: string,\n secretOrOptions?: StreamChatOptions | string,\n options?: StreamChatOptions,\n ): StreamChat {\n if (!StreamChat._instance) {\n if (typeof secretOrOptions === 'string') {\n StreamChat._instance = new StreamChat(key, secretOrOptions, options);\n } else {\n StreamChat._instance = new StreamChat(key, secretOrOptions);\n }\n }\n\n return StreamChat._instance as StreamChat;\n }\n\n setOfflineDBApi(offlineDBInstance: AbstractOfflineDB) {\n if (this.offlineDb) {\n return;\n }\n\n this.offlineDb = offlineDBInstance;\n }\n\n devToken(userID: string) {\n return DevToken(userID);\n }\n\n getAuthType() {\n return this.anonymous ? 'anonymous' : 'jwt';\n }\n\n setBaseURL(baseURL: string) {\n this.baseURL = baseURL;\n this.wsBaseURL = this.baseURL.replace('http', 'ws').replace(':3030', ':8800');\n }\n\n _getConnectionID = () =>\n this.wsConnection?.connectionID || this.wsFallback?.connectionID;\n\n _hasConnectionID = () => Boolean(this._getConnectionID());\n\n public setMessageComposerSetupFunction = (\n setupFunction: MessageComposerSetupState['setupFunction'],\n ) => {\n this._messageComposerSetupState.partialNext({ setupFunction });\n };\n\n /**\n * connectUser - Set the current user and open a WebSocket connection\n *\n * @param {OwnUserResponse | UserResponse} user Data about this user. IE {name: \"john\"}\n * @param {TokenOrProvider} userTokenOrProvider Token or provider\n *\n * @return {ConnectAPIResponse} Returns a promise that resolves when the connection is setup\n */\n connectUser = async (\n user: OwnUserResponse | UserResponse,\n userTokenOrProvider: TokenOrProvider,\n ) => {\n if (!user.id) {\n throw new Error('The \"id\" field on the user is missing');\n }\n\n /**\n * Calling connectUser multiple times is potentially the result of a bad integration, however,\n * If the user id remains the same we don't throw error\n */\n if (this.userID === user.id && this.setUserPromise) {\n console.warn(\n 'Consecutive calls to connectUser is detected, ideally you should only call this function once in your app.',\n );\n return this.setUserPromise;\n }\n\n if (this.userID) {\n throw new Error(\n 'Use client.disconnect() before trying to connect as a different user. connectUser was called twice.',\n );\n }\n\n if (\n (this._isUsingServerAuth() || this.node) &&\n !this.options.allowServerSideConnect\n ) {\n console.warn(\n 'Please do not use connectUser server side. connectUser impacts MAU and concurrent connection usage and thus your bill. If you have a valid use-case, add \"allowServerSideConnect: true\" to the client options to disable this warning.',\n );\n }\n\n // we generate the client id client side\n this.userID = user.id;\n this.anonymous = false;\n\n const setTokenPromise = this._setToken(user, userTokenOrProvider);\n this._setUser(user);\n\n const wsPromise = this.openConnection();\n\n this.setUserPromise = Promise.all([setTokenPromise, wsPromise]).then(\n (result) => result[1], // We only return connection promise;\n );\n\n try {\n return await this.setUserPromise;\n } catch (err) {\n if (this.persistUserOnConnectionFailure) {\n // cleanup client to allow the user to retry connectUser again\n this.closeConnection();\n } else {\n this.disconnectUser();\n }\n throw err;\n }\n };\n\n /**\n * @deprecated Please use connectUser() function instead. Its naming is more consistent with its functionality.\n *\n * setUser - Set the current user and open a WebSocket connection\n *\n * @param {OwnUserResponse | UserResponse} user Data about this user. IE {name: \"john\"}\n * @param {TokenOrProvider} userTokenOrProvider Token or provider\n *\n * @return {ConnectAPIResponse} Returns a promise that resolves when the connection is setup\n */\n setUser = this.connectUser;\n\n _setToken = (user: UserResponse, userTokenOrProvider: TokenOrProvider) =>\n this.tokenManager.setTokenOrProvider(userTokenOrProvider, user);\n\n _setUser(user: OwnUserResponse | UserResponse) {\n /**\n * This one is used by the frontend. This is a copy of the current user object stored on backend.\n * It contains reserved properties and own user properties which are not present in `this._user`.\n */\n this.user = user;\n this.userID = user.id;\n // this one is actually used for requests. This is a copy of current user provided to `connectUser` function.\n this._user = { ...user };\n }\n\n /**\n * Disconnects the websocket connection, without removing the user set on client.\n * client.closeConnection will not trigger default auto-retry mechanism for reconnection. You need\n * to call client.openConnection to reconnect to websocket.\n *\n * This is mainly useful on mobile side. You can only receive push notifications\n * if you don't have active websocket connection.\n * So when your app goes to background, you can call `client.closeConnection`.\n * And when app comes back to foreground, call `client.openConnection`.\n *\n * @param timeout Max number of ms, to wait for close event of websocket, before forcefully assuming succesful disconnection.\n * https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent\n */\n closeConnection = async (timeout?: number) => {\n if (this.cleaningIntervalRef != null) {\n clearInterval(this.cleaningIntervalRef);\n this.cleaningIntervalRef = undefined;\n }\n\n await Promise.all([\n this.wsConnection?.disconnect(timeout),\n this.wsFallback?.disconnect(timeout),\n ]);\n\n this.offlineDb?.executeQuerySafely(\n async (db) => {\n if (this.userID) {\n await db.upsertUserSyncStatus({\n userId: this.userID,\n lastSyncedAt: new Date().toString(),\n });\n }\n },\n { method: 'upsertUserSyncStatus' },\n );\n\n return Promise.resolve();\n };\n\n /**\n * Creates an instance of ChannelManager.\n *\n * @internal\n *\n * @param eventHandlerOverrides - the overrides for event handlers to be used\n * @param options - the options used for the channel manager\n */\n createChannelManager = ({\n eventHandlerOverrides = {},\n options = {},\n queryChannelsOverride,\n }: {\n eventHandlerOverrides?: ChannelManagerEventHandlerOverrides;\n options?: ChannelManagerOptions;\n queryChannelsOverride?: QueryChannelsRequestType;\n }) =>\n new ChannelManager({\n client: this,\n eventHandlerOverrides,\n options,\n queryChannelsOverride,\n });\n\n /**\n * Creates a new WebSocket connection with the current user. Returns empty promise, if there is an active connection\n */\n openConnection = () => {\n if (!this.userID) {\n throw Error(\n 'User is not set on client, use client.connectUser or client.connectAnonymousUser instead',\n );\n }\n\n if (this.wsConnection?.isConnecting && this.wsPromise) {\n this.logger('info', 'client:openConnection() - connection already in progress', {\n tags: ['connection', 'client'],\n });\n return this.wsPromise;\n }\n\n if (\n (this.wsConnection?.isHealthy || this.wsFallback?.isHealthy()) &&\n this._hasConnectionID()\n ) {\n this.logger(\n 'info',\n 'client:openConnection() - openConnection called twice, healthy connection already exists',\n {\n tags: ['connection', 'client'],\n },\n );\n\n return;\n }\n\n this.clientID = `${this.userID}--${randomId()}`;\n this.wsPromise = this.connect();\n this._startCleaning();\n return this.wsPromise;\n };\n\n /**\n * @deprecated Please use client.openConnction instead.\n * @private\n *\n * Creates a new websocket connection with current user.\n */\n _setupConnection = this.openConnection;\n\n /**\n * updateAppSettings - updates application settings\n *\n * @param {AppSettings} options App settings.\n * IE: {\n 'apn_config': {\n 'auth_type': 'token',\n 'auth_key\": fs.readFileSync(\n './apn-push-auth-key.p8',\n 'utf-8',\n ),\n 'key_id': 'keyid',\n 'team_id': 'teamid',\n 'notification_template\": 'notification handlebars template',\n 'bundle_id': 'com.apple.your.app',\n 'development': true\n },\n 'firebase_config': {\n 'server_key': 'server key from fcm',\n 'notification_template': 'notification handlebars template',\n 'data_template': 'data handlebars template',\n 'apn_template': 'apn notification handlebars template under v2'\n },\n 'webhook_url': 'https://acme.com/my/awesome/webhook/',\n 'event_hooks': [\n {\n 'hook_type': 'webhook',\n 'enabled': true,\n 'event_types': ['message.new'],\n 'webhook_url': 'https://acme.com/my/awesome/webhook/'\n },\n {\n 'hook_type': 'sqs',\n 'enabled': true,\n 'event_types': ['message.new'],\n 'sqs_url': 'https://sqs.us-east-1.amazonaws.com/1234567890/my-queue',\n 'sqs_auth_type': 'key',\n 'sqs_key': 'my-access-key',\n 'sqs_secret': 'my-secret-key'\n }\n ]\n }\n */\n async updateAppSettings(options: AppSettings) {\n const apn_config = options.apn_config;\n if (apn_config?.p12_cert) {\n options = {\n ...options,\n apn_config: {\n ...apn_config,\n p12_cert: Buffer.from(apn_config.p12_cert).toString('base64'),\n },\n };\n }\n return await this.patch<APIResponse>(this.baseURL + '/app', options);\n }\n\n _normalizeDate = (before: Date | string | null): string | null => {\n if (before instanceof Date) {\n before = before.toISOString();\n }\n\n if (before === '') {\n throw new Error(\n \"Don't pass blank string for since, use null instead if resetting the token revoke\",\n );\n }\n\n return before;\n };\n\n /**\n * Revokes all tokens on application level issued before given time\n */\n async revokeTokens(before: Date | string | null) {\n return await this.updateAppSettings({\n revoke_tokens_issued_before: this._normalizeDate(before),\n });\n }\n\n /**\n * Revokes token for a user issued before given time\n */\n async revokeUserToken(userID: string, before?: Date | string | null) {\n return await this.revokeUsersToken([userID], before);\n }\n\n /**\n * Revokes tokens for a list of users issued before given time\n */\n async revokeUsersToken(userIDs: string[], before?: Date | string | null) {\n if (before === undefined) {\n before = new Date().toISOString();\n } else {\n before = this._normalizeDate(before);\n }\n\n const users: PartialUserUpdate[] = [];\n for (const userID of userIDs) {\n users.push({\n id: userID,\n set: <Partial<UserResponse>>{\n revoke_tokens_issued_before: before,\n },\n });\n }\n\n return await this.partialUpdateUsers(users);\n }\n\n /**\n * getAppSettings - retrieves application settings\n */\n async getAppSettings() {\n this.appSettingsPromise = this.get<AppSettingsAPIResponse>(this.baseURL + '/app');\n return await this.appSettingsPromise;\n }\n\n /**\n * testPushSettings - Tests the push settings for a user with a random chat message and the configured push templates\n *\n * @param {string} userID User ID. If user has no devices, it will error\n * @param {TestPushDataInput} [data] Overrides for push templates/message used\n * IE: {\n messageID: 'id-of-message', // will error if message does not exist\n apnTemplate: '{}', // if app doesn't have apn configured it will error\n firebaseTemplate: '{}', // if app doesn't have firebase configured it will error\n firebaseDataTemplate: '{}', // if app doesn't have firebase configured it will error\n skipDevices: true, // skip config/device checks and sending to real devices\n pushProviderName: 'staging' // one of your configured push providers\n pushProviderType: 'apn' // one of supported provider types\n }\n */\n async testPushSettings(userID: string, data: TestPushDataInput = {}) {\n return await this.post<CheckPushResponse>(this.baseURL + '/check_push', {\n user_id: userID,\n ...(data.messageID ? { message_id: data.messageID } : {}),\n ...(data.apnTemplate ? { apn_template: data.apnTemplate } : {}),\n ...(data.firebaseTemplate ? { firebase_template: data.firebaseTemplate } : {}),\n ...(data.firebaseDataTemplate\n ? { firebase_data_template: data.firebaseDataTemplate }\n : {}),\n ...(data.skipDevices ? { skip_devices: true } : {}),\n ...(data.pushProviderName ? { push_provider_name: data.pushProviderName } : {}),\n ...(data.pushProviderType ? { push_provider_type: data.pushProviderType } : {}),\n });\n }\n\n /**\n * testSQSSettings - Tests that the given or configured SQS configuration is valid\n *\n * @param {TestSQSDataInput} [data] Overrides SQS settings for testing if needed\n * IE: {\n sqs_key: 'auth_key',\n sqs_secret: 'auth_secret',\n sqs_url: 'url_to_queue',\n }\n */\n async testSQSSettings(data: TestSQSDataInput = {}) {\n return await this.post<CheckSQSResponse>(this.baseURL + '/check_sqs', data);\n }\n\n /**\n * testSNSSettings - Tests that the given or configured SNS configuration is valid\n *\n * @param {TestSNSDataInput} [data] Overrides SNS settings for testing if needed\n * IE: {\n sns_key: 'auth_key',\n sns_secret: 'auth_secret',\n sns_topic_arn: 'topic_to_publish_to',\n }\n */\n async testSNSSettings(data: TestSNSDataInput = {}) {\n return await this.post<CheckSNSResponse>(this.baseURL + '/check_sns', data);\n }\n\n /**\n * Disconnects the websocket and removes the user from client.\n *\n * @param timeout Max number of ms, to wait for close event of websocket, before forcefully assuming successful disconnection.\n * https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent\n */\n disconnectUser = (timeout?: number) => {\n this.logger('info', 'client:disconnect() - Disconnecting the client', {\n tags: ['connection', 'client'],\n });\n\n // remove the user specific fields\n delete this.user;\n delete this._user;\n delete this.userID;\n\n this.anonymous = false;\n\n const closePromise = this.closeConnection(timeout);\n\n for (const channel of Object.values(this.activeChannels)) {\n channel._disconnect();\n }\n // ensure we no longer return inactive channels\n this.activeChannels = {};\n // reset client state\n this.state = new ClientState({ client: this });\n // reset thread manager\n this.threads.resetState();\n\n // Since we wipe all user data already, we should reset token manager as well\n closePromise\n .finally(() => {\n this.tokenManager.reset();\n })\n .catch((err) => console.error(err));\n\n // close the WS connection\n return closePromise;\n };\n\n /**\n *\n * @deprecated Please use client.disconnectUser instead.\n *\n * Disconnects the websocket and removes the user from client.\n */\n disconnect = this.disconnectUser;\n\n /**\n * connectAnonymousUser - Set an anonymous user and open a WebSocket connection\n */\n connectAnonymousUser = () => {\n if (\n (this._isUsingServerAuth() || this.node) &&\n !this.options.allowServerSideConnect\n ) {\n console.warn(\n 'Please do not use connectUser server side. connectUser impacts MAU and concurrent connection usage and thus your bill. If you have a valid use-case, add \"allowServerSideConnect: true\" to the client options to disable this warning.',\n );\n }\n\n this.anonymous = true;\n this.userID = randomId();\n const anonymousUser = {\n id: this.userID,\n anon: true,\n } as UserResponse;\n\n this._setToken(anonymousUser, '');\n this._setUser(anonymousUser);\n\n return this._setupConnection();\n };\n\n /**\n * @deprecated Please use connectAnonymousUser. Its naming is more consistent with its functionality.\n */\n setAnonymousUser = this.connectAnonymousUser;\n\n /**\n * setGuestUser - Setup a temporary guest user\n *\n * @param {UserResponse} user Data about this user. IE {name: \"john\"}\n *\n * @return {ConnectAPIResponse} Returns a promise that resolves when the connection is setup\n */\n async setGuestUser(user: UserResponse) {\n let response: { access_token: string; user: UserResponse } | undefined;\n this.anonymous = true;\n try {\n response = await this.post<\n APIResponse & {\n access_token: string;\n user: UserResponse;\n }\n >(this.baseURL + '/guest', { user });\n } catch (e) {\n this.anonymous = false;\n throw e;\n }\n this.anonymous = false;\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n const { created_at, updated_at, last_active, online, ...guestUser } = response.user;\n return await this.connectUser(guestUser as UserResponse, response.access_token);\n }\n\n /**\n * createToken - Creates a token to authenticate this user. This function is used server side.\n * The resulting token should be passed to the client side when the users registers or logs in.\n *\n * @param {string} userID The User ID\n * @param {number} [exp] The expiration time for the token expressed in the number of seconds since the epoch\n *\n * @return {string} Returns a token\n */\n createToken(userID: string, exp?: number, iat?: number) {\n if (this.secret == null) {\n throw Error(`tokens can only be created server-side using the API Secret`);\n }\n const extra: { exp?: number; iat?: number } = {};\n\n if (exp) {\n extra.exp = exp;\n }\n\n if (iat) {\n extra.iat = iat;\n }\n\n return JWTUserToken(this.secret, userID, extra, {});\n }\n\n /**\n * on - Listen to events on all channels and users your watching\n *\n * client.on('message.new', event => {console.log(\"my new message\", event, channel.state.messages)})\n * or\n * client.on(event => {console.log(event.type)})\n *\n * @param {EventHandler | string} callbackOrString The event type to listen for (optional)\n * @param {EventHandler} [callbackOrNothing] The callback to call\n *\n * @return {{ unsubscribe: () => void }} Description\n */\n on(callback: EventHandler): { unsubscribe: () => void };\n on(eventType: string, callback: EventHandler): { unsubscribe: () => void };\n on(\n callbackOrString: EventHandler | string,\n callbackOrNothing?: EventHandler,\n ): { unsubscribe: () => void } {\n const key = callbackOrNothing ? (callbackOrString as string) : 'all';\n const callback = callbackOrNothing\n ? callbackOrNothing\n : (callbackOrString as EventHandler);\n if (!(key in this.listeners)) {\n this.listeners[key] = [];\n }\n this.logger('info', `Attaching listener for ${key} event`, {\n tags: ['event', 'client'],\n });\n this.listeners[key].push(callback);\n return {\n unsubscribe: () => {\n this.logger('info', `Removing listener for ${key} event`, {\n tags: ['event', 'client'],\n });\n this.listeners[key] = this.listeners[key].filter((el) => el !== callback);\n },\n };\n }\n\n /**\n * off - Remove the event handler\n *\n */\n off(callback: EventHandler): void;\n off(eventType: string, callback: EventHandler): void;\n off(callbackOrString: EventHandler | string, callbackOrNothing?: EventHandler) {\n const key = callbackOrNothing ? (callbackOrString as string) : 'all';\n const callback = callbackOrNothing\n ? callbackOrNothing\n : (callbackOrString as EventHandler);\n if (!(key in this.listeners)) {\n this.listeners[key] = [];\n }\n\n this.logger('info', `Removing listener for ${key} event`, {\n tags: ['event', 'client'],\n });\n this.listeners[key] = this.listeners[key].filter((value) => value !== callback);\n }\n\n _logApiRequest(\n type: string,\n url: string,\n data: unknown,\n config: AxiosRequestConfig & {\n config?: AxiosRequestConfig & { maxBodyLength?: number };\n },\n ) {\n this.logger('info', `client: ${type} - Request - ${url}`, {\n tags: ['api', 'api_request', 'client'],\n url,\n payload: data,\n config,\n });\n }\n\n _logApiResponse<T>(type: string, url: string, response: AxiosResponse<T>) {\n this.logger(\n 'info',\n `client:${type} - Response - url: ${url} > status ${response.status}`,\n {\n tags: ['api', 'api_response', 'client'],\n url,\n response,\n },\n );\n }\n\n _logApiError(type: string, url: string, error: unknown) {\n this.logger('error', `client:${type} - Error - url: ${url}`, {\n tags: ['api', 'api_response', 'client'],\n url,\n error,\n });\n }\n\n doAxiosRequest = async <T>(\n type: string,\n url: string,\n data?: unknown,\n options: AxiosRequestConfig & {\n config?: AxiosRequestConfig & { maxBodyLength?: number };\n } = {},\n ): Promise<T> => {\n await this.tokenManager.tokenReady();\n const requestConfig = this._enrichAxiosOptions(options);\n try {\n let response: AxiosResponse<T>;\n this._logApiRequest(type, url, data, requestConfig);\n switch (type) {\n case 'get':\n response = await this.axiosInstance.get(url, requestConfig);\n break;\n case 'delete':\n response = await this.axiosInstance.delete(url, requestConfig);\n break;\n case 'post':\n response = await this.axiosInstance.post(url, data, requestConfig);\n break;\n case 'postForm':\n response = await this.axiosInstance.postForm(url, data, requestConfig);\n break;\n case 'put':\n response = await this.axiosInstance.put(url, data, requestConfig);\n break;\n case 'patch':\n response = await this.axiosInstance.patch(url, data, requestConfig);\n break;\n case 'options':\n response = await this.axiosInstance.options(url, requestConfig);\n break;\n default:\n throw new Error('Invalid request type');\n }\n this._logApiResponse<T>(type, url, response);\n this.consecutiveFailures = 0;\n return this.handleResponse(response);\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n } catch (e: any /**TODO: generalize error types */) {\n e.client_request_id = requestConfig.headers?.['x-client-request-id'];\n this._logApiError(type, url, e);\n this.consecutiveFailures += 1;\n if (e.response) {\n /** connection_fallback depends on this token expiration logic */\n if (\n e.response.data.code === chatCodes.TOKEN_EXPIRED &&\n !this.tokenMa