@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
@@ -1,6 +1,6 @@
1
1
  // RN port of the web MessageNotificationProvider. Renders Telegram-style
2
2
  // in-app toasts when a new message arrives in a room that is NOT the
3
- // currently active room. Backed by the global messageNotificationManager,
3
+ // currently visible room. Backed by the global messageNotificationManager,
4
4
  // which means non-React code (stanza handlers) can push notifications.
5
5
  import React, {
6
6
  createContext,
@@ -63,8 +63,8 @@ export const MessageNotificationProvider: React.FC<ProviderProps> = ({
63
63
  const contextConfig = useSelector(
64
64
  (state: RootState) => state.chatSettingStore?.config
65
65
  );
66
- const activeRoomJID = useSelector(
67
- (state: RootState) => state.rooms.activeRoomJID
66
+ const visibleRoomJID = useSelector(
67
+ (state: RootState) => state.rooms.visibleRoomJID
68
68
  );
69
69
 
70
70
  const config = propConfig || contextConfig;
@@ -104,9 +104,9 @@ export const MessageNotificationProvider: React.FC<ProviderProps> = ({
104
104
 
105
105
  // Clear toasts when their room becomes active.
106
106
  useEffect(() => {
107
- if (!activeRoomJID) {return;}
108
- setToasts((prev) => prev.filter((t) => t.roomJID !== activeRoomJID));
109
- }, [activeRoomJID]);
107
+ if (!visibleRoomJID) {return;}
108
+ setToasts((prev) => prev.filter((t) => t.roomJID !== visibleRoomJID));
109
+ }, [visibleRoomJID]);
110
110
 
111
111
  const dismiss = useCallback((id: string) => {
112
112
  setToasts((prev) => prev.filter((t) => t.id !== id));
@@ -142,7 +142,7 @@ export const MessageNotificationProvider: React.FC<ProviderProps> = ({
142
142
  ) => {
143
143
  if (!isEnabled) {return;}
144
144
  // Don't toast for the currently active room.
145
- if (activeRoomJID && activeRoomJID === roomJID) {return;}
145
+ if (visibleRoomJID && visibleRoomJID === roomJID) {return;}
146
146
  const id = `msg-notification-${message.id}-${Date.now()}`;
147
147
  const item: ToastItem = {
148
148
  id,
@@ -159,7 +159,7 @@ export const MessageNotificationProvider: React.FC<ProviderProps> = ({
159
159
  : next;
160
160
  });
161
161
  },
162
- [isEnabled, activeRoomJID, maxNotifications]
162
+ [isEnabled, visibleRoomJID, maxNotifications]
163
163
  );
164
164
 
165
165
  // Register with the global manager.
@@ -4,11 +4,16 @@ import React, {
4
4
  useCallback,
5
5
  useContext,
6
6
  useEffect,
7
+ useMemo,
7
8
  useRef,
8
9
  useState,
9
10
  } from 'react';
10
11
  import { AppState, DeviceEventEmitter } from 'react-native';
11
- import XmppClient from '../networking/xmppClient';
12
+ import NetInfo from '@react-native-community/netinfo';
13
+ import XmppClient, {
14
+ XmppCredentialsProvider,
15
+ } from '../networking/xmppClient';
16
+ import { refreshTokens } from '../roomStore/chatSettingsSlice';
12
17
  import {
13
18
  IConfig,
14
19
  ProviderBootstrapStatus,
@@ -17,6 +22,7 @@ import {
17
22
  } from '../types/types';
18
23
  import {
19
24
  buildXmppClientKey,
25
+ getGlobalXmppClient,
20
26
  getReusableXmppClientByKey,
21
27
  isXmppClientReusable,
22
28
  setGlobalXmppClient,
@@ -24,6 +30,7 @@ import {
24
30
  } from '../utils/clientRegistry';
25
31
  import {
26
32
  applyResolvedUserToStore,
33
+ refreshUserCredentialsForXmpp,
27
34
  resolveInitBeforeLoadUser,
28
35
  } from '../helpers/resolveInitBeforeLoadUser';
29
36
  import { ensureScopedChatCache } from '../helpers/ensureScopedChatCache';
@@ -31,7 +38,13 @@ import { getRooms as prefetchRoomsViaRest } from '../networking/api-requests/roo
31
38
  import { allRoomPresences } from '../networking/xmpp/allRoomPresences.xmpp';
32
39
  import { store } from '../roomStore';
33
40
  import { logout, setStoreClient } from '../roomStore/chatSettingsSlice';
34
- import { setLogoutState } from '../roomStore/roomsSlice';
41
+ import {
42
+ setLogoutState,
43
+ setVisibleRoom,
44
+ clearVisibleRoom,
45
+ setLastViewedTimestamp,
46
+ deleteRoomMessage,
47
+ } from '../roomStore/roomsSlice';
35
48
  import { runHistoryPreloadScheduler } from '../helpers/historyPreloadScheduler';
36
49
  import { asyncLocalStorage } from '../hooks/useLocalStorage';
37
50
  import { localStorageConstants } from '../helpers/constants/LOCAL_STORAGE';
@@ -59,15 +72,18 @@ const XmppContext = createContext<XmppContextType | null>(null);
59
72
  interface XmppProviderProps {
60
73
  children: ReactNode;
61
74
  config?: IConfig;
75
+ // Consumer-supplied "is the chat surface shown" signal for hosts that
76
+ // keep <Chat> mounted while hidden (tab/route navigators). See the
77
+ // visibility effect below. Omit when you unmount <Chat> on hide.
78
+ isVisible?: boolean;
62
79
  }
63
80
 
64
81
  const LOGOUT_EVENT = 'ethora-xmpp-logout';
65
82
 
66
- export const XmppProvider: React.FC<XmppProviderProps> = ({ children, config }) => {
83
+ export const XmppProvider: React.FC<XmppProviderProps> = ({ children, config, isVisible }) => {
67
84
  const [client, setClient] = useState<XmppClient | null>(null);
68
85
  const [providerBootstrapStatus, setProviderBootstrapStatus] =
69
86
  useState<ProviderBootstrapStatus>('idle');
70
- const [reconnectAttempts, setReconnectAttempts] = useState(0);
71
87
 
72
88
  // Track which "init mode" this provider runs in. When config.initBeforeLoad
73
89
  // is true, the provider owns bootstrap and ChatWrapper just waits.
@@ -77,6 +93,79 @@ export const XmppProvider: React.FC<XmppProviderProps> = ({ children, config })
77
93
  const completedBootstrapKeyRef = useRef<string>('');
78
94
  const inflightBootstrapKeyRef = useRef<string>('');
79
95
 
96
+ // Callback XmppClient uses to fetch fresh credentials after a SASL
97
+ // `not-authorized` (typically JWT expiry during idle, bug #17).
98
+ // Always installed — the underlying `refreshUserCredentialsForXmpp`
99
+ // picks the right path based on config:
100
+ // - `jwtLogin` → re-exchange JWT via `/users/client`
101
+ // - `userLogin` / `customLogin` / store / persisted → `/users/my`
102
+ // with REST refresh chain (handles 401 → refresh → retry)
103
+ // Returns last-known cached creds if nothing usable is available so
104
+ // the XmppClient at least doesn't crash on `undefined.password`.
105
+ const credentialsProvider = useMemo<XmppCredentialsProvider>(() => {
106
+ return async () => {
107
+ // 1. Consumer-supplied REST refresh (when wired). This runs
108
+ // before the SDK's own refresh chain so non-Ethora backends
109
+ // can keep auth state in sync with their own token endpoint.
110
+ const customRefresh = config?.refreshTokens?.refreshFunction;
111
+ if (customRefresh) {
112
+ try {
113
+ const result = await customRefresh();
114
+ if (result?.accessToken) {
115
+ store.dispatch(
116
+ refreshTokens({
117
+ token: result.accessToken,
118
+ refreshToken: result.refreshToken || '',
119
+ })
120
+ );
121
+ }
122
+ } catch (err) {
123
+ devPushLog('warn', 'XMPP creds refresh: customRefresh failed', err);
124
+ }
125
+ }
126
+
127
+ // 2. Re-mint XMPP creds via the right priority chain for the
128
+ // current auth mode. This call ALWAYS hydrates (unlike
129
+ // `resolveInitBeforeLoadUser` which short-circuits when a
130
+ // cached user already has xmppCredentials).
131
+ const fresh = await refreshUserCredentialsForXmpp(config).catch(
132
+ (err) => {
133
+ devPushLog('warn', 'XMPP creds refresh: full chain failed', err);
134
+ return null;
135
+ }
136
+ );
137
+ if (fresh) {
138
+ applyResolvedUserToStore(fresh);
139
+ return {
140
+ username:
141
+ fresh.xmppUsername ||
142
+ fresh.defaultWallet?.walletAddress ||
143
+ '',
144
+ password: fresh.xmppPassword || '',
145
+ };
146
+ }
147
+
148
+ // 3. Last-resort: return the cached creds so reconnect() can
149
+ // still attempt a connection. If the original failure was
150
+ // a stale JWT this will fail again and the user has to
151
+ // re-mount the chat — but at least we don't break the
152
+ // transient-network-blip case where the cached creds are
153
+ // still valid.
154
+ const u = store.getState().chatSettingStore.user;
155
+ return {
156
+ username: u?.xmppUsername || u?.defaultWallet?.walletAddress || '',
157
+ password: u?.xmppPassword || '',
158
+ };
159
+ };
160
+ }, [
161
+ config?.jwtLogin?.enabled,
162
+ config?.jwtLogin?.token,
163
+ config?.refreshTokens?.refreshFunction,
164
+ config?.userLogin?.enabled,
165
+ config?.customLogin?.enabled,
166
+ config?.initBeforeLoadAuth?.myEndpoint,
167
+ ]);
168
+
80
169
  const initializeClient = useCallback(
81
170
  async (
82
171
  username: string,
@@ -103,7 +192,17 @@ export const XmppProvider: React.FC<XmppProviderProps> = ({ children, config })
103
192
  // 3. Otherwise create a new client under the init lock to dedup
104
193
  // concurrent calls for the same key.
105
194
  const created = await withXmppClientInitLock(key, async () => {
195
+ const prevGlobal = getGlobalXmppClient();
196
+ if (prevGlobal) {
197
+ try { await prevGlobal.close(); } catch {}
198
+ }
106
199
  const newClient = new XmppClient(username, password, settings);
200
+ // Wire up the credentialsProvider so SASL `not-authorized`
201
+ // after idle triggers a refresh chain (jwtLogin →
202
+ // /users/client; userLogin/customLogin/store → /users/my +
203
+ // REST refresh) instead of looping forever with stale creds.
204
+ // Bug #17 — full coverage in 26.5.6.
205
+ newClient.setCredentialsProvider(credentialsProvider);
107
206
  setGlobalXmppClient(newClient, key);
108
207
  await newClient.waitForOnline().catch((err) => {
109
208
  console.error('Error waiting for xmpp online:', err);
@@ -112,12 +211,37 @@ export const XmppProvider: React.FC<XmppProviderProps> = ({ children, config })
112
211
  return newClient;
113
212
  });
114
213
 
214
+ // Reused clients (steps 1+2 above) also need the provider — config
215
+ // may have changed since the singleton was created.
216
+ created.setCredentialsProvider(credentialsProvider);
217
+
218
+ // Re-join MUC rooms on every 'online'. After a reconnect the new
219
+ // XMPP stream isn't a member of any room, so a sent message gets a
220
+ // local double-tick but never reaches the room (bug #21). Re-sending
221
+ // presence on reconnect fixes delivery. On the very first connect
222
+ // the room list isn't loaded yet so this joins nothing — the
223
+ // bootstrap's own allRoomPresences handles that pass.
224
+ created.setOnOnline(() => {
225
+ const underlying = (created as any).client;
226
+ if (!underlying) {return;}
227
+ allRoomPresences(underlying).catch((e) =>
228
+ devPushLog('warn', 'reconnect: allRoomPresences re-join failed', e)
229
+ );
230
+ // Also refresh the private store so unread / lastViewed markers
231
+ // are accurate after a long reconnect — the MUC re-join above only
232
+ // restores delivery, not unread state. Idempotent on first connect.
233
+ created
234
+ .getChatsPrivateStoreRequestStanza()
235
+ .catch((e: unknown) =>
236
+ devPushLog('warn', 'reconnect: privateStore refresh failed', e)
237
+ );
238
+ });
239
+
115
240
  setClient(created);
116
241
  lastCredsRef.current = { username, password, settings };
117
- setReconnectAttempts(0);
118
242
  return created;
119
243
  },
120
- [client, config?.xmppSettings]
244
+ [client, config?.xmppSettings, credentialsProvider]
121
245
  );
122
246
 
123
247
  // -----------------------------------------------------------
@@ -131,6 +255,30 @@ export const XmppProvider: React.FC<XmppProviderProps> = ({ children, config })
131
255
 
132
256
  const abortController = new AbortController();
133
257
  let cancelled = false;
258
+ // Auto-retry a TRANSIENT bootstrap failure a few times with backoff
259
+ // before surfacing the error modal — a flaky network at launch should
260
+ // self-heal, not dead-end the user on a Retry button. (Bad-creds
261
+ // failures resolve to null below and fail fast — they never reach the
262
+ // catch, so they aren't retried.)
263
+ const MAX_BOOTSTRAP_ATTEMPTS = 3;
264
+ let bootstrapAttempt = 0;
265
+ // Overall time budget: if we never reach 'ready' (a step hangs), abort
266
+ // the in-flight work and fail so the loader can't spin forever.
267
+ const BOOTSTRAP_BUDGET_MS = 45000;
268
+ let reachedReady = false;
269
+ let budgetTimer: ReturnType<typeof setTimeout> | null = setTimeout(() => {
270
+ if (cancelled || reachedReady) {return;}
271
+ devPushLog('warn', 'initBeforeLoad: time budget exceeded → failed');
272
+ abortController.abort();
273
+ inflightBootstrapKeyRef.current = '';
274
+ setProviderBootstrapStatus('failed');
275
+ }, BOOTSTRAP_BUDGET_MS);
276
+ const clearBudget = () => {
277
+ if (budgetTimer) {
278
+ clearTimeout(budgetTimer);
279
+ budgetTimer = null;
280
+ }
281
+ };
134
282
 
135
283
  const run = async () => {
136
284
  // Stable bootstrap key — re-run only when these change.
@@ -244,6 +392,8 @@ export const XmppProvider: React.FC<XmppProviderProps> = ({ children, config })
244
392
  store.dispatch(setStoreClient(c));
245
393
  completedBootstrapKeyRef.current = key;
246
394
  inflightBootstrapKeyRef.current = '';
395
+ reachedReady = true;
396
+ clearBudget();
247
397
  setProviderBootstrapStatus('ready');
248
398
  devPushLog('rn', 'initBeforeLoad: READY');
249
399
 
@@ -264,13 +414,34 @@ export const XmppProvider: React.FC<XmppProviderProps> = ({ children, config })
264
414
  roomLimit: qos?.preloadTopKRooms,
265
415
  }).catch((err) => console.warn('History preload scheduler failed', err));
266
416
  } catch (error: any) {
267
- if (cancelled) {return;}
268
- devPushLog('error', 'initBeforeLoad: bootstrap threw', {
417
+ if (cancelled || abortController.signal.aborted) {return;}
418
+ bootstrapAttempt += 1;
419
+ // Throws here are transient (network / XMPP connect). A bad-creds
420
+ // failure resolves to null above and fails fast without throwing,
421
+ // so it never reaches this catch. Retry with backoff before the
422
+ // modal; release the in-flight key so the recursive run() isn't
423
+ // short-circuited by its own guard.
424
+ if (bootstrapAttempt < MAX_BOOTSTRAP_ATTEMPTS) {
425
+ const backoff = Math.min(8000, 1000 * 2 ** (bootstrapAttempt - 1));
426
+ devPushLog(
427
+ 'rn',
428
+ `initBeforeLoad: attempt ${bootstrapAttempt} failed (${error?.message}); retrying in ${backoff}ms`
429
+ );
430
+ inflightBootstrapKeyRef.current = '';
431
+ setTimeout(() => {
432
+ if (!cancelled && !abortController.signal.aborted) {
433
+ run();
434
+ }
435
+ }, backoff);
436
+ return;
437
+ }
438
+ devPushLog('error', 'initBeforeLoad: bootstrap threw (giving up)', {
269
439
  message: error?.message,
270
440
  status: error?.response?.status,
271
441
  data: error?.response?.data,
272
442
  });
273
443
  inflightBootstrapKeyRef.current = '';
444
+ clearBudget();
274
445
  setProviderBootstrapStatus('failed');
275
446
  }
276
447
  };
@@ -279,6 +450,7 @@ export const XmppProvider: React.FC<XmppProviderProps> = ({ children, config })
279
450
 
280
451
  return () => {
281
452
  cancelled = true;
453
+ clearBudget();
282
454
  abortController.abort();
283
455
  // Re-running this effect (deps changed or unmount) must release the
284
456
  // in-flight bootstrap key so the next render isn't permanently
@@ -308,29 +480,30 @@ export const XmppProvider: React.FC<XmppProviderProps> = ({ children, config })
308
480
  ]);
309
481
 
310
482
  // -----------------------------------------------------------
311
- // Reconnect / reinit on disconnect
483
+ // Reconnect watchdog. The client now owns reconnection (onDisconnect →
484
+ // scheduleReconnect with uncapped, clamped backoff; a connect 'error'
485
+ // reschedules too). This is the single provider-side safety net for the
486
+ // states the event-driven triggers miss: a stuck 'connecting', a connect
487
+ // 'error', or a server-side outage where NetInfo never reported the
488
+ // device offline. While foregrounded, if we're not provably online and
489
+ // not suppressed, force a reconnect (debounced inside forceReconnect).
490
+ //
491
+ // Replaces the old status-keyed reconnect effect, which keyed on
492
+ // `client.status` — a mutated class field React does NOT track — so on a
493
+ // silent socket drop it frequently never re-ran (and its 3-attempts-then-
494
+ // full-reinit path was effectively dead).
312
495
  // -----------------------------------------------------------
313
496
  useEffect(() => {
314
497
  if (!client) {return;}
315
- if (client.status !== 'offline') {return;}
316
-
317
- if (reconnectAttempts < 3) {
318
- console.log(`xmpp reconnect attempt ${reconnectAttempts + 1}`);
319
- client.scheduleReconnect();
320
- setReconnectAttempts((prev) => prev + 1);
321
- return;
322
- }
323
-
324
- // After 3 failed reconnects, drop the client and re-init from scratch.
325
- if (lastCredsRef.current) {
326
- const { username, password, settings } = lastCredsRef.current;
327
- setGlobalXmppClient(null);
328
- setClient(null);
329
- initializeClient(username, password, settings).catch((err) =>
330
- console.error('Full reinit failed:', err)
331
- );
332
- }
333
- }, [client, client?.status, reconnectAttempts, initializeClient]);
498
+ const id = setInterval(() => {
499
+ if (AppState.currentState !== 'active') {return;}
500
+ if (client.status !== 'online' && !client.suppressReconnect) {
501
+ devPushLog('rn', `watchdog: client ${client.status} → forceReconnect`);
502
+ client.forceReconnect();
503
+ }
504
+ }, 30000);
505
+ return () => clearInterval(id);
506
+ }, [client]);
334
507
 
335
508
  // -----------------------------------------------------------
336
509
  // AppState → background: flush lastViewedTimestamp into the
@@ -345,22 +518,143 @@ export const XmppProvider: React.FC<XmppProviderProps> = ({ children, config })
345
518
  // entry, or when this build has `disableLastRead`.
346
519
  // -----------------------------------------------------------
347
520
  useEffect(() => {
521
+ // Remembers the room that was visible when we backgrounded so it can
522
+ // be re-marked visible on return. Lives in the effect closure (one per
523
+ // client) — persists across the listener's many invocations.
524
+ let visibleBeforeBackground: string | null = null;
348
525
  const sub = AppState.addEventListener('change', (next) => {
349
- if (next === 'active') {return;}
350
526
  const c = client;
527
+ if (next === 'active') {
528
+ // Returned to foreground. If the socket died while backgrounded
529
+ // (iOS suspends the WebSocket; long background kills it), the
530
+ // status may still read 'online' until xmpp.js notices the dead
531
+ // TCP — which can take a full timeout. Force an immediate
532
+ // reconnect when we're not provably online so cached UI gets
533
+ // live again fast instead of "100 years".
534
+ // Only force a reconnect from a genuinely dead state. Don't
535
+ // interrupt an in-progress 'connecting' (iOS delivers spurious
536
+ // inactive→active flaps for control-center / notifications /
537
+ // biometric prompts that would otherwise restart a healthy
538
+ // connect).
539
+ if (c && (c.status === 'offline' || c.status === 'error')) {
540
+ devPushLog('rn', `AppState active: client ${c.status} → forceReconnect`);
541
+ c.forceReconnect();
542
+ }
543
+ // Re-mark the previously-open room visible so its unread clears
544
+ // now that the user is looking again. We restore ONLY what was
545
+ // visible at background time, so a chat the consumer had already
546
+ // blurred (e.g. on a non-chat tab/route) correctly stays cleared.
547
+ if (visibleBeforeBackground) {
548
+ store.dispatch(setVisibleRoom({ roomJID: visibleBeforeBackground }));
549
+ visibleBeforeBackground = null;
550
+ }
551
+ return;
552
+ }
553
+ // Going to background/inactive (sleep / app switcher / tray):
351
554
  if (!c) {return;}
352
555
  const state = store.getState();
353
556
  const rooms = state.rooms?.rooms;
354
- const activeRoomJID = state.rooms?.activeRoomJID || null;
557
+ const visibleRoomJID = state.rooms?.visibleRoomJID || null;
558
+ // Mark the open room "not visible" while backgrounded so messages
559
+ // that arrive (or MAM-replay on reconnect) count as unread instead
560
+ // of being silently treated as read — the "mounted == visible ==
561
+ // read" gap. Stamp lastViewed=now as the read baseline, clear
562
+ // visibility, then flush that marker to the server private store.
563
+ // Restored on the next 'active' transition above. Handled here in
564
+ // the provider so consumers using the component as a package get
565
+ // correct unread without reaching into the chat store themselves.
566
+ visibleBeforeBackground = visibleRoomJID;
567
+ if (visibleRoomJID) {
568
+ store.dispatch(
569
+ setLastViewedTimestamp({ chatJID: visibleRoomJID, timestamp: Date.now() })
570
+ );
571
+ // Drop the "New messages" divider for the room we're leaving so it
572
+ // doesn't linger when the user comes back (mirrors ChatRoom's
573
+ // unmount cleanup, which never runs while the pane stays mounted).
574
+ store.dispatch(
575
+ deleteRoomMessage({ roomJID: visibleRoomJID, messageId: 'delimiter-new' })
576
+ );
577
+ store.dispatch(clearVisibleRoom());
578
+ }
355
579
  // Fire-and-forget — we're going to the background and don't
356
580
  // care about the resolution path.
357
- c.flushLastViewedToPrivateStoreStanza(rooms, { activeRoomJID }).catch(
581
+ c.flushLastViewedToPrivateStoreStanza(rooms, { visibleRoomJID }).catch(
358
582
  () => {}
359
583
  );
360
584
  });
361
585
  return () => sub.remove();
362
586
  }, [client]);
363
587
 
588
+ // -----------------------------------------------------------
589
+ // Consumer visibility signal (`isVisible` prop). For hosts that keep
590
+ // <Chat> MOUNTED while hidden (tab/route navigators — so the XMPP
591
+ // connection survives tab switches), ChatRoom's mount/unmount can't tell
592
+ // "shown" from "hidden", so the unread middleware would keep the open
593
+ // room's badge at 0 and messages arriving while you're elsewhere look
594
+ // already-read. When the host passes `isVisible`, mirror the AppState
595
+ // background/foreground handling: hidden → stamp lastViewed=now + clear
596
+ // room visibility (so later messages count) + flush; shown → restore
597
+ // visibility for the open room. Doing it here means consumers never
598
+ // touch the chat store themselves. (Undefined = host unmounts on hide,
599
+ // nothing to do.)
600
+ // -----------------------------------------------------------
601
+ useEffect(() => {
602
+ if (typeof isVisible !== 'boolean') {return;}
603
+ const state = store.getState();
604
+ const rooms = state.rooms?.rooms;
605
+ const activeRoomJID = state.rooms?.activeRoomJID || null;
606
+ if (isVisible) {
607
+ if (activeRoomJID) {
608
+ store.dispatch(setVisibleRoom({ roomJID: activeRoomJID }));
609
+ }
610
+ } else {
611
+ if (activeRoomJID) {
612
+ store.dispatch(
613
+ setLastViewedTimestamp({ chatJID: activeRoomJID, timestamp: Date.now() })
614
+ );
615
+ // Drop the "New messages" divider so it's gone when the user
616
+ // returns to the chat (mirrors ChatRoom's unmount cleanup).
617
+ store.dispatch(
618
+ deleteRoomMessage({ roomJID: activeRoomJID, messageId: 'delimiter-new' })
619
+ );
620
+ }
621
+ store.dispatch(clearVisibleRoom());
622
+ if (client?.flushLastViewedToPrivateStoreStanza) {
623
+ client
624
+ .flushLastViewedToPrivateStoreStanza(rooms, { visibleRoomJID: activeRoomJID })
625
+ .catch(() => {});
626
+ }
627
+ }
628
+ }, [isVisible, client]);
629
+
630
+ // -----------------------------------------------------------
631
+ // NetInfo → proactive reconnect on network restore. Without this the
632
+ // only reconnect trigger is xmpp.js noticing the socket is dead, which
633
+ // on a Wi-Fi/cellular drop can hang on the TCP timeout for tens of
634
+ // seconds. NetInfo tells us the instant connectivity returns so we can
635
+ // force a reconnect immediately (and reset the backoff budget).
636
+ // -----------------------------------------------------------
637
+ useEffect(() => {
638
+ if (!client) {return;}
639
+ const unsub = NetInfo.addEventListener((state) => {
640
+ // Guard against undefined/partial state (jest mock, early emit).
641
+ const reachable =
642
+ !!state?.isConnected && state?.isInternetReachable !== false;
643
+ if (!reachable) {return;}
644
+ // Reachable. If we're in a dead state, kick a reconnect — do NOT
645
+ // require a prior offline→online transition (a missed/coalesced
646
+ // offline event otherwise left us stuck offline forever). Skip
647
+ // 'connecting' so we don't interrupt an in-progress connect; the
648
+ // watchdog covers a stuck 'connecting'. forceReconnect() debounces
649
+ // bursts and resets the backoff itself.
650
+ if (client.status === 'offline' || client.status === 'error') {
651
+ devPushLog('rn', `NetInfo: reachable & client ${client.status} → forceReconnect`);
652
+ client.forceReconnect();
653
+ }
654
+ });
655
+ return () => unsub();
656
+ }, [client]);
657
+
364
658
  // -----------------------------------------------------------
365
659
  // Retry-bootstrap listener — fired by ChatWrapper's error modal so
366
660
  // a transient failure (bad endpoint, server hiccup, etc.) can be
@@ -391,7 +685,6 @@ export const XmppProvider: React.FC<XmppProviderProps> = ({ children, config })
391
685
  setClient(null);
392
686
  completedBootstrapKeyRef.current = '';
393
687
  inflightBootstrapKeyRef.current = '';
394
- setReconnectAttempts(0);
395
688
  setProviderBootstrapStatus('idle');
396
689
  await clearPersistedState();
397
690
  });
@@ -418,6 +711,18 @@ export const useXmppClient = () => {
418
711
  if (!context) {
419
712
  throw new Error('useXmppClient must be used within an XmppProvider');
420
713
  }
714
+ if (!context.client) {
715
+ const candidates: Array<XmppClient | null | undefined> = [
716
+ getGlobalXmppClient(),
717
+ store.getState()?.chatSettingStore?.client as XmppClient | undefined,
718
+ ];
719
+ const live = candidates.find(
720
+ (c) => c && (c as any).status === 'online'
721
+ );
722
+ if (live) {
723
+ return { ...context, client: live };
724
+ }
725
+ }
421
726
  return context;
422
727
  };
423
728
 
@@ -2,6 +2,7 @@
2
2
 
3
3
  import { AppDispatch } from '../roomStore';
4
4
  import { setCurrentRoom } from '../roomStore/roomsSlice';
5
+ import { normalizeRoomJid } from './normalizeRoomJid';
5
6
 
6
7
  interface XmppConfig {
7
8
  xmppSettings?: {
@@ -25,15 +26,8 @@ export const chatAutoEnterer = ({
25
26
  console.log('🟡 chatAutoEnterer: Called', { roomJID, wasAutoSelected });
26
27
 
27
28
  if (roomJID) {
28
- // If roomJID is already a full JID, use it directly
29
- // Otherwise, construct it from chatId if needed
30
29
  const conferenceDomain = config.xmppSettings?.conference ?? '';
31
- let finalRoomJID = roomJID;
32
-
33
- if (!roomJID.includes('@') && conferenceDomain) {
34
- finalRoomJID = `${roomJID}@${conferenceDomain}`;
35
- console.log('🟡 chatAutoEnterer: Constructed roomJID', finalRoomJID);
36
- }
30
+ const finalRoomJID = normalizeRoomJid(roomJID, conferenceDomain);
37
31
 
38
32
  console.log('🟡 chatAutoEnterer: Setting current room', finalRoomJID);
39
33
  dispatch(setCurrentRoom({ roomJID: finalRoomJID }));
@@ -25,7 +25,10 @@ interface QueueItem {
25
25
  }
26
26
 
27
27
  const DEFAULT_CONCURRENCY = 3;
28
- const DEFAULT_PAGE_SIZE = 10;
28
+ // Fetch the most recent ~20 on re-entry (mirrors web). A larger first page
29
+ // also means more id-overlap with cache, so the merge in
30
+ // `mergeHistoryIntoCache` rarely has to fall back to clear-and-replace.
31
+ const DEFAULT_PAGE_SIZE = 20;
29
32
  const DEFAULT_RETRY_LIMIT = 2;
30
33
 
31
34
  const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
@@ -29,10 +29,21 @@ export function insertMessageWithDelimiter(
29
29
  );
30
30
 
31
31
  if (existingIndex !== -1) {
32
- roomMessages[existingIndex] = deepMerge(
33
- { ...roomMessages[existingIndex] },
34
- { ...message, pending: false }
35
- );
32
+ const existing = roomMessages[existingIndex];
33
+ // Preserve the sender identity captured on first insert. The MUC echo
34
+ // parses user.id from the stanza's `from`/senderJID (the room nick or
35
+ // bare-jid local part), which often differs from the optimistic
36
+ // sender id (xmppUsername / walletAddress) set when WE sent it.
37
+ // deepMerge would overwrite it, flipping an own message to the "other"
38
+ // side (rendered on the left, "as if someone else wrote it"). Keep the
39
+ // original id so ownership (user.id === self) stays correct.
40
+ const preservedUserId =
41
+ (existing as any)?.user?.id || (message as any)?.user?.id;
42
+ const merged = deepMerge({ ...existing }, { ...message, pending: false });
43
+ if (merged.user && preservedUserId) {
44
+ merged.user.id = preservedUserId;
45
+ }
46
+ roomMessages[existingIndex] = merged;
36
47
  return;
37
48
  }
38
49
 
@@ -60,25 +71,21 @@ export function insertMessageWithDelimiter(
60
71
  isDateAfter(msg.date?.toString() ?? '', lastViewedTimestamp.toString())
61
72
  );
62
73
 
63
- if (delimiterIndex !== -1) {
64
- roomMessages.splice(delimiterIndex, 0, {
65
- id: 'delimiter-new',
66
- user: {
74
+ if (delimiterIndex !== -1) {
75
+ roomMessages.splice(delimiterIndex, 0, {
76
+ id: 'delimiter-new',
77
+ user: {
67
78
  id: 'system',
68
79
  name: undefined,
69
80
  token: '',
70
81
  refreshToken: '',
71
82
  },
72
83
  date: new Date().toString(),
73
- body: 'New Messages',
74
- roomJid: '',
75
- });
76
-
77
- if (lastViewedTimestamp.toString() === '0') {
78
- roomMessages.splice(delimiterIndex, 1);
84
+ body: 'New Messages',
85
+ roomJid: '',
86
+ });
79
87
  }
80
88
  }
81
- }
82
89
  } else if (
83
90
  isDateBefore(newMessageDate.toString(), firstMessage?.date?.toString() ?? '')
84
91
  ) {