softchatjs-core
Version:
Javascript client for JS based frameworks
1 lines • 87 kB
Source Map (JSON)
{"version":3,"sources":["../src/utils.ts","../src/fetch.ts","../src/MessageClient.ts"],"sourcesContent":["import moment from 'moment';\r\n\r\nimport { Conversation, StringOrNumber, Participant, ParticipantListInfo, Message, MessageStates, UserMeta } from \"./types\";\r\n\r\nfunction murmurHash3_x64_64(str: string, seed = 0) {\r\n let h1 = BigInt(seed);\r\n let h2 = BigInt(seed);\r\n const c1 = BigInt(\"0x87c37b91114253d5\");\r\n const c2 = BigInt(\"0x4cf5ad432745937f\");\r\n\r\n let length = str.length;\r\n const remainder = length & 15; // length % 16\r\n const bytes = length - remainder;\r\n\r\n for (let i = 0; i < bytes; i += 16) {\r\n let k1 =\r\n BigInt(str.charCodeAt(i)) |\r\n (BigInt(str.charCodeAt(i + 1)) << BigInt(8)) |\r\n (BigInt(str.charCodeAt(i + 2)) << BigInt(16)) |\r\n (BigInt(str.charCodeAt(i + 3)) << BigInt(24)) |\r\n (BigInt(str.charCodeAt(i + 4)) << BigInt(32)) |\r\n (BigInt(str.charCodeAt(i + 5)) << BigInt(40)) |\r\n (BigInt(str.charCodeAt(i + 6)) << BigInt(48)) |\r\n (BigInt(str.charCodeAt(i + 7)) << BigInt(56));\r\n\r\n let k2 =\r\n BigInt(str.charCodeAt(i + 8)) |\r\n (BigInt(str.charCodeAt(i + 9)) << BigInt(8)) |\r\n (BigInt(str.charCodeAt(i + 10)) << BigInt(16)) |\r\n (BigInt(str.charCodeAt(i + 11)) << BigInt(24)) |\r\n (BigInt(str.charCodeAt(i + 12)) << BigInt(32)) |\r\n (BigInt(str.charCodeAt(i + 13)) << BigInt(40)) |\r\n (BigInt(str.charCodeAt(i + 14)) << BigInt(48)) |\r\n (BigInt(str.charCodeAt(i + 15)) << BigInt(56));\r\n\r\n k1 = k1 * c1;\r\n k1 = (k1 << BigInt(31)) | (k1 >> BigInt(33));\r\n k1 = k1 * c2;\r\n h1 ^= k1;\r\n\r\n h1 = (h1 << BigInt(27)) | (h1 >> BigInt(37));\r\n h1 = h1 + h2;\r\n h1 = h1 * BigInt(5) + BigInt(\"0x52dce729\");\r\n\r\n k2 = k2 * c2;\r\n k2 = (k2 << BigInt(33)) | (k2 >> BigInt(31));\r\n k2 = k2 * c1;\r\n h2 ^= k2;\r\n\r\n h2 = (h2 << BigInt(31)) | (h2 >> BigInt(33));\r\n h2 = h1 + h2;\r\n h2 = h2 * BigInt(5) + BigInt(\"0x38495ab5\");\r\n }\r\n\r\n let k1 = BigInt(0);\r\n let k2 = BigInt(0);\r\n\r\n switch (remainder) {\r\n case 15:\r\n k2 ^= BigInt(str.charCodeAt(bytes + 14)) << BigInt(48);\r\n case 14:\r\n k2 ^= BigInt(str.charCodeAt(bytes + 13)) << BigInt(40);\r\n case 13:\r\n k2 ^= BigInt(str.charCodeAt(bytes + 12)) << BigInt(32);\r\n case 12:\r\n k2 ^= BigInt(str.charCodeAt(bytes + 11)) << BigInt(24);\r\n case 11:\r\n k2 ^= BigInt(str.charCodeAt(bytes + 10)) << BigInt(16);\r\n case 10:\r\n k2 ^= BigInt(str.charCodeAt(bytes + 9)) << BigInt(8);\r\n case 9:\r\n k2 ^= BigInt(str.charCodeAt(bytes + 8));\r\n k2 = k2 * c2;\r\n k2 = (k2 << BigInt(33)) | (k2 >> BigInt(31));\r\n k2 = k2 * c1;\r\n h2 ^= k2;\r\n case 8:\r\n k1 ^= BigInt(str.charCodeAt(bytes + 7)) << BigInt(56);\r\n case 7:\r\n k1 ^= BigInt(str.charCodeAt(bytes + 6)) << BigInt(48);\r\n case 6:\r\n k1 ^= BigInt(str.charCodeAt(bytes + 5)) << BigInt(40);\r\n case 5:\r\n k1 ^= BigInt(str.charCodeAt(bytes + 4)) << BigInt(32);\r\n case 4:\r\n k1 ^= BigInt(str.charCodeAt(bytes + 3)) << BigInt(24);\r\n case 3:\r\n k1 ^= BigInt(str.charCodeAt(bytes + 2)) << BigInt(16);\r\n case 2:\r\n k1 ^= BigInt(str.charCodeAt(bytes + 1)) << BigInt(8);\r\n case 1:\r\n k1 ^= BigInt(str.charCodeAt(bytes));\r\n k1 = k1 * c1;\r\n k1 = (k1 << BigInt(31)) | (k1 >> BigInt(33));\r\n k1 = k1 * c2;\r\n h1 ^= k1;\r\n }\r\n\r\n h1 ^= BigInt(length);\r\n h2 ^= BigInt(length);\r\n\r\n h1 += h2;\r\n h2 += h1;\r\n\r\n h1 ^= h1 >> BigInt(33);\r\n h1 = h1 * BigInt(\"0xff51afd7ed558ccd\");\r\n h1 ^= h1 >> BigInt(33);\r\n h1 = h1 * BigInt(\"0xc4ceb9fe1a85ec53\");\r\n h1 ^= h1 >> BigInt(33);\r\n\r\n h2 ^= h2 >> BigInt(33);\r\n h2 = h2 * BigInt(\"0xff51afd7ed558ccd\");\r\n h2 ^= h2 >> BigInt(33);\r\n h2 = h2 * BigInt(\"0xc4ceb9fe1a85ec53\");\r\n h2 ^= h2 >> BigInt(33);\r\n\r\n h1 += h2;\r\n h2 += h1;\r\n\r\n // Combine h1 and h2 to return a 64-bit hash\r\n return (h1 & BigInt(\"0xFFFFFFFFFFFFFFFF\")).toString(16);\r\n}\r\n\r\n// Updated generateConversationId using MurmurHash3 x64 (64-bit)\r\nexport function generateConversationId(str1: string, str2: string, projectId: string) {\r\n // Sort the strings alphabetically to ensure consistency\r\n const sortedStrings = [str1, str2].sort();\r\n // Concatenate the sorted strings with a delimiter\r\n const combinedString = sortedStrings.join(\"_\");\r\n // Generate a 64-bit hash of the combined string using MurmurHash3 x64\r\n const hash = murmurHash3_x64_64(`${projectId}:${combinedString}`);\r\n // Return the hash as the unique ID\r\n return hash;\r\n}\r\n\r\nexport const generateId = () => {\r\n let uuid = '';\r\n const characters = 'abcdef0123456789';\r\n for (let i = 0; i < 32; i++) {\r\n const randomNumber = Math.floor(Math.random() * characters.length);\r\n const character = characters.charAt(randomNumber);\r\n if (i === 8 || i === 12 || i === 16 || i === 20) {\r\n uuid += '-';\r\n }\r\n uuid += character;\r\n }\r\n return uuid;\r\n}\r\n\r\n\r\nexport const getUserInfoWithId = (userId: string, participantList: ParticipantListInfo[]): {\r\n presentUser: UserMeta | undefined,\r\n receivingUser: UserMeta | undefined,\r\n} => {\r\n let presentUser = participantList.find(participant => participant.participantId === userId);\r\n let otherParticipants = participantList.filter(participant => participant.participantId !== userId)\r\n return { presentUser: presentUser?.participantDetails, receivingUser: otherParticipants[0]?.participantDetails };\r\n};\r\n\r\nexport const truncate = (str: string, len: number) => {\r\n return str.length > len ? str.substring(0, len)+'...' : str;\r\n}\r\n\r\nexport const getConversationTitle = (userId: string, converstaion: Conversation) => {\r\n if(converstaion.conversationType === 'private-chat'){\r\n const userInfos = getUserInfoWithId(userId, converstaion.participantList);\r\n\r\n const firstname = userInfos.receivingUser?.firstname\r\n const username = userInfos.receivingUser?.username\r\n return firstname? firstname : username\r\n }\r\n if(converstaion.conversationType === 'group-chat') {\r\n return converstaion.groupMeta?.groupName || 'no-groupname'\r\n }\r\n}\r\n\r\nexport const getUsernameInitials = (username: string) =>{\r\n return username.substring(0, 1)\r\n}\r\n\r\nexport function formatMessageTime(time: Date | string) {\r\n return moment(new Date(time)).format(\"hh:mm a\");\r\n}\r\n\r\nexport function formatConversationTime(time: Date | string) {\r\n const now = moment();\r\n const then = moment(time);\r\n const duration = moment.duration(now.diff(then));\r\n\r\n // Get the largest unit\r\n const years = Math.floor(duration.asYears());\r\n if (years > 0) return years + 'yr';\r\n\r\n const months = Math.floor(duration.asMonths());\r\n if (months > 0) return months + 'mo';\r\n\r\n const weeks = Math.floor(duration.asWeeks());\r\n if (weeks > 0) return weeks + 'w';\r\n\r\n const days = Math.floor(duration.asDays());\r\n if (days > 0) return days + 'd';\r\n\r\n const hours = Math.floor(duration.asHours());\r\n if (hours > 0) return hours + 'h';\r\n\r\n const minutes = Math.floor(duration.asMinutes());\r\n if (minutes > 0) return minutes + 'm';\r\n\r\n // If duration is less than 1 minute\r\n return 'Just now';\r\n}\r\n\r\nexport const generateFillerTimestamps = () => {\r\n return {\r\n createdAt: new Date(),\r\n updatedAt: new Date(),\r\n }\r\n}\r\n\r\nexport const getUnreadMessageIds = (conversation: Conversation, userId: string) => {\r\n var ids: string[] = []\r\n conversation.messages.map(m => {\r\n if (m.messageState === MessageStates.SENT && m.from !== userId) {\r\n ids.push(m.messageId)\r\n }\r\n })\r\n return ids\r\n}\r\n\r\nexport const getQuotedMessage = (messageId: string, messages: Message[]) => {\r\n const message = messages.find(msg => msg.messageId === messageId)\r\n return message\r\n}\r\n","import axios, { AxiosResponse } from \"axios\";\r\nimport { MediaType, Prettify, UploadContent } from \"./types\";\r\n\r\nlet API = \"https://api.softchatjs.com\";\r\n\r\nenum ENDPOINTS {\r\n CONVERSATIONS = \"/conversations\",\r\n CONVERSATION = \"/conversation\",\r\n MESSAGES = \"/messages\",\r\n UPLOAD = \"/upload\",\r\n UPLOAD_ATTACHMENT = \"/upload-attachment\",\r\n CREATE_SESSION = \"/auth/session\",\r\n EMOJIS = \"/gifs/trending\",\r\n GET_PRESIGNED_URL = \"/presigned-url\",\r\n BROADCAST_LIST = \"/broadcastlist\",\r\n BROADCAST_LISTS = \"/broadcastlists\",\r\n}\r\n\r\ntype Payload = {\r\n endpoint: string;\r\n body: Object;\r\n method: \"GET\" | \"POST\" | \"PUT\";\r\n token?: string;\r\n headers?: Record<string, string>;\r\n};\r\n\r\nconst chatApi = async <R>(payload: Payload): Promise<APIResponse<R>> => {\r\n try {\r\n const res: AxiosResponse<R> = await axios({\r\n url: payload.endpoint,\r\n method: payload.method,\r\n headers: {\r\n \"Cache-Control\": \"no-cache\",\r\n accessToken: payload.token || \"\",\r\n \"Content-Type\": \"application/json\",\r\n ...payload.headers,\r\n },\r\n data: payload.method === \"POST\" ? payload.body : undefined,\r\n responseType: \"json\",\r\n timeout: 30000,\r\n });\r\n const response = res.data;\r\n if (typeof response === \"object\" && response && \"success\" in response) {\r\n return response as unknown as APIResponse<R>;\r\n }\r\n return { ...response, success: false } as unknown as APIResponse<R>;\r\n } catch (error) {\r\n console.log(error, \"fetch error\");\r\n if (axios.isAxiosError(error) && error.response) {\r\n throw new Error(`HTTP error! Status: ${error.response.status}`);\r\n }\r\n throw new Error(\"An unknown error occurred.\");\r\n }\r\n};\r\n\r\ntype APIResponse<R> = {\r\n success: boolean;\r\n data: R;\r\n message: string;\r\n};\r\n\r\nexport async function CREATE_SESSION<Response>({\r\n userId,\r\n subId,\r\n projectId,\r\n}: {\r\n userId: string;\r\n subId: string;\r\n projectId: string;\r\n}): Promise<APIResponse<Response>> {\r\n return await chatApi<Response>({\r\n endpoint: `${API}${ENDPOINTS.CREATE_SESSION}`,\r\n body: { userId, projectId, subId },\r\n method: \"POST\",\r\n // headers: { \"x-api-key\": apiKey },\r\n });\r\n}\r\n\r\nexport async function GET_CONVERSATIONS<Response>(\r\n token: string | undefined,\r\n): Promise<APIResponse<Response>> {\r\n return await chatApi<Response>({\r\n endpoint: `${API}${ENDPOINTS.CONVERSATIONS}`,\r\n body: {},\r\n method: \"GET\",\r\n token,\r\n });\r\n}\r\n\r\nexport async function GET_CONVERSATION<Response>(\r\n token: string | undefined, conversationId: string\r\n): Promise<APIResponse<Response>> {\r\n return await chatApi<Response>({\r\n endpoint: `${API}${ENDPOINTS.CONVERSATION}/${conversationId}`,\r\n body: {},\r\n method: \"GET\",\r\n token,\r\n });\r\n}\r\n\r\nexport async function GET_BROADCASTLISTS<Response>(\r\n token: string | undefined\r\n): Promise<APIResponse<Response>> {\r\n return await chatApi<Response>({\r\n endpoint: `${API}${ENDPOINTS.BROADCAST_LISTS}`,\r\n body: {},\r\n method: \"GET\",\r\n token,\r\n });\r\n}\r\n\r\nexport async function GET_MESSAGES<Response>(\r\n token: string | undefined,\r\n conversationId: string,\r\n page?: number\r\n): Promise<APIResponse<Response>> {\r\n return await chatApi<Response>({\r\n endpoint: `${API}${ENDPOINTS.MESSAGES}/${conversationId}${page ? \"?page=\" + page : \"\"}`,\r\n body: {},\r\n method: \"GET\",\r\n token,\r\n });\r\n}\r\n\r\nexport async function GET_BROADCAST_LIST_MESSAGES<Response>(\r\n token: string | undefined,\r\n broadcastListId: string,\r\n page?: number\r\n): Promise<APIResponse<Response>> {\r\n return await chatApi<Response>({\r\n endpoint: `${API}${ENDPOINTS.BROADCAST_LIST}/messages/${broadcastListId}${page ? \"?page=\" + page : \"\"}`,\r\n body: {},\r\n method: \"GET\",\r\n token,\r\n });\r\n}\r\n\r\nexport async function GET_EMOJIS<Response>(\r\n token: string | undefined\r\n): Promise<APIResponse<Response>> {\r\n return await chatApi<Response>({\r\n endpoint: `${API}${ENDPOINTS.EMOJIS}`,\r\n body: {},\r\n method: \"GET\",\r\n token,\r\n });\r\n}\r\n\r\nexport async function UPLOAD_MEDIA<Response>(\r\n token: string,\r\n data: Prettify<UploadContent>\r\n): Promise<APIResponse<Response>> {\r\n return await chatApi<Response>({\r\n endpoint: `${API}${ENDPOINTS.UPLOAD}`,\r\n body: data,\r\n method: \"POST\",\r\n token,\r\n });\r\n}\r\n\r\nexport async function GET_PRESIGNED_URL<Response>(\r\n token: string,\r\n data: Prettify<UploadContent & { mediaType: string, uid: string, ext: string }>\r\n): Promise<APIResponse<Response>> {\r\n return await chatApi<Response>({\r\n endpoint: `${API}${ENDPOINTS.GET_PRESIGNED_URL}`,\r\n body: data,\r\n method: \"POST\",\r\n token,\r\n });\r\n}\r\n\r\nexport async function UPLOAD_ATTACHMENT<Response>(\r\n token: string,\r\n data: any\r\n): Promise<APIResponse<Response>> {\r\n return await chatApi<Response>({\r\n endpoint: `${API}${ENDPOINTS.UPLOAD_ATTACHMENT}`,\r\n body: data,\r\n method: \"POST\",\r\n token,\r\n headers: { \"Content-Type\": \"multipart/form-data\" },\r\n });\r\n}\r\n","import Connection from \"./Connection\";\r\nimport {\r\n ClientActions,\r\n Conversation,\r\n ConversationListItem,\r\n ConversationType,\r\n ConversationWithTypingIndicator,\r\n DeletedMessage,\r\n EditedMessage,\r\n MediaType,\r\n Message,\r\n MessageStates,\r\n Prettify,\r\n Reaction,\r\n ReadMessages,\r\n Screens,\r\n SendMessageGenerics,\r\n ServerActions,\r\n UserMeta,\r\n WsPayLoad,\r\n} from \"./types\";\r\nimport {\r\n generateConversationId,\r\n generateFillerTimestamps,\r\n generateId,\r\n} from \"./utils\";\r\nimport { Events } from \"./events\";\r\nimport {\r\n GET_BROADCAST_LIST_MESSAGES,\r\n GET_CONVERSATION,\r\n GET_EMOJIS,\r\n GET_MESSAGES,\r\n GET_PRESIGNED_URL,\r\n UPLOAD_ATTACHMENT,\r\n UPLOAD_MEDIA,\r\n} from \"./fetch\";\r\nimport { Emoticon } from \"./emoticon.type\";\r\nimport moment from \"moment\";\r\nimport { Readable } from \"stream\";\r\n\r\nlet CLEAR_UNREAD_TIMEOUT = 1000;\r\n\r\nexport default class MessageClient {\r\n private static message_client: MessageClient;\r\n private connection: Connection;\r\n // private currentConversationId: string;\r\n private screen: Screens;\r\n private idleTimers: { [key: string]: NodeJS.Timeout | undefined };\r\n\r\n constructor(connection: Connection, conversationId: string) {\r\n this.connection = connection;\r\n this.idleTimers = {};\r\n // this.connection.activeConversationId = conversationId;\r\n this.screen = Screens.CONVERSATIONS;\r\n }\r\n\r\n static getInstace(connection: Connection, conversationId: string) {\r\n if (MessageClient.message_client) {\r\n if (conversationId) {\r\n MessageClient.message_client.connection.activeConversationId =\r\n conversationId;\r\n }\r\n return MessageClient.message_client;\r\n } else {\r\n MessageClient.message_client = new MessageClient(\r\n connection,\r\n conversationId\r\n );\r\n return MessageClient.message_client;\r\n }\r\n }\r\n\r\n private getPublicMethods() {\r\n return {\r\n getMessages: this.getMessages.bind(this),\r\n sendMessage: this.sendMessage.bind(this),\r\n editMessage: this.editMessage.bind(this),\r\n sendTypingNotification: this.sendTypingNotification.bind(this),\r\n reactToMessage: this.reactToMessage.bind(this),\r\n uploadAttachment: this.uploadAttachment.bind(this),\r\n };\r\n }\r\n\r\n private getConversationType(conversationId?: string) {\r\n // const conversationMeta = this.connection.conversationListMeta[conversationId? conversationId : this.connection.activeConversationId];\r\n try {\r\n const conversationMeta =\r\n this.connection.conversationListMeta[\r\n conversationId ? conversationId : this.connection.activeConversationId\r\n ];\r\n return conversationMeta.conversation.conversationType;\r\n } catch (error) {\r\n return \"private-chat\";\r\n }\r\n }\r\n\r\n // removes the last item in the list and adds a new one to the end keeping the original length\r\n private rotateAndInsertMessageList = (\r\n messageList: Array<Message>,\r\n message: Message\r\n ) => {\r\n var list = [...messageList];\r\n if (messageList.length >= 25) {\r\n var list = [...messageList];\r\n list.unshift();\r\n list.push(message);\r\n return list;\r\n }\r\n list.push(message);\r\n return list;\r\n };\r\n\r\n private _createMessage(newMessage: Message) {\r\n try {\r\n if (newMessage) {\r\n const socketMessage = {\r\n action: ServerActions.SEND_MESSAGE,\r\n message: {\r\n messageId: newMessage.messageId,\r\n from: this.connection.userMeta.uid,\r\n to: newMessage.to,\r\n conversationType: this.getConversationType(),\r\n message: { ...newMessage, messageState: MessageStates.SENT },\r\n user: this.connection.userMeta,\r\n token: this.connection.wsAccessConfig.token,\r\n },\r\n };\r\n this.connection.emit(Events.NEW_MESSAGE, {\r\n message: {\r\n ...newMessage,\r\n reactions: [],\r\n messageState: MessageStates.LOADING,\r\n },\r\n });\r\n if (\r\n this.connection.socket &&\r\n this.connection.socket?.readyState === WebSocket.OPEN\r\n ) {\r\n this.connection.socket.send(JSON.stringify(socketMessage));\r\n this.connection.emit(Events.EDITED_MESSAGE, {\r\n message: {\r\n ...newMessage,\r\n reactions: [],\r\n messageState: MessageStates.SENT,\r\n },\r\n });\r\n var conversationMeta =\r\n this.connection.conversationListMeta[newMessage.conversationId];\r\n\r\n var updatedMessages = this.rotateAndInsertMessageList(\r\n conversationMeta.conversation.messages,\r\n {\r\n ...newMessage,\r\n reactions: [],\r\n messageState: MessageStates.SENT,\r\n }\r\n );\r\n const unread = conversationMeta.unread;\r\n this.connection.conversationListMeta[newMessage.conversationId] = {\r\n conversation: {\r\n ...conversationMeta.conversation,\r\n messages: updatedMessages,\r\n },\r\n lastMessage: {\r\n ...newMessage,\r\n },\r\n unread: unread,\r\n };\r\n this.connection.emit(Events.CONVERSATION_LIST_META_CHANGED, {\r\n conversationListMeta: this.connection.conversationListMeta,\r\n });\r\n } else {\r\n this.connection.emit(Events.EDITED_MESSAGE, {\r\n message: {\r\n ...newMessage,\r\n reactions: [],\r\n messageState: MessageStates.FAILED,\r\n },\r\n });\r\n this.connection.conversationListMeta[newMessage.conversationId] = {\r\n conversation:\r\n this.connection.conversationListMeta[newMessage.conversationId]\r\n .conversation,\r\n lastMessage: {\r\n ...newMessage,\r\n messageState: MessageStates.FAILED,\r\n },\r\n unread: [],\r\n };\r\n this.connection.emit(Events.CONVERSATION_LIST_META_CHANGED, {\r\n conversationListMeta: this.connection.conversationListMeta,\r\n });\r\n }\r\n }\r\n } catch (error) {\r\n if (error instanceof Error) {\r\n console.error(error);\r\n this.connection.emit(Events.DELETED_MESSAGE, {\r\n message: {\r\n ...newMessage,\r\n reactions: [],\r\n messageState: MessageStates.FAILED,\r\n },\r\n });\r\n }\r\n }\r\n }\r\n\r\n private editConversationListMetaMessage({ isBroadcast, updatedMessage } : { isBroadcast: boolean, updatedMessage: any }) {\r\n var conversationMeta = isBroadcast? this.connection.broadcastListMeta[updatedMessage.conversationId] : this.connection.conversationListMeta[updatedMessage.conversationId];\r\n var messageId = updatedMessage.messageId;\r\n if (conversationMeta) {\r\n // const socketMessage = {\r\n // action: ServerActions.EDIT_MESSAGE,\r\n // message: {\r\n // ...updatedMessage,\r\n // token: this.connection.wsAccessConfig.token,\r\n // },\r\n // };\r\n // this.connection.socket.send(JSON.stringify(socketMessage));\r\n var prevMessage = conversationMeta.conversation.messages.find(\r\n (m) => m.messageId === messageId\r\n );\r\n if (prevMessage) {\r\n var editedMessage = {\r\n ...prevMessage,\r\n message: updatedMessage.textMessage,\r\n lastEdited: new Date(),\r\n };\r\n var updatedMessageList = conversationMeta.conversation.messages.map(\r\n (m) => {\r\n if (m.messageId === messageId) {\r\n return editedMessage;\r\n }\r\n return m;\r\n }\r\n );\r\n \r\n if(isBroadcast){\r\n this.connection.broadcastListMeta[updatedMessage.conversationId] = {\r\n conversation: {\r\n ...conversationMeta.conversation,\r\n messages: updatedMessageList,\r\n },\r\n lastMessage: null,\r\n unread: [],\r\n };\r\n this.connection.emit(Events.BROADCAST_LIST_META_CHANGED, {\r\n broadcastListMeta: this.connection.broadcastListMeta,\r\n });\r\n }else{\r\n this.connection.conversationListMeta[updatedMessage.conversationId] = {\r\n conversation: {\r\n ...conversationMeta.conversation,\r\n messages: updatedMessageList,\r\n },\r\n lastMessage: { ...editedMessage },\r\n unread: conversationMeta.unread,\r\n };\r\n this.connection.emit(Events.CONVERSATION_LIST_META_CHANGED, {\r\n conversationListMeta: this.connection.conversationListMeta,\r\n });\r\n }\r\n }\r\n }\r\n }\r\n\r\n private _editMessage(message: EditedMessage) {\r\n try {\r\n if (message) {\r\n let updatedMessage = {\r\n ...message,\r\n message: message.textMessage,\r\n lastEdited: new Date(),\r\n };\r\n this.connection.emit(Events.EDITED_MESSAGE, {\r\n message: updatedMessage,\r\n });\r\n this.editConversationListMetaMessage({\r\n isBroadcast: false, \r\n updatedMessage \r\n });\r\n\r\n // update both lists !!might need improvement, maybe go back to using a single list.\r\n this.editConversationListMetaMessage({\r\n isBroadcast: true, \r\n updatedMessage \r\n });\r\n \r\n if(this.connection.socket){\r\n const socketMessage = {\r\n action: ServerActions.EDIT_MESSAGE,\r\n message: {\r\n ...updatedMessage,\r\n token: this.connection.wsAccessConfig.token,\r\n isBroadcast: message.isBroadcast,\r\n user: this.connection.userMeta\r\n },\r\n };\r\n this.connection.socket.send(JSON.stringify(socketMessage));\r\n }\r\n // if(this.connection.socket){\r\n // const socketMessage = {\r\n // action: ServerActions.EDIT_MESSAGE,\r\n // message: {\r\n // ...updatedMessage,\r\n // token: this.connection.wsAccessConfig.token,\r\n // },\r\n // };\r\n // console.log(socketMessage)\r\n // this.connection.socket.send(JSON.stringify(socketMessage));\r\n // }\r\n // if (conversationMeta && this.connection.socket) {\r\n // const socketMessage = {\r\n // action: ServerActions.EDIT_MESSAGE,\r\n // message: {\r\n // ...updatedMessage,\r\n // token: this.connection.wsAccessConfig.token,\r\n // },\r\n // };\r\n // this.connection.socket.send(JSON.stringify(socketMessage));\r\n // var prevMessage = conversationMeta.conversation.messages.find(\r\n // (m) => m.messageId === message.messageId\r\n // );\r\n // if (prevMessage) {\r\n // var editedMessage = {\r\n // ...prevMessage,\r\n // message: message.textMessage,\r\n // lastEdited: new Date(),\r\n // };\r\n // var updatedMessageList = conversationMeta.conversation.messages.map(\r\n // (m) => {\r\n // if (m.messageId === message.messageId) {\r\n // return editedMessage;\r\n // }\r\n // return m;\r\n // }\r\n // );\r\n // this.connection.conversationListMeta[message.conversationId] = {\r\n // conversation: {\r\n // ...conversationMeta.conversation,\r\n // messages: updatedMessageList,\r\n // },\r\n // lastMessage: { ...editedMessage },\r\n // unread: conversationMeta.unread,\r\n // };\r\n // this.connection.emit(Events.CONVERSATION_LIST_META_CHANGED, {\r\n // conversationListMeta: this.connection.conversationListMeta,\r\n // });\r\n // // update message copy on broadcast chat screen\r\n // this.connection.emit(Events.BROADCAST_LIST_META_CHANGED, {\r\n // broadcastListMeta: this.connection.broadcastListMeta,\r\n // });\r\n // }\r\n // }\r\n }\r\n } catch (error) {\r\n // maybe an error listener instead\r\n console.error(error);\r\n }\r\n }\r\n\r\n _updateMessageReactions(\r\n conversationId: string,\r\n messageId: string,\r\n reactions: Reaction[],\r\n config?: { ws: boolean; to: string }\r\n ) {\r\n try {\r\n this.connection.emit(Events.EDITED_MESSAGE, {\r\n message: { messageId, reactions },\r\n });\r\n // should also send to the lastMessage in conversations meta.\r\n if (config?.ws) {\r\n const reactionPayload = {\r\n action: ServerActions.SEND_MESSAGE_REACTION,\r\n message: {\r\n conversationId,\r\n messageId: messageId,\r\n from: this.connection.userMeta.uid,\r\n to: config.to,\r\n reactions,\r\n token: this.connection.wsAccessConfig.token,\r\n user: this.connection.userMeta\r\n },\r\n };\r\n this.connection.socket.send(JSON.stringify(reactionPayload));\r\n }\r\n\r\n var conversationMeta =\r\n this.connection.conversationListMeta[conversationId];\r\n var prevLastMessage = conversationMeta?.lastMessage;\r\n\r\n if (prevLastMessage && prevLastMessage.messageId === messageId) {\r\n var updatedMessage = {\r\n ...prevLastMessage,\r\n reactions,\r\n };\r\n var updatedMessages = this.rotateAndInsertMessageList(\r\n conversationMeta.conversation.messages,\r\n updatedMessage\r\n );\r\n\r\n var updatedConversationListMeta = {\r\n conversation: {\r\n ...conversationMeta.conversation,\r\n messages: updatedMessages,\r\n },\r\n unread: conversationMeta.unread,\r\n lastMessage: {\r\n ...updatedMessage,\r\n },\r\n };\r\n this.connection.conversationListMeta[conversationId] =\r\n updatedConversationListMeta;\r\n this.connection.emit(Events.CONVERSATION_LIST_META_CHANGED, {\r\n conversationListMeta: this.connection.conversationListMeta,\r\n });\r\n }\r\n } catch (error) {\r\n if (error instanceof Error) {\r\n }\r\n }\r\n }\r\n\r\n private storeEditedMessage(data: EditedMessage) {\r\n const conversation = this.connection.conversationMap[data.conversationId];\r\n // const conversation = this.connection.conversationMap[data.conversationId];\r\n if (conversation) {\r\n var message = conversation.messages.find(\r\n (message) => message.messageId === data.messageId\r\n );\r\n if (message) {\r\n const updatedMessage = {\r\n ...message,\r\n message: data.textMessage,\r\n lastEdited: new Date(),\r\n };\r\n this.connection.emit(Events.EDITED_MESSAGE, {\r\n message: updatedMessage,\r\n });\r\n }\r\n }\r\n }\r\n\r\n /**\r\n *\r\n * @param conversationId\r\n * @summary Updates the list of conversations with typing indicator flags\r\n */\r\n private showTypingIndicator = (\r\n conversationId: string,\r\n action: \"START\" | \"STOP\"\r\n ) => {\r\n if (action === \"START\") {\r\n this.connection.emit(Events.HAS_STARTED_TYPING, {\r\n conversationId,\r\n });\r\n } else {\r\n this._clearActiveTypingIndicator(conversationId);\r\n this.idleTimers[conversationId] = setTimeout(() => {\r\n this.connection.emit(Events.HAS_STOPPED_TYPING, {\r\n conversationId,\r\n });\r\n }, 4000);\r\n }\r\n };\r\n // sent to message recipient\r\n private _sendTypingNotification(uid: string) {\r\n if (this.connection.socket) {\r\n this.connection.socket.send(\r\n JSON.stringify({\r\n action: ServerActions.USER_TYPING,\r\n message: {\r\n uid,\r\n conversationId: this.connection.activeConversationId,\r\n action: \"START\",\r\n conversationType: this.getConversationType(\r\n this.connection.activeConversationId\r\n ),\r\n user: this.connection.userMeta,\r\n token: this.connection.wsAccessConfig.token,\r\n },\r\n })\r\n );\r\n }\r\n }\r\n\r\n private _sendStoppedTypingNotification(uid: string) {\r\n if (this.connection.socket) {\r\n this.connection.socket.send(\r\n JSON.stringify({\r\n action: ServerActions.USER_TYPING,\r\n message: {\r\n uid,\r\n conversationId: this.connection.activeConversationId,\r\n action: \"STOP\",\r\n conversationType: this.getConversationType(\r\n this.connection.activeConversationId\r\n ),\r\n user: this.connection.userMeta,\r\n token: this.connection.wsAccessConfig.token,\r\n },\r\n })\r\n );\r\n }\r\n }\r\n\r\n private addMessageToConversation(newMessage: Message, screen: string) {\r\n try {\r\n const conversation =\r\n this.connection.conversationMap[newMessage.conversationId];\r\n if (conversation) {\r\n const updatedMessages = [\r\n ...conversation.messages,\r\n {\r\n ...newMessage,\r\n messageState:\r\n screen === Screens.CHAT ? MessageStates.READ : MessageStates.SENT,\r\n },\r\n ];\r\n\r\n this.connection.conversationMap[newMessage.conversationId] = {\r\n ...conversation,\r\n messages: updatedMessages,\r\n };\r\n this.connection.emit(Events.NEW_MESSAGE, {\r\n message: {\r\n ...newMessage,\r\n messageState:\r\n screen === Screens.CHAT ? MessageStates.READ : MessageStates.SENT,\r\n },\r\n });\r\n\r\n const unread = [\r\n ...this.connection.conversationListMeta[newMessage.conversationId]\r\n .unread,\r\n ];\r\n\r\n if (\r\n newMessage.conversationId !== this.connection.activeConversationId\r\n ) {\r\n unread.push(newMessage.messageId);\r\n }\r\n\r\n const updatedConversationListMeta = {\r\n conversation: {\r\n ...this.connection.conversationListMeta[newMessage.conversationId]\r\n .conversation,\r\n messages: updatedMessages,\r\n },\r\n lastMessage: {\r\n ...newMessage,\r\n },\r\n unread: unread,\r\n };\r\n this.connection.conversationListMeta[newMessage.conversationId] =\r\n updatedConversationListMeta;\r\n this.connection.emit(Events.CONVERSATION_LIST_META_CHANGED, {\r\n conversationListMeta: this.connection.conversationListMeta,\r\n });\r\n }\r\n } catch (error) {\r\n console.error(error);\r\n }\r\n }\r\n\r\n private sendReadNotification(data: ReadMessages) {\r\n if (this.connection.socket) {\r\n this.connection.socket.send(\r\n JSON.stringify({\r\n action: ServerActions.READ_MESSAGES,\r\n message: { \r\n ...data,\r\n user: this.connection.userMeta,\r\n token: this.connection.wsAccessConfig.token },\r\n })\r\n );\r\n }\r\n }\r\n\r\n private _clearActiveTypingIndicator(conversationId: string, emit?: boolean) {\r\n clearTimeout(this.idleTimers[conversationId]);\r\n delete this.idleTimers[conversationId];\r\n if (emit === true) {\r\n this.connection.emit(Events.HAS_STOPPED_TYPING, {\r\n conversationId,\r\n });\r\n }\r\n }\r\n\r\n private _updateConversationListMetaMessages() {}\r\n\r\n readMessages(conversationId: string, data: ReadMessages) {\r\n if (this.connection.socket) {\r\n const socketMessage = {\r\n action: ServerActions.READ_MESSAGES,\r\n message: {\r\n ...data,\r\n user: this.connection.userMeta,\r\n token: this.connection.wsAccessConfig.token,\r\n },\r\n };\r\n \r\n this.connection.socket.send(JSON.stringify(socketMessage));\r\n \r\n // Create an updated conversation list meta immutably\r\n const conversationMeta = this.connection.conversationListMeta[conversationId];\r\n if (conversationMeta) {\r\n const updatedConversationListMeta = {\r\n ...this.connection.conversationListMeta,\r\n [conversationId]: {\r\n ...conversationMeta,\r\n unread: [], // Clears unread without direct mutation\r\n },\r\n };\r\n \r\n this.connection.conversationListMeta = updatedConversationListMeta;\r\n \r\n this.connection.emit(Events.CONVERSATION_LIST_META_CHANGED, {\r\n conversationListMeta: updatedConversationListMeta,\r\n });\r\n }\r\n }\r\n }\r\n \r\n\r\n //this is to clear notifications for user who hasn't opened chat\r\n clearUserUnreadNotifications(conversationId: string, ids: string[]) {\r\n var i = 0;\r\n var len = ids.length;\r\n\r\n while (i < len) {\r\n this.connection.emit(Events.EDITED_MESSAGE, {\r\n message: { messageId: ids[i], messageState: MessageStates.READ },\r\n });\r\n i++;\r\n }\r\n }\r\n\r\n private wsOnError(error: CloseEvent) {\r\n this.connection.emit(Events.CONNECTION_CHANGED, {\r\n isConnected: false,\r\n connecting: false,\r\n fetchingConversations: false,\r\n });\r\n }\r\n\r\n private deleteMessageFromConversationMeta(\r\n conversationId: string,\r\n messageId: string\r\n ) {\r\n try {\r\n const conversationMeta =\r\n this.connection.conversationListMeta[conversationId];\r\n\r\n // check if the message being deleted is the last message\r\n var isLastMessage = messageId === conversationMeta.lastMessage?.messageId;\r\n\r\n if (conversationMeta) {\r\n const filteredMessages = conversationMeta.conversation.messages.filter(\r\n (m) => m.messageId !== messageId\r\n );\r\n\r\n var newLastMessage = filteredMessages[filteredMessages.length - 1];\r\n\r\n var updatedConversationListMeta = {\r\n ...conversationMeta,\r\n conversation: {\r\n ...conversationMeta.conversation,\r\n messages: filteredMessages,\r\n },\r\n };\r\n\r\n if (isLastMessage) {\r\n updatedConversationListMeta.lastMessage = newLastMessage\r\n ? newLastMessage\r\n : null;\r\n }\r\n\r\n this.connection.conversationListMeta[conversationId] =\r\n updatedConversationListMeta;\r\n\r\n this.connection.emit(Events.CONVERSATION_LIST_META_CHANGED, {\r\n conversationListMeta: this.connection.conversationListMeta,\r\n });\r\n } else {\r\n throw new Error(`Conversation with ID ${conversationId} not found.`);\r\n }\r\n } catch (error) {\r\n if (error instanceof Error) {\r\n console.error(error.message);\r\n }\r\n }\r\n }\r\n\r\n reactToMessage({\r\n conversationId,\r\n messageId,\r\n reactions,\r\n to,\r\n }: {\r\n conversationId: string;\r\n messageId: string;\r\n reactions: Reaction[];\r\n to: string;\r\n }) {\r\n if (this.connection) {\r\n this._updateMessageReactions(conversationId, messageId, reactions, {\r\n ws: true,\r\n to,\r\n });\r\n }\r\n }\r\n\r\n sendMessage(newMessage: Prettify<SendMessageGenerics<Message>>) {\r\n if (this.connection) {\r\n const messageId = generateId();\r\n var timeStamps = generateFillerTimestamps();\r\n var messageOwner = {\r\n ...this.connection.userMeta,\r\n ...timeStamps,\r\n };\r\n const messageStruct: Message = {\r\n ...newMessage,\r\n quotedMessageId: newMessage?.quotedMessage?.messageId,\r\n from: messageOwner.uid,\r\n lastEdited: null,\r\n messageState: MessageStates.LOADING,\r\n messageOwner,\r\n messageId,\r\n ...timeStamps,\r\n };\r\n this._createMessage(messageStruct);\r\n }\r\n }\r\n\r\n updateBroadcastList(payload: { broadcastListId: string, participants: string[], name: string }) {\r\n try {\r\n if (\r\n this.connection.socket &&\r\n this.connection.socket?.readyState === WebSocket.OPEN\r\n ) {\r\n const socketMessage = {\r\n action: ServerActions.DELETE_BROADCAST_LIST,\r\n message: {\r\n broadcastListId: payload.broadcastListId,\r\n participants: payload.participants,\r\n name: payload.name,\r\n user: this.connection.userMeta,\r\n token: this.connection.wsAccessConfig.token,\r\n },\r\n };\r\n this.connection.socket.send(JSON.stringify(socketMessage));\r\n }else{\r\n console.error(\"Failed to send broadcast\");\r\n }\r\n } catch (error) {\r\n if(error instanceof Error){\r\n console.error(error.message)\r\n }\r\n }\r\n }\r\n\r\n deleteBroadcastList(payload: { broadcastListId: string, participants: string[], name: string }) {\r\n try {\r\n if (\r\n this.connection.socket &&\r\n this.connection.socket?.readyState === WebSocket.OPEN\r\n ) {\r\n const socketMessage = {\r\n action: ServerActions.UPDATE_BROADCAST_LIST,\r\n message: {\r\n broadcastListId: payload.broadcastListId,\r\n participants: payload.participants,\r\n name: payload.name,\r\n user: this.connection.userMeta,\r\n token: this.connection.wsAccessConfig.token,\r\n },\r\n };\r\n this.connection.socket.send(JSON.stringify(socketMessage));\r\n if(this.connection.broadcastListMeta[payload.broadcastListId]){{\r\n delete this.connection.broadcastListMeta[payload.broadcastListId]\r\n this.connection.emit(Events.BROADCAST_LIST_META_CHANGED, {\r\n broadcastListMeta: this.connection.broadcastListMeta,\r\n });\r\n }}\r\n }else{\r\n console.error(\"Failed to send broadcast\");\r\n }\r\n } catch (error) {\r\n if(error instanceof Error){\r\n console.error(error.message)\r\n }\r\n }\r\n }\r\n\r\n broadcastMessage(\r\n { broadcastListId, participantsIds, newMessage }: { broadcastListId: string, participantsIds: string[], newMessage: Prettify<SendMessageGenerics<Message>> }) {\r\n try {\r\n if (\r\n this.connection.socket &&\r\n this.connection.socket?.readyState === WebSocket.OPEN\r\n ) {\r\n const messageId = generateId();\r\n var timeStamps = generateFillerTimestamps();\r\n var messageOwner = {\r\n ...this.connection.userMeta,\r\n ...timeStamps,\r\n };\r\n const messageStruct: Message = {\r\n ...newMessage,\r\n quotedMessageId: newMessage?.quotedMessage?.messageId,\r\n from: messageOwner.uid,\r\n lastEdited: null,\r\n messageState: MessageStates.SENT,\r\n messageOwner,\r\n messageId,\r\n isBroadcast: true,\r\n broadcastListId,\r\n ...timeStamps,\r\n };\r\n\r\n const socketMessage = {\r\n action: ServerActions.BROADCAST_MESSAGE,\r\n message: {\r\n broadcastListId,\r\n messageId,\r\n from: messageOwner.uid,\r\n to: participantsIds,\r\n shouldEdit: false,\r\n conversationType: \"broadcast-chat\",\r\n message: messageStruct,\r\n user: this.connection.userMeta,\r\n token: this.connection.wsAccessConfig.token,\r\n },\r\n };\r\n\r\n var broadcastItem = this.connection.broadcastListMeta[broadcastListId];\r\n var messages = [...broadcastItem.conversation.messages];\r\n\r\n messages.push(messageStruct);\r\n\r\n var updatedBroadCastItem = { \r\n conversation: { ...broadcastItem.conversation, messages },\r\n lastMessage: null,\r\n unread: []\r\n };\r\n this.connection.broadcastListMeta[broadcastListId] = updatedBroadCastItem;\r\n\r\n this.connection.socket.send(JSON.stringify(socketMessage));\r\n\r\n participantsIds.map(p => {\r\n // send out new message events for any active listeners\r\n const conversationId = generateConversationId(p, messageOwner.uid, this.connection.projectConfig.projectId);\r\n this.connection.emit(Events.NEW_MESSAGE, {\r\n message: {\r\n ...messageStruct,\r\n conversationId,\r\n reactions: [],\r\n },\r\n });\r\n var prevConversation = this.connection.conversationListMeta[conversationId]\r\n // if a conversation exists, update all user conversations in conversationListMeta\r\n if(prevConversation){\r\n this.connection.conversationListMeta[conversationId] = {\r\n conversation: {\r\n ...prevConversation.conversation,\r\n messages: [ ...prevConversation.conversation.messages, {\r\n ...messageStruct,\r\n conversationId,\r\n reactions: [],\r\n } ],\r\n },\r\n lastMessage: {\r\n ...messageStruct,\r\n conversationId,\r\n reactions: [],\r\n },\r\n unread: prevConversation.unread,\r\n };\r\n }\r\n });\r\n // send the updated conversationListMeta to active listeners\r\n this.connection.emit(Events.CONVERSATION_LIST_META_CHANGED, {\r\n conversationListMeta: this.connection.conversationListMeta,\r\n });\r\n\r\n // send a new message event for the current broadcast chat\r\n this.connection.emit(Events.NEW_MESSAGE, {\r\n message: {\r\n ...messageStruct,\r\n conversationId: broadcastListId,\r\n reactions: [],\r\n },\r\n });\r\n\r\n this.connection.emit(Events.BROADCAST_LIST_META_CHANGED, {\r\n broadcastListMeta: this.connection.broadcastListMeta,\r\n });\r\n } else {\r\n console.error(\"Failed to send broadcast\");\r\n }\r\n } catch (error) {\r\n if (error instanceof Error) {\r\n console.error(error.message);\r\n }\r\n }\r\n }\r\n\r\n editMessage(message: Omit<EditedMessage, \"from\">) {\r\n if (this.connection) {\r\n this._editMessage({\r\n ...message,\r\n from: this.connection.userMeta.uid,\r\n });\r\n }\r\n }\r\n\r\n sendTypingNotification(uid: string) {\r\n if (this.connection) {\r\n this._sendTypingNotification(uid);\r\n }\r\n }\r\n\r\n sendStoppedTypingNotification(uid: string) {\r\n if (this.connection) {\r\n this._sendStoppedTypingNotification(uid);\r\n }\r\n }\r\n\r\n deleteMessage(messageId: string, to: string, conversationId: string) {\r\n if (this.connection) {\r\n this.connection.socket.send(\r\n JSON.stringify({\r\n action: ServerActions.DELETE_MESSAGE,\r\n message: {\r\n messageId,\r\n to,\r\n conversationId,\r\n conversationType: this.getConversationType(),\r\n user: this.connection.userMeta,\r\n token: this.connection.wsAccessConfig.token,\r\n },\r\n })\r\n );\r\n this.connection.emit(Events.DELETED_MESSAGE, {\r\n message: {\r\n conversationId,\r\n messageId,\r\n },\r\n });\r\n this.deleteMessageFromConversationMeta(conversationId, messageId);\r\n }\r\n }\r\n\r\n async getMessages(page?: number) {\r\n if (this.connection) {\r\n try {\r\n const response = await GET_MESSAGES<{ messages: Message[] }>(\r\n this.connection.wsAccessConfig.token,\r\n this.connection.activeConversationId,\r\n page\r\n );\r\n if (response.success) {\r\n const sortedMessages = [...response.data.messages].sort((a, b) => {\r\n const dateA = moment(a.createdAt).valueOf();\r\n const dateB = moment(b.createdAt).valueOf();\r\n return dateA - dateB;\r\n });\r\n return sortedMessages;\r\n } else {\r\n return [];\r\n }\r\n } catch (err) {\r\n console.error(err);\r\n return [];\r\n }\r\n } else {\r\n return [];\r\n }\r\n }\r\n\r\n async getBroadcastListMessages(page?: number) {\r\n if (this.connection) {\r\n try {\r\n const response = await GET_BROADCAST_LIST_MESSAGES<{\r\n messages: Message[];\r\n }>(\r\n this.connection.wsAccessConfig.token,\r\n this.connection.activeConversationId,\r\n page\r\n );\r\n if (response.success) {\r\n const sortedMessages = [...response.data.messages].sort((a, b) => {\r\n const dateA = moment(a.createdAt).valueOf();\r\n const dateB = moment(b.createdAt).valueOf();\r\n return dateA - dateB;\r\n });\r\n return sortedMessages;\r\n } else {\r\n return [];\r\n }\r\n } catch (err) {\r\n console.error(err);\r\n return [];\r\n }\r\n } else {\r\n return [];\r\n }\r\n }\r\n\r\n async getConversation(conversationId: string) {\r\n if (this.connection) {\r\n try {\r\n const response = await GET_CONVERSATION<{ conversation: Conversation }>(\r\n this.connection.wsAccessConfig.token,\r\n conversationId\r\n );\r\n if (response.success) {\r\n return response.data.conversation;\r\n } else {\r\n return null;\r\n }\r\n } catch (err) {\r\n return null;\r\n }\r\n } else {\r\n return null;\r\n }\r\n }\r\n\r\n async getEmojiList() {\r\n if (this.connection) {\r\n try {\r\n const response = await GET_EMOJIS<{ gifs: Emoticon[] }>(\r\n this.connection.wsAccessConfig.token\r\n );\r\n return response.data.gifs;\r\n } catch (err) {\r\n return [];\r\n }\r\n } else {\r\n return [];\r\n }\r\n }\r\n\r\n async uploadAttachment({\r\n base64,\r\n fileKey,\r\n }: {\r\n base64: string;\r\n fileKey: string;\r\n }) {\r\n if (this.connection) {\r\n try {\r\n const res = await UPLOAD_MEDIA<{ url: string }>(\r\n this.connection.wsAccessConfig?.token,\r\n {\r\n base64,\r\n conversationId: this.connection.activeConversationId,\r\n key: fileKey,\r\n }\r\n );\r\n return res;\r\n } catch (error) {\r\n if (error instanceof Error) {\r\n console.error(error.message);\r\n }\r\n }\r\n } else {\r\n throw new Error(\"No connection established\");\r\n }\r\n }\r\n\r\n private isReadableStream(uri: any): uri is Readable {\r\n return uri && typeof uri.pipe === \"function\";\r\n }\r\n\r\n async uploadFile(\r\n uri: string | NodeJS.ReadableStream | Buffer | File,\r\n meta: {\r\n filename: string;\r\n mimeType: string;\r\n ext: string\r\n }\r\n ) {\r\n try {\r\n // Get the presigned URL for upload\r\n const res = await GET_PRESIGNED_URL<{\r\n uploadUrl: string;\r\n s3Link: string;\r