@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,603 @@
1
+ import xmpp, { Client } from '@xmpp/client';
2
+ import { walletToUsername } from '../helpers/walletUsername';
3
+ import { xmppSettingsInterface } from '../types/types';
4
+
5
+ import { sendMediaMessage } from './xmpp/sendMediaMessage.xmpp';
6
+ import { getChatsPrivateStoreRequest } from './xmpp/getChatsPrivateStoreRequest.xmpp';
7
+ import { actionSetTimestampToPrivateStore } from './xmpp/actionSetTimestampToPrivateStore.xmpp';
8
+ import { sendTypingRequest } from './xmpp/sendTypingRequest.xmpp';
9
+ import { getHistory } from './xmpp/getHistory.xmpp';
10
+ import { sendTextMessage } from './xmpp/sendTextMessage.xmpp';
11
+ import { deleteMessage } from './xmpp/deleteMessage.xmpp';
12
+ import { presenceInRoom } from './xmpp/presenceInRoom.xmpp';
13
+ import { getLastMessage } from './xmpp/getLastMessageArchive.xmpp';
14
+ import { createRoom } from './xmpp/createRoom.xmpp';
15
+ import { setRoomImage } from './xmpp/setRoomImage.xmpp';
16
+ import { getRoomMembers } from './xmpp/getRoomMembers.xmpp';
17
+ import { getRoomInfo } from './xmpp/getRoomInfo.xmpp';
18
+ import { leaveTheRoom } from './xmpp/leaveTheRoom.xmpp';
19
+ import { editMessage } from './xmpp/editMessage.xmpp';
20
+ import { inviteRoomRequest } from './xmpp/inviteRoomRequest.xmpp';
21
+ import { getRooms } from './xmpp/getRooms.xmpp';
22
+ import { handleStanza } from './xmpp/handleStanzas.xmpp';
23
+ import { pushLog as devPushLog } from '../utils/devLogger';
24
+
25
+ // Canonical production XMPP WSS endpoint (standard wss/443, no port suffix).
26
+ const DEFAULT_DEV_SERVER = 'xmpp.chat.ethora.com';
27
+
28
+ type HistorySource = 'active' | 'send_ack' | 'background' | 'default';
29
+
30
+ interface HistoryOptions {
31
+ coalesceRoom?: boolean;
32
+ skipIfPreloaded?: boolean;
33
+ source?: HistorySource;
34
+ }
35
+
36
+ interface MamInFlightEntry {
37
+ promise: Promise<any>;
38
+ source: HistorySource;
39
+ startedAt: number;
40
+ }
41
+
42
+ export class XmppClient {
43
+ client!: Client;
44
+ xmppSettings?: xmppSettingsInterface;
45
+ devServer: string | undefined;
46
+ host: string = '';
47
+ service: string = '';
48
+ conference: string = '';
49
+ username: string;
50
+ status: 'offline' | 'connecting' | 'online' | 'error' = 'offline';
51
+
52
+ password = '';
53
+ reconnectAttempts = 0;
54
+ maxReconnectAttempts = 5;
55
+ reconnectDelay = 2000;
56
+ suppressReconnect = false;
57
+
58
+ // ---- QoS state (mirrors web XmppClient) ----------------------------
59
+ disableLastRead = false;
60
+ private activeRoomJID: string | null = null;
61
+ private activeRoomBoostUntil = 0;
62
+ private softPauseUntil = 0;
63
+ private mamInFlightByRoom: Map<string, MamInFlightEntry> = new Map();
64
+ private maxInFlightHistory = 3;
65
+ private softPauseAfterSendMs = 250;
66
+ private activeRoomBoostTtlMs = 4000;
67
+ private alwaysPrioritizeActiveRoom = true;
68
+
69
+ // -------------------------------------------------------------------
70
+
71
+ checkOnline() {
72
+ return this.client && this.client.status === 'online';
73
+ }
74
+
75
+ constructor(
76
+ username: string,
77
+ password: string,
78
+ xmppSettings?: xmppSettingsInterface | string
79
+ ) {
80
+ // Back-compat: third arg used to be a bare devServer string.
81
+ if (typeof xmppSettings === 'string') {
82
+ this.xmppSettings = { devServer: xmppSettings };
83
+ this.devServer = xmppSettings;
84
+ } else {
85
+ this.xmppSettings = xmppSettings;
86
+ this.devServer = xmppSettings?.devServer;
87
+ }
88
+ this.disableLastRead = this.xmppSettings?.disableLastRead === true;
89
+
90
+ const qos = this.xmppSettings?.historyQoS;
91
+ if (qos) {
92
+ if (typeof qos.maxInFlightHistory === 'number')
93
+ {this.maxInFlightHistory = qos.maxInFlightHistory;}
94
+ if (typeof qos.softPauseAfterSendMs === 'number')
95
+ {this.softPauseAfterSendMs = qos.softPauseAfterSendMs;}
96
+ if (typeof qos.activeRoomBoostTtlMs === 'number')
97
+ {this.activeRoomBoostTtlMs = qos.activeRoomBoostTtlMs;}
98
+ if (typeof qos.alwaysPrioritizeActiveRoom === 'boolean')
99
+ {this.alwaysPrioritizeActiveRoom = qos.alwaysPrioritizeActiveRoom;}
100
+ }
101
+
102
+ this.username = username;
103
+ this.password = password;
104
+ this.initializeClient();
105
+ }
106
+
107
+ // ===================================================================
108
+ // QoS API — mirrors web XmppClientInterface
109
+ // ===================================================================
110
+ setActiveRoomJid(roomJID: string | null) {
111
+ this.activeRoomJID = roomJID;
112
+ if (roomJID) {this.promoteRoomHistory(roomJID);}
113
+ }
114
+
115
+ promoteRoomHistory(_roomJID: string) {
116
+ this.activeRoomBoostUntil = Date.now() + this.activeRoomBoostTtlMs;
117
+ }
118
+
119
+ /**
120
+ * Active-room gate is "open" whenever the boost has expired OR there's
121
+ * room for more concurrent MAM fetches. Scheduler uses this to backoff.
122
+ */
123
+ isActiveRoomGateOpen(): boolean {
124
+ if (this.mamInFlightByRoom.size >= this.maxInFlightHistory) {return false;}
125
+ if (Date.now() < this.softPauseUntil) {return false;}
126
+ return true;
127
+ }
128
+
129
+ /** Called right before a critical send. Soft-pauses background MAM. */
130
+ onCriticalSend(roomJID: string, _messageId?: string) {
131
+ this.softPauseUntil = Date.now() + this.softPauseAfterSendMs;
132
+ if (roomJID) {this.promoteRoomHistory(roomJID);}
133
+ }
134
+
135
+ /**
136
+ * Higher-priority presence than presenceInRoomStanza. For now just
137
+ * delegates — kept distinct so callers don't change when full priority
138
+ * lanes land later.
139
+ */
140
+ async prioritizeRoomPresence(roomJID: string): Promise<boolean> {
141
+ try {
142
+ this.presenceInRoomStanza(roomJID);
143
+ return true;
144
+ } catch {
145
+ return false;
146
+ }
147
+ }
148
+
149
+ /**
150
+ * Queued history fetch. Coalesces by room: if the same room already has
151
+ * a higher-priority request in flight, return that promise. Honors
152
+ * `skipIfPreloaded` by checking redux state (lazy import to avoid cycle).
153
+ */
154
+ async enqueueHistoryTask(params: {
155
+ chatJID: string;
156
+ max: number;
157
+ before?: number;
158
+ id?: string;
159
+ source?: HistorySource;
160
+ }): Promise<any> {
161
+ const { chatJID, max, before, id, source = 'default' } = params;
162
+
163
+ // Coalesce — if a fetch for the same room is in-flight, reuse it
164
+ // unless the new request is higher priority.
165
+ const existing = this.mamInFlightByRoom.get(chatJID);
166
+ if (existing && this.sourceRank(existing.source) <= this.sourceRank(source)) {
167
+ return existing.promise;
168
+ }
169
+
170
+ // Wait for the gate (in-flight cap + soft-pause). For active-room
171
+ // requests we still proceed past the cap when the boost is fresh.
172
+ while (
173
+ this.mamInFlightByRoom.size >= this.maxInFlightHistory &&
174
+ !(this.alwaysPrioritizeActiveRoom && this.activeRoomJID === chatJID)
175
+ ) {
176
+ await new Promise((r) => setTimeout(r, 80));
177
+ }
178
+ if (Date.now() < this.softPauseUntil && source === 'background') {
179
+ const wait = this.softPauseUntil - Date.now();
180
+ await new Promise((r) => setTimeout(r, wait));
181
+ }
182
+
183
+ // Self-reference via an outer holder so the IIFE's `finally` can
184
+ // identify "is the registry entry I started still the current one?"
185
+ // without TS's TDZ false-positive (TS2454 on a bare `let promise`).
186
+ const handle: { p?: Promise<any> } = {};
187
+ handle.p = (async () => {
188
+ try {
189
+ return await getHistory(this.client, chatJID, max, before, id);
190
+ } finally {
191
+ const cur = this.mamInFlightByRoom.get(chatJID);
192
+ if (cur && cur.promise === handle.p) {
193
+ this.mamInFlightByRoom.delete(chatJID);
194
+ }
195
+ }
196
+ })();
197
+
198
+ this.mamInFlightByRoom.set(chatJID, {
199
+ promise: handle.p,
200
+ source,
201
+ startedAt: Date.now(),
202
+ });
203
+ return handle.p;
204
+ }
205
+
206
+ private sourceRank(s: HistorySource): number {
207
+ switch (s) {
208
+ case 'active':
209
+ return 0;
210
+ case 'send_ack':
211
+ return 1;
212
+ case 'default':
213
+ return 2;
214
+ case 'background':
215
+ return 3;
216
+ }
217
+ }
218
+
219
+ initializeClient() {
220
+ try {
221
+ const devServer = this.devServer || DEFAULT_DEV_SERVER;
222
+ const url = `wss://${devServer}/ws`;
223
+ this.service = url;
224
+ this.host =
225
+ this.xmppSettings?.host || url.match(/wss:\/\/([^:/]+)/)?.[1] || '';
226
+ this.conference =
227
+ this.xmppSettings?.conference || `conference.${this.host}`;
228
+ this.status = 'connecting';
229
+
230
+ this.client = xmpp.client({
231
+ service: url,
232
+ username: walletToUsername(this.username),
233
+ password: this.password,
234
+ });
235
+
236
+ // Wrap `send` so the dev logger sees outgoing stanzas too.
237
+ // Guarded: some @xmpp/client builds define `send` as a getter or
238
+ // non-writable property, and overwriting would throw silently and
239
+ // strand the whole pipeline. We catch everything and fall back to
240
+ // the unwrapped client.
241
+ try {
242
+ const origSend = this.client.send?.bind(this.client);
243
+ if (origSend) {
244
+ const wrapped = (stanza: any) => {
245
+ try {
246
+ const tag = stanza?.name || 'stanza';
247
+ const id = stanza?.attrs?.id || '';
248
+ const to = stanza?.attrs?.to || '';
249
+ devPushLog(
250
+ 'xmpp',
251
+ `→ ${tag}${id ? ` id=${id}` : ''}${to ? ` to=${to.split('/')[0]}` : ''}`,
252
+ stanza?.toString ? stanza.toString() : undefined
253
+ );
254
+ } catch {}
255
+ return origSend(stanza);
256
+ };
257
+ // Property may be non-writable on some builds; defineProperty
258
+ // gives us a clearer error than a plain assignment.
259
+ Object.defineProperty(this.client, 'send', {
260
+ value: wrapped,
261
+ writable: true,
262
+ configurable: true,
263
+ });
264
+ }
265
+ } catch (err) {
266
+ console.warn(
267
+ 'XmppClient: could not wrap client.send for dev logging',
268
+ err
269
+ );
270
+ }
271
+
272
+ this.attachEventListeners();
273
+ this.client.start().catch((error) => {
274
+ console.error('Error starting xmpp client:', error);
275
+ this.status = 'error';
276
+ });
277
+ } catch (error) {
278
+ console.error('Error initializing client:', error);
279
+ this.status = 'error';
280
+ }
281
+ }
282
+
283
+ attachEventListeners() {
284
+ this.client.on('disconnect', () => {
285
+ console.log('XMPP disconnected.');
286
+ this.status = 'offline';
287
+ try {
288
+ // lazy-require to avoid pulling devLogger into prod bundles
289
+ // that don't reference it; tree-shaken via dead-code elim.
290
+ devPushLog('xmpp', 'disconnect');
291
+ } catch {}
292
+ });
293
+
294
+ this.client.on('online', () => {
295
+ console.log('XMPP online.', new Date());
296
+ this.status = 'online';
297
+ this.reconnectAttempts = 0;
298
+ try {
299
+ devPushLog(
300
+ 'xmpp',
301
+ 'online',
302
+ this.username
303
+ );
304
+ } catch {}
305
+ });
306
+
307
+ this.client.on('error', (error) => {
308
+ console.error('XMPP client error:', error);
309
+ try {
310
+ devPushLog(
311
+ 'xmpp',
312
+ 'error',
313
+ (error && error.message) || error
314
+ );
315
+ } catch {}
316
+ });
317
+
318
+ this.client.on('stanza', (stanza: any) => {
319
+ try {
320
+ const tag = stanza?.name || 'stanza';
321
+ const id = stanza?.attrs?.id || '';
322
+ const from = stanza?.attrs?.from || '';
323
+ const type = stanza?.attrs?.type || '';
324
+ devPushLog(
325
+ 'xmpp',
326
+ `← ${tag}${id ? ` id=${id}` : ''}${type ? ` type=${type}` : ''}${from ? ` from=${from.split('/')[0]}` : ''}`,
327
+ stanza?.toString ? stanza.toString() : undefined
328
+ );
329
+ } catch {}
330
+ handleStanza.bind(this, stanza, this)();
331
+ });
332
+ }
333
+
334
+ /**
335
+ * Resolves once status === 'online' or rejects when status === 'error'.
336
+ * Mirrors the web client's `ensureConnected` helper.
337
+ */
338
+ waitForOnline(timeoutMs: number = 15000): Promise<void> {
339
+ if (this.status === 'online') {return Promise.resolve();}
340
+ return new Promise((resolve, reject) => {
341
+ const start = Date.now();
342
+ const tick = () => {
343
+ if (this.status === 'online') {return resolve();}
344
+ if (this.status === 'error') {
345
+ return reject(new Error('XMPP client error'));
346
+ }
347
+ if (Date.now() - start > timeoutMs) {
348
+ return reject(new Error('XMPP connect timeout'));
349
+ }
350
+ setTimeout(tick, 250);
351
+ };
352
+ tick();
353
+ });
354
+ }
355
+
356
+ // Alias to match the web XmppClientInterface.
357
+ ensureConnected(timeout?: number): Promise<void> {
358
+ return this.waitForOnline(timeout);
359
+ }
360
+
361
+ scheduleReconnect() {
362
+ if (this.suppressReconnect) {return;}
363
+ if (this.reconnectAttempts >= this.maxReconnectAttempts) {
364
+ console.error('Max reconnect attempts reached.');
365
+ return;
366
+ }
367
+ this.reconnectAttempts++;
368
+ const delay =
369
+ this.reconnectDelay * Math.pow(2, this.reconnectAttempts - 1);
370
+ console.log(`Reconnecting attempt ${this.reconnectAttempts} in ${delay}ms`);
371
+ setTimeout(() => this.reconnect(), delay);
372
+ }
373
+
374
+ reconnect() {
375
+ if (this.suppressReconnect) {return;}
376
+ console.log('Attempting to reconnect xmpp client...');
377
+ if (this.client) {
378
+ this.client.stop().finally(() => {
379
+ this.initializeClient();
380
+ });
381
+ } else {
382
+ this.initializeClient();
383
+ }
384
+ }
385
+
386
+ async disconnect(options?: { suppressReconnect?: boolean }): Promise<void> {
387
+ if (options?.suppressReconnect) {this.suppressReconnect = true;}
388
+ return this.close();
389
+ }
390
+
391
+ async close() {
392
+ if (this.client) {
393
+ try {
394
+ await this.client.stop();
395
+ console.log('XMPP client connection closed.');
396
+ } catch (error) {
397
+ console.error('Error closing the xmpp client:', error);
398
+ }
399
+ }
400
+ this.status = 'offline';
401
+ }
402
+
403
+ getRoomsStanza = async () => {
404
+ await getRooms(this.client);
405
+ };
406
+
407
+ //room functions
408
+
409
+ async createRoomStanza(title: string, description: string, to?: string) {
410
+ return await createRoom(title, description, this.client, to);
411
+ }
412
+
413
+ async inviteRoomRequestStanza(to: string, roomJid: string) {
414
+ await inviteRoomRequest(this.client, to, roomJid);
415
+ }
416
+
417
+ leaveTheRoomStanza = (roomJID: string) => {
418
+ leaveTheRoom(roomJID, this.client);
419
+ };
420
+
421
+ presenceInRoomStanza = (roomJID: string) => {
422
+ presenceInRoom(this.client, roomJID);
423
+ };
424
+
425
+ getHistoryStanza = async (
426
+ chatJID: string,
427
+ max: number,
428
+ before?: number,
429
+ id?: string,
430
+ options?: HistoryOptions
431
+ ) => {
432
+ if (options?.coalesceRoom) {
433
+ return this.enqueueHistoryTask({
434
+ chatJID,
435
+ max,
436
+ before,
437
+ id,
438
+ source: options?.source || 'default',
439
+ });
440
+ }
441
+ return await getHistory(this.client, chatJID, max, before, id);
442
+ };
443
+
444
+ getLastMessageArchiveStanza(roomJID: string) {
445
+ getLastMessage(this.client, roomJID);
446
+ }
447
+
448
+ setRoomImageStanza = (
449
+ roomJid: string,
450
+ roomThumbnail: string,
451
+ type: string,
452
+ roomBackground?: string
453
+ ) => {
454
+ setRoomImage(roomJid, roomThumbnail, type, this.client, roomBackground);
455
+ };
456
+
457
+ getRoomInfoStanza = (roomJID: string) => {
458
+ getRoomInfo(roomJID, this.client);
459
+ };
460
+
461
+ getRoomMembersStanza = (roomJID: string) => {
462
+ getRoomMembers(roomJID, this.client);
463
+ };
464
+
465
+ //messages
466
+ sendMessage = (
467
+ roomJID: string,
468
+ firstName: string,
469
+ lastName: string,
470
+ photo: string,
471
+ walletAddress: string,
472
+ userMessage: string,
473
+ notDisplayedValue?: string,
474
+ isReply?: boolean,
475
+ showInChannel?: boolean,
476
+ mainMessage?: string,
477
+ customId?: string
478
+ ) => {
479
+ sendTextMessage(
480
+ this.client,
481
+ roomJID,
482
+ firstName,
483
+ lastName,
484
+ photo,
485
+ walletAddress,
486
+ userMessage,
487
+ notDisplayedValue,
488
+ isReply,
489
+ showInChannel,
490
+ mainMessage,
491
+ this.devServer || DEFAULT_DEV_SERVER,
492
+ customId
493
+ );
494
+ };
495
+
496
+ deleteMessageStanza(room: string, msgId: string) {
497
+ deleteMessage(this.client, room, msgId);
498
+ }
499
+
500
+ editMessageStanza(room: string, msgId: string, text: string) {
501
+ editMessage(this.client, room, msgId, text);
502
+ }
503
+
504
+ sendTypingRequestStanza(chatId: string, fullName: string, start: boolean) {
505
+ sendTypingRequest(this.client, chatId, fullName, start);
506
+ }
507
+
508
+ getChatsPrivateStoreRequestStanza = async () => {
509
+ if (this.disableLastRead) {return null;}
510
+ try {
511
+ return await getChatsPrivateStoreRequest(this.client);
512
+ } catch (error) {
513
+ console.log(error);
514
+ return null;
515
+ }
516
+ };
517
+
518
+ async actionSetTimestampToPrivateStoreStanza(
519
+ chatId: string,
520
+ timestamp: number,
521
+ chats?: string[]
522
+ ) {
523
+ if (this.disableLastRead) {return;}
524
+ try {
525
+ await actionSetTimestampToPrivateStore(
526
+ this.client,
527
+ chatId,
528
+ timestamp,
529
+ chats
530
+ );
531
+ } catch (error) {}
532
+ }
533
+
534
+ sendMediaMessageStanza(roomJID: string, data: any, customId?: string) {
535
+ return sendMediaMessage(
536
+ this.client,
537
+ roomJID,
538
+ data,
539
+ customId,
540
+ this.devServer || DEFAULT_DEV_SERVER
541
+ );
542
+ }
543
+
544
+ // -------------------------------------------------------------------
545
+ // Method stubs to satisfy `XmppClientInterface`. Concrete RN xmpp
546
+ // helpers don't exist yet — the stubs log a single warning and no-op
547
+ // so consumers don't blow up at runtime. Replace with real impls when
548
+ // their `*.xmpp.ts` helpers are ported from the web side.
549
+ // -------------------------------------------------------------------
550
+
551
+ setVCardStanza(_xmppUsername: string) {
552
+ console.warn('setVCardStanza: not implemented in RN xmpp client');
553
+ }
554
+
555
+ async createPrivateRoomStanza(
556
+ title: string,
557
+ description: string,
558
+ to: string
559
+ ): Promise<string> {
560
+ console.warn('createPrivateRoomStanza: not implemented; falling back to createRoom');
561
+ const result = await this.createRoomStanza(title, description, to);
562
+ return typeof result === 'string' ? result : '';
563
+ }
564
+
565
+ sendMessageReactionStanza(
566
+ _messageId: string,
567
+ _roomJid: string,
568
+ _reactionsList: string[],
569
+ _reactionSymbol?: string
570
+ ) {
571
+ console.warn('sendMessageReactionStanza: not implemented in RN xmpp client');
572
+ }
573
+
574
+ sendTextMessageWithTranslateTagStanza(
575
+ roomJID: string,
576
+ firstName: string,
577
+ lastName: string,
578
+ photo: string,
579
+ walletAddress: string,
580
+ userMessage: string,
581
+ notDisplayedValue?: string,
582
+ isReply?: boolean,
583
+ showInChannel?: boolean,
584
+ mainMessage?: string,
585
+ _langSource?: string
586
+ ) {
587
+ // No translate tag support yet — fall back to a regular text send.
588
+ this.sendMessage(
589
+ roomJID,
590
+ firstName,
591
+ lastName,
592
+ photo,
593
+ walletAddress,
594
+ userMessage,
595
+ notDisplayedValue,
596
+ isReply,
597
+ showInChannel,
598
+ mainMessage
599
+ );
600
+ }
601
+ }
602
+
603
+ export default XmppClient;
@@ -0,0 +1,24 @@
1
+ import { Middleware } from '@reduxjs/toolkit';
2
+ import { DeviceEventEmitter } from 'react-native';
3
+
4
+ export const logoutMiddleware: Middleware =
5
+ (storeAPI) => (next) => (action: any) => {
6
+ if (!action || !action.type) {
7
+ console.error('Invalid action in logoutMiddleware:', action);
8
+ return next(action);
9
+ }
10
+
11
+ const result = next(action);
12
+
13
+ if (action.type === 'chatSettingStore/logout') {
14
+ try {
15
+ setTimeout(() => {
16
+ DeviceEventEmitter.emit('ethora-xmpp-logout');
17
+ }, 0);
18
+ } catch (error) {
19
+ console.error('Error dispatching ethora-xmpp-logout event:', error);
20
+ }
21
+ }
22
+
23
+ return result;
24
+ };
@@ -0,0 +1,87 @@
1
+ import { Middleware } from '@reduxjs/toolkit';
2
+ import { updateRoom } from '../roomsSlice';
3
+ import { IRoom } from '../../types/types';
4
+
5
+ export const newMessageMidlleware: Middleware =
6
+ (storeAPI) => (next) => (action: any) => {
7
+ if (!action || !action.type) {
8
+ console.error('Invalid action in newMessageMiddleware:', action);
9
+ return next(action);
10
+ }
11
+
12
+ if (action.type !== 'roomMessages/addRoomMessage') {
13
+ return next(action);
14
+ }
15
+
16
+ if (!action.payload || typeof action.payload !== 'object') {
17
+ console.error('Invalid action payload for addRoomMessage:', action);
18
+ return next(action);
19
+ }
20
+
21
+ const result = next(action);
22
+ const state = storeAPI.getState();
23
+ const rooms: { [jid: string]: IRoom } = state.rooms.rooms;
24
+
25
+ const { roomJID, message } = action.payload;
26
+
27
+ if (rooms[roomJID]?.lastMessageTimestamp <= Number(message.id)) {
28
+ storeAPI.dispatch(
29
+ updateRoom({
30
+ jid: roomJID,
31
+ updates: { lastMessageTimestamp: Number(message.id) ?? 0 },
32
+ })
33
+ );
34
+ }
35
+
36
+ return result;
37
+ };
38
+
39
+ // import { Middleware, PayloadAction } from '@reduxjs/toolkit';
40
+ // import { updateRoom } from '../roomsSlice';
41
+ // import { AddRoomMessageAction, IRoom } from '../../types/types';
42
+ // import { nanoToMs } from '../../helpers/nanoToMs';
43
+
44
+ // export const newMessageMidlleware: Middleware =
45
+ // (storeAPI) =>
46
+ // (next) =>
47
+ // (action: PayloadAction<Partial<AddRoomMessageAction>>) => {
48
+ // if (action.type !== 'roomMessages/addRoomMessage') {
49
+ // return next(action);
50
+ // }
51
+
52
+ // const result = next(action);
53
+ // const state = storeAPI.getState();
54
+ // const rooms: { [jid: string]: IRoom } = state.rooms.rooms;
55
+ // const { roomJID, message } = action.payload;
56
+
57
+ // const updLastMessage = () => {
58
+ // const updates = {
59
+ // lastMessageTimestamp: nanoToMs(message.id) ?? 0,
60
+ // lastMessage: { ...message },
61
+ // };
62
+
63
+ // storeAPI.dispatch(
64
+ // updateRoom({
65
+ // jid: roomJID,
66
+ // updates,
67
+ // })
68
+ // );
69
+ // };
70
+
71
+ // if (!message.body) {
72
+ // return result;
73
+ // }
74
+
75
+ // try {
76
+ // if (
77
+ // rooms[roomJID]?.lastMessageTimestamp <= nanoToMs(message.id) ||
78
+ // !rooms[roomJID]?.lastMessageTimestamp
79
+ // ) {
80
+ // updLastMessage();
81
+ // }
82
+ // } catch (error) {
83
+ // console.error('Error updating room last message:', error);
84
+ // }
85
+
86
+ // return result;
87
+ // };