import { useContext, useEffect, useState } from 'react'; import { throttle } from '/imports/utils/throttle'; import { ChatContext, ACTIONS, MESSAGE_TYPES } from './context'; import { UsersContext } from '../users-context/context'; import { makeCall } from '/imports/ui/services/api'; import ChatLogger from '/imports/ui/components/chat/chat-logger/ChatLogger'; import Auth from '/imports/ui/services/auth'; import CollectionEventsBroker from '/imports/ui/services/LiveDataEventBroker/LiveDataEventBroker'; let prevUserData = {}; let currentUserData = {}; let messageQueue = []; let referenceIds = {}; const CHAT_CONFIG = Meteor.settings.public.chat; const SYSTEM_CHAT_TYPE = CHAT_CONFIG.type_system; const CHAT_CLEAR_MESSAGE = CHAT_CONFIG.system_messages_keys.chat_clear; const ITENS_PER_PAGE = CHAT_CONFIG.itemsPerPage; const TIME_BETWEEN_FETCHS = CHAT_CONFIG.timeBetweenFetchs; const EVENT_NAME = 'bbb-group-chat-messages-subscription-has-stoppped'; const EVENT_NAME_SUBSCRIPTION_READY = 'bbb-group-chat-messages-subscriptions-ready'; const getMessagesBeforeJoinCounter = async () => { const counter = await makeCall('chatMessageBeforeJoinCounter'); return counter; }; const startSyncMessagesbeforeJoin = async (dispatch) => { const chatsMessagesCount = await getMessagesBeforeJoinCounter(); const pagesPerChat = chatsMessagesCount.map((chat) => ({ ...chat, pages: Math.ceil(chat.count / ITENS_PER_PAGE), syncedPages: 0, })); const syncRoutine = async (chatsToSync) => { if (!chatsToSync.length) return; const pagesToFetch = [...chatsToSync].sort((a, b) => a.pages - b.pages); const chatWithLessPages = pagesToFetch[0]; chatWithLessPages.syncedPages += 1; const messagesFromPage = await makeCall( 'fetchMessagePerPage', chatWithLessPages.chatId, chatWithLessPages.syncedPages, ); if (messagesFromPage.length) { dispatch({ type: ACTIONS.ADDED, value: messagesFromPage, messageType: MESSAGE_TYPES.HISTORY, }); dispatch({ type: ACTIONS.SYNC_STATUS, value: { chatId: chatWithLessPages.chatId, percentage: Math.floor((chatWithLessPages.syncedPages / chatWithLessPages.pages) * 100), }, }); } await new Promise((r) => setTimeout(r, TIME_BETWEEN_FETCHS)); syncRoutine(pagesToFetch.filter((chat) => !(chat.syncedPages > chat.pages))); }; syncRoutine(pagesPerChat); }; const Adapter = () => { const usingChatContext = useContext(ChatContext); const { dispatch } = usingChatContext; const usingUsersContext = useContext(UsersContext); const { users } = usingUsersContext; const [syncStarted, setSync] = useState(true); const [subscriptionReady, setSubscriptionReady] = useState(false); ChatLogger.trace('chatAdapter::body::users', users[Auth.meetingID]); useEffect(() => { window.addEventListener(EVENT_NAME, () => { /* needed to prevent an issue with dupĺicated messages when user role is changed more info: https://github.com/bigbluebutton/bigbluebutton/issues/11842 */ if (prevUserData.role && prevUserData?.role !== currentUserData?.role) { dispatch({ type: ACTIONS.CLEAR_STREAM_MESSAGES, }); } }); window.addEventListener(EVENT_NAME_SUBSCRIPTION_READY, () => { setSubscriptionReady(true); }); }, []); useEffect(() => { const connectionStatus = Meteor.status(); if (connectionStatus.connected && !syncStarted && Auth.userID && subscriptionReady) { setTimeout(() => { setSync(true); startSyncMessagesbeforeJoin(dispatch); }, 1000); } }, [Meteor.status().connected, syncStarted, Auth.userID, subscriptionReady]); /* needed to prevent an issue with dupĺicated messages when user role is changed more info: https://github.com/bigbluebutton/bigbluebutton/issues/11842 */ useEffect(() => { if (users[Auth.meetingID] && users[Auth.meetingID][Auth.userID]) { if (currentUserData?.role !== users[Auth.meetingID][Auth.userID]?.role) { prevUserData = currentUserData; referenceIds = {}; } currentUserData = users[Auth.meetingID][Auth.userID]; } }, [usingUsersContext]); useEffect(() => { if (!Meteor.status().connected) return () => null; setSync(false); dispatch({ type: ACTIONS.CLEAR_ALL, }); const throttledDispatch = throttle( () => { const dispatchedMessageQueue = [...messageQueue]; messageQueue = []; dispatch({ type: ACTIONS.ADDED, value: dispatchedMessageQueue, messageType: MESSAGE_TYPES.STREAM, }); }, 1000, { trailing: true, leading: true }, ); const insertToContext = (fields) => { if (fields.id === `${SYSTEM_CHAT_TYPE}-${CHAT_CLEAR_MESSAGE}`) { messageQueue = []; dispatch({ type: ACTIONS.REMOVED, }); } if (referenceIds[fields.referenceId]) return; referenceIds[fields.referenceId] = true; messageQueue.push(fields); throttledDispatch(); }; CollectionEventsBroker.addListener('group-chat-msg', 'added', insertToContext); }, [Meteor.status().connected, Meteor.connection._lastSessionId]); return null; }; export default Adapter;