@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.
Files changed (273) hide show
  1. package/LICENSE +661 -0
  2. package/README.md +148 -0
  3. package/empty-shim.js +15 -0
  4. package/index.js +13 -0
  5. package/metro.js +72 -0
  6. package/package.json +99 -0
  7. package/src/api.config.ts +34 -0
  8. package/src/assets/BG.png +0 -0
  9. package/src/assets/BG.tsx +56 -0
  10. package/src/assets/File.svg +4 -0
  11. package/src/assets/ForzaCare.png +0 -0
  12. package/src/assets/ForzaCare.tsx +27 -0
  13. package/src/assets/icons.tsx +685 -0
  14. package/src/assets/images.tsx +41 -0
  15. package/src/assets/test.xml +243 -0
  16. package/src/components/AuthForms/Login.tsx +295 -0
  17. package/src/components/AuthForms/LoginAnonym.tsx +68 -0
  18. package/src/components/AuthForms/Register.tsx +8 -0
  19. package/src/components/ContextMenu/ContextMenuComponents.tsx +87 -0
  20. package/src/components/DropdownMenu/DropdownMenu.tsx +244 -0
  21. package/src/components/ExampleComponents/CustomMessage.tsx +92 -0
  22. package/src/components/ExampleComponents/CustomTypingIndicatorExamples.tsx +239 -0
  23. package/src/components/Header/HeaderRoomList.tsx +104 -0
  24. package/src/components/InputComponents/AudioInput.tsx +24 -0
  25. package/src/components/InputComponents/AudioRecorder.tsx +100 -0
  26. package/src/components/InputComponents/MediaInput.tsx +114 -0
  27. package/src/components/InputComponents/RecordingIndicator.tsx +77 -0
  28. package/src/components/InputComponents/Search.tsx +135 -0
  29. package/src/components/InputComponents/TextInput.tsx +66 -0
  30. package/src/components/MainComponents/ChatHeader.tsx +163 -0
  31. package/src/components/MainComponents/ChatRoom.tsx +392 -0
  32. package/src/components/MainComponents/ChatWrapper.tsx +318 -0
  33. package/src/components/MainComponents/ChooseChatMessage.tsx +44 -0
  34. package/src/components/MainComponents/ConnectionBanner.tsx +33 -0
  35. package/src/components/MainComponents/EditWrapper.tsx +72 -0
  36. package/src/components/MainComponents/LoginWrapper.tsx +134 -0
  37. package/src/components/MainComponents/MediaMessage.tsx +55 -0
  38. package/src/components/MainComponents/MessageContainer.tsx +122 -0
  39. package/src/components/MainComponents/MessageList.tsx +429 -0
  40. package/src/components/MainComponents/NoMessagesPlaceholder.tsx +49 -0
  41. package/src/components/MainComponents/ProfileImagePlaceholder.tsx +143 -0
  42. package/src/components/MainComponents/RNStyled.tsx +34 -0
  43. package/src/components/MainComponents/ReduxWrapper.tsx +40 -0
  44. package/src/components/MainComponents/RoomList.tsx +246 -0
  45. package/src/components/MainComponents/Select.tsx +165 -0
  46. package/src/components/MainComponents/Switch.tsx +73 -0
  47. package/src/components/MainComponents/SystemMessage.tsx +42 -0
  48. package/src/components/Menu/HeaderRoomListMenu.tsx +169 -0
  49. package/src/components/MenuRoom/MenuRoom.tsx +46 -0
  50. package/src/components/MessageBubble/Avatar.tsx +84 -0
  51. package/src/components/MessageBubble/BottomReplyContainer.tsx +117 -0
  52. package/src/components/MessageBubble/CustomDivider.tsx +10 -0
  53. package/src/components/MessageBubble/DeletedMessage.tsx +36 -0
  54. package/src/components/MessageBubble/Message.tsx +400 -0
  55. package/src/components/MessageBubble/MessageInteractions.tsx +247 -0
  56. package/src/components/MessageBubble/MessageReaction.tsx +149 -0
  57. package/src/components/MessageBubble/MessageReply.tsx +50 -0
  58. package/src/components/MessageBubble/MessageTranslations.tsx +30 -0
  59. package/src/components/Modals/AddMembersModal/AddMembersModal.tsx +142 -0
  60. package/src/components/Modals/ChatProfileModal/ChatProfileModal.tsx +471 -0
  61. package/src/components/Modals/ChatProfileModal/DeleteChatModal.tsx +91 -0
  62. package/src/components/Modals/FilePreviewModal/FilePreviewModal.tsx +298 -0
  63. package/src/components/Modals/Modal/Modal.tsx +52 -0
  64. package/src/components/Modals/ModalHeaderComponent.tsx +41 -0
  65. package/src/components/Modals/ModalSelectMedia/ModalSelectMedia.tsx +285 -0
  66. package/src/components/Modals/ModalWrapper/ModalWrapper.tsx +103 -0
  67. package/src/components/Modals/NewChatModal/ModalContent.tsx +151 -0
  68. package/src/components/Modals/NewChatModal/NewChatModal.tsx +391 -0
  69. package/src/components/Modals/NewChatModal/UpdNewChatModal.tsx +136 -0
  70. package/src/components/Modals/SelectUsersModal/SelectUsersModal.tsx +134 -0
  71. package/src/components/Modals/SettingsModals/BlockedUsers/BlockedUsersModal.tsx +63 -0
  72. package/src/components/Modals/SettingsModals/DocumentShares/DocumentSharesModal.tsx +115 -0
  73. package/src/components/Modals/SettingsModals/ManageDataModal/ManageDataModal.tsx +121 -0
  74. package/src/components/Modals/SettingsModals/ManageDataModal/StyledComponents.ts +0 -0
  75. package/src/components/Modals/SettingsModals/ProfileShares/ProfileShares.tsx +65 -0
  76. package/src/components/Modals/SettingsModals/Referrals/Referrals.tsx +90 -0
  77. package/src/components/Modals/SettingsModals/SharedStyledComponents.ts +74 -0
  78. package/src/components/Modals/SettingsModals/Visibility/RadioInput.tsx +59 -0
  79. package/src/components/Modals/SettingsModals/Visibility/StyledComponents.ts +32 -0
  80. package/src/components/Modals/SettingsModals/Visibility/VisibilityModal.tsx +146 -0
  81. package/src/components/Modals/UserProfileModal/EditUserModal.tsx +163 -0
  82. package/src/components/Modals/UserProfileModal/UserProfileModal.tsx +410 -0
  83. package/src/components/Modals/UserSettingsModal/UserSettingsModal.tsx +85 -0
  84. package/src/components/Modals/styledModalComponents.tsx +181 -0
  85. package/src/components/OperationalModal/OperationalModal.tsx +78 -0
  86. package/src/components/RoomComponents/ChatRoomItem.tsx +219 -0
  87. package/src/components/RoomComponents/LastMessageItem.tsx +47 -0
  88. package/src/components/RoomComponents/styled/LastAudioMessage.tsx +62 -0
  89. package/src/components/RoomComponents/styled/LastMessageEmoji.tsx +26 -0
  90. package/src/components/RoomComponents/styled/LastMessageFile.tsx +54 -0
  91. package/src/components/RoomComponents/styled/LastMessagePhoto.tsx +54 -0
  92. package/src/components/RoomComponents/styled/LastMessageVideo.tsx +70 -0
  93. package/src/components/RoomComponents/styled/LastTextMessage.tsx +65 -0
  94. package/src/components/RoomComponents/styled/StyledRoomComponents.ts +53 -0
  95. package/src/components/SortDropDown/index.tsx +76 -0
  96. package/src/components/Thread/ThreadHeader.tsx +41 -0
  97. package/src/components/Thread/ThreadWrapper.tsx +270 -0
  98. package/src/components/Toast/Toast.tsx +128 -0
  99. package/src/components/Toast/index.ts +13 -0
  100. package/src/components/UsersList/StyledComponents.ts +30 -0
  101. package/src/components/UsersList/UsersList.tsx +112 -0
  102. package/src/components/styled/ActionButton.tsx +40 -0
  103. package/src/components/styled/AudioMessage.tsx +82 -0
  104. package/src/components/styled/Button.tsx +107 -0
  105. package/src/components/styled/ChatWrapperBox.tsx +9 -0
  106. package/src/components/styled/DateLabel.tsx +77 -0
  107. package/src/components/styled/Loader.tsx +65 -0
  108. package/src/components/styled/MediaFilePreview.tsx +42 -0
  109. package/src/components/styled/MediaModal.tsx +54 -0
  110. package/src/components/styled/MessageImage.tsx +54 -0
  111. package/src/components/styled/NewMessageLabel.tsx +50 -0
  112. package/src/components/styled/RoomListComponents/index.tsx +164 -0
  113. package/src/components/styled/SendInput.tsx +297 -0
  114. package/src/components/styled/StyledComponents.ts +320 -0
  115. package/src/components/styled/StyledInput.tsx +101 -0
  116. package/src/components/styled/StyledInputComponents/AudioComponents.tsx +1 -0
  117. package/src/components/styled/StyledInputComponents/Composing.tsx +97 -0
  118. package/src/components/styled/StyledInputComponents/CustomTypingIndicator.tsx +261 -0
  119. package/src/components/styled/StyledInputComponents/MediaComponents.tsx +90 -0
  120. package/src/components/styled/StyledInputComponents/StyledInputComponents.tsx +131 -0
  121. package/src/components/styled/StyledInputComponents/VideoComponents.tsx +49 -0
  122. package/src/components/styled/TreadLabel.tsx +50 -0
  123. package/src/components/styled/UnsupportedType.tsx +95 -0
  124. package/src/components/styled/VideoMessage.tsx +68 -0
  125. package/src/context/MessageNotificationContext.tsx +335 -0
  126. package/src/context/PushNotificationProvider.tsx +78 -0
  127. package/src/context/ToastContext.tsx +63 -0
  128. package/src/context/toastProvider.tsx +0 -0
  129. package/src/context/xmppProvider.tsx +364 -0
  130. package/src/helpers/block_scroll.ts +95 -0
  131. package/src/helpers/chatAutoEnterer.ts +46 -0
  132. package/src/helpers/checkUniqueUsers.tsx +31 -0
  133. package/src/helpers/constants/CHAT_TYPES.ts +6 -0
  134. package/src/helpers/constants/LANGUAGE_OPTIONS.ts +14 -0
  135. package/src/helpers/constants/LOCAL_STORAGE.ts +3 -0
  136. package/src/helpers/constants/MESSAGE_INTERACTIONS.ts +29 -0
  137. package/src/helpers/constants/MODAL_TYPES.ts +57 -0
  138. package/src/helpers/constants/PLATFORM_CONSTANTS.ts +3 -0
  139. package/src/helpers/createMainMessageForThread.ts +23 -0
  140. package/src/helpers/createMessageFromXml.ts +118 -0
  141. package/src/helpers/createRoomFromApi.tsx +27 -0
  142. package/src/helpers/createUserNameFromSetUser.ts +15 -0
  143. package/src/helpers/dateComparison.ts +12 -0
  144. package/src/helpers/debounce.ts +10 -0
  145. package/src/helpers/ensureScopedChatCache.ts +61 -0
  146. package/src/helpers/extractUniqueMembersFromRooms.ts +45 -0
  147. package/src/helpers/formatNumberWithCommas.ts +3 -0
  148. package/src/helpers/getBooleanFromString.ts +7 -0
  149. package/src/helpers/getDataFromXml.ts +80 -0
  150. package/src/helpers/getNumberFromString.ts +5 -0
  151. package/src/helpers/getRoomsWithRetry.ts +45 -0
  152. package/src/helpers/getTintedColor.ts +28 -0
  153. package/src/helpers/getUnnamedUsers.ts +39 -0
  154. package/src/helpers/handleCopyClick.ts +3 -0
  155. package/src/helpers/handleXmppError.ts +14 -0
  156. package/src/helpers/hashcolor.ts +34 -0
  157. package/src/helpers/historyPreloadScheduler.ts +264 -0
  158. package/src/helpers/initRoomsPresence.ts +32 -0
  159. package/src/helpers/initXmppRooms.tsx +71 -0
  160. package/src/helpers/insertMessageWithDelimiter.ts +96 -0
  161. package/src/helpers/isChatIdPresentInArray.ts +54 -0
  162. package/src/helpers/localStorageUser.ts +9 -0
  163. package/src/helpers/markdownParser.tsx +295 -0
  164. package/src/helpers/nanoToMs.ts +3 -0
  165. package/src/helpers/parseMessageBody.tsx +516 -0
  166. package/src/helpers/parseUrls.tsx +42 -0
  167. package/src/helpers/resolveInitBeforeLoadUser.ts +240 -0
  168. package/src/helpers/storeConsole.ts +26 -0
  169. package/src/helpers/transformTranslatations.ts +16 -0
  170. package/src/helpers/updateMessagesTillLast.tsx +104 -0
  171. package/src/helpers/updatedChatLastTimestamps.ts +29 -0
  172. package/src/helpers/validator.ts +20 -0
  173. package/src/helpers/walletUsername.ts +12 -0
  174. package/src/hooks/hooks.ts +6 -0
  175. package/src/hooks/useChatSettingState.tsx +38 -0
  176. package/src/hooks/useChatWrapperInit.ts +266 -0
  177. package/src/hooks/useComposing.tsx +59 -0
  178. package/src/hooks/useCustomTypingIndicator.tsx +193 -0
  179. package/src/hooks/useEventHandlers.tsx +81 -0
  180. package/src/hooks/useGetNewArchRoom.tsx +57 -0
  181. package/src/hooks/useHeapSender.tsx +88 -0
  182. package/src/hooks/useLocalStorage.tsx +53 -0
  183. package/src/hooks/useLogout.tsx +31 -0
  184. package/src/hooks/useMessageHeapState.tsx +11 -0
  185. package/src/hooks/useMessageLoaderQueue.tsx +90 -0
  186. package/src/hooks/useMessageQueue.ts +146 -0
  187. package/src/hooks/usePendingNotification.ts +49 -0
  188. package/src/hooks/usePrevious.tsx +15 -0
  189. package/src/hooks/usePushNotifications.ts +106 -0
  190. package/src/hooks/useQRCodeChatHandler.ts +58 -0
  191. package/src/hooks/useRoomInitialization.tsx +160 -0
  192. package/src/hooks/useRoomState.tsx +47 -0
  193. package/src/hooks/useRoomUrl.tsx +41 -0
  194. package/src/hooks/useSendMessage.tsx +306 -0
  195. package/src/hooks/useUnreadMessagesCounter.ts +55 -0
  196. package/src/hooks/useXmppInitialization.ts +148 -0
  197. package/src/main.ts +45 -0
  198. package/src/networking/api-requests/auth.api.ts +140 -0
  199. package/src/networking/api-requests/push.api.ts +55 -0
  200. package/src/networking/api-requests/roomMembers.api.ts +20 -0
  201. package/src/networking/api-requests/rooms.api.ts +142 -0
  202. package/src/networking/api-requests/user.api.ts +65 -0
  203. package/src/networking/apiClient.ts +149 -0
  204. package/src/networking/helpers/refreshChecker.ts +13 -0
  205. package/src/networking/stanzaHandlers.ts +418 -0
  206. package/src/networking/xmpp/actionSetTimestampToPrivateStore.xmpp.ts +37 -0
  207. package/src/networking/xmpp/allRoomPresences.xmpp.ts +10 -0
  208. package/src/networking/xmpp/createPrivateRoom.xmpp.ts +26 -0
  209. package/src/networking/xmpp/createRoom.xmpp.ts +35 -0
  210. package/src/networking/xmpp/createRoomPresence.xmpp.ts +74 -0
  211. package/src/networking/xmpp/createTimeoutPromise.xmpp.ts +15 -0
  212. package/src/networking/xmpp/deleteMessage.xmpp.ts +19 -0
  213. package/src/networking/xmpp/editMessage.xmpp.ts +46 -0
  214. package/src/networking/xmpp/getChatsPrivateStoreRequest.xmpp.ts +57 -0
  215. package/src/networking/xmpp/getHistory.xmpp.ts +140 -0
  216. package/src/networking/xmpp/getLastMessageArchive.xmpp.ts +24 -0
  217. package/src/networking/xmpp/getRoomInfo.xmpp.ts +15 -0
  218. package/src/networking/xmpp/getRoomMembers.xmpp.ts +13 -0
  219. package/src/networking/xmpp/getRooms.xmpp.ts +43 -0
  220. package/src/networking/xmpp/getRoomsPaged.xmpp.ts +59 -0
  221. package/src/networking/xmpp/handlePong.xmpp.ts +7 -0
  222. package/src/networking/xmpp/handleStanzas.xmpp.ts +55 -0
  223. package/src/networking/xmpp/inviteRoomRequest.xmpp.ts +25 -0
  224. package/src/networking/xmpp/leaveTheRoom.xmpp.ts +15 -0
  225. package/src/networking/xmpp/listenerManager.ts +116 -0
  226. package/src/networking/xmpp/presenceInRoom.xmpp.ts +57 -0
  227. package/src/networking/xmpp/roomConfig.xmpp.ts +71 -0
  228. package/src/networking/xmpp/sendMediaMessage.xmpp.ts +97 -0
  229. package/src/networking/xmpp/sendMessageReaction.xmpp.ts +41 -0
  230. package/src/networking/xmpp/sendPing.xmpp.ts +21 -0
  231. package/src/networking/xmpp/sendTextMessage.xmpp.ts +99 -0
  232. package/src/networking/xmpp/sendTextMessageWithTranslateTag.xmpp.ts +44 -0
  233. package/src/networking/xmpp/sendTypingRequest.xmpp.ts +26 -0
  234. package/src/networking/xmpp/setChatsPrivateStoreRequest.xmpp.ts +48 -0
  235. package/src/networking/xmpp/setMeAsOwner.xmpp.ts +52 -0
  236. package/src/networking/xmpp/setRoomImage.xmpp.ts +26 -0
  237. package/src/networking/xmpp/setVCard.xmpp.ts +19 -0
  238. package/src/networking/xmpp/subscribeToRoomMessages.xmpp.ts +75 -0
  239. package/src/networking/xmppClient.ts +603 -0
  240. package/src/roomStore/Middleware/logoutMiddleware.tsx +24 -0
  241. package/src/roomStore/Middleware/newMessageMidlleware.tsx +87 -0
  242. package/src/roomStore/Middleware/reactionsMiddleware.tsx +77 -0
  243. package/src/roomStore/Middleware/testMiddleware.tsx +20 -0
  244. package/src/roomStore/Middleware/unreadMidlleware.tsx +58 -0
  245. package/src/roomStore/chatSettingsSlice.ts +178 -0
  246. package/src/roomStore/index.ts +68 -0
  247. package/src/roomStore/persistence.ts +152 -0
  248. package/src/roomStore/roomHeapSlice.ts +37 -0
  249. package/src/roomStore/roomsSlice.ts +353 -0
  250. package/src/services/pushNotifications.ts +170 -0
  251. package/src/services/pushSubscriptionService.ts +145 -0
  252. package/src/types/messages.ts +61 -0
  253. package/src/types/models/action.model.ts +30 -0
  254. package/src/types/models/config.model.ts +16 -0
  255. package/src/types/models/customComponents.model.ts +53 -0
  256. package/src/types/models/language.model.ts +11 -0
  257. package/src/types/models/media.model.ts +5 -0
  258. package/src/types/models/message.model.ts +69 -0
  259. package/src/types/models/modal.model.ts +9 -0
  260. package/src/types/models/room.model.ts +120 -0
  261. package/src/types/models/user.model.ts +90 -0
  262. package/src/types/models/xmpp.model.ts +155 -0
  263. package/src/types/types.ts +505 -0
  264. package/src/utils/PriorityQueue.ts +47 -0
  265. package/src/utils/actionHelpers.ts +40 -0
  266. package/src/utils/actionLogger.ts +64 -0
  267. package/src/utils/clientRegistry.ts +80 -0
  268. package/src/utils/devLogger.ts +197 -0
  269. package/src/utils/messageNotificationManager.ts +108 -0
  270. package/src/utils/resendMessage.ts +87 -0
  271. package/src/utils/toastEmitter.ts +27 -0
  272. package/src/utils/xmppMemoryMonitor.ts +63 -0
  273. 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 };