@ethora/chat-component-rn 26.5.7 → 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 (371) hide show
  1. package/CHANGELOG.md +133 -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/ChatRoom.js +64 -38
  9. package/lib/commonjs/components/MainComponents/ChatRoom.js.map +1 -1
  10. package/lib/commonjs/components/MainComponents/ChatWrapper.js +57 -13
  11. package/lib/commonjs/components/MainComponents/ChatWrapper.js.map +1 -1
  12. package/lib/commonjs/components/MainComponents/MediaMessage.js +4 -2
  13. package/lib/commonjs/components/MainComponents/MediaMessage.js.map +1 -1
  14. package/lib/commonjs/components/MainComponents/MessageContainer.js +2 -2
  15. package/lib/commonjs/components/MainComponents/MessageContainer.js.map +1 -1
  16. package/lib/commonjs/components/MainComponents/MessageList.js +165 -28
  17. package/lib/commonjs/components/MainComponents/MessageList.js.map +1 -1
  18. package/lib/commonjs/components/MainComponents/ReduxWrapper.js +17 -8
  19. package/lib/commonjs/components/MainComponents/ReduxWrapper.js.map +1 -1
  20. package/lib/commonjs/components/MainComponents/SystemMessage.js +34 -22
  21. package/lib/commonjs/components/MainComponents/SystemMessage.js.map +1 -1
  22. package/lib/commonjs/components/MessageBubble/Message.js +84 -39
  23. package/lib/commonjs/components/MessageBubble/Message.js.map +1 -1
  24. package/lib/commonjs/components/MessageBubble/MessageInteractions.js +82 -22
  25. package/lib/commonjs/components/MessageBubble/MessageInteractions.js.map +1 -1
  26. package/lib/commonjs/components/Modals/AttachSheet/AttachSheet.js +16 -4
  27. package/lib/commonjs/components/Modals/AttachSheet/AttachSheet.js.map +1 -1
  28. package/lib/commonjs/components/Modals/FilePreviewModal/DocumentViewer.js +104 -0
  29. package/lib/commonjs/components/Modals/FilePreviewModal/DocumentViewer.js.map +1 -0
  30. package/lib/commonjs/components/Modals/FilePreviewModal/FilePreviewModal.js +140 -24
  31. package/lib/commonjs/components/Modals/FilePreviewModal/FilePreviewModal.js.map +1 -1
  32. package/lib/commonjs/components/Modals/FilePreviewModal/PdfView.js +29 -1
  33. package/lib/commonjs/components/Modals/FilePreviewModal/PdfView.js.map +1 -1
  34. package/lib/commonjs/components/Modals/Modal/Modal.js +5 -1
  35. package/lib/commonjs/components/Modals/Modal/Modal.js.map +1 -1
  36. package/lib/commonjs/components/Modals/ModalWrapper/ModalWrapper.js +77 -47
  37. package/lib/commonjs/components/Modals/ModalWrapper/ModalWrapper.js.map +1 -1
  38. package/lib/commonjs/components/Modals/UserProfileModal/UserProfileModal.js +3 -3
  39. package/lib/commonjs/components/Modals/UserProfileModal/UserProfileModal.js.map +1 -1
  40. package/lib/commonjs/components/Modals/styledModalComponents.js +4 -9
  41. package/lib/commonjs/components/Modals/styledModalComponents.js.map +1 -1
  42. package/lib/commonjs/components/styled/MediaFilePreview.js +47 -28
  43. package/lib/commonjs/components/styled/MediaFilePreview.js.map +1 -1
  44. package/lib/commonjs/components/styled/MessageImage.js +49 -9
  45. package/lib/commonjs/components/styled/MessageImage.js.map +1 -1
  46. package/lib/commonjs/components/styled/NewMessageLabel.js +8 -6
  47. package/lib/commonjs/components/styled/NewMessageLabel.js.map +1 -1
  48. package/lib/commonjs/components/styled/SendInput.js +49 -27
  49. package/lib/commonjs/components/styled/SendInput.js.map +1 -1
  50. package/lib/commonjs/components/styled/StyledInputComponents/MediaComponents.js +14 -4
  51. package/lib/commonjs/components/styled/StyledInputComponents/MediaComponents.js.map +1 -1
  52. package/lib/commonjs/components/styled/StyledInputComponents/StyledInputComponents.js +7 -2
  53. package/lib/commonjs/components/styled/StyledInputComponents/StyledInputComponents.js.map +1 -1
  54. package/lib/commonjs/components/styled/UnsupportedType.js +5 -1
  55. package/lib/commonjs/components/styled/UnsupportedType.js.map +1 -1
  56. package/lib/commonjs/components/styled/VideoMessage.js +60 -14
  57. package/lib/commonjs/components/styled/VideoMessage.js.map +1 -1
  58. package/lib/commonjs/context/MessageNotificationContext.js +7 -7
  59. package/lib/commonjs/context/MessageNotificationContext.js.map +1 -1
  60. package/lib/commonjs/context/xmppProvider.js +244 -32
  61. package/lib/commonjs/context/xmppProvider.js.map +1 -1
  62. package/lib/commonjs/helpers/chatAutoEnterer.js +2 -7
  63. package/lib/commonjs/helpers/chatAutoEnterer.js.map +1 -1
  64. package/lib/commonjs/helpers/historyPreloadScheduler.js +4 -1
  65. package/lib/commonjs/helpers/historyPreloadScheduler.js.map +1 -1
  66. package/lib/commonjs/helpers/insertMessageWithDelimiter.js +15 -5
  67. package/lib/commonjs/helpers/insertMessageWithDelimiter.js.map +1 -1
  68. package/lib/commonjs/helpers/keyboardLayout.js +18 -0
  69. package/lib/commonjs/helpers/keyboardLayout.js.map +1 -0
  70. package/lib/commonjs/helpers/mediaDimensions.js +51 -0
  71. package/lib/commonjs/helpers/mediaDimensions.js.map +1 -0
  72. package/lib/commonjs/helpers/normalizeRoomJid.js +20 -0
  73. package/lib/commonjs/helpers/normalizeRoomJid.js.map +1 -0
  74. package/lib/commonjs/hooks/useChatRoomFocus.js +87 -0
  75. package/lib/commonjs/hooks/useChatRoomFocus.js.map +1 -0
  76. package/lib/commonjs/hooks/useHeapSender.js +1 -1
  77. package/lib/commonjs/hooks/useHeapSender.js.map +1 -1
  78. package/lib/commonjs/hooks/useLogout.js +3 -3
  79. package/lib/commonjs/hooks/useLogout.js.map +1 -1
  80. package/lib/commonjs/hooks/usePushNotifications.js +6 -2
  81. package/lib/commonjs/hooks/usePushNotifications.js.map +1 -1
  82. package/lib/commonjs/hooks/useRoomState.js +2 -0
  83. package/lib/commonjs/hooks/useRoomState.js.map +1 -1
  84. package/lib/commonjs/hooks/useSendMessage.js +201 -10
  85. package/lib/commonjs/hooks/useSendMessage.js.map +1 -1
  86. package/lib/commonjs/hooks/useUnreadMessagesCounter.js +12 -14
  87. package/lib/commonjs/hooks/useUnreadMessagesCounter.js.map +1 -1
  88. package/lib/commonjs/main.js +7 -0
  89. package/lib/commonjs/main.js.map +1 -1
  90. package/lib/commonjs/networking/api-requests/auth.api.js +67 -0
  91. package/lib/commonjs/networking/api-requests/auth.api.js.map +1 -1
  92. package/lib/commonjs/networking/apiClient.js +2 -1
  93. package/lib/commonjs/networking/apiClient.js.map +1 -1
  94. package/lib/commonjs/networking/outboundQueue.js +92 -0
  95. package/lib/commonjs/networking/outboundQueue.js.map +1 -0
  96. package/lib/commonjs/networking/xmpp/createTimeoutPromise.xmpp.js +4 -1
  97. package/lib/commonjs/networking/xmpp/createTimeoutPromise.xmpp.js.map +1 -1
  98. package/lib/commonjs/networking/xmpp/flushLastViewedToPrivateStore.js +7 -8
  99. package/lib/commonjs/networking/xmpp/flushLastViewedToPrivateStore.js.map +1 -1
  100. package/lib/commonjs/networking/xmpp/getRoomsPaged.xmpp.js +26 -3
  101. package/lib/commonjs/networking/xmpp/getRoomsPaged.xmpp.js.map +1 -1
  102. package/lib/commonjs/networking/xmpp/presenceInRoom.xmpp.js +26 -8
  103. package/lib/commonjs/networking/xmpp/presenceInRoom.xmpp.js.map +1 -1
  104. package/lib/commonjs/networking/xmpp/sendTextMessage.xmpp.js +2 -1
  105. package/lib/commonjs/networking/xmpp/sendTextMessage.xmpp.js.map +1 -1
  106. package/lib/commonjs/networking/xmppClient.js +294 -35
  107. package/lib/commonjs/networking/xmppClient.js.map +1 -1
  108. package/lib/commonjs/roomStore/Middleware/unreadMidlleware.js +22 -6
  109. package/lib/commonjs/roomStore/Middleware/unreadMidlleware.js.map +1 -1
  110. package/lib/commonjs/roomStore/roomsSlice.js +157 -23
  111. package/lib/commonjs/roomStore/roomsSlice.js.map +1 -1
  112. package/lib/commonjs/utils/installPromiseRejectionTracker.js +83 -0
  113. package/lib/commonjs/utils/installPromiseRejectionTracker.js.map +1 -0
  114. package/lib/commonjs/utils/resendMessage.js +1 -1
  115. package/lib/commonjs/utils/resendMessage.js.map +1 -1
  116. package/lib/module/assets/icons.js +5 -1
  117. package/lib/module/assets/icons.js.map +1 -1
  118. package/lib/module/components/ContextMenu/ContextMenuComponents.js +11 -5
  119. package/lib/module/components/ContextMenu/ContextMenuComponents.js.map +1 -1
  120. package/lib/module/components/MainComponents/ChatRoom.js +65 -39
  121. package/lib/module/components/MainComponents/ChatRoom.js.map +1 -1
  122. package/lib/module/components/MainComponents/ChatWrapper.js +57 -13
  123. package/lib/module/components/MainComponents/ChatWrapper.js.map +1 -1
  124. package/lib/module/components/MainComponents/MediaMessage.js +4 -2
  125. package/lib/module/components/MainComponents/MediaMessage.js.map +1 -1
  126. package/lib/module/components/MainComponents/MessageContainer.js +2 -2
  127. package/lib/module/components/MainComponents/MessageContainer.js.map +1 -1
  128. package/lib/module/components/MainComponents/MessageList.js +166 -29
  129. package/lib/module/components/MainComponents/MessageList.js.map +1 -1
  130. package/lib/module/components/MainComponents/ReduxWrapper.js +18 -8
  131. package/lib/module/components/MainComponents/ReduxWrapper.js.map +1 -1
  132. package/lib/module/components/MainComponents/SystemMessage.js +33 -21
  133. package/lib/module/components/MainComponents/SystemMessage.js.map +1 -1
  134. package/lib/module/components/MessageBubble/Message.js +85 -40
  135. package/lib/module/components/MessageBubble/Message.js.map +1 -1
  136. package/lib/module/components/MessageBubble/MessageInteractions.js +84 -24
  137. package/lib/module/components/MessageBubble/MessageInteractions.js.map +1 -1
  138. package/lib/module/components/Modals/AttachSheet/AttachSheet.js +15 -3
  139. package/lib/module/components/Modals/AttachSheet/AttachSheet.js.map +1 -1
  140. package/lib/module/components/Modals/FilePreviewModal/DocumentViewer.js +99 -0
  141. package/lib/module/components/Modals/FilePreviewModal/DocumentViewer.js.map +1 -0
  142. package/lib/module/components/Modals/FilePreviewModal/FilePreviewModal.js +143 -25
  143. package/lib/module/components/Modals/FilePreviewModal/FilePreviewModal.js.map +1 -1
  144. package/lib/module/components/Modals/FilePreviewModal/PdfView.js +31 -3
  145. package/lib/module/components/Modals/FilePreviewModal/PdfView.js.map +1 -1
  146. package/lib/module/components/Modals/Modal/Modal.js +5 -1
  147. package/lib/module/components/Modals/Modal/Modal.js.map +1 -1
  148. package/lib/module/components/Modals/ModalWrapper/ModalWrapper.js +79 -49
  149. package/lib/module/components/Modals/ModalWrapper/ModalWrapper.js.map +1 -1
  150. package/lib/module/components/Modals/UserProfileModal/UserProfileModal.js +3 -3
  151. package/lib/module/components/Modals/UserProfileModal/UserProfileModal.js.map +1 -1
  152. package/lib/module/components/Modals/styledModalComponents.js +4 -9
  153. package/lib/module/components/Modals/styledModalComponents.js.map +1 -1
  154. package/lib/module/components/styled/MediaFilePreview.js +47 -28
  155. package/lib/module/components/styled/MediaFilePreview.js.map +1 -1
  156. package/lib/module/components/styled/MessageImage.js +51 -11
  157. package/lib/module/components/styled/MessageImage.js.map +1 -1
  158. package/lib/module/components/styled/NewMessageLabel.js +8 -6
  159. package/lib/module/components/styled/NewMessageLabel.js.map +1 -1
  160. package/lib/module/components/styled/SendInput.js +50 -28
  161. package/lib/module/components/styled/SendInput.js.map +1 -1
  162. package/lib/module/components/styled/StyledInputComponents/MediaComponents.js +14 -4
  163. package/lib/module/components/styled/StyledInputComponents/MediaComponents.js.map +1 -1
  164. package/lib/module/components/styled/StyledInputComponents/StyledInputComponents.js +7 -2
  165. package/lib/module/components/styled/StyledInputComponents/StyledInputComponents.js.map +1 -1
  166. package/lib/module/components/styled/UnsupportedType.js +5 -1
  167. package/lib/module/components/styled/UnsupportedType.js.map +1 -1
  168. package/lib/module/components/styled/VideoMessage.js +61 -15
  169. package/lib/module/components/styled/VideoMessage.js.map +1 -1
  170. package/lib/module/context/MessageNotificationContext.js +7 -7
  171. package/lib/module/context/MessageNotificationContext.js.map +1 -1
  172. package/lib/module/context/xmppProvider.js +246 -34
  173. package/lib/module/context/xmppProvider.js.map +1 -1
  174. package/lib/module/helpers/chatAutoEnterer.js +2 -7
  175. package/lib/module/helpers/chatAutoEnterer.js.map +1 -1
  176. package/lib/module/helpers/historyPreloadScheduler.js +4 -1
  177. package/lib/module/helpers/historyPreloadScheduler.js.map +1 -1
  178. package/lib/module/helpers/insertMessageWithDelimiter.js +15 -5
  179. package/lib/module/helpers/insertMessageWithDelimiter.js.map +1 -1
  180. package/lib/module/helpers/keyboardLayout.js +12 -0
  181. package/lib/module/helpers/keyboardLayout.js.map +1 -0
  182. package/lib/module/helpers/mediaDimensions.js +46 -0
  183. package/lib/module/helpers/mediaDimensions.js.map +1 -0
  184. package/lib/module/helpers/normalizeRoomJid.js +15 -0
  185. package/lib/module/helpers/normalizeRoomJid.js.map +1 -0
  186. package/lib/module/hooks/useChatRoomFocus.js +82 -0
  187. package/lib/module/hooks/useChatRoomFocus.js.map +1 -0
  188. package/lib/module/hooks/useHeapSender.js +1 -1
  189. package/lib/module/hooks/useHeapSender.js.map +1 -1
  190. package/lib/module/hooks/useLogout.js +3 -3
  191. package/lib/module/hooks/useLogout.js.map +1 -1
  192. package/lib/module/hooks/usePushNotifications.js +6 -2
  193. package/lib/module/hooks/usePushNotifications.js.map +1 -1
  194. package/lib/module/hooks/useRoomState.js +2 -0
  195. package/lib/module/hooks/useRoomState.js.map +1 -1
  196. package/lib/module/hooks/useSendMessage.js +203 -12
  197. package/lib/module/hooks/useSendMessage.js.map +1 -1
  198. package/lib/module/hooks/useUnreadMessagesCounter.js +12 -14
  199. package/lib/module/hooks/useUnreadMessagesCounter.js.map +1 -1
  200. package/lib/module/main.js +1 -0
  201. package/lib/module/main.js.map +1 -1
  202. package/lib/module/networking/api-requests/auth.api.js +67 -1
  203. package/lib/module/networking/api-requests/auth.api.js.map +1 -1
  204. package/lib/module/networking/apiClient.js +2 -1
  205. package/lib/module/networking/apiClient.js.map +1 -1
  206. package/lib/module/networking/outboundQueue.js +84 -0
  207. package/lib/module/networking/outboundQueue.js.map +1 -0
  208. package/lib/module/networking/xmpp/createTimeoutPromise.xmpp.js +4 -1
  209. package/lib/module/networking/xmpp/createTimeoutPromise.xmpp.js.map +1 -1
  210. package/lib/module/networking/xmpp/flushLastViewedToPrivateStore.js +7 -8
  211. package/lib/module/networking/xmpp/flushLastViewedToPrivateStore.js.map +1 -1
  212. package/lib/module/networking/xmpp/getRoomsPaged.xmpp.js +26 -3
  213. package/lib/module/networking/xmpp/getRoomsPaged.xmpp.js.map +1 -1
  214. package/lib/module/networking/xmpp/presenceInRoom.xmpp.js +26 -8
  215. package/lib/module/networking/xmpp/presenceInRoom.xmpp.js.map +1 -1
  216. package/lib/module/networking/xmpp/sendTextMessage.xmpp.js +2 -1
  217. package/lib/module/networking/xmpp/sendTextMessage.xmpp.js.map +1 -1
  218. package/lib/module/networking/xmppClient.js +294 -35
  219. package/lib/module/networking/xmppClient.js.map +1 -1
  220. package/lib/module/roomStore/Middleware/unreadMidlleware.js +22 -6
  221. package/lib/module/roomStore/Middleware/unreadMidlleware.js.map +1 -1
  222. package/lib/module/roomStore/roomsSlice.js +154 -22
  223. package/lib/module/roomStore/roomsSlice.js.map +1 -1
  224. package/lib/module/utils/installPromiseRejectionTracker.js +78 -0
  225. package/lib/module/utils/installPromiseRejectionTracker.js.map +1 -0
  226. package/lib/module/utils/resendMessage.js +1 -1
  227. package/lib/module/utils/resendMessage.js.map +1 -1
  228. package/lib/typescript/assets/icons.d.ts +8 -4
  229. package/lib/typescript/assets/icons.d.ts.map +1 -1
  230. package/lib/typescript/components/ContextMenu/ContextMenuComponents.d.ts.map +1 -1
  231. package/lib/typescript/components/MainComponents/ChatRoom.d.ts.map +1 -1
  232. package/lib/typescript/components/MainComponents/ChatWrapper.d.ts.map +1 -1
  233. package/lib/typescript/components/MainComponents/MediaMessage.d.ts +1 -0
  234. package/lib/typescript/components/MainComponents/MediaMessage.d.ts.map +1 -1
  235. package/lib/typescript/components/MainComponents/MessageContainer.d.ts.map +1 -1
  236. package/lib/typescript/components/MainComponents/MessageList.d.ts.map +1 -1
  237. package/lib/typescript/components/MainComponents/ReduxWrapper.d.ts +1 -0
  238. package/lib/typescript/components/MainComponents/ReduxWrapper.d.ts.map +1 -1
  239. package/lib/typescript/components/MainComponents/SystemMessage.d.ts +0 -7
  240. package/lib/typescript/components/MainComponents/SystemMessage.d.ts.map +1 -1
  241. package/lib/typescript/components/MessageBubble/Message.d.ts.map +1 -1
  242. package/lib/typescript/components/MessageBubble/MessageInteractions.d.ts +4 -2
  243. package/lib/typescript/components/MessageBubble/MessageInteractions.d.ts.map +1 -1
  244. package/lib/typescript/components/Modals/AttachSheet/AttachSheet.d.ts.map +1 -1
  245. package/lib/typescript/components/Modals/FilePreviewModal/DocumentViewer.d.ts +29 -0
  246. package/lib/typescript/components/Modals/FilePreviewModal/DocumentViewer.d.ts.map +1 -0
  247. package/lib/typescript/components/Modals/FilePreviewModal/FilePreviewModal.d.ts +0 -1
  248. package/lib/typescript/components/Modals/FilePreviewModal/FilePreviewModal.d.ts.map +1 -1
  249. package/lib/typescript/components/Modals/FilePreviewModal/PdfView.d.ts.map +1 -1
  250. package/lib/typescript/components/Modals/Modal/Modal.d.ts.map +1 -1
  251. package/lib/typescript/components/Modals/ModalWrapper/ModalWrapper.d.ts.map +1 -1
  252. package/lib/typescript/components/Modals/UserProfileModal/UserProfileModal.d.ts.map +1 -1
  253. package/lib/typescript/components/Modals/styledModalComponents.d.ts.map +1 -1
  254. package/lib/typescript/components/styled/MediaFilePreview.d.ts.map +1 -1
  255. package/lib/typescript/components/styled/MessageImage.d.ts.map +1 -1
  256. package/lib/typescript/components/styled/NewMessageLabel.d.ts +1 -5
  257. package/lib/typescript/components/styled/NewMessageLabel.d.ts.map +1 -1
  258. package/lib/typescript/components/styled/SendInput.d.ts.map +1 -1
  259. package/lib/typescript/components/styled/StyledInputComponents/MediaComponents.d.ts +9 -1
  260. package/lib/typescript/components/styled/StyledInputComponents/MediaComponents.d.ts.map +1 -1
  261. package/lib/typescript/components/styled/StyledInputComponents/StyledInputComponents.d.ts.map +1 -1
  262. package/lib/typescript/components/styled/UnsupportedType.d.ts +1 -0
  263. package/lib/typescript/components/styled/UnsupportedType.d.ts.map +1 -1
  264. package/lib/typescript/components/styled/VideoMessage.d.ts.map +1 -1
  265. package/lib/typescript/context/xmppProvider.d.ts +1 -0
  266. package/lib/typescript/context/xmppProvider.d.ts.map +1 -1
  267. package/lib/typescript/helpers/chatAutoEnterer.d.ts.map +1 -1
  268. package/lib/typescript/helpers/historyPreloadScheduler.d.ts.map +1 -1
  269. package/lib/typescript/helpers/insertMessageWithDelimiter.d.ts.map +1 -1
  270. package/lib/typescript/helpers/keyboardLayout.d.ts +9 -0
  271. package/lib/typescript/helpers/keyboardLayout.d.ts.map +1 -0
  272. package/lib/typescript/helpers/mediaDimensions.d.ts +9 -0
  273. package/lib/typescript/helpers/mediaDimensions.d.ts.map +1 -0
  274. package/lib/typescript/helpers/normalizeRoomJid.d.ts +2 -0
  275. package/lib/typescript/helpers/normalizeRoomJid.d.ts.map +1 -0
  276. package/lib/typescript/hooks/useChatRoomFocus.d.ts +42 -0
  277. package/lib/typescript/hooks/useChatRoomFocus.d.ts.map +1 -0
  278. package/lib/typescript/hooks/useHeapSender.d.ts.map +1 -1
  279. package/lib/typescript/hooks/usePushNotifications.d.ts.map +1 -1
  280. package/lib/typescript/hooks/useRoomState.d.ts +1 -0
  281. package/lib/typescript/hooks/useRoomState.d.ts.map +1 -1
  282. package/lib/typescript/hooks/useSendMessage.d.ts.map +1 -1
  283. package/lib/typescript/hooks/useUnreadMessagesCounter.d.ts.map +1 -1
  284. package/lib/typescript/main.d.ts +1 -0
  285. package/lib/typescript/main.d.ts.map +1 -1
  286. package/lib/typescript/networking/api-requests/auth.api.d.ts +3 -0
  287. package/lib/typescript/networking/api-requests/auth.api.d.ts.map +1 -1
  288. package/lib/typescript/networking/apiClient.d.ts.map +1 -1
  289. package/lib/typescript/networking/outboundQueue.d.ts +57 -0
  290. package/lib/typescript/networking/outboundQueue.d.ts.map +1 -0
  291. package/lib/typescript/networking/xmpp/createTimeoutPromise.xmpp.d.ts.map +1 -1
  292. package/lib/typescript/networking/xmpp/flushLastViewedToPrivateStore.d.ts +1 -1
  293. package/lib/typescript/networking/xmpp/flushLastViewedToPrivateStore.d.ts.map +1 -1
  294. package/lib/typescript/networking/xmpp/getRoomsPaged.xmpp.d.ts +1 -1
  295. package/lib/typescript/networking/xmpp/getRoomsPaged.xmpp.d.ts.map +1 -1
  296. package/lib/typescript/networking/xmpp/presenceInRoom.xmpp.d.ts.map +1 -1
  297. package/lib/typescript/networking/xmpp/sendTextMessage.xmpp.d.ts.map +1 -1
  298. package/lib/typescript/networking/xmppClient.d.ts +33 -4
  299. package/lib/typescript/networking/xmppClient.d.ts.map +1 -1
  300. package/lib/typescript/roomStore/Middleware/unreadMidlleware.d.ts.map +1 -1
  301. package/lib/typescript/roomStore/roomsSlice.d.ts +8 -1
  302. package/lib/typescript/roomStore/roomsSlice.d.ts.map +1 -1
  303. package/lib/typescript/types/models/media.model.d.ts +1 -0
  304. package/lib/typescript/types/models/media.model.d.ts.map +1 -1
  305. package/lib/typescript/types/models/xmpp.model.d.ts +1 -1
  306. package/lib/typescript/types/models/xmpp.model.d.ts.map +1 -1
  307. package/lib/typescript/types/types.d.ts +4 -0
  308. package/lib/typescript/types/types.d.ts.map +1 -1
  309. package/lib/typescript/utils/installPromiseRejectionTracker.d.ts +20 -0
  310. package/lib/typescript/utils/installPromiseRejectionTracker.d.ts.map +1 -0
  311. package/lib/typescript/utils/resendMessage.d.ts.map +1 -1
  312. package/package.json +18 -2
  313. package/src/assets/icons.tsx +23 -14
  314. package/src/components/ContextMenu/ContextMenuComponents.tsx +10 -5
  315. package/src/components/MainComponents/ChatRoom.tsx +81 -66
  316. package/src/components/MainComponents/ChatWrapper.tsx +99 -21
  317. package/src/components/MainComponents/MediaMessage.tsx +3 -0
  318. package/src/components/MainComponents/MessageContainer.tsx +4 -2
  319. package/src/components/MainComponents/MessageList.tsx +175 -28
  320. package/src/components/MainComponents/ReduxWrapper.tsx +19 -9
  321. package/src/components/MainComponents/SystemMessage.tsx +38 -26
  322. package/src/components/MessageBubble/Message.tsx +91 -62
  323. package/src/components/MessageBubble/MessageInteractions.tsx +98 -29
  324. package/src/components/Modals/AttachSheet/AttachSheet.tsx +17 -3
  325. package/src/components/Modals/FilePreviewModal/DocumentViewer.tsx +93 -0
  326. package/src/components/Modals/FilePreviewModal/FilePreviewModal.tsx +152 -25
  327. package/src/components/Modals/FilePreviewModal/PdfView.tsx +32 -4
  328. package/src/components/Modals/Modal/Modal.tsx +4 -1
  329. package/src/components/Modals/ModalWrapper/ModalWrapper.tsx +70 -58
  330. package/src/components/Modals/UserProfileModal/UserProfileModal.tsx +19 -15
  331. package/src/components/Modals/styledModalComponents.tsx +4 -9
  332. package/src/components/styled/MediaFilePreview.tsx +46 -26
  333. package/src/components/styled/MessageImage.tsx +58 -13
  334. package/src/components/styled/NewMessageLabel.tsx +8 -5
  335. package/src/components/styled/SendInput.tsx +64 -29
  336. package/src/components/styled/StyledInputComponents/MediaComponents.tsx +15 -5
  337. package/src/components/styled/StyledInputComponents/StyledInputComponents.tsx +7 -2
  338. package/src/components/styled/UnsupportedType.tsx +7 -1
  339. package/src/components/styled/VideoMessage.tsx +59 -11
  340. package/src/context/MessageNotificationContext.tsx +8 -8
  341. package/src/context/xmppProvider.tsx +247 -30
  342. package/src/helpers/chatAutoEnterer.ts +2 -8
  343. package/src/helpers/historyPreloadScheduler.ts +4 -1
  344. package/src/helpers/insertMessageWithDelimiter.ts +22 -15
  345. package/src/helpers/keyboardLayout.ts +18 -0
  346. package/src/helpers/mediaDimensions.ts +47 -0
  347. package/src/helpers/normalizeRoomJid.ts +6 -0
  348. package/src/hooks/useChatRoomFocus.ts +95 -0
  349. package/src/hooks/useHeapSender.tsx +2 -1
  350. package/src/hooks/useLogout.tsx +3 -3
  351. package/src/hooks/usePushNotifications.ts +21 -8
  352. package/src/hooks/useRoomState.tsx +4 -0
  353. package/src/hooks/useSendMessage.tsx +258 -13
  354. package/src/hooks/useUnreadMessagesCounter.ts +13 -16
  355. package/src/main.ts +1 -0
  356. package/src/networking/api-requests/auth.api.ts +55 -1
  357. package/src/networking/apiClient.ts +2 -1
  358. package/src/networking/outboundQueue.ts +98 -0
  359. package/src/networking/xmpp/createTimeoutPromise.xmpp.ts +5 -1
  360. package/src/networking/xmpp/flushLastViewedToPrivateStore.ts +11 -13
  361. package/src/networking/xmpp/getRoomsPaged.xmpp.ts +27 -3
  362. package/src/networking/xmpp/presenceInRoom.xmpp.ts +24 -9
  363. package/src/networking/xmpp/sendTextMessage.xmpp.ts +2 -1
  364. package/src/networking/xmppClient.ts +317 -43
  365. package/src/roomStore/Middleware/unreadMidlleware.tsx +23 -5
  366. package/src/roomStore/roomsSlice.ts +177 -27
  367. package/src/types/models/media.model.ts +3 -0
  368. package/src/types/models/xmpp.model.ts +2 -1
  369. package/src/types/types.ts +15 -0
  370. package/src/utils/installPromiseRejectionTracker.ts +91 -0
  371. package/src/utils/resendMessage.ts +2 -1
@@ -9,6 +9,7 @@ import React, {
9
9
  useState,
10
10
  } from 'react';
11
11
  import { AppState, DeviceEventEmitter } from 'react-native';
12
+ import NetInfo from '@react-native-community/netinfo';
12
13
  import XmppClient, {
13
14
  XmppCredentialsProvider,
14
15
  } from '../networking/xmppClient';
@@ -21,6 +22,7 @@ import {
21
22
  } from '../types/types';
22
23
  import {
23
24
  buildXmppClientKey,
25
+ getGlobalXmppClient,
24
26
  getReusableXmppClientByKey,
25
27
  isXmppClientReusable,
26
28
  setGlobalXmppClient,
@@ -36,7 +38,13 @@ import { getRooms as prefetchRoomsViaRest } from '../networking/api-requests/roo
36
38
  import { allRoomPresences } from '../networking/xmpp/allRoomPresences.xmpp';
37
39
  import { store } from '../roomStore';
38
40
  import { logout, setStoreClient } from '../roomStore/chatSettingsSlice';
39
- import { setLogoutState } from '../roomStore/roomsSlice';
41
+ import {
42
+ setLogoutState,
43
+ setVisibleRoom,
44
+ clearVisibleRoom,
45
+ setLastViewedTimestamp,
46
+ deleteRoomMessage,
47
+ } from '../roomStore/roomsSlice';
40
48
  import { runHistoryPreloadScheduler } from '../helpers/historyPreloadScheduler';
41
49
  import { asyncLocalStorage } from '../hooks/useLocalStorage';
42
50
  import { localStorageConstants } from '../helpers/constants/LOCAL_STORAGE';
@@ -64,15 +72,18 @@ const XmppContext = createContext<XmppContextType | null>(null);
64
72
  interface XmppProviderProps {
65
73
  children: ReactNode;
66
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;
67
79
  }
68
80
 
69
81
  const LOGOUT_EVENT = 'ethora-xmpp-logout';
70
82
 
71
- export const XmppProvider: React.FC<XmppProviderProps> = ({ children, config }) => {
83
+ export const XmppProvider: React.FC<XmppProviderProps> = ({ children, config, isVisible }) => {
72
84
  const [client, setClient] = useState<XmppClient | null>(null);
73
85
  const [providerBootstrapStatus, setProviderBootstrapStatus] =
74
86
  useState<ProviderBootstrapStatus>('idle');
75
- const [reconnectAttempts, setReconnectAttempts] = useState(0);
76
87
 
77
88
  // Track which "init mode" this provider runs in. When config.initBeforeLoad
78
89
  // is true, the provider owns bootstrap and ChatWrapper just waits.
@@ -181,6 +192,10 @@ export const XmppProvider: React.FC<XmppProviderProps> = ({ children, config })
181
192
  // 3. Otherwise create a new client under the init lock to dedup
182
193
  // concurrent calls for the same key.
183
194
  const created = await withXmppClientInitLock(key, async () => {
195
+ const prevGlobal = getGlobalXmppClient();
196
+ if (prevGlobal) {
197
+ try { await prevGlobal.close(); } catch {}
198
+ }
184
199
  const newClient = new XmppClient(username, password, settings);
185
200
  // Wire up the credentialsProvider so SASL `not-authorized`
186
201
  // after idle triggers a refresh chain (jwtLogin →
@@ -200,9 +215,30 @@ export const XmppProvider: React.FC<XmppProviderProps> = ({ children, config })
200
215
  // may have changed since the singleton was created.
201
216
  created.setCredentialsProvider(credentialsProvider);
202
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
+
203
240
  setClient(created);
204
241
  lastCredsRef.current = { username, password, settings };
205
- setReconnectAttempts(0);
206
242
  return created;
207
243
  },
208
244
  [client, config?.xmppSettings, credentialsProvider]
@@ -219,6 +255,30 @@ export const XmppProvider: React.FC<XmppProviderProps> = ({ children, config })
219
255
 
220
256
  const abortController = new AbortController();
221
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
+ };
222
282
 
223
283
  const run = async () => {
224
284
  // Stable bootstrap key — re-run only when these change.
@@ -332,6 +392,8 @@ export const XmppProvider: React.FC<XmppProviderProps> = ({ children, config })
332
392
  store.dispatch(setStoreClient(c));
333
393
  completedBootstrapKeyRef.current = key;
334
394
  inflightBootstrapKeyRef.current = '';
395
+ reachedReady = true;
396
+ clearBudget();
335
397
  setProviderBootstrapStatus('ready');
336
398
  devPushLog('rn', 'initBeforeLoad: READY');
337
399
 
@@ -352,13 +414,34 @@ export const XmppProvider: React.FC<XmppProviderProps> = ({ children, config })
352
414
  roomLimit: qos?.preloadTopKRooms,
353
415
  }).catch((err) => console.warn('History preload scheduler failed', err));
354
416
  } catch (error: any) {
355
- if (cancelled) {return;}
356
- 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)', {
357
439
  message: error?.message,
358
440
  status: error?.response?.status,
359
441
  data: error?.response?.data,
360
442
  });
361
443
  inflightBootstrapKeyRef.current = '';
444
+ clearBudget();
362
445
  setProviderBootstrapStatus('failed');
363
446
  }
364
447
  };
@@ -367,6 +450,7 @@ export const XmppProvider: React.FC<XmppProviderProps> = ({ children, config })
367
450
 
368
451
  return () => {
369
452
  cancelled = true;
453
+ clearBudget();
370
454
  abortController.abort();
371
455
  // Re-running this effect (deps changed or unmount) must release the
372
456
  // in-flight bootstrap key so the next render isn't permanently
@@ -396,29 +480,30 @@ export const XmppProvider: React.FC<XmppProviderProps> = ({ children, config })
396
480
  ]);
397
481
 
398
482
  // -----------------------------------------------------------
399
- // 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).
400
495
  // -----------------------------------------------------------
401
496
  useEffect(() => {
402
497
  if (!client) {return;}
403
- if (client.status !== 'offline') {return;}
404
-
405
- if (reconnectAttempts < 3) {
406
- console.log(`xmpp reconnect attempt ${reconnectAttempts + 1}`);
407
- client.scheduleReconnect();
408
- setReconnectAttempts((prev) => prev + 1);
409
- return;
410
- }
411
-
412
- // After 3 failed reconnects, drop the client and re-init from scratch.
413
- if (lastCredsRef.current) {
414
- const { username, password, settings } = lastCredsRef.current;
415
- setGlobalXmppClient(null);
416
- setClient(null);
417
- initializeClient(username, password, settings).catch((err) =>
418
- console.error('Full reinit failed:', err)
419
- );
420
- }
421
- }, [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]);
422
507
 
423
508
  // -----------------------------------------------------------
424
509
  // AppState → background: flush lastViewedTimestamp into the
@@ -433,22 +518,143 @@ export const XmppProvider: React.FC<XmppProviderProps> = ({ children, config })
433
518
  // entry, or when this build has `disableLastRead`.
434
519
  // -----------------------------------------------------------
435
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;
436
525
  const sub = AppState.addEventListener('change', (next) => {
437
- if (next === 'active') {return;}
438
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):
439
554
  if (!c) {return;}
440
555
  const state = store.getState();
441
556
  const rooms = state.rooms?.rooms;
442
- 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
+ }
443
579
  // Fire-and-forget — we're going to the background and don't
444
580
  // care about the resolution path.
445
- c.flushLastViewedToPrivateStoreStanza(rooms, { activeRoomJID }).catch(
581
+ c.flushLastViewedToPrivateStoreStanza(rooms, { visibleRoomJID }).catch(
446
582
  () => {}
447
583
  );
448
584
  });
449
585
  return () => sub.remove();
450
586
  }, [client]);
451
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
+
452
658
  // -----------------------------------------------------------
453
659
  // Retry-bootstrap listener — fired by ChatWrapper's error modal so
454
660
  // a transient failure (bad endpoint, server hiccup, etc.) can be
@@ -479,7 +685,6 @@ export const XmppProvider: React.FC<XmppProviderProps> = ({ children, config })
479
685
  setClient(null);
480
686
  completedBootstrapKeyRef.current = '';
481
687
  inflightBootstrapKeyRef.current = '';
482
- setReconnectAttempts(0);
483
688
  setProviderBootstrapStatus('idle');
484
689
  await clearPersistedState();
485
690
  });
@@ -506,6 +711,18 @@ export const useXmppClient = () => {
506
711
  if (!context) {
507
712
  throw new Error('useXmppClient must be used within an XmppProvider');
508
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
+ }
509
726
  return context;
510
727
  };
511
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
  ) {
@@ -0,0 +1,18 @@
1
+ type KeyboardLayoutArgs = {
2
+ platform: string;
3
+ configuredOffset?: number;
4
+ bottomInset?: number;
5
+ };
6
+
7
+ export const getKeyboardVerticalOffset = ({
8
+ platform,
9
+ configuredOffset = 0,
10
+ bottomInset = 0,
11
+ }: KeyboardLayoutArgs): number =>
12
+ configuredOffset + (platform === 'ios' ? bottomInset : 0);
13
+
14
+ export const getInputDockPaddingBottom = ({
15
+ platform,
16
+ bottomInset = 0,
17
+ }: Omit<KeyboardLayoutArgs, 'configuredOffset'>): number =>
18
+ platform === 'ios' ? bottomInset : 0;
@@ -0,0 +1,47 @@
1
+ import { Dimensions } from 'react-native';
2
+
3
+ // Shared sizing for inline media bubbles (images + video posters).
4
+ // Goal: show the media UNCROPPED, just scaled, preserving aspect ratio —
5
+ // portrait stays portrait, landscape stays landscape.
6
+ const screenW = Dimensions.get('window').width;
7
+
8
+ // Box the media must fit inside. Width adapts to the screen so it never
9
+ // blows past the bubble's max-width on small phones, but caps on tablets.
10
+ export const MEDIA_MAX_W = Math.min(260, Math.round(screenW * 0.64));
11
+ export const MEDIA_MAX_H = 320;
12
+ // Keep thumbnails from collapsing into a sliver (very wide/tall media).
13
+ const MEDIA_MIN_W = 140;
14
+ const MEDIA_MIN_H = 120;
15
+
16
+ export interface MediaDims {
17
+ width: number;
18
+ height: number;
19
+ }
20
+
21
+ // Neutral 4:3 placeholder used before the natural size is known.
22
+ export const defaultMediaDims = (): MediaDims => ({
23
+ width: MEDIA_MAX_W,
24
+ height: Math.round(MEDIA_MAX_W * 0.75),
25
+ });
26
+
27
+ // Fit (natW × natH) inside the box, preserving aspect ratio. Never
28
+ // upscales beyond the source resolution (avoids blur), but bumps very
29
+ // small media up to a sensible minimum so it stays tappable.
30
+ export const fitMediaDimensions = (natW: number, natH: number): MediaDims => {
31
+ if (!natW || !natH) {
32
+ return defaultMediaDims();
33
+ }
34
+ let scale = Math.min(MEDIA_MAX_W / natW, MEDIA_MAX_H / natH);
35
+ if (scale > 1) {
36
+ scale = 1;
37
+ }
38
+ let width = Math.round(natW * scale);
39
+ let height = Math.round(natH * scale);
40
+
41
+ if (width < MEDIA_MIN_W || height < MEDIA_MIN_H) {
42
+ const up = Math.max(MEDIA_MIN_W / width, MEDIA_MIN_H / height);
43
+ width = Math.min(MEDIA_MAX_W, Math.round(width * up));
44
+ height = Math.min(MEDIA_MAX_H, Math.round(height * up));
45
+ }
46
+ return { width, height };
47
+ };
@@ -0,0 +1,6 @@
1
+ export const normalizeRoomJid = (roomJID: string, conference?: string) => {
2
+ if (!roomJID) {return roomJID;}
3
+ if (roomJID.includes('@')) {return roomJID;}
4
+ if (!conference) {return roomJID;}
5
+ return `${roomJID}@${conference}`;
6
+ };
@@ -0,0 +1,95 @@
1
+ import { useEffect, useRef } from 'react';
2
+ import { useDispatch } from 'react-redux';
3
+ import {
4
+ clearVisibleRoom,
5
+ setCurrentRoom,
6
+ setLastViewedTimestamp,
7
+ setVisibleRoom,
8
+ } from '../roomStore/roomsSlice';
9
+ import { store } from '../roomStore';
10
+
11
+ interface UseChatRoomFocusOptions {
12
+ /** The room JID that the consumer's tab/screen is currently showing. */
13
+ roomJID: string | null | undefined;
14
+ /**
15
+ * Whether the chat tab/screen is currently focused. In tab-based
16
+ * navigators where `<ChatRoom>` never unmounts, the SDK can't tell
17
+ * "user is looking at this tab" from "user is on a different tab"
18
+ * — pass this from `useFocusEffect` (React Navigation) or a similar
19
+ * focus signal so the unread counter clears on focus and starts
20
+ * counting again on blur.
21
+ */
22
+ isFocused: boolean;
23
+ }
24
+
25
+ /**
26
+ * Public hook for consumers embedding `<ChatRoom>` inside a tab-based
27
+ * navigator. Mirrors the mount/unmount unread-tracking that ChatRoom
28
+ * does internally — but driven by an explicit focus signal so the
29
+ * unread counter behaves correctly when the chat tab is not focused
30
+ * but the component is still mounted.
31
+ *
32
+ * Without this hook, `useUnread()` will always return 0 for the chat
33
+ * room because the SDK assumes "mounted == active". With this hook,
34
+ * focus marks the room visible (clearing the badge) and blur stamps
35
+ * `lastViewedTimestamp = Date.now()` so future messages count as unread.
36
+ *
37
+ * Usage with React Navigation:
38
+ *
39
+ * ```tsx
40
+ * import { useIsFocused } from '@react-navigation/native';
41
+ *
42
+ * function ChatTab() {
43
+ * const isFocused = useIsFocused();
44
+ * useChatRoomFocus({ roomJID: 'general@conference.host', isFocused });
45
+ * return <Chat config={...} />;
46
+ * }
47
+ * ```
48
+ *
49
+ * Bug #18-adjacent / unread-tracking bug from sdk-bug-tracker.md.
50
+ */
51
+ export const useChatRoomFocus = ({
52
+ roomJID,
53
+ isFocused,
54
+ }: UseChatRoomFocusOptions) => {
55
+ const dispatch = useDispatch();
56
+ const prevRef = useRef<{ roomJID: string | null; isFocused: boolean }>({
57
+ roomJID: null,
58
+ isFocused: false,
59
+ });
60
+
61
+ const leaveRoom = (jid: string) => {
62
+ const timestamp = Date.now();
63
+ dispatch(setLastViewedTimestamp({ chatJID: jid, timestamp }));
64
+ dispatch(clearVisibleRoom());
65
+ const rooms = store.getState().rooms?.rooms;
66
+ (store.getState().chatSettingStore as any)?.client
67
+ ?.flushLastViewedToPrivateStoreStanza(rooms, { visibleRoomJID: jid })
68
+ .catch(() => {});
69
+ };
70
+
71
+ useEffect(() => {
72
+ const prev = prevRef.current;
73
+ if (prev.isFocused && prev.roomJID && (prev.roomJID !== roomJID || !isFocused)) {
74
+ leaveRoom(prev.roomJID);
75
+ }
76
+
77
+ if (roomJID && isFocused) {
78
+ dispatch(setCurrentRoom({ roomJID }));
79
+ dispatch(setVisibleRoom({ roomJID }));
80
+ }
81
+
82
+ prevRef.current = { roomJID: roomJID || null, isFocused };
83
+ }, [roomJID, isFocused, dispatch]);
84
+
85
+ useEffect(() => {
86
+ return () => {
87
+ const prev = prevRef.current;
88
+ if (prev.isFocused && prev.roomJID) {
89
+ leaveRoom(prev.roomJID);
90
+ }
91
+ };
92
+ }, []);
93
+ };
94
+
95
+ export default useChatRoomFocus;