@ethora/chat-component-rn 26.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +661 -0
- package/README.md +148 -0
- package/empty-shim.js +15 -0
- package/index.js +13 -0
- package/metro.js +72 -0
- package/package.json +99 -0
- package/src/api.config.ts +34 -0
- package/src/assets/BG.png +0 -0
- package/src/assets/BG.tsx +56 -0
- package/src/assets/File.svg +4 -0
- package/src/assets/ForzaCare.png +0 -0
- package/src/assets/ForzaCare.tsx +27 -0
- package/src/assets/icons.tsx +685 -0
- package/src/assets/images.tsx +41 -0
- package/src/assets/test.xml +243 -0
- package/src/components/AuthForms/Login.tsx +295 -0
- package/src/components/AuthForms/LoginAnonym.tsx +68 -0
- package/src/components/AuthForms/Register.tsx +8 -0
- package/src/components/ContextMenu/ContextMenuComponents.tsx +87 -0
- package/src/components/DropdownMenu/DropdownMenu.tsx +244 -0
- package/src/components/ExampleComponents/CustomMessage.tsx +92 -0
- package/src/components/ExampleComponents/CustomTypingIndicatorExamples.tsx +239 -0
- package/src/components/Header/HeaderRoomList.tsx +104 -0
- package/src/components/InputComponents/AudioInput.tsx +24 -0
- package/src/components/InputComponents/AudioRecorder.tsx +100 -0
- package/src/components/InputComponents/MediaInput.tsx +114 -0
- package/src/components/InputComponents/RecordingIndicator.tsx +77 -0
- package/src/components/InputComponents/Search.tsx +135 -0
- package/src/components/InputComponents/TextInput.tsx +66 -0
- package/src/components/MainComponents/ChatHeader.tsx +163 -0
- package/src/components/MainComponents/ChatRoom.tsx +392 -0
- package/src/components/MainComponents/ChatWrapper.tsx +318 -0
- package/src/components/MainComponents/ChooseChatMessage.tsx +44 -0
- package/src/components/MainComponents/ConnectionBanner.tsx +33 -0
- package/src/components/MainComponents/EditWrapper.tsx +72 -0
- package/src/components/MainComponents/LoginWrapper.tsx +134 -0
- package/src/components/MainComponents/MediaMessage.tsx +55 -0
- package/src/components/MainComponents/MessageContainer.tsx +122 -0
- package/src/components/MainComponents/MessageList.tsx +429 -0
- package/src/components/MainComponents/NoMessagesPlaceholder.tsx +49 -0
- package/src/components/MainComponents/ProfileImagePlaceholder.tsx +143 -0
- package/src/components/MainComponents/RNStyled.tsx +34 -0
- package/src/components/MainComponents/ReduxWrapper.tsx +40 -0
- package/src/components/MainComponents/RoomList.tsx +246 -0
- package/src/components/MainComponents/Select.tsx +165 -0
- package/src/components/MainComponents/Switch.tsx +73 -0
- package/src/components/MainComponents/SystemMessage.tsx +42 -0
- package/src/components/Menu/HeaderRoomListMenu.tsx +169 -0
- package/src/components/MenuRoom/MenuRoom.tsx +46 -0
- package/src/components/MessageBubble/Avatar.tsx +84 -0
- package/src/components/MessageBubble/BottomReplyContainer.tsx +117 -0
- package/src/components/MessageBubble/CustomDivider.tsx +10 -0
- package/src/components/MessageBubble/DeletedMessage.tsx +36 -0
- package/src/components/MessageBubble/Message.tsx +400 -0
- package/src/components/MessageBubble/MessageInteractions.tsx +247 -0
- package/src/components/MessageBubble/MessageReaction.tsx +149 -0
- package/src/components/MessageBubble/MessageReply.tsx +50 -0
- package/src/components/MessageBubble/MessageTranslations.tsx +30 -0
- package/src/components/Modals/AddMembersModal/AddMembersModal.tsx +142 -0
- package/src/components/Modals/ChatProfileModal/ChatProfileModal.tsx +471 -0
- package/src/components/Modals/ChatProfileModal/DeleteChatModal.tsx +91 -0
- package/src/components/Modals/FilePreviewModal/FilePreviewModal.tsx +298 -0
- package/src/components/Modals/Modal/Modal.tsx +52 -0
- package/src/components/Modals/ModalHeaderComponent.tsx +41 -0
- package/src/components/Modals/ModalSelectMedia/ModalSelectMedia.tsx +285 -0
- package/src/components/Modals/ModalWrapper/ModalWrapper.tsx +103 -0
- package/src/components/Modals/NewChatModal/ModalContent.tsx +151 -0
- package/src/components/Modals/NewChatModal/NewChatModal.tsx +391 -0
- package/src/components/Modals/NewChatModal/UpdNewChatModal.tsx +136 -0
- package/src/components/Modals/SelectUsersModal/SelectUsersModal.tsx +134 -0
- package/src/components/Modals/SettingsModals/BlockedUsers/BlockedUsersModal.tsx +63 -0
- package/src/components/Modals/SettingsModals/DocumentShares/DocumentSharesModal.tsx +115 -0
- package/src/components/Modals/SettingsModals/ManageDataModal/ManageDataModal.tsx +121 -0
- package/src/components/Modals/SettingsModals/ManageDataModal/StyledComponents.ts +0 -0
- package/src/components/Modals/SettingsModals/ProfileShares/ProfileShares.tsx +65 -0
- package/src/components/Modals/SettingsModals/Referrals/Referrals.tsx +90 -0
- package/src/components/Modals/SettingsModals/SharedStyledComponents.ts +74 -0
- package/src/components/Modals/SettingsModals/Visibility/RadioInput.tsx +59 -0
- package/src/components/Modals/SettingsModals/Visibility/StyledComponents.ts +32 -0
- package/src/components/Modals/SettingsModals/Visibility/VisibilityModal.tsx +146 -0
- package/src/components/Modals/UserProfileModal/EditUserModal.tsx +163 -0
- package/src/components/Modals/UserProfileModal/UserProfileModal.tsx +410 -0
- package/src/components/Modals/UserSettingsModal/UserSettingsModal.tsx +85 -0
- package/src/components/Modals/styledModalComponents.tsx +181 -0
- package/src/components/OperationalModal/OperationalModal.tsx +78 -0
- package/src/components/RoomComponents/ChatRoomItem.tsx +219 -0
- package/src/components/RoomComponents/LastMessageItem.tsx +47 -0
- package/src/components/RoomComponents/styled/LastAudioMessage.tsx +62 -0
- package/src/components/RoomComponents/styled/LastMessageEmoji.tsx +26 -0
- package/src/components/RoomComponents/styled/LastMessageFile.tsx +54 -0
- package/src/components/RoomComponents/styled/LastMessagePhoto.tsx +54 -0
- package/src/components/RoomComponents/styled/LastMessageVideo.tsx +70 -0
- package/src/components/RoomComponents/styled/LastTextMessage.tsx +65 -0
- package/src/components/RoomComponents/styled/StyledRoomComponents.ts +53 -0
- package/src/components/SortDropDown/index.tsx +76 -0
- package/src/components/Thread/ThreadHeader.tsx +41 -0
- package/src/components/Thread/ThreadWrapper.tsx +270 -0
- package/src/components/Toast/Toast.tsx +128 -0
- package/src/components/Toast/index.ts +13 -0
- package/src/components/UsersList/StyledComponents.ts +30 -0
- package/src/components/UsersList/UsersList.tsx +112 -0
- package/src/components/styled/ActionButton.tsx +40 -0
- package/src/components/styled/AudioMessage.tsx +82 -0
- package/src/components/styled/Button.tsx +107 -0
- package/src/components/styled/ChatWrapperBox.tsx +9 -0
- package/src/components/styled/DateLabel.tsx +77 -0
- package/src/components/styled/Loader.tsx +65 -0
- package/src/components/styled/MediaFilePreview.tsx +42 -0
- package/src/components/styled/MediaModal.tsx +54 -0
- package/src/components/styled/MessageImage.tsx +54 -0
- package/src/components/styled/NewMessageLabel.tsx +50 -0
- package/src/components/styled/RoomListComponents/index.tsx +164 -0
- package/src/components/styled/SendInput.tsx +297 -0
- package/src/components/styled/StyledComponents.ts +320 -0
- package/src/components/styled/StyledInput.tsx +101 -0
- package/src/components/styled/StyledInputComponents/AudioComponents.tsx +1 -0
- package/src/components/styled/StyledInputComponents/Composing.tsx +97 -0
- package/src/components/styled/StyledInputComponents/CustomTypingIndicator.tsx +261 -0
- package/src/components/styled/StyledInputComponents/MediaComponents.tsx +90 -0
- package/src/components/styled/StyledInputComponents/StyledInputComponents.tsx +131 -0
- package/src/components/styled/StyledInputComponents/VideoComponents.tsx +49 -0
- package/src/components/styled/TreadLabel.tsx +50 -0
- package/src/components/styled/UnsupportedType.tsx +95 -0
- package/src/components/styled/VideoMessage.tsx +68 -0
- package/src/context/MessageNotificationContext.tsx +335 -0
- package/src/context/PushNotificationProvider.tsx +78 -0
- package/src/context/ToastContext.tsx +63 -0
- package/src/context/toastProvider.tsx +0 -0
- package/src/context/xmppProvider.tsx +364 -0
- package/src/helpers/block_scroll.ts +95 -0
- package/src/helpers/chatAutoEnterer.ts +46 -0
- package/src/helpers/checkUniqueUsers.tsx +31 -0
- package/src/helpers/constants/CHAT_TYPES.ts +6 -0
- package/src/helpers/constants/LANGUAGE_OPTIONS.ts +14 -0
- package/src/helpers/constants/LOCAL_STORAGE.ts +3 -0
- package/src/helpers/constants/MESSAGE_INTERACTIONS.ts +29 -0
- package/src/helpers/constants/MODAL_TYPES.ts +57 -0
- package/src/helpers/constants/PLATFORM_CONSTANTS.ts +3 -0
- package/src/helpers/createMainMessageForThread.ts +23 -0
- package/src/helpers/createMessageFromXml.ts +118 -0
- package/src/helpers/createRoomFromApi.tsx +27 -0
- package/src/helpers/createUserNameFromSetUser.ts +15 -0
- package/src/helpers/dateComparison.ts +12 -0
- package/src/helpers/debounce.ts +10 -0
- package/src/helpers/ensureScopedChatCache.ts +61 -0
- package/src/helpers/extractUniqueMembersFromRooms.ts +45 -0
- package/src/helpers/formatNumberWithCommas.ts +3 -0
- package/src/helpers/getBooleanFromString.ts +7 -0
- package/src/helpers/getDataFromXml.ts +80 -0
- package/src/helpers/getNumberFromString.ts +5 -0
- package/src/helpers/getRoomsWithRetry.ts +45 -0
- package/src/helpers/getTintedColor.ts +28 -0
- package/src/helpers/getUnnamedUsers.ts +39 -0
- package/src/helpers/handleCopyClick.ts +3 -0
- package/src/helpers/handleXmppError.ts +14 -0
- package/src/helpers/hashcolor.ts +34 -0
- package/src/helpers/historyPreloadScheduler.ts +264 -0
- package/src/helpers/initRoomsPresence.ts +32 -0
- package/src/helpers/initXmppRooms.tsx +71 -0
- package/src/helpers/insertMessageWithDelimiter.ts +96 -0
- package/src/helpers/isChatIdPresentInArray.ts +54 -0
- package/src/helpers/localStorageUser.ts +9 -0
- package/src/helpers/markdownParser.tsx +295 -0
- package/src/helpers/nanoToMs.ts +3 -0
- package/src/helpers/parseMessageBody.tsx +516 -0
- package/src/helpers/parseUrls.tsx +42 -0
- package/src/helpers/resolveInitBeforeLoadUser.ts +240 -0
- package/src/helpers/storeConsole.ts +26 -0
- package/src/helpers/transformTranslatations.ts +16 -0
- package/src/helpers/updateMessagesTillLast.tsx +104 -0
- package/src/helpers/updatedChatLastTimestamps.ts +29 -0
- package/src/helpers/validator.ts +20 -0
- package/src/helpers/walletUsername.ts +12 -0
- package/src/hooks/hooks.ts +6 -0
- package/src/hooks/useChatSettingState.tsx +38 -0
- package/src/hooks/useChatWrapperInit.ts +266 -0
- package/src/hooks/useComposing.tsx +59 -0
- package/src/hooks/useCustomTypingIndicator.tsx +193 -0
- package/src/hooks/useEventHandlers.tsx +81 -0
- package/src/hooks/useGetNewArchRoom.tsx +57 -0
- package/src/hooks/useHeapSender.tsx +88 -0
- package/src/hooks/useLocalStorage.tsx +53 -0
- package/src/hooks/useLogout.tsx +31 -0
- package/src/hooks/useMessageHeapState.tsx +11 -0
- package/src/hooks/useMessageLoaderQueue.tsx +90 -0
- package/src/hooks/useMessageQueue.ts +146 -0
- package/src/hooks/usePendingNotification.ts +49 -0
- package/src/hooks/usePrevious.tsx +15 -0
- package/src/hooks/usePushNotifications.ts +106 -0
- package/src/hooks/useQRCodeChatHandler.ts +58 -0
- package/src/hooks/useRoomInitialization.tsx +160 -0
- package/src/hooks/useRoomState.tsx +47 -0
- package/src/hooks/useRoomUrl.tsx +41 -0
- package/src/hooks/useSendMessage.tsx +306 -0
- package/src/hooks/useUnreadMessagesCounter.ts +55 -0
- package/src/hooks/useXmppInitialization.ts +148 -0
- package/src/main.ts +45 -0
- package/src/networking/api-requests/auth.api.ts +140 -0
- package/src/networking/api-requests/push.api.ts +55 -0
- package/src/networking/api-requests/roomMembers.api.ts +20 -0
- package/src/networking/api-requests/rooms.api.ts +142 -0
- package/src/networking/api-requests/user.api.ts +65 -0
- package/src/networking/apiClient.ts +149 -0
- package/src/networking/helpers/refreshChecker.ts +13 -0
- package/src/networking/stanzaHandlers.ts +418 -0
- package/src/networking/xmpp/actionSetTimestampToPrivateStore.xmpp.ts +37 -0
- package/src/networking/xmpp/allRoomPresences.xmpp.ts +10 -0
- package/src/networking/xmpp/createPrivateRoom.xmpp.ts +26 -0
- package/src/networking/xmpp/createRoom.xmpp.ts +35 -0
- package/src/networking/xmpp/createRoomPresence.xmpp.ts +74 -0
- package/src/networking/xmpp/createTimeoutPromise.xmpp.ts +15 -0
- package/src/networking/xmpp/deleteMessage.xmpp.ts +19 -0
- package/src/networking/xmpp/editMessage.xmpp.ts +46 -0
- package/src/networking/xmpp/getChatsPrivateStoreRequest.xmpp.ts +57 -0
- package/src/networking/xmpp/getHistory.xmpp.ts +140 -0
- package/src/networking/xmpp/getLastMessageArchive.xmpp.ts +24 -0
- package/src/networking/xmpp/getRoomInfo.xmpp.ts +15 -0
- package/src/networking/xmpp/getRoomMembers.xmpp.ts +13 -0
- package/src/networking/xmpp/getRooms.xmpp.ts +43 -0
- package/src/networking/xmpp/getRoomsPaged.xmpp.ts +59 -0
- package/src/networking/xmpp/handlePong.xmpp.ts +7 -0
- package/src/networking/xmpp/handleStanzas.xmpp.ts +55 -0
- package/src/networking/xmpp/inviteRoomRequest.xmpp.ts +25 -0
- package/src/networking/xmpp/leaveTheRoom.xmpp.ts +15 -0
- package/src/networking/xmpp/listenerManager.ts +116 -0
- package/src/networking/xmpp/presenceInRoom.xmpp.ts +57 -0
- package/src/networking/xmpp/roomConfig.xmpp.ts +71 -0
- package/src/networking/xmpp/sendMediaMessage.xmpp.ts +97 -0
- package/src/networking/xmpp/sendMessageReaction.xmpp.ts +41 -0
- package/src/networking/xmpp/sendPing.xmpp.ts +21 -0
- package/src/networking/xmpp/sendTextMessage.xmpp.ts +99 -0
- package/src/networking/xmpp/sendTextMessageWithTranslateTag.xmpp.ts +44 -0
- package/src/networking/xmpp/sendTypingRequest.xmpp.ts +26 -0
- package/src/networking/xmpp/setChatsPrivateStoreRequest.xmpp.ts +48 -0
- package/src/networking/xmpp/setMeAsOwner.xmpp.ts +52 -0
- package/src/networking/xmpp/setRoomImage.xmpp.ts +26 -0
- package/src/networking/xmpp/setVCard.xmpp.ts +19 -0
- package/src/networking/xmpp/subscribeToRoomMessages.xmpp.ts +75 -0
- package/src/networking/xmppClient.ts +603 -0
- package/src/roomStore/Middleware/logoutMiddleware.tsx +24 -0
- package/src/roomStore/Middleware/newMessageMidlleware.tsx +87 -0
- package/src/roomStore/Middleware/reactionsMiddleware.tsx +77 -0
- package/src/roomStore/Middleware/testMiddleware.tsx +20 -0
- package/src/roomStore/Middleware/unreadMidlleware.tsx +58 -0
- package/src/roomStore/chatSettingsSlice.ts +178 -0
- package/src/roomStore/index.ts +68 -0
- package/src/roomStore/persistence.ts +152 -0
- package/src/roomStore/roomHeapSlice.ts +37 -0
- package/src/roomStore/roomsSlice.ts +353 -0
- package/src/services/pushNotifications.ts +170 -0
- package/src/services/pushSubscriptionService.ts +145 -0
- package/src/types/messages.ts +61 -0
- package/src/types/models/action.model.ts +30 -0
- package/src/types/models/config.model.ts +16 -0
- package/src/types/models/customComponents.model.ts +53 -0
- package/src/types/models/language.model.ts +11 -0
- package/src/types/models/media.model.ts +5 -0
- package/src/types/models/message.model.ts +69 -0
- package/src/types/models/modal.model.ts +9 -0
- package/src/types/models/room.model.ts +120 -0
- package/src/types/models/user.model.ts +90 -0
- package/src/types/models/xmpp.model.ts +155 -0
- package/src/types/types.ts +505 -0
- package/src/utils/PriorityQueue.ts +47 -0
- package/src/utils/actionHelpers.ts +40 -0
- package/src/utils/actionLogger.ts +64 -0
- package/src/utils/clientRegistry.ts +80 -0
- package/src/utils/devLogger.ts +197 -0
- package/src/utils/messageNotificationManager.ts +108 -0
- package/src/utils/resendMessage.ts +87 -0
- package/src/utils/toastEmitter.ts +27 -0
- package/src/utils/xmppMemoryMonitor.ts +63 -0
- package/src/version.ts +1 -0
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { useEffect } from 'react';
|
|
2
|
+
import { setIsLoading } from '../roomStore/roomsSlice';
|
|
3
|
+
import { useXmppClient } from '../context/xmppProvider';
|
|
4
|
+
import { IConfig, IMessage, IRoom } from '../types/types';
|
|
5
|
+
import { useDispatch } from 'react-redux';
|
|
6
|
+
import useGetNewArchRoom from './useGetNewArchRoom';
|
|
7
|
+
|
|
8
|
+
const countUndefinedText = (arr: IMessage[]) =>
|
|
9
|
+
(Array.isArray(arr) ? arr : []).filter(
|
|
10
|
+
(item) => item?.body === undefined
|
|
11
|
+
)?.length;
|
|
12
|
+
|
|
13
|
+
const hasLoadedRoomHistory = (room?: IRoom): boolean => {
|
|
14
|
+
const messages = Array.isArray(room?.messages) ? room.messages : [];
|
|
15
|
+
if (!messages.length) {return false;}
|
|
16
|
+
return messages.some(
|
|
17
|
+
(message) =>
|
|
18
|
+
message?.id !== 'delimiter-new' &&
|
|
19
|
+
message?.pending !== true &&
|
|
20
|
+
!!String(message?.body || '').trim()
|
|
21
|
+
);
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export const useRoomInitialization = (
|
|
25
|
+
activeRoomJID: string,
|
|
26
|
+
roomsList: Record<string, IRoom>,
|
|
27
|
+
config: IConfig,
|
|
28
|
+
messageLength: number
|
|
29
|
+
) => {
|
|
30
|
+
const { client } = useXmppClient();
|
|
31
|
+
const dispatch = useDispatch();
|
|
32
|
+
|
|
33
|
+
const syncRooms = useGetNewArchRoom();
|
|
34
|
+
|
|
35
|
+
// Fast active-room join. Mirrors web's first effect: as soon as the
|
|
36
|
+
// active room changes, eagerly send presence + ask for room info so
|
|
37
|
+
// the header populates and the MUC join lets MAM history flow.
|
|
38
|
+
// Without this, REST-hydrated rooms (which are in `roomsList`
|
|
39
|
+
// without a join) silently return zero messages when getHistoryStanza
|
|
40
|
+
// runs — the user sees an empty chat after tapping a room.
|
|
41
|
+
useEffect(() => {
|
|
42
|
+
if (client && activeRoomJID) {
|
|
43
|
+
client.setActiveRoomJid?.(activeRoomJID);
|
|
44
|
+
client.promoteRoomHistory?.(activeRoomJID);
|
|
45
|
+
try {
|
|
46
|
+
client.presenceInRoomStanza(activeRoomJID);
|
|
47
|
+
} catch {
|
|
48
|
+
/* non-fatal */
|
|
49
|
+
}
|
|
50
|
+
try {
|
|
51
|
+
client.getRoomInfoStanza?.(activeRoomJID);
|
|
52
|
+
} catch {
|
|
53
|
+
/* non-fatal */
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
if (client && !activeRoomJID) {
|
|
57
|
+
client.setActiveRoomJid?.(null);
|
|
58
|
+
}
|
|
59
|
+
}, [client, activeRoomJID]);
|
|
60
|
+
|
|
61
|
+
useEffect(() => {
|
|
62
|
+
const activeRoom = roomsList?.[activeRoomJID];
|
|
63
|
+
const shouldLoadActiveHistory =
|
|
64
|
+
!!activeRoomJID && !hasLoadedRoomHistory(activeRoom);
|
|
65
|
+
|
|
66
|
+
const getDefaultHistory = async () => {
|
|
67
|
+
if (!client || !activeRoomJID) {return;}
|
|
68
|
+
// Re-send presence inside the history fetch as well — joining is
|
|
69
|
+
// idempotent and ensures REST-hydrated rooms have actually joined
|
|
70
|
+
// the MUC before MAM. Web does the same.
|
|
71
|
+
try {
|
|
72
|
+
client.presenceInRoomStanza(activeRoomJID);
|
|
73
|
+
} catch {
|
|
74
|
+
/* non-fatal */
|
|
75
|
+
}
|
|
76
|
+
dispatch(setIsLoading({ loading: true, chatJID: activeRoomJID }));
|
|
77
|
+
try {
|
|
78
|
+
const res = await client.getHistoryStanza(activeRoomJID, 30);
|
|
79
|
+
if (!res?.length) {
|
|
80
|
+
client.prioritizeRoomPresence?.(activeRoomJID).catch(() => {});
|
|
81
|
+
}
|
|
82
|
+
if (res && countUndefinedText(res) > 0) {
|
|
83
|
+
dispatch(setIsLoading({ loading: false, chatJID: activeRoomJID }));
|
|
84
|
+
await client.getHistoryStanza(
|
|
85
|
+
activeRoomJID,
|
|
86
|
+
20 + countUndefinedText(res),
|
|
87
|
+
Number(res[0].id)
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
} finally {
|
|
91
|
+
dispatch(
|
|
92
|
+
setIsLoading({
|
|
93
|
+
loading: false,
|
|
94
|
+
chatJID: activeRoomJID,
|
|
95
|
+
loadingText: undefined,
|
|
96
|
+
})
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
const initialPresenceAndHistory = async () => {
|
|
102
|
+
if (!roomsList[activeRoomJID] && activeRoomJID && client) {
|
|
103
|
+
await client.presenceInRoomStanza(activeRoomJID);
|
|
104
|
+
if (config?.newArch) {
|
|
105
|
+
await syncRooms(client, config);
|
|
106
|
+
} else {
|
|
107
|
+
await client.getRoomsStanza();
|
|
108
|
+
}
|
|
109
|
+
await getDefaultHistory();
|
|
110
|
+
} else {
|
|
111
|
+
await getDefaultHistory();
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
if (Object.keys(roomsList)?.length > 0) {
|
|
116
|
+
if (
|
|
117
|
+
activeRoomJID &&
|
|
118
|
+
!roomsList?.[activeRoomJID] &&
|
|
119
|
+
Object.keys(roomsList).length > 0
|
|
120
|
+
) {
|
|
121
|
+
dispatch(setIsLoading({ loading: true, chatJID: activeRoomJID }));
|
|
122
|
+
initialPresenceAndHistory();
|
|
123
|
+
} else if (activeRoomJID && shouldLoadActiveHistory) {
|
|
124
|
+
// Was: messageLength<1 && !historyComplete — that gated REST-
|
|
125
|
+
// hydrated rooms out of the history fetch entirely because they
|
|
126
|
+
// have 0 messages but historyComplete is also falsy, so the
|
|
127
|
+
// expression still fires, BUT then getDefaultHistory ran without
|
|
128
|
+
// a presence join. Now we use the same predicate as web (any
|
|
129
|
+
// real, non-pending message in the room counts as "loaded").
|
|
130
|
+
dispatch(setIsLoading({ loading: true, chatJID: activeRoomJID }));
|
|
131
|
+
getDefaultHistory();
|
|
132
|
+
} else {
|
|
133
|
+
dispatch(setIsLoading({ loading: false, chatJID: activeRoomJID }));
|
|
134
|
+
}
|
|
135
|
+
} else if (!roomsList?.[activeRoomJID]) {
|
|
136
|
+
initialPresenceAndHistory();
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (client && config?.defaultRooms) {
|
|
140
|
+
const allExist = config?.defaultRooms.every(
|
|
141
|
+
(room) => roomsList[room.jid] !== undefined
|
|
142
|
+
);
|
|
143
|
+
if (roomsList && !allExist) {
|
|
144
|
+
config?.defaultRooms.map(async (room) => {
|
|
145
|
+
client.presenceInRoomStanza(room.jid);
|
|
146
|
+
});
|
|
147
|
+
if (config?.newArch) {
|
|
148
|
+
// syncRooms(client, config);
|
|
149
|
+
} else {
|
|
150
|
+
client.getRoomsStanza();
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}, [
|
|
155
|
+
activeRoomJID,
|
|
156
|
+
Object.keys(roomsList).length,
|
|
157
|
+
messageLength,
|
|
158
|
+
roomsList?.[activeRoomJID]?.messages?.length,
|
|
159
|
+
]);
|
|
160
|
+
};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/** @format */
|
|
2
|
+
|
|
3
|
+
import { useMemo } from 'react';
|
|
4
|
+
import { useSelector } from 'react-redux';
|
|
5
|
+
import { RootState } from '../roomStore';
|
|
6
|
+
|
|
7
|
+
export const useRoomState = (roomJID?: string) => {
|
|
8
|
+
const room = useSelector((state: RootState) =>
|
|
9
|
+
roomJID ? state.rooms.rooms[roomJID] : undefined
|
|
10
|
+
);
|
|
11
|
+
const roomsList = useSelector((state: RootState) => state.rooms.rooms);
|
|
12
|
+
const activeRoomJID = useSelector(
|
|
13
|
+
(state: RootState) => state.rooms.activeRoomJID
|
|
14
|
+
);
|
|
15
|
+
const editAction = useSelector((state: RootState) => state.rooms.editAction);
|
|
16
|
+
const globalLoading = useSelector(
|
|
17
|
+
(state: RootState) => state.rooms.isLoading
|
|
18
|
+
);
|
|
19
|
+
const loading = useSelector((state: RootState) => {
|
|
20
|
+
const activeJID = state.rooms.activeRoomJID;
|
|
21
|
+
return activeJID ? state.rooms.rooms[activeJID]?.isLoading || false : false;
|
|
22
|
+
});
|
|
23
|
+
const loadingText = useSelector(
|
|
24
|
+
(state: RootState) => state.rooms.loadingText
|
|
25
|
+
);
|
|
26
|
+
const usersSet = useSelector((state: RootState) => state.rooms.usersSet);
|
|
27
|
+
|
|
28
|
+
const roomMessages = useMemo(
|
|
29
|
+
() =>
|
|
30
|
+
activeRoomJID && roomsList[activeRoomJID]
|
|
31
|
+
? roomsList[activeRoomJID].messages || []
|
|
32
|
+
: [],
|
|
33
|
+
[roomsList, activeRoomJID]
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
return {
|
|
37
|
+
room,
|
|
38
|
+
roomsList,
|
|
39
|
+
activeRoomJID,
|
|
40
|
+
editAction,
|
|
41
|
+
globalLoading,
|
|
42
|
+
loadingText,
|
|
43
|
+
loading,
|
|
44
|
+
roomMessages,
|
|
45
|
+
usersSet,
|
|
46
|
+
};
|
|
47
|
+
};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { useEffect } from 'react';
|
|
2
|
+
import { useDispatch } from 'react-redux';
|
|
3
|
+
import { setCurrentRoom } from '../roomStore/roomsSlice';
|
|
4
|
+
import { IConfig, IRoom } from '../types/types';
|
|
5
|
+
|
|
6
|
+
export const useRoomUrl = (
|
|
7
|
+
activeRoomJID: string,
|
|
8
|
+
roomsList: Record<string, IRoom>,
|
|
9
|
+
config: IConfig
|
|
10
|
+
) => {
|
|
11
|
+
const dispatch = useDispatch();
|
|
12
|
+
|
|
13
|
+
useEffect(() => {
|
|
14
|
+
if (
|
|
15
|
+
config?.setRoomJidInPath &&
|
|
16
|
+
activeRoomJID &&
|
|
17
|
+
typeof activeRoomJID === 'string'
|
|
18
|
+
) {
|
|
19
|
+
const chatJidUrl = activeRoomJID.split('@')[0];
|
|
20
|
+
|
|
21
|
+
const searchParams = new URLSearchParams(window.location.search);
|
|
22
|
+
searchParams.set('chatId', chatJidUrl);
|
|
23
|
+
|
|
24
|
+
const newUrl = `${window.location.pathname}?${searchParams.toString()}`;
|
|
25
|
+
|
|
26
|
+
window.history.pushState(null, '', newUrl);
|
|
27
|
+
} else if (!activeRoomJID && Object.values(roomsList).length > 0) {
|
|
28
|
+
dispatch(setCurrentRoom({ roomJID: roomsList[0]?.jid }));
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return () => {
|
|
32
|
+
if (config?.setRoomJidInPath) {
|
|
33
|
+
const searchParams = new URLSearchParams(window.location.search);
|
|
34
|
+
searchParams.delete('chatId');
|
|
35
|
+
|
|
36
|
+
const newUrl = `${window.location.pathname}?${searchParams.toString()}`;
|
|
37
|
+
window.history.pushState(null, '', newUrl);
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
}, [activeRoomJID, roomsList?.length]);
|
|
41
|
+
};
|
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
import { useCallback } from 'react';
|
|
2
|
+
import { useXmppClient } from '../context/xmppProvider';
|
|
3
|
+
import { useDispatch, useSelector } from 'react-redux';
|
|
4
|
+
import { addRoomMessage, setEditAction } from '../roomStore/roomsSlice';
|
|
5
|
+
import { uploadFile } from '../networking/api-requests/auth.api';
|
|
6
|
+
import { RootState } from '../roomStore';
|
|
7
|
+
import { useEventHandlers } from './useEventHandlers';
|
|
8
|
+
import type { IConfig, IMessage } from '../types/types';
|
|
9
|
+
|
|
10
|
+
export const useSendMessage = (_configOverride?: IConfig) => {
|
|
11
|
+
const { client } = useXmppClient();
|
|
12
|
+
const dispatch = useDispatch();
|
|
13
|
+
|
|
14
|
+
// Split selectors so each returns a primitive/stable reference — the
|
|
15
|
+
// single-object selector above caused a "returned a different result
|
|
16
|
+
// when called with the same parameters" warning and unnecessary
|
|
17
|
+
// rerenders on every redux update.
|
|
18
|
+
const user = useSelector((state: RootState) => state.chatSettingStore.user);
|
|
19
|
+
const config = useSelector(
|
|
20
|
+
(state: RootState) => state.chatSettingStore.config
|
|
21
|
+
);
|
|
22
|
+
const editAction = useSelector((state: RootState) => state.rooms.editAction);
|
|
23
|
+
const rooms = useSelector((state: RootState) => state.rooms.rooms);
|
|
24
|
+
|
|
25
|
+
const {
|
|
26
|
+
handleMessageSent,
|
|
27
|
+
handleMessageFailed,
|
|
28
|
+
handleMessageEdited,
|
|
29
|
+
} = useEventHandlers(config);
|
|
30
|
+
|
|
31
|
+
const sendMessage = useCallback(
|
|
32
|
+
async (
|
|
33
|
+
message: string,
|
|
34
|
+
activeRoomJID: string,
|
|
35
|
+
isReply?: boolean,
|
|
36
|
+
isChecked?: boolean,
|
|
37
|
+
mainMessage?: string
|
|
38
|
+
) => {
|
|
39
|
+
if (editAction?.isEdit) {
|
|
40
|
+
try {
|
|
41
|
+
client?.editMessageStanza(
|
|
42
|
+
editAction.roomJid!,
|
|
43
|
+
editAction.messageId!,
|
|
44
|
+
message
|
|
45
|
+
);
|
|
46
|
+
handleMessageEdited({
|
|
47
|
+
messageId: editAction.messageId!,
|
|
48
|
+
newMessage: message,
|
|
49
|
+
roomJID: editAction.roomJid!,
|
|
50
|
+
user,
|
|
51
|
+
});
|
|
52
|
+
} catch (error) {
|
|
53
|
+
handleMessageFailed({
|
|
54
|
+
message,
|
|
55
|
+
roomJID: editAction.roomJid!,
|
|
56
|
+
error: error as Error,
|
|
57
|
+
messageType: 'text',
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
dispatch(setEditAction({ isEdit: false }));
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Critical-send hint to the QoS scheduler.
|
|
65
|
+
client?.onCriticalSend?.(activeRoomJID);
|
|
66
|
+
|
|
67
|
+
// Optimistic pending render — push the message into redux
|
|
68
|
+
// immediately with `pending: true` so the bubble shows
|
|
69
|
+
// "sending..." while the stanza is in flight. The server echoes
|
|
70
|
+
// it back with the same id and insertMessageWithDelimiter
|
|
71
|
+
// dedupes by id and flips `pending: false` → DoubleTick renders.
|
|
72
|
+
// Without this, the user taps send and sees nothing for ~200ms
|
|
73
|
+
// until the echo lands, which feels broken.
|
|
74
|
+
const optimisticId = `send-text-message-${Date.now()}`;
|
|
75
|
+
const optimisticDate = new Date().toISOString();
|
|
76
|
+
const selfId =
|
|
77
|
+
(user as any)?.xmppUsername || (user as any)?.walletAddress || '';
|
|
78
|
+
const optimisticMessage: IMessage = {
|
|
79
|
+
id: optimisticId,
|
|
80
|
+
// Also set xmppId so the echo (which carries our outer stanza
|
|
81
|
+
// id as xmppId via getDataFromXml) can be matched and dedup'd
|
|
82
|
+
// by insertMessageWithDelimiter (`msg.xmppId === message.id`
|
|
83
|
+
// branch).
|
|
84
|
+
xmppId: optimisticId,
|
|
85
|
+
body: message,
|
|
86
|
+
roomJid: activeRoomJID,
|
|
87
|
+
date: optimisticDate,
|
|
88
|
+
pending: true,
|
|
89
|
+
isDeleted: false,
|
|
90
|
+
user: {
|
|
91
|
+
...(user as any),
|
|
92
|
+
id: selfId,
|
|
93
|
+
name: `${user?.firstName || ''} ${user?.lastName || ''}`.trim() || selfId,
|
|
94
|
+
} as any,
|
|
95
|
+
} as IMessage;
|
|
96
|
+
if (!config?.disableSentLogic) {
|
|
97
|
+
dispatch(
|
|
98
|
+
addRoomMessage({ roomJID: activeRoomJID, message: optimisticMessage })
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
console.log('🔵 [useSendMessage] sending', {
|
|
103
|
+
room: activeRoomJID,
|
|
104
|
+
hasClient: !!client,
|
|
105
|
+
first: user?.firstName,
|
|
106
|
+
last: user?.lastName,
|
|
107
|
+
wallet: user?.walletAddress,
|
|
108
|
+
len: message?.length,
|
|
109
|
+
optimisticId,
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
try {
|
|
113
|
+
if (!client) {
|
|
114
|
+
throw new Error('No XMPP client');
|
|
115
|
+
}
|
|
116
|
+
// Pass the optimistic id as the stanza id so the server echoes
|
|
117
|
+
// it back with the same value — the reducer's dedupe lookup
|
|
118
|
+
// matches and the bubble flips from pending → delivered in-place
|
|
119
|
+
// (instead of rendering two copies, which is what happens when
|
|
120
|
+
// ids don't match).
|
|
121
|
+
client.sendMessage(
|
|
122
|
+
activeRoomJID,
|
|
123
|
+
user.firstName,
|
|
124
|
+
user.lastName,
|
|
125
|
+
'',
|
|
126
|
+
user.walletAddress,
|
|
127
|
+
message,
|
|
128
|
+
'',
|
|
129
|
+
isReply || false,
|
|
130
|
+
isChecked || false,
|
|
131
|
+
mainMessage || '',
|
|
132
|
+
optimisticId
|
|
133
|
+
);
|
|
134
|
+
await handleMessageSent({
|
|
135
|
+
message,
|
|
136
|
+
roomJID: activeRoomJID,
|
|
137
|
+
user,
|
|
138
|
+
messageType: 'text',
|
|
139
|
+
});
|
|
140
|
+
} catch (error) {
|
|
141
|
+
handleMessageFailed({
|
|
142
|
+
message,
|
|
143
|
+
roomJID: activeRoomJID,
|
|
144
|
+
error: error as Error,
|
|
145
|
+
messageType: 'text',
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
},
|
|
149
|
+
[editAction, client, user, dispatch, handleMessageSent, handleMessageEdited, handleMessageFailed]
|
|
150
|
+
);
|
|
151
|
+
|
|
152
|
+
const sendMedia = useCallback(
|
|
153
|
+
async (
|
|
154
|
+
data: any,
|
|
155
|
+
type: string,
|
|
156
|
+
activeRoomJID: string,
|
|
157
|
+
isReply?: boolean,
|
|
158
|
+
isChecked?: boolean,
|
|
159
|
+
mainMessage?: string
|
|
160
|
+
) => {
|
|
161
|
+
const mediaData = new FormData();
|
|
162
|
+
mediaData.append('files', data);
|
|
163
|
+
|
|
164
|
+
client?.onCriticalSend?.(activeRoomJID);
|
|
165
|
+
|
|
166
|
+
// Optimistic pending bubble — same pattern as text send. Render
|
|
167
|
+
// a placeholder with `isMediafile: true` so MessageBubble shows
|
|
168
|
+
// a media-shaped pending tile while the upload + send is in
|
|
169
|
+
// flight; replaced in-place by the server echo via xmppId dedupe.
|
|
170
|
+
const optimisticId = `send-media-message-${Date.now()}`;
|
|
171
|
+
const optimisticDate = new Date().toISOString();
|
|
172
|
+
const selfId =
|
|
173
|
+
(user as any)?.xmppUsername || (user as any)?.walletAddress || '';
|
|
174
|
+
const placeholderMessage: IMessage = {
|
|
175
|
+
id: optimisticId,
|
|
176
|
+
xmppId: optimisticId,
|
|
177
|
+
body: 'media',
|
|
178
|
+
roomJid: activeRoomJID,
|
|
179
|
+
date: optimisticDate,
|
|
180
|
+
pending: true,
|
|
181
|
+
isDeleted: false,
|
|
182
|
+
isMediafile: 'true',
|
|
183
|
+
mimetype: type,
|
|
184
|
+
originalName: data?.name,
|
|
185
|
+
user: {
|
|
186
|
+
...(user as any),
|
|
187
|
+
id: selfId,
|
|
188
|
+
name: `${user?.firstName || ''} ${user?.lastName || ''}`.trim() || selfId,
|
|
189
|
+
} as any,
|
|
190
|
+
} as IMessage;
|
|
191
|
+
if (!config?.disableSentLogic) {
|
|
192
|
+
dispatch(
|
|
193
|
+
addRoomMessage({ roomJID: activeRoomJID, message: placeholderMessage })
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
try {
|
|
198
|
+
const response = await uploadFile(mediaData);
|
|
199
|
+
response.data.results.forEach(async (item: any) => {
|
|
200
|
+
const payload = {
|
|
201
|
+
firstName: user.firstName,
|
|
202
|
+
lastName: user.lastName,
|
|
203
|
+
walletAddress: user.walletAddress,
|
|
204
|
+
createdAt: item.createdAt,
|
|
205
|
+
expiresAt: item.expiresAt,
|
|
206
|
+
fileName: item.filename,
|
|
207
|
+
isVisible: item?.isVisible,
|
|
208
|
+
location: item.location,
|
|
209
|
+
locationPreview: item.locationPreview,
|
|
210
|
+
mimetype: item.mimetype,
|
|
211
|
+
originalName: item?.originalname,
|
|
212
|
+
ownerKey: item?.ownerKey,
|
|
213
|
+
size: item.size,
|
|
214
|
+
duration: item?.duration,
|
|
215
|
+
updatedAt: item?.updatedAt,
|
|
216
|
+
userId: item?.userId,
|
|
217
|
+
attachmentId: item?._id,
|
|
218
|
+
wrappable: true,
|
|
219
|
+
roomJid: activeRoomJID,
|
|
220
|
+
showInChannel: isChecked || false,
|
|
221
|
+
isReply: isReply || false,
|
|
222
|
+
mainMessage: mainMessage || '',
|
|
223
|
+
isPrivate: item?.isPrivate,
|
|
224
|
+
__v: item.__v,
|
|
225
|
+
};
|
|
226
|
+
// Pass optimisticId as the stanza id so the echo's outer
|
|
227
|
+
// id matches our placeholder's xmppId and dedup'es in place.
|
|
228
|
+
client?.sendMediaMessageStanza(activeRoomJID, payload, optimisticId);
|
|
229
|
+
await handleMessageSent({
|
|
230
|
+
message: item.location || '',
|
|
231
|
+
roomJID: activeRoomJID,
|
|
232
|
+
user,
|
|
233
|
+
messageType: 'media',
|
|
234
|
+
metadata: payload,
|
|
235
|
+
});
|
|
236
|
+
});
|
|
237
|
+
} catch (error) {
|
|
238
|
+
console.error('Media upload failed', error);
|
|
239
|
+
handleMessageFailed({
|
|
240
|
+
message: '',
|
|
241
|
+
roomJID: activeRoomJID,
|
|
242
|
+
error: error as Error,
|
|
243
|
+
messageType: 'media',
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
},
|
|
247
|
+
[client, user, handleMessageSent, handleMessageFailed]
|
|
248
|
+
);
|
|
249
|
+
|
|
250
|
+
// ChatRoom/ThreadWrapper consume this as the "edit branch" of send.
|
|
251
|
+
// It mirrors the edit path inside `sendMessage` so callers can wire it
|
|
252
|
+
// up directly when `editAction.isEdit` is true.
|
|
253
|
+
const sendEditMessage = useCallback(
|
|
254
|
+
async (message: string, _activeRoomJID?: string) => {
|
|
255
|
+
if (!editAction?.isEdit || !editAction.roomJid || !editAction.messageId) {
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
try {
|
|
259
|
+
client?.editMessageStanza(
|
|
260
|
+
editAction.roomJid,
|
|
261
|
+
editAction.messageId,
|
|
262
|
+
message
|
|
263
|
+
);
|
|
264
|
+
handleMessageEdited({
|
|
265
|
+
messageId: editAction.messageId,
|
|
266
|
+
newMessage: message,
|
|
267
|
+
roomJID: editAction.roomJid,
|
|
268
|
+
user,
|
|
269
|
+
});
|
|
270
|
+
} catch (error) {
|
|
271
|
+
handleMessageFailed({
|
|
272
|
+
message,
|
|
273
|
+
roomJID: editAction.roomJid,
|
|
274
|
+
error: error as Error,
|
|
275
|
+
messageType: 'text',
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
dispatch(setEditAction({ isEdit: false }));
|
|
279
|
+
},
|
|
280
|
+
[client, editAction, user, dispatch, handleMessageEdited, handleMessageFailed]
|
|
281
|
+
);
|
|
282
|
+
|
|
283
|
+
// True when the most recent message in the given room is from the
|
|
284
|
+
// current user and still in a pending state (i.e. not yet ack'd by
|
|
285
|
+
// the server). Used by SendInput to disable rapid re-sends.
|
|
286
|
+
const isLastMessageFromUserAndProcessing = useCallback(
|
|
287
|
+
(roomJID: string): boolean => {
|
|
288
|
+
if (!roomJID) {return false;}
|
|
289
|
+
const msgs = rooms?.[roomJID]?.messages;
|
|
290
|
+
if (!msgs || msgs.length === 0) {return false;}
|
|
291
|
+
const last = msgs[msgs.length - 1];
|
|
292
|
+
if (!last?.pending) {return false;}
|
|
293
|
+
const selfId = user?.xmppUsername || user?.walletAddress;
|
|
294
|
+
if (!selfId) {return false;}
|
|
295
|
+
return last.user?.id === selfId;
|
|
296
|
+
},
|
|
297
|
+
[rooms, user?.xmppUsername, user?.walletAddress]
|
|
298
|
+
);
|
|
299
|
+
|
|
300
|
+
return {
|
|
301
|
+
sendMessage,
|
|
302
|
+
sendMedia,
|
|
303
|
+
sendEditMessage,
|
|
304
|
+
isLastMessageFromUserAndProcessing,
|
|
305
|
+
};
|
|
306
|
+
};
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { useSyncExternalStore } from 'react';
|
|
2
|
+
import { IRoom } from '../types/types';
|
|
3
|
+
import { RootState, store } from '../roomStore';
|
|
4
|
+
|
|
5
|
+
interface UnreadMessagesMap {
|
|
6
|
+
[roomJid: string]: number;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
interface UnreadMessagesStats {
|
|
10
|
+
hasUnread: boolean;
|
|
11
|
+
totalCount: number;
|
|
12
|
+
unreadByRoom: UnreadMessagesMap;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export const useUnreadMessagesCounter = (): UnreadMessagesStats => {
|
|
16
|
+
const subscribe = (callback: () => void) => {
|
|
17
|
+
const unsubscribe = store.subscribe(() => {
|
|
18
|
+
const state: RootState = store.getState();
|
|
19
|
+
const rooms = state.rooms?.rooms;
|
|
20
|
+
|
|
21
|
+
if (rooms) {
|
|
22
|
+
Object.entries(rooms).forEach(([roomJid, room]: [string, IRoom]) => {
|
|
23
|
+
if (room.unreadMessages !== undefined) {
|
|
24
|
+
callback();
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
return unsubscribe;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const rooms = useSyncExternalStore(
|
|
33
|
+
subscribe,
|
|
34
|
+
() => store.getState().rooms.rooms
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
const unreadByRoom: UnreadMessagesMap = {};
|
|
38
|
+
let totalCount = 0;
|
|
39
|
+
|
|
40
|
+
Object.entries(rooms).forEach(([roomJid, room]: [string, IRoom]) => {
|
|
41
|
+
const unreadCount = room.unreadMessages || 0;
|
|
42
|
+
if (unreadCount > 0) {
|
|
43
|
+
unreadByRoom[roomJid] = unreadCount;
|
|
44
|
+
totalCount += unreadCount;
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
return {
|
|
49
|
+
hasUnread: totalCount > 0,
|
|
50
|
+
totalCount,
|
|
51
|
+
unreadByRoom,
|
|
52
|
+
};
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
export { useUnreadMessagesCounter as useUnread };
|