@ethora/chat-component-rn 26.5.5 → 26.5.10

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 (420) hide show
  1. package/CHANGELOG.md +177 -0
  2. package/README.md +67 -10
  3. package/index.js +24 -0
  4. package/lib/commonjs/assets/icons.js +5 -1
  5. package/lib/commonjs/assets/icons.js.map +1 -1
  6. package/lib/commonjs/components/ContextMenu/ContextMenuComponents.js +11 -5
  7. package/lib/commonjs/components/ContextMenu/ContextMenuComponents.js.map +1 -1
  8. package/lib/commonjs/components/MainComponents/ChatHeader.js +7 -1
  9. package/lib/commonjs/components/MainComponents/ChatHeader.js.map +1 -1
  10. package/lib/commonjs/components/MainComponents/ChatRoom.js +66 -34
  11. package/lib/commonjs/components/MainComponents/ChatRoom.js.map +1 -1
  12. package/lib/commonjs/components/MainComponents/ChatWrapper.js +59 -14
  13. package/lib/commonjs/components/MainComponents/ChatWrapper.js.map +1 -1
  14. package/lib/commonjs/components/MainComponents/MediaMessage.js +15 -7
  15. package/lib/commonjs/components/MainComponents/MediaMessage.js.map +1 -1
  16. package/lib/commonjs/components/MainComponents/MessageContainer.js +2 -2
  17. package/lib/commonjs/components/MainComponents/MessageContainer.js.map +1 -1
  18. package/lib/commonjs/components/MainComponents/MessageList.js +175 -30
  19. package/lib/commonjs/components/MainComponents/MessageList.js.map +1 -1
  20. package/lib/commonjs/components/MainComponents/ReduxWrapper.js +17 -8
  21. package/lib/commonjs/components/MainComponents/ReduxWrapper.js.map +1 -1
  22. package/lib/commonjs/components/MainComponents/SystemMessage.js +34 -22
  23. package/lib/commonjs/components/MainComponents/SystemMessage.js.map +1 -1
  24. package/lib/commonjs/components/MessageBubble/Message.js +117 -19
  25. package/lib/commonjs/components/MessageBubble/Message.js.map +1 -1
  26. package/lib/commonjs/components/MessageBubble/MessageInteractions.js +82 -22
  27. package/lib/commonjs/components/MessageBubble/MessageInteractions.js.map +1 -1
  28. package/lib/commonjs/components/Modals/AttachSheet/AttachSheet.js +16 -4
  29. package/lib/commonjs/components/Modals/AttachSheet/AttachSheet.js.map +1 -1
  30. package/lib/commonjs/components/Modals/FilePreviewModal/DocumentViewer.js +104 -0
  31. package/lib/commonjs/components/Modals/FilePreviewModal/DocumentViewer.js.map +1 -0
  32. package/lib/commonjs/components/Modals/FilePreviewModal/FilePreviewModal.js +175 -40
  33. package/lib/commonjs/components/Modals/FilePreviewModal/FilePreviewModal.js.map +1 -1
  34. package/lib/commonjs/components/Modals/FilePreviewModal/PdfView.js +29 -1
  35. package/lib/commonjs/components/Modals/FilePreviewModal/PdfView.js.map +1 -1
  36. package/lib/commonjs/components/Modals/Modal/Modal.js +5 -1
  37. package/lib/commonjs/components/Modals/Modal/Modal.js.map +1 -1
  38. package/lib/commonjs/components/Modals/ModalWrapper/ModalWrapper.js +78 -39
  39. package/lib/commonjs/components/Modals/ModalWrapper/ModalWrapper.js.map +1 -1
  40. package/lib/commonjs/components/Modals/UserProfileModal/UserProfileModal.js +3 -3
  41. package/lib/commonjs/components/Modals/UserProfileModal/UserProfileModal.js.map +1 -1
  42. package/lib/commonjs/components/Modals/styledModalComponents.js +4 -9
  43. package/lib/commonjs/components/Modals/styledModalComponents.js.map +1 -1
  44. package/lib/commonjs/components/styled/MediaFilePreview.js +115 -5
  45. package/lib/commonjs/components/styled/MediaFilePreview.js.map +1 -1
  46. package/lib/commonjs/components/styled/MessageImage.js +49 -10
  47. package/lib/commonjs/components/styled/MessageImage.js.map +1 -1
  48. package/lib/commonjs/components/styled/NewMessageLabel.js +8 -6
  49. package/lib/commonjs/components/styled/NewMessageLabel.js.map +1 -1
  50. package/lib/commonjs/components/styled/SendInput.js +53 -29
  51. package/lib/commonjs/components/styled/SendInput.js.map +1 -1
  52. package/lib/commonjs/components/styled/StyledInputComponents/MediaComponents.js +14 -4
  53. package/lib/commonjs/components/styled/StyledInputComponents/MediaComponents.js.map +1 -1
  54. package/lib/commonjs/components/styled/StyledInputComponents/StyledInputComponents.js +7 -2
  55. package/lib/commonjs/components/styled/StyledInputComponents/StyledInputComponents.js.map +1 -1
  56. package/lib/commonjs/components/styled/UnsupportedType.js +5 -1
  57. package/lib/commonjs/components/styled/UnsupportedType.js.map +1 -1
  58. package/lib/commonjs/components/styled/VideoMessage.js +60 -22
  59. package/lib/commonjs/components/styled/VideoMessage.js.map +1 -1
  60. package/lib/commonjs/context/MessageNotificationContext.js +7 -7
  61. package/lib/commonjs/context/MessageNotificationContext.js.map +1 -1
  62. package/lib/commonjs/context/xmppProvider.js +315 -34
  63. package/lib/commonjs/context/xmppProvider.js.map +1 -1
  64. package/lib/commonjs/helpers/chatAutoEnterer.js +2 -7
  65. package/lib/commonjs/helpers/chatAutoEnterer.js.map +1 -1
  66. package/lib/commonjs/helpers/historyPreloadScheduler.js +4 -1
  67. package/lib/commonjs/helpers/historyPreloadScheduler.js.map +1 -1
  68. package/lib/commonjs/helpers/insertMessageWithDelimiter.js +15 -5
  69. package/lib/commonjs/helpers/insertMessageWithDelimiter.js.map +1 -1
  70. package/lib/commonjs/helpers/keyboardLayout.js +18 -0
  71. package/lib/commonjs/helpers/keyboardLayout.js.map +1 -0
  72. package/lib/commonjs/helpers/mediaDimensions.js +51 -0
  73. package/lib/commonjs/helpers/mediaDimensions.js.map +1 -0
  74. package/lib/commonjs/helpers/mimeToExtension.js +131 -0
  75. package/lib/commonjs/helpers/mimeToExtension.js.map +1 -0
  76. package/lib/commonjs/helpers/normalizeRoomJid.js +20 -0
  77. package/lib/commonjs/helpers/normalizeRoomJid.js.map +1 -0
  78. package/lib/commonjs/helpers/resolveInitBeforeLoadUser.js +73 -1
  79. package/lib/commonjs/helpers/resolveInitBeforeLoadUser.js.map +1 -1
  80. package/lib/commonjs/hooks/useChatRoomFocus.js +87 -0
  81. package/lib/commonjs/hooks/useChatRoomFocus.js.map +1 -0
  82. package/lib/commonjs/hooks/useChatWrapperInit.js +22 -10
  83. package/lib/commonjs/hooks/useChatWrapperInit.js.map +1 -1
  84. package/lib/commonjs/hooks/useEventHandlers.js +9 -1
  85. package/lib/commonjs/hooks/useEventHandlers.js.map +1 -1
  86. package/lib/commonjs/hooks/useHeapSender.js +1 -1
  87. package/lib/commonjs/hooks/useHeapSender.js.map +1 -1
  88. package/lib/commonjs/hooks/useLogout.js +3 -3
  89. package/lib/commonjs/hooks/useLogout.js.map +1 -1
  90. package/lib/commonjs/hooks/useMessageHeapState.js +6 -2
  91. package/lib/commonjs/hooks/useMessageHeapState.js.map +1 -1
  92. package/lib/commonjs/hooks/usePushNotifications.js +6 -2
  93. package/lib/commonjs/hooks/usePushNotifications.js.map +1 -1
  94. package/lib/commonjs/hooks/useRoomState.js +2 -0
  95. package/lib/commonjs/hooks/useRoomState.js.map +1 -1
  96. package/lib/commonjs/hooks/useSendMessage.js +249 -14
  97. package/lib/commonjs/hooks/useSendMessage.js.map +1 -1
  98. package/lib/commonjs/hooks/useUnreadMessagesCounter.js +12 -14
  99. package/lib/commonjs/hooks/useUnreadMessagesCounter.js.map +1 -1
  100. package/lib/commonjs/main.js +7 -0
  101. package/lib/commonjs/main.js.map +1 -1
  102. package/lib/commonjs/networking/api-requests/auth.api.js +74 -1
  103. package/lib/commonjs/networking/api-requests/auth.api.js.map +1 -1
  104. package/lib/commonjs/networking/apiClient.js +2 -1
  105. package/lib/commonjs/networking/apiClient.js.map +1 -1
  106. package/lib/commonjs/networking/outboundQueue.js +92 -0
  107. package/lib/commonjs/networking/outboundQueue.js.map +1 -0
  108. package/lib/commonjs/networking/xmpp/createTimeoutPromise.xmpp.js +4 -1
  109. package/lib/commonjs/networking/xmpp/createTimeoutPromise.xmpp.js.map +1 -1
  110. package/lib/commonjs/networking/xmpp/flushLastViewedToPrivateStore.js +7 -8
  111. package/lib/commonjs/networking/xmpp/flushLastViewedToPrivateStore.js.map +1 -1
  112. package/lib/commonjs/networking/xmpp/getRoomsPaged.xmpp.js +26 -3
  113. package/lib/commonjs/networking/xmpp/getRoomsPaged.xmpp.js.map +1 -1
  114. package/lib/commonjs/networking/xmpp/presenceInRoom.xmpp.js +26 -8
  115. package/lib/commonjs/networking/xmpp/presenceInRoom.xmpp.js.map +1 -1
  116. package/lib/commonjs/networking/xmpp/sendTextMessage.xmpp.js +2 -1
  117. package/lib/commonjs/networking/xmpp/sendTextMessage.xmpp.js.map +1 -1
  118. package/lib/commonjs/networking/xmppClient.js +371 -26
  119. package/lib/commonjs/networking/xmppClient.js.map +1 -1
  120. package/lib/commonjs/roomStore/Middleware/unreadMidlleware.js +22 -6
  121. package/lib/commonjs/roomStore/Middleware/unreadMidlleware.js.map +1 -1
  122. package/lib/commonjs/roomStore/roomHeapSlice.js +26 -8
  123. package/lib/commonjs/roomStore/roomHeapSlice.js.map +1 -1
  124. package/lib/commonjs/roomStore/roomsSlice.js +157 -23
  125. package/lib/commonjs/roomStore/roomsSlice.js.map +1 -1
  126. package/lib/commonjs/utils/installPromiseRejectionTracker.js +83 -0
  127. package/lib/commonjs/utils/installPromiseRejectionTracker.js.map +1 -0
  128. package/lib/commonjs/utils/resendMessage.js +1 -1
  129. package/lib/commonjs/utils/resendMessage.js.map +1 -1
  130. package/lib/module/assets/icons.js +5 -1
  131. package/lib/module/assets/icons.js.map +1 -1
  132. package/lib/module/components/ContextMenu/ContextMenuComponents.js +11 -5
  133. package/lib/module/components/ContextMenu/ContextMenuComponents.js.map +1 -1
  134. package/lib/module/components/MainComponents/ChatHeader.js +7 -1
  135. package/lib/module/components/MainComponents/ChatHeader.js.map +1 -1
  136. package/lib/module/components/MainComponents/ChatRoom.js +67 -35
  137. package/lib/module/components/MainComponents/ChatRoom.js.map +1 -1
  138. package/lib/module/components/MainComponents/ChatWrapper.js +59 -14
  139. package/lib/module/components/MainComponents/ChatWrapper.js.map +1 -1
  140. package/lib/module/components/MainComponents/MediaMessage.js +15 -7
  141. package/lib/module/components/MainComponents/MediaMessage.js.map +1 -1
  142. package/lib/module/components/MainComponents/MessageContainer.js +2 -2
  143. package/lib/module/components/MainComponents/MessageContainer.js.map +1 -1
  144. package/lib/module/components/MainComponents/MessageList.js +176 -31
  145. package/lib/module/components/MainComponents/MessageList.js.map +1 -1
  146. package/lib/module/components/MainComponents/ReduxWrapper.js +18 -8
  147. package/lib/module/components/MainComponents/ReduxWrapper.js.map +1 -1
  148. package/lib/module/components/MainComponents/SystemMessage.js +33 -21
  149. package/lib/module/components/MainComponents/SystemMessage.js.map +1 -1
  150. package/lib/module/components/MessageBubble/Message.js +118 -20
  151. package/lib/module/components/MessageBubble/Message.js.map +1 -1
  152. package/lib/module/components/MessageBubble/MessageInteractions.js +84 -24
  153. package/lib/module/components/MessageBubble/MessageInteractions.js.map +1 -1
  154. package/lib/module/components/Modals/AttachSheet/AttachSheet.js +15 -3
  155. package/lib/module/components/Modals/AttachSheet/AttachSheet.js.map +1 -1
  156. package/lib/module/components/Modals/FilePreviewModal/DocumentViewer.js +99 -0
  157. package/lib/module/components/Modals/FilePreviewModal/DocumentViewer.js.map +1 -0
  158. package/lib/module/components/Modals/FilePreviewModal/FilePreviewModal.js +179 -42
  159. package/lib/module/components/Modals/FilePreviewModal/FilePreviewModal.js.map +1 -1
  160. package/lib/module/components/Modals/FilePreviewModal/PdfView.js +31 -3
  161. package/lib/module/components/Modals/FilePreviewModal/PdfView.js.map +1 -1
  162. package/lib/module/components/Modals/Modal/Modal.js +5 -1
  163. package/lib/module/components/Modals/Modal/Modal.js.map +1 -1
  164. package/lib/module/components/Modals/ModalWrapper/ModalWrapper.js +80 -41
  165. package/lib/module/components/Modals/ModalWrapper/ModalWrapper.js.map +1 -1
  166. package/lib/module/components/Modals/UserProfileModal/UserProfileModal.js +3 -3
  167. package/lib/module/components/Modals/UserProfileModal/UserProfileModal.js.map +1 -1
  168. package/lib/module/components/Modals/styledModalComponents.js +4 -9
  169. package/lib/module/components/Modals/styledModalComponents.js.map +1 -1
  170. package/lib/module/components/styled/MediaFilePreview.js +116 -6
  171. package/lib/module/components/styled/MediaFilePreview.js.map +1 -1
  172. package/lib/module/components/styled/MessageImage.js +51 -12
  173. package/lib/module/components/styled/MessageImage.js.map +1 -1
  174. package/lib/module/components/styled/NewMessageLabel.js +8 -6
  175. package/lib/module/components/styled/NewMessageLabel.js.map +1 -1
  176. package/lib/module/components/styled/SendInput.js +54 -30
  177. package/lib/module/components/styled/SendInput.js.map +1 -1
  178. package/lib/module/components/styled/StyledInputComponents/MediaComponents.js +14 -4
  179. package/lib/module/components/styled/StyledInputComponents/MediaComponents.js.map +1 -1
  180. package/lib/module/components/styled/StyledInputComponents/StyledInputComponents.js +7 -2
  181. package/lib/module/components/styled/StyledInputComponents/StyledInputComponents.js.map +1 -1
  182. package/lib/module/components/styled/UnsupportedType.js +5 -1
  183. package/lib/module/components/styled/UnsupportedType.js.map +1 -1
  184. package/lib/module/components/styled/VideoMessage.js +61 -23
  185. package/lib/module/components/styled/VideoMessage.js.map +1 -1
  186. package/lib/module/context/MessageNotificationContext.js +7 -7
  187. package/lib/module/context/MessageNotificationContext.js.map +1 -1
  188. package/lib/module/context/xmppProvider.js +319 -37
  189. package/lib/module/context/xmppProvider.js.map +1 -1
  190. package/lib/module/helpers/chatAutoEnterer.js +2 -7
  191. package/lib/module/helpers/chatAutoEnterer.js.map +1 -1
  192. package/lib/module/helpers/historyPreloadScheduler.js +4 -1
  193. package/lib/module/helpers/historyPreloadScheduler.js.map +1 -1
  194. package/lib/module/helpers/insertMessageWithDelimiter.js +15 -5
  195. package/lib/module/helpers/insertMessageWithDelimiter.js.map +1 -1
  196. package/lib/module/helpers/keyboardLayout.js +12 -0
  197. package/lib/module/helpers/keyboardLayout.js.map +1 -0
  198. package/lib/module/helpers/mediaDimensions.js +46 -0
  199. package/lib/module/helpers/mediaDimensions.js.map +1 -0
  200. package/lib/module/helpers/mimeToExtension.js +124 -0
  201. package/lib/module/helpers/mimeToExtension.js.map +1 -0
  202. package/lib/module/helpers/normalizeRoomJid.js +15 -0
  203. package/lib/module/helpers/normalizeRoomJid.js.map +1 -0
  204. package/lib/module/helpers/resolveInitBeforeLoadUser.js +71 -0
  205. package/lib/module/helpers/resolveInitBeforeLoadUser.js.map +1 -1
  206. package/lib/module/hooks/useChatRoomFocus.js +82 -0
  207. package/lib/module/hooks/useChatRoomFocus.js.map +1 -0
  208. package/lib/module/hooks/useChatWrapperInit.js +22 -10
  209. package/lib/module/hooks/useChatWrapperInit.js.map +1 -1
  210. package/lib/module/hooks/useEventHandlers.js +9 -1
  211. package/lib/module/hooks/useEventHandlers.js.map +1 -1
  212. package/lib/module/hooks/useHeapSender.js +1 -1
  213. package/lib/module/hooks/useHeapSender.js.map +1 -1
  214. package/lib/module/hooks/useLogout.js +3 -3
  215. package/lib/module/hooks/useLogout.js.map +1 -1
  216. package/lib/module/hooks/useMessageHeapState.js +6 -2
  217. package/lib/module/hooks/useMessageHeapState.js.map +1 -1
  218. package/lib/module/hooks/usePushNotifications.js +6 -2
  219. package/lib/module/hooks/usePushNotifications.js.map +1 -1
  220. package/lib/module/hooks/useRoomState.js +2 -0
  221. package/lib/module/hooks/useRoomState.js.map +1 -1
  222. package/lib/module/hooks/useSendMessage.js +252 -17
  223. package/lib/module/hooks/useSendMessage.js.map +1 -1
  224. package/lib/module/hooks/useUnreadMessagesCounter.js +12 -14
  225. package/lib/module/hooks/useUnreadMessagesCounter.js.map +1 -1
  226. package/lib/module/main.js +1 -0
  227. package/lib/module/main.js.map +1 -1
  228. package/lib/module/networking/api-requests/auth.api.js +74 -2
  229. package/lib/module/networking/api-requests/auth.api.js.map +1 -1
  230. package/lib/module/networking/apiClient.js +2 -1
  231. package/lib/module/networking/apiClient.js.map +1 -1
  232. package/lib/module/networking/outboundQueue.js +84 -0
  233. package/lib/module/networking/outboundQueue.js.map +1 -0
  234. package/lib/module/networking/xmpp/createTimeoutPromise.xmpp.js +4 -1
  235. package/lib/module/networking/xmpp/createTimeoutPromise.xmpp.js.map +1 -1
  236. package/lib/module/networking/xmpp/flushLastViewedToPrivateStore.js +7 -8
  237. package/lib/module/networking/xmpp/flushLastViewedToPrivateStore.js.map +1 -1
  238. package/lib/module/networking/xmpp/getRoomsPaged.xmpp.js +26 -3
  239. package/lib/module/networking/xmpp/getRoomsPaged.xmpp.js.map +1 -1
  240. package/lib/module/networking/xmpp/presenceInRoom.xmpp.js +26 -8
  241. package/lib/module/networking/xmpp/presenceInRoom.xmpp.js.map +1 -1
  242. package/lib/module/networking/xmpp/sendTextMessage.xmpp.js +2 -1
  243. package/lib/module/networking/xmpp/sendTextMessage.xmpp.js.map +1 -1
  244. package/lib/module/networking/xmppClient.js +371 -26
  245. package/lib/module/networking/xmppClient.js.map +1 -1
  246. package/lib/module/roomStore/Middleware/unreadMidlleware.js +22 -6
  247. package/lib/module/roomStore/Middleware/unreadMidlleware.js.map +1 -1
  248. package/lib/module/roomStore/roomHeapSlice.js +24 -7
  249. package/lib/module/roomStore/roomHeapSlice.js.map +1 -1
  250. package/lib/module/roomStore/roomsSlice.js +154 -22
  251. package/lib/module/roomStore/roomsSlice.js.map +1 -1
  252. package/lib/module/utils/installPromiseRejectionTracker.js +78 -0
  253. package/lib/module/utils/installPromiseRejectionTracker.js.map +1 -0
  254. package/lib/module/utils/resendMessage.js +1 -1
  255. package/lib/module/utils/resendMessage.js.map +1 -1
  256. package/lib/typescript/assets/icons.d.ts +8 -4
  257. package/lib/typescript/assets/icons.d.ts.map +1 -1
  258. package/lib/typescript/components/ContextMenu/ContextMenuComponents.d.ts.map +1 -1
  259. package/lib/typescript/components/MainComponents/ChatHeader.d.ts.map +1 -1
  260. package/lib/typescript/components/MainComponents/ChatRoom.d.ts.map +1 -1
  261. package/lib/typescript/components/MainComponents/ChatWrapper.d.ts.map +1 -1
  262. package/lib/typescript/components/MainComponents/MediaMessage.d.ts +1 -0
  263. package/lib/typescript/components/MainComponents/MediaMessage.d.ts.map +1 -1
  264. package/lib/typescript/components/MainComponents/MessageContainer.d.ts.map +1 -1
  265. package/lib/typescript/components/MainComponents/MessageList.d.ts.map +1 -1
  266. package/lib/typescript/components/MainComponents/ReduxWrapper.d.ts +1 -0
  267. package/lib/typescript/components/MainComponents/ReduxWrapper.d.ts.map +1 -1
  268. package/lib/typescript/components/MainComponents/SystemMessage.d.ts +0 -7
  269. package/lib/typescript/components/MainComponents/SystemMessage.d.ts.map +1 -1
  270. package/lib/typescript/components/MessageBubble/Message.d.ts.map +1 -1
  271. package/lib/typescript/components/MessageBubble/MessageInteractions.d.ts +4 -2
  272. package/lib/typescript/components/MessageBubble/MessageInteractions.d.ts.map +1 -1
  273. package/lib/typescript/components/Modals/AttachSheet/AttachSheet.d.ts.map +1 -1
  274. package/lib/typescript/components/Modals/FilePreviewModal/DocumentViewer.d.ts +29 -0
  275. package/lib/typescript/components/Modals/FilePreviewModal/DocumentViewer.d.ts.map +1 -0
  276. package/lib/typescript/components/Modals/FilePreviewModal/FilePreviewModal.d.ts +0 -1
  277. package/lib/typescript/components/Modals/FilePreviewModal/FilePreviewModal.d.ts.map +1 -1
  278. package/lib/typescript/components/Modals/FilePreviewModal/PdfView.d.ts.map +1 -1
  279. package/lib/typescript/components/Modals/Modal/Modal.d.ts.map +1 -1
  280. package/lib/typescript/components/Modals/ModalWrapper/ModalWrapper.d.ts +6 -0
  281. package/lib/typescript/components/Modals/ModalWrapper/ModalWrapper.d.ts.map +1 -1
  282. package/lib/typescript/components/Modals/UserProfileModal/UserProfileModal.d.ts.map +1 -1
  283. package/lib/typescript/components/Modals/styledModalComponents.d.ts.map +1 -1
  284. package/lib/typescript/components/styled/MediaFilePreview.d.ts.map +1 -1
  285. package/lib/typescript/components/styled/MessageImage.d.ts.map +1 -1
  286. package/lib/typescript/components/styled/NewMessageLabel.d.ts +1 -5
  287. package/lib/typescript/components/styled/NewMessageLabel.d.ts.map +1 -1
  288. package/lib/typescript/components/styled/SendInput.d.ts.map +1 -1
  289. package/lib/typescript/components/styled/StyledInputComponents/MediaComponents.d.ts +9 -1
  290. package/lib/typescript/components/styled/StyledInputComponents/MediaComponents.d.ts.map +1 -1
  291. package/lib/typescript/components/styled/StyledInputComponents/StyledInputComponents.d.ts.map +1 -1
  292. package/lib/typescript/components/styled/UnsupportedType.d.ts +1 -0
  293. package/lib/typescript/components/styled/UnsupportedType.d.ts.map +1 -1
  294. package/lib/typescript/components/styled/VideoMessage.d.ts.map +1 -1
  295. package/lib/typescript/context/xmppProvider.d.ts +1 -0
  296. package/lib/typescript/context/xmppProvider.d.ts.map +1 -1
  297. package/lib/typescript/helpers/chatAutoEnterer.d.ts.map +1 -1
  298. package/lib/typescript/helpers/historyPreloadScheduler.d.ts.map +1 -1
  299. package/lib/typescript/helpers/insertMessageWithDelimiter.d.ts.map +1 -1
  300. package/lib/typescript/helpers/keyboardLayout.d.ts +9 -0
  301. package/lib/typescript/helpers/keyboardLayout.d.ts.map +1 -0
  302. package/lib/typescript/helpers/mediaDimensions.d.ts +9 -0
  303. package/lib/typescript/helpers/mediaDimensions.d.ts.map +1 -0
  304. package/lib/typescript/helpers/mimeToExtension.d.ts +37 -0
  305. package/lib/typescript/helpers/mimeToExtension.d.ts.map +1 -0
  306. package/lib/typescript/helpers/normalizeRoomJid.d.ts +2 -0
  307. package/lib/typescript/helpers/normalizeRoomJid.d.ts.map +1 -0
  308. package/lib/typescript/helpers/resolveInitBeforeLoadUser.d.ts +18 -0
  309. package/lib/typescript/helpers/resolveInitBeforeLoadUser.d.ts.map +1 -1
  310. package/lib/typescript/hooks/useChatRoomFocus.d.ts +42 -0
  311. package/lib/typescript/hooks/useChatRoomFocus.d.ts.map +1 -0
  312. package/lib/typescript/hooks/useChatWrapperInit.d.ts.map +1 -1
  313. package/lib/typescript/hooks/useEventHandlers.d.ts +6 -0
  314. package/lib/typescript/hooks/useEventHandlers.d.ts.map +1 -1
  315. package/lib/typescript/hooks/useHeapSender.d.ts.map +1 -1
  316. package/lib/typescript/hooks/useMessageHeapState.d.ts +2 -0
  317. package/lib/typescript/hooks/useMessageHeapState.d.ts.map +1 -1
  318. package/lib/typescript/hooks/usePushNotifications.d.ts.map +1 -1
  319. package/lib/typescript/hooks/useRoomState.d.ts +1 -0
  320. package/lib/typescript/hooks/useRoomState.d.ts.map +1 -1
  321. package/lib/typescript/hooks/useSendMessage.d.ts +2 -1
  322. package/lib/typescript/hooks/useSendMessage.d.ts.map +1 -1
  323. package/lib/typescript/hooks/useUnreadMessagesCounter.d.ts.map +1 -1
  324. package/lib/typescript/main.d.ts +1 -0
  325. package/lib/typescript/main.d.ts.map +1 -1
  326. package/lib/typescript/networking/api-requests/auth.api.d.ts +3 -0
  327. package/lib/typescript/networking/api-requests/auth.api.d.ts.map +1 -1
  328. package/lib/typescript/networking/apiClient.d.ts.map +1 -1
  329. package/lib/typescript/networking/outboundQueue.d.ts +57 -0
  330. package/lib/typescript/networking/outboundQueue.d.ts.map +1 -0
  331. package/lib/typescript/networking/xmpp/createTimeoutPromise.xmpp.d.ts.map +1 -1
  332. package/lib/typescript/networking/xmpp/flushLastViewedToPrivateStore.d.ts +1 -1
  333. package/lib/typescript/networking/xmpp/flushLastViewedToPrivateStore.d.ts.map +1 -1
  334. package/lib/typescript/networking/xmpp/getRoomsPaged.xmpp.d.ts +1 -1
  335. package/lib/typescript/networking/xmpp/getRoomsPaged.xmpp.d.ts.map +1 -1
  336. package/lib/typescript/networking/xmpp/presenceInRoom.xmpp.d.ts.map +1 -1
  337. package/lib/typescript/networking/xmpp/sendTextMessage.xmpp.d.ts.map +1 -1
  338. package/lib/typescript/networking/xmppClient.d.ts +58 -5
  339. package/lib/typescript/networking/xmppClient.d.ts.map +1 -1
  340. package/lib/typescript/roomStore/Middleware/unreadMidlleware.d.ts.map +1 -1
  341. package/lib/typescript/roomStore/roomHeapSlice.d.ts +22 -1
  342. package/lib/typescript/roomStore/roomHeapSlice.d.ts.map +1 -1
  343. package/lib/typescript/roomStore/roomsSlice.d.ts +8 -1
  344. package/lib/typescript/roomStore/roomsSlice.d.ts.map +1 -1
  345. package/lib/typescript/types/models/media.model.d.ts +1 -0
  346. package/lib/typescript/types/models/media.model.d.ts.map +1 -1
  347. package/lib/typescript/types/models/xmpp.model.d.ts +1 -1
  348. package/lib/typescript/types/models/xmpp.model.d.ts.map +1 -1
  349. package/lib/typescript/types/types.d.ts +10 -0
  350. package/lib/typescript/types/types.d.ts.map +1 -1
  351. package/lib/typescript/utils/installPromiseRejectionTracker.d.ts +20 -0
  352. package/lib/typescript/utils/installPromiseRejectionTracker.d.ts.map +1 -0
  353. package/lib/typescript/utils/resendMessage.d.ts.map +1 -1
  354. package/package.json +18 -2
  355. package/src/assets/icons.tsx +23 -14
  356. package/src/components/ContextMenu/ContextMenuComponents.tsx +10 -5
  357. package/src/components/MainComponents/ChatHeader.tsx +5 -1
  358. package/src/components/MainComponents/ChatRoom.tsx +81 -61
  359. package/src/components/MainComponents/ChatWrapper.tsx +100 -21
  360. package/src/components/MainComponents/MediaMessage.tsx +18 -8
  361. package/src/components/MainComponents/MessageContainer.tsx +4 -2
  362. package/src/components/MainComponents/MessageList.tsx +185 -31
  363. package/src/components/MainComponents/ReduxWrapper.tsx +19 -9
  364. package/src/components/MainComponents/SystemMessage.tsx +38 -26
  365. package/src/components/MessageBubble/Message.tsx +110 -27
  366. package/src/components/MessageBubble/MessageInteractions.tsx +98 -29
  367. package/src/components/Modals/AttachSheet/AttachSheet.tsx +17 -3
  368. package/src/components/Modals/FilePreviewModal/DocumentViewer.tsx +93 -0
  369. package/src/components/Modals/FilePreviewModal/FilePreviewModal.tsx +182 -37
  370. package/src/components/Modals/FilePreviewModal/PdfView.tsx +32 -4
  371. package/src/components/Modals/Modal/Modal.tsx +4 -1
  372. package/src/components/Modals/ModalWrapper/ModalWrapper.tsx +77 -44
  373. package/src/components/Modals/UserProfileModal/UserProfileModal.tsx +19 -15
  374. package/src/components/Modals/styledModalComponents.tsx +4 -9
  375. package/src/components/styled/MediaFilePreview.tsx +111 -5
  376. package/src/components/styled/MessageImage.tsx +58 -14
  377. package/src/components/styled/NewMessageLabel.tsx +8 -5
  378. package/src/components/styled/SendInput.tsx +67 -29
  379. package/src/components/styled/StyledInputComponents/MediaComponents.tsx +15 -5
  380. package/src/components/styled/StyledInputComponents/StyledInputComponents.tsx +7 -2
  381. package/src/components/styled/UnsupportedType.tsx +7 -1
  382. package/src/components/styled/VideoMessage.tsx +59 -18
  383. package/src/context/MessageNotificationContext.tsx +8 -8
  384. package/src/context/xmppProvider.tsx +337 -32
  385. package/src/helpers/chatAutoEnterer.ts +2 -8
  386. package/src/helpers/historyPreloadScheduler.ts +4 -1
  387. package/src/helpers/insertMessageWithDelimiter.ts +22 -15
  388. package/src/helpers/keyboardLayout.ts +18 -0
  389. package/src/helpers/mediaDimensions.ts +47 -0
  390. package/src/helpers/mimeToExtension.ts +135 -0
  391. package/src/helpers/normalizeRoomJid.ts +6 -0
  392. package/src/helpers/resolveInitBeforeLoadUser.ts +79 -0
  393. package/src/hooks/useChatRoomFocus.ts +95 -0
  394. package/src/hooks/useChatWrapperInit.ts +51 -46
  395. package/src/hooks/useEventHandlers.tsx +24 -1
  396. package/src/hooks/useHeapSender.tsx +2 -1
  397. package/src/hooks/useLogout.tsx +3 -3
  398. package/src/hooks/useMessageHeapState.tsx +6 -2
  399. package/src/hooks/usePushNotifications.ts +21 -8
  400. package/src/hooks/useRoomState.tsx +4 -0
  401. package/src/hooks/useSendMessage.tsx +333 -17
  402. package/src/hooks/useUnreadMessagesCounter.ts +13 -16
  403. package/src/main.ts +1 -0
  404. package/src/networking/api-requests/auth.api.ts +60 -2
  405. package/src/networking/apiClient.ts +2 -1
  406. package/src/networking/outboundQueue.ts +98 -0
  407. package/src/networking/xmpp/createTimeoutPromise.xmpp.ts +5 -1
  408. package/src/networking/xmpp/flushLastViewedToPrivateStore.ts +11 -13
  409. package/src/networking/xmpp/getRoomsPaged.xmpp.ts +27 -3
  410. package/src/networking/xmpp/presenceInRoom.xmpp.ts +24 -9
  411. package/src/networking/xmpp/sendTextMessage.xmpp.ts +2 -1
  412. package/src/networking/xmppClient.ts +399 -31
  413. package/src/roomStore/Middleware/unreadMidlleware.tsx +23 -5
  414. package/src/roomStore/roomHeapSlice.ts +47 -4
  415. package/src/roomStore/roomsSlice.ts +177 -27
  416. package/src/types/models/media.model.ts +3 -0
  417. package/src/types/models/xmpp.model.ts +2 -1
  418. package/src/types/types.ts +25 -0
  419. package/src/utils/installPromiseRejectionTracker.ts +91 -0
  420. package/src/utils/resendMessage.ts +2 -1
@@ -22,10 +22,35 @@ import { inviteRoomRequest } from './xmpp/inviteRoomRequest.xmpp';
22
22
  import { getRooms } from './xmpp/getRooms.xmpp';
23
23
  import { handleStanza } from './xmpp/handleStanzas.xmpp';
24
24
  import { pushLog as devPushLog } from '../utils/devLogger';
25
+ import { normalizeRoomJid } from '../helpers/normalizeRoomJid';
26
+ import {
27
+ clearOutboundSends,
28
+ enqueueOutboundSend,
29
+ flushOutboundSends,
30
+ } from './outboundQueue';
25
31
 
26
32
  // Canonical production XMPP WSS endpoint (standard wss/443, no port suffix).
27
33
  const DEFAULT_DEV_SERVER = 'xmpp.chat.ethora.com';
28
34
 
35
+ // @xmpp/client surfaces SASL/stream errors in a few shapes depending on
36
+ // where it fails — XMPPError on stream:error, SASLError on SASL bind, or
37
+ // a generic Error whose message contains "not-authorized". Match all of
38
+ // them so we trigger the credentials refresh path consistently.
39
+ function isNotAuthorizedError(err: any): boolean {
40
+ if (!err) {return false;}
41
+ const condition =
42
+ err?.condition ||
43
+ err?.name ||
44
+ err?.type ||
45
+ err?.error?.condition ||
46
+ '';
47
+ if (typeof condition === 'string' && condition.includes('not-authorized')) {
48
+ return true;
49
+ }
50
+ const message = (err?.message || err?.text || '').toString();
51
+ return /not-authorized|SASLError/i.test(message);
52
+ }
53
+
29
54
  type HistorySource = 'active' | 'send_ack' | 'background' | 'default';
30
55
 
31
56
  interface HistoryOptions {
@@ -40,6 +65,11 @@ interface MamInFlightEntry {
40
65
  startedAt: number;
41
66
  }
42
67
 
68
+ export type XmppCredentialsProvider = () => Promise<{
69
+ username: string;
70
+ password: string;
71
+ }>;
72
+
43
73
  export class XmppClient {
44
74
  client!: Client;
45
75
  xmppSettings?: xmppSettingsInterface;
@@ -54,7 +84,35 @@ export class XmppClient {
54
84
  reconnectAttempts = 0;
55
85
  maxReconnectAttempts = 5;
56
86
  reconnectDelay = 2000;
87
+ // Upper bound on the backoff delay. The old hard stop at
88
+ // maxReconnectAttempts gave up entirely after ~62s; now we keep retrying
89
+ // at a steady cadence on a long outage and just clamp the delay here.
90
+ maxReconnectDelay = 30000;
91
+ // Timestamp of the last forced reconnect so bursty external triggers
92
+ // (NetInfo + AppState + the provider watchdog firing together) can't
93
+ // spawn a reconnect storm.
94
+ private lastReconnectAt = 0;
57
95
  suppressReconnect = false;
96
+ // Tracked so close()/disconnect() can cancel a pending reconnect —
97
+ // otherwise a scheduleReconnect() timer fires after logout and (until
98
+ // suppressReconnect short-circuits it) keeps the dead instance alive.
99
+ private reconnectTimer: ReturnType<typeof setTimeout> | null = null;
100
+ // Single-flight guard for reconnect(). Multiple triggers can fire near-
101
+ // simultaneously (AppState 'active' + NetInfo restore + a scheduled
102
+ // timer). Without this, two overlapping reconnect()s each spawn a fresh
103
+ // underlying client and one is leaked with its `stanza` handler still
104
+ // bound → duplicate stanza dispatch into redux.
105
+ private reconnecting = false;
106
+
107
+ // Set when SASL fails with `not-authorized` (typically a stale JWT-
108
+ // derived XMPP password after idle). Triggers the credentialsProvider
109
+ // refresh path in reconnect().
110
+ lastAuthError: 'not-authorized' | null = null;
111
+ private credentialsProvider: XmppCredentialsProvider | null = null;
112
+ private credentialsRefreshInFlight: Promise<void> | null = null;
113
+ // Fired by the provider on every 'online' so it can re-join MUC rooms
114
+ // after a reconnect (bug #21 — reconnected but not in the room).
115
+ private onOnlineCallback: (() => void) | null = null;
58
116
 
59
117
  // ---- QoS state (mirrors web XmppClient) ----------------------------
60
118
  presencesReady = false;
@@ -74,6 +132,29 @@ export class XmppClient {
74
132
  return this.client && this.client.status === 'online';
75
133
  }
76
134
 
135
+ /**
136
+ * Inject (or replace) the callback used by reconnect() to fetch fresh
137
+ * XMPP credentials after a `not-authorized` SASL failure. The provider
138
+ * is responsible for refreshing whatever upstream token is needed
139
+ * (REST JWT, /users/client, etc.) and returning the resulting
140
+ * username + password.
141
+ */
142
+ setCredentialsProvider(provider: XmppCredentialsProvider | null) {
143
+ this.credentialsProvider = provider;
144
+ }
145
+
146
+ /** Install a callback invoked on every successful 'online'. The provider
147
+ * uses it to re-join MUC rooms after a reconnect. */
148
+ setOnOnline(cb: (() => void) | null) {
149
+ this.onOnlineCallback = cb;
150
+ }
151
+
152
+ /** Swap in fresh credentials before the next reconnect. */
153
+ updateCredentials(username: string, password: string) {
154
+ this.username = username;
155
+ this.password = password;
156
+ }
157
+
77
158
  constructor(
78
159
  username: string,
79
160
  password: string,
@@ -229,12 +310,24 @@ export class XmppClient {
229
310
  this.xmppSettings?.conference || `conference.${this.host}`;
230
311
  this.status = 'connecting';
231
312
 
313
+ // Defensive: if a previous underlying client is still referenced
314
+ // (initializeClient reached from a path other than reconnect()),
315
+ // detach its handlers before we overwrite the ref so the orphaned
316
+ // client can't keep dispatching stanzas into redux.
317
+ try {
318
+ this.detachEventListeners();
319
+ } catch {}
320
+
232
321
  this.client = xmpp.client({
233
322
  service: url,
234
323
  username: walletToUsername(this.username),
235
324
  password: this.password,
236
325
  });
237
326
 
327
+ try {
328
+ (this.client as any)?.reconnect?.stop?.();
329
+ } catch {}
330
+
238
331
  // Concurrent room preloads each register their own short-lived
239
332
  // 'stanza' handler (getHistory, getRooms, getChatsPrivateStore,
240
333
  // ...). With 5+ rooms preloading in parallel, Node's default
@@ -265,7 +358,18 @@ export class XmppClient {
265
358
  stanza?.toString ? stanza.toString() : undefined
266
359
  );
267
360
  } catch {}
268
- return origSend(stanza);
361
+ const result = origSend(stanza);
362
+ // Most stanza helpers fire-and-forget `client.send(...)`
363
+ // without awaiting/catching. On @xmpp/client builds where
364
+ // send() returns a Promise, a transient send failure (e.g.
365
+ // mid-reconnect) then surfaces as a red-screen "Uncaught (in
366
+ // promise, id: N)" (#4). Attach a no-op catch so the
367
+ // rejection is considered handled — callers that DO await/
368
+ // catch still get it, because we return the SAME promise.
369
+ if (result && typeof (result as any).catch === 'function') {
370
+ (result as any).catch(() => {});
371
+ }
372
+ return result;
269
373
  };
270
374
  // Property may be non-writable on some builds; defineProperty
271
375
  // gives us a clearer error than a plain assignment.
@@ -285,7 +389,21 @@ export class XmppClient {
285
389
  this.attachEventListeners();
286
390
  this.client.start().catch((error: any) => {
287
391
  console.error('Error starting xmpp client:', error);
392
+ if (isNotAuthorizedError(error)) {
393
+ this.lastAuthError = 'not-authorized';
394
+ }
288
395
  this.status = 'error';
396
+ // A connect-time failure lands in 'error', which — unlike a
397
+ // 'disconnect' after being online — has no other retry path
398
+ // (onError doesn't reschedule; the provider only reacts to
399
+ // 'offline'). Schedule a backoff retry so a failed initial or
400
+ // reconnect attempt recovers on its own. reconnect() refreshes
401
+ // creds first when the failure was a SASL not-authorized.
402
+ if (!this.suppressReconnect) {
403
+ try {
404
+ this.scheduleReconnect();
405
+ } catch {}
406
+ }
289
407
  });
290
408
  } catch (error) {
291
409
  console.error('Error initializing client:', error);
@@ -293,8 +411,22 @@ export class XmppClient {
293
411
  }
294
412
  }
295
413
 
414
+ // Stored so detachEventListeners() can remove exactly the handlers we
415
+ // added (not xmpp.js's internal ones). Without this a re-login left the
416
+ // previous instance's `stanza` handler bound, double-dispatching every
417
+ // incoming stanza into the (new session's) redux store.
418
+ private onDisconnect?: () => void;
419
+ private onOnline?: () => void;
420
+ private onError?: (error: any) => void;
421
+ private onStanza?: (stanza: any) => void;
422
+
296
423
  attachEventListeners() {
297
- this.client.on('disconnect', () => {
424
+ // Idempotent: drop any previously-attached handlers first so a
425
+ // reconnect that re-runs initializeClient on a fresh underlying
426
+ // client doesn't stack duplicates.
427
+ this.detachEventListeners();
428
+
429
+ this.onDisconnect = () => {
298
430
  console.log('XMPP disconnected.');
299
431
  this.status = 'offline';
300
432
  try {
@@ -302,9 +434,20 @@ export class XmppClient {
302
434
  // that don't reference it; tree-shaken via dead-code elim.
303
435
  devPushLog('xmpp', 'disconnect');
304
436
  } catch {}
305
- });
437
+ // Self-heal at the socket level. The provider's reconnect effect
438
+ // keys on `client.status`, but that's a mutated class field React
439
+ // does NOT track — so a silent socket drop (e.g. JS paused by the
440
+ // debugger, iOS WebSocket suspend) never re-runs the effect and the
441
+ // client sits 'offline' forever (sends then queue and time out).
442
+ // Driving reconnect here makes recovery independent of React.
443
+ if (!this.suppressReconnect) {
444
+ try {
445
+ this.scheduleReconnect();
446
+ } catch {}
447
+ }
448
+ };
306
449
 
307
- this.client.on('online', () => {
450
+ this.onOnline = () => {
308
451
  console.log('XMPP online.', new Date());
309
452
  this.status = 'online';
310
453
  this.presencesReady = true;
@@ -316,10 +459,35 @@ export class XmppClient {
316
459
  this.username
317
460
  );
318
461
  } catch {}
319
- });
462
+ // Notify the provider that the session is live again. On a RECONNECT
463
+ // (new stream) the client is no longer joined to any MUC, so the
464
+ // provider must re-send room presences — otherwise messages get a
465
+ // local double-tick but never reach the room (bug #21). Harmless on
466
+ // the first connect (room list not loaded yet → joins nothing; the
467
+ // bootstrap's own allRoomPresences handles that pass).
468
+ try {
469
+ this.onOnlineCallback?.();
470
+ } catch (err) {
471
+ console.warn('onOnline callback failed', err);
472
+ }
473
+ // Drain any sends that were buffered while the stream was down. The
474
+ // presence stanzas from onOnlineCallback above were written to the
475
+ // socket synchronously (allRoomPresences → presenceInRoom send the
476
+ // <presence> before yielding), so flushing here puts the message
477
+ // stanzas AFTER the joins on the same stream — XMPP's in-order
478
+ // per-stream processing guarantees the MUC join lands first.
479
+ try {
480
+ this.flushPendingSends();
481
+ } catch (err) {
482
+ console.warn('flushPendingSends failed', err);
483
+ }
484
+ };
320
485
 
321
- this.client.on('error', (error: any) => {
486
+ this.onError = (error: any) => {
322
487
  console.error('XMPP client error:', error);
488
+ if (isNotAuthorizedError(error)) {
489
+ this.lastAuthError = 'not-authorized';
490
+ }
323
491
  try {
324
492
  devPushLog(
325
493
  'xmpp',
@@ -327,9 +495,9 @@ export class XmppClient {
327
495
  (error && error.message) || error
328
496
  );
329
497
  } catch {}
330
- });
498
+ };
331
499
 
332
- this.client.on('stanza', (stanza: any) => {
500
+ this.onStanza = (stanza: any) => {
333
501
  try {
334
502
  const tag = stanza?.name || 'stanza';
335
503
  const id = stanza?.attrs?.id || '';
@@ -342,7 +510,27 @@ export class XmppClient {
342
510
  );
343
511
  } catch {}
344
512
  handleStanza.bind(this, stanza, this)();
345
- });
513
+ };
514
+
515
+ this.client.on('disconnect', this.onDisconnect);
516
+ this.client.on('online', this.onOnline);
517
+ this.client.on('error', this.onError);
518
+ this.client.on('stanza', this.onStanza);
519
+ }
520
+
521
+ detachEventListeners() {
522
+ const c = this.client as any;
523
+ if (!c?.removeListener) {return;}
524
+ try {
525
+ if (this.onDisconnect) {c.removeListener('disconnect', this.onDisconnect);}
526
+ if (this.onOnline) {c.removeListener('online', this.onOnline);}
527
+ if (this.onError) {c.removeListener('error', this.onError);}
528
+ if (this.onStanza) {c.removeListener('stanza', this.onStanza);}
529
+ } catch {}
530
+ this.onDisconnect = undefined;
531
+ this.onOnline = undefined;
532
+ this.onError = undefined;
533
+ this.onStanza = undefined;
346
534
  }
347
535
 
348
536
  /**
@@ -374,36 +562,150 @@ export class XmppClient {
374
562
 
375
563
  scheduleReconnect() {
376
564
  if (this.suppressReconnect) {return;}
377
- if (this.reconnectAttempts >= this.maxReconnectAttempts) {
378
- console.error('Max reconnect attempts reached.');
379
- return;
380
- }
565
+ if (this.reconnecting || this.reconnectTimer) {return;}
381
566
  this.reconnectAttempts++;
382
- const delay =
383
- this.reconnectDelay * Math.pow(2, this.reconnectAttempts - 1);
567
+ const exp = Math.min(
568
+ this.reconnectAttempts - 1,
569
+ Math.max(0, this.maxReconnectAttempts)
570
+ );
571
+ const delay = Math.min(
572
+ this.maxReconnectDelay,
573
+ this.reconnectDelay * Math.pow(2, exp)
574
+ );
384
575
  console.log(`Reconnecting attempt ${this.reconnectAttempts} in ${delay}ms`);
385
- setTimeout(() => this.reconnect(), delay);
576
+ // No clearTimeout needed — the guard above guarantees no pending timer.
577
+ this.reconnectTimer = setTimeout(() => {
578
+ this.reconnectTimer = null;
579
+ // The connection may have recovered on its own (xmpp.js auto-
580
+ // reconnect) between scheduling and firing — don't tear down a
581
+ // healthy stream. forceReconnect() bypasses this by calling
582
+ // reconnect() directly.
583
+ if (this.status === 'online') {
584
+ this.reconnectAttempts = 0;
585
+ return;
586
+ }
587
+ this.reconnect();
588
+ }, delay);
589
+ }
590
+
591
+ /**
592
+ * Force an immediate reconnect, resetting the backoff counter. Called
593
+ * by the provider when NetInfo reports the network came back or the app
594
+ * returned to the foreground — we don't want to wait out the existing
595
+ * exponential delay (or a maxed-out attempt count) in those cases.
596
+ */
597
+ forceReconnect() {
598
+ if (this.suppressReconnect) {return;}
599
+ // Debounce bursts: NetInfo restore + AppState 'active' + the provider
600
+ // watchdog can fire within the same instant. reconnect() dedups
601
+ // CONCURRENT calls, but sequential ones (one finishes, the next
602
+ // arrives) would still each tear down + re-create the client. Ignore
603
+ // forces within 2s of the last one.
604
+ const now = Date.now();
605
+ if (now - this.lastReconnectAt < 2000) {return;}
606
+ this.lastReconnectAt = now;
607
+ if (this.reconnectTimer) {
608
+ clearTimeout(this.reconnectTimer);
609
+ this.reconnectTimer = null;
610
+ }
611
+ this.reconnectAttempts = 0;
612
+ this.reconnect();
386
613
  }
387
614
 
388
- reconnect() {
615
+ async reconnect() {
389
616
  if (this.suppressReconnect) {return;}
617
+ // Single-flight: ignore overlapping triggers (AppState + NetInfo +
618
+ // scheduled timer) so we never spawn two underlying clients and leak
619
+ // one with live listeners (duplicate stanza dispatch).
620
+ if (this.reconnecting) {
621
+ console.log('reconnect already in progress — skip');
622
+ return;
623
+ }
624
+ this.reconnecting = true;
390
625
  console.log('Attempting to reconnect xmpp client...');
391
- if (this.client) {
392
- this.client.stop().finally(() => {
393
- this.initializeClient();
394
- });
395
- } else {
626
+
627
+ try {
628
+ // If the last failure was a SASL `not-authorized` (typically a stale
629
+ // JWT-derived XMPP password after idle), try to fetch fresh creds
630
+ // before retrying. Without this, reconnect retries with the same
631
+ // stale password forever and the user has to kill the app.
632
+ if (this.lastAuthError === 'not-authorized' && this.credentialsProvider) {
633
+ try {
634
+ await this.refreshCredentialsOnce();
635
+ } catch (err) {
636
+ console.warn(
637
+ 'XMPP credential refresh failed; reconnecting with cached creds',
638
+ err
639
+ );
640
+ }
641
+ }
642
+
643
+ // Tear the OLD underlying client down FULLY before spinning up a new
644
+ // one: detach our handlers (so its `stanza` handler can't keep
645
+ // dispatching) and stop it. detachEventListeners() must run while
646
+ // `this.client` still points at the old client — i.e. before
647
+ // initializeClient() reassigns it.
648
+ const old = this.client;
649
+ if (old) {
650
+ this.detachEventListeners();
651
+ try {
652
+ await old.stop();
653
+ } catch {}
654
+ }
396
655
  this.initializeClient();
656
+ } finally {
657
+ this.reconnecting = false;
397
658
  }
398
659
  }
399
660
 
661
+ /**
662
+ * Single-flight wrapper around credentialsProvider. Multiple concurrent
663
+ * reconnect() calls (e.g. from the provider's reconnect effect firing
664
+ * twice quickly) share one in-flight request and update `this.password`
665
+ * exactly once.
666
+ */
667
+ private refreshCredentialsOnce(): Promise<void> {
668
+ if (this.credentialsRefreshInFlight) {return this.credentialsRefreshInFlight;}
669
+ if (!this.credentialsProvider) {return Promise.resolve();}
670
+ const provider = this.credentialsProvider;
671
+ this.credentialsRefreshInFlight = (async () => {
672
+ try {
673
+ const fresh = await provider();
674
+ if (fresh?.username && fresh?.password) {
675
+ // Apply whatever the provider returns and retry. We do NOT try to
676
+ // detect "auth expired" by comparing passwords — a stable
677
+ // xmppPassword (e.g. deterministic from the JWT/wallet) is the
678
+ // NORMAL case for a transient blip, and treating it as expiry
679
+ // falsely bricked recoverable sessions (sending stopped). If the
680
+ // creds really are stale we simply get not-authorized again and
681
+ // retry, which is the confirmed-good 26.5.7 behaviour.
682
+ this.updateCredentials(fresh.username, fresh.password);
683
+ this.lastAuthError = null;
684
+ }
685
+ } finally {
686
+ this.credentialsRefreshInFlight = null;
687
+ }
688
+ })();
689
+ return this.credentialsRefreshInFlight;
690
+ }
691
+
400
692
  async disconnect(options?: { suppressReconnect?: boolean }): Promise<void> {
401
693
  if (options?.suppressReconnect) {this.suppressReconnect = true;}
402
694
  return this.close();
403
695
  }
404
696
 
405
697
  async close() {
698
+ // Cancel any pending reconnect so a stale timer can't resurrect this
699
+ // instance after teardown/logout.
700
+ if (this.reconnectTimer) {
701
+ clearTimeout(this.reconnectTimer);
702
+ this.reconnectTimer = null;
703
+ }
406
704
  if (this.client) {
705
+ // Detach OUR handlers first so a late stanza during the close
706
+ // handshake can't re-enter handleStanza and dispatch into a store
707
+ // that's being wiped (or, on re-login, the new session's store).
708
+ this.detachEventListeners();
407
709
  try {
408
710
  await this.client.stop();
409
711
  console.log('XMPP client connection closed.');
@@ -412,6 +714,11 @@ export class XmppClient {
412
714
  }
413
715
  }
414
716
  this.status = 'offline';
717
+ this.presencesReady = false;
718
+ // Permanent teardown (logout / unmount) — drop buffered sends so they
719
+ // don't replay into the next session. Reconnect uses old.stop(), NOT
720
+ // close(), so the queue still survives a transient drop+reconnect.
721
+ clearOutboundSends();
415
722
  }
416
723
 
417
724
  getRoomsStanza = async () => {
@@ -429,11 +736,11 @@ export class XmppClient {
429
736
  }
430
737
 
431
738
  leaveTheRoomStanza = (roomJID: string) => {
432
- leaveTheRoom(roomJID, this.client);
739
+ leaveTheRoom(normalizeRoomJid(roomJID, this.conference), this.client);
433
740
  };
434
741
 
435
742
  presenceInRoomStanza = (roomJID: string) => {
436
- presenceInRoom(this.client, roomJID);
743
+ presenceInRoom(this.client, normalizeRoomJid(roomJID, this.conference));
437
744
  };
438
745
 
439
746
  getHistoryStanza = async (
@@ -456,7 +763,7 @@ export class XmppClient {
456
763
  };
457
764
 
458
765
  getLastMessageArchiveStanza(roomJID: string) {
459
- getLastMessage(this.client, roomJID);
766
+ getLastMessage(this.client, normalizeRoomJid(roomJID, this.conference));
460
767
  }
461
768
 
462
769
  setRoomImageStanza = (
@@ -469,13 +776,28 @@ export class XmppClient {
469
776
  };
470
777
 
471
778
  getRoomInfoStanza = (roomJID: string) => {
472
- getRoomInfo(roomJID, this.client);
779
+ getRoomInfo(normalizeRoomJid(roomJID, this.conference), this.client);
473
780
  };
474
781
 
475
782
  getRoomMembersStanza = (roomJID: string) => {
476
- getRoomMembers(roomJID, this.client);
783
+ getRoomMembers(normalizeRoomJid(roomJID, this.conference), this.client);
477
784
  };
478
785
 
786
+ /**
787
+ * True when the underlying stream is live enough to actually put a
788
+ * stanza on the wire. Used to gate sends: when false we buffer instead
789
+ * of firing a fire-and-forget stanza into a dead/half-open socket
790
+ * (which is silently dropped and only caught by the 30s watchdog).
791
+ */
792
+ private isStreamReady(): boolean {
793
+ return !!this.client && this.status === 'online' && !this.reconnecting;
794
+ }
795
+
796
+ /** Replay buffered sends against this (now-online) client, in order. */
797
+ flushPendingSends() {
798
+ flushOutboundSends(this, Date.now());
799
+ }
800
+
479
801
  //messages
480
802
  sendMessage = (
481
803
  roomJID: string,
@@ -489,7 +811,32 @@ export class XmppClient {
489
811
  showInChannel?: boolean,
490
812
  mainMessage?: string,
491
813
  customId?: string
492
- ) => {
814
+ ): boolean => {
815
+ // Stream down (start race / reconnect window): buffer and replay on
816
+ // the next 'online' instead of losing the stanza. Returns false so
817
+ // callers know the send was deferred, not delivered.
818
+ if (!this.isStreamReady()) {
819
+ enqueueOutboundSend({
820
+ optimisticId: customId || `send-text-message-${Date.now()}`,
821
+ roomJID,
822
+ enqueuedAt: Date.now(),
823
+ send: (c) =>
824
+ c.sendMessage(
825
+ roomJID,
826
+ firstName,
827
+ lastName,
828
+ photo,
829
+ walletAddress,
830
+ userMessage,
831
+ notDisplayedValue,
832
+ isReply,
833
+ showInChannel,
834
+ mainMessage,
835
+ customId
836
+ ),
837
+ });
838
+ return false;
839
+ }
493
840
  sendTextMessage(
494
841
  this.client,
495
842
  roomJID,
@@ -505,6 +852,7 @@ export class XmppClient {
505
852
  this.devServer || DEFAULT_DEV_SERVER,
506
853
  customId
507
854
  );
855
+ return true;
508
856
  };
509
857
 
510
858
  deleteMessageStanza(room: string, msgId: string) {
@@ -550,7 +898,7 @@ export class XmppClient {
550
898
  // src/networking/xmpp/flushLastViewedToPrivateStore.ts for details.
551
899
  async flushLastViewedToPrivateStoreStanza(
552
900
  rooms: Record<string, any> | null | undefined,
553
- opts: { activeRoomJID?: string | null; onlyIfNoUnread?: boolean } = {}
901
+ opts: { visibleRoomJID?: string | null; onlyIfNoUnread?: boolean } = {}
554
902
  ) {
555
903
  if (this.disableLastRead) {return false;}
556
904
  try {
@@ -561,6 +909,19 @@ export class XmppClient {
561
909
  }
562
910
 
563
911
  sendMediaMessageStanza(roomJID: string, data: any, customId?: string) {
912
+ // Media: the file is already uploaded by the time we get here; only the
913
+ // stanza needs the stream. Buffer + replay on reconnect if it's down so
914
+ // the upload isn't wasted (otherwise the stanza is lost and the media
915
+ // bubble sits pending until the 30s watchdog fails it).
916
+ if (!this.isStreamReady()) {
917
+ enqueueOutboundSend({
918
+ optimisticId: customId || `send-media-message-${Date.now()}`,
919
+ roomJID,
920
+ enqueuedAt: Date.now(),
921
+ send: (c) => c.sendMediaMessageStanza(roomJID, data, customId),
922
+ });
923
+ return undefined;
924
+ }
564
925
  return sendMediaMessage(
565
926
  this.client,
566
927
  roomJID,
@@ -611,9 +972,15 @@ export class XmppClient {
611
972
  isReply?: boolean,
612
973
  showInChannel?: boolean,
613
974
  mainMessage?: string,
614
- _langSource?: string
975
+ _langSource?: string,
976
+ customId?: string
615
977
  ) {
616
978
  // No translate tag support yet — fall back to a regular text send.
979
+ // Forward the caller's customId so the optimistic message (heap/redux)
980
+ // reconciles with the server echo. Without it, translated sends went
981
+ // out with no client id → the echo carried a server-assigned id and
982
+ // surfaced as a stuck-pending message PLUS a duplicate (affected both
983
+ // the live translated-send path and resend).
617
984
  this.sendMessage(
618
985
  roomJID,
619
986
  firstName,
@@ -624,7 +991,8 @@ export class XmppClient {
624
991
  notDisplayedValue,
625
992
  isReply,
626
993
  showInChannel,
627
- mainMessage
994
+ mainMessage,
995
+ customId
628
996
  );
629
997
  }
630
998
  }
@@ -65,8 +65,15 @@ const TRIGGER_ACTIONS = new Set([
65
65
  'roomMessages/editRoomMessage',
66
66
  'roomMessages/setLastViewedTimestamp',
67
67
  'roomMessages/setCurrentRoom',
68
+ 'roomMessages/setVisibleRoom',
69
+ 'roomMessages/clearVisibleRoom',
68
70
  'roomMessages/addRoom',
69
71
  'roomMessages/updateRoom',
72
+ // The history preload scheduler merges fetched pages via this action on
73
+ // re-entry; recompute so unread reflects messages that arrived while the
74
+ // app was away (the per-room fingerprint cache below skips no-op batches,
75
+ // e.g. the 'loading' marker dispatch that carries no messages).
76
+ 'roomMessages/applyRoomsPreloadBatch',
70
77
  ]);
71
78
 
72
79
  export const unreadMiddleware: Middleware =
@@ -95,7 +102,7 @@ export const unreadMiddleware: Middleware =
95
102
 
96
103
  const state = storeAPI.getState();
97
104
  const rooms = state.rooms.rooms;
98
- const activeChatJID = state.rooms.activeRoomJID;
105
+ const visibleRoomJID = state.rooms.visibleRoomJID;
99
106
  const selfUser = state.chatSettingStore?.user;
100
107
  const selfXmpp = selfUser?.xmppUsername || '';
101
108
  const selfWallet = selfUser?.walletAddress || '';
@@ -104,13 +111,24 @@ export const unreadMiddleware: Middleware =
104
111
  Object.keys(rooms).forEach((jid) => {
105
112
  const room = rooms[jid];
106
113
  if (!room) {return;}
107
- // Skip rooms the user is currently viewing — `setLastViewedTimestamp(0)`
108
- // clears their unread directly. Skip rooms with no reference point
109
- // (covers undefined / null / 0) — after logout→login, hydrated rooms
114
+ // Skip rooms the user is currently viewing — visibility clears the
115
+ // badge directly. Skip rooms with no reference point (covers
116
+ // undefined / null / 0) — after logout→login, hydrated rooms
110
117
  // come back without lastViewedTimestamp, and treating that as 0
111
118
  // made every history message satisfy `date > 0` and incorrectly
112
119
  // bumped unread for already-seen content.
113
- if (!room.lastViewedTimestamp || jid === activeChatJID) {return;}
120
+ if (jid === visibleRoomJID) {
121
+ if (room.unreadMessages !== 0) {
122
+ storeAPI.dispatch(
123
+ updateRoom({
124
+ jid,
125
+ updates: { unreadMessages: 0 },
126
+ })
127
+ );
128
+ }
129
+ return;
130
+ }
131
+ if (!(room.lastViewedTimestamp > 0)) {return;}
114
132
 
115
133
  // Fingerprint = "what would affect the answer". If neither the
116
134
  // message count nor the lastViewedTimestamp changed since last