@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,88 @@
1
+ import { useCallback, useEffect, useRef } from 'react';
2
+ import { useDispatch } from 'react-redux';
3
+ import { useMessageHeapState } from './useMessageHeapState';
4
+ import { removeMessageFromHeapById } from '../roomStore/roomHeapSlice';
5
+ import XmppClient from '../networking/xmppClient';
6
+
7
+ export const useHeapSender = (client: XmppClient | null) => {
8
+ const dispatch = useDispatch();
9
+ const { queue } = useMessageHeapState();
10
+ const sendingRef = useRef(false);
11
+ const inFlightRef = useRef<Map<string, number>>(new Map());
12
+
13
+ const sendHeapMessages = useCallback(async () => {
14
+ if (!client || queue.length === 0) {return;}
15
+ if (!client.presencesReady) {
16
+ console.warn('Presences not ready, delaying heap send');
17
+ return;
18
+ }
19
+ if (sendingRef.current) {return;}
20
+ sendingRef.current = true;
21
+ try {
22
+ for (const msg of queue) {
23
+ const lastAttempt = inFlightRef.current.get(msg.id);
24
+ if (lastAttempt && Date.now() - lastAttempt < 15000) {
25
+ continue;
26
+ }
27
+ inFlightRef.current.set(msg.id, Date.now());
28
+ try {
29
+ if (msg.langSource) {
30
+ await client.sendTextMessageWithTranslateTagStanza(
31
+ msg.roomJid,
32
+ msg.user.firstName || msg.user.name?.split(' ')[0] || '',
33
+ msg.user.lastName || msg.user.name?.split(' ')[1] || '',
34
+ '',
35
+ msg.user.walletAddress || '',
36
+ msg.body,
37
+ '',
38
+ !!msg.isReply,
39
+ msg.showInChannel === 'true',
40
+ msg.mainMessage || '',
41
+ msg.langSource
42
+ );
43
+ } else {
44
+ await client.sendMessage(
45
+ msg.roomJid,
46
+ msg.user.firstName || msg.user.name?.split(' ')[0] || '',
47
+ msg.user.lastName || msg.user.name?.split(' ')[1] || '',
48
+ '',
49
+ msg.user.walletAddress || '',
50
+ msg.body,
51
+ '',
52
+ !!msg.isReply,
53
+ !!msg.showInChannel,
54
+ msg.mainMessage,
55
+ msg.id
56
+ );
57
+ }
58
+ } catch (err) {
59
+ console.warn('Failed to send heap message', msg, err);
60
+ inFlightRef.current.delete(msg.id);
61
+ }
62
+ }
63
+ } finally {
64
+ sendingRef.current = false;
65
+ }
66
+ }, [client, queue, dispatch]);
67
+
68
+ const prevReadyRef = useRef<boolean>(false);
69
+ useEffect(() => {
70
+ const nowReady = !!client?.presencesReady;
71
+ const wasReady = prevReadyRef.current;
72
+ prevReadyRef.current = nowReady;
73
+ if (!wasReady && nowReady && queue.length > 0) {
74
+ sendHeapMessages();
75
+ }
76
+ }, [client?.presencesReady, queue.length, sendHeapMessages]);
77
+
78
+ useEffect(() => {
79
+ const currentIds = new Set(queue.map((m) => m.id));
80
+ for (const id of Array.from(inFlightRef.current.keys())) {
81
+ if (!currentIds.has(id)) {
82
+ inFlightRef.current.delete(id);
83
+ }
84
+ }
85
+ }, [queue]);
86
+
87
+ return { sendHeapMessages };
88
+ };
@@ -0,0 +1,53 @@
1
+ import AsyncStorage from '@react-native-async-storage/async-storage';
2
+
3
+ // Plain key-scoped AsyncStorage helper (NOT a React hook). Callable from
4
+ // reducers, helpers, and React code alike. The `use*` name (now
5
+ // `asyncLocalStorage`) was renamed to stop ESLint's `react-hooks/rules-of-hooks`
6
+ // firing in every non-React call site. `useLocalStorage` is kept below
7
+ // as a back-compat alias.
8
+ export function asyncLocalStorage<T>(key: string) {
9
+ const get = async (): Promise<T | null> => {
10
+ try {
11
+ const storedValue = await AsyncStorage.getItem(key);
12
+ if (!storedValue) {return null;}
13
+ return JSON.parse(storedValue) as T;
14
+ } catch (error) {
15
+ console.error('Failed to read from AsyncStorage', error);
16
+ return null;
17
+ }
18
+ };
19
+
20
+ const set = async (value: T) => {
21
+ try {
22
+ const stringValue = JSON.stringify(value);
23
+ await AsyncStorage.setItem(key, stringValue);
24
+ } catch (error) {
25
+ console.error('Failed to write to AsyncStorage', error);
26
+ }
27
+ };
28
+
29
+ const update = async (updates: Partial<T>) => {
30
+ try {
31
+ const currentValue = await get();
32
+ const newValue = currentValue
33
+ ? { ...currentValue, ...updates }
34
+ : (updates as T);
35
+ await set(newValue);
36
+ } catch (error) {
37
+ console.error('Failed to update AsyncStorage value', error);
38
+ }
39
+ };
40
+
41
+ const remove = async () => {
42
+ try {
43
+ await AsyncStorage.removeItem(key);
44
+ } catch (error) {
45
+ console.error('Failed to remove AsyncStorage value', error);
46
+ }
47
+ };
48
+
49
+ return { get, set, update, remove };
50
+ }
51
+
52
+ // Back-compat alias for old call sites. New code: use asyncLocalStorage.
53
+ export const useLocalStorage = asyncLocalStorage;
@@ -0,0 +1,31 @@
1
+ import { useDispatch } from 'react-redux';
2
+ import { DeviceEventEmitter } from 'react-native';
3
+ import { store } from '../roomStore';
4
+ import { logout } from '../roomStore/chatSettingsSlice';
5
+ import { setLogoutState } from '../roomStore/roomsSlice';
6
+ import { useCallback } from 'react';
7
+ import { clearHeap } from '../roomStore/roomHeapSlice';
8
+ import { pushSubscriptionService } from '../services/pushSubscriptionService';
9
+
10
+ const logoutService = {
11
+ performLogout: () => {
12
+ // Clear any pending notifications
13
+ DeviceEventEmitter.emit('chat:clear-notifications');
14
+
15
+ pushSubscriptionService.reset();
16
+ store.dispatch(logout());
17
+ store.dispatch(setLogoutState());
18
+ store.dispatch(clearHeap());
19
+ },
20
+ };
21
+ export const useLogout = () => {
22
+ const dispatch = useDispatch();
23
+
24
+ const handleLogout = useCallback(() => {
25
+ logoutService.performLogout();
26
+ }, []);
27
+
28
+ return handleLogout;
29
+ };
30
+
31
+ export { logoutService };
@@ -0,0 +1,11 @@
1
+ import { useSelector } from 'react-redux';
2
+ import { RootState } from '../roomStore';
3
+
4
+ export const useMessageHeapState = () => {
5
+ const queue = useSelector(
6
+ (state: RootState) => state.roomHeapSlice.messageHeap
7
+ );
8
+ const idSet = new Set(queue?.map((m) => m.id) ?? []);
9
+
10
+ return { queue, idSet };
11
+ };
@@ -0,0 +1,90 @@
1
+ import { useCallback, useEffect, useRef } from 'react';
2
+ import { IRoom } from '../types/types';
3
+
4
+ const DEFAULT_BATCH_SIZE = 5;
5
+ const DEFAULT_PAGE_SIZE = 10;
6
+ const DEFAULT_POLL_INTERVAL = 1_000;
7
+
8
+ const roomHasMoreMessages = (room: IRoom, max: number = 20) =>
9
+ (room.messages?.length ?? 0) < max;
10
+
11
+ const useMessageLoaderQueue = (
12
+ roomsList: string[],
13
+ rooms: Record<string, IRoom>,
14
+ globalLoading: boolean,
15
+ loading: boolean,
16
+ loadMoreMessages: (roomJid: string, max: number) => Promise<unknown>,
17
+ batchSize: number = DEFAULT_BATCH_SIZE,
18
+ pageSize: number = DEFAULT_PAGE_SIZE,
19
+ pollInterval: number = DEFAULT_POLL_INTERVAL
20
+ ) => {
21
+ const processedChats = useRef<Set<string>>(new Set());
22
+ const intervalRef = useRef<NodeJS.Timeout | null>(null);
23
+
24
+ const processQueue = useCallback(async () => {
25
+ if (globalLoading || loading) {return;}
26
+
27
+ const unprocessed = roomsList.filter(
28
+ (jid) => !processedChats.current.has(jid)
29
+ );
30
+
31
+ if (!unprocessed.length) {
32
+ if (intervalRef.current) {
33
+ clearInterval(intervalRef.current);
34
+ intervalRef.current = null;
35
+ }
36
+ return;
37
+ }
38
+
39
+ for (let i = 0; i < unprocessed.length; i += batchSize) {
40
+ const batch = unprocessed.slice(i, i + batchSize);
41
+
42
+ await Promise.all(
43
+ batch.map(async (jid) => {
44
+ const room = rooms[jid];
45
+ if (
46
+ !!room &&
47
+ roomHasMoreMessages(room) &&
48
+ !room.noMessages &&
49
+ !room.historyComplete
50
+ ) {
51
+ try {
52
+ await loadMoreMessages(jid, pageSize);
53
+ } catch (err) {
54
+ console.error(`Error loading messages for ${jid}`, err);
55
+ }
56
+
57
+ await new Promise((res) => setTimeout(res, 200));
58
+ }
59
+ processedChats.current.add(jid);
60
+ })
61
+ );
62
+ }
63
+ }, [
64
+ roomsList?.length,
65
+ globalLoading,
66
+ loading,
67
+ loadMoreMessages,
68
+ batchSize,
69
+ pageSize,
70
+ ]);
71
+
72
+ useEffect(() => {
73
+ if (intervalRef.current) {
74
+ clearInterval(intervalRef.current);
75
+ intervalRef.current = null;
76
+ }
77
+
78
+ processedChats.current = new Set();
79
+
80
+ if (!globalLoading && !loading && !!roomsList.length) {
81
+ intervalRef.current = setInterval(processQueue, pollInterval);
82
+ }
83
+
84
+ return () => {
85
+ if (intervalRef.current) {clearInterval(intervalRef.current);}
86
+ };
87
+ }, [roomsList?.length, globalLoading, loading]);
88
+ };
89
+
90
+ export default useMessageLoaderQueue;
@@ -0,0 +1,146 @@
1
+ import { useEffect, useRef, useState } from 'react';
2
+ import { useDispatch } from 'react-redux';
3
+ import { PriorityQueue } from '../utils/PriorityQueue';
4
+ import { IMessage, IRoom } from '../types/types';
5
+ import { setIsLoading } from '../roomStore/roomsSlice';
6
+
7
+ interface QueuedRoom {
8
+ jid: string;
9
+ priority: number;
10
+ lastMessageId?: string;
11
+ messageCount: number;
12
+ messages: IMessage[];
13
+ }
14
+
15
+ const hasDuplicateMessages = (
16
+ newMessages: IMessage[],
17
+ currentMessages: IMessage[]
18
+ ) => {
19
+ return newMessages.some((newMsg: IMessage) =>
20
+ currentMessages.some((currMsg: IMessage) => currMsg.id === newMsg.id)
21
+ );
22
+ };
23
+
24
+ const addRoomToProcessed = (
25
+ queueRoom: QueuedRoom,
26
+ processedRooms: React.MutableRefObject<Set<string>>,
27
+ rooms: { [jid: string]: IRoom }
28
+ ) => {
29
+ if (
30
+ queueRoom.messageCount >= 20 &&
31
+ !processedRooms.current.has(queueRoom.jid)
32
+ ) {
33
+ processedRooms.current.add(queueRoom.jid);
34
+ console.log(`Marking room: ${queueRoom.jid} as processed`);
35
+ if (rooms[queueRoom.jid].messages.length >= 30) {
36
+ console.log(`Trimming messages for room: ${queueRoom.jid}`);
37
+ rooms[queueRoom.jid].messages = rooms[queueRoom.jid].messages.slice(-30);
38
+ }
39
+ }
40
+ };
41
+
42
+ export const useMessageQueue = (
43
+ rooms: { [jid: string]: IRoom },
44
+ activeRoomJID: string,
45
+ loadMessages: (
46
+ roomJid: string,
47
+ max: number,
48
+ before?: number
49
+ ) => Promise<IMessage[]>
50
+ ) => {
51
+ const dispatch = useDispatch();
52
+ const messageQueue = useRef<PriorityQueue<QueuedRoom>>(
53
+ new PriorityQueue((a, b) => b.priority - a.priority)
54
+ );
55
+ const processedRooms = useRef<Set<string>>(new Set());
56
+
57
+ const asyncLoadMessages = async (
58
+ queueRoom: QueuedRoom,
59
+ rooms: { [jid: string]: IRoom }
60
+ ) => {
61
+ try {
62
+ if (
63
+ queueRoom.messageCount < 30 &&
64
+ !rooms[queueRoom.jid].historyComplete
65
+ ) {
66
+ console.log(`Loading messages for room: ${queueRoom.jid}`);
67
+ dispatch(setIsLoading({ chatJID: queueRoom.jid, loading: true }));
68
+
69
+ const newMessages = await loadMessages(
70
+ queueRoom.jid,
71
+ 5,
72
+ Number(queueRoom.lastMessageId)
73
+ );
74
+ queueRoom.messages = [...queueRoom.messages, ...newMessages];
75
+
76
+ if (
77
+ queueRoom.messageCount + queueRoom.messages.length < 30 &&
78
+ !rooms[queueRoom.jid].historyComplete
79
+ ) {
80
+ console.log(`Re-queuing room: ${queueRoom.jid} for more messages`);
81
+ messageQueue.current.push({
82
+ jid: queueRoom.jid,
83
+ priority: queueRoom.priority,
84
+ lastMessageId: queueRoom.lastMessageId,
85
+ messageCount: queueRoom.messageCount + newMessages.length,
86
+ messages: queueRoom.messages,
87
+ });
88
+ }
89
+ }
90
+ } catch (error) {
91
+ console.error(
92
+ `Error processing room: ${queueRoom.jid}, continue to next room`,
93
+ error
94
+ );
95
+ } finally {
96
+ dispatch(setIsLoading({ chatJID: queueRoom.jid, loading: false }));
97
+ dispatch(setIsLoading({ loading: false }));
98
+ console.log(`Finished processing queue: ${queueRoom.jid}`);
99
+ }
100
+ };
101
+
102
+ // Process queue
103
+ useEffect(() => {
104
+ const processQueue = async () => {
105
+ const queue = messageQueue.current;
106
+ if (queue.isEmpty()) {return;}
107
+
108
+ const roomsToProcess = queue.pop(5);
109
+ if (roomsToProcess.length === 0) {return;}
110
+
111
+ await Promise.all(
112
+ roomsToProcess.map(async (room) => {
113
+ try {
114
+ if (room.messageCount < 30 && !rooms[room.jid].historyComplete) {
115
+ console.log(`Loading messages for room: ${room.jid}`);
116
+ dispatch(setIsLoading({ chatJID: room.jid, loading: true }));
117
+ await loadMessages(room.jid, 20, Number(room.lastMessageId));
118
+
119
+ if (
120
+ room.messageCount + 20 < 30 &&
121
+ !rooms[room.jid].historyComplete
122
+ ) {
123
+ console.log(`Re-queuing room: ${room.jid} for more messages`);
124
+ queue.push({
125
+ ...room,
126
+ messageCount: room.messageCount + 6,
127
+ });
128
+ }
129
+ }
130
+
131
+ addRoomToProcessed(room, processedRooms, rooms);
132
+ } catch (error) {
133
+ console.error(`Error processing room: ${room.jid}`, error);
134
+ queue.push({ ...room, priority: Math.max(room.priority - 1, 1) });
135
+ } finally {
136
+ dispatch(setIsLoading({ chatJID: room.jid, loading: false }));
137
+ console.log(`Finished processing room: ${room.jid}`);
138
+ }
139
+ })
140
+ );
141
+ };
142
+
143
+ const interval = setInterval(processQueue, 250);
144
+ return () => clearInterval(interval);
145
+ }, []);
146
+ };
@@ -0,0 +1,49 @@
1
+ import { useEffect } from 'react';
2
+ import { useSelector, useDispatch } from 'react-redux';
3
+ import AsyncStorage from '@react-native-async-storage/async-storage';
4
+ import { RootState } from '../roomStore';
5
+ import { setCurrentRoom, clearPendingNotificationJid } from '../roomStore/roomsSlice';
6
+
7
+ const PENDING_NOTIFICATION_JID_KEY = 'ethora_pending_notification_jid';
8
+
9
+ export function usePendingNotification() {
10
+ const dispatch = useDispatch();
11
+ const rooms = useSelector((state: RootState) => state.rooms.rooms);
12
+ const pendingNotificationJid = useSelector(
13
+ (state: RootState) => state.rooms.pendingNotificationJid
14
+ );
15
+ const isLoading = useSelector((state: RootState) => state.rooms.isLoading);
16
+
17
+ useEffect(() => {
18
+ const checkPendingNotification = async () => {
19
+ if (isLoading) {return;}
20
+
21
+ let jidToOpen = pendingNotificationJid;
22
+
23
+ if (!jidToOpen) {
24
+ try {
25
+ const storedJid = await AsyncStorage.getItem(PENDING_NOTIFICATION_JID_KEY);
26
+ if (storedJid) {
27
+ jidToOpen = storedJid;
28
+ await AsyncStorage.removeItem(PENDING_NOTIFICATION_JID_KEY);
29
+ }
30
+ } catch (error) {
31
+ console.error('Error reading from AsyncStorage:', error);
32
+ }
33
+ }
34
+
35
+ if (jidToOpen) {
36
+ const room = rooms[jidToOpen];
37
+ if (room) {
38
+ return;
39
+ }
40
+ }
41
+ };
42
+
43
+ checkPendingNotification();
44
+ }, [rooms, pendingNotificationJid, isLoading, dispatch]);
45
+
46
+ return { pendingNotificationJid };
47
+ }
48
+
49
+ export default usePendingNotification;
@@ -0,0 +1,15 @@
1
+ import { useEffect, useRef } from 'react';
2
+
3
+ function usePrevious(value: any) {
4
+ // The ref object is a generic container whose current property is mutable ...
5
+ // ... and can hold any value, similar to an instance property on a class
6
+ const ref = useRef();
7
+ // Store current value in ref
8
+ useEffect(() => {
9
+ ref.current = value;
10
+ }, [value]); // Only re-run if value changes
11
+ // Return previous value (happens before update in useEffect above)
12
+ return ref.current;
13
+ }
14
+
15
+ export default usePrevious;
@@ -0,0 +1,106 @@
1
+ import { useEffect, useState } from 'react';
2
+ import { Alert, Platform } from 'react-native';
3
+ import {
4
+ initPushNotifications,
5
+ onForegroundMessage,
6
+ onNotificationOpenedApp,
7
+ getInitialNotification,
8
+ } from '../services/pushNotifications';
9
+ import { pushSubscriptionService } from '../services/pushSubscriptionService';
10
+ import { useSelector, useDispatch } from 'react-redux';
11
+ import { RootState } from '../roomStore';
12
+ import { useLocalStorage } from './useLocalStorage';
13
+ import { setCurrentRoom, setPendingNotificationJid } from '../roomStore/roomsSlice';
14
+
15
+ export function usePushNotifications() {
16
+ const [fcmToken, setFcmToken] = useState<string | null>(null);
17
+ const [notification, setNotification] = useState<any>(null);
18
+ const { user, config } = useSelector((state: RootState) => state.chatSettingStore);
19
+ const rooms = useSelector((state: RootState) => state.rooms.rooms);
20
+ const dispatch = useDispatch();
21
+
22
+ const { set, get } = useLocalStorage('fcmToken');
23
+
24
+ const handleNotificationPress = (remoteMessage: any) => {
25
+ console.log('[usePushNotifications] Notification pressed:', JSON.stringify(remoteMessage, null, 2));
26
+
27
+ const jid = remoteMessage?.data?.jid || remoteMessage?.data?.chatJid || remoteMessage?.jid;
28
+
29
+ if (jid) {
30
+ dispatch(setPendingNotificationJid(jid));
31
+ } else {
32
+ console.warn('No JID found in notification data:', remoteMessage?.data);
33
+ }
34
+ };
35
+
36
+ const initToken = async () => {
37
+ try {
38
+
39
+ const token = await get();
40
+ if (token) {
41
+ setFcmToken(token as string);
42
+ return;
43
+ }
44
+ const newToken = await initPushNotifications();
45
+
46
+ setFcmToken(newToken);
47
+ set(newToken as string);
48
+ } catch (error) {
49
+ console.error('Failed to init token:', error);
50
+ }
51
+ };
52
+
53
+ useEffect(() => {
54
+ initToken();
55
+
56
+ const unsubscribeForeground = onForegroundMessage((message) => {
57
+ setNotification(message);
58
+
59
+ const { notification: notif, data } = message;
60
+ if (notif) {
61
+ Alert.alert(
62
+ notif.title || 'Notification',
63
+ notif.body || '',
64
+ [
65
+ { text: 'OK' },
66
+ {
67
+ text: 'Open',
68
+ onPress: () => handleNotificationPress(message),
69
+ },
70
+ ]
71
+ );
72
+ }
73
+ });
74
+
75
+ const unsubscribeOpened = onNotificationOpenedApp((message) => {
76
+ console.log('[usePushNotifications] App opened from notification:', JSON.stringify(message, null, 2));
77
+ setNotification(message);
78
+ handleNotificationPress(message);
79
+ });
80
+
81
+ getInitialNotification().then((message) => {
82
+ if (message) {
83
+ console.log('[usePushNotifications] Initial notification:', JSON.stringify(message, null, 2));
84
+ setNotification(message);
85
+ handleNotificationPress(message);
86
+ }
87
+ });
88
+
89
+ return () => {
90
+ unsubscribeForeground();
91
+ unsubscribeOpened();
92
+ };
93
+ }, [rooms]);
94
+
95
+ useEffect(() => {
96
+ if (fcmToken && user && user.xmppPassword && user.defaultWallet?.walletAddress) {
97
+ pushSubscriptionService
98
+ .subscribeToPush(fcmToken, user, config?.projectName)
99
+ .catch((error) => {
100
+ console.error('Failed to subscribe to push:', error);
101
+ });
102
+ }
103
+ }, [fcmToken, user?.defaultWallet?.walletAddress, user?.xmppPassword]);
104
+
105
+ return { fcmToken, notification };
106
+ }
@@ -0,0 +1,58 @@
1
+ import { useEffect, useState } from 'react';
2
+ import { Linking } from 'react-native';
3
+ import AsyncStorage from '@react-native-async-storage/async-storage';
4
+
5
+ const QR_CHAT_STORAGE_KEY = '@ethora/chat-component-qrChatId';
6
+
7
+ export const handleQRChatId = async () => {
8
+ try {
9
+ const initialUrl = await Linking.getInitialURL();
10
+
11
+ if (initialUrl) {
12
+ const url = new URL(initialUrl);
13
+ const qrChatId = new URLSearchParams(url.search).get('qrChatId');
14
+
15
+ if (qrChatId) {
16
+ await AsyncStorage.setItem(QR_CHAT_STORAGE_KEY, qrChatId);
17
+ }
18
+ }
19
+ } catch (error) {
20
+ console.error('Error handling QR chat ID in RN:', error);
21
+ }
22
+ };
23
+
24
+ export const useQRCodeChat = (
25
+ setCurrentRoom: (params: { roomJID: string }) => void,
26
+ conferenceServer?: string
27
+ ) => {
28
+ const [wasAutoSelected, setWasAutoSelected] = useState(false);
29
+
30
+ useEffect(() => {
31
+ handleQRChatId();
32
+ }, []);
33
+
34
+ useEffect(() => {
35
+ const checkStoredQrId = async () => {
36
+ try {
37
+ const qrChatId = await AsyncStorage.getItem(QR_CHAT_STORAGE_KEY);
38
+
39
+ if (qrChatId) {
40
+ const roomJID = conferenceServer
41
+ ? `${qrChatId}@${conferenceServer}`
42
+ : `${qrChatId}@conference.xmpp.ethoradev.com`;
43
+
44
+ setCurrentRoom({ roomJID });
45
+
46
+ await AsyncStorage.removeItem(QR_CHAT_STORAGE_KEY);
47
+ setWasAutoSelected(true);
48
+ }
49
+ } catch (error) {
50
+ console.error('Error using QR chat ID:', error);
51
+ }
52
+ };
53
+
54
+ checkStoredQrId();
55
+ }, [setCurrentRoom, conferenceServer]);
56
+
57
+ return { wasAutoSelected };
58
+ };